@gethmy/mcp 2.3.2 → 2.3.4
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 +199 -4
- package/dist/index.js +199 -4
- package/dist/lib/api-client.js +1 -433
- package/dist/lib/config.js +0 -18
- package/package.json +9 -5
- package/src/api-client.ts +6 -0
- package/src/memory-cleanup.ts +158 -0
- package/src/server.ts +140 -7
- package/dist/lib/active-learning.js +0 -974
- package/dist/lib/auto-session.js +0 -195
- package/dist/lib/cli.js +0 -34964
- package/dist/lib/consolidation.js +0 -388
- package/dist/lib/context-assembly.js +0 -1311
- package/dist/lib/graph-expansion.js +0 -147
- package/dist/lib/http.js +0 -1962
- package/dist/lib/index.js +0 -29527
- package/dist/lib/lifecycle-maintenance.js +0 -672
- package/dist/lib/memory-cleanup.js +0 -1361
- package/dist/lib/onboard.js +0 -2592
- package/dist/lib/prompt-builder.js +0 -481
- package/dist/lib/remote.js +0 -31756
- package/dist/lib/server.js +0 -29524
- package/dist/lib/skills.js +0 -776
- package/dist/lib/tui/agents.js +0 -137
- package/dist/lib/tui/docs.js +0 -1647
- package/dist/lib/tui/setup.js +0 -5828
- package/dist/lib/tui/theme.js +0 -192
- package/dist/lib/tui/writer.js +0 -1173
package/dist/lib/onboard.js
DELETED
|
@@ -1,2592 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
|
-
var __export = (target, all) => {
|
|
20
|
-
for (var name in all)
|
|
21
|
-
__defProp(target, name, {
|
|
22
|
-
get: all[name],
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
set: (newValue) => all[name] = () => newValue
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
29
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
-
|
|
31
|
-
// ../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;
|
|
58
|
-
}
|
|
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
|
-
};
|
|
157
|
-
}
|
|
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
|
-
};
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return base;
|
|
171
|
-
}
|
|
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
|
-
};
|
|
187
|
-
}
|
|
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
|
|
205
|
-
}
|
|
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;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return result;
|
|
317
|
-
}
|
|
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 as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
328
|
-
import { join as join2, 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 = join2(memoryDir, entity.workspace_id);
|
|
342
|
-
if (entity.scope === "private") {
|
|
343
|
-
return join2(wsDir, "_private");
|
|
344
|
-
}
|
|
345
|
-
if (entity.scope === "workspace" || !entity.project_id) {
|
|
346
|
-
return join2(wsDir, "_workspace");
|
|
347
|
-
}
|
|
348
|
-
return join2(wsDir, entity.project_id);
|
|
349
|
-
}
|
|
350
|
-
function emptySyncState() {
|
|
351
|
-
return { version: 1, lastPullAt: null, entities: {} };
|
|
352
|
-
}
|
|
353
|
-
function loadSyncState(memoryDir) {
|
|
354
|
-
const statePath = join2(memoryDir, ".sync-state.json");
|
|
355
|
-
if (!existsSync2(statePath))
|
|
356
|
-
return emptySyncState();
|
|
357
|
-
try {
|
|
358
|
-
return JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
359
|
-
} catch {
|
|
360
|
-
return emptySyncState();
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
function saveSyncState(memoryDir, state) {
|
|
364
|
-
if (!existsSync2(memoryDir)) {
|
|
365
|
-
mkdirSync2(memoryDir, { recursive: true });
|
|
366
|
-
}
|
|
367
|
-
writeFileSync2(join2(memoryDir, ".sync-state.json"), JSON.stringify(state, null, 2));
|
|
368
|
-
}
|
|
369
|
-
function writeEntityFile(entity, memoryDir) {
|
|
370
|
-
const dir = entityToDirectoryPath(entity, memoryDir);
|
|
371
|
-
if (!existsSync2(dir))
|
|
372
|
-
mkdirSync2(dir, { recursive: true });
|
|
373
|
-
const filename = entityToFilename(entity);
|
|
374
|
-
const filePath = join2(dir, filename);
|
|
375
|
-
const markdown = serializeSyncMarkdown(entity);
|
|
376
|
-
writeFileSync2(filePath, markdown);
|
|
377
|
-
return relative(memoryDir, filePath);
|
|
378
|
-
}
|
|
379
|
-
function deleteEntityFile(relPath, memoryDir) {
|
|
380
|
-
const absPath = join2(memoryDir, relPath);
|
|
381
|
-
if (existsSync2(absPath)) {
|
|
382
|
-
rmSync(absPath);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
function findMarkdownFiles(dir) {
|
|
386
|
-
const results = [];
|
|
387
|
-
if (!existsSync2(dir))
|
|
388
|
-
return results;
|
|
389
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
390
|
-
const fullPath = join2(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);
|
|
395
|
-
}
|
|
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;
|
|
428
|
-
}
|
|
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 = join2(memoryDir, existing.filePath);
|
|
448
|
-
let localChanged = false;
|
|
449
|
-
if (existsSync2(absPath)) {
|
|
450
|
-
const localContent = readFileSync2(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++;
|
|
467
|
-
}
|
|
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++;
|
|
474
|
-
}
|
|
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 = readFileSync2(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
|
-
writeFileSync2(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;
|
|
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 (!existsSync2(dir))
|
|
556
|
-
mkdirSync2(dir, { recursive: true });
|
|
557
|
-
const newFilename = entityToFilename(created);
|
|
558
|
-
const newAbsPath = join2(dir, newFilename);
|
|
559
|
-
const newMarkdown = serializeSyncMarkdown(created);
|
|
560
|
-
writeFileSync2(newAbsPath, newMarkdown);
|
|
561
|
-
if (absPath !== newAbsPath && existsSync2(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
|
-
}
|
|
574
|
-
}
|
|
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
|
-
}
|
|
663
|
-
}
|
|
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);
|
|
688
|
-
}
|
|
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);
|
|
705
|
-
}
|
|
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(",")})`);
|
|
735
|
-
}
|
|
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;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
score += bestBonus;
|
|
781
|
-
reasons.push(`graph_walk(${bestRelType})`);
|
|
782
|
-
}
|
|
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 }
|
|
814
|
-
}
|
|
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);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
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
|
-
}
|
|
850
|
-
}
|
|
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
|
-
}
|
|
868
|
-
}
|
|
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);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
} catch {}
|
|
891
|
-
}
|
|
892
|
-
if (candidates.length === 0) {
|
|
893
|
-
return {
|
|
894
|
-
context: "",
|
|
895
|
-
manifest,
|
|
896
|
-
memories: []
|
|
897
|
-
};
|
|
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: []
|
|
917
|
-
};
|
|
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 {}
|
|
943
|
-
}
|
|
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) {
|
|
967
|
-
included.push({ ...item, tokens, truncated });
|
|
968
|
-
item.entity.content = text;
|
|
969
|
-
totalUsed += tokens;
|
|
970
|
-
tierUsed.reference += tokens;
|
|
971
|
-
referenceCount++;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
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;
|
|
988
|
-
}
|
|
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"
|
|
1002
|
-
});
|
|
1003
|
-
continue;
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
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;
|
|
1016
|
-
}
|
|
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;
|
|
1038
|
-
}
|
|
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
|
-
}
|
|
1056
|
-
}
|
|
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;
|
|
1067
|
-
}
|
|
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
|
|
1088
|
-
}
|
|
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
|
-
}
|
|
1119
|
-
}
|
|
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 {}
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
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
|
-
|
|
1300
|
-
// src/prompt-builder.ts
|
|
1301
|
-
var exports_prompt_builder = {};
|
|
1302
|
-
__export(exports_prompt_builder, {
|
|
1303
|
-
inferCategoryFromLabels: () => inferCategoryFromLabels,
|
|
1304
|
-
getRoleFraming: () => getRoleFraming,
|
|
1305
|
-
getAvailableVariants: () => getAvailableVariants,
|
|
1306
|
-
getAvailableCategories: () => getAvailableCategories,
|
|
1307
|
-
generatePrompt: () => generatePrompt
|
|
1308
|
-
});
|
|
1309
|
-
function inferCategoryFromLabels(labels) {
|
|
1310
|
-
for (const label of labels) {
|
|
1311
|
-
const normalizedName = label.name.toLowerCase().trim();
|
|
1312
|
-
if (LABEL_CATEGORY_MAP[normalizedName]) {
|
|
1313
|
-
return LABEL_CATEGORY_MAP[normalizedName];
|
|
1314
|
-
}
|
|
1315
|
-
for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
|
|
1316
|
-
if (normalizedName.includes(key) || key.includes(normalizedName)) {
|
|
1317
|
-
return category;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
return "custom";
|
|
1322
|
-
}
|
|
1323
|
-
function getRoleFraming(category) {
|
|
1324
|
-
return DEFAULT_ROLE_FRAMINGS[category];
|
|
1325
|
-
}
|
|
1326
|
-
function estimateTokens2(text) {
|
|
1327
|
-
return Math.ceil(text.length / 4);
|
|
1328
|
-
}
|
|
1329
|
-
function formatSubtasks(subtasks) {
|
|
1330
|
-
if (subtasks.length === 0)
|
|
1331
|
-
return "";
|
|
1332
|
-
const completed = subtasks.filter((s) => s.completed).length;
|
|
1333
|
-
const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
|
|
1334
|
-
return `
|
|
1335
|
-
## Subtasks (${completed}/${subtasks.length} completed)
|
|
1336
|
-
${lines.join(`
|
|
1337
|
-
`)}`;
|
|
1338
|
-
}
|
|
1339
|
-
function formatLabels(labels) {
|
|
1340
|
-
if (labels.length === 0)
|
|
1341
|
-
return "";
|
|
1342
|
-
return `
|
|
1343
|
-
**Labels:** ${labels.map((l) => l.name).join(", ")}`;
|
|
1344
|
-
}
|
|
1345
|
-
function formatLinkedCards(links) {
|
|
1346
|
-
if (!links || links.length === 0)
|
|
1347
|
-
return "";
|
|
1348
|
-
const lines = links.map((link) => {
|
|
1349
|
-
const prefix = link.direction === "outgoing" ? "->" : "<-";
|
|
1350
|
-
return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
|
|
1351
|
-
});
|
|
1352
|
-
return `
|
|
1353
|
-
## Related Cards
|
|
1354
|
-
${lines.join(`
|
|
1355
|
-
`)}`;
|
|
1356
|
-
}
|
|
1357
|
-
function generatePrompt(options) {
|
|
1358
|
-
const {
|
|
1359
|
-
card,
|
|
1360
|
-
column,
|
|
1361
|
-
variant,
|
|
1362
|
-
customConstraints,
|
|
1363
|
-
memories,
|
|
1364
|
-
assembledContext,
|
|
1365
|
-
assemblyId
|
|
1366
|
-
} = options;
|
|
1367
|
-
const contextOpts = {
|
|
1368
|
-
includeTitle: true,
|
|
1369
|
-
includeDescription: true,
|
|
1370
|
-
includeLabels: true,
|
|
1371
|
-
includeSubtasks: true,
|
|
1372
|
-
includeActivity: false,
|
|
1373
|
-
includeAssignee: true,
|
|
1374
|
-
includeDueDate: true,
|
|
1375
|
-
includePriority: true,
|
|
1376
|
-
includeLinks: true,
|
|
1377
|
-
includeColumn: true,
|
|
1378
|
-
...options.contextOptions
|
|
1379
|
-
};
|
|
1380
|
-
const labels = card.labels || [];
|
|
1381
|
-
const subtasks = card.subtasks || [];
|
|
1382
|
-
const links = card.links || [];
|
|
1383
|
-
const category = inferCategoryFromLabels(labels);
|
|
1384
|
-
const roleFraming = getRoleFraming(category);
|
|
1385
|
-
const sections = [];
|
|
1386
|
-
sections.push(`# Role: ${roleFraming.role}
|
|
1387
|
-
`);
|
|
1388
|
-
sections.push(roleFraming.perspective);
|
|
1389
|
-
sections.push("");
|
|
1390
|
-
sections.push(VARIANT_INSTRUCTIONS[variant]);
|
|
1391
|
-
sections.push("");
|
|
1392
|
-
sections.push(`# Task: ${card.title}`);
|
|
1393
|
-
if (contextOpts.includeColumn && column) {
|
|
1394
|
-
sections.push(`**Status:** ${column.name}`);
|
|
1395
|
-
}
|
|
1396
|
-
if (contextOpts.includePriority) {
|
|
1397
|
-
sections.push(`**Priority:** ${card.priority}`);
|
|
1398
|
-
}
|
|
1399
|
-
if (contextOpts.includeDueDate && card.due_date) {
|
|
1400
|
-
sections.push(`**Due:** ${card.due_date}`);
|
|
1401
|
-
}
|
|
1402
|
-
if (contextOpts.includeAssignee && card.assignee) {
|
|
1403
|
-
sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
|
|
1404
|
-
}
|
|
1405
|
-
if (contextOpts.includeLabels && labels.length > 0) {
|
|
1406
|
-
sections.push(formatLabels(labels));
|
|
1407
|
-
}
|
|
1408
|
-
if (contextOpts.includeDescription && card.description) {
|
|
1409
|
-
sections.push(`
|
|
1410
|
-
## Description
|
|
1411
|
-
${card.description}`);
|
|
1412
|
-
}
|
|
1413
|
-
if (contextOpts.includeSubtasks && subtasks.length > 0) {
|
|
1414
|
-
sections.push(formatSubtasks(subtasks));
|
|
1415
|
-
}
|
|
1416
|
-
if (contextOpts.includeLinks && links.length > 0) {
|
|
1417
|
-
sections.push(formatLinkedCards(links));
|
|
1418
|
-
}
|
|
1419
|
-
sections.push(`
|
|
1420
|
-
## Focus Areas`);
|
|
1421
|
-
roleFraming.focus.forEach((f) => {
|
|
1422
|
-
sections.push(`- ${f}`);
|
|
1423
|
-
});
|
|
1424
|
-
sections.push(`- **Memory:** Store reusable knowledge via \`harmony_remember\`. Only store what a future agent couldn't easily discover from the code itself, applies beyond this specific card, and includes a "because" (not just what, but why).`);
|
|
1425
|
-
sections.push(` - GOOD: "BoardContext card state must use moveCard action, never direct setState — optimistic updates depend on action ordering"`);
|
|
1426
|
-
sections.push(` - GOOD: "Mobile bottom bar is 64px, overlaps fixed-position drawers — always add pb-16 to drawer content"`);
|
|
1427
|
-
sections.push(` - BAD: "Fixed the login button" (no reusable knowledge — the fix is in the code)`);
|
|
1428
|
-
sections.push(` - BAD: "Completed card #42" (ephemeral, auto-tracked by session)`);
|
|
1429
|
-
sections.push(`
|
|
1430
|
-
## Suggested Outputs`);
|
|
1431
|
-
roleFraming.outputSuggestions.forEach((s) => {
|
|
1432
|
-
sections.push(`- ${s}`);
|
|
1433
|
-
});
|
|
1434
|
-
if (assembledContext) {
|
|
1435
|
-
sections.push(`
|
|
1436
|
-
${assembledContext}`);
|
|
1437
|
-
} else if (memories && memories.length > 0) {
|
|
1438
|
-
sections.push(`
|
|
1439
|
-
## Relevant Memories`);
|
|
1440
|
-
sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
|
|
1441
|
-
for (const memory of memories) {
|
|
1442
|
-
const tags = memory.tags.length > 0 ? ` [${memory.tags.join(", ")}]` : "";
|
|
1443
|
-
sections.push(`
|
|
1444
|
-
### ${memory.title} (${memory.type}, confidence: ${memory.confidence})${tags}`);
|
|
1445
|
-
sections.push(memory.content);
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
const oneThingLine = synthesizeOneThing(card, subtasks, links, assembledContext);
|
|
1449
|
-
if (oneThingLine) {
|
|
1450
|
-
sections.push(`
|
|
1451
|
-
## Recommended Next Step
|
|
1452
|
-
${oneThingLine}`);
|
|
1453
|
-
}
|
|
1454
|
-
if (variant === "execute") {
|
|
1455
|
-
sections.push(`
|
|
1456
|
-
## Progress Tracking
|
|
1457
|
-
Update your progress by calling \`harmony_update_agent_progress\` with \`currentTask\` describing what you're doing now:
|
|
1458
|
-
- After exploring the codebase and understanding requirements (~20%)
|
|
1459
|
-
- When you start implementing changes (~50%)
|
|
1460
|
-
- When you move to testing or verification (~80%)
|
|
1461
|
-
- When done, before ending the session (100%)
|
|
1462
|
-
|
|
1463
|
-
Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working on card").`);
|
|
1464
|
-
}
|
|
1465
|
-
if (customConstraints) {
|
|
1466
|
-
sections.push(`
|
|
1467
|
-
## Additional Instructions
|
|
1468
|
-
${customConstraints}`);
|
|
1469
|
-
}
|
|
1470
|
-
sections.push(`
|
|
1471
|
-
---
|
|
1472
|
-
*Card #${card.short_id} | Generated for ${variant} mode*`);
|
|
1473
|
-
const prompt = sections.join(`
|
|
1474
|
-
`);
|
|
1475
|
-
const memoryCount = assembledContext ? (assembledContext.match(/^### /gm) || []).length : memories?.length || 0;
|
|
1476
|
-
return {
|
|
1477
|
-
prompt,
|
|
1478
|
-
variant,
|
|
1479
|
-
category,
|
|
1480
|
-
role: roleFraming.role,
|
|
1481
|
-
contextSummary: {
|
|
1482
|
-
hasDescription: !!card.description,
|
|
1483
|
-
labelCount: labels.length,
|
|
1484
|
-
subtaskCount: subtasks.length,
|
|
1485
|
-
completedSubtasks: subtasks.filter((s) => s.completed).length,
|
|
1486
|
-
linkedCardCount: links.length,
|
|
1487
|
-
memoryCount
|
|
1488
|
-
},
|
|
1489
|
-
tokenEstimate: estimateTokens2(prompt),
|
|
1490
|
-
...assemblyId && { assemblyId }
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
|
-
function extractSessionInsights(assembledContext) {
|
|
1494
|
-
const result = {
|
|
1495
|
-
lastSessionStatus: null,
|
|
1496
|
-
lastSessionTask: null,
|
|
1497
|
-
lastSessionProgress: null,
|
|
1498
|
-
blockers: [],
|
|
1499
|
-
procedureNextStep: null
|
|
1500
|
-
};
|
|
1501
|
-
const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
|
|
1502
|
-
if (sessionMatches && sessionMatches.length > 0) {
|
|
1503
|
-
const latest = sessionMatches[0];
|
|
1504
|
-
if (/Completed work on/i.test(latest)) {
|
|
1505
|
-
result.lastSessionStatus = "completed";
|
|
1506
|
-
} else if (/Paused work on|status:\s*paused/i.test(latest)) {
|
|
1507
|
-
result.lastSessionStatus = "paused";
|
|
1508
|
-
}
|
|
1509
|
-
const taskMatch = latest.match(/Final task:\s*(.+)/);
|
|
1510
|
-
if (taskMatch)
|
|
1511
|
-
result.lastSessionTask = taskMatch[1].trim();
|
|
1512
|
-
const progressMatch = latest.match(/Progress:\s*(\d+)%/);
|
|
1513
|
-
if (progressMatch)
|
|
1514
|
-
result.lastSessionProgress = parseInt(progressMatch[1], 10);
|
|
1515
|
-
}
|
|
1516
|
-
const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
|
|
1517
|
-
if (blockerMatches) {
|
|
1518
|
-
result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
|
|
1519
|
-
}
|
|
1520
|
-
const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
|
|
1521
|
-
if (stepMatches && stepMatches.length > 0) {
|
|
1522
|
-
result.procedureNextStep = stepMatches[0].replace(/^\d+\.\s+/, "").replace(/\s*\*\*\[key step\]\*\*.*$/, "").trim();
|
|
1523
|
-
}
|
|
1524
|
-
return result;
|
|
1525
|
-
}
|
|
1526
|
-
function synthesizeOneThing(card, subtasks, links, assembledContext) {
|
|
1527
|
-
if (card.done)
|
|
1528
|
-
return null;
|
|
1529
|
-
const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
|
|
1530
|
-
if (blockers.length > 0) {
|
|
1531
|
-
const blocker = blockers[0];
|
|
1532
|
-
return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
|
|
1533
|
-
}
|
|
1534
|
-
const session = assembledContext ? extractSessionInsights(assembledContext) : null;
|
|
1535
|
-
if (session?.blockers && session.blockers.length > 0) {
|
|
1536
|
-
return `Resolve blocker: ${session.blockers[0]}`;
|
|
1537
|
-
}
|
|
1538
|
-
if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
|
|
1539
|
-
const progress = session.lastSessionProgress ? ` (was ${session.lastSessionProgress}% complete)` : "";
|
|
1540
|
-
return `Resume previous session${progress}: "${session.lastSessionTask}".`;
|
|
1541
|
-
}
|
|
1542
|
-
if (subtasks.length > 0) {
|
|
1543
|
-
const completed = subtasks.filter((s) => s.completed).length;
|
|
1544
|
-
if (completed === subtasks.length) {
|
|
1545
|
-
return "All subtasks completed. Review the work and mark the card as done.";
|
|
1546
|
-
}
|
|
1547
|
-
const nextSubtask = subtasks.find((s) => !s.completed);
|
|
1548
|
-
if (nextSubtask) {
|
|
1549
|
-
return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
if (session?.procedureNextStep) {
|
|
1553
|
-
return `Follow procedure: ${session.procedureNextStep}`;
|
|
1554
|
-
}
|
|
1555
|
-
if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
|
|
1556
|
-
return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
|
|
1557
|
-
}
|
|
1558
|
-
if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
|
|
1559
|
-
return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
|
|
1560
|
-
}
|
|
1561
|
-
if (card.description) {
|
|
1562
|
-
return "Analyze the description, identify the approach, and begin implementation.";
|
|
1563
|
-
}
|
|
1564
|
-
return null;
|
|
1565
|
-
}
|
|
1566
|
-
function getAvailableCategories() {
|
|
1567
|
-
return ["bug", "feature", "design", "review", "onboarding", "epic", "custom"];
|
|
1568
|
-
}
|
|
1569
|
-
function getAvailableVariants() {
|
|
1570
|
-
return ["analysis", "draft", "execute"];
|
|
1571
|
-
}
|
|
1572
|
-
var LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
|
|
1573
|
-
var init_prompt_builder = __esm(() => {
|
|
1574
|
-
LABEL_CATEGORY_MAP = {
|
|
1575
|
-
bug: "bug",
|
|
1576
|
-
fix: "bug",
|
|
1577
|
-
hotfix: "bug",
|
|
1578
|
-
defect: "bug",
|
|
1579
|
-
issue: "bug",
|
|
1580
|
-
error: "bug",
|
|
1581
|
-
feature: "feature",
|
|
1582
|
-
enhancement: "feature",
|
|
1583
|
-
improvement: "feature",
|
|
1584
|
-
new: "feature",
|
|
1585
|
-
design: "design",
|
|
1586
|
-
ui: "design",
|
|
1587
|
-
ux: "design",
|
|
1588
|
-
frontend: "design",
|
|
1589
|
-
styling: "design",
|
|
1590
|
-
review: "review",
|
|
1591
|
-
"code review": "review",
|
|
1592
|
-
pr: "review",
|
|
1593
|
-
feedback: "review",
|
|
1594
|
-
onboarding: "onboarding",
|
|
1595
|
-
documentation: "onboarding",
|
|
1596
|
-
docs: "onboarding",
|
|
1597
|
-
guide: "onboarding",
|
|
1598
|
-
tutorial: "onboarding",
|
|
1599
|
-
epic: "epic",
|
|
1600
|
-
initiative: "epic",
|
|
1601
|
-
project: "epic",
|
|
1602
|
-
milestone: "epic"
|
|
1603
|
-
};
|
|
1604
|
-
DEFAULT_ROLE_FRAMINGS = {
|
|
1605
|
-
bug: {
|
|
1606
|
-
category: "bug",
|
|
1607
|
-
role: "Senior QA Engineer and Software Developer",
|
|
1608
|
-
perspective: "You are investigating and fixing a bug report.",
|
|
1609
|
-
focus: [
|
|
1610
|
-
"Root cause analysis",
|
|
1611
|
-
"Steps to reproduce",
|
|
1612
|
-
"Impact assessment",
|
|
1613
|
-
"Fix implementation",
|
|
1614
|
-
"Regression prevention",
|
|
1615
|
-
"Test cases to prevent recurrence"
|
|
1616
|
-
],
|
|
1617
|
-
outputSuggestions: [
|
|
1618
|
-
"Bug triage summary",
|
|
1619
|
-
"Root cause explanation",
|
|
1620
|
-
"Fix implementation plan",
|
|
1621
|
-
"Test cases"
|
|
1622
|
-
]
|
|
1623
|
-
},
|
|
1624
|
-
feature: {
|
|
1625
|
-
category: "feature",
|
|
1626
|
-
role: "Product Engineer",
|
|
1627
|
-
perspective: "You are implementing a new feature or enhancement.",
|
|
1628
|
-
focus: [
|
|
1629
|
-
"User requirements",
|
|
1630
|
-
"Technical specification",
|
|
1631
|
-
"Implementation approach",
|
|
1632
|
-
"Edge cases",
|
|
1633
|
-
"Acceptance criteria",
|
|
1634
|
-
"Integration points"
|
|
1635
|
-
],
|
|
1636
|
-
outputSuggestions: [
|
|
1637
|
-
"Technical specification",
|
|
1638
|
-
"Implementation tasks",
|
|
1639
|
-
"Acceptance criteria checklist",
|
|
1640
|
-
"API design"
|
|
1641
|
-
]
|
|
1642
|
-
},
|
|
1643
|
-
design: {
|
|
1644
|
-
category: "design",
|
|
1645
|
-
role: "UX Designer and Frontend Developer",
|
|
1646
|
-
perspective: "You are designing and implementing a user interface.",
|
|
1647
|
-
focus: [
|
|
1648
|
-
"User experience flow",
|
|
1649
|
-
"Visual design consistency",
|
|
1650
|
-
"Accessibility (WCAG)",
|
|
1651
|
-
"Responsive behavior",
|
|
1652
|
-
"Component architecture",
|
|
1653
|
-
"Interaction patterns"
|
|
1654
|
-
],
|
|
1655
|
-
outputSuggestions: [
|
|
1656
|
-
"User flow diagram",
|
|
1657
|
-
"Component specifications",
|
|
1658
|
-
"Accessibility checklist",
|
|
1659
|
-
"Responsive breakpoints"
|
|
1660
|
-
]
|
|
1661
|
-
},
|
|
1662
|
-
review: {
|
|
1663
|
-
category: "review",
|
|
1664
|
-
role: "Code Reviewer and Technical Lead",
|
|
1665
|
-
perspective: "You are reviewing code for quality, correctness, and maintainability.",
|
|
1666
|
-
focus: [
|
|
1667
|
-
"Code correctness",
|
|
1668
|
-
"Performance implications",
|
|
1669
|
-
"Security considerations",
|
|
1670
|
-
"Testing coverage",
|
|
1671
|
-
"Documentation",
|
|
1672
|
-
"Best practices adherence"
|
|
1673
|
-
],
|
|
1674
|
-
outputSuggestions: [
|
|
1675
|
-
"Review checklist",
|
|
1676
|
-
"Suggested improvements",
|
|
1677
|
-
"Test scenarios",
|
|
1678
|
-
"Security audit"
|
|
1679
|
-
]
|
|
1680
|
-
},
|
|
1681
|
-
onboarding: {
|
|
1682
|
-
category: "onboarding",
|
|
1683
|
-
role: "Technical Writer and Developer Advocate",
|
|
1684
|
-
perspective: "You are creating documentation or onboarding materials.",
|
|
1685
|
-
focus: [
|
|
1686
|
-
"Clear step-by-step instructions",
|
|
1687
|
-
"Prerequisites and setup",
|
|
1688
|
-
"Common pitfalls",
|
|
1689
|
-
"Examples and use cases",
|
|
1690
|
-
"Troubleshooting guide",
|
|
1691
|
-
"Related resources"
|
|
1692
|
-
],
|
|
1693
|
-
outputSuggestions: [
|
|
1694
|
-
"Getting started guide",
|
|
1695
|
-
"Step-by-step tutorial",
|
|
1696
|
-
"FAQ section",
|
|
1697
|
-
"Troubleshooting guide"
|
|
1698
|
-
]
|
|
1699
|
-
},
|
|
1700
|
-
epic: {
|
|
1701
|
-
category: "epic",
|
|
1702
|
-
role: "Technical Project Manager and Architect",
|
|
1703
|
-
perspective: "You are planning and coordinating a large initiative.",
|
|
1704
|
-
focus: [
|
|
1705
|
-
"Scope definition",
|
|
1706
|
-
"Task breakdown",
|
|
1707
|
-
"Dependencies",
|
|
1708
|
-
"Risk assessment",
|
|
1709
|
-
"Timeline considerations",
|
|
1710
|
-
"Success metrics"
|
|
1711
|
-
],
|
|
1712
|
-
outputSuggestions: [
|
|
1713
|
-
"Epic breakdown into stories",
|
|
1714
|
-
"Dependency graph",
|
|
1715
|
-
"Risk mitigation plan",
|
|
1716
|
-
"Success criteria"
|
|
1717
|
-
]
|
|
1718
|
-
},
|
|
1719
|
-
custom: {
|
|
1720
|
-
category: "custom",
|
|
1721
|
-
role: "Software Engineer",
|
|
1722
|
-
perspective: "You are working on a software task.",
|
|
1723
|
-
focus: [
|
|
1724
|
-
"Understanding requirements",
|
|
1725
|
-
"Implementation approach",
|
|
1726
|
-
"Quality considerations",
|
|
1727
|
-
"Testing strategy"
|
|
1728
|
-
],
|
|
1729
|
-
outputSuggestions: [
|
|
1730
|
-
"Implementation plan",
|
|
1731
|
-
"Technical notes",
|
|
1732
|
-
"Task checklist"
|
|
1733
|
-
]
|
|
1734
|
-
}
|
|
1735
|
-
};
|
|
1736
|
-
VARIANT_INSTRUCTIONS = {
|
|
1737
|
-
analysis: `ANALYSIS MODE: Analyze this task thoroughly. Identify requirements, constraints, edge cases, and potential challenges. Do NOT implement anything yet - focus on understanding and planning.`,
|
|
1738
|
-
draft: `DRAFT MODE: Create a detailed implementation plan with code structure, key decisions, and approach. Include pseudocode or skeleton code where helpful. This is for review before full implementation.`,
|
|
1739
|
-
execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
|
|
1740
|
-
};
|
|
1741
|
-
});
|
|
1742
|
-
|
|
1743
|
-
// src/config.ts
|
|
1744
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1745
|
-
import { homedir } from "node:os";
|
|
1746
|
-
import { join } from "node:path";
|
|
1747
|
-
var DEFAULT_API_URL = "https://app.gethmy.com/api";
|
|
1748
|
-
var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
|
|
1749
|
-
function getConfigDir() {
|
|
1750
|
-
return join(homedir(), ".harmony-mcp");
|
|
1751
|
-
}
|
|
1752
|
-
function getConfigPath() {
|
|
1753
|
-
return join(getConfigDir(), "config.json");
|
|
1754
|
-
}
|
|
1755
|
-
function getLocalConfigPath(cwd) {
|
|
1756
|
-
return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
1757
|
-
}
|
|
1758
|
-
function loadConfig() {
|
|
1759
|
-
const configPath = getConfigPath();
|
|
1760
|
-
if (!existsSync(configPath)) {
|
|
1761
|
-
return {
|
|
1762
|
-
apiKey: null,
|
|
1763
|
-
apiUrl: DEFAULT_API_URL,
|
|
1764
|
-
activeWorkspaceId: null,
|
|
1765
|
-
activeProjectId: null,
|
|
1766
|
-
userEmail: null,
|
|
1767
|
-
memoryDir: null
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
try {
|
|
1771
|
-
const data = readFileSync(configPath, "utf-8");
|
|
1772
|
-
const config = JSON.parse(data);
|
|
1773
|
-
return {
|
|
1774
|
-
apiKey: config.apiKey || null,
|
|
1775
|
-
apiUrl: config.apiUrl || DEFAULT_API_URL,
|
|
1776
|
-
activeWorkspaceId: config.activeWorkspaceId || null,
|
|
1777
|
-
activeProjectId: config.activeProjectId || null,
|
|
1778
|
-
userEmail: config.userEmail || null,
|
|
1779
|
-
memoryDir: config.memoryDir || null
|
|
1780
|
-
};
|
|
1781
|
-
} catch {
|
|
1782
|
-
return {
|
|
1783
|
-
apiKey: null,
|
|
1784
|
-
apiUrl: DEFAULT_API_URL,
|
|
1785
|
-
activeWorkspaceId: null,
|
|
1786
|
-
activeProjectId: null,
|
|
1787
|
-
userEmail: null,
|
|
1788
|
-
memoryDir: null
|
|
1789
|
-
};
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
function saveConfig(config) {
|
|
1793
|
-
const configDir = getConfigDir();
|
|
1794
|
-
const configPath = getConfigPath();
|
|
1795
|
-
if (!existsSync(configDir)) {
|
|
1796
|
-
mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
1797
|
-
}
|
|
1798
|
-
const existingConfig = loadConfig();
|
|
1799
|
-
const newConfig = { ...existingConfig, ...config };
|
|
1800
|
-
writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
|
|
1801
|
-
mode: 384
|
|
1802
|
-
});
|
|
1803
|
-
}
|
|
1804
|
-
function loadLocalConfig(cwd) {
|
|
1805
|
-
const localConfigPath = getLocalConfigPath(cwd);
|
|
1806
|
-
if (!existsSync(localConfigPath)) {
|
|
1807
|
-
return null;
|
|
1808
|
-
}
|
|
1809
|
-
try {
|
|
1810
|
-
const data = readFileSync(localConfigPath, "utf-8");
|
|
1811
|
-
const config = JSON.parse(data);
|
|
1812
|
-
return {
|
|
1813
|
-
workspaceId: config.workspaceId || null,
|
|
1814
|
-
projectId: config.projectId || null
|
|
1815
|
-
};
|
|
1816
|
-
} catch {
|
|
1817
|
-
return null;
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
function saveLocalConfig(config, cwd) {
|
|
1821
|
-
const localConfigPath = getLocalConfigPath(cwd);
|
|
1822
|
-
const existingConfig = loadLocalConfig(cwd) || {
|
|
1823
|
-
workspaceId: null,
|
|
1824
|
-
projectId: null
|
|
1825
|
-
};
|
|
1826
|
-
const newConfig = { ...existingConfig, ...config };
|
|
1827
|
-
const cleanConfig = {};
|
|
1828
|
-
if (newConfig.workspaceId)
|
|
1829
|
-
cleanConfig.workspaceId = newConfig.workspaceId;
|
|
1830
|
-
if (newConfig.projectId)
|
|
1831
|
-
cleanConfig.projectId = newConfig.projectId;
|
|
1832
|
-
writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
|
|
1833
|
-
}
|
|
1834
|
-
function hasLocalConfig(cwd) {
|
|
1835
|
-
return existsSync(getLocalConfigPath(cwd));
|
|
1836
|
-
}
|
|
1837
|
-
function getApiKey() {
|
|
1838
|
-
const config = loadConfig();
|
|
1839
|
-
if (!config.apiKey) {
|
|
1840
|
-
throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
|
|
1841
|
-
` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
|
|
1842
|
-
}
|
|
1843
|
-
return config.apiKey;
|
|
1844
|
-
}
|
|
1845
|
-
function getApiUrl() {
|
|
1846
|
-
const config = loadConfig();
|
|
1847
|
-
return config.apiUrl;
|
|
1848
|
-
}
|
|
1849
|
-
function getUserEmail() {
|
|
1850
|
-
const config = loadConfig();
|
|
1851
|
-
return config.userEmail;
|
|
1852
|
-
}
|
|
1853
|
-
function setUserEmail(email) {
|
|
1854
|
-
saveConfig({ userEmail: email });
|
|
1855
|
-
}
|
|
1856
|
-
function setActiveWorkspace(workspaceId, options) {
|
|
1857
|
-
if (options?.local) {
|
|
1858
|
-
saveLocalConfig({ workspaceId }, options.cwd);
|
|
1859
|
-
} else {
|
|
1860
|
-
saveConfig({ activeWorkspaceId: workspaceId });
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
function setActiveProject(projectId, options) {
|
|
1864
|
-
if (options?.local) {
|
|
1865
|
-
saveLocalConfig({ projectId }, options.cwd);
|
|
1866
|
-
} else {
|
|
1867
|
-
saveConfig({ activeProjectId: projectId });
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
function getActiveWorkspaceId(cwd) {
|
|
1871
|
-
const localConfig = loadLocalConfig(cwd);
|
|
1872
|
-
if (localConfig?.workspaceId) {
|
|
1873
|
-
return localConfig.workspaceId;
|
|
1874
|
-
}
|
|
1875
|
-
return loadConfig().activeWorkspaceId;
|
|
1876
|
-
}
|
|
1877
|
-
function getActiveProjectId(cwd) {
|
|
1878
|
-
const localConfig = loadLocalConfig(cwd);
|
|
1879
|
-
if (localConfig?.projectId) {
|
|
1880
|
-
return localConfig.projectId;
|
|
1881
|
-
}
|
|
1882
|
-
return loadConfig().activeProjectId;
|
|
1883
|
-
}
|
|
1884
|
-
function isConfigured() {
|
|
1885
|
-
const config = loadConfig();
|
|
1886
|
-
return !!config.apiKey;
|
|
1887
|
-
}
|
|
1888
|
-
function areSkillsInstalled(cwd) {
|
|
1889
|
-
const home = homedir();
|
|
1890
|
-
const workingDir = cwd || process.cwd();
|
|
1891
|
-
const foundPaths = [];
|
|
1892
|
-
const globalSkillsDir = join(home, ".agents", "skills");
|
|
1893
|
-
const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
|
|
1894
|
-
if (existsSync(globalSkillPath)) {
|
|
1895
|
-
foundPaths.push(globalSkillPath);
|
|
1896
|
-
return { installed: true, location: "global", paths: foundPaths };
|
|
1897
|
-
}
|
|
1898
|
-
const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
|
|
1899
|
-
if (existsSync(claudeGlobalSkill)) {
|
|
1900
|
-
foundPaths.push(claudeGlobalSkill);
|
|
1901
|
-
return { installed: true, location: "global", paths: foundPaths };
|
|
1902
|
-
}
|
|
1903
|
-
const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
|
|
1904
|
-
if (existsSync(claudeGlobalSkillAlt)) {
|
|
1905
|
-
foundPaths.push(claudeGlobalSkillAlt);
|
|
1906
|
-
return { installed: true, location: "global", paths: foundPaths };
|
|
1907
|
-
}
|
|
1908
|
-
const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
|
|
1909
|
-
if (existsSync(localSkillPath)) {
|
|
1910
|
-
foundPaths.push(localSkillPath);
|
|
1911
|
-
return { installed: true, location: "local", paths: foundPaths };
|
|
1912
|
-
}
|
|
1913
|
-
const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
|
|
1914
|
-
if (existsSync(localSkillPathAlt)) {
|
|
1915
|
-
foundPaths.push(localSkillPathAlt);
|
|
1916
|
-
return { installed: true, location: "local", paths: foundPaths };
|
|
1917
|
-
}
|
|
1918
|
-
return { installed: false, location: null, paths: [] };
|
|
1919
|
-
}
|
|
1920
|
-
function hasProjectContext(cwd) {
|
|
1921
|
-
const localConfig = loadLocalConfig(cwd);
|
|
1922
|
-
return !!(localConfig?.workspaceId || localConfig?.projectId);
|
|
1923
|
-
}
|
|
1924
|
-
function getMemoryDir() {
|
|
1925
|
-
const config = loadConfig();
|
|
1926
|
-
if (config.memoryDir)
|
|
1927
|
-
return config.memoryDir;
|
|
1928
|
-
return join(homedir(), ".harmony", "memory");
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
// src/api-client.ts
|
|
1932
|
-
var RETRY_CONFIG = {
|
|
1933
|
-
maxRetries: 3,
|
|
1934
|
-
baseDelayMs: 1000,
|
|
1935
|
-
maxDelayMs: 1e4,
|
|
1936
|
-
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
|
|
1937
|
-
};
|
|
1938
|
-
function isRetryableError(error, status) {
|
|
1939
|
-
if (status && RETRY_CONFIG.retryableStatusCodes.includes(status)) {
|
|
1940
|
-
return true;
|
|
1941
|
-
}
|
|
1942
|
-
if (error instanceof TypeError)
|
|
1943
|
-
return true;
|
|
1944
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
1945
|
-
return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed");
|
|
1946
|
-
}
|
|
1947
|
-
function getRetryDelay(attempt) {
|
|
1948
|
-
const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
|
|
1949
|
-
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
1950
|
-
}
|
|
1951
|
-
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1952
|
-
|
|
1953
|
-
class Semaphore {
|
|
1954
|
-
permits;
|
|
1955
|
-
queue = [];
|
|
1956
|
-
constructor(permits) {
|
|
1957
|
-
this.permits = permits;
|
|
1958
|
-
}
|
|
1959
|
-
async acquire() {
|
|
1960
|
-
if (this.permits > 0) {
|
|
1961
|
-
this.permits--;
|
|
1962
|
-
return;
|
|
1963
|
-
}
|
|
1964
|
-
return new Promise((resolve) => this.queue.push(resolve));
|
|
1965
|
-
}
|
|
1966
|
-
release() {
|
|
1967
|
-
const next = this.queue.shift();
|
|
1968
|
-
if (next)
|
|
1969
|
-
next();
|
|
1970
|
-
else
|
|
1971
|
-
this.permits++;
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
var requestSemaphore = new Semaphore(3);
|
|
1975
|
-
async function signupUser(apiUrl, data) {
|
|
1976
|
-
const url = `${apiUrl}/v1/auth/signup`;
|
|
1977
|
-
const response = await fetch(url, {
|
|
1978
|
-
method: "POST",
|
|
1979
|
-
headers: { "Content-Type": "application/json" },
|
|
1980
|
-
body: JSON.stringify(data)
|
|
1981
|
-
});
|
|
1982
|
-
const result = await response.json();
|
|
1983
|
-
if (!response.ok) {
|
|
1984
|
-
throw new Error(result.error || `Signup failed: ${response.status}`);
|
|
1985
|
-
}
|
|
1986
|
-
return result;
|
|
1987
|
-
}
|
|
1988
|
-
async function requestWithBearer(apiUrl, bearerToken, method, path, body) {
|
|
1989
|
-
const url = `${apiUrl}/v1${path}`;
|
|
1990
|
-
const response = await fetch(url, {
|
|
1991
|
-
method,
|
|
1992
|
-
headers: {
|
|
1993
|
-
"Content-Type": "application/json",
|
|
1994
|
-
Authorization: `Bearer ${bearerToken}`
|
|
1995
|
-
},
|
|
1996
|
-
body: body ? JSON.stringify(body) : undefined
|
|
1997
|
-
});
|
|
1998
|
-
const result = await response.json();
|
|
1999
|
-
if (!response.ok) {
|
|
2000
|
-
throw new Error(result.error || `API error: ${response.status}`);
|
|
2001
|
-
}
|
|
2002
|
-
return result;
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
class HarmonyApiClient {
|
|
2006
|
-
apiKey;
|
|
2007
|
-
apiUrl;
|
|
2008
|
-
constructor(options) {
|
|
2009
|
-
this.apiKey = options?.apiKey ?? getApiKey();
|
|
2010
|
-
this.apiUrl = options?.apiUrl ?? getApiUrl();
|
|
2011
|
-
}
|
|
2012
|
-
getApiUrl() {
|
|
2013
|
-
return this.apiUrl;
|
|
2014
|
-
}
|
|
2015
|
-
async request(method, path, body, options) {
|
|
2016
|
-
await requestSemaphore.acquire();
|
|
2017
|
-
try {
|
|
2018
|
-
return await this.requestWithRetry(method, path, body, options);
|
|
2019
|
-
} finally {
|
|
2020
|
-
requestSemaphore.release();
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
async requestRaw(method, path, body, options) {
|
|
2024
|
-
await requestSemaphore.acquire();
|
|
2025
|
-
try {
|
|
2026
|
-
return await this.requestRawWithRetry(method, path, body, options);
|
|
2027
|
-
} finally {
|
|
2028
|
-
requestSemaphore.release();
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
async requestWithRetry(method, path, body, options) {
|
|
2032
|
-
const url = `${this.apiUrl}/v1${path}`;
|
|
2033
|
-
let lastError = null;
|
|
2034
|
-
const contentType = options?.contentType || "application/json";
|
|
2035
|
-
const accept = options?.accept || "application/json";
|
|
2036
|
-
for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
2037
|
-
try {
|
|
2038
|
-
const response = await fetch(url, {
|
|
2039
|
-
method,
|
|
2040
|
-
headers: {
|
|
2041
|
-
"Content-Type": contentType,
|
|
2042
|
-
Accept: accept,
|
|
2043
|
-
"X-API-Key": this.apiKey
|
|
2044
|
-
},
|
|
2045
|
-
body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined)
|
|
2046
|
-
});
|
|
2047
|
-
const data = await response.json();
|
|
2048
|
-
if (!response.ok) {
|
|
2049
|
-
const errorMsg = data.error || `API error: ${response.status}`;
|
|
2050
|
-
if (!isRetryableError(null, response.status)) {
|
|
2051
|
-
throw new Error(errorMsg);
|
|
2052
|
-
}
|
|
2053
|
-
lastError = new Error(errorMsg);
|
|
2054
|
-
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
2055
|
-
await sleep(getRetryDelay(attempt));
|
|
2056
|
-
continue;
|
|
2057
|
-
}
|
|
2058
|
-
throw lastError;
|
|
2059
|
-
}
|
|
2060
|
-
return data;
|
|
2061
|
-
} catch (error) {
|
|
2062
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2063
|
-
if (!isRetryableError(error))
|
|
2064
|
-
throw lastError;
|
|
2065
|
-
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
2066
|
-
await sleep(getRetryDelay(attempt));
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
throw lastError || new Error("Request failed after retries");
|
|
2071
|
-
}
|
|
2072
|
-
async requestRawWithRetry(method, path, body, options) {
|
|
2073
|
-
const url = `${this.apiUrl}/v1${path}`;
|
|
2074
|
-
let lastError = null;
|
|
2075
|
-
const contentType = options?.contentType || "application/json";
|
|
2076
|
-
const accept = options?.accept || "text/markdown";
|
|
2077
|
-
for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
2078
|
-
try {
|
|
2079
|
-
const response = await fetch(url, {
|
|
2080
|
-
method,
|
|
2081
|
-
headers: {
|
|
2082
|
-
"Content-Type": contentType,
|
|
2083
|
-
Accept: accept,
|
|
2084
|
-
"X-API-Key": this.apiKey
|
|
2085
|
-
},
|
|
2086
|
-
body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined)
|
|
2087
|
-
});
|
|
2088
|
-
if (!response.ok) {
|
|
2089
|
-
const text = await response.text();
|
|
2090
|
-
let errorMsg;
|
|
2091
|
-
try {
|
|
2092
|
-
errorMsg = JSON.parse(text).error || `API error: ${response.status}`;
|
|
2093
|
-
} catch {
|
|
2094
|
-
errorMsg = text || `API error: ${response.status}`;
|
|
2095
|
-
}
|
|
2096
|
-
if (!isRetryableError(null, response.status)) {
|
|
2097
|
-
throw new Error(errorMsg);
|
|
2098
|
-
}
|
|
2099
|
-
lastError = new Error(errorMsg);
|
|
2100
|
-
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
2101
|
-
await sleep(getRetryDelay(attempt));
|
|
2102
|
-
continue;
|
|
2103
|
-
}
|
|
2104
|
-
throw lastError;
|
|
2105
|
-
}
|
|
2106
|
-
return await response.text();
|
|
2107
|
-
} catch (error) {
|
|
2108
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2109
|
-
if (!isRetryableError(error))
|
|
2110
|
-
throw lastError;
|
|
2111
|
-
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
2112
|
-
await sleep(getRetryDelay(attempt));
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
throw lastError || new Error("Request failed after retries");
|
|
2117
|
-
}
|
|
2118
|
-
async listWorkspaces() {
|
|
2119
|
-
return this.request("GET", "/workspaces");
|
|
2120
|
-
}
|
|
2121
|
-
async getWorkspaceMembers(workspaceId) {
|
|
2122
|
-
return this.request("GET", `/workspaces/${workspaceId}/members`);
|
|
2123
|
-
}
|
|
2124
|
-
async listProjects(workspaceId) {
|
|
2125
|
-
return this.request("GET", `/workspaces/${workspaceId}/projects`);
|
|
2126
|
-
}
|
|
2127
|
-
async getBoard(projectId, options) {
|
|
2128
|
-
const params = new URLSearchParams;
|
|
2129
|
-
if (options?.limit !== undefined)
|
|
2130
|
-
params.set("limit", String(options.limit));
|
|
2131
|
-
if (options?.offset !== undefined)
|
|
2132
|
-
params.set("offset", String(options.offset));
|
|
2133
|
-
if (options?.columnId)
|
|
2134
|
-
params.set("column_id", options.columnId);
|
|
2135
|
-
if (options?.summary)
|
|
2136
|
-
params.set("summary", "true");
|
|
2137
|
-
if (options?.includeArchived)
|
|
2138
|
-
params.set("include_archived", "true");
|
|
2139
|
-
if (options?.labelName)
|
|
2140
|
-
params.set("label_name", options.labelName);
|
|
2141
|
-
const query = params.toString() ? `?${params.toString()}` : "";
|
|
2142
|
-
return this.request("GET", `/board/${projectId}${query}`);
|
|
2143
|
-
}
|
|
2144
|
-
async createCard(projectId, data) {
|
|
2145
|
-
return this.request("POST", "/cards", { projectId, ...data });
|
|
2146
|
-
}
|
|
2147
|
-
async updateCard(cardId, updates) {
|
|
2148
|
-
return this.request("PATCH", `/cards/${cardId}`, updates);
|
|
2149
|
-
}
|
|
2150
|
-
async moveCard(cardId, columnId, position) {
|
|
2151
|
-
return this.request("POST", `/cards/${cardId}/move`, {
|
|
2152
|
-
columnId,
|
|
2153
|
-
position
|
|
2154
|
-
});
|
|
2155
|
-
}
|
|
2156
|
-
async archiveCard(cardId) {
|
|
2157
|
-
return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
|
|
2158
|
-
}
|
|
2159
|
-
async unarchiveCard(cardId) {
|
|
2160
|
-
return this.updateCard(cardId, { archivedAt: null });
|
|
2161
|
-
}
|
|
2162
|
-
async deleteCard(cardId) {
|
|
2163
|
-
return this.request("DELETE", `/cards/${cardId}`);
|
|
2164
|
-
}
|
|
2165
|
-
async getCard(cardId) {
|
|
2166
|
-
return this.request("GET", `/cards/${cardId}`);
|
|
2167
|
-
}
|
|
2168
|
-
async getCardByShortId(projectId, shortId) {
|
|
2169
|
-
return this.request("GET", `/projects/${projectId}/cards/${shortId}`);
|
|
2170
|
-
}
|
|
2171
|
-
async searchCards(query, options) {
|
|
2172
|
-
const params = new URLSearchParams({ q: query });
|
|
2173
|
-
if (options?.projectId) {
|
|
2174
|
-
params.set("project_id", options.projectId);
|
|
2175
|
-
}
|
|
2176
|
-
return this.request("GET", `/search?${params.toString()}`);
|
|
2177
|
-
}
|
|
2178
|
-
async addLabelToCard(cardId, labelId) {
|
|
2179
|
-
return this.request("POST", `/cards/${cardId}/labels`, { labelId });
|
|
2180
|
-
}
|
|
2181
|
-
async removeLabelFromCard(cardId, labelId) {
|
|
2182
|
-
return this.request("DELETE", `/cards/${cardId}/labels/${labelId}`);
|
|
2183
|
-
}
|
|
2184
|
-
async addLinkToCard(sourceCardId, targetCardId, linkType) {
|
|
2185
|
-
return this.request("POST", `/cards/${sourceCardId}/links`, {
|
|
2186
|
-
targetCardId,
|
|
2187
|
-
linkType
|
|
2188
|
-
});
|
|
2189
|
-
}
|
|
2190
|
-
async removeLinkFromCard(linkId) {
|
|
2191
|
-
return this.request("DELETE", `/card-links/${linkId}`);
|
|
2192
|
-
}
|
|
2193
|
-
async getCardLinks(cardId) {
|
|
2194
|
-
return this.request("GET", `/cards/${cardId}/links`);
|
|
2195
|
-
}
|
|
2196
|
-
async createColumn(projectId, name) {
|
|
2197
|
-
return this.request("POST", "/columns", { projectId, name });
|
|
2198
|
-
}
|
|
2199
|
-
async updateColumn(columnId, name) {
|
|
2200
|
-
return this.request("PATCH", `/columns/${columnId}`, { name });
|
|
2201
|
-
}
|
|
2202
|
-
async deleteColumn(columnId) {
|
|
2203
|
-
return this.request("DELETE", `/columns/${columnId}`);
|
|
2204
|
-
}
|
|
2205
|
-
async createLabel(projectId, data) {
|
|
2206
|
-
return this.request("POST", "/labels", { projectId, ...data });
|
|
2207
|
-
}
|
|
2208
|
-
async createSubtask(cardId, title) {
|
|
2209
|
-
return this.request("POST", "/subtasks", { cardId, title });
|
|
2210
|
-
}
|
|
2211
|
-
async toggleSubtask(subtaskId) {
|
|
2212
|
-
return this.request("POST", `/subtasks/${subtaskId}/toggle`);
|
|
2213
|
-
}
|
|
2214
|
-
async deleteSubtask(subtaskId) {
|
|
2215
|
-
return this.request("DELETE", `/subtasks/${subtaskId}`);
|
|
2216
|
-
}
|
|
2217
|
-
async startAgentSession(cardId, data) {
|
|
2218
|
-
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
2219
|
-
}
|
|
2220
|
-
async updateAgentProgress(cardId, data) {
|
|
2221
|
-
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
2222
|
-
}
|
|
2223
|
-
async endAgentSession(cardId, data) {
|
|
2224
|
-
return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
|
|
2225
|
-
}
|
|
2226
|
-
async flushActivityLog(cardId, data) {
|
|
2227
|
-
return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
|
|
2228
|
-
}
|
|
2229
|
-
async getActivityLog(cardId, sessionId) {
|
|
2230
|
-
return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
|
|
2231
|
-
}
|
|
2232
|
-
async getAgentSession(cardId, options) {
|
|
2233
|
-
const params = new URLSearchParams;
|
|
2234
|
-
if (options?.includeEnded)
|
|
2235
|
-
params.set("include_ended", "true");
|
|
2236
|
-
const query = params.toString() ? `?${params.toString()}` : "";
|
|
2237
|
-
return this.request("GET", `/cards/${cardId}/agent-context${query}`);
|
|
2238
|
-
}
|
|
2239
|
-
async getAgentProfile(workspaceId, agentIdentifier) {
|
|
2240
|
-
const params = new URLSearchParams({
|
|
2241
|
-
workspace_id: workspaceId,
|
|
2242
|
-
agent_identifier: agentIdentifier
|
|
2243
|
-
});
|
|
2244
|
-
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
2245
|
-
}
|
|
2246
|
-
async listAgentProfiles(workspaceId) {
|
|
2247
|
-
const params = new URLSearchParams({ workspace_id: workspaceId });
|
|
2248
|
-
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
2249
|
-
}
|
|
2250
|
-
async refreshAgentProfiles(workspaceId) {
|
|
2251
|
-
return this.request("POST", "/agent-profiles/refresh", {
|
|
2252
|
-
workspace_id: workspaceId
|
|
2253
|
-
});
|
|
2254
|
-
}
|
|
2255
|
-
async createMemoryEntity(data) {
|
|
2256
|
-
return this.request("POST", "/memory/entities", data);
|
|
2257
|
-
}
|
|
2258
|
-
async listMemoryEntities(options) {
|
|
2259
|
-
const params = new URLSearchParams;
|
|
2260
|
-
params.set("workspace_id", options.workspace_id);
|
|
2261
|
-
if (options.project_id)
|
|
2262
|
-
params.set("project_id", options.project_id);
|
|
2263
|
-
if (options.type)
|
|
2264
|
-
params.set("type", options.type);
|
|
2265
|
-
if (options.scope)
|
|
2266
|
-
params.set("scope", options.scope);
|
|
2267
|
-
if (options.tags?.length)
|
|
2268
|
-
params.set("tags", options.tags.join(","));
|
|
2269
|
-
if (options.agent_identifier)
|
|
2270
|
-
params.set("agent_identifier", options.agent_identifier);
|
|
2271
|
-
if (options.min_confidence !== undefined)
|
|
2272
|
-
params.set("min_confidence", String(options.min_confidence));
|
|
2273
|
-
if (options.q)
|
|
2274
|
-
params.set("q", options.q);
|
|
2275
|
-
if (options.limit !== undefined)
|
|
2276
|
-
params.set("limit", String(options.limit));
|
|
2277
|
-
if (options.offset !== undefined)
|
|
2278
|
-
params.set("offset", String(options.offset));
|
|
2279
|
-
return this.request("GET", `/memory/entities?${params.toString()}`);
|
|
2280
|
-
}
|
|
2281
|
-
async getMemoryEntity(entityId) {
|
|
2282
|
-
return this.request("GET", `/memory/entities/${entityId}`);
|
|
2283
|
-
}
|
|
2284
|
-
async updateMemoryEntity(entityId, updates) {
|
|
2285
|
-
return this.request("PUT", `/memory/entities/${entityId}`, updates);
|
|
2286
|
-
}
|
|
2287
|
-
async deleteMemoryEntity(entityId) {
|
|
2288
|
-
return this.request("DELETE", `/memory/entities/${entityId}`);
|
|
2289
|
-
}
|
|
2290
|
-
async touchMemoryEntity(entityId) {
|
|
2291
|
-
return this.request("POST", `/memory/entities/${entityId}/touch`);
|
|
2292
|
-
}
|
|
2293
|
-
async batchTouchMemoryEntities(entityIds) {
|
|
2294
|
-
return this.request("POST", "/memory/entities/batch-touch", {
|
|
2295
|
-
entity_ids: entityIds
|
|
2296
|
-
});
|
|
2297
|
-
}
|
|
2298
|
-
async createMemoryRelation(data) {
|
|
2299
|
-
return this.request("POST", "/memory/relations", data);
|
|
2300
|
-
}
|
|
2301
|
-
async deleteMemoryRelation(relationId) {
|
|
2302
|
-
return this.request("DELETE", `/memory/relations/${relationId}`);
|
|
2303
|
-
}
|
|
2304
|
-
async getRelatedEntities(entityId) {
|
|
2305
|
-
return this.request("GET", `/memory/entities/${entityId}/related`);
|
|
2306
|
-
}
|
|
2307
|
-
async searchMemoryEntities(workspaceId, query, options) {
|
|
2308
|
-
const params = new URLSearchParams;
|
|
2309
|
-
params.set("workspace_id", workspaceId);
|
|
2310
|
-
params.set("q", query);
|
|
2311
|
-
if (options?.project_id)
|
|
2312
|
-
params.set("project_id", options.project_id);
|
|
2313
|
-
if (options?.type)
|
|
2314
|
-
params.set("type", options.type);
|
|
2315
|
-
if (options?.limit !== undefined)
|
|
2316
|
-
params.set("limit", String(options.limit));
|
|
2317
|
-
return this.request("GET", `/memory/search?${params.toString()}`);
|
|
2318
|
-
}
|
|
2319
|
-
async getVaultIndex(options) {
|
|
2320
|
-
const params = new URLSearchParams;
|
|
2321
|
-
params.set("workspace_id", options.workspace_id);
|
|
2322
|
-
if (options.project_id)
|
|
2323
|
-
params.set("project_id", options.project_id);
|
|
2324
|
-
if (options.type)
|
|
2325
|
-
params.set("type", options.type);
|
|
2326
|
-
if (options.limit !== undefined)
|
|
2327
|
-
params.set("limit", String(options.limit));
|
|
2328
|
-
return this.request("GET", `/memory/index?${params.toString()}`);
|
|
2329
|
-
}
|
|
2330
|
-
async getVaultIndexMarkdown(options) {
|
|
2331
|
-
const params = new URLSearchParams;
|
|
2332
|
-
params.set("workspace_id", options.workspace_id);
|
|
2333
|
-
if (options.project_id)
|
|
2334
|
-
params.set("project_id", options.project_id);
|
|
2335
|
-
if (options.type)
|
|
2336
|
-
params.set("type", options.type);
|
|
2337
|
-
if (options.limit !== undefined)
|
|
2338
|
-
params.set("limit", String(options.limit));
|
|
2339
|
-
return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
|
|
2340
|
-
accept: "text/markdown"
|
|
2341
|
-
});
|
|
2342
|
-
}
|
|
2343
|
-
async getMemoryStats(options) {
|
|
2344
|
-
const params = new URLSearchParams;
|
|
2345
|
-
params.set("workspace_id", options.workspace_id);
|
|
2346
|
-
if (options.project_id)
|
|
2347
|
-
params.set("project_id", options.project_id);
|
|
2348
|
-
return this.request("GET", `/memory/stats?${params.toString()}`);
|
|
2349
|
-
}
|
|
2350
|
-
async refreshMemoryStats(workspaceId) {
|
|
2351
|
-
return this.request("POST", "/memory/stats", { workspace_id: workspaceId });
|
|
2352
|
-
}
|
|
2353
|
-
async resolveLinks(options) {
|
|
2354
|
-
return this.request("POST", "/memory/resolve-links", options);
|
|
2355
|
-
}
|
|
2356
|
-
async listMemoryEntitiesMarkdown(options) {
|
|
2357
|
-
const params = new URLSearchParams;
|
|
2358
|
-
params.set("workspace_id", options.workspace_id);
|
|
2359
|
-
if (options.project_id)
|
|
2360
|
-
params.set("project_id", options.project_id);
|
|
2361
|
-
if (options.type)
|
|
2362
|
-
params.set("type", options.type);
|
|
2363
|
-
if (options.scope)
|
|
2364
|
-
params.set("scope", options.scope);
|
|
2365
|
-
if (options.tags?.length)
|
|
2366
|
-
params.set("tags", options.tags.join(","));
|
|
2367
|
-
if (options.agent_identifier)
|
|
2368
|
-
params.set("agent_identifier", options.agent_identifier);
|
|
2369
|
-
if (options.min_confidence !== undefined)
|
|
2370
|
-
params.set("min_confidence", String(options.min_confidence));
|
|
2371
|
-
if (options.q)
|
|
2372
|
-
params.set("q", options.q);
|
|
2373
|
-
if (options.limit !== undefined)
|
|
2374
|
-
params.set("limit", String(options.limit));
|
|
2375
|
-
if (options.offset !== undefined)
|
|
2376
|
-
params.set("offset", String(options.offset));
|
|
2377
|
-
return this.requestRaw("GET", `/memory/entities?${params.toString()}`, undefined, {
|
|
2378
|
-
accept: "text/markdown"
|
|
2379
|
-
});
|
|
2380
|
-
}
|
|
2381
|
-
async getMemoryEntityMarkdown(entityId) {
|
|
2382
|
-
return this.requestRaw("GET", `/memory/entities/${entityId}`, undefined, {
|
|
2383
|
-
accept: "text/markdown"
|
|
2384
|
-
});
|
|
2385
|
-
}
|
|
2386
|
-
async searchMemoryEntitiesMarkdown(workspaceId, query, options) {
|
|
2387
|
-
const params = new URLSearchParams;
|
|
2388
|
-
params.set("workspace_id", workspaceId);
|
|
2389
|
-
params.set("q", query);
|
|
2390
|
-
if (options?.project_id)
|
|
2391
|
-
params.set("project_id", options.project_id);
|
|
2392
|
-
if (options?.type)
|
|
2393
|
-
params.set("type", options.type);
|
|
2394
|
-
if (options?.limit !== undefined)
|
|
2395
|
-
params.set("limit", String(options.limit));
|
|
2396
|
-
return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
|
|
2397
|
-
accept: "text/markdown"
|
|
2398
|
-
});
|
|
2399
|
-
}
|
|
2400
|
-
async backfillEmbeddings(workspaceId, batchSize) {
|
|
2401
|
-
return this.request("POST", "/memory/backfill-embeddings", {
|
|
2402
|
-
workspace_id: workspaceId,
|
|
2403
|
-
batch_size: batchSize || 50
|
|
2404
|
-
});
|
|
2405
|
-
}
|
|
2406
|
-
async processNLU(data) {
|
|
2407
|
-
return this.request("POST", "/nlu", data);
|
|
2408
|
-
}
|
|
2409
|
-
async createPlan(projectId, data) {
|
|
2410
|
-
return this.request("POST", "/plans", { projectId, ...data });
|
|
2411
|
-
}
|
|
2412
|
-
async listPlans(projectId, options) {
|
|
2413
|
-
const params = new URLSearchParams({ projectId });
|
|
2414
|
-
if (options?.search)
|
|
2415
|
-
params.set("search", options.search);
|
|
2416
|
-
if (options?.status)
|
|
2417
|
-
params.set("status", options.status);
|
|
2418
|
-
return this.request("GET", `/plans?${params.toString()}`);
|
|
2419
|
-
}
|
|
2420
|
-
async getPlan(planId) {
|
|
2421
|
-
return this.request("GET", `/plans/${planId}`);
|
|
2422
|
-
}
|
|
2423
|
-
async getPlanByCardId(cardId) {
|
|
2424
|
-
return this.request("GET", `/cards/${cardId}/plan`);
|
|
2425
|
-
}
|
|
2426
|
-
async updatePlan(planId, updates) {
|
|
2427
|
-
return this.request("PATCH", `/plans/${planId}`, updates);
|
|
2428
|
-
}
|
|
2429
|
-
async updatePlanTask(planId, taskId, updates) {
|
|
2430
|
-
return this.request("PATCH", `/plans/${planId}/tasks/${taskId}`, updates);
|
|
2431
|
-
}
|
|
2432
|
-
async createWorkspace(data) {
|
|
2433
|
-
return this.request("POST", "/workspaces", data);
|
|
2434
|
-
}
|
|
2435
|
-
async createProject(data) {
|
|
2436
|
-
return this.request("POST", "/projects", data);
|
|
2437
|
-
}
|
|
2438
|
-
async sendInvitations(data) {
|
|
2439
|
-
return this.request("POST", "/invitations", data);
|
|
2440
|
-
}
|
|
2441
|
-
async generateApiKey(name) {
|
|
2442
|
-
return this.request("POST", "/api-keys", { name });
|
|
2443
|
-
}
|
|
2444
|
-
async generateCardPrompt(options) {
|
|
2445
|
-
const { assembleContext: assembleContext2, cacheManifest: cacheManifest2, generatePrompt: generatePrompt2 } = await loadPromptModules();
|
|
2446
|
-
const cardResult = await this.getCard(options.cardId);
|
|
2447
|
-
const cardData = cardResult.card;
|
|
2448
|
-
let columnData = null;
|
|
2449
|
-
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
2450
|
-
if (projectIdForBoard) {
|
|
2451
|
-
try {
|
|
2452
|
-
const board = await this.getBoard(projectIdForBoard, { summary: true });
|
|
2453
|
-
const column = board.columns.find((col) => col.id === cardData.column_id);
|
|
2454
|
-
if (column) {
|
|
2455
|
-
columnData = { name: column.name };
|
|
2456
|
-
}
|
|
2457
|
-
} catch {}
|
|
2458
|
-
}
|
|
2459
|
-
const variant = options.variant || "execute";
|
|
2460
|
-
let assembledContextStr;
|
|
2461
|
-
let assemblyId;
|
|
2462
|
-
let memories;
|
|
2463
|
-
try {
|
|
2464
|
-
if (options.workspaceId && cardData.title) {
|
|
2465
|
-
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
2466
|
-
const taskContext = [cardData.title, cardData.description || ""].filter(Boolean).join(" ");
|
|
2467
|
-
const assembled = await assembleContext2({
|
|
2468
|
-
workspaceId: options.workspaceId,
|
|
2469
|
-
projectId: options.projectId,
|
|
2470
|
-
taskContext,
|
|
2471
|
-
cardLabels,
|
|
2472
|
-
cardId: cardData.id,
|
|
2473
|
-
client: this
|
|
2474
|
-
});
|
|
2475
|
-
if (assembled.context) {
|
|
2476
|
-
assembledContextStr = assembled.context;
|
|
2477
|
-
assemblyId = assembled.manifest.assemblyId;
|
|
2478
|
-
cacheManifest2(assembled.manifest);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
} catch (err) {
|
|
2482
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2483
|
-
console.debug(`[generateCardPrompt] Context assembly failed: ${msg}`);
|
|
2484
|
-
try {
|
|
2485
|
-
if (options.workspaceId && cardData.title) {
|
|
2486
|
-
const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
|
|
2487
|
-
project_id: options.projectId,
|
|
2488
|
-
limit: 5
|
|
2489
|
-
});
|
|
2490
|
-
if (memoryResult.entities?.length > 0) {
|
|
2491
|
-
memories = memoryResult.entities.map((e) => ({
|
|
2492
|
-
id: e.id,
|
|
2493
|
-
type: e.type,
|
|
2494
|
-
title: e.title,
|
|
2495
|
-
content: e.content,
|
|
2496
|
-
confidence: e.confidence,
|
|
2497
|
-
tags: e.tags || []
|
|
2498
|
-
}));
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
} catch (fallbackErr) {
|
|
2502
|
-
const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
|
|
2503
|
-
console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
const result = generatePrompt2({
|
|
2507
|
-
card: cardData,
|
|
2508
|
-
column: columnData,
|
|
2509
|
-
variant,
|
|
2510
|
-
contextOptions: options.contextOptions,
|
|
2511
|
-
customConstraints: options.customConstraints,
|
|
2512
|
-
memories,
|
|
2513
|
-
assembledContext: assembledContextStr,
|
|
2514
|
-
assemblyId
|
|
2515
|
-
});
|
|
2516
|
-
return {
|
|
2517
|
-
...result,
|
|
2518
|
-
cardId: cardData.id,
|
|
2519
|
-
shortId: cardData.short_id,
|
|
2520
|
-
title: cardData.title
|
|
2521
|
-
};
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
var _promptModules = null;
|
|
2525
|
-
async function loadPromptModules() {
|
|
2526
|
-
if (!_promptModules) {
|
|
2527
|
-
const [ca, pb] = await Promise.all([
|
|
2528
|
-
Promise.resolve().then(() => (init_context_assembly(), exports_context_assembly)),
|
|
2529
|
-
Promise.resolve().then(() => (init_prompt_builder(), exports_prompt_builder))
|
|
2530
|
-
]);
|
|
2531
|
-
_promptModules = {
|
|
2532
|
-
assembleContext: ca.assembleContext,
|
|
2533
|
-
cacheManifest: ca.cacheManifest,
|
|
2534
|
-
generatePrompt: pb.generatePrompt
|
|
2535
|
-
};
|
|
2536
|
-
}
|
|
2537
|
-
return _promptModules;
|
|
2538
|
-
}
|
|
2539
|
-
var client2 = null;
|
|
2540
|
-
function getClient() {
|
|
2541
|
-
if (!client2) {
|
|
2542
|
-
client2 = new HarmonyApiClient;
|
|
2543
|
-
}
|
|
2544
|
-
return client2;
|
|
2545
|
-
}
|
|
2546
|
-
function resetClient() {
|
|
2547
|
-
client2 = null;
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
// src/onboard.ts
|
|
2551
|
-
async function onboardNewUser(params) {
|
|
2552
|
-
const {
|
|
2553
|
-
email,
|
|
2554
|
-
password,
|
|
2555
|
-
fullName,
|
|
2556
|
-
workspaceName = `${fullName}'s Workspace`,
|
|
2557
|
-
projectName = "My First Board",
|
|
2558
|
-
template = "kanban",
|
|
2559
|
-
keyName = "mcp-agent",
|
|
2560
|
-
apiUrl = getApiUrl()
|
|
2561
|
-
} = params;
|
|
2562
|
-
const signupResult = await signupUser(apiUrl, {
|
|
2563
|
-
email,
|
|
2564
|
-
password,
|
|
2565
|
-
full_name: fullName
|
|
2566
|
-
});
|
|
2567
|
-
const token = signupResult.session.access_token;
|
|
2568
|
-
const workspaceResult = await requestWithBearer(apiUrl, token, "POST", "/workspaces", {
|
|
2569
|
-
name: workspaceName
|
|
2570
|
-
});
|
|
2571
|
-
const projectResult = await requestWithBearer(apiUrl, token, "POST", "/projects", {
|
|
2572
|
-
workspaceId: workspaceResult.workspace.id,
|
|
2573
|
-
name: projectName,
|
|
2574
|
-
template
|
|
2575
|
-
});
|
|
2576
|
-
const keyResult = await requestWithBearer(apiUrl, token, "POST", "/api-keys", {
|
|
2577
|
-
name: keyName
|
|
2578
|
-
});
|
|
2579
|
-
return {
|
|
2580
|
-
user: signupResult.user,
|
|
2581
|
-
workspace: workspaceResult.workspace,
|
|
2582
|
-
project: projectResult.project,
|
|
2583
|
-
columns: projectResult.columns,
|
|
2584
|
-
apiKey: {
|
|
2585
|
-
rawKey: keyResult.rawKey,
|
|
2586
|
-
prefix: keyResult.apiKey.prefix
|
|
2587
|
-
}
|
|
2588
|
-
};
|
|
2589
|
-
}
|
|
2590
|
-
export {
|
|
2591
|
-
onboardNewUser
|
|
2592
|
-
};
|