@gethmy/mcp 2.3.0 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +80 -23
- package/dist/index.js +80 -23
- package/dist/lib/active-learning.js +939 -787
- package/dist/lib/api-client.js +2527 -638
- package/dist/lib/auto-session.js +177 -196
- package/dist/lib/cli.js +34954 -128
- package/dist/lib/config.js +235 -201
- package/dist/lib/consolidation.js +374 -289
- package/dist/lib/context-assembly.js +1265 -838
- package/dist/lib/graph-expansion.js +139 -155
- package/dist/lib/http.js +1917 -130
- package/dist/lib/index.js +29525 -5
- package/dist/lib/lifecycle-maintenance.js +663 -79
- package/dist/lib/memory-cleanup.js +1316 -381
- package/dist/lib/onboard.js +2588 -32
- package/dist/lib/prompt-builder.js +438 -445
- package/dist/lib/remote.js +31733 -143
- package/dist/lib/server.js +29389 -3216
- package/dist/lib/skills.js +315 -132
- package/dist/lib/tui/agents.js +128 -107
- package/dist/lib/tui/docs.js +1590 -687
- package/dist/lib/tui/setup.js +5698 -804
- package/dist/lib/tui/theme.js +183 -86
- package/dist/lib/tui/writer.js +1149 -176
- package/package.json +2 -2
- package/src/api-client.ts +37 -1
- package/src/memory-cleanup.ts +92 -52
- package/src/server.ts +16 -1
- package/dist/lib/__tests__/active-learning.test.js +0 -386
- package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
- package/dist/lib/__tests__/auto-session.test.js +0 -661
- package/dist/lib/__tests__/context-assembly.test.js +0 -362
- package/dist/lib/__tests__/graph-expansion.test.js +0 -150
- package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
- package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
- package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
- package/dist/lib/__tests__/pattern-detection.test.js +0 -295
- package/dist/lib/__tests__/prompt-builder.test.js +0 -418
|
@@ -1,884 +1,1311 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
// Dedicated procedure budget as a fraction of total budget
|
|
19
|
-
const PROCEDURE_BUDGET_FRACTION = 0.15;
|
|
20
|
-
// Tier budget allocation percentages (of remaining budget after procedure reservation)
|
|
21
|
-
const TIER_BUDGET_ALLOCATION = {
|
|
22
|
-
reference: 0.6,
|
|
23
|
-
episode: 0.3,
|
|
24
|
-
draft: 0.1,
|
|
25
|
-
};
|
|
26
|
-
// Minimum guaranteed slots per tier (reduced from 3 to avoid filling context with noise)
|
|
27
|
-
const MIN_REFERENCE_SLOTS = 1;
|
|
28
|
-
// Graph walk configuration
|
|
29
|
-
const GRAPH_WALK_MAX_DEPTH = 1;
|
|
30
|
-
const GRAPH_WALK_MAX_ENTITIES = 10;
|
|
31
|
-
const GRAPH_WALK_MIN_CONFIDENCE = 0.5;
|
|
32
|
-
const GRAPH_WALK_SEED_COUNT = 5;
|
|
33
|
-
// Query expansion configuration
|
|
34
|
-
const MAX_QUERY_VARIATIONS = 4;
|
|
35
|
-
// LLM re-ranking configuration
|
|
36
|
-
const RERANK_CLUSTER_THRESHOLD = 0.05;
|
|
37
|
-
const RERANK_TOP_N = 10;
|
|
38
|
-
const RERANK_MIN_CANDIDATES = 5;
|
|
39
|
-
// Graph walk relation-type bonuses for relevance scoring
|
|
40
|
-
const RELATION_BONUSES = {
|
|
41
|
-
depends_on: 0.15,
|
|
42
|
-
resolved_by: 0.2,
|
|
43
|
-
relates_to: 0.1,
|
|
44
|
-
implements: 0.15,
|
|
45
|
-
blocks: 0.15,
|
|
46
|
-
references: 0.1,
|
|
47
|
-
extends: 0.1,
|
|
48
|
-
caused_by: 0.15,
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
49
17
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fix: ["resolve", "patch", "repair", "correct"],
|
|
60
|
-
deploy: ["deployment", "release", "ship", "publish"],
|
|
61
|
-
test: ["testing", "spec", "assertion", "verify"],
|
|
62
|
-
config: ["configuration", "settings", "setup"],
|
|
63
|
-
db: ["database", "storage", "persistence"],
|
|
64
|
-
database: ["storage", "persistence", "data store"],
|
|
65
|
-
api: ["endpoint", "route", "service"],
|
|
66
|
-
ui: ["frontend", "component", "view"],
|
|
67
|
-
perf: ["performance", "speed", "latency"],
|
|
68
|
-
performance: ["speed", "latency", "optimization"],
|
|
18
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
69
27
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
28
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
+
|
|
31
|
+
// ../memory/dist/schema.js
|
|
32
|
+
var init_schema = () => {};
|
|
33
|
+
|
|
34
|
+
// ../memory/dist/constraints.js
|
|
35
|
+
var init_constraints = __esm(() => {
|
|
36
|
+
init_schema();
|
|
37
|
+
});
|
|
38
|
+
// ../memory/dist/client.js
|
|
39
|
+
var init_client = __esm(() => {
|
|
40
|
+
init_constraints();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ../memory/dist/graph-walk.js
|
|
44
|
+
async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
|
|
45
|
+
const visited = new Set;
|
|
46
|
+
const collectedEntities = [];
|
|
47
|
+
const collectedRelations = [];
|
|
48
|
+
let truncated = false;
|
|
49
|
+
const queue = startIds.map((id) => [id, 0]);
|
|
50
|
+
for (const id of startIds) {
|
|
51
|
+
visited.add(id);
|
|
52
|
+
}
|
|
53
|
+
while (queue.length > 0) {
|
|
54
|
+
const [entityId, depth] = queue.shift();
|
|
55
|
+
if (collectedEntities.length >= maxEntities) {
|
|
56
|
+
truncated = true;
|
|
57
|
+
break;
|
|
99
58
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
59
|
+
if (depth > maxDepth)
|
|
60
|
+
continue;
|
|
61
|
+
try {
|
|
62
|
+
const entityResult = await client.getMemoryEntity(entityId);
|
|
63
|
+
const entity = entityResult.entity;
|
|
64
|
+
if (entity) {
|
|
65
|
+
collectedEntities.push({
|
|
66
|
+
id: entity.id,
|
|
67
|
+
type: entity.type,
|
|
68
|
+
title: entity.title,
|
|
69
|
+
confidence: entity.confidence ?? 1,
|
|
70
|
+
memory_tier: entity.memory_tier || "reference"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (depth >= maxDepth)
|
|
74
|
+
continue;
|
|
75
|
+
const related = await client.getRelatedEntities(entityId);
|
|
76
|
+
for (const raw of related.outgoing || []) {
|
|
77
|
+
const rel = raw;
|
|
78
|
+
const relConfidence = rel.confidence ?? 1;
|
|
79
|
+
if (relConfidence < minConfidence)
|
|
80
|
+
continue;
|
|
81
|
+
const target = rel.target;
|
|
82
|
+
const targetId = target?.id ?? rel.target_id;
|
|
83
|
+
if (targetId && !visited.has(targetId)) {
|
|
84
|
+
visited.add(targetId);
|
|
85
|
+
queue.push([targetId, depth + 1]);
|
|
86
|
+
collectedRelations.push({
|
|
87
|
+
id: rel.id,
|
|
88
|
+
source_id: entityId,
|
|
89
|
+
target_id: targetId,
|
|
90
|
+
relation_type: rel.relation_type,
|
|
91
|
+
confidence: relConfidence
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const raw of related.incoming || []) {
|
|
96
|
+
const rel = raw;
|
|
97
|
+
const relConfidence = rel.confidence ?? 1;
|
|
98
|
+
if (relConfidence < minConfidence)
|
|
99
|
+
continue;
|
|
100
|
+
const source = rel.source;
|
|
101
|
+
const sourceId = source?.id ?? rel.source_id;
|
|
102
|
+
if (sourceId && !visited.has(sourceId)) {
|
|
103
|
+
visited.add(sourceId);
|
|
104
|
+
queue.push([sourceId, depth + 1]);
|
|
105
|
+
collectedRelations.push({
|
|
106
|
+
id: rel.id,
|
|
107
|
+
source_id: sourceId,
|
|
108
|
+
target_id: entityId,
|
|
109
|
+
relation_type: rel.relation_type,
|
|
110
|
+
confidence: relConfidence
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
entities: collectedEntities,
|
|
118
|
+
relations: collectedRelations,
|
|
119
|
+
depth: maxDepth,
|
|
120
|
+
truncated
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ../memory/dist/lifecycle.js
|
|
125
|
+
function computeDecayScore(tier, lastAccessedAt, accessCount) {
|
|
126
|
+
const halfLife = DECAY_HALF_LIVES[tier];
|
|
127
|
+
const now = Date.now();
|
|
128
|
+
let daysSinceAccess = 0;
|
|
129
|
+
if (lastAccessedAt) {
|
|
130
|
+
daysSinceAccess = (now - new Date(lastAccessedAt).getTime()) / (1000 * 60 * 60 * 24);
|
|
131
|
+
}
|
|
132
|
+
const timeDecay = 0.5 ** (daysSinceAccess / halfLife);
|
|
133
|
+
const accessBonus = Math.log10(accessCount + 1) * 0.1;
|
|
134
|
+
const score = Math.min(timeDecay + accessBonus, 1);
|
|
135
|
+
return { score, daysSinceAccess, halfLife, accessBonus };
|
|
136
|
+
}
|
|
137
|
+
function checkPromotion(currentTier, accessCount, confidence, createdAt) {
|
|
138
|
+
const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
|
|
139
|
+
const base = {
|
|
140
|
+
eligible: false,
|
|
141
|
+
targetTier: null,
|
|
142
|
+
reason: null,
|
|
143
|
+
currentTier,
|
|
144
|
+
accessCount,
|
|
145
|
+
confidence,
|
|
146
|
+
ageDays
|
|
147
|
+
};
|
|
148
|
+
if (currentTier === "draft") {
|
|
149
|
+
const rules = PROMOTION_RULES.draftToEpisode;
|
|
150
|
+
if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
|
|
151
|
+
return {
|
|
152
|
+
...base,
|
|
153
|
+
eligible: true,
|
|
154
|
+
targetTier: "episode",
|
|
155
|
+
reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
|
|
156
|
+
};
|
|
109
157
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
158
|
+
}
|
|
159
|
+
if (currentTier === "episode") {
|
|
160
|
+
const rules = PROMOTION_RULES.episodeToReference;
|
|
161
|
+
if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
|
|
162
|
+
return {
|
|
163
|
+
...base,
|
|
164
|
+
eligible: true,
|
|
165
|
+
targetTier: "reference",
|
|
166
|
+
reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
|
|
167
|
+
};
|
|
117
168
|
}
|
|
118
|
-
|
|
169
|
+
}
|
|
170
|
+
return base;
|
|
119
171
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
172
|
+
function evaluateLifecycle(entity) {
|
|
173
|
+
const decay = computeDecayScore(entity.memory_tier, entity.last_accessed_at, entity.access_count);
|
|
174
|
+
const promotion = checkPromotion(entity.memory_tier, entity.access_count, entity.confidence, entity.created_at);
|
|
175
|
+
const shouldArchive = entity.confidence < ARCHIVE_THRESHOLD;
|
|
176
|
+
const archiveReason = shouldArchive ? `Confidence ${entity.confidence} below threshold ${ARCHIVE_THRESHOLD}` : undefined;
|
|
177
|
+
const shouldFlagForReview = decay.daysSinceAccess >= STALE_DAYS && entity.access_count < STALE_MIN_ACCESS;
|
|
178
|
+
const reviewReason = shouldFlagForReview ? `Not accessed in ${Math.round(decay.daysSinceAccess)} days with only ${entity.access_count} accesses` : undefined;
|
|
179
|
+
return {
|
|
180
|
+
decay,
|
|
181
|
+
promotion,
|
|
182
|
+
shouldArchive,
|
|
183
|
+
shouldFlagForReview,
|
|
184
|
+
archiveReason,
|
|
185
|
+
reviewReason
|
|
186
|
+
};
|
|
125
187
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
188
|
+
var DECAY_HALF_LIVES, PROMOTION_RULES, ARCHIVE_THRESHOLD = 0.3, STALE_DAYS = 90, STALE_MIN_ACCESS = 3;
|
|
189
|
+
var init_lifecycle = __esm(() => {
|
|
190
|
+
DECAY_HALF_LIVES = {
|
|
191
|
+
draft: 7,
|
|
192
|
+
episode: 30,
|
|
193
|
+
reference: 180
|
|
194
|
+
};
|
|
195
|
+
PROMOTION_RULES = {
|
|
196
|
+
draftToEpisode: {
|
|
197
|
+
minAccessCount: 5,
|
|
198
|
+
minConfidence: 0.8,
|
|
199
|
+
minAgeDays: 1
|
|
200
|
+
},
|
|
201
|
+
episodeToReference: {
|
|
202
|
+
minAccessCount: 10,
|
|
203
|
+
minConfidence: 0.9,
|
|
204
|
+
minAgeDays: 7
|
|
134
205
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ../memory/dist/sync-storage.js
|
|
210
|
+
function parseSyncMarkdown(markdown) {
|
|
211
|
+
const trimmed = markdown.trim();
|
|
212
|
+
let frontmatter = {
|
|
213
|
+
type: "context",
|
|
214
|
+
scope: "project",
|
|
215
|
+
tier: "reference",
|
|
216
|
+
confidence: 1,
|
|
217
|
+
tags: []
|
|
218
|
+
};
|
|
219
|
+
let body = trimmed;
|
|
220
|
+
const fmMatch = trimmed.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
221
|
+
if (fmMatch) {
|
|
222
|
+
frontmatter = parseSyncYamlFrontmatter(fmMatch[1]);
|
|
223
|
+
body = fmMatch[2].trim();
|
|
224
|
+
}
|
|
225
|
+
let title = "";
|
|
226
|
+
const titleMatch = body.match(/^#\s+(.+)/m);
|
|
227
|
+
if (titleMatch) {
|
|
228
|
+
title = titleMatch[1].trim();
|
|
229
|
+
body = body.replace(/^#\s+.+\n?/, "").trim();
|
|
230
|
+
}
|
|
231
|
+
return { frontmatter, title, content: body };
|
|
232
|
+
}
|
|
233
|
+
function serializeSyncMarkdown(entity) {
|
|
234
|
+
const lines = ["---"];
|
|
235
|
+
lines.push(`id: ${entity.id}`);
|
|
236
|
+
lines.push(`workspace_id: ${entity.workspace_id}`);
|
|
237
|
+
if (entity.project_id) {
|
|
238
|
+
lines.push(`project_id: ${entity.project_id}`);
|
|
239
|
+
}
|
|
240
|
+
lines.push(`type: ${entity.type}`);
|
|
241
|
+
lines.push(`scope: ${entity.scope}`);
|
|
242
|
+
lines.push(`tier: ${entity.memory_tier || "reference"}`);
|
|
243
|
+
lines.push(`confidence: ${entity.confidence}`);
|
|
244
|
+
if (entity.tags.length > 0) {
|
|
245
|
+
lines.push(`tags: [${entity.tags.join(", ")}]`);
|
|
246
|
+
} else {
|
|
247
|
+
lines.push("tags: []");
|
|
248
|
+
}
|
|
249
|
+
if (entity.agent_identifier) {
|
|
250
|
+
lines.push(`agent: ${entity.agent_identifier}`);
|
|
251
|
+
}
|
|
252
|
+
lines.push(`created_at: ${entity.created_at}`);
|
|
253
|
+
lines.push(`updated_at: ${entity.updated_at}`);
|
|
254
|
+
lines.push("---");
|
|
255
|
+
lines.push("");
|
|
256
|
+
lines.push(`# ${entity.title}`);
|
|
257
|
+
lines.push("");
|
|
258
|
+
lines.push(entity.content);
|
|
259
|
+
return lines.join(`
|
|
260
|
+
`);
|
|
261
|
+
}
|
|
262
|
+
function parseSyncYamlFrontmatter(yaml) {
|
|
263
|
+
const result = {
|
|
264
|
+
type: "context",
|
|
265
|
+
scope: "project",
|
|
266
|
+
tier: "reference",
|
|
267
|
+
confidence: 1,
|
|
268
|
+
tags: []
|
|
269
|
+
};
|
|
270
|
+
for (const line of yaml.split(`
|
|
271
|
+
`)) {
|
|
272
|
+
const colonIndex = line.indexOf(":");
|
|
273
|
+
if (colonIndex === -1)
|
|
274
|
+
continue;
|
|
275
|
+
const key = line.slice(0, colonIndex).trim();
|
|
276
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
277
|
+
switch (key) {
|
|
278
|
+
case "id":
|
|
279
|
+
result.id = value;
|
|
280
|
+
break;
|
|
281
|
+
case "workspace_id":
|
|
282
|
+
result.workspace_id = value;
|
|
283
|
+
break;
|
|
284
|
+
case "project_id":
|
|
285
|
+
result.project_id = value;
|
|
286
|
+
break;
|
|
287
|
+
case "type":
|
|
288
|
+
result.type = value;
|
|
289
|
+
break;
|
|
290
|
+
case "scope":
|
|
291
|
+
result.scope = value;
|
|
292
|
+
break;
|
|
293
|
+
case "tier":
|
|
294
|
+
result.tier = value;
|
|
295
|
+
break;
|
|
296
|
+
case "confidence":
|
|
297
|
+
result.confidence = parseFloat(value) || 1;
|
|
298
|
+
break;
|
|
299
|
+
case "tags":
|
|
300
|
+
result.tags = parseYamlArray(value);
|
|
301
|
+
break;
|
|
302
|
+
case "related":
|
|
303
|
+
result.related = parseYamlArray(value);
|
|
304
|
+
break;
|
|
305
|
+
case "agent":
|
|
306
|
+
result.agent = value;
|
|
307
|
+
break;
|
|
308
|
+
case "created_at":
|
|
309
|
+
result.created_at = value;
|
|
310
|
+
break;
|
|
311
|
+
case "updated_at":
|
|
312
|
+
result.updated_at = value;
|
|
313
|
+
break;
|
|
154
314
|
}
|
|
155
|
-
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
156
317
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
318
|
+
function parseYamlArray(value) {
|
|
319
|
+
const match = value.match(/^\[(.*)]\s*$/);
|
|
320
|
+
if (!match)
|
|
321
|
+
return [];
|
|
322
|
+
return match[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ../memory/dist/sync.js
|
|
326
|
+
import { createHash } from "node:crypto";
|
|
327
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
328
|
+
import { join, relative, sep } from "node:path";
|
|
329
|
+
function computeFileHash(content) {
|
|
330
|
+
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
331
|
+
}
|
|
332
|
+
function slugifyTitle(title) {
|
|
333
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
334
|
+
}
|
|
335
|
+
function entityToFilename(entity) {
|
|
336
|
+
const slug = slugifyTitle(entity.title);
|
|
337
|
+
const shortId = entity.id.slice(0, 8);
|
|
338
|
+
return `${entity.type}--${slug}--${shortId}.md`;
|
|
339
|
+
}
|
|
340
|
+
function entityToDirectoryPath(entity, memoryDir) {
|
|
341
|
+
const wsDir = join(memoryDir, entity.workspace_id);
|
|
342
|
+
if (entity.scope === "private") {
|
|
343
|
+
return join(wsDir, "_private");
|
|
344
|
+
}
|
|
345
|
+
if (entity.scope === "workspace" || !entity.project_id) {
|
|
346
|
+
return join(wsDir, "_workspace");
|
|
347
|
+
}
|
|
348
|
+
return join(wsDir, entity.project_id);
|
|
349
|
+
}
|
|
350
|
+
function emptySyncState() {
|
|
351
|
+
return { version: 1, lastPullAt: null, entities: {} };
|
|
352
|
+
}
|
|
353
|
+
function loadSyncState(memoryDir) {
|
|
354
|
+
const statePath = join(memoryDir, ".sync-state.json");
|
|
355
|
+
if (!existsSync(statePath))
|
|
356
|
+
return emptySyncState();
|
|
357
|
+
try {
|
|
358
|
+
return JSON.parse(readFileSync(statePath, "utf-8"));
|
|
359
|
+
} catch {
|
|
360
|
+
return emptySyncState();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function saveSyncState(memoryDir, state) {
|
|
364
|
+
if (!existsSync(memoryDir)) {
|
|
365
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
366
|
+
}
|
|
367
|
+
writeFileSync(join(memoryDir, ".sync-state.json"), JSON.stringify(state, null, 2));
|
|
368
|
+
}
|
|
369
|
+
function writeEntityFile(entity, memoryDir) {
|
|
370
|
+
const dir = entityToDirectoryPath(entity, memoryDir);
|
|
371
|
+
if (!existsSync(dir))
|
|
372
|
+
mkdirSync(dir, { recursive: true });
|
|
373
|
+
const filename = entityToFilename(entity);
|
|
374
|
+
const filePath = join(dir, filename);
|
|
375
|
+
const markdown = serializeSyncMarkdown(entity);
|
|
376
|
+
writeFileSync(filePath, markdown);
|
|
377
|
+
return relative(memoryDir, filePath);
|
|
378
|
+
}
|
|
379
|
+
function deleteEntityFile(relPath, memoryDir) {
|
|
380
|
+
const absPath = join(memoryDir, relPath);
|
|
381
|
+
if (existsSync(absPath)) {
|
|
382
|
+
rmSync(absPath);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function findMarkdownFiles(dir) {
|
|
386
|
+
const results = [];
|
|
387
|
+
if (!existsSync(dir))
|
|
388
|
+
return results;
|
|
389
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
390
|
+
const fullPath = join(dir, entry.name);
|
|
391
|
+
if (entry.isDirectory()) {
|
|
392
|
+
results.push(...findMarkdownFiles(fullPath));
|
|
393
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
394
|
+
results.push(fullPath);
|
|
190
395
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
396
|
+
}
|
|
397
|
+
return results;
|
|
398
|
+
}
|
|
399
|
+
async function syncPull(client, config, workspaceId, projectId) {
|
|
400
|
+
const { memoryDir } = config;
|
|
401
|
+
const state = loadSyncState(memoryDir);
|
|
402
|
+
const result = {
|
|
403
|
+
pulled: 0,
|
|
404
|
+
pushed: 0,
|
|
405
|
+
deleted: 0,
|
|
406
|
+
conflicts: 0,
|
|
407
|
+
errors: []
|
|
408
|
+
};
|
|
409
|
+
const allEntities = [];
|
|
410
|
+
let offset = 0;
|
|
411
|
+
const batchSize = 100;
|
|
412
|
+
while (true) {
|
|
413
|
+
try {
|
|
414
|
+
const resp = await client.listMemoryEntities({
|
|
415
|
+
workspace_id: workspaceId,
|
|
416
|
+
project_id: projectId,
|
|
417
|
+
limit: batchSize,
|
|
418
|
+
offset
|
|
419
|
+
});
|
|
420
|
+
const batch = resp.entities;
|
|
421
|
+
allEntities.push(...batch);
|
|
422
|
+
if (batch.length < batchSize)
|
|
423
|
+
break;
|
|
424
|
+
offset += batchSize;
|
|
425
|
+
} catch (err) {
|
|
426
|
+
result.errors.push(`Failed to fetch entities: ${err}`);
|
|
427
|
+
return result;
|
|
209
428
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
429
|
+
}
|
|
430
|
+
const remoteIds = new Set(allEntities.map((e) => e.id));
|
|
431
|
+
for (const entity of allEntities) {
|
|
432
|
+
const existing = state.entities[entity.id];
|
|
433
|
+
const markdown = serializeSyncMarkdown(entity);
|
|
434
|
+
const hash = computeFileHash(markdown);
|
|
435
|
+
if (!existing) {
|
|
436
|
+
const relPath = writeEntityFile(entity, memoryDir);
|
|
437
|
+
state.entities[entity.id] = {
|
|
438
|
+
filePath: relPath,
|
|
439
|
+
remoteUpdatedAt: entity.updated_at,
|
|
440
|
+
lastSyncedHash: hash
|
|
441
|
+
};
|
|
442
|
+
result.pulled++;
|
|
443
|
+
} else {
|
|
444
|
+
const remoteChanged = entity.updated_at > existing.remoteUpdatedAt;
|
|
445
|
+
if (!remoteChanged)
|
|
446
|
+
continue;
|
|
447
|
+
const absPath = join(memoryDir, existing.filePath);
|
|
448
|
+
let localChanged = false;
|
|
449
|
+
if (existsSync(absPath)) {
|
|
450
|
+
const localContent = readFileSync(absPath, "utf-8");
|
|
451
|
+
const localHash = computeFileHash(localContent);
|
|
452
|
+
localChanged = localHash !== existing.lastSyncedHash;
|
|
453
|
+
}
|
|
454
|
+
if (localChanged) {
|
|
455
|
+
result.conflicts++;
|
|
456
|
+
}
|
|
457
|
+
const newRelPath = writeEntityFile(entity, memoryDir);
|
|
458
|
+
if (existing.filePath !== newRelPath) {
|
|
459
|
+
deleteEntityFile(existing.filePath, memoryDir);
|
|
460
|
+
}
|
|
461
|
+
state.entities[entity.id] = {
|
|
462
|
+
filePath: newRelPath,
|
|
463
|
+
remoteUpdatedAt: entity.updated_at,
|
|
464
|
+
lastSyncedHash: hash
|
|
465
|
+
};
|
|
466
|
+
result.pulled++;
|
|
227
467
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const entityWords = new Set(`${entity.title} ${entity.content}`
|
|
235
|
-
.toLowerCase()
|
|
236
|
-
.split(/\W+/)
|
|
237
|
-
.filter((w) => w.length > 2));
|
|
238
|
-
const overlap = [...taskWords].filter((w) => entityWords.has(w));
|
|
239
|
-
if (overlap.length > 0) {
|
|
240
|
-
const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1.0) *
|
|
241
|
-
textMatchWeight;
|
|
242
|
-
score += textScore;
|
|
243
|
-
reasons.push(`text_match(${overlap.length} words)`);
|
|
468
|
+
}
|
|
469
|
+
for (const [entityId, entry] of Object.entries(state.entities)) {
|
|
470
|
+
if (!remoteIds.has(entityId)) {
|
|
471
|
+
deleteEntityFile(entry.filePath, memoryDir);
|
|
472
|
+
delete state.entities[entityId];
|
|
473
|
+
result.deleted++;
|
|
244
474
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
475
|
+
}
|
|
476
|
+
state.lastPullAt = new Date().toISOString();
|
|
477
|
+
saveSyncState(memoryDir, state);
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
async function syncPush(client, config, workspaceId) {
|
|
481
|
+
const { memoryDir } = config;
|
|
482
|
+
const state = loadSyncState(memoryDir);
|
|
483
|
+
const result = {
|
|
484
|
+
pulled: 0,
|
|
485
|
+
pushed: 0,
|
|
486
|
+
deleted: 0,
|
|
487
|
+
conflicts: 0,
|
|
488
|
+
errors: []
|
|
489
|
+
};
|
|
490
|
+
const mdFiles = findMarkdownFiles(memoryDir);
|
|
491
|
+
for (const absPath of mdFiles) {
|
|
492
|
+
const content = readFileSync(absPath, "utf-8");
|
|
493
|
+
const hash = computeFileHash(content);
|
|
494
|
+
const parsed = parseSyncMarkdown(content);
|
|
495
|
+
if (parsed.frontmatter.id) {
|
|
496
|
+
const entityId = parsed.frontmatter.id;
|
|
497
|
+
const existing = state.entities[entityId];
|
|
498
|
+
if (existing && hash === existing.lastSyncedHash)
|
|
499
|
+
continue;
|
|
500
|
+
try {
|
|
501
|
+
const resp = await client.updateMemoryEntity(entityId, {
|
|
502
|
+
title: parsed.title || "Untitled",
|
|
503
|
+
content: parsed.content,
|
|
504
|
+
type: parsed.frontmatter.type,
|
|
505
|
+
scope: parsed.frontmatter.scope,
|
|
506
|
+
confidence: parsed.frontmatter.confidence,
|
|
507
|
+
tags: parsed.frontmatter.tags
|
|
508
|
+
});
|
|
509
|
+
const updated = resp.entity;
|
|
510
|
+
const newMarkdown = serializeSyncMarkdown(updated);
|
|
511
|
+
writeFileSync(absPath, newMarkdown);
|
|
512
|
+
const relPath = relative(memoryDir, absPath);
|
|
513
|
+
state.entities[entityId] = {
|
|
514
|
+
filePath: relPath,
|
|
515
|
+
remoteUpdatedAt: updated.updated_at,
|
|
516
|
+
lastSyncedHash: computeFileHash(newMarkdown)
|
|
517
|
+
};
|
|
518
|
+
result.pushed++;
|
|
519
|
+
} catch (err) {
|
|
520
|
+
result.errors.push(`Failed to update ${entityId}: ${err}`);
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
const relPath = relative(memoryDir, absPath);
|
|
524
|
+
const parts = relPath.split(sep);
|
|
525
|
+
const fileWorkspaceId = parts.length >= 2 ? parts[0] : workspaceId;
|
|
526
|
+
let fileProjectId;
|
|
527
|
+
if (parts.length >= 3) {
|
|
528
|
+
const scopeDir = parts[1];
|
|
529
|
+
if (scopeDir !== "_workspace" && scopeDir !== "_private") {
|
|
530
|
+
fileProjectId = scopeDir;
|
|
253
531
|
}
|
|
532
|
+
}
|
|
533
|
+
let scope = parsed.frontmatter.scope || "project";
|
|
534
|
+
if (parts.length >= 3) {
|
|
535
|
+
const scopeDir = parts[1];
|
|
536
|
+
if (scopeDir === "_private")
|
|
537
|
+
scope = "private";
|
|
538
|
+
else if (scopeDir === "_workspace")
|
|
539
|
+
scope = "workspace";
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
const resp = await client.createMemoryEntity({
|
|
543
|
+
workspace_id: fileWorkspaceId,
|
|
544
|
+
project_id: fileProjectId,
|
|
545
|
+
type: parsed.frontmatter.type,
|
|
546
|
+
scope,
|
|
547
|
+
title: parsed.title || "Untitled",
|
|
548
|
+
content: parsed.content,
|
|
549
|
+
confidence: parsed.frontmatter.confidence,
|
|
550
|
+
tags: parsed.frontmatter.tags,
|
|
551
|
+
agent_identifier: parsed.frontmatter.agent
|
|
552
|
+
});
|
|
553
|
+
const created = resp.entity;
|
|
554
|
+
const dir = entityToDirectoryPath(created, memoryDir);
|
|
555
|
+
if (!existsSync(dir))
|
|
556
|
+
mkdirSync(dir, { recursive: true });
|
|
557
|
+
const newFilename = entityToFilename(created);
|
|
558
|
+
const newAbsPath = join(dir, newFilename);
|
|
559
|
+
const newMarkdown = serializeSyncMarkdown(created);
|
|
560
|
+
writeFileSync(newAbsPath, newMarkdown);
|
|
561
|
+
if (absPath !== newAbsPath && existsSync(absPath)) {
|
|
562
|
+
rmSync(absPath);
|
|
563
|
+
}
|
|
564
|
+
const newRelPath = relative(memoryDir, newAbsPath);
|
|
565
|
+
state.entities[created.id] = {
|
|
566
|
+
filePath: newRelPath,
|
|
567
|
+
remoteUpdatedAt: created.updated_at,
|
|
568
|
+
lastSyncedHash: computeFileHash(newMarkdown)
|
|
569
|
+
};
|
|
570
|
+
result.pushed++;
|
|
571
|
+
} catch (err) {
|
|
572
|
+
result.errors.push(`Failed to create entity from ${relPath}: ${err}`);
|
|
573
|
+
}
|
|
254
574
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
575
|
+
}
|
|
576
|
+
saveSyncState(memoryDir, state);
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
579
|
+
async function syncFull(client, config, workspaceId, projectId) {
|
|
580
|
+
const pullResult = await syncPull(client, config, workspaceId, projectId);
|
|
581
|
+
const pushResult = await syncPush(client, config, workspaceId);
|
|
582
|
+
return {
|
|
583
|
+
pulled: pullResult.pulled,
|
|
584
|
+
pushed: pushResult.pushed,
|
|
585
|
+
deleted: pullResult.deleted,
|
|
586
|
+
conflicts: pullResult.conflicts,
|
|
587
|
+
errors: [...pullResult.errors, ...pushResult.errors]
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
var init_sync = () => {};
|
|
591
|
+
|
|
592
|
+
// ../memory/dist/index.js
|
|
593
|
+
var init_dist = __esm(() => {
|
|
594
|
+
init_client();
|
|
595
|
+
init_constraints();
|
|
596
|
+
init_lifecycle();
|
|
597
|
+
init_schema();
|
|
598
|
+
init_sync();
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// src/context-assembly.ts
|
|
602
|
+
var exports_context_assembly = {};
|
|
603
|
+
__export(exports_context_assembly, {
|
|
604
|
+
trackSessionAssembly: () => trackSessionAssembly,
|
|
605
|
+
recordContextFeedback: () => recordContextFeedback,
|
|
606
|
+
mapToContextEntity: () => mapToContextEntity,
|
|
607
|
+
getSessionAssemblyId: () => getSessionAssemblyId,
|
|
608
|
+
getCachedManifest: () => getCachedManifest,
|
|
609
|
+
expandQuery: () => expandQuery,
|
|
610
|
+
computeRelevanceScore: () => computeRelevanceScore,
|
|
611
|
+
cacheManifest: () => cacheManifest,
|
|
612
|
+
assembleContext: () => assembleContext
|
|
613
|
+
});
|
|
614
|
+
function estimateTokens(text) {
|
|
615
|
+
return Math.ceil(text.length / 4);
|
|
616
|
+
}
|
|
617
|
+
function passesQualityGate(entity) {
|
|
618
|
+
const content = entity.content.trim();
|
|
619
|
+
if (content.length < 50)
|
|
620
|
+
return false;
|
|
621
|
+
const normalizedTitle = entity.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
622
|
+
const normalizedContent = content.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
623
|
+
if (normalizedContent.length < normalizedTitle.length * 1.5) {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
if (entity.type === "pattern" && /recurring .+ \(\d+ instances\)/i.test(entity.title)) {
|
|
627
|
+
const lines = content.split(`
|
|
628
|
+
`).filter((l) => l.trim().length > 0);
|
|
629
|
+
const bulletLines = lines.filter((l) => l.trim().startsWith("- "));
|
|
630
|
+
if (bulletLines.length > lines.length * 0.6)
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
if (entity.type === "procedure") {
|
|
634
|
+
const stepCount = (content.match(/^\d+\.\s/gm) || []).length;
|
|
635
|
+
if (stepCount < 3)
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
function generateAssemblyId() {
|
|
641
|
+
return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
642
|
+
}
|
|
643
|
+
function truncateContent(content, maxTokens) {
|
|
644
|
+
const currentTokens = estimateTokens(content);
|
|
645
|
+
if (currentTokens <= maxTokens) {
|
|
646
|
+
return { text: content, truncated: false };
|
|
647
|
+
}
|
|
648
|
+
const paragraphs = content.split(/\n\n+/);
|
|
649
|
+
let result = paragraphs[0];
|
|
650
|
+
for (let i = 1;i < paragraphs.length; i++) {
|
|
651
|
+
const lines = paragraphs[i].split(`
|
|
652
|
+
`).filter((l) => l.startsWith("- ") || l.startsWith("* "));
|
|
653
|
+
if (lines.length > 0) {
|
|
654
|
+
const bulletSection = lines.join(`
|
|
655
|
+
`);
|
|
656
|
+
if (estimateTokens(result + `
|
|
657
|
+
|
|
658
|
+
` + bulletSection) <= maxTokens) {
|
|
659
|
+
result += `
|
|
660
|
+
|
|
661
|
+
` + bulletSection;
|
|
662
|
+
}
|
|
283
663
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
664
|
+
}
|
|
665
|
+
if (estimateTokens(result) > maxTokens) {
|
|
666
|
+
const maxChars = maxTokens * 4;
|
|
667
|
+
result = result.slice(0, maxChars - 3) + "...";
|
|
668
|
+
}
|
|
669
|
+
return { text: result, truncated: true };
|
|
670
|
+
}
|
|
671
|
+
function escapeRegex(str) {
|
|
672
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
673
|
+
}
|
|
674
|
+
function expandQuery(taskContext) {
|
|
675
|
+
const queries = [taskContext];
|
|
676
|
+
const lowerQueries = [taskContext.toLowerCase()];
|
|
677
|
+
const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
678
|
+
const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
|
|
679
|
+
for (const word of expandableWords) {
|
|
680
|
+
const synonyms = QUERY_SYNONYMS[word];
|
|
681
|
+
if (!synonyms)
|
|
682
|
+
continue;
|
|
683
|
+
const variation = taskContext.replace(new RegExp(`\\b${escapeRegex(word)}\\b`, "gi"), synonyms[0]);
|
|
684
|
+
const lowerVariation = variation.toLowerCase();
|
|
685
|
+
if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
|
|
686
|
+
queries.push(variation);
|
|
687
|
+
lowerQueries.push(lowerVariation);
|
|
288
688
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
689
|
+
if (queries.length >= MAX_QUERY_VARIATIONS)
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
if (words.length >= 3) {
|
|
693
|
+
const keyPhrases = words.filter((w) => ![
|
|
694
|
+
"the",
|
|
695
|
+
"and",
|
|
696
|
+
"for",
|
|
697
|
+
"with",
|
|
698
|
+
"this",
|
|
699
|
+
"that",
|
|
700
|
+
"from",
|
|
701
|
+
"into"
|
|
702
|
+
].includes(w)).slice(0, 4).join(" ");
|
|
703
|
+
if (!lowerQueries.includes(keyPhrases)) {
|
|
704
|
+
queries.push(keyPhrases);
|
|
293
705
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
706
|
+
}
|
|
707
|
+
return queries.slice(0, MAX_QUERY_VARIATIONS);
|
|
708
|
+
}
|
|
709
|
+
function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
|
|
710
|
+
const reasons = [];
|
|
711
|
+
let score = 0;
|
|
712
|
+
const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
|
|
713
|
+
if (hasRrfScore) {
|
|
714
|
+
const normalizedRrf = Math.min(entity.rrf_score / 0.04, 1);
|
|
715
|
+
const rrfContribution = normalizedRrf * 0.3;
|
|
716
|
+
score += rrfContribution;
|
|
717
|
+
reasons.push(`hybrid_search(rrf=${entity.rrf_score.toFixed(4)})`);
|
|
718
|
+
}
|
|
719
|
+
const textMatchWeight = hasRrfScore ? 0.15 : 0.4;
|
|
720
|
+
const taskWords = new Set(taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
|
|
721
|
+
const entityWords = new Set(`${entity.title} ${entity.content}`.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
|
|
722
|
+
const overlap = [...taskWords].filter((w) => entityWords.has(w));
|
|
723
|
+
if (overlap.length > 0) {
|
|
724
|
+
const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1) * textMatchWeight;
|
|
725
|
+
score += textScore;
|
|
726
|
+
reasons.push(`text_match(${overlap.length} words)`);
|
|
727
|
+
}
|
|
728
|
+
if (cardLabels.length > 0 && entity.tags.length > 0) {
|
|
729
|
+
const labelSet = new Set(cardLabels.map((l) => l.toLowerCase()));
|
|
730
|
+
const tagOverlap = entity.tags.filter((t) => labelSet.has(t.toLowerCase()));
|
|
731
|
+
if (tagOverlap.length > 0) {
|
|
732
|
+
const tagScore = tagOverlap.length / cardLabels.length * 0.3;
|
|
733
|
+
score += tagScore;
|
|
734
|
+
reasons.push(`tag_match(${tagOverlap.join(",")})`);
|
|
311
735
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const entity = mapToContextEntity(raw);
|
|
355
|
-
if (!candidateIds.has(entity.id)) {
|
|
356
|
-
candidateIds.add(entity.id);
|
|
357
|
-
candidates.push(entity);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
736
|
+
}
|
|
737
|
+
score += entity.confidence * 0.15;
|
|
738
|
+
if (entity.confidence >= 0.9) {
|
|
739
|
+
reasons.push("high_confidence");
|
|
740
|
+
}
|
|
741
|
+
if (entity.last_accessed_at) {
|
|
742
|
+
const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
743
|
+
const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
|
|
744
|
+
const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
|
|
745
|
+
score += recencyScore;
|
|
746
|
+
if (daysSinceAccess < 7)
|
|
747
|
+
reasons.push("recently_accessed");
|
|
748
|
+
}
|
|
749
|
+
if (entity.access_count > 0) {
|
|
750
|
+
const freqScore = Math.log10(entity.access_count + 1) * 0.05;
|
|
751
|
+
score += Math.min(freqScore, 0.1);
|
|
752
|
+
if (entity.access_count >= 5)
|
|
753
|
+
reasons.push(`frequently_used(${entity.access_count})`);
|
|
754
|
+
}
|
|
755
|
+
const usefulnessScore = entity.metadata?.usefulness_score ?? 0;
|
|
756
|
+
if (usefulnessScore >= 3) {
|
|
757
|
+
const usefulnessBoost = Math.min(usefulnessScore / 20, 0.15);
|
|
758
|
+
score += usefulnessBoost;
|
|
759
|
+
reasons.push(`useful(${usefulnessScore})`);
|
|
760
|
+
} else if (usefulnessScore === 0 && entity.access_count >= 5) {
|
|
761
|
+
score -= 0.02;
|
|
762
|
+
reasons.push("low_usefulness");
|
|
763
|
+
}
|
|
764
|
+
if (entity.type === "procedure") {
|
|
765
|
+
score += 0.1;
|
|
766
|
+
reasons.push("procedure_boost");
|
|
767
|
+
}
|
|
768
|
+
if (graphRelations && graphRelations.length > 0) {
|
|
769
|
+
const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
|
|
770
|
+
if (entityRelations.length > 0) {
|
|
771
|
+
let bestBonus = 0;
|
|
772
|
+
let bestRelType = "";
|
|
773
|
+
for (const rel of entityRelations) {
|
|
774
|
+
const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
|
|
775
|
+
if (bonus > bestBonus) {
|
|
776
|
+
bestBonus = bonus;
|
|
777
|
+
bestRelType = rel.relation_type;
|
|
360
778
|
}
|
|
779
|
+
}
|
|
780
|
+
score += bestBonus;
|
|
781
|
+
reasons.push(`graph_walk(${bestRelType})`);
|
|
361
782
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
783
|
+
}
|
|
784
|
+
score = Math.max(0, Math.min(score, 1));
|
|
785
|
+
const tierWeight = TIER_WEIGHTS[entity.memory_tier];
|
|
786
|
+
score *= tierWeight;
|
|
787
|
+
return { score, reasons };
|
|
788
|
+
}
|
|
789
|
+
async function assembleContext(options) {
|
|
790
|
+
const {
|
|
791
|
+
workspaceId,
|
|
792
|
+
projectId,
|
|
793
|
+
taskContext,
|
|
794
|
+
cardLabels = [],
|
|
795
|
+
tokenBudget = DEFAULT_TOKEN_BUDGET,
|
|
796
|
+
client: client2,
|
|
797
|
+
graphWalkEnabled = true,
|
|
798
|
+
queryExpansionEnabled = true,
|
|
799
|
+
enableLlmReranking = false,
|
|
800
|
+
rerankFn
|
|
801
|
+
} = options;
|
|
802
|
+
const assemblyId = generateAssemblyId();
|
|
803
|
+
const manifest = {
|
|
804
|
+
assemblyId,
|
|
805
|
+
timestamp: new Date().toISOString(),
|
|
806
|
+
included: [],
|
|
807
|
+
excluded: [],
|
|
808
|
+
budgetUsed: 0,
|
|
809
|
+
budgetTotal: tokenBudget,
|
|
810
|
+
tierBreakdown: {
|
|
811
|
+
draft: { count: 0, tokens: 0 },
|
|
812
|
+
episode: { count: 0, tokens: 0 },
|
|
813
|
+
reference: { count: 0, tokens: 0 }
|
|
383
814
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
catch {
|
|
404
|
-
// Continue with what we have
|
|
815
|
+
};
|
|
816
|
+
const candidates = [];
|
|
817
|
+
const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
|
|
818
|
+
const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
|
|
819
|
+
project_id: projectId,
|
|
820
|
+
limit: 30
|
|
821
|
+
})));
|
|
822
|
+
const candidateIds = new Set;
|
|
823
|
+
for (const result of searchResults) {
|
|
824
|
+
if (result.status !== "fulfilled")
|
|
825
|
+
continue;
|
|
826
|
+
if (result.value.entities?.length > 0) {
|
|
827
|
+
for (const raw of result.value.entities) {
|
|
828
|
+
const entity = mapToContextEntity(raw);
|
|
829
|
+
if (!candidateIds.has(entity.id)) {
|
|
830
|
+
candidateIds.add(entity.id);
|
|
831
|
+
candidates.push(entity);
|
|
405
832
|
}
|
|
833
|
+
}
|
|
406
834
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (newEntityIds.length > 0) {
|
|
423
|
-
// Fetch full entity data in parallel (graph walk only returns summary fields)
|
|
424
|
-
const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client.getMemoryEntity(id)));
|
|
425
|
-
for (const result of fetchResults) {
|
|
426
|
-
if (result.status !== "fulfilled" || !result.value.entity)
|
|
427
|
-
continue;
|
|
428
|
-
const mapped = mapToContextEntity(result.value.entity);
|
|
429
|
-
candidateIds.add(mapped.id);
|
|
430
|
-
candidates.push(mapped);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
835
|
+
}
|
|
836
|
+
if (candidates.length < 10 && projectId) {
|
|
837
|
+
try {
|
|
838
|
+
const listResult = await client2.listMemoryEntities({
|
|
839
|
+
workspace_id: workspaceId,
|
|
840
|
+
project_id: projectId,
|
|
841
|
+
limit: 30
|
|
842
|
+
});
|
|
843
|
+
if (listResult.entities?.length > 0) {
|
|
844
|
+
for (const raw of listResult.entities) {
|
|
845
|
+
const entity = mapToContextEntity(raw);
|
|
846
|
+
if (!candidateIds.has(entity.id)) {
|
|
847
|
+
candidateIds.add(entity.id);
|
|
848
|
+
candidates.push(entity);
|
|
849
|
+
}
|
|
433
850
|
}
|
|
434
|
-
|
|
435
|
-
|
|
851
|
+
}
|
|
852
|
+
} catch {}
|
|
853
|
+
}
|
|
854
|
+
if (candidates.length < 20) {
|
|
855
|
+
try {
|
|
856
|
+
const wsResult = await client2.listMemoryEntities({
|
|
857
|
+
workspace_id: workspaceId,
|
|
858
|
+
scope: "workspace",
|
|
859
|
+
limit: 20
|
|
860
|
+
});
|
|
861
|
+
if (wsResult.entities?.length > 0) {
|
|
862
|
+
for (const raw of wsResult.entities) {
|
|
863
|
+
const entity = mapToContextEntity(raw);
|
|
864
|
+
if (!candidateIds.has(entity.id)) {
|
|
865
|
+
candidateIds.add(entity.id);
|
|
866
|
+
candidates.push(entity);
|
|
867
|
+
}
|
|
436
868
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
});
|
|
457
|
-
return false;
|
|
458
|
-
});
|
|
459
|
-
if (qualityCandidates.length === 0) {
|
|
460
|
-
return {
|
|
461
|
-
context: "",
|
|
462
|
-
manifest,
|
|
463
|
-
memories: [],
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
// Score all candidates (pass graph relations for relation-type bonuses)
|
|
467
|
-
const scored = qualityCandidates.map((entity) => {
|
|
468
|
-
const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
|
|
469
|
-
return { entity, score, reasons };
|
|
470
|
-
});
|
|
471
|
-
// Sort by score descending
|
|
472
|
-
scored.sort((a, b) => b.score - a.score);
|
|
473
|
-
// P2: Optional LLM re-ranking when top scores are clustered
|
|
474
|
-
if (enableLlmReranking &&
|
|
475
|
-
rerankFn &&
|
|
476
|
-
scored.length >= RERANK_MIN_CANDIDATES) {
|
|
477
|
-
const topN = scored.slice(0, RERANK_TOP_N);
|
|
478
|
-
const scoreRange = topN[0].score - topN[topN.length - 1].score;
|
|
479
|
-
// Only re-rank when scores are tightly clustered
|
|
480
|
-
if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
|
|
481
|
-
try {
|
|
482
|
-
const rerankCandidates = topN.map((s) => ({
|
|
483
|
-
id: s.entity.id,
|
|
484
|
-
title: s.entity.title,
|
|
485
|
-
snippet: s.entity.content.slice(0, 200),
|
|
486
|
-
}));
|
|
487
|
-
const rerankedIds = await rerankFn(taskContext, rerankCandidates);
|
|
488
|
-
// Reorder based on LLM ranking
|
|
489
|
-
const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
|
|
490
|
-
topN.sort((a, b) => {
|
|
491
|
-
const aIdx = idOrder.get(a.entity.id) ?? 999;
|
|
492
|
-
const bIdx = idOrder.get(b.entity.id) ?? 999;
|
|
493
|
-
return aIdx - bIdx;
|
|
494
|
-
});
|
|
495
|
-
// Splice reranked items back in
|
|
496
|
-
scored.splice(0, topN.length, ...topN);
|
|
497
|
-
}
|
|
498
|
-
catch {
|
|
499
|
-
// Re-ranking failed, continue with static ordering
|
|
500
|
-
}
|
|
869
|
+
}
|
|
870
|
+
} catch {}
|
|
871
|
+
}
|
|
872
|
+
let graphRelations = [];
|
|
873
|
+
if (graphWalkEnabled && candidates.length > 0) {
|
|
874
|
+
try {
|
|
875
|
+
const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
|
|
876
|
+
const seedIds = seedCandidates.map((c) => c.id);
|
|
877
|
+
const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
|
|
878
|
+
graphRelations = walkResult.relations;
|
|
879
|
+
const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
|
|
880
|
+
if (newEntityIds.length > 0) {
|
|
881
|
+
const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
|
|
882
|
+
for (const result of fetchResults) {
|
|
883
|
+
if (result.status !== "fulfilled" || !result.value.entity)
|
|
884
|
+
continue;
|
|
885
|
+
const mapped = mapToContextEntity(result.value.entity);
|
|
886
|
+
candidateIds.add(mapped.id);
|
|
887
|
+
candidates.push(mapped);
|
|
501
888
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
889
|
+
}
|
|
890
|
+
} catch {}
|
|
891
|
+
}
|
|
892
|
+
if (candidates.length === 0) {
|
|
893
|
+
return {
|
|
894
|
+
context: "",
|
|
895
|
+
manifest,
|
|
896
|
+
memories: []
|
|
510
897
|
};
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
898
|
+
}
|
|
899
|
+
const qualityCandidates = candidates.filter((entity) => {
|
|
900
|
+
if (passesQualityGate(entity))
|
|
901
|
+
return true;
|
|
902
|
+
manifest.excluded.push({
|
|
903
|
+
entityId: entity.id,
|
|
904
|
+
title: entity.title,
|
|
905
|
+
type: entity.type,
|
|
906
|
+
tier: entity.memory_tier,
|
|
907
|
+
relevanceScore: 0,
|
|
908
|
+
reason: "failed_quality_gate"
|
|
909
|
+
});
|
|
910
|
+
return false;
|
|
911
|
+
});
|
|
912
|
+
if (qualityCandidates.length === 0) {
|
|
913
|
+
return {
|
|
914
|
+
context: "",
|
|
915
|
+
manifest,
|
|
916
|
+
memories: []
|
|
515
917
|
};
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
918
|
+
}
|
|
919
|
+
const scored = qualityCandidates.map((entity) => {
|
|
920
|
+
const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
|
|
921
|
+
return { entity, score, reasons };
|
|
922
|
+
});
|
|
923
|
+
scored.sort((a, b) => b.score - a.score);
|
|
924
|
+
if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
|
|
925
|
+
const topN = scored.slice(0, RERANK_TOP_N);
|
|
926
|
+
const scoreRange = topN[0].score - topN[topN.length - 1].score;
|
|
927
|
+
if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
|
|
928
|
+
try {
|
|
929
|
+
const rerankCandidates = topN.map((s) => ({
|
|
930
|
+
id: s.entity.id,
|
|
931
|
+
title: s.entity.title,
|
|
932
|
+
snippet: s.entity.content.slice(0, 200)
|
|
933
|
+
}));
|
|
934
|
+
const rerankedIds = await rerankFn(taskContext, rerankCandidates);
|
|
935
|
+
const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
|
|
936
|
+
topN.sort((a, b) => {
|
|
937
|
+
const aIdx = idOrder.get(a.entity.id) ?? 999;
|
|
938
|
+
const bIdx = idOrder.get(b.entity.id) ?? 999;
|
|
939
|
+
return aIdx - bIdx;
|
|
940
|
+
});
|
|
941
|
+
scored.splice(0, topN.length, ...topN);
|
|
942
|
+
} catch {}
|
|
535
943
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
title: item.entity.title,
|
|
560
|
-
type: item.entity.type,
|
|
561
|
-
tier: item.entity.memory_tier,
|
|
562
|
-
relevanceScore: item.score,
|
|
563
|
-
reason: "procedure_budget_exceeded",
|
|
564
|
-
});
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (totalUsed + tokens > tokenBudget) {
|
|
569
|
-
manifest.excluded.push({
|
|
570
|
-
entityId: item.entity.id,
|
|
571
|
-
title: item.entity.title,
|
|
572
|
-
type: item.entity.type,
|
|
573
|
-
tier: item.entity.memory_tier,
|
|
574
|
-
relevanceScore: item.score,
|
|
575
|
-
reason: "total_budget_exceeded",
|
|
576
|
-
});
|
|
577
|
-
continue;
|
|
578
|
-
}
|
|
944
|
+
}
|
|
945
|
+
const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
|
|
946
|
+
const remainingBudget = tokenBudget - procedureBudget;
|
|
947
|
+
const tierBudgets = {
|
|
948
|
+
reference: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.reference),
|
|
949
|
+
episode: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.episode),
|
|
950
|
+
draft: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.draft)
|
|
951
|
+
};
|
|
952
|
+
const tierUsed = {
|
|
953
|
+
reference: 0,
|
|
954
|
+
episode: 0,
|
|
955
|
+
draft: 0
|
|
956
|
+
};
|
|
957
|
+
let procedureUsed = 0;
|
|
958
|
+
const included = [];
|
|
959
|
+
let totalUsed = 0;
|
|
960
|
+
let referenceCount = 0;
|
|
961
|
+
for (const item of scored) {
|
|
962
|
+
if (item.entity.memory_tier === "reference" && item.entity.type !== "procedure" && referenceCount < MIN_REFERENCE_SLOTS) {
|
|
963
|
+
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
964
|
+
const tokens = estimateTokens(`### ${item.entity.title}
|
|
965
|
+
${text}`);
|
|
966
|
+
if (totalUsed + tokens <= tokenBudget) {
|
|
579
967
|
included.push({ ...item, tokens, truncated });
|
|
580
968
|
item.entity.content = text;
|
|
581
969
|
totalUsed += tokens;
|
|
582
|
-
|
|
583
|
-
|
|
970
|
+
tierUsed.reference += tokens;
|
|
971
|
+
referenceCount++;
|
|
972
|
+
}
|
|
584
973
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
});
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
const tier = item.entity.memory_tier;
|
|
603
|
-
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
604
|
-
const tokens = estimateTokens(`### ${item.entity.title}\n${text}`);
|
|
605
|
-
// Check tier budget (allow overflow to unused tiers)
|
|
606
|
-
if (tierUsed[tier] + tokens > tierBudgets[tier]) {
|
|
607
|
-
// Check if there's unused budget from other tiers
|
|
608
|
-
const totalRemaining = tokenBudget - totalUsed;
|
|
609
|
-
if (tokens > totalRemaining) {
|
|
610
|
-
manifest.excluded.push({
|
|
611
|
-
entityId: item.entity.id,
|
|
612
|
-
title: item.entity.title,
|
|
613
|
-
type: item.entity.type,
|
|
614
|
-
tier,
|
|
615
|
-
relevanceScore: item.score,
|
|
616
|
-
reason: "budget_exceeded",
|
|
617
|
-
});
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
if (totalUsed + tokens > tokenBudget) {
|
|
622
|
-
manifest.excluded.push({
|
|
623
|
-
entityId: item.entity.id,
|
|
624
|
-
title: item.entity.title,
|
|
625
|
-
type: item.entity.type,
|
|
626
|
-
tier,
|
|
627
|
-
relevanceScore: item.score,
|
|
628
|
-
reason: "total_budget_exceeded",
|
|
629
|
-
});
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
|
-
included.push({ ...item, tokens, truncated });
|
|
633
|
-
item.entity.content = text;
|
|
634
|
-
totalUsed += tokens;
|
|
635
|
-
tierUsed[tier] += tokens;
|
|
636
|
-
includedIds.add(item.entity.id);
|
|
974
|
+
}
|
|
975
|
+
const includedIds = new Set(included.map((i) => i.entity.id));
|
|
976
|
+
const procedureCandidates = scored.filter((item) => item.entity.type === "procedure" && !includedIds.has(item.entity.id));
|
|
977
|
+
for (const item of procedureCandidates) {
|
|
978
|
+
if (item.score < MIN_RELEVANCE_THRESHOLD) {
|
|
979
|
+
manifest.excluded.push({
|
|
980
|
+
entityId: item.entity.id,
|
|
981
|
+
title: item.entity.title,
|
|
982
|
+
type: item.entity.type,
|
|
983
|
+
tier: item.entity.memory_tier,
|
|
984
|
+
relevanceScore: item.score,
|
|
985
|
+
reason: "below_relevance_threshold"
|
|
986
|
+
});
|
|
987
|
+
continue;
|
|
637
988
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
count: included.filter((i) => i.entity.memory_tier === "draft" && i.entity.type !== "procedure").length,
|
|
652
|
-
tokens: tierUsed.draft,
|
|
653
|
-
},
|
|
654
|
-
};
|
|
655
|
-
manifest.procedureBreakdown = {
|
|
656
|
-
count: procedureItems.length,
|
|
657
|
-
tokens: procedureUsed,
|
|
658
|
-
budget: procedureBudget,
|
|
659
|
-
};
|
|
660
|
-
for (const item of included) {
|
|
661
|
-
manifest.included.push({
|
|
662
|
-
entityId: item.entity.id,
|
|
663
|
-
title: item.entity.title,
|
|
664
|
-
type: item.entity.type,
|
|
665
|
-
tier: item.entity.memory_tier,
|
|
666
|
-
relevanceScore: item.score,
|
|
667
|
-
reasons: item.reasons,
|
|
668
|
-
tokenCount: item.tokens,
|
|
669
|
-
truncated: item.truncated,
|
|
989
|
+
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
990
|
+
const tokens = estimateTokens(`### ${item.entity.title}
|
|
991
|
+
${text}`);
|
|
992
|
+
if (procedureUsed + tokens > procedureBudget) {
|
|
993
|
+
const totalRemaining = tokenBudget - totalUsed;
|
|
994
|
+
if (tokens > totalRemaining) {
|
|
995
|
+
manifest.excluded.push({
|
|
996
|
+
entityId: item.entity.id,
|
|
997
|
+
title: item.entity.title,
|
|
998
|
+
type: item.entity.type,
|
|
999
|
+
tier: item.entity.memory_tier,
|
|
1000
|
+
relevanceScore: item.score,
|
|
1001
|
+
reason: "procedure_budget_exceeded"
|
|
670
1002
|
});
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
671
1005
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
: "";
|
|
683
|
-
const tierLabel = item.entity.memory_tier !== "reference"
|
|
684
|
-
? ` (${item.entity.memory_tier})`
|
|
685
|
-
: "";
|
|
686
|
-
contextSections.push(`\n### ${item.entity.title} (confidence: ${item.entity.confidence})${tierLabel}${tags}`);
|
|
687
|
-
contextSections.push(item.entity.content);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
// Non-procedure memories
|
|
691
|
-
if (nonProcedureItems.length > 0) {
|
|
692
|
-
contextSections.push(`\n## Relevant Memories (${nonProcedureItems.length} loaded, ${manifest.excluded.length} excluded)`);
|
|
693
|
-
contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
|
|
694
|
-
for (const item of nonProcedureItems) {
|
|
695
|
-
const tags = item.entity.tags.length > 0
|
|
696
|
-
? ` [${item.entity.tags.join(", ")}]`
|
|
697
|
-
: "";
|
|
698
|
-
const tierLabel = item.entity.memory_tier !== "reference"
|
|
699
|
-
? ` (${item.entity.memory_tier})`
|
|
700
|
-
: "";
|
|
701
|
-
contextSections.push(`\n### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
|
|
702
|
-
contextSections.push(item.entity.content);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
1006
|
+
if (totalUsed + tokens > tokenBudget) {
|
|
1007
|
+
manifest.excluded.push({
|
|
1008
|
+
entityId: item.entity.id,
|
|
1009
|
+
title: item.entity.title,
|
|
1010
|
+
type: item.entity.type,
|
|
1011
|
+
tier: item.entity.memory_tier,
|
|
1012
|
+
relevanceScore: item.score,
|
|
1013
|
+
reason: "total_budget_exceeded"
|
|
1014
|
+
});
|
|
1015
|
+
continue;
|
|
705
1016
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
tags: e.tags || [],
|
|
728
|
-
memory_tier: e.memory_tier || "reference",
|
|
729
|
-
access_count: e.access_count || 0,
|
|
730
|
-
last_accessed_at: e.last_accessed_at || null,
|
|
731
|
-
created_at: e.created_at || "",
|
|
732
|
-
updated_at: e.updated_at || "",
|
|
733
|
-
metadata: e.metadata ?? undefined,
|
|
734
|
-
// Hybrid search signals (present when results come from RPC)
|
|
735
|
-
rrf_score: e.rrf_score ?? undefined,
|
|
736
|
-
fts_rank: e.fts_rank ?? undefined,
|
|
737
|
-
semantic_rank: e.semantic_rank ?? undefined,
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Increment access counts for entities loaded into context.
|
|
742
|
-
* Uses batch_touch_knowledge_entities RPC for a single-roundtrip update.
|
|
743
|
-
* Falls back to individual touches if the batch endpoint is unavailable.
|
|
744
|
-
*/
|
|
745
|
-
async function incrementAccessCounts(client, entityIds) {
|
|
746
|
-
if (entityIds.length === 0)
|
|
747
|
-
return;
|
|
748
|
-
try {
|
|
749
|
-
await client.batchTouchMemoryEntities(entityIds);
|
|
1017
|
+
included.push({ ...item, tokens, truncated });
|
|
1018
|
+
item.entity.content = text;
|
|
1019
|
+
totalUsed += tokens;
|
|
1020
|
+
procedureUsed += tokens;
|
|
1021
|
+
includedIds.add(item.entity.id);
|
|
1022
|
+
}
|
|
1023
|
+
for (const item of scored) {
|
|
1024
|
+
if (includedIds.has(item.entity.id))
|
|
1025
|
+
continue;
|
|
1026
|
+
if (item.entity.type === "procedure")
|
|
1027
|
+
continue;
|
|
1028
|
+
if (item.score < MIN_RELEVANCE_THRESHOLD) {
|
|
1029
|
+
manifest.excluded.push({
|
|
1030
|
+
entityId: item.entity.id,
|
|
1031
|
+
title: item.entity.title,
|
|
1032
|
+
type: item.entity.type,
|
|
1033
|
+
tier: item.entity.memory_tier,
|
|
1034
|
+
relevanceScore: item.score,
|
|
1035
|
+
reason: "below_relevance_threshold"
|
|
1036
|
+
});
|
|
1037
|
+
continue;
|
|
750
1038
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1039
|
+
const tier = item.entity.memory_tier;
|
|
1040
|
+
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
1041
|
+
const tokens = estimateTokens(`### ${item.entity.title}
|
|
1042
|
+
${text}`);
|
|
1043
|
+
if (tierUsed[tier] + tokens > tierBudgets[tier]) {
|
|
1044
|
+
const totalRemaining = tokenBudget - totalUsed;
|
|
1045
|
+
if (tokens > totalRemaining) {
|
|
1046
|
+
manifest.excluded.push({
|
|
1047
|
+
entityId: item.entity.id,
|
|
1048
|
+
title: item.entity.title,
|
|
1049
|
+
type: item.entity.type,
|
|
1050
|
+
tier,
|
|
1051
|
+
relevanceScore: item.score,
|
|
1052
|
+
reason: "budget_exceeded"
|
|
1053
|
+
});
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
754
1056
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
continue;
|
|
766
|
-
// +1 because incrementAccessCounts just bumped it
|
|
767
|
-
const promotion = checkPromotion(entity.memory_tier, entity.access_count + 1, entity.confidence, entity.created_at);
|
|
768
|
-
if (promotion.eligible && promotion.targetTier) {
|
|
769
|
-
try {
|
|
770
|
-
await client.updateMemoryEntity(entity.id, {
|
|
771
|
-
memory_tier: promotion.targetTier,
|
|
772
|
-
metadata: {
|
|
773
|
-
...(entity.metadata || {}),
|
|
774
|
-
promoted_at: new Date().toISOString(),
|
|
775
|
-
promotion_reason: promotion.reason,
|
|
776
|
-
promoted_from: entity.memory_tier,
|
|
777
|
-
},
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
catch {
|
|
781
|
-
// Non-fatal: promotion is best-effort
|
|
782
|
-
}
|
|
783
|
-
}
|
|
1057
|
+
if (totalUsed + tokens > tokenBudget) {
|
|
1058
|
+
manifest.excluded.push({
|
|
1059
|
+
entityId: item.entity.id,
|
|
1060
|
+
title: item.entity.title,
|
|
1061
|
+
type: item.entity.type,
|
|
1062
|
+
tier,
|
|
1063
|
+
relevanceScore: item.score,
|
|
1064
|
+
reason: "total_budget_exceeded"
|
|
1065
|
+
});
|
|
1066
|
+
continue;
|
|
784
1067
|
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1068
|
+
included.push({ ...item, tokens, truncated });
|
|
1069
|
+
item.entity.content = text;
|
|
1070
|
+
totalUsed += tokens;
|
|
1071
|
+
tierUsed[tier] += tokens;
|
|
1072
|
+
includedIds.add(item.entity.id);
|
|
1073
|
+
}
|
|
1074
|
+
manifest.budgetUsed = totalUsed;
|
|
1075
|
+
const procedureItems = included.filter((i) => i.entity.type === "procedure");
|
|
1076
|
+
manifest.tierBreakdown = {
|
|
1077
|
+
reference: {
|
|
1078
|
+
count: included.filter((i) => i.entity.memory_tier === "reference" && i.entity.type !== "procedure").length,
|
|
1079
|
+
tokens: tierUsed.reference
|
|
1080
|
+
},
|
|
1081
|
+
episode: {
|
|
1082
|
+
count: included.filter((i) => i.entity.memory_tier === "episode" && i.entity.type !== "procedure").length,
|
|
1083
|
+
tokens: tierUsed.episode
|
|
1084
|
+
},
|
|
1085
|
+
draft: {
|
|
1086
|
+
count: included.filter((i) => i.entity.memory_tier === "draft" && i.entity.type !== "procedure").length,
|
|
1087
|
+
tokens: tierUsed.draft
|
|
798
1088
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1089
|
+
};
|
|
1090
|
+
manifest.procedureBreakdown = {
|
|
1091
|
+
count: procedureItems.length,
|
|
1092
|
+
tokens: procedureUsed,
|
|
1093
|
+
budget: procedureBudget
|
|
1094
|
+
};
|
|
1095
|
+
for (const item of included) {
|
|
1096
|
+
manifest.included.push({
|
|
1097
|
+
entityId: item.entity.id,
|
|
1098
|
+
title: item.entity.title,
|
|
1099
|
+
type: item.entity.type,
|
|
1100
|
+
tier: item.entity.memory_tier,
|
|
1101
|
+
relevanceScore: item.score,
|
|
1102
|
+
reasons: item.reasons,
|
|
1103
|
+
tokenCount: item.tokens,
|
|
1104
|
+
truncated: item.truncated
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
const contextSections = [];
|
|
1108
|
+
const nonProcedureItems = included.filter((i) => i.entity.type !== "procedure");
|
|
1109
|
+
if (included.length > 0) {
|
|
1110
|
+
if (procedureItems.length > 0) {
|
|
1111
|
+
contextSections.push(`## Procedures (${procedureItems.length} loaded, ${procedureUsed}/${procedureBudget} tokens)`);
|
|
1112
|
+
for (const item of procedureItems) {
|
|
1113
|
+
const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
|
|
1114
|
+
const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
|
|
1115
|
+
contextSections.push(`
|
|
1116
|
+
### ${item.entity.title} (confidence: ${item.entity.confidence})${tierLabel}${tags}`);
|
|
1117
|
+
contextSections.push(item.entity.content);
|
|
1118
|
+
}
|
|
820
1119
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1120
|
+
if (nonProcedureItems.length > 0) {
|
|
1121
|
+
contextSections.push(`
|
|
1122
|
+
## Relevant Memories (${nonProcedureItems.length} loaded, ${manifest.excluded.length} excluded)`);
|
|
1123
|
+
contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
|
|
1124
|
+
for (const item of nonProcedureItems) {
|
|
1125
|
+
const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
|
|
1126
|
+
const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
|
|
1127
|
+
contextSections.push(`
|
|
1128
|
+
### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
|
|
1129
|
+
contextSections.push(item.entity.content);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
incrementAccessCounts(client2, included.map((i) => i.entity.id)).catch(() => {});
|
|
1134
|
+
promoteEligibleEntities(client2, included.map((i) => i.entity)).catch(() => {});
|
|
1135
|
+
return {
|
|
1136
|
+
context: contextSections.join(`
|
|
1137
|
+
`),
|
|
1138
|
+
manifest,
|
|
1139
|
+
memories: included.map((i) => i.entity)
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
function mapToContextEntity(raw) {
|
|
1143
|
+
const e = raw;
|
|
1144
|
+
return {
|
|
1145
|
+
id: e.id,
|
|
1146
|
+
type: e.type,
|
|
1147
|
+
title: e.title,
|
|
1148
|
+
content: e.content,
|
|
1149
|
+
confidence: e.confidence ?? 1,
|
|
1150
|
+
tags: e.tags || [],
|
|
1151
|
+
memory_tier: e.memory_tier || "reference",
|
|
1152
|
+
access_count: e.access_count || 0,
|
|
1153
|
+
last_accessed_at: e.last_accessed_at || null,
|
|
1154
|
+
created_at: e.created_at || "",
|
|
1155
|
+
updated_at: e.updated_at || "",
|
|
1156
|
+
metadata: e.metadata ?? undefined,
|
|
1157
|
+
rrf_score: e.rrf_score ?? undefined,
|
|
1158
|
+
fts_rank: e.fts_rank ?? undefined,
|
|
1159
|
+
semantic_rank: e.semantic_rank ?? undefined
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
async function incrementAccessCounts(client2, entityIds) {
|
|
1163
|
+
if (entityIds.length === 0)
|
|
1164
|
+
return;
|
|
1165
|
+
try {
|
|
1166
|
+
await client2.batchTouchMemoryEntities(entityIds);
|
|
1167
|
+
} catch {
|
|
1168
|
+
await Promise.allSettled(entityIds.map((id) => client2.touchMemoryEntity(id)));
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
async function promoteEligibleEntities(client2, entities) {
|
|
1172
|
+
for (const entity of entities) {
|
|
1173
|
+
if (entity.memory_tier === "reference")
|
|
1174
|
+
continue;
|
|
1175
|
+
if (!entity.created_at)
|
|
1176
|
+
continue;
|
|
1177
|
+
const promotion = checkPromotion(entity.memory_tier, entity.access_count + 1, entity.confidence, entity.created_at);
|
|
1178
|
+
if (promotion.eligible && promotion.targetTier) {
|
|
1179
|
+
try {
|
|
1180
|
+
await client2.updateMemoryEntity(entity.id, {
|
|
1181
|
+
memory_tier: promotion.targetTier,
|
|
1182
|
+
metadata: {
|
|
1183
|
+
...entity.metadata || {},
|
|
1184
|
+
promoted_at: new Date().toISOString(),
|
|
1185
|
+
promotion_reason: promotion.reason,
|
|
1186
|
+
promoted_from: entity.memory_tier
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
} catch {}
|
|
880
1190
|
}
|
|
881
|
-
|
|
882
|
-
sessionAssemblyMap.delete(cardId);
|
|
883
|
-
return { adjusted };
|
|
1191
|
+
}
|
|
884
1192
|
}
|
|
1193
|
+
function cacheManifest(manifest) {
|
|
1194
|
+
if (manifestCache.size >= MAX_CACHE_SIZE) {
|
|
1195
|
+
const firstKey = manifestCache.keys().next().value;
|
|
1196
|
+
if (firstKey)
|
|
1197
|
+
manifestCache.delete(firstKey);
|
|
1198
|
+
}
|
|
1199
|
+
manifestCache.set(manifest.assemblyId, manifest);
|
|
1200
|
+
}
|
|
1201
|
+
function getCachedManifest(assemblyId) {
|
|
1202
|
+
return manifestCache.get(assemblyId);
|
|
1203
|
+
}
|
|
1204
|
+
function trackSessionAssembly(cardId, assemblyId) {
|
|
1205
|
+
if (sessionAssemblyMap.size >= MAX_SESSION_MAP_SIZE) {
|
|
1206
|
+
const firstKey = sessionAssemblyMap.keys().next().value;
|
|
1207
|
+
if (firstKey)
|
|
1208
|
+
sessionAssemblyMap.delete(firstKey);
|
|
1209
|
+
}
|
|
1210
|
+
sessionAssemblyMap.set(cardId, assemblyId);
|
|
1211
|
+
}
|
|
1212
|
+
function getSessionAssemblyId(cardId) {
|
|
1213
|
+
return sessionAssemblyMap.get(cardId);
|
|
1214
|
+
}
|
|
1215
|
+
async function recordContextFeedback(client2, cardId, sessionStatus, progressPercent, hadBlockers) {
|
|
1216
|
+
const assemblyId = sessionAssemblyMap.get(cardId);
|
|
1217
|
+
if (!assemblyId)
|
|
1218
|
+
return { adjusted: 0 };
|
|
1219
|
+
const manifest = manifestCache.get(assemblyId);
|
|
1220
|
+
if (!manifest || manifest.included.length === 0)
|
|
1221
|
+
return { adjusted: 0 };
|
|
1222
|
+
let adjusted = 0;
|
|
1223
|
+
const isSuccess = sessionStatus === "completed" && (progressPercent ?? 0) >= 100;
|
|
1224
|
+
for (const entry of manifest.included) {
|
|
1225
|
+
try {
|
|
1226
|
+
if (isSuccess) {
|
|
1227
|
+
const { entity } = await client2.getMemoryEntity(entry.entityId);
|
|
1228
|
+
const e = entity;
|
|
1229
|
+
const currentUsefulness = e.metadata?.usefulness_score ?? 0;
|
|
1230
|
+
const newConfidence = Math.min((e.confidence ?? 0.5) + 0.05, 1);
|
|
1231
|
+
await client2.updateMemoryEntity(entry.entityId, {
|
|
1232
|
+
confidence: newConfidence,
|
|
1233
|
+
metadata: {
|
|
1234
|
+
usefulness_score: currentUsefulness + 1,
|
|
1235
|
+
last_feedback_at: new Date().toISOString()
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
adjusted++;
|
|
1239
|
+
} else if (hadBlockers) {
|
|
1240
|
+
const { entity } = await client2.getMemoryEntity(entry.entityId);
|
|
1241
|
+
const e = entity;
|
|
1242
|
+
const newConfidence = Math.max((e.confidence ?? 0.5) - 0.02, 0.1);
|
|
1243
|
+
await client2.updateMemoryEntity(entry.entityId, {
|
|
1244
|
+
confidence: newConfidence,
|
|
1245
|
+
metadata: {
|
|
1246
|
+
last_feedback_at: new Date().toISOString()
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
adjusted++;
|
|
1250
|
+
}
|
|
1251
|
+
} catch {}
|
|
1252
|
+
}
|
|
1253
|
+
sessionAssemblyMap.delete(cardId);
|
|
1254
|
+
return { adjusted };
|
|
1255
|
+
}
|
|
1256
|
+
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;
|
|
1257
|
+
var init_context_assembly = __esm(() => {
|
|
1258
|
+
init_dist();
|
|
1259
|
+
TIER_WEIGHTS = {
|
|
1260
|
+
reference: 1,
|
|
1261
|
+
episode: 0.7,
|
|
1262
|
+
draft: 0.4
|
|
1263
|
+
};
|
|
1264
|
+
TIER_BUDGET_ALLOCATION = {
|
|
1265
|
+
reference: 0.6,
|
|
1266
|
+
episode: 0.3,
|
|
1267
|
+
draft: 0.1
|
|
1268
|
+
};
|
|
1269
|
+
RELATION_BONUSES = {
|
|
1270
|
+
depends_on: 0.15,
|
|
1271
|
+
resolved_by: 0.2,
|
|
1272
|
+
relates_to: 0.1,
|
|
1273
|
+
implements: 0.15,
|
|
1274
|
+
blocks: 0.15,
|
|
1275
|
+
references: 0.1,
|
|
1276
|
+
extends: 0.1,
|
|
1277
|
+
caused_by: 0.15
|
|
1278
|
+
};
|
|
1279
|
+
QUERY_SYNONYMS = {
|
|
1280
|
+
auth: ["authentication", "authorization", "session"],
|
|
1281
|
+
authentication: ["auth", "session", "sign-in"],
|
|
1282
|
+
login: ["sign-in", "authentication", "session"],
|
|
1283
|
+
bug: ["error", "issue", "defect", "problem"],
|
|
1284
|
+
error: ["exception", "failure", "issue"],
|
|
1285
|
+
fix: ["resolve", "patch", "repair", "correct"],
|
|
1286
|
+
deploy: ["deployment", "release", "ship", "publish"],
|
|
1287
|
+
test: ["testing", "spec", "assertion", "verify"],
|
|
1288
|
+
config: ["configuration", "settings", "setup"],
|
|
1289
|
+
db: ["database", "storage", "persistence"],
|
|
1290
|
+
database: ["storage", "persistence", "data store"],
|
|
1291
|
+
api: ["endpoint", "route", "service"],
|
|
1292
|
+
ui: ["frontend", "component", "view"],
|
|
1293
|
+
perf: ["performance", "speed", "latency"],
|
|
1294
|
+
performance: ["speed", "latency", "optimization"]
|
|
1295
|
+
};
|
|
1296
|
+
manifestCache = new Map;
|
|
1297
|
+
sessionAssemblyMap = new Map;
|
|
1298
|
+
});
|
|
1299
|
+
init_context_assembly();
|
|
1300
|
+
|
|
1301
|
+
export {
|
|
1302
|
+
trackSessionAssembly,
|
|
1303
|
+
recordContextFeedback,
|
|
1304
|
+
mapToContextEntity,
|
|
1305
|
+
getSessionAssemblyId,
|
|
1306
|
+
getCachedManifest,
|
|
1307
|
+
expandQuery,
|
|
1308
|
+
computeRelevanceScore,
|
|
1309
|
+
cacheManifest,
|
|
1310
|
+
assembleContext
|
|
1311
|
+
};
|