@gethmy/mcp 2.3.1 → 2.3.3
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/lib/api-client.js +2099 -648
- package/dist/lib/config.js +217 -201
- package/package.json +9 -5
- package/src/memory-cleanup.ts +2 -4
- package/dist/lib/__tests__/active-learning.test.js +0 -386
- package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
- package/dist/lib/__tests__/auto-session.test.js +0 -661
- package/dist/lib/__tests__/context-assembly.test.js +0 -362
- package/dist/lib/__tests__/graph-expansion.test.js +0 -150
- package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
- package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
- package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
- package/dist/lib/__tests__/pattern-detection.test.js +0 -295
- package/dist/lib/__tests__/prompt-builder.test.js +0 -418
- package/dist/lib/active-learning.js +0 -822
- package/dist/lib/auto-session.js +0 -214
- package/dist/lib/cli.js +0 -138
- package/dist/lib/consolidation.js +0 -303
- package/dist/lib/context-assembly.js +0 -884
- package/dist/lib/graph-expansion.js +0 -163
- package/dist/lib/http.js +0 -175
- package/dist/lib/index.js +0 -7
- package/dist/lib/lifecycle-maintenance.js +0 -88
- package/dist/lib/memory-cleanup.js +0 -455
- package/dist/lib/onboard.js +0 -36
- package/dist/lib/prompt-builder.js +0 -488
- package/dist/lib/remote.js +0 -166
- package/dist/lib/server.js +0 -3365
- package/dist/lib/skills.js +0 -593
- package/dist/lib/tui/agents.js +0 -116
- package/dist/lib/tui/docs.js +0 -744
- package/dist/lib/tui/setup.js +0 -934
- package/dist/lib/tui/theme.js +0 -95
- package/dist/lib/tui/writer.js +0 -200
package/dist/lib/api-client.js
CHANGED
|
@@ -1,672 +1,2123 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// Check if error is retryable
|
|
10
|
-
function isRetryableError(error, status) {
|
|
11
|
-
if (status && RETRY_CONFIG.retryableStatusCodes.includes(status)) {
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
if (error instanceof TypeError)
|
|
15
|
-
return true; // Network errors
|
|
16
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
17
|
-
return (msg.includes("ECONNRESET") ||
|
|
18
|
-
msg.includes("ETIMEDOUT") ||
|
|
19
|
-
msg.includes("fetch failed"));
|
|
20
|
-
}
|
|
21
|
-
// Exponential backoff with jitter
|
|
22
|
-
function getRetryDelay(attempt) {
|
|
23
|
-
const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
|
|
24
|
-
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
25
|
-
}
|
|
26
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
27
|
-
// Semaphore for concurrency control
|
|
28
|
-
class Semaphore {
|
|
29
|
-
permits;
|
|
30
|
-
queue = [];
|
|
31
|
-
constructor(permits) {
|
|
32
|
-
this.permits = permits;
|
|
33
|
-
}
|
|
34
|
-
async acquire() {
|
|
35
|
-
if (this.permits > 0) {
|
|
36
|
-
this.permits--;
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
return new Promise((resolve) => this.queue.push(resolve));
|
|
40
|
-
}
|
|
41
|
-
release() {
|
|
42
|
-
const next = this.queue.shift();
|
|
43
|
-
if (next)
|
|
44
|
-
next();
|
|
45
|
-
else
|
|
46
|
-
this.permits++;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Limit concurrent requests to 3
|
|
50
|
-
const requestSemaphore = new Semaphore(3);
|
|
51
|
-
// ============ PRE-AUTH FREE FUNCTIONS ============
|
|
52
|
-
// These bypass the singleton + semaphore for unauthenticated/JWT-based requests
|
|
53
|
-
export async function signupUser(apiUrl, data) {
|
|
54
|
-
const url = `${apiUrl}/v1/auth/signup`;
|
|
55
|
-
const response = await fetch(url, {
|
|
56
|
-
method: "POST",
|
|
57
|
-
headers: { "Content-Type": "application/json" },
|
|
58
|
-
body: JSON.stringify(data),
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
59
9
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
10
|
+
};
|
|
11
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
12
|
+
|
|
13
|
+
// ../memory/dist/schema.js
|
|
14
|
+
var init_schema = () => {};
|
|
15
|
+
|
|
16
|
+
// ../memory/dist/constraints.js
|
|
17
|
+
var init_constraints = __esm(() => {
|
|
18
|
+
init_schema();
|
|
19
|
+
});
|
|
20
|
+
// ../memory/dist/client.js
|
|
21
|
+
var init_client = __esm(() => {
|
|
22
|
+
init_constraints();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ../memory/dist/graph-walk.js
|
|
26
|
+
async function discoverRelatedContext(client, startIds, maxDepth = 2, maxEntities = 20, minConfidence = 0.5) {
|
|
27
|
+
const visited = new Set;
|
|
28
|
+
const collectedEntities = [];
|
|
29
|
+
const collectedRelations = [];
|
|
30
|
+
let truncated = false;
|
|
31
|
+
const queue = startIds.map((id) => [id, 0]);
|
|
32
|
+
for (const id of startIds) {
|
|
33
|
+
visited.add(id);
|
|
34
|
+
}
|
|
35
|
+
while (queue.length > 0) {
|
|
36
|
+
const [entityId, depth] = queue.shift();
|
|
37
|
+
if (collectedEntities.length >= maxEntities) {
|
|
38
|
+
truncated = true;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (depth > maxDepth)
|
|
42
|
+
continue;
|
|
43
|
+
try {
|
|
44
|
+
const entityResult = await client.getMemoryEntity(entityId);
|
|
45
|
+
const entity = entityResult.entity;
|
|
46
|
+
if (entity) {
|
|
47
|
+
collectedEntities.push({
|
|
48
|
+
id: entity.id,
|
|
49
|
+
type: entity.type,
|
|
50
|
+
title: entity.title,
|
|
51
|
+
confidence: entity.confidence ?? 1,
|
|
52
|
+
memory_tier: entity.memory_tier || "reference"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (depth >= maxDepth)
|
|
56
|
+
continue;
|
|
57
|
+
const related = await client.getRelatedEntities(entityId);
|
|
58
|
+
for (const raw of related.outgoing || []) {
|
|
59
|
+
const rel = raw;
|
|
60
|
+
const relConfidence = rel.confidence ?? 1;
|
|
61
|
+
if (relConfidence < minConfidence)
|
|
62
|
+
continue;
|
|
63
|
+
const target = rel.target;
|
|
64
|
+
const targetId = target?.id ?? rel.target_id;
|
|
65
|
+
if (targetId && !visited.has(targetId)) {
|
|
66
|
+
visited.add(targetId);
|
|
67
|
+
queue.push([targetId, depth + 1]);
|
|
68
|
+
collectedRelations.push({
|
|
69
|
+
id: rel.id,
|
|
70
|
+
source_id: entityId,
|
|
71
|
+
target_id: targetId,
|
|
72
|
+
relation_type: rel.relation_type,
|
|
73
|
+
confidence: relConfidence
|
|
74
|
+
});
|
|
96
75
|
}
|
|
97
|
-
|
|
98
|
-
|
|
76
|
+
}
|
|
77
|
+
for (const raw of related.incoming || []) {
|
|
78
|
+
const rel = raw;
|
|
79
|
+
const relConfidence = rel.confidence ?? 1;
|
|
80
|
+
if (relConfidence < minConfidence)
|
|
81
|
+
continue;
|
|
82
|
+
const source = rel.source;
|
|
83
|
+
const sourceId = source?.id ?? rel.source_id;
|
|
84
|
+
if (sourceId && !visited.has(sourceId)) {
|
|
85
|
+
visited.add(sourceId);
|
|
86
|
+
queue.push([sourceId, depth + 1]);
|
|
87
|
+
collectedRelations.push({
|
|
88
|
+
id: rel.id,
|
|
89
|
+
source_id: sourceId,
|
|
90
|
+
target_id: entityId,
|
|
91
|
+
relation_type: rel.relation_type,
|
|
92
|
+
confidence: relConfidence
|
|
93
|
+
});
|
|
99
94
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
95
|
+
}
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
entities: collectedEntities,
|
|
100
|
+
relations: collectedRelations,
|
|
101
|
+
depth: maxDepth,
|
|
102
|
+
truncated
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ../memory/dist/lifecycle.js
|
|
107
|
+
function checkPromotion(currentTier, accessCount, confidence, createdAt) {
|
|
108
|
+
const ageDays = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24);
|
|
109
|
+
const base = {
|
|
110
|
+
eligible: false,
|
|
111
|
+
targetTier: null,
|
|
112
|
+
reason: null,
|
|
113
|
+
currentTier,
|
|
114
|
+
accessCount,
|
|
115
|
+
confidence,
|
|
116
|
+
ageDays
|
|
117
|
+
};
|
|
118
|
+
if (currentTier === "draft") {
|
|
119
|
+
const rules = PROMOTION_RULES.draftToEpisode;
|
|
120
|
+
if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
|
|
121
|
+
return {
|
|
122
|
+
...base,
|
|
123
|
+
eligible: true,
|
|
124
|
+
targetTier: "episode",
|
|
125
|
+
reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (currentTier === "episode") {
|
|
130
|
+
const rules = PROMOTION_RULES.episodeToReference;
|
|
131
|
+
if (accessCount >= rules.minAccessCount && confidence >= rules.minConfidence && ageDays >= rules.minAgeDays) {
|
|
132
|
+
return {
|
|
133
|
+
...base,
|
|
134
|
+
eligible: true,
|
|
135
|
+
targetTier: "reference",
|
|
136
|
+
reason: `Accessed ${accessCount} times (≥${rules.minAccessCount}), confidence ${confidence} (≥${rules.minConfidence}), age ${Math.round(ageDays)}d (≥${rules.minAgeDays}d)`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return base;
|
|
141
|
+
}
|
|
142
|
+
var PROMOTION_RULES;
|
|
143
|
+
var init_lifecycle = __esm(() => {
|
|
144
|
+
PROMOTION_RULES = {
|
|
145
|
+
draftToEpisode: {
|
|
146
|
+
minAccessCount: 5,
|
|
147
|
+
minConfidence: 0.8,
|
|
148
|
+
minAgeDays: 1
|
|
149
|
+
},
|
|
150
|
+
episodeToReference: {
|
|
151
|
+
minAccessCount: 10,
|
|
152
|
+
minConfidence: 0.9,
|
|
153
|
+
minAgeDays: 7
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
// ../memory/dist/sync.js
|
|
158
|
+
var init_sync = () => {};
|
|
159
|
+
|
|
160
|
+
// ../memory/dist/index.js
|
|
161
|
+
var init_dist = __esm(() => {
|
|
162
|
+
init_client();
|
|
163
|
+
init_constraints();
|
|
164
|
+
init_lifecycle();
|
|
165
|
+
init_schema();
|
|
166
|
+
init_sync();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// src/context-assembly.ts
|
|
170
|
+
var exports_context_assembly = {};
|
|
171
|
+
__export(exports_context_assembly, {
|
|
172
|
+
trackSessionAssembly: () => trackSessionAssembly,
|
|
173
|
+
recordContextFeedback: () => recordContextFeedback,
|
|
174
|
+
mapToContextEntity: () => mapToContextEntity,
|
|
175
|
+
getSessionAssemblyId: () => getSessionAssemblyId,
|
|
176
|
+
getCachedManifest: () => getCachedManifest,
|
|
177
|
+
expandQuery: () => expandQuery,
|
|
178
|
+
computeRelevanceScore: () => computeRelevanceScore,
|
|
179
|
+
cacheManifest: () => cacheManifest,
|
|
180
|
+
assembleContext: () => assembleContext
|
|
181
|
+
});
|
|
182
|
+
function estimateTokens(text) {
|
|
183
|
+
return Math.ceil(text.length / 4);
|
|
184
|
+
}
|
|
185
|
+
function passesQualityGate(entity) {
|
|
186
|
+
const content = entity.content.trim();
|
|
187
|
+
if (content.length < 50)
|
|
188
|
+
return false;
|
|
189
|
+
const normalizedTitle = entity.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
190
|
+
const normalizedContent = content.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
191
|
+
if (normalizedContent.length < normalizedTitle.length * 1.5) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
if (entity.type === "pattern" && /recurring .+ \(\d+ instances\)/i.test(entity.title)) {
|
|
195
|
+
const lines = content.split(`
|
|
196
|
+
`).filter((l) => l.trim().length > 0);
|
|
197
|
+
const bulletLines = lines.filter((l) => l.trim().startsWith("- "));
|
|
198
|
+
if (bulletLines.length > lines.length * 0.6)
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
if (entity.type === "procedure") {
|
|
202
|
+
const stepCount = (content.match(/^\d+\.\s/gm) || []).length;
|
|
203
|
+
if (stepCount < 3)
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
function generateAssemblyId() {
|
|
209
|
+
return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
210
|
+
}
|
|
211
|
+
function truncateContent(content, maxTokens) {
|
|
212
|
+
const currentTokens = estimateTokens(content);
|
|
213
|
+
if (currentTokens <= maxTokens) {
|
|
214
|
+
return { text: content, truncated: false };
|
|
215
|
+
}
|
|
216
|
+
const paragraphs = content.split(/\n\n+/);
|
|
217
|
+
let result = paragraphs[0];
|
|
218
|
+
for (let i = 1;i < paragraphs.length; i++) {
|
|
219
|
+
const lines = paragraphs[i].split(`
|
|
220
|
+
`).filter((l) => l.startsWith("- ") || l.startsWith("* "));
|
|
221
|
+
if (lines.length > 0) {
|
|
222
|
+
const bulletSection = lines.join(`
|
|
223
|
+
`);
|
|
224
|
+
if (estimateTokens(result + `
|
|
225
|
+
|
|
226
|
+
` + bulletSection) <= maxTokens) {
|
|
227
|
+
result += `
|
|
228
|
+
|
|
229
|
+
` + bulletSection;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (estimateTokens(result) > maxTokens) {
|
|
234
|
+
const maxChars = maxTokens * 4;
|
|
235
|
+
result = result.slice(0, maxChars - 3) + "...";
|
|
236
|
+
}
|
|
237
|
+
return { text: result, truncated: true };
|
|
238
|
+
}
|
|
239
|
+
function escapeRegex(str) {
|
|
240
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
241
|
+
}
|
|
242
|
+
function expandQuery(taskContext) {
|
|
243
|
+
const queries = [taskContext];
|
|
244
|
+
const lowerQueries = [taskContext.toLowerCase()];
|
|
245
|
+
const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
246
|
+
const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
|
|
247
|
+
for (const word of expandableWords) {
|
|
248
|
+
const synonyms = QUERY_SYNONYMS[word];
|
|
249
|
+
if (!synonyms)
|
|
250
|
+
continue;
|
|
251
|
+
const variation = taskContext.replace(new RegExp(`\\b${escapeRegex(word)}\\b`, "gi"), synonyms[0]);
|
|
252
|
+
const lowerVariation = variation.toLowerCase();
|
|
253
|
+
if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
|
|
254
|
+
queries.push(variation);
|
|
255
|
+
lowerQueries.push(lowerVariation);
|
|
256
|
+
}
|
|
257
|
+
if (queries.length >= MAX_QUERY_VARIATIONS)
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
if (words.length >= 3) {
|
|
261
|
+
const keyPhrases = words.filter((w) => ![
|
|
262
|
+
"the",
|
|
263
|
+
"and",
|
|
264
|
+
"for",
|
|
265
|
+
"with",
|
|
266
|
+
"this",
|
|
267
|
+
"that",
|
|
268
|
+
"from",
|
|
269
|
+
"into"
|
|
270
|
+
].includes(w)).slice(0, 4).join(" ");
|
|
271
|
+
if (!lowerQueries.includes(keyPhrases)) {
|
|
272
|
+
queries.push(keyPhrases);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return queries.slice(0, MAX_QUERY_VARIATIONS);
|
|
276
|
+
}
|
|
277
|
+
function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
|
|
278
|
+
const reasons = [];
|
|
279
|
+
let score = 0;
|
|
280
|
+
const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
|
|
281
|
+
if (hasRrfScore) {
|
|
282
|
+
const normalizedRrf = Math.min(entity.rrf_score / 0.04, 1);
|
|
283
|
+
const rrfContribution = normalizedRrf * 0.3;
|
|
284
|
+
score += rrfContribution;
|
|
285
|
+
reasons.push(`hybrid_search(rrf=${entity.rrf_score.toFixed(4)})`);
|
|
286
|
+
}
|
|
287
|
+
const textMatchWeight = hasRrfScore ? 0.15 : 0.4;
|
|
288
|
+
const taskWords = new Set(taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
|
|
289
|
+
const entityWords = new Set(`${entity.title} ${entity.content}`.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
|
|
290
|
+
const overlap = [...taskWords].filter((w) => entityWords.has(w));
|
|
291
|
+
if (overlap.length > 0) {
|
|
292
|
+
const textScore = Math.min(overlap.length / Math.max(taskWords.size, 1), 1) * textMatchWeight;
|
|
293
|
+
score += textScore;
|
|
294
|
+
reasons.push(`text_match(${overlap.length} words)`);
|
|
295
|
+
}
|
|
296
|
+
if (cardLabels.length > 0 && entity.tags.length > 0) {
|
|
297
|
+
const labelSet = new Set(cardLabels.map((l) => l.toLowerCase()));
|
|
298
|
+
const tagOverlap = entity.tags.filter((t) => labelSet.has(t.toLowerCase()));
|
|
299
|
+
if (tagOverlap.length > 0) {
|
|
300
|
+
const tagScore = tagOverlap.length / cardLabels.length * 0.3;
|
|
301
|
+
score += tagScore;
|
|
302
|
+
reasons.push(`tag_match(${tagOverlap.join(",")})`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
score += entity.confidence * 0.15;
|
|
306
|
+
if (entity.confidence >= 0.9) {
|
|
307
|
+
reasons.push("high_confidence");
|
|
308
|
+
}
|
|
309
|
+
if (entity.last_accessed_at) {
|
|
310
|
+
const daysSinceAccess = (Date.now() - new Date(entity.last_accessed_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
311
|
+
const halfLife = { draft: 7, episode: 30, reference: 180 }[entity.memory_tier];
|
|
312
|
+
const recencyScore = 0.5 ** (daysSinceAccess / halfLife) * 0.1;
|
|
313
|
+
score += recencyScore;
|
|
314
|
+
if (daysSinceAccess < 7)
|
|
315
|
+
reasons.push("recently_accessed");
|
|
316
|
+
}
|
|
317
|
+
if (entity.access_count > 0) {
|
|
318
|
+
const freqScore = Math.log10(entity.access_count + 1) * 0.05;
|
|
319
|
+
score += Math.min(freqScore, 0.1);
|
|
320
|
+
if (entity.access_count >= 5)
|
|
321
|
+
reasons.push(`frequently_used(${entity.access_count})`);
|
|
322
|
+
}
|
|
323
|
+
const usefulnessScore = entity.metadata?.usefulness_score ?? 0;
|
|
324
|
+
if (usefulnessScore >= 3) {
|
|
325
|
+
const usefulnessBoost = Math.min(usefulnessScore / 20, 0.15);
|
|
326
|
+
score += usefulnessBoost;
|
|
327
|
+
reasons.push(`useful(${usefulnessScore})`);
|
|
328
|
+
} else if (usefulnessScore === 0 && entity.access_count >= 5) {
|
|
329
|
+
score -= 0.02;
|
|
330
|
+
reasons.push("low_usefulness");
|
|
331
|
+
}
|
|
332
|
+
if (entity.type === "procedure") {
|
|
333
|
+
score += 0.1;
|
|
334
|
+
reasons.push("procedure_boost");
|
|
335
|
+
}
|
|
336
|
+
if (graphRelations && graphRelations.length > 0) {
|
|
337
|
+
const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
|
|
338
|
+
if (entityRelations.length > 0) {
|
|
339
|
+
let bestBonus = 0;
|
|
340
|
+
let bestRelType = "";
|
|
341
|
+
for (const rel of entityRelations) {
|
|
342
|
+
const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
|
|
343
|
+
if (bonus > bestBonus) {
|
|
344
|
+
bestBonus = bonus;
|
|
345
|
+
bestRelType = rel.relation_type;
|
|
105
346
|
}
|
|
106
|
-
|
|
107
|
-
|
|
347
|
+
}
|
|
348
|
+
score += bestBonus;
|
|
349
|
+
reasons.push(`graph_walk(${bestRelType})`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
score = Math.max(0, Math.min(score, 1));
|
|
353
|
+
const tierWeight = TIER_WEIGHTS[entity.memory_tier];
|
|
354
|
+
score *= tierWeight;
|
|
355
|
+
return { score, reasons };
|
|
356
|
+
}
|
|
357
|
+
async function assembleContext(options) {
|
|
358
|
+
const {
|
|
359
|
+
workspaceId,
|
|
360
|
+
projectId,
|
|
361
|
+
taskContext,
|
|
362
|
+
cardLabels = [],
|
|
363
|
+
tokenBudget = DEFAULT_TOKEN_BUDGET,
|
|
364
|
+
client: client2,
|
|
365
|
+
graphWalkEnabled = true,
|
|
366
|
+
queryExpansionEnabled = true,
|
|
367
|
+
enableLlmReranking = false,
|
|
368
|
+
rerankFn
|
|
369
|
+
} = options;
|
|
370
|
+
const assemblyId = generateAssemblyId();
|
|
371
|
+
const manifest = {
|
|
372
|
+
assemblyId,
|
|
373
|
+
timestamp: new Date().toISOString(),
|
|
374
|
+
included: [],
|
|
375
|
+
excluded: [],
|
|
376
|
+
budgetUsed: 0,
|
|
377
|
+
budgetTotal: tokenBudget,
|
|
378
|
+
tierBreakdown: {
|
|
379
|
+
draft: { count: 0, tokens: 0 },
|
|
380
|
+
episode: { count: 0, tokens: 0 },
|
|
381
|
+
reference: { count: 0, tokens: 0 }
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
const candidates = [];
|
|
385
|
+
const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
|
|
386
|
+
const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
|
|
387
|
+
project_id: projectId,
|
|
388
|
+
limit: 30
|
|
389
|
+
})));
|
|
390
|
+
const candidateIds = new Set;
|
|
391
|
+
for (const result of searchResults) {
|
|
392
|
+
if (result.status !== "fulfilled")
|
|
393
|
+
continue;
|
|
394
|
+
if (result.value.entities?.length > 0) {
|
|
395
|
+
for (const raw of result.value.entities) {
|
|
396
|
+
const entity = mapToContextEntity(raw);
|
|
397
|
+
if (!candidateIds.has(entity.id)) {
|
|
398
|
+
candidateIds.add(entity.id);
|
|
399
|
+
candidates.push(entity);
|
|
108
400
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const data = await response.json();
|
|
127
|
-
if (!response.ok) {
|
|
128
|
-
const errorMsg = data.error || `API error: ${response.status}`;
|
|
129
|
-
if (!isRetryableError(null, response.status)) {
|
|
130
|
-
throw new Error(errorMsg);
|
|
131
|
-
}
|
|
132
|
-
lastError = new Error(errorMsg);
|
|
133
|
-
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
134
|
-
await sleep(getRetryDelay(attempt));
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
throw lastError;
|
|
138
|
-
}
|
|
139
|
-
return data;
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
143
|
-
if (!isRetryableError(error))
|
|
144
|
-
throw lastError;
|
|
145
|
-
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
146
|
-
await sleep(getRetryDelay(attempt));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (candidates.length < 10 && projectId) {
|
|
405
|
+
try {
|
|
406
|
+
const listResult = await client2.listMemoryEntities({
|
|
407
|
+
workspace_id: workspaceId,
|
|
408
|
+
project_id: projectId,
|
|
409
|
+
limit: 30
|
|
410
|
+
});
|
|
411
|
+
if (listResult.entities?.length > 0) {
|
|
412
|
+
for (const raw of listResult.entities) {
|
|
413
|
+
const entity = mapToContextEntity(raw);
|
|
414
|
+
if (!candidateIds.has(entity.id)) {
|
|
415
|
+
candidateIds.add(entity.id);
|
|
416
|
+
candidates.push(entity);
|
|
417
|
+
}
|
|
149
418
|
}
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
});
|
|
168
|
-
if (!response.ok) {
|
|
169
|
-
const text = await response.text();
|
|
170
|
-
let errorMsg;
|
|
171
|
-
try {
|
|
172
|
-
errorMsg =
|
|
173
|
-
JSON.parse(text).error || `API error: ${response.status}`;
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
errorMsg = text || `API error: ${response.status}`;
|
|
177
|
-
}
|
|
178
|
-
if (!isRetryableError(null, response.status)) {
|
|
179
|
-
throw new Error(errorMsg);
|
|
180
|
-
}
|
|
181
|
-
lastError = new Error(errorMsg);
|
|
182
|
-
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
183
|
-
await sleep(getRetryDelay(attempt));
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
throw lastError;
|
|
187
|
-
}
|
|
188
|
-
return await response.text();
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
192
|
-
if (!isRetryableError(error))
|
|
193
|
-
throw lastError;
|
|
194
|
-
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
195
|
-
await sleep(getRetryDelay(attempt));
|
|
196
|
-
}
|
|
197
|
-
}
|
|
419
|
+
}
|
|
420
|
+
} catch {}
|
|
421
|
+
}
|
|
422
|
+
if (candidates.length < 20) {
|
|
423
|
+
try {
|
|
424
|
+
const wsResult = await client2.listMemoryEntities({
|
|
425
|
+
workspace_id: workspaceId,
|
|
426
|
+
scope: "workspace",
|
|
427
|
+
limit: 20
|
|
428
|
+
});
|
|
429
|
+
if (wsResult.entities?.length > 0) {
|
|
430
|
+
for (const raw of wsResult.entities) {
|
|
431
|
+
const entity = mapToContextEntity(raw);
|
|
432
|
+
if (!candidateIds.has(entity.id)) {
|
|
433
|
+
candidateIds.add(entity.id);
|
|
434
|
+
candidates.push(entity);
|
|
435
|
+
}
|
|
198
436
|
}
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (options?.columnId)
|
|
219
|
-
params.set("column_id", options.columnId);
|
|
220
|
-
if (options?.summary)
|
|
221
|
-
params.set("summary", "true");
|
|
222
|
-
if (options?.includeArchived)
|
|
223
|
-
params.set("include_archived", "true");
|
|
224
|
-
if (options?.labelName)
|
|
225
|
-
params.set("label_name", options.labelName);
|
|
226
|
-
const query = params.toString() ? `?${params.toString()}` : "";
|
|
227
|
-
return this.request("GET", `/board/${projectId}${query}`);
|
|
228
|
-
}
|
|
229
|
-
// ============ CARD OPERATIONS ============
|
|
230
|
-
async createCard(projectId, data) {
|
|
231
|
-
return this.request("POST", "/cards", { projectId, ...data });
|
|
232
|
-
}
|
|
233
|
-
async updateCard(cardId, updates) {
|
|
234
|
-
return this.request("PATCH", `/cards/${cardId}`, updates);
|
|
235
|
-
}
|
|
236
|
-
async moveCard(cardId, columnId, position) {
|
|
237
|
-
return this.request("POST", `/cards/${cardId}/move`, {
|
|
238
|
-
columnId,
|
|
239
|
-
position,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
async archiveCard(cardId) {
|
|
243
|
-
return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
|
|
244
|
-
}
|
|
245
|
-
async unarchiveCard(cardId) {
|
|
246
|
-
return this.updateCard(cardId, { archivedAt: null });
|
|
247
|
-
}
|
|
248
|
-
async deleteCard(cardId) {
|
|
249
|
-
return this.request("DELETE", `/cards/${cardId}`);
|
|
250
|
-
}
|
|
251
|
-
async getCard(cardId) {
|
|
252
|
-
return this.request("GET", `/cards/${cardId}`);
|
|
253
|
-
}
|
|
254
|
-
async getCardByShortId(projectId, shortId) {
|
|
255
|
-
return this.request("GET", `/projects/${projectId}/cards/${shortId}`);
|
|
256
|
-
}
|
|
257
|
-
async searchCards(query, options) {
|
|
258
|
-
const params = new URLSearchParams({ q: query });
|
|
259
|
-
if (options?.projectId) {
|
|
260
|
-
params.set("project_id", options.projectId);
|
|
437
|
+
}
|
|
438
|
+
} catch {}
|
|
439
|
+
}
|
|
440
|
+
let graphRelations = [];
|
|
441
|
+
if (graphWalkEnabled && candidates.length > 0) {
|
|
442
|
+
try {
|
|
443
|
+
const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
|
|
444
|
+
const seedIds = seedCandidates.map((c) => c.id);
|
|
445
|
+
const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
|
|
446
|
+
graphRelations = walkResult.relations;
|
|
447
|
+
const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
|
|
448
|
+
if (newEntityIds.length > 0) {
|
|
449
|
+
const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
|
|
450
|
+
for (const result of fetchResults) {
|
|
451
|
+
if (result.status !== "fulfilled" || !result.value.entity)
|
|
452
|
+
continue;
|
|
453
|
+
const mapped = mapToContextEntity(result.value.entity);
|
|
454
|
+
candidateIds.add(mapped.id);
|
|
455
|
+
candidates.push(mapped);
|
|
261
456
|
}
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
async endAgentSession(cardId, data) {
|
|
315
|
-
return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
|
|
316
|
-
}
|
|
317
|
-
async flushActivityLog(cardId, data) {
|
|
318
|
-
return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
|
|
319
|
-
}
|
|
320
|
-
async getActivityLog(cardId, sessionId) {
|
|
321
|
-
return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
|
|
322
|
-
}
|
|
323
|
-
async getAgentSession(cardId, options) {
|
|
324
|
-
const params = new URLSearchParams();
|
|
325
|
-
if (options?.includeEnded)
|
|
326
|
-
params.set("include_ended", "true");
|
|
327
|
-
const query = params.toString() ? `?${params.toString()}` : "";
|
|
328
|
-
return this.request("GET", `/cards/${cardId}/agent-context${query}`);
|
|
329
|
-
}
|
|
330
|
-
// ============ AGENT PERFORMANCE PROFILES ============
|
|
331
|
-
async getAgentProfile(workspaceId, agentIdentifier) {
|
|
332
|
-
const params = new URLSearchParams({
|
|
333
|
-
workspace_id: workspaceId,
|
|
334
|
-
agent_identifier: agentIdentifier,
|
|
457
|
+
}
|
|
458
|
+
} catch {}
|
|
459
|
+
}
|
|
460
|
+
if (candidates.length === 0) {
|
|
461
|
+
return {
|
|
462
|
+
context: "",
|
|
463
|
+
manifest,
|
|
464
|
+
memories: []
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const qualityCandidates = candidates.filter((entity) => {
|
|
468
|
+
if (passesQualityGate(entity))
|
|
469
|
+
return true;
|
|
470
|
+
manifest.excluded.push({
|
|
471
|
+
entityId: entity.id,
|
|
472
|
+
title: entity.title,
|
|
473
|
+
type: entity.type,
|
|
474
|
+
tier: entity.memory_tier,
|
|
475
|
+
relevanceScore: 0,
|
|
476
|
+
reason: "failed_quality_gate"
|
|
477
|
+
});
|
|
478
|
+
return false;
|
|
479
|
+
});
|
|
480
|
+
if (qualityCandidates.length === 0) {
|
|
481
|
+
return {
|
|
482
|
+
context: "",
|
|
483
|
+
manifest,
|
|
484
|
+
memories: []
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const scored = qualityCandidates.map((entity) => {
|
|
488
|
+
const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
|
|
489
|
+
return { entity, score, reasons };
|
|
490
|
+
});
|
|
491
|
+
scored.sort((a, b) => b.score - a.score);
|
|
492
|
+
if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
|
|
493
|
+
const topN = scored.slice(0, RERANK_TOP_N);
|
|
494
|
+
const scoreRange = topN[0].score - topN[topN.length - 1].score;
|
|
495
|
+
if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
|
|
496
|
+
try {
|
|
497
|
+
const rerankCandidates = topN.map((s) => ({
|
|
498
|
+
id: s.entity.id,
|
|
499
|
+
title: s.entity.title,
|
|
500
|
+
snippet: s.entity.content.slice(0, 200)
|
|
501
|
+
}));
|
|
502
|
+
const rerankedIds = await rerankFn(taskContext, rerankCandidates);
|
|
503
|
+
const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
|
|
504
|
+
topN.sort((a, b) => {
|
|
505
|
+
const aIdx = idOrder.get(a.entity.id) ?? 999;
|
|
506
|
+
const bIdx = idOrder.get(b.entity.id) ?? 999;
|
|
507
|
+
return aIdx - bIdx;
|
|
335
508
|
});
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
509
|
+
scored.splice(0, topN.length, ...topN);
|
|
510
|
+
} catch {}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
|
|
514
|
+
const remainingBudget = tokenBudget - procedureBudget;
|
|
515
|
+
const tierBudgets = {
|
|
516
|
+
reference: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.reference),
|
|
517
|
+
episode: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.episode),
|
|
518
|
+
draft: Math.floor(remainingBudget * TIER_BUDGET_ALLOCATION.draft)
|
|
519
|
+
};
|
|
520
|
+
const tierUsed = {
|
|
521
|
+
reference: 0,
|
|
522
|
+
episode: 0,
|
|
523
|
+
draft: 0
|
|
524
|
+
};
|
|
525
|
+
let procedureUsed = 0;
|
|
526
|
+
const included = [];
|
|
527
|
+
let totalUsed = 0;
|
|
528
|
+
let referenceCount = 0;
|
|
529
|
+
for (const item of scored) {
|
|
530
|
+
if (item.entity.memory_tier === "reference" && item.entity.type !== "procedure" && referenceCount < MIN_REFERENCE_SLOTS) {
|
|
531
|
+
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
532
|
+
const tokens = estimateTokens(`### ${item.entity.title}
|
|
533
|
+
${text}`);
|
|
534
|
+
if (totalUsed + tokens <= tokenBudget) {
|
|
535
|
+
included.push({ ...item, tokens, truncated });
|
|
536
|
+
item.entity.content = text;
|
|
537
|
+
totalUsed += tokens;
|
|
538
|
+
tierUsed.reference += tokens;
|
|
539
|
+
referenceCount++;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const includedIds = new Set(included.map((i) => i.entity.id));
|
|
544
|
+
const procedureCandidates = scored.filter((item) => item.entity.type === "procedure" && !includedIds.has(item.entity.id));
|
|
545
|
+
for (const item of procedureCandidates) {
|
|
546
|
+
if (item.score < MIN_RELEVANCE_THRESHOLD) {
|
|
547
|
+
manifest.excluded.push({
|
|
548
|
+
entityId: item.entity.id,
|
|
549
|
+
title: item.entity.title,
|
|
550
|
+
type: item.entity.type,
|
|
551
|
+
tier: item.entity.memory_tier,
|
|
552
|
+
relevanceScore: item.score,
|
|
553
|
+
reason: "below_relevance_threshold"
|
|
554
|
+
});
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
558
|
+
const tokens = estimateTokens(`### ${item.entity.title}
|
|
559
|
+
${text}`);
|
|
560
|
+
if (procedureUsed + tokens > procedureBudget) {
|
|
561
|
+
const totalRemaining = tokenBudget - totalUsed;
|
|
562
|
+
if (tokens > totalRemaining) {
|
|
563
|
+
manifest.excluded.push({
|
|
564
|
+
entityId: item.entity.id,
|
|
565
|
+
title: item.entity.title,
|
|
566
|
+
type: item.entity.type,
|
|
567
|
+
tier: item.entity.memory_tier,
|
|
568
|
+
relevanceScore: item.score,
|
|
569
|
+
reason: "procedure_budget_exceeded"
|
|
345
570
|
});
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (totalUsed + tokens > tokenBudget) {
|
|
575
|
+
manifest.excluded.push({
|
|
576
|
+
entityId: item.entity.id,
|
|
577
|
+
title: item.entity.title,
|
|
578
|
+
type: item.entity.type,
|
|
579
|
+
tier: item.entity.memory_tier,
|
|
580
|
+
relevanceScore: item.score,
|
|
581
|
+
reason: "total_budget_exceeded"
|
|
582
|
+
});
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
included.push({ ...item, tokens, truncated });
|
|
586
|
+
item.entity.content = text;
|
|
587
|
+
totalUsed += tokens;
|
|
588
|
+
procedureUsed += tokens;
|
|
589
|
+
includedIds.add(item.entity.id);
|
|
590
|
+
}
|
|
591
|
+
for (const item of scored) {
|
|
592
|
+
if (includedIds.has(item.entity.id))
|
|
593
|
+
continue;
|
|
594
|
+
if (item.entity.type === "procedure")
|
|
595
|
+
continue;
|
|
596
|
+
if (item.score < MIN_RELEVANCE_THRESHOLD) {
|
|
597
|
+
manifest.excluded.push({
|
|
598
|
+
entityId: item.entity.id,
|
|
599
|
+
title: item.entity.title,
|
|
600
|
+
type: item.entity.type,
|
|
601
|
+
tier: item.entity.memory_tier,
|
|
602
|
+
relevanceScore: item.score,
|
|
603
|
+
reason: "below_relevance_threshold"
|
|
604
|
+
});
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const tier = item.entity.memory_tier;
|
|
608
|
+
const { text, truncated } = truncateContent(item.entity.content, MAX_TOKENS_PER_ENTITY);
|
|
609
|
+
const tokens = estimateTokens(`### ${item.entity.title}
|
|
610
|
+
${text}`);
|
|
611
|
+
if (tierUsed[tier] + tokens > tierBudgets[tier]) {
|
|
612
|
+
const totalRemaining = tokenBudget - totalUsed;
|
|
613
|
+
if (tokens > totalRemaining) {
|
|
614
|
+
manifest.excluded.push({
|
|
615
|
+
entityId: item.entity.id,
|
|
616
|
+
title: item.entity.title,
|
|
617
|
+
type: item.entity.type,
|
|
618
|
+
tier,
|
|
619
|
+
relevanceScore: item.score,
|
|
620
|
+
reason: "budget_exceeded"
|
|
389
621
|
});
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (totalUsed + tokens > tokenBudget) {
|
|
626
|
+
manifest.excluded.push({
|
|
627
|
+
entityId: item.entity.id,
|
|
628
|
+
title: item.entity.title,
|
|
629
|
+
type: item.entity.type,
|
|
630
|
+
tier,
|
|
631
|
+
relevanceScore: item.score,
|
|
632
|
+
reason: "total_budget_exceeded"
|
|
633
|
+
});
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
included.push({ ...item, tokens, truncated });
|
|
637
|
+
item.entity.content = text;
|
|
638
|
+
totalUsed += tokens;
|
|
639
|
+
tierUsed[tier] += tokens;
|
|
640
|
+
includedIds.add(item.entity.id);
|
|
641
|
+
}
|
|
642
|
+
manifest.budgetUsed = totalUsed;
|
|
643
|
+
const procedureItems = included.filter((i) => i.entity.type === "procedure");
|
|
644
|
+
manifest.tierBreakdown = {
|
|
645
|
+
reference: {
|
|
646
|
+
count: included.filter((i) => i.entity.memory_tier === "reference" && i.entity.type !== "procedure").length,
|
|
647
|
+
tokens: tierUsed.reference
|
|
648
|
+
},
|
|
649
|
+
episode: {
|
|
650
|
+
count: included.filter((i) => i.entity.memory_tier === "episode" && i.entity.type !== "procedure").length,
|
|
651
|
+
tokens: tierUsed.episode
|
|
652
|
+
},
|
|
653
|
+
draft: {
|
|
654
|
+
count: included.filter((i) => i.entity.memory_tier === "draft" && i.entity.type !== "procedure").length,
|
|
655
|
+
tokens: tierUsed.draft
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
manifest.procedureBreakdown = {
|
|
659
|
+
count: procedureItems.length,
|
|
660
|
+
tokens: procedureUsed,
|
|
661
|
+
budget: procedureBudget
|
|
662
|
+
};
|
|
663
|
+
for (const item of included) {
|
|
664
|
+
manifest.included.push({
|
|
665
|
+
entityId: item.entity.id,
|
|
666
|
+
title: item.entity.title,
|
|
667
|
+
type: item.entity.type,
|
|
668
|
+
tier: item.entity.memory_tier,
|
|
669
|
+
relevanceScore: item.score,
|
|
670
|
+
reasons: item.reasons,
|
|
671
|
+
tokenCount: item.tokens,
|
|
672
|
+
truncated: item.truncated
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
const contextSections = [];
|
|
676
|
+
const nonProcedureItems = included.filter((i) => i.entity.type !== "procedure");
|
|
677
|
+
if (included.length > 0) {
|
|
678
|
+
if (procedureItems.length > 0) {
|
|
679
|
+
contextSections.push(`## Procedures (${procedureItems.length} loaded, ${procedureUsed}/${procedureBudget} tokens)`);
|
|
680
|
+
for (const item of procedureItems) {
|
|
681
|
+
const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
|
|
682
|
+
const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
|
|
683
|
+
contextSections.push(`
|
|
684
|
+
### ${item.entity.title} (confidence: ${item.entity.confidence})${tierLabel}${tags}`);
|
|
685
|
+
contextSections.push(item.entity.content);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (nonProcedureItems.length > 0) {
|
|
689
|
+
contextSections.push(`
|
|
690
|
+
## Relevant Memories (${nonProcedureItems.length} loaded, ${manifest.excluded.length} excluded)`);
|
|
691
|
+
contextSections.push(`*Assembly: ${assemblyId} | Budget: ${totalUsed}/${tokenBudget} tokens*`);
|
|
692
|
+
for (const item of nonProcedureItems) {
|
|
693
|
+
const tags = item.entity.tags.length > 0 ? ` [${item.entity.tags.join(", ")}]` : "";
|
|
694
|
+
const tierLabel = item.entity.memory_tier !== "reference" ? ` (${item.entity.memory_tier})` : "";
|
|
695
|
+
contextSections.push(`
|
|
696
|
+
### ${item.entity.title} (${item.entity.type}, confidence: ${item.entity.confidence})${tierLabel}${tags}`);
|
|
697
|
+
contextSections.push(item.entity.content);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
incrementAccessCounts(client2, included.map((i) => i.entity.id)).catch(() => {});
|
|
702
|
+
promoteEligibleEntities(client2, included.map((i) => i.entity)).catch(() => {});
|
|
703
|
+
return {
|
|
704
|
+
context: contextSections.join(`
|
|
705
|
+
`),
|
|
706
|
+
manifest,
|
|
707
|
+
memories: included.map((i) => i.entity)
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function mapToContextEntity(raw) {
|
|
711
|
+
const e = raw;
|
|
712
|
+
return {
|
|
713
|
+
id: e.id,
|
|
714
|
+
type: e.type,
|
|
715
|
+
title: e.title,
|
|
716
|
+
content: e.content,
|
|
717
|
+
confidence: e.confidence ?? 1,
|
|
718
|
+
tags: e.tags || [],
|
|
719
|
+
memory_tier: e.memory_tier || "reference",
|
|
720
|
+
access_count: e.access_count || 0,
|
|
721
|
+
last_accessed_at: e.last_accessed_at || null,
|
|
722
|
+
created_at: e.created_at || "",
|
|
723
|
+
updated_at: e.updated_at || "",
|
|
724
|
+
metadata: e.metadata ?? undefined,
|
|
725
|
+
rrf_score: e.rrf_score ?? undefined,
|
|
726
|
+
fts_rank: e.fts_rank ?? undefined,
|
|
727
|
+
semantic_rank: e.semantic_rank ?? undefined
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
async function incrementAccessCounts(client2, entityIds) {
|
|
731
|
+
if (entityIds.length === 0)
|
|
732
|
+
return;
|
|
733
|
+
try {
|
|
734
|
+
await client2.batchTouchMemoryEntities(entityIds);
|
|
735
|
+
} catch {
|
|
736
|
+
await Promise.allSettled(entityIds.map((id) => client2.touchMemoryEntity(id)));
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
async function promoteEligibleEntities(client2, entities) {
|
|
740
|
+
for (const entity of entities) {
|
|
741
|
+
if (entity.memory_tier === "reference")
|
|
742
|
+
continue;
|
|
743
|
+
if (!entity.created_at)
|
|
744
|
+
continue;
|
|
745
|
+
const promotion = checkPromotion(entity.memory_tier, entity.access_count + 1, entity.confidence, entity.created_at);
|
|
746
|
+
if (promotion.eligible && promotion.targetTier) {
|
|
747
|
+
try {
|
|
748
|
+
await client2.updateMemoryEntity(entity.id, {
|
|
749
|
+
memory_tier: promotion.targetTier,
|
|
750
|
+
metadata: {
|
|
751
|
+
...entity.metadata || {},
|
|
752
|
+
promoted_at: new Date().toISOString(),
|
|
753
|
+
promotion_reason: promotion.reason,
|
|
754
|
+
promoted_from: entity.memory_tier
|
|
755
|
+
}
|
|
435
756
|
});
|
|
757
|
+
} catch {}
|
|
436
758
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function cacheManifest(manifest) {
|
|
762
|
+
if (manifestCache.size >= MAX_CACHE_SIZE) {
|
|
763
|
+
const firstKey = manifestCache.keys().next().value;
|
|
764
|
+
if (firstKey)
|
|
765
|
+
manifestCache.delete(firstKey);
|
|
766
|
+
}
|
|
767
|
+
manifestCache.set(manifest.assemblyId, manifest);
|
|
768
|
+
}
|
|
769
|
+
function getCachedManifest(assemblyId) {
|
|
770
|
+
return manifestCache.get(assemblyId);
|
|
771
|
+
}
|
|
772
|
+
function trackSessionAssembly(cardId, assemblyId) {
|
|
773
|
+
if (sessionAssemblyMap.size >= MAX_SESSION_MAP_SIZE) {
|
|
774
|
+
const firstKey = sessionAssemblyMap.keys().next().value;
|
|
775
|
+
if (firstKey)
|
|
776
|
+
sessionAssemblyMap.delete(firstKey);
|
|
777
|
+
}
|
|
778
|
+
sessionAssemblyMap.set(cardId, assemblyId);
|
|
779
|
+
}
|
|
780
|
+
function getSessionAssemblyId(cardId) {
|
|
781
|
+
return sessionAssemblyMap.get(cardId);
|
|
782
|
+
}
|
|
783
|
+
async function recordContextFeedback(client2, cardId, sessionStatus, progressPercent, hadBlockers) {
|
|
784
|
+
const assemblyId = sessionAssemblyMap.get(cardId);
|
|
785
|
+
if (!assemblyId)
|
|
786
|
+
return { adjusted: 0 };
|
|
787
|
+
const manifest = manifestCache.get(assemblyId);
|
|
788
|
+
if (!manifest || manifest.included.length === 0)
|
|
789
|
+
return { adjusted: 0 };
|
|
790
|
+
let adjusted = 0;
|
|
791
|
+
const isSuccess = sessionStatus === "completed" && (progressPercent ?? 0) >= 100;
|
|
792
|
+
for (const entry of manifest.included) {
|
|
793
|
+
try {
|
|
794
|
+
if (isSuccess) {
|
|
795
|
+
const { entity } = await client2.getMemoryEntity(entry.entityId);
|
|
796
|
+
const e = entity;
|
|
797
|
+
const currentUsefulness = e.metadata?.usefulness_score ?? 0;
|
|
798
|
+
const newConfidence = Math.min((e.confidence ?? 0.5) + 0.05, 1);
|
|
799
|
+
await client2.updateMemoryEntity(entry.entityId, {
|
|
800
|
+
confidence: newConfidence,
|
|
801
|
+
metadata: {
|
|
802
|
+
usefulness_score: currentUsefulness + 1,
|
|
803
|
+
last_feedback_at: new Date().toISOString()
|
|
804
|
+
}
|
|
476
805
|
});
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
806
|
+
adjusted++;
|
|
807
|
+
} else if (hadBlockers) {
|
|
808
|
+
const { entity } = await client2.getMemoryEntity(entry.entityId);
|
|
809
|
+
const e = entity;
|
|
810
|
+
const newConfidence = Math.max((e.confidence ?? 0.5) - 0.02, 0.1);
|
|
811
|
+
await client2.updateMemoryEntity(entry.entityId, {
|
|
812
|
+
confidence: newConfidence,
|
|
813
|
+
metadata: {
|
|
814
|
+
last_feedback_at: new Date().toISOString()
|
|
815
|
+
}
|
|
481
816
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
817
|
+
adjusted++;
|
|
818
|
+
}
|
|
819
|
+
} catch {}
|
|
820
|
+
}
|
|
821
|
+
sessionAssemblyMap.delete(cardId);
|
|
822
|
+
return { adjusted };
|
|
823
|
+
}
|
|
824
|
+
var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.15, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 1, GRAPH_WALK_MAX_DEPTH = 1, GRAPH_WALK_MAX_ENTITIES = 10, GRAPH_WALK_MIN_CONFIDENCE = 0.5, GRAPH_WALK_SEED_COUNT = 5, MAX_QUERY_VARIATIONS = 4, RERANK_CLUSTER_THRESHOLD = 0.05, RERANK_TOP_N = 10, RERANK_MIN_CANDIDATES = 5, RELATION_BONUSES, QUERY_SYNONYMS, manifestCache, MAX_CACHE_SIZE = 50, sessionAssemblyMap, MAX_SESSION_MAP_SIZE = 100;
|
|
825
|
+
var init_context_assembly = __esm(() => {
|
|
826
|
+
init_dist();
|
|
827
|
+
TIER_WEIGHTS = {
|
|
828
|
+
reference: 1,
|
|
829
|
+
episode: 0.7,
|
|
830
|
+
draft: 0.4
|
|
831
|
+
};
|
|
832
|
+
TIER_BUDGET_ALLOCATION = {
|
|
833
|
+
reference: 0.6,
|
|
834
|
+
episode: 0.3,
|
|
835
|
+
draft: 0.1
|
|
836
|
+
};
|
|
837
|
+
RELATION_BONUSES = {
|
|
838
|
+
depends_on: 0.15,
|
|
839
|
+
resolved_by: 0.2,
|
|
840
|
+
relates_to: 0.1,
|
|
841
|
+
implements: 0.15,
|
|
842
|
+
blocks: 0.15,
|
|
843
|
+
references: 0.1,
|
|
844
|
+
extends: 0.1,
|
|
845
|
+
caused_by: 0.15
|
|
846
|
+
};
|
|
847
|
+
QUERY_SYNONYMS = {
|
|
848
|
+
auth: ["authentication", "authorization", "session"],
|
|
849
|
+
authentication: ["auth", "session", "sign-in"],
|
|
850
|
+
login: ["sign-in", "authentication", "session"],
|
|
851
|
+
bug: ["error", "issue", "defect", "problem"],
|
|
852
|
+
error: ["exception", "failure", "issue"],
|
|
853
|
+
fix: ["resolve", "patch", "repair", "correct"],
|
|
854
|
+
deploy: ["deployment", "release", "ship", "publish"],
|
|
855
|
+
test: ["testing", "spec", "assertion", "verify"],
|
|
856
|
+
config: ["configuration", "settings", "setup"],
|
|
857
|
+
db: ["database", "storage", "persistence"],
|
|
858
|
+
database: ["storage", "persistence", "data store"],
|
|
859
|
+
api: ["endpoint", "route", "service"],
|
|
860
|
+
ui: ["frontend", "component", "view"],
|
|
861
|
+
perf: ["performance", "speed", "latency"],
|
|
862
|
+
performance: ["speed", "latency", "optimization"]
|
|
863
|
+
};
|
|
864
|
+
manifestCache = new Map;
|
|
865
|
+
sessionAssemblyMap = new Map;
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
// src/prompt-builder.ts
|
|
869
|
+
var exports_prompt_builder = {};
|
|
870
|
+
__export(exports_prompt_builder, {
|
|
871
|
+
inferCategoryFromLabels: () => inferCategoryFromLabels,
|
|
872
|
+
getRoleFraming: () => getRoleFraming,
|
|
873
|
+
getAvailableVariants: () => getAvailableVariants,
|
|
874
|
+
getAvailableCategories: () => getAvailableCategories,
|
|
875
|
+
generatePrompt: () => generatePrompt
|
|
876
|
+
});
|
|
877
|
+
function inferCategoryFromLabels(labels) {
|
|
878
|
+
for (const label of labels) {
|
|
879
|
+
const normalizedName = label.name.toLowerCase().trim();
|
|
880
|
+
if (LABEL_CATEGORY_MAP[normalizedName]) {
|
|
881
|
+
return LABEL_CATEGORY_MAP[normalizedName];
|
|
882
|
+
}
|
|
883
|
+
for (const [key, category] of Object.entries(LABEL_CATEGORY_MAP)) {
|
|
884
|
+
if (normalizedName.includes(key) || key.includes(normalizedName)) {
|
|
885
|
+
return category;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return "custom";
|
|
890
|
+
}
|
|
891
|
+
function getRoleFraming(category) {
|
|
892
|
+
return DEFAULT_ROLE_FRAMINGS[category];
|
|
893
|
+
}
|
|
894
|
+
function estimateTokens2(text) {
|
|
895
|
+
return Math.ceil(text.length / 4);
|
|
896
|
+
}
|
|
897
|
+
function formatSubtasks(subtasks) {
|
|
898
|
+
if (subtasks.length === 0)
|
|
899
|
+
return "";
|
|
900
|
+
const completed = subtasks.filter((s) => s.completed).length;
|
|
901
|
+
const lines = subtasks.map((s) => ` ${s.completed ? "[x]" : "[ ]"} ${s.title}`);
|
|
902
|
+
return `
|
|
903
|
+
## Subtasks (${completed}/${subtasks.length} completed)
|
|
904
|
+
${lines.join(`
|
|
905
|
+
`)}`;
|
|
906
|
+
}
|
|
907
|
+
function formatLabels(labels) {
|
|
908
|
+
if (labels.length === 0)
|
|
909
|
+
return "";
|
|
910
|
+
return `
|
|
911
|
+
**Labels:** ${labels.map((l) => l.name).join(", ")}`;
|
|
912
|
+
}
|
|
913
|
+
function formatLinkedCards(links) {
|
|
914
|
+
if (!links || links.length === 0)
|
|
915
|
+
return "";
|
|
916
|
+
const lines = links.map((link) => {
|
|
917
|
+
const prefix = link.direction === "outgoing" ? "->" : "<-";
|
|
918
|
+
return ` ${prefix} #${link.target_card.short_id}: ${link.target_card.title} (${link.display_type})`;
|
|
919
|
+
});
|
|
920
|
+
return `
|
|
921
|
+
## Related Cards
|
|
922
|
+
${lines.join(`
|
|
923
|
+
`)}`;
|
|
924
|
+
}
|
|
925
|
+
function generatePrompt(options) {
|
|
926
|
+
const {
|
|
927
|
+
card,
|
|
928
|
+
column,
|
|
929
|
+
variant,
|
|
930
|
+
customConstraints,
|
|
931
|
+
memories,
|
|
932
|
+
assembledContext,
|
|
933
|
+
assemblyId
|
|
934
|
+
} = options;
|
|
935
|
+
const contextOpts = {
|
|
936
|
+
includeTitle: true,
|
|
937
|
+
includeDescription: true,
|
|
938
|
+
includeLabels: true,
|
|
939
|
+
includeSubtasks: true,
|
|
940
|
+
includeActivity: false,
|
|
941
|
+
includeAssignee: true,
|
|
942
|
+
includeDueDate: true,
|
|
943
|
+
includePriority: true,
|
|
944
|
+
includeLinks: true,
|
|
945
|
+
includeColumn: true,
|
|
946
|
+
...options.contextOptions
|
|
947
|
+
};
|
|
948
|
+
const labels = card.labels || [];
|
|
949
|
+
const subtasks = card.subtasks || [];
|
|
950
|
+
const links = card.links || [];
|
|
951
|
+
const category = inferCategoryFromLabels(labels);
|
|
952
|
+
const roleFraming = getRoleFraming(category);
|
|
953
|
+
const sections = [];
|
|
954
|
+
sections.push(`# Role: ${roleFraming.role}
|
|
955
|
+
`);
|
|
956
|
+
sections.push(roleFraming.perspective);
|
|
957
|
+
sections.push("");
|
|
958
|
+
sections.push(VARIANT_INSTRUCTIONS[variant]);
|
|
959
|
+
sections.push("");
|
|
960
|
+
sections.push(`# Task: ${card.title}`);
|
|
961
|
+
if (contextOpts.includeColumn && column) {
|
|
962
|
+
sections.push(`**Status:** ${column.name}`);
|
|
963
|
+
}
|
|
964
|
+
if (contextOpts.includePriority) {
|
|
965
|
+
sections.push(`**Priority:** ${card.priority}`);
|
|
966
|
+
}
|
|
967
|
+
if (contextOpts.includeDueDate && card.due_date) {
|
|
968
|
+
sections.push(`**Due:** ${card.due_date}`);
|
|
969
|
+
}
|
|
970
|
+
if (contextOpts.includeAssignee && card.assignee) {
|
|
971
|
+
sections.push(`**Assignee:** ${card.assignee.full_name || card.assignee.email}`);
|
|
972
|
+
}
|
|
973
|
+
if (contextOpts.includeLabels && labels.length > 0) {
|
|
974
|
+
sections.push(formatLabels(labels));
|
|
975
|
+
}
|
|
976
|
+
if (contextOpts.includeDescription && card.description) {
|
|
977
|
+
sections.push(`
|
|
978
|
+
## Description
|
|
979
|
+
${card.description}`);
|
|
980
|
+
}
|
|
981
|
+
if (contextOpts.includeSubtasks && subtasks.length > 0) {
|
|
982
|
+
sections.push(formatSubtasks(subtasks));
|
|
983
|
+
}
|
|
984
|
+
if (contextOpts.includeLinks && links.length > 0) {
|
|
985
|
+
sections.push(formatLinkedCards(links));
|
|
986
|
+
}
|
|
987
|
+
sections.push(`
|
|
988
|
+
## Focus Areas`);
|
|
989
|
+
roleFraming.focus.forEach((f) => {
|
|
990
|
+
sections.push(`- ${f}`);
|
|
991
|
+
});
|
|
992
|
+
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).`);
|
|
993
|
+
sections.push(` - GOOD: "BoardContext card state must use moveCard action, never direct setState — optimistic updates depend on action ordering"`);
|
|
994
|
+
sections.push(` - GOOD: "Mobile bottom bar is 64px, overlaps fixed-position drawers — always add pb-16 to drawer content"`);
|
|
995
|
+
sections.push(` - BAD: "Fixed the login button" (no reusable knowledge — the fix is in the code)`);
|
|
996
|
+
sections.push(` - BAD: "Completed card #42" (ephemeral, auto-tracked by session)`);
|
|
997
|
+
sections.push(`
|
|
998
|
+
## Suggested Outputs`);
|
|
999
|
+
roleFraming.outputSuggestions.forEach((s) => {
|
|
1000
|
+
sections.push(`- ${s}`);
|
|
1001
|
+
});
|
|
1002
|
+
if (assembledContext) {
|
|
1003
|
+
sections.push(`
|
|
1004
|
+
${assembledContext}`);
|
|
1005
|
+
} else if (memories && memories.length > 0) {
|
|
1006
|
+
sections.push(`
|
|
1007
|
+
## Relevant Memories`);
|
|
1008
|
+
sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
|
|
1009
|
+
for (const memory of memories) {
|
|
1010
|
+
const tags = memory.tags.length > 0 ? ` [${memory.tags.join(", ")}]` : "";
|
|
1011
|
+
sections.push(`
|
|
1012
|
+
### ${memory.title} (${memory.type}, confidence: ${memory.confidence})${tags}`);
|
|
1013
|
+
sections.push(memory.content);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
const oneThingLine = synthesizeOneThing(card, subtasks, links, assembledContext);
|
|
1017
|
+
if (oneThingLine) {
|
|
1018
|
+
sections.push(`
|
|
1019
|
+
## Recommended Next Step
|
|
1020
|
+
${oneThingLine}`);
|
|
1021
|
+
}
|
|
1022
|
+
if (variant === "execute") {
|
|
1023
|
+
sections.push(`
|
|
1024
|
+
## Progress Tracking
|
|
1025
|
+
Update your progress by calling \`harmony_update_agent_progress\` with \`currentTask\` describing what you're doing now:
|
|
1026
|
+
- After exploring the codebase and understanding requirements (~20%)
|
|
1027
|
+
- When you start implementing changes (~50%)
|
|
1028
|
+
- When you move to testing or verification (~80%)
|
|
1029
|
+
- When done, before ending the session (100%)
|
|
1030
|
+
|
|
1031
|
+
Keep \`currentTask\` specific (e.g., "Refactoring auth middleware" not "Working on card").`);
|
|
1032
|
+
}
|
|
1033
|
+
if (customConstraints) {
|
|
1034
|
+
sections.push(`
|
|
1035
|
+
## Additional Instructions
|
|
1036
|
+
${customConstraints}`);
|
|
1037
|
+
}
|
|
1038
|
+
sections.push(`
|
|
1039
|
+
---
|
|
1040
|
+
*Card #${card.short_id} | Generated for ${variant} mode*`);
|
|
1041
|
+
const prompt = sections.join(`
|
|
1042
|
+
`);
|
|
1043
|
+
const memoryCount = assembledContext ? (assembledContext.match(/^### /gm) || []).length : memories?.length || 0;
|
|
1044
|
+
return {
|
|
1045
|
+
prompt,
|
|
1046
|
+
variant,
|
|
1047
|
+
category,
|
|
1048
|
+
role: roleFraming.role,
|
|
1049
|
+
contextSummary: {
|
|
1050
|
+
hasDescription: !!card.description,
|
|
1051
|
+
labelCount: labels.length,
|
|
1052
|
+
subtaskCount: subtasks.length,
|
|
1053
|
+
completedSubtasks: subtasks.filter((s) => s.completed).length,
|
|
1054
|
+
linkedCardCount: links.length,
|
|
1055
|
+
memoryCount
|
|
1056
|
+
},
|
|
1057
|
+
tokenEstimate: estimateTokens2(prompt),
|
|
1058
|
+
...assemblyId && { assemblyId }
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function extractSessionInsights(assembledContext) {
|
|
1062
|
+
const result = {
|
|
1063
|
+
lastSessionStatus: null,
|
|
1064
|
+
lastSessionTask: null,
|
|
1065
|
+
lastSessionProgress: null,
|
|
1066
|
+
blockers: [],
|
|
1067
|
+
procedureNextStep: null
|
|
1068
|
+
};
|
|
1069
|
+
const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
|
|
1070
|
+
if (sessionMatches && sessionMatches.length > 0) {
|
|
1071
|
+
const latest = sessionMatches[0];
|
|
1072
|
+
if (/Completed work on/i.test(latest)) {
|
|
1073
|
+
result.lastSessionStatus = "completed";
|
|
1074
|
+
} else if (/Paused work on|status:\s*paused/i.test(latest)) {
|
|
1075
|
+
result.lastSessionStatus = "paused";
|
|
1076
|
+
}
|
|
1077
|
+
const taskMatch = latest.match(/Final task:\s*(.+)/);
|
|
1078
|
+
if (taskMatch)
|
|
1079
|
+
result.lastSessionTask = taskMatch[1].trim();
|
|
1080
|
+
const progressMatch = latest.match(/Progress:\s*(\d+)%/);
|
|
1081
|
+
if (progressMatch)
|
|
1082
|
+
result.lastSessionProgress = parseInt(progressMatch[1], 10);
|
|
1083
|
+
}
|
|
1084
|
+
const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
|
|
1085
|
+
if (blockerMatches) {
|
|
1086
|
+
result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
|
|
1087
|
+
}
|
|
1088
|
+
const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
|
|
1089
|
+
if (stepMatches && stepMatches.length > 0) {
|
|
1090
|
+
result.procedureNextStep = stepMatches[0].replace(/^\d+\.\s+/, "").replace(/\s*\*\*\[key step\]\*\*.*$/, "").trim();
|
|
1091
|
+
}
|
|
1092
|
+
return result;
|
|
1093
|
+
}
|
|
1094
|
+
function synthesizeOneThing(card, subtasks, links, assembledContext) {
|
|
1095
|
+
if (card.done)
|
|
1096
|
+
return null;
|
|
1097
|
+
const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
|
|
1098
|
+
if (blockers.length > 0) {
|
|
1099
|
+
const blocker = blockers[0];
|
|
1100
|
+
return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
|
|
1101
|
+
}
|
|
1102
|
+
const session = assembledContext ? extractSessionInsights(assembledContext) : null;
|
|
1103
|
+
if (session?.blockers && session.blockers.length > 0) {
|
|
1104
|
+
return `Resolve blocker: ${session.blockers[0]}`;
|
|
1105
|
+
}
|
|
1106
|
+
if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
|
|
1107
|
+
const progress = session.lastSessionProgress ? ` (was ${session.lastSessionProgress}% complete)` : "";
|
|
1108
|
+
return `Resume previous session${progress}: "${session.lastSessionTask}".`;
|
|
1109
|
+
}
|
|
1110
|
+
if (subtasks.length > 0) {
|
|
1111
|
+
const completed = subtasks.filter((s) => s.completed).length;
|
|
1112
|
+
if (completed === subtasks.length) {
|
|
1113
|
+
return "All subtasks completed. Review the work and mark the card as done.";
|
|
1114
|
+
}
|
|
1115
|
+
const nextSubtask = subtasks.find((s) => !s.completed);
|
|
1116
|
+
if (nextSubtask) {
|
|
1117
|
+
return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (session?.procedureNextStep) {
|
|
1121
|
+
return `Follow procedure: ${session.procedureNextStep}`;
|
|
1122
|
+
}
|
|
1123
|
+
if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
|
|
1124
|
+
return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
|
|
1125
|
+
}
|
|
1126
|
+
if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
|
|
1127
|
+
return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
|
|
1128
|
+
}
|
|
1129
|
+
if (card.description) {
|
|
1130
|
+
return "Analyze the description, identify the approach, and begin implementation.";
|
|
1131
|
+
}
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
function getAvailableCategories() {
|
|
1135
|
+
return ["bug", "feature", "design", "review", "onboarding", "epic", "custom"];
|
|
1136
|
+
}
|
|
1137
|
+
function getAvailableVariants() {
|
|
1138
|
+
return ["analysis", "draft", "execute"];
|
|
1139
|
+
}
|
|
1140
|
+
var LABEL_CATEGORY_MAP, DEFAULT_ROLE_FRAMINGS, VARIANT_INSTRUCTIONS;
|
|
1141
|
+
var init_prompt_builder = __esm(() => {
|
|
1142
|
+
LABEL_CATEGORY_MAP = {
|
|
1143
|
+
bug: "bug",
|
|
1144
|
+
fix: "bug",
|
|
1145
|
+
hotfix: "bug",
|
|
1146
|
+
defect: "bug",
|
|
1147
|
+
issue: "bug",
|
|
1148
|
+
error: "bug",
|
|
1149
|
+
feature: "feature",
|
|
1150
|
+
enhancement: "feature",
|
|
1151
|
+
improvement: "feature",
|
|
1152
|
+
new: "feature",
|
|
1153
|
+
design: "design",
|
|
1154
|
+
ui: "design",
|
|
1155
|
+
ux: "design",
|
|
1156
|
+
frontend: "design",
|
|
1157
|
+
styling: "design",
|
|
1158
|
+
review: "review",
|
|
1159
|
+
"code review": "review",
|
|
1160
|
+
pr: "review",
|
|
1161
|
+
feedback: "review",
|
|
1162
|
+
onboarding: "onboarding",
|
|
1163
|
+
documentation: "onboarding",
|
|
1164
|
+
docs: "onboarding",
|
|
1165
|
+
guide: "onboarding",
|
|
1166
|
+
tutorial: "onboarding",
|
|
1167
|
+
epic: "epic",
|
|
1168
|
+
initiative: "epic",
|
|
1169
|
+
project: "epic",
|
|
1170
|
+
milestone: "epic"
|
|
1171
|
+
};
|
|
1172
|
+
DEFAULT_ROLE_FRAMINGS = {
|
|
1173
|
+
bug: {
|
|
1174
|
+
category: "bug",
|
|
1175
|
+
role: "Senior QA Engineer and Software Developer",
|
|
1176
|
+
perspective: "You are investigating and fixing a bug report.",
|
|
1177
|
+
focus: [
|
|
1178
|
+
"Root cause analysis",
|
|
1179
|
+
"Steps to reproduce",
|
|
1180
|
+
"Impact assessment",
|
|
1181
|
+
"Fix implementation",
|
|
1182
|
+
"Regression prevention",
|
|
1183
|
+
"Test cases to prevent recurrence"
|
|
1184
|
+
],
|
|
1185
|
+
outputSuggestions: [
|
|
1186
|
+
"Bug triage summary",
|
|
1187
|
+
"Root cause explanation",
|
|
1188
|
+
"Fix implementation plan",
|
|
1189
|
+
"Test cases"
|
|
1190
|
+
]
|
|
1191
|
+
},
|
|
1192
|
+
feature: {
|
|
1193
|
+
category: "feature",
|
|
1194
|
+
role: "Product Engineer",
|
|
1195
|
+
perspective: "You are implementing a new feature or enhancement.",
|
|
1196
|
+
focus: [
|
|
1197
|
+
"User requirements",
|
|
1198
|
+
"Technical specification",
|
|
1199
|
+
"Implementation approach",
|
|
1200
|
+
"Edge cases",
|
|
1201
|
+
"Acceptance criteria",
|
|
1202
|
+
"Integration points"
|
|
1203
|
+
],
|
|
1204
|
+
outputSuggestions: [
|
|
1205
|
+
"Technical specification",
|
|
1206
|
+
"Implementation tasks",
|
|
1207
|
+
"Acceptance criteria checklist",
|
|
1208
|
+
"API design"
|
|
1209
|
+
]
|
|
1210
|
+
},
|
|
1211
|
+
design: {
|
|
1212
|
+
category: "design",
|
|
1213
|
+
role: "UX Designer and Frontend Developer",
|
|
1214
|
+
perspective: "You are designing and implementing a user interface.",
|
|
1215
|
+
focus: [
|
|
1216
|
+
"User experience flow",
|
|
1217
|
+
"Visual design consistency",
|
|
1218
|
+
"Accessibility (WCAG)",
|
|
1219
|
+
"Responsive behavior",
|
|
1220
|
+
"Component architecture",
|
|
1221
|
+
"Interaction patterns"
|
|
1222
|
+
],
|
|
1223
|
+
outputSuggestions: [
|
|
1224
|
+
"User flow diagram",
|
|
1225
|
+
"Component specifications",
|
|
1226
|
+
"Accessibility checklist",
|
|
1227
|
+
"Responsive breakpoints"
|
|
1228
|
+
]
|
|
1229
|
+
},
|
|
1230
|
+
review: {
|
|
1231
|
+
category: "review",
|
|
1232
|
+
role: "Code Reviewer and Technical Lead",
|
|
1233
|
+
perspective: "You are reviewing code for quality, correctness, and maintainability.",
|
|
1234
|
+
focus: [
|
|
1235
|
+
"Code correctness",
|
|
1236
|
+
"Performance implications",
|
|
1237
|
+
"Security considerations",
|
|
1238
|
+
"Testing coverage",
|
|
1239
|
+
"Documentation",
|
|
1240
|
+
"Best practices adherence"
|
|
1241
|
+
],
|
|
1242
|
+
outputSuggestions: [
|
|
1243
|
+
"Review checklist",
|
|
1244
|
+
"Suggested improvements",
|
|
1245
|
+
"Test scenarios",
|
|
1246
|
+
"Security audit"
|
|
1247
|
+
]
|
|
1248
|
+
},
|
|
1249
|
+
onboarding: {
|
|
1250
|
+
category: "onboarding",
|
|
1251
|
+
role: "Technical Writer and Developer Advocate",
|
|
1252
|
+
perspective: "You are creating documentation or onboarding materials.",
|
|
1253
|
+
focus: [
|
|
1254
|
+
"Clear step-by-step instructions",
|
|
1255
|
+
"Prerequisites and setup",
|
|
1256
|
+
"Common pitfalls",
|
|
1257
|
+
"Examples and use cases",
|
|
1258
|
+
"Troubleshooting guide",
|
|
1259
|
+
"Related resources"
|
|
1260
|
+
],
|
|
1261
|
+
outputSuggestions: [
|
|
1262
|
+
"Getting started guide",
|
|
1263
|
+
"Step-by-step tutorial",
|
|
1264
|
+
"FAQ section",
|
|
1265
|
+
"Troubleshooting guide"
|
|
1266
|
+
]
|
|
1267
|
+
},
|
|
1268
|
+
epic: {
|
|
1269
|
+
category: "epic",
|
|
1270
|
+
role: "Technical Project Manager and Architect",
|
|
1271
|
+
perspective: "You are planning and coordinating a large initiative.",
|
|
1272
|
+
focus: [
|
|
1273
|
+
"Scope definition",
|
|
1274
|
+
"Task breakdown",
|
|
1275
|
+
"Dependencies",
|
|
1276
|
+
"Risk assessment",
|
|
1277
|
+
"Timeline considerations",
|
|
1278
|
+
"Success metrics"
|
|
1279
|
+
],
|
|
1280
|
+
outputSuggestions: [
|
|
1281
|
+
"Epic breakdown into stories",
|
|
1282
|
+
"Dependency graph",
|
|
1283
|
+
"Risk mitigation plan",
|
|
1284
|
+
"Success criteria"
|
|
1285
|
+
]
|
|
1286
|
+
},
|
|
1287
|
+
custom: {
|
|
1288
|
+
category: "custom",
|
|
1289
|
+
role: "Software Engineer",
|
|
1290
|
+
perspective: "You are working on a software task.",
|
|
1291
|
+
focus: [
|
|
1292
|
+
"Understanding requirements",
|
|
1293
|
+
"Implementation approach",
|
|
1294
|
+
"Quality considerations",
|
|
1295
|
+
"Testing strategy"
|
|
1296
|
+
],
|
|
1297
|
+
outputSuggestions: [
|
|
1298
|
+
"Implementation plan",
|
|
1299
|
+
"Technical notes",
|
|
1300
|
+
"Task checklist"
|
|
1301
|
+
]
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
VARIANT_INSTRUCTIONS = {
|
|
1305
|
+
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.`,
|
|
1306
|
+
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.`,
|
|
1307
|
+
execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
|
|
1308
|
+
};
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
// src/config.ts
|
|
1312
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1313
|
+
import { homedir } from "node:os";
|
|
1314
|
+
import { join } from "node:path";
|
|
1315
|
+
var DEFAULT_API_URL = "https://app.gethmy.com/api";
|
|
1316
|
+
var LOCAL_CONFIG_FILENAME = ".harmony-mcp.json";
|
|
1317
|
+
function getConfigDir() {
|
|
1318
|
+
return join(homedir(), ".harmony-mcp");
|
|
1319
|
+
}
|
|
1320
|
+
function getConfigPath() {
|
|
1321
|
+
return join(getConfigDir(), "config.json");
|
|
1322
|
+
}
|
|
1323
|
+
function getLocalConfigPath(cwd) {
|
|
1324
|
+
return join(cwd || process.cwd(), LOCAL_CONFIG_FILENAME);
|
|
1325
|
+
}
|
|
1326
|
+
function loadConfig() {
|
|
1327
|
+
const configPath = getConfigPath();
|
|
1328
|
+
if (!existsSync(configPath)) {
|
|
1329
|
+
return {
|
|
1330
|
+
apiKey: null,
|
|
1331
|
+
apiUrl: DEFAULT_API_URL,
|
|
1332
|
+
activeWorkspaceId: null,
|
|
1333
|
+
activeProjectId: null,
|
|
1334
|
+
userEmail: null,
|
|
1335
|
+
memoryDir: null
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
try {
|
|
1339
|
+
const data = readFileSync(configPath, "utf-8");
|
|
1340
|
+
const config = JSON.parse(data);
|
|
1341
|
+
return {
|
|
1342
|
+
apiKey: config.apiKey || null,
|
|
1343
|
+
apiUrl: config.apiUrl || DEFAULT_API_URL,
|
|
1344
|
+
activeWorkspaceId: config.activeWorkspaceId || null,
|
|
1345
|
+
activeProjectId: config.activeProjectId || null,
|
|
1346
|
+
userEmail: config.userEmail || null,
|
|
1347
|
+
memoryDir: config.memoryDir || null
|
|
1348
|
+
};
|
|
1349
|
+
} catch {
|
|
1350
|
+
return {
|
|
1351
|
+
apiKey: null,
|
|
1352
|
+
apiUrl: DEFAULT_API_URL,
|
|
1353
|
+
activeWorkspaceId: null,
|
|
1354
|
+
activeProjectId: null,
|
|
1355
|
+
userEmail: null,
|
|
1356
|
+
memoryDir: null
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
function saveConfig(config) {
|
|
1361
|
+
const configDir = getConfigDir();
|
|
1362
|
+
const configPath = getConfigPath();
|
|
1363
|
+
if (!existsSync(configDir)) {
|
|
1364
|
+
mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
1365
|
+
}
|
|
1366
|
+
const existingConfig = loadConfig();
|
|
1367
|
+
const newConfig = { ...existingConfig, ...config };
|
|
1368
|
+
writeFileSync(configPath, JSON.stringify(newConfig, null, 2), {
|
|
1369
|
+
mode: 384
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
function loadLocalConfig(cwd) {
|
|
1373
|
+
const localConfigPath = getLocalConfigPath(cwd);
|
|
1374
|
+
if (!existsSync(localConfigPath)) {
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
try {
|
|
1378
|
+
const data = readFileSync(localConfigPath, "utf-8");
|
|
1379
|
+
const config = JSON.parse(data);
|
|
1380
|
+
return {
|
|
1381
|
+
workspaceId: config.workspaceId || null,
|
|
1382
|
+
projectId: config.projectId || null
|
|
1383
|
+
};
|
|
1384
|
+
} catch {
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function saveLocalConfig(config, cwd) {
|
|
1389
|
+
const localConfigPath = getLocalConfigPath(cwd);
|
|
1390
|
+
const existingConfig = loadLocalConfig(cwd) || {
|
|
1391
|
+
workspaceId: null,
|
|
1392
|
+
projectId: null
|
|
1393
|
+
};
|
|
1394
|
+
const newConfig = { ...existingConfig, ...config };
|
|
1395
|
+
const cleanConfig = {};
|
|
1396
|
+
if (newConfig.workspaceId)
|
|
1397
|
+
cleanConfig.workspaceId = newConfig.workspaceId;
|
|
1398
|
+
if (newConfig.projectId)
|
|
1399
|
+
cleanConfig.projectId = newConfig.projectId;
|
|
1400
|
+
writeFileSync(localConfigPath, JSON.stringify(cleanConfig, null, 2));
|
|
1401
|
+
}
|
|
1402
|
+
function hasLocalConfig(cwd) {
|
|
1403
|
+
return existsSync(getLocalConfigPath(cwd));
|
|
1404
|
+
}
|
|
1405
|
+
function getApiKey() {
|
|
1406
|
+
const config = loadConfig();
|
|
1407
|
+
if (!config.apiKey) {
|
|
1408
|
+
throw new Error(`Not configured. Run "npx @gethmy/mcp setup" to set your API key.
|
|
1409
|
+
` + "You can generate an API key at https://gethmy.com → Settings → API Keys.");
|
|
1410
|
+
}
|
|
1411
|
+
return config.apiKey;
|
|
1412
|
+
}
|
|
1413
|
+
function getApiUrl() {
|
|
1414
|
+
const config = loadConfig();
|
|
1415
|
+
return config.apiUrl;
|
|
1416
|
+
}
|
|
1417
|
+
function getUserEmail() {
|
|
1418
|
+
const config = loadConfig();
|
|
1419
|
+
return config.userEmail;
|
|
1420
|
+
}
|
|
1421
|
+
function setUserEmail(email) {
|
|
1422
|
+
saveConfig({ userEmail: email });
|
|
1423
|
+
}
|
|
1424
|
+
function setActiveWorkspace(workspaceId, options) {
|
|
1425
|
+
if (options?.local) {
|
|
1426
|
+
saveLocalConfig({ workspaceId }, options.cwd);
|
|
1427
|
+
} else {
|
|
1428
|
+
saveConfig({ activeWorkspaceId: workspaceId });
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
function setActiveProject(projectId, options) {
|
|
1432
|
+
if (options?.local) {
|
|
1433
|
+
saveLocalConfig({ projectId }, options.cwd);
|
|
1434
|
+
} else {
|
|
1435
|
+
saveConfig({ activeProjectId: projectId });
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function getActiveWorkspaceId(cwd) {
|
|
1439
|
+
const localConfig = loadLocalConfig(cwd);
|
|
1440
|
+
if (localConfig?.workspaceId) {
|
|
1441
|
+
return localConfig.workspaceId;
|
|
1442
|
+
}
|
|
1443
|
+
return loadConfig().activeWorkspaceId;
|
|
1444
|
+
}
|
|
1445
|
+
function getActiveProjectId(cwd) {
|
|
1446
|
+
const localConfig = loadLocalConfig(cwd);
|
|
1447
|
+
if (localConfig?.projectId) {
|
|
1448
|
+
return localConfig.projectId;
|
|
1449
|
+
}
|
|
1450
|
+
return loadConfig().activeProjectId;
|
|
1451
|
+
}
|
|
1452
|
+
function isConfigured() {
|
|
1453
|
+
const config = loadConfig();
|
|
1454
|
+
return !!config.apiKey;
|
|
1455
|
+
}
|
|
1456
|
+
function areSkillsInstalled(cwd) {
|
|
1457
|
+
const home = homedir();
|
|
1458
|
+
const workingDir = cwd || process.cwd();
|
|
1459
|
+
const foundPaths = [];
|
|
1460
|
+
const globalSkillsDir = join(home, ".agents", "skills");
|
|
1461
|
+
const globalSkillPath = join(globalSkillsDir, "hmy", "SKILL.md");
|
|
1462
|
+
if (existsSync(globalSkillPath)) {
|
|
1463
|
+
foundPaths.push(globalSkillPath);
|
|
1464
|
+
return { installed: true, location: "global", paths: foundPaths };
|
|
1465
|
+
}
|
|
1466
|
+
const claudeGlobalSkill = join(home, ".claude", "skills", "hmy.md");
|
|
1467
|
+
if (existsSync(claudeGlobalSkill)) {
|
|
1468
|
+
foundPaths.push(claudeGlobalSkill);
|
|
1469
|
+
return { installed: true, location: "global", paths: foundPaths };
|
|
1470
|
+
}
|
|
1471
|
+
const claudeGlobalSkillAlt = join(home, ".claude", "skills", "hmy", "SKILL.md");
|
|
1472
|
+
if (existsSync(claudeGlobalSkillAlt)) {
|
|
1473
|
+
foundPaths.push(claudeGlobalSkillAlt);
|
|
1474
|
+
return { installed: true, location: "global", paths: foundPaths };
|
|
1475
|
+
}
|
|
1476
|
+
const localSkillPath = join(workingDir, ".claude", "skills", "hmy.md");
|
|
1477
|
+
if (existsSync(localSkillPath)) {
|
|
1478
|
+
foundPaths.push(localSkillPath);
|
|
1479
|
+
return { installed: true, location: "local", paths: foundPaths };
|
|
1480
|
+
}
|
|
1481
|
+
const localSkillPathAlt = join(workingDir, ".claude", "skills", "hmy", "SKILL.md");
|
|
1482
|
+
if (existsSync(localSkillPathAlt)) {
|
|
1483
|
+
foundPaths.push(localSkillPathAlt);
|
|
1484
|
+
return { installed: true, location: "local", paths: foundPaths };
|
|
1485
|
+
}
|
|
1486
|
+
return { installed: false, location: null, paths: [] };
|
|
1487
|
+
}
|
|
1488
|
+
function hasProjectContext(cwd) {
|
|
1489
|
+
const localConfig = loadLocalConfig(cwd);
|
|
1490
|
+
return !!(localConfig?.workspaceId || localConfig?.projectId);
|
|
1491
|
+
}
|
|
1492
|
+
function getMemoryDir() {
|
|
1493
|
+
const config = loadConfig();
|
|
1494
|
+
if (config.memoryDir)
|
|
1495
|
+
return config.memoryDir;
|
|
1496
|
+
return join(homedir(), ".harmony", "memory");
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// src/api-client.ts
|
|
1500
|
+
var RETRY_CONFIG = {
|
|
1501
|
+
maxRetries: 3,
|
|
1502
|
+
baseDelayMs: 1000,
|
|
1503
|
+
maxDelayMs: 1e4,
|
|
1504
|
+
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
|
|
1505
|
+
};
|
|
1506
|
+
function isRetryableError(error, status) {
|
|
1507
|
+
if (status && RETRY_CONFIG.retryableStatusCodes.includes(status)) {
|
|
1508
|
+
return true;
|
|
1509
|
+
}
|
|
1510
|
+
if (error instanceof TypeError)
|
|
1511
|
+
return true;
|
|
1512
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1513
|
+
return msg.includes("ECONNRESET") || msg.includes("ETIMEDOUT") || msg.includes("fetch failed");
|
|
1514
|
+
}
|
|
1515
|
+
function getRetryDelay(attempt) {
|
|
1516
|
+
const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
|
|
1517
|
+
return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
|
|
1518
|
+
}
|
|
1519
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1520
|
+
|
|
1521
|
+
class Semaphore {
|
|
1522
|
+
permits;
|
|
1523
|
+
queue = [];
|
|
1524
|
+
constructor(permits) {
|
|
1525
|
+
this.permits = permits;
|
|
1526
|
+
}
|
|
1527
|
+
async acquire() {
|
|
1528
|
+
if (this.permits > 0) {
|
|
1529
|
+
this.permits--;
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
return new Promise((resolve) => this.queue.push(resolve));
|
|
1533
|
+
}
|
|
1534
|
+
release() {
|
|
1535
|
+
const next = this.queue.shift();
|
|
1536
|
+
if (next)
|
|
1537
|
+
next();
|
|
1538
|
+
else
|
|
1539
|
+
this.permits++;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
var requestSemaphore = new Semaphore(3);
|
|
1543
|
+
async function signupUser(apiUrl, data) {
|
|
1544
|
+
const url = `${apiUrl}/v1/auth/signup`;
|
|
1545
|
+
const response = await fetch(url, {
|
|
1546
|
+
method: "POST",
|
|
1547
|
+
headers: { "Content-Type": "application/json" },
|
|
1548
|
+
body: JSON.stringify(data)
|
|
1549
|
+
});
|
|
1550
|
+
const result = await response.json();
|
|
1551
|
+
if (!response.ok) {
|
|
1552
|
+
throw new Error(result.error || `Signup failed: ${response.status}`);
|
|
1553
|
+
}
|
|
1554
|
+
return result;
|
|
1555
|
+
}
|
|
1556
|
+
async function requestWithBearer(apiUrl, bearerToken, method, path, body) {
|
|
1557
|
+
const url = `${apiUrl}/v1${path}`;
|
|
1558
|
+
const response = await fetch(url, {
|
|
1559
|
+
method,
|
|
1560
|
+
headers: {
|
|
1561
|
+
"Content-Type": "application/json",
|
|
1562
|
+
Authorization: `Bearer ${bearerToken}`
|
|
1563
|
+
},
|
|
1564
|
+
body: body ? JSON.stringify(body) : undefined
|
|
1565
|
+
});
|
|
1566
|
+
const result = await response.json();
|
|
1567
|
+
if (!response.ok) {
|
|
1568
|
+
throw new Error(result.error || `API error: ${response.status}`);
|
|
1569
|
+
}
|
|
1570
|
+
return result;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
class HarmonyApiClient {
|
|
1574
|
+
apiKey;
|
|
1575
|
+
apiUrl;
|
|
1576
|
+
constructor(options) {
|
|
1577
|
+
this.apiKey = options?.apiKey ?? getApiKey();
|
|
1578
|
+
this.apiUrl = options?.apiUrl ?? getApiUrl();
|
|
1579
|
+
}
|
|
1580
|
+
getApiUrl() {
|
|
1581
|
+
return this.apiUrl;
|
|
1582
|
+
}
|
|
1583
|
+
async request(method, path, body, options) {
|
|
1584
|
+
await requestSemaphore.acquire();
|
|
1585
|
+
try {
|
|
1586
|
+
return await this.requestWithRetry(method, path, body, options);
|
|
1587
|
+
} finally {
|
|
1588
|
+
requestSemaphore.release();
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
async requestRaw(method, path, body, options) {
|
|
1592
|
+
await requestSemaphore.acquire();
|
|
1593
|
+
try {
|
|
1594
|
+
return await this.requestRawWithRetry(method, path, body, options);
|
|
1595
|
+
} finally {
|
|
1596
|
+
requestSemaphore.release();
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
async requestWithRetry(method, path, body, options) {
|
|
1600
|
+
const url = `${this.apiUrl}/v1${path}`;
|
|
1601
|
+
let lastError = null;
|
|
1602
|
+
const contentType = options?.contentType || "application/json";
|
|
1603
|
+
const accept = options?.accept || "application/json";
|
|
1604
|
+
for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
1605
|
+
try {
|
|
1606
|
+
const response = await fetch(url, {
|
|
1607
|
+
method,
|
|
1608
|
+
headers: {
|
|
1609
|
+
"Content-Type": contentType,
|
|
1610
|
+
Accept: accept,
|
|
1611
|
+
"X-API-Key": this.apiKey
|
|
1612
|
+
},
|
|
1613
|
+
body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined)
|
|
495
1614
|
});
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
1615
|
+
const data = await response.json();
|
|
1616
|
+
if (!response.ok) {
|
|
1617
|
+
const errorMsg = data.error || `API error: ${response.status}`;
|
|
1618
|
+
if (!isRetryableError(null, response.status)) {
|
|
1619
|
+
throw new Error(errorMsg);
|
|
1620
|
+
}
|
|
1621
|
+
lastError = new Error(errorMsg);
|
|
1622
|
+
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
1623
|
+
await sleep(getRetryDelay(attempt));
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
throw lastError;
|
|
1627
|
+
}
|
|
1628
|
+
return data;
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1631
|
+
if (!isRetryableError(error))
|
|
1632
|
+
throw lastError;
|
|
1633
|
+
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
1634
|
+
await sleep(getRetryDelay(attempt));
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
throw lastError || new Error("Request failed after retries");
|
|
1639
|
+
}
|
|
1640
|
+
async requestRawWithRetry(method, path, body, options) {
|
|
1641
|
+
const url = `${this.apiUrl}/v1${path}`;
|
|
1642
|
+
let lastError = null;
|
|
1643
|
+
const contentType = options?.contentType || "application/json";
|
|
1644
|
+
const accept = options?.accept || "text/markdown";
|
|
1645
|
+
for (let attempt = 0;attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
1646
|
+
try {
|
|
1647
|
+
const response = await fetch(url, {
|
|
1648
|
+
method,
|
|
1649
|
+
headers: {
|
|
1650
|
+
"Content-Type": contentType,
|
|
1651
|
+
Accept: accept,
|
|
1652
|
+
"X-API-Key": this.apiKey
|
|
1653
|
+
},
|
|
1654
|
+
body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined)
|
|
502
1655
|
});
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
async getPlan(planId) {
|
|
521
|
-
return this.request("GET", `/plans/${planId}`);
|
|
522
|
-
}
|
|
523
|
-
async getPlanByCardId(cardId) {
|
|
524
|
-
return this.request("GET", `/cards/${cardId}/plan`);
|
|
525
|
-
}
|
|
526
|
-
async updatePlan(planId, updates) {
|
|
527
|
-
return this.request("PATCH", `/plans/${planId}`, updates);
|
|
528
|
-
}
|
|
529
|
-
async updatePlanTask(planId, taskId, updates) {
|
|
530
|
-
return this.request("PATCH", `/plans/${planId}/tasks/${taskId}`, updates);
|
|
531
|
-
}
|
|
532
|
-
// ============ ONBOARDING OPERATIONS ============
|
|
533
|
-
async createWorkspace(data) {
|
|
534
|
-
return this.request("POST", "/workspaces", data);
|
|
535
|
-
}
|
|
536
|
-
async createProject(data) {
|
|
537
|
-
return this.request("POST", "/projects", data);
|
|
538
|
-
}
|
|
539
|
-
async sendInvitations(data) {
|
|
540
|
-
return this.request("POST", "/invitations", data);
|
|
541
|
-
}
|
|
542
|
-
async generateApiKey(name) {
|
|
543
|
-
return this.request("POST", "/api-keys", { name });
|
|
544
|
-
}
|
|
545
|
-
// ============ PROMPT GENERATION ============
|
|
546
|
-
/**
|
|
547
|
-
* Generate a prompt for a card with full memory context assembly.
|
|
548
|
-
*
|
|
549
|
-
* This is the shared entry point for prompt generation — used by the MCP
|
|
550
|
-
* server tool handler and the agent daemon. It fetches the card, assembles
|
|
551
|
-
* relevant memories, and produces a role-framed prompt.
|
|
552
|
-
*/
|
|
553
|
-
async generateCardPrompt(options) {
|
|
554
|
-
const { assembleContext, cacheManifest, generatePrompt } = await loadPromptModules();
|
|
555
|
-
// Fetch card data
|
|
556
|
-
const cardResult = await this.getCard(options.cardId);
|
|
557
|
-
const cardData = cardResult.card;
|
|
558
|
-
// Try to get column info
|
|
559
|
-
let columnData = null;
|
|
560
|
-
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
561
|
-
if (projectIdForBoard) {
|
|
562
|
-
try {
|
|
563
|
-
const board = await this.getBoard(projectIdForBoard, { summary: true });
|
|
564
|
-
const column = board.columns.find((col) => col.id === cardData.column_id);
|
|
565
|
-
if (column) {
|
|
566
|
-
columnData = { name: column.name };
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
catch {
|
|
570
|
-
// Column info not available, continue without it
|
|
571
|
-
}
|
|
1656
|
+
if (!response.ok) {
|
|
1657
|
+
const text = await response.text();
|
|
1658
|
+
let errorMsg;
|
|
1659
|
+
try {
|
|
1660
|
+
errorMsg = JSON.parse(text).error || `API error: ${response.status}`;
|
|
1661
|
+
} catch {
|
|
1662
|
+
errorMsg = text || `API error: ${response.status}`;
|
|
1663
|
+
}
|
|
1664
|
+
if (!isRetryableError(null, response.status)) {
|
|
1665
|
+
throw new Error(errorMsg);
|
|
1666
|
+
}
|
|
1667
|
+
lastError = new Error(errorMsg);
|
|
1668
|
+
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
1669
|
+
await sleep(getRetryDelay(attempt));
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
throw lastError;
|
|
572
1673
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
581
|
-
const taskContext = [cardData.title, cardData.description || ""]
|
|
582
|
-
.filter(Boolean)
|
|
583
|
-
.join(" ");
|
|
584
|
-
const assembled = await assembleContext({
|
|
585
|
-
workspaceId: options.workspaceId,
|
|
586
|
-
projectId: options.projectId,
|
|
587
|
-
taskContext,
|
|
588
|
-
cardLabels,
|
|
589
|
-
cardId: cardData.id,
|
|
590
|
-
client: this,
|
|
591
|
-
});
|
|
592
|
-
if (assembled.context) {
|
|
593
|
-
assembledContextStr = assembled.context;
|
|
594
|
-
assemblyId = assembled.manifest.assemblyId;
|
|
595
|
-
cacheManifest(assembled.manifest);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
1674
|
+
return await response.text();
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1677
|
+
if (!isRetryableError(error))
|
|
1678
|
+
throw lastError;
|
|
1679
|
+
if (attempt < RETRY_CONFIG.maxRetries) {
|
|
1680
|
+
await sleep(getRetryDelay(attempt));
|
|
598
1681
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
throw lastError || new Error("Request failed after retries");
|
|
1685
|
+
}
|
|
1686
|
+
async listWorkspaces() {
|
|
1687
|
+
return this.request("GET", "/workspaces");
|
|
1688
|
+
}
|
|
1689
|
+
async getWorkspaceMembers(workspaceId) {
|
|
1690
|
+
return this.request("GET", `/workspaces/${workspaceId}/members`);
|
|
1691
|
+
}
|
|
1692
|
+
async listProjects(workspaceId) {
|
|
1693
|
+
return this.request("GET", `/workspaces/${workspaceId}/projects`);
|
|
1694
|
+
}
|
|
1695
|
+
async getBoard(projectId, options) {
|
|
1696
|
+
const params = new URLSearchParams;
|
|
1697
|
+
if (options?.limit !== undefined)
|
|
1698
|
+
params.set("limit", String(options.limit));
|
|
1699
|
+
if (options?.offset !== undefined)
|
|
1700
|
+
params.set("offset", String(options.offset));
|
|
1701
|
+
if (options?.columnId)
|
|
1702
|
+
params.set("column_id", options.columnId);
|
|
1703
|
+
if (options?.summary)
|
|
1704
|
+
params.set("summary", "true");
|
|
1705
|
+
if (options?.includeArchived)
|
|
1706
|
+
params.set("include_archived", "true");
|
|
1707
|
+
if (options?.labelName)
|
|
1708
|
+
params.set("label_name", options.labelName);
|
|
1709
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1710
|
+
return this.request("GET", `/board/${projectId}${query}`);
|
|
1711
|
+
}
|
|
1712
|
+
async createCard(projectId, data) {
|
|
1713
|
+
return this.request("POST", "/cards", { projectId, ...data });
|
|
1714
|
+
}
|
|
1715
|
+
async updateCard(cardId, updates) {
|
|
1716
|
+
return this.request("PATCH", `/cards/${cardId}`, updates);
|
|
1717
|
+
}
|
|
1718
|
+
async moveCard(cardId, columnId, position) {
|
|
1719
|
+
return this.request("POST", `/cards/${cardId}/move`, {
|
|
1720
|
+
columnId,
|
|
1721
|
+
position
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
async archiveCard(cardId) {
|
|
1725
|
+
return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
|
|
1726
|
+
}
|
|
1727
|
+
async unarchiveCard(cardId) {
|
|
1728
|
+
return this.updateCard(cardId, { archivedAt: null });
|
|
1729
|
+
}
|
|
1730
|
+
async deleteCard(cardId) {
|
|
1731
|
+
return this.request("DELETE", `/cards/${cardId}`);
|
|
1732
|
+
}
|
|
1733
|
+
async getCard(cardId) {
|
|
1734
|
+
return this.request("GET", `/cards/${cardId}`);
|
|
1735
|
+
}
|
|
1736
|
+
async getCardByShortId(projectId, shortId) {
|
|
1737
|
+
return this.request("GET", `/projects/${projectId}/cards/${shortId}`);
|
|
1738
|
+
}
|
|
1739
|
+
async searchCards(query, options) {
|
|
1740
|
+
const params = new URLSearchParams({ q: query });
|
|
1741
|
+
if (options?.projectId) {
|
|
1742
|
+
params.set("project_id", options.projectId);
|
|
1743
|
+
}
|
|
1744
|
+
return this.request("GET", `/search?${params.toString()}`);
|
|
1745
|
+
}
|
|
1746
|
+
async addLabelToCard(cardId, labelId) {
|
|
1747
|
+
return this.request("POST", `/cards/${cardId}/labels`, { labelId });
|
|
1748
|
+
}
|
|
1749
|
+
async removeLabelFromCard(cardId, labelId) {
|
|
1750
|
+
return this.request("DELETE", `/cards/${cardId}/labels/${labelId}`);
|
|
1751
|
+
}
|
|
1752
|
+
async addLinkToCard(sourceCardId, targetCardId, linkType) {
|
|
1753
|
+
return this.request("POST", `/cards/${sourceCardId}/links`, {
|
|
1754
|
+
targetCardId,
|
|
1755
|
+
linkType
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
async removeLinkFromCard(linkId) {
|
|
1759
|
+
return this.request("DELETE", `/card-links/${linkId}`);
|
|
1760
|
+
}
|
|
1761
|
+
async getCardLinks(cardId) {
|
|
1762
|
+
return this.request("GET", `/cards/${cardId}/links`);
|
|
1763
|
+
}
|
|
1764
|
+
async createColumn(projectId, name) {
|
|
1765
|
+
return this.request("POST", "/columns", { projectId, name });
|
|
1766
|
+
}
|
|
1767
|
+
async updateColumn(columnId, name) {
|
|
1768
|
+
return this.request("PATCH", `/columns/${columnId}`, { name });
|
|
1769
|
+
}
|
|
1770
|
+
async deleteColumn(columnId) {
|
|
1771
|
+
return this.request("DELETE", `/columns/${columnId}`);
|
|
1772
|
+
}
|
|
1773
|
+
async createLabel(projectId, data) {
|
|
1774
|
+
return this.request("POST", "/labels", { projectId, ...data });
|
|
1775
|
+
}
|
|
1776
|
+
async createSubtask(cardId, title) {
|
|
1777
|
+
return this.request("POST", "/subtasks", { cardId, title });
|
|
1778
|
+
}
|
|
1779
|
+
async toggleSubtask(subtaskId) {
|
|
1780
|
+
return this.request("POST", `/subtasks/${subtaskId}/toggle`);
|
|
1781
|
+
}
|
|
1782
|
+
async deleteSubtask(subtaskId) {
|
|
1783
|
+
return this.request("DELETE", `/subtasks/${subtaskId}`);
|
|
1784
|
+
}
|
|
1785
|
+
async startAgentSession(cardId, data) {
|
|
1786
|
+
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
1787
|
+
}
|
|
1788
|
+
async updateAgentProgress(cardId, data) {
|
|
1789
|
+
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
1790
|
+
}
|
|
1791
|
+
async endAgentSession(cardId, data) {
|
|
1792
|
+
return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
|
|
1793
|
+
}
|
|
1794
|
+
async flushActivityLog(cardId, data) {
|
|
1795
|
+
return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
|
|
1796
|
+
}
|
|
1797
|
+
async getActivityLog(cardId, sessionId) {
|
|
1798
|
+
return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
|
|
1799
|
+
}
|
|
1800
|
+
async getAgentSession(cardId, options) {
|
|
1801
|
+
const params = new URLSearchParams;
|
|
1802
|
+
if (options?.includeEnded)
|
|
1803
|
+
params.set("include_ended", "true");
|
|
1804
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1805
|
+
return this.request("GET", `/cards/${cardId}/agent-context${query}`);
|
|
1806
|
+
}
|
|
1807
|
+
async getAgentProfile(workspaceId, agentIdentifier) {
|
|
1808
|
+
const params = new URLSearchParams({
|
|
1809
|
+
workspace_id: workspaceId,
|
|
1810
|
+
agent_identifier: agentIdentifier
|
|
1811
|
+
});
|
|
1812
|
+
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
1813
|
+
}
|
|
1814
|
+
async listAgentProfiles(workspaceId) {
|
|
1815
|
+
const params = new URLSearchParams({ workspace_id: workspaceId });
|
|
1816
|
+
return this.request("GET", `/agent-profiles?${params.toString()}`);
|
|
1817
|
+
}
|
|
1818
|
+
async refreshAgentProfiles(workspaceId) {
|
|
1819
|
+
return this.request("POST", "/agent-profiles/refresh", {
|
|
1820
|
+
workspace_id: workspaceId
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
async createMemoryEntity(data) {
|
|
1824
|
+
return this.request("POST", "/memory/entities", data);
|
|
1825
|
+
}
|
|
1826
|
+
async listMemoryEntities(options) {
|
|
1827
|
+
const params = new URLSearchParams;
|
|
1828
|
+
params.set("workspace_id", options.workspace_id);
|
|
1829
|
+
if (options.project_id)
|
|
1830
|
+
params.set("project_id", options.project_id);
|
|
1831
|
+
if (options.type)
|
|
1832
|
+
params.set("type", options.type);
|
|
1833
|
+
if (options.scope)
|
|
1834
|
+
params.set("scope", options.scope);
|
|
1835
|
+
if (options.tags?.length)
|
|
1836
|
+
params.set("tags", options.tags.join(","));
|
|
1837
|
+
if (options.agent_identifier)
|
|
1838
|
+
params.set("agent_identifier", options.agent_identifier);
|
|
1839
|
+
if (options.min_confidence !== undefined)
|
|
1840
|
+
params.set("min_confidence", String(options.min_confidence));
|
|
1841
|
+
if (options.q)
|
|
1842
|
+
params.set("q", options.q);
|
|
1843
|
+
if (options.limit !== undefined)
|
|
1844
|
+
params.set("limit", String(options.limit));
|
|
1845
|
+
if (options.offset !== undefined)
|
|
1846
|
+
params.set("offset", String(options.offset));
|
|
1847
|
+
return this.request("GET", `/memory/entities?${params.toString()}`);
|
|
1848
|
+
}
|
|
1849
|
+
async getMemoryEntity(entityId) {
|
|
1850
|
+
return this.request("GET", `/memory/entities/${entityId}`);
|
|
1851
|
+
}
|
|
1852
|
+
async updateMemoryEntity(entityId, updates) {
|
|
1853
|
+
return this.request("PUT", `/memory/entities/${entityId}`, updates);
|
|
1854
|
+
}
|
|
1855
|
+
async deleteMemoryEntity(entityId) {
|
|
1856
|
+
return this.request("DELETE", `/memory/entities/${entityId}`);
|
|
1857
|
+
}
|
|
1858
|
+
async touchMemoryEntity(entityId) {
|
|
1859
|
+
return this.request("POST", `/memory/entities/${entityId}/touch`);
|
|
1860
|
+
}
|
|
1861
|
+
async batchTouchMemoryEntities(entityIds) {
|
|
1862
|
+
return this.request("POST", "/memory/entities/batch-touch", {
|
|
1863
|
+
entity_ids: entityIds
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
async createMemoryRelation(data) {
|
|
1867
|
+
return this.request("POST", "/memory/relations", data);
|
|
1868
|
+
}
|
|
1869
|
+
async deleteMemoryRelation(relationId) {
|
|
1870
|
+
return this.request("DELETE", `/memory/relations/${relationId}`);
|
|
1871
|
+
}
|
|
1872
|
+
async getRelatedEntities(entityId) {
|
|
1873
|
+
return this.request("GET", `/memory/entities/${entityId}/related`);
|
|
1874
|
+
}
|
|
1875
|
+
async searchMemoryEntities(workspaceId, query, options) {
|
|
1876
|
+
const params = new URLSearchParams;
|
|
1877
|
+
params.set("workspace_id", workspaceId);
|
|
1878
|
+
params.set("q", query);
|
|
1879
|
+
if (options?.project_id)
|
|
1880
|
+
params.set("project_id", options.project_id);
|
|
1881
|
+
if (options?.type)
|
|
1882
|
+
params.set("type", options.type);
|
|
1883
|
+
if (options?.limit !== undefined)
|
|
1884
|
+
params.set("limit", String(options.limit));
|
|
1885
|
+
return this.request("GET", `/memory/search?${params.toString()}`);
|
|
1886
|
+
}
|
|
1887
|
+
async getVaultIndex(options) {
|
|
1888
|
+
const params = new URLSearchParams;
|
|
1889
|
+
params.set("workspace_id", options.workspace_id);
|
|
1890
|
+
if (options.project_id)
|
|
1891
|
+
params.set("project_id", options.project_id);
|
|
1892
|
+
if (options.type)
|
|
1893
|
+
params.set("type", options.type);
|
|
1894
|
+
if (options.limit !== undefined)
|
|
1895
|
+
params.set("limit", String(options.limit));
|
|
1896
|
+
return this.request("GET", `/memory/index?${params.toString()}`);
|
|
1897
|
+
}
|
|
1898
|
+
async getVaultIndexMarkdown(options) {
|
|
1899
|
+
const params = new URLSearchParams;
|
|
1900
|
+
params.set("workspace_id", options.workspace_id);
|
|
1901
|
+
if (options.project_id)
|
|
1902
|
+
params.set("project_id", options.project_id);
|
|
1903
|
+
if (options.type)
|
|
1904
|
+
params.set("type", options.type);
|
|
1905
|
+
if (options.limit !== undefined)
|
|
1906
|
+
params.set("limit", String(options.limit));
|
|
1907
|
+
return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
|
|
1908
|
+
accept: "text/markdown"
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
async getMemoryStats(options) {
|
|
1912
|
+
const params = new URLSearchParams;
|
|
1913
|
+
params.set("workspace_id", options.workspace_id);
|
|
1914
|
+
if (options.project_id)
|
|
1915
|
+
params.set("project_id", options.project_id);
|
|
1916
|
+
return this.request("GET", `/memory/stats?${params.toString()}`);
|
|
1917
|
+
}
|
|
1918
|
+
async refreshMemoryStats(workspaceId) {
|
|
1919
|
+
return this.request("POST", "/memory/stats", { workspace_id: workspaceId });
|
|
1920
|
+
}
|
|
1921
|
+
async resolveLinks(options) {
|
|
1922
|
+
return this.request("POST", "/memory/resolve-links", options);
|
|
1923
|
+
}
|
|
1924
|
+
async listMemoryEntitiesMarkdown(options) {
|
|
1925
|
+
const params = new URLSearchParams;
|
|
1926
|
+
params.set("workspace_id", options.workspace_id);
|
|
1927
|
+
if (options.project_id)
|
|
1928
|
+
params.set("project_id", options.project_id);
|
|
1929
|
+
if (options.type)
|
|
1930
|
+
params.set("type", options.type);
|
|
1931
|
+
if (options.scope)
|
|
1932
|
+
params.set("scope", options.scope);
|
|
1933
|
+
if (options.tags?.length)
|
|
1934
|
+
params.set("tags", options.tags.join(","));
|
|
1935
|
+
if (options.agent_identifier)
|
|
1936
|
+
params.set("agent_identifier", options.agent_identifier);
|
|
1937
|
+
if (options.min_confidence !== undefined)
|
|
1938
|
+
params.set("min_confidence", String(options.min_confidence));
|
|
1939
|
+
if (options.q)
|
|
1940
|
+
params.set("q", options.q);
|
|
1941
|
+
if (options.limit !== undefined)
|
|
1942
|
+
params.set("limit", String(options.limit));
|
|
1943
|
+
if (options.offset !== undefined)
|
|
1944
|
+
params.set("offset", String(options.offset));
|
|
1945
|
+
return this.requestRaw("GET", `/memory/entities?${params.toString()}`, undefined, {
|
|
1946
|
+
accept: "text/markdown"
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
async getMemoryEntityMarkdown(entityId) {
|
|
1950
|
+
return this.requestRaw("GET", `/memory/entities/${entityId}`, undefined, {
|
|
1951
|
+
accept: "text/markdown"
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
async searchMemoryEntitiesMarkdown(workspaceId, query, options) {
|
|
1955
|
+
const params = new URLSearchParams;
|
|
1956
|
+
params.set("workspace_id", workspaceId);
|
|
1957
|
+
params.set("q", query);
|
|
1958
|
+
if (options?.project_id)
|
|
1959
|
+
params.set("project_id", options.project_id);
|
|
1960
|
+
if (options?.type)
|
|
1961
|
+
params.set("type", options.type);
|
|
1962
|
+
if (options?.limit !== undefined)
|
|
1963
|
+
params.set("limit", String(options.limit));
|
|
1964
|
+
return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
|
|
1965
|
+
accept: "text/markdown"
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
async backfillEmbeddings(workspaceId, batchSize) {
|
|
1969
|
+
return this.request("POST", "/memory/backfill-embeddings", {
|
|
1970
|
+
workspace_id: workspaceId,
|
|
1971
|
+
batch_size: batchSize || 50
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
async processNLU(data) {
|
|
1975
|
+
return this.request("POST", "/nlu", data);
|
|
1976
|
+
}
|
|
1977
|
+
async createPlan(projectId, data) {
|
|
1978
|
+
return this.request("POST", "/plans", { projectId, ...data });
|
|
1979
|
+
}
|
|
1980
|
+
async listPlans(projectId, options) {
|
|
1981
|
+
const params = new URLSearchParams({ projectId });
|
|
1982
|
+
if (options?.search)
|
|
1983
|
+
params.set("search", options.search);
|
|
1984
|
+
if (options?.status)
|
|
1985
|
+
params.set("status", options.status);
|
|
1986
|
+
return this.request("GET", `/plans?${params.toString()}`);
|
|
1987
|
+
}
|
|
1988
|
+
async getPlan(planId) {
|
|
1989
|
+
return this.request("GET", `/plans/${planId}`);
|
|
1990
|
+
}
|
|
1991
|
+
async getPlanByCardId(cardId) {
|
|
1992
|
+
return this.request("GET", `/cards/${cardId}/plan`);
|
|
1993
|
+
}
|
|
1994
|
+
async updatePlan(planId, updates) {
|
|
1995
|
+
return this.request("PATCH", `/plans/${planId}`, updates);
|
|
1996
|
+
}
|
|
1997
|
+
async updatePlanTask(planId, taskId, updates) {
|
|
1998
|
+
return this.request("PATCH", `/plans/${planId}/tasks/${taskId}`, updates);
|
|
1999
|
+
}
|
|
2000
|
+
async createWorkspace(data) {
|
|
2001
|
+
return this.request("POST", "/workspaces", data);
|
|
2002
|
+
}
|
|
2003
|
+
async createProject(data) {
|
|
2004
|
+
return this.request("POST", "/projects", data);
|
|
2005
|
+
}
|
|
2006
|
+
async sendInvitations(data) {
|
|
2007
|
+
return this.request("POST", "/invitations", data);
|
|
2008
|
+
}
|
|
2009
|
+
async generateApiKey(name) {
|
|
2010
|
+
return this.request("POST", "/api-keys", { name });
|
|
2011
|
+
}
|
|
2012
|
+
async generateCardPrompt(options) {
|
|
2013
|
+
const { assembleContext: assembleContext2, cacheManifest: cacheManifest2, generatePrompt: generatePrompt2 } = await loadPromptModules();
|
|
2014
|
+
const cardResult = await this.getCard(options.cardId);
|
|
2015
|
+
const cardData = cardResult.card;
|
|
2016
|
+
let columnData = null;
|
|
2017
|
+
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
2018
|
+
if (projectIdForBoard) {
|
|
2019
|
+
try {
|
|
2020
|
+
const board = await this.getBoard(projectIdForBoard, { summary: true });
|
|
2021
|
+
const column = board.columns.find((col) => col.id === cardData.column_id);
|
|
2022
|
+
if (column) {
|
|
2023
|
+
columnData = { name: column.name };
|
|
627
2024
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
2025
|
+
} catch {}
|
|
2026
|
+
}
|
|
2027
|
+
const variant = options.variant || "execute";
|
|
2028
|
+
let assembledContextStr;
|
|
2029
|
+
let assemblyId;
|
|
2030
|
+
let memories;
|
|
2031
|
+
try {
|
|
2032
|
+
if (options.workspaceId && cardData.title) {
|
|
2033
|
+
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
2034
|
+
const taskContext = [cardData.title, cardData.description || ""].filter(Boolean).join(" ");
|
|
2035
|
+
const assembled = await assembleContext2({
|
|
2036
|
+
workspaceId: options.workspaceId,
|
|
2037
|
+
projectId: options.projectId,
|
|
2038
|
+
taskContext,
|
|
2039
|
+
cardLabels,
|
|
2040
|
+
cardId: cardData.id,
|
|
2041
|
+
client: this
|
|
637
2042
|
});
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
2043
|
+
if (assembled.context) {
|
|
2044
|
+
assembledContextStr = assembled.context;
|
|
2045
|
+
assemblyId = assembled.manifest.assemblyId;
|
|
2046
|
+
cacheManifest2(assembled.manifest);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
} catch (err) {
|
|
2050
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2051
|
+
console.debug(`[generateCardPrompt] Context assembly failed: ${msg}`);
|
|
2052
|
+
try {
|
|
2053
|
+
if (options.workspaceId && cardData.title) {
|
|
2054
|
+
const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
|
|
2055
|
+
project_id: options.projectId,
|
|
2056
|
+
limit: 5
|
|
2057
|
+
});
|
|
2058
|
+
if (memoryResult.entities?.length > 0) {
|
|
2059
|
+
memories = memoryResult.entities.map((e) => ({
|
|
2060
|
+
id: e.id,
|
|
2061
|
+
type: e.type,
|
|
2062
|
+
title: e.title,
|
|
2063
|
+
content: e.content,
|
|
2064
|
+
confidence: e.confidence,
|
|
2065
|
+
tags: e.tags || []
|
|
2066
|
+
}));
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
} catch (fallbackErr) {
|
|
2070
|
+
const fallbackMsg = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);
|
|
2071
|
+
console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
const result = generatePrompt2({
|
|
2075
|
+
card: cardData,
|
|
2076
|
+
column: columnData,
|
|
2077
|
+
variant,
|
|
2078
|
+
contextOptions: options.contextOptions,
|
|
2079
|
+
customConstraints: options.customConstraints,
|
|
2080
|
+
memories,
|
|
2081
|
+
assembledContext: assembledContextStr,
|
|
2082
|
+
assemblyId
|
|
2083
|
+
});
|
|
2084
|
+
return {
|
|
2085
|
+
...result,
|
|
2086
|
+
cardId: cardData.id,
|
|
2087
|
+
shortId: cardData.short_id,
|
|
2088
|
+
title: cardData.title
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
645
2091
|
}
|
|
646
|
-
|
|
647
|
-
let _promptModules = null;
|
|
2092
|
+
var _promptModules = null;
|
|
648
2093
|
async function loadPromptModules() {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
// Singleton instance
|
|
663
|
-
let client = null;
|
|
664
|
-
export function getClient() {
|
|
665
|
-
if (!client) {
|
|
666
|
-
client = new HarmonyApiClient();
|
|
667
|
-
}
|
|
668
|
-
return client;
|
|
669
|
-
}
|
|
670
|
-
export function resetClient() {
|
|
671
|
-
client = null;
|
|
2094
|
+
if (!_promptModules) {
|
|
2095
|
+
const [ca, pb] = await Promise.all([
|
|
2096
|
+
Promise.resolve().then(() => (init_context_assembly(), exports_context_assembly)),
|
|
2097
|
+
Promise.resolve().then(() => (init_prompt_builder(), exports_prompt_builder))
|
|
2098
|
+
]);
|
|
2099
|
+
_promptModules = {
|
|
2100
|
+
assembleContext: ca.assembleContext,
|
|
2101
|
+
cacheManifest: ca.cacheManifest,
|
|
2102
|
+
generatePrompt: pb.generatePrompt
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
return _promptModules;
|
|
672
2106
|
}
|
|
2107
|
+
var client2 = null;
|
|
2108
|
+
function getClient() {
|
|
2109
|
+
if (!client2) {
|
|
2110
|
+
client2 = new HarmonyApiClient;
|
|
2111
|
+
}
|
|
2112
|
+
return client2;
|
|
2113
|
+
}
|
|
2114
|
+
function resetClient() {
|
|
2115
|
+
client2 = null;
|
|
2116
|
+
}
|
|
2117
|
+
export {
|
|
2118
|
+
signupUser,
|
|
2119
|
+
resetClient,
|
|
2120
|
+
requestWithBearer,
|
|
2121
|
+
getClient,
|
|
2122
|
+
HarmonyApiClient
|
|
2123
|
+
};
|