@gethmy/mcp 2.3.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +670 -19
- package/dist/index.js +670 -19
- package/package.json +2 -2
- package/src/__tests__/memory-audit.test.ts +296 -0
- package/src/api-client.ts +6 -0
- package/src/memory-audit.ts +485 -0
- package/src/memory-cleanup.ts +247 -1
- package/src/server.ts +237 -11
package/dist/cli.js
CHANGED
|
@@ -28063,6 +28063,334 @@ async function runLifecycleMaintenance(client3, workspaceId, projectId) {
|
|
|
28063
28063
|
return result;
|
|
28064
28064
|
}
|
|
28065
28065
|
|
|
28066
|
+
// src/memory-audit.ts
|
|
28067
|
+
init_dist();
|
|
28068
|
+
var EMBEDDINGS_MIGRATION_AT = Date.parse("2026-02-18T00:00:00Z");
|
|
28069
|
+
var MS_PER_DAY = 1000 * 60 * 60 * 24;
|
|
28070
|
+
var BATCH_SIZE = 100;
|
|
28071
|
+
var CONCURRENCY_LIMIT = 5;
|
|
28072
|
+
var BOILERPLATE_PATTERNS = [
|
|
28073
|
+
/^todo:?$/i,
|
|
28074
|
+
/^placeholder/i,
|
|
28075
|
+
/^\.\.\.$/,
|
|
28076
|
+
/^untitled/i,
|
|
28077
|
+
/^(note|memo|draft)\s*\d*$/i
|
|
28078
|
+
];
|
|
28079
|
+
function isBoilerplate(title, content) {
|
|
28080
|
+
const t = title.trim();
|
|
28081
|
+
const c = content.trim();
|
|
28082
|
+
if (c.length === 0)
|
|
28083
|
+
return true;
|
|
28084
|
+
for (const pat of BOILERPLATE_PATTERNS) {
|
|
28085
|
+
if (pat.test(t))
|
|
28086
|
+
return true;
|
|
28087
|
+
}
|
|
28088
|
+
return false;
|
|
28089
|
+
}
|
|
28090
|
+
function scoreEntity(entity, relationCount, archiveBelow, deleteBelow) {
|
|
28091
|
+
const now = Date.now();
|
|
28092
|
+
const ageDays = (now - Date.parse(entity.created_at)) / MS_PER_DAY;
|
|
28093
|
+
const effectiveLastAccess = entity.last_accessed_at ?? entity.created_at;
|
|
28094
|
+
const lifecycle2 = evaluateLifecycle({
|
|
28095
|
+
memory_tier: entity.memory_tier,
|
|
28096
|
+
confidence: entity.confidence,
|
|
28097
|
+
access_count: entity.access_count,
|
|
28098
|
+
last_accessed_at: effectiveLastAccess,
|
|
28099
|
+
created_at: entity.created_at
|
|
28100
|
+
});
|
|
28101
|
+
const reasons = [];
|
|
28102
|
+
const legacyReasons = [];
|
|
28103
|
+
const confidence = Math.max(0, Math.min(1, entity.confidence)) * 25;
|
|
28104
|
+
const decay = Math.max(0, Math.min(1, lifecycle2.decay.score)) * 20;
|
|
28105
|
+
if (lifecycle2.decay.score < 0.2)
|
|
28106
|
+
reasons.push(`decay score ${lifecycle2.decay.score.toFixed(2)}`);
|
|
28107
|
+
const hasEmbedding = entity.embedding != null;
|
|
28108
|
+
const hasTags = (entity.tags?.length || 0) >= 1;
|
|
28109
|
+
const hasRelations = relationCount > 0;
|
|
28110
|
+
let structural = 0;
|
|
28111
|
+
if (hasEmbedding)
|
|
28112
|
+
structural += 6;
|
|
28113
|
+
if (hasTags)
|
|
28114
|
+
structural += 4;
|
|
28115
|
+
if (hasRelations)
|
|
28116
|
+
structural += 5;
|
|
28117
|
+
if (!hasEmbedding)
|
|
28118
|
+
reasons.push("no embedding");
|
|
28119
|
+
if (!hasTags)
|
|
28120
|
+
reasons.push("no tags");
|
|
28121
|
+
if (!hasRelations)
|
|
28122
|
+
reasons.push("no relations");
|
|
28123
|
+
let content = 0;
|
|
28124
|
+
const contentLen = entity.content?.length || 0;
|
|
28125
|
+
if (contentLen >= 80)
|
|
28126
|
+
content += 8;
|
|
28127
|
+
const titleOk = entity.title.trim().length >= 4 && !/^(untitled|draft|note)\b/i.test(entity.title.trim());
|
|
28128
|
+
if (titleOk)
|
|
28129
|
+
content += 4;
|
|
28130
|
+
if (!isBoilerplate(entity.title, entity.content))
|
|
28131
|
+
content += 3;
|
|
28132
|
+
if (contentLen < 80)
|
|
28133
|
+
reasons.push(`thin content (${contentLen} chars)`);
|
|
28134
|
+
if (isBoilerplate(entity.title, entity.content))
|
|
28135
|
+
reasons.push("boilerplate title/content");
|
|
28136
|
+
let tierAgeFit = 15;
|
|
28137
|
+
if (entity.memory_tier === "draft" && ageDays > 60 && !entity.promoted_from_id) {
|
|
28138
|
+
tierAgeFit = 0;
|
|
28139
|
+
reasons.push("stuck draft >60d never promoted");
|
|
28140
|
+
}
|
|
28141
|
+
if (entity.promoted_from_id) {
|
|
28142
|
+
tierAgeFit = Math.min(15, tierAgeFit + 5);
|
|
28143
|
+
}
|
|
28144
|
+
const access = Math.min(10, Math.log10((entity.access_count || 0) + 1) * 5);
|
|
28145
|
+
if (entity.access_count === 0 && ageDays > 14)
|
|
28146
|
+
reasons.push("never accessed");
|
|
28147
|
+
const raw = confidence + decay + structural + content + tierAgeFit + access;
|
|
28148
|
+
const score = Math.round(Math.max(0, Math.min(100, raw)));
|
|
28149
|
+
let legacy = false;
|
|
28150
|
+
if (entity.confidence === 1 && entity.access_count === 0 && ageDays > 30) {
|
|
28151
|
+
legacy = true;
|
|
28152
|
+
legacyReasons.push("default confidence never validated");
|
|
28153
|
+
}
|
|
28154
|
+
if (!hasEmbedding && Date.parse(entity.created_at) < EMBEDDINGS_MIGRATION_AT) {
|
|
28155
|
+
legacy = true;
|
|
28156
|
+
legacyReasons.push("pre-embeddings migration");
|
|
28157
|
+
}
|
|
28158
|
+
if (entity.memory_tier === "draft" && ageDays > 60 && !entity.promoted_from_id) {
|
|
28159
|
+
legacy = true;
|
|
28160
|
+
legacyReasons.push("stuck draft");
|
|
28161
|
+
}
|
|
28162
|
+
if (!hasTags && !hasRelations) {
|
|
28163
|
+
legacy = true;
|
|
28164
|
+
legacyReasons.push("no graph presence");
|
|
28165
|
+
}
|
|
28166
|
+
let bucket;
|
|
28167
|
+
if (score < deleteBelow)
|
|
28168
|
+
bucket = "delete";
|
|
28169
|
+
else if (score < archiveBelow)
|
|
28170
|
+
bucket = "archive";
|
|
28171
|
+
else if (score < 70)
|
|
28172
|
+
bucket = "review";
|
|
28173
|
+
else
|
|
28174
|
+
bucket = "keep";
|
|
28175
|
+
return {
|
|
28176
|
+
id: entity.id,
|
|
28177
|
+
title: entity.title,
|
|
28178
|
+
type: entity.type,
|
|
28179
|
+
tier: entity.memory_tier,
|
|
28180
|
+
ageDays: Math.round(ageDays),
|
|
28181
|
+
score,
|
|
28182
|
+
bucket,
|
|
28183
|
+
reasons,
|
|
28184
|
+
legacy,
|
|
28185
|
+
legacyReasons,
|
|
28186
|
+
subScores: {
|
|
28187
|
+
confidence: Math.round(confidence),
|
|
28188
|
+
decay: Math.round(decay),
|
|
28189
|
+
structural,
|
|
28190
|
+
content,
|
|
28191
|
+
tierAgeFit,
|
|
28192
|
+
access: Math.round(access)
|
|
28193
|
+
}
|
|
28194
|
+
};
|
|
28195
|
+
}
|
|
28196
|
+
async function runMemoryAudit(client3, workspaceId, projectId, options) {
|
|
28197
|
+
const dryRun = options?.dryRun !== false;
|
|
28198
|
+
const archiveBelow = options?.archiveBelow ?? 40;
|
|
28199
|
+
const deleteBelow = options?.deleteBelow ?? 20;
|
|
28200
|
+
const limit = options?.limit ?? 500;
|
|
28201
|
+
const report = {
|
|
28202
|
+
success: true,
|
|
28203
|
+
dryRun,
|
|
28204
|
+
timestamp: new Date().toISOString(),
|
|
28205
|
+
workspace: { id: workspaceId, projectId },
|
|
28206
|
+
summary: {
|
|
28207
|
+
totalEntities: 0,
|
|
28208
|
+
scanned: 0,
|
|
28209
|
+
keep: 0,
|
|
28210
|
+
review: 0,
|
|
28211
|
+
archive: 0,
|
|
28212
|
+
delete: 0,
|
|
28213
|
+
legacyCount: 0
|
|
28214
|
+
},
|
|
28215
|
+
actionsTaken: { flaggedReview: 0, archived: 0, deleted: 0 },
|
|
28216
|
+
distribution: { "0-20": 0, "20-40": 0, "40-70": 0, "70-100": 0 },
|
|
28217
|
+
legacyBreakdown: {
|
|
28218
|
+
defaultConfidence: 0,
|
|
28219
|
+
missingEmbedding: 0,
|
|
28220
|
+
stuckDraft: 0,
|
|
28221
|
+
noGraphPresence: 0
|
|
28222
|
+
},
|
|
28223
|
+
lowest: [],
|
|
28224
|
+
errors: [],
|
|
28225
|
+
healthReport: ""
|
|
28226
|
+
};
|
|
28227
|
+
const entities = [];
|
|
28228
|
+
let offset = 0;
|
|
28229
|
+
try {
|
|
28230
|
+
while (entities.length < limit) {
|
|
28231
|
+
const pageSize = Math.min(BATCH_SIZE, limit - entities.length);
|
|
28232
|
+
const result = await client3.listMemoryEntities({
|
|
28233
|
+
workspace_id: workspaceId,
|
|
28234
|
+
project_id: projectId,
|
|
28235
|
+
limit: pageSize,
|
|
28236
|
+
offset
|
|
28237
|
+
});
|
|
28238
|
+
const page = result.entities || [];
|
|
28239
|
+
if (page.length === 0)
|
|
28240
|
+
break;
|
|
28241
|
+
entities.push(...page);
|
|
28242
|
+
if (page.length < pageSize)
|
|
28243
|
+
break;
|
|
28244
|
+
offset += pageSize;
|
|
28245
|
+
}
|
|
28246
|
+
} catch (err) {
|
|
28247
|
+
report.errors.push({
|
|
28248
|
+
step: "fetch",
|
|
28249
|
+
message: `Failed to fetch entities: ${err.message}`
|
|
28250
|
+
});
|
|
28251
|
+
report.success = false;
|
|
28252
|
+
report.healthReport = renderReport(report);
|
|
28253
|
+
return report;
|
|
28254
|
+
}
|
|
28255
|
+
report.summary.totalEntities = entities.length;
|
|
28256
|
+
const relationCounts = new Map;
|
|
28257
|
+
for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT) {
|
|
28258
|
+
const batch = entities.slice(i, i + CONCURRENCY_LIMIT);
|
|
28259
|
+
const results = await Promise.allSettled(batch.map(async (e) => {
|
|
28260
|
+
const related = await client3.getRelatedEntities(e.id);
|
|
28261
|
+
const count = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
|
|
28262
|
+
return { id: e.id, count };
|
|
28263
|
+
}));
|
|
28264
|
+
for (const r of results) {
|
|
28265
|
+
if (r.status === "fulfilled") {
|
|
28266
|
+
relationCounts.set(r.value.id, r.value.count);
|
|
28267
|
+
}
|
|
28268
|
+
}
|
|
28269
|
+
}
|
|
28270
|
+
const audits = [];
|
|
28271
|
+
for (const entity of entities) {
|
|
28272
|
+
const relCount = relationCounts.get(entity.id) ?? 0;
|
|
28273
|
+
const audit = scoreEntity(entity, relCount, archiveBelow, deleteBelow);
|
|
28274
|
+
audits.push(audit);
|
|
28275
|
+
report.summary.scanned++;
|
|
28276
|
+
report.summary[audit.bucket]++;
|
|
28277
|
+
if (audit.legacy)
|
|
28278
|
+
report.summary.legacyCount++;
|
|
28279
|
+
if (audit.score < 20)
|
|
28280
|
+
report.distribution["0-20"]++;
|
|
28281
|
+
else if (audit.score < 40)
|
|
28282
|
+
report.distribution["20-40"]++;
|
|
28283
|
+
else if (audit.score < 70)
|
|
28284
|
+
report.distribution["40-70"]++;
|
|
28285
|
+
else
|
|
28286
|
+
report.distribution["70-100"]++;
|
|
28287
|
+
for (const reason of audit.legacyReasons) {
|
|
28288
|
+
if (reason.startsWith("default confidence"))
|
|
28289
|
+
report.legacyBreakdown.defaultConfidence++;
|
|
28290
|
+
else if (reason.startsWith("pre-embeddings"))
|
|
28291
|
+
report.legacyBreakdown.missingEmbedding++;
|
|
28292
|
+
else if (reason.startsWith("stuck draft"))
|
|
28293
|
+
report.legacyBreakdown.stuckDraft++;
|
|
28294
|
+
else if (reason.startsWith("no graph"))
|
|
28295
|
+
report.legacyBreakdown.noGraphPresence++;
|
|
28296
|
+
}
|
|
28297
|
+
}
|
|
28298
|
+
report.lowest = [...audits].sort((a, b) => a.score - b.score).slice(0, 10);
|
|
28299
|
+
if (!dryRun) {
|
|
28300
|
+
for (const audit of audits) {
|
|
28301
|
+
try {
|
|
28302
|
+
if (audit.bucket === "delete") {
|
|
28303
|
+
await client3.deleteMemoryEntity(audit.id);
|
|
28304
|
+
report.actionsTaken.deleted++;
|
|
28305
|
+
} else if (audit.bucket === "archive") {
|
|
28306
|
+
await client3.updateMemoryEntity(audit.id, {
|
|
28307
|
+
confidence: 0.25,
|
|
28308
|
+
metadata: {
|
|
28309
|
+
audit_archived_at: new Date().toISOString(),
|
|
28310
|
+
audit_score: audit.score,
|
|
28311
|
+
audit_reasons: audit.reasons
|
|
28312
|
+
}
|
|
28313
|
+
});
|
|
28314
|
+
report.actionsTaken.archived++;
|
|
28315
|
+
} else if (audit.bucket === "review") {
|
|
28316
|
+
await client3.updateMemoryEntity(audit.id, {
|
|
28317
|
+
metadata: {
|
|
28318
|
+
needs_review: true,
|
|
28319
|
+
audit_score: audit.score,
|
|
28320
|
+
audit_reasons: audit.reasons,
|
|
28321
|
+
audit_at: new Date().toISOString()
|
|
28322
|
+
}
|
|
28323
|
+
});
|
|
28324
|
+
report.actionsTaken.flaggedReview++;
|
|
28325
|
+
}
|
|
28326
|
+
} catch (err) {
|
|
28327
|
+
report.errors.push({
|
|
28328
|
+
entityId: audit.id,
|
|
28329
|
+
step: audit.bucket,
|
|
28330
|
+
message: err.message
|
|
28331
|
+
});
|
|
28332
|
+
}
|
|
28333
|
+
}
|
|
28334
|
+
}
|
|
28335
|
+
report.healthReport = renderReport(report);
|
|
28336
|
+
return report;
|
|
28337
|
+
}
|
|
28338
|
+
function renderReport(report) {
|
|
28339
|
+
const mode = report.dryRun ? "Dry Run (preview)" : "Executed";
|
|
28340
|
+
const s = report.summary;
|
|
28341
|
+
const lines = [
|
|
28342
|
+
`# Memory Quality Audit
|
|
28343
|
+
`,
|
|
28344
|
+
`**Mode:** ${mode} | **Scanned:** ${s.scanned}/${s.totalEntities} | **Legacy:** ${s.legacyCount}`,
|
|
28345
|
+
"",
|
|
28346
|
+
"## Distribution",
|
|
28347
|
+
`- 70-100 (keep): ${report.distribution["70-100"]}`,
|
|
28348
|
+
`- 40-69 (review): ${report.distribution["40-70"]}`,
|
|
28349
|
+
`- 20-39 (archive): ${report.distribution["20-40"]}`,
|
|
28350
|
+
`- 0-19 (delete): ${report.distribution["0-20"]}`,
|
|
28351
|
+
"",
|
|
28352
|
+
"## Buckets",
|
|
28353
|
+
`- **Keep:** ${s.keep}`,
|
|
28354
|
+
`- **Review:** ${s.review}${!report.dryRun ? ` (flagged ${report.actionsTaken.flaggedReview})` : ""}`,
|
|
28355
|
+
`- **Archive:** ${s.archive}${!report.dryRun ? ` (archived ${report.actionsTaken.archived})` : ""}`,
|
|
28356
|
+
`- **Delete:** ${s.delete}${!report.dryRun ? ` (deleted ${report.actionsTaken.deleted})` : ""}`,
|
|
28357
|
+
""
|
|
28358
|
+
];
|
|
28359
|
+
const l = report.legacyBreakdown;
|
|
28360
|
+
if (s.legacyCount > 0) {
|
|
28361
|
+
lines.push("## Legacy Breakdown");
|
|
28362
|
+
lines.push(`- Default confidence, never validated: ${l.defaultConfidence}`);
|
|
28363
|
+
lines.push(`- Pre-embeddings migration: ${l.missingEmbedding}`);
|
|
28364
|
+
lines.push(`- Stuck drafts (>60d, no promotion): ${l.stuckDraft}`);
|
|
28365
|
+
lines.push(`- No tags + no relations: ${l.noGraphPresence}`);
|
|
28366
|
+
lines.push("");
|
|
28367
|
+
}
|
|
28368
|
+
if (report.lowest.length > 0) {
|
|
28369
|
+
lines.push("## Lowest-Scoring (top 10)");
|
|
28370
|
+
lines.push("| Score | Bucket | Tier | Age | Title | Reasons |");
|
|
28371
|
+
lines.push("|-------|--------|------|-----|-------|---------|");
|
|
28372
|
+
for (const a of report.lowest) {
|
|
28373
|
+
const reasonStr = a.reasons.slice(0, 3).join(", ") || "—";
|
|
28374
|
+
const titleTrunc = a.title.length > 40 ? `${a.title.slice(0, 37)}...` : a.title;
|
|
28375
|
+
lines.push(`| ${a.score} | ${a.bucket} | ${a.tier} | ${a.ageDays}d | ${titleTrunc} | ${reasonStr} |`);
|
|
28376
|
+
}
|
|
28377
|
+
lines.push("");
|
|
28378
|
+
}
|
|
28379
|
+
if (report.errors.length > 0) {
|
|
28380
|
+
lines.push("## Errors");
|
|
28381
|
+
for (const e of report.errors.slice(0, 10)) {
|
|
28382
|
+
lines.push(`- **${e.step}${e.entityId ? ` ${e.entityId}` : ""}:** ${e.message}`);
|
|
28383
|
+
}
|
|
28384
|
+
lines.push("");
|
|
28385
|
+
}
|
|
28386
|
+
if (report.dryRun) {
|
|
28387
|
+
lines.push("---");
|
|
28388
|
+
lines.push("*Run with `dryRun: false` to flag review entries, archive low-quality memories, and delete worst offenders.*");
|
|
28389
|
+
}
|
|
28390
|
+
return lines.join(`
|
|
28391
|
+
`);
|
|
28392
|
+
}
|
|
28393
|
+
|
|
28066
28394
|
// src/memory-cleanup.ts
|
|
28067
28395
|
init_dist();
|
|
28068
28396
|
var ALL_STEPS = [
|
|
@@ -28070,12 +28398,13 @@ var ALL_STEPS = [
|
|
|
28070
28398
|
"consolidate",
|
|
28071
28399
|
"orphans",
|
|
28072
28400
|
"duplicates",
|
|
28073
|
-
"backfill"
|
|
28401
|
+
"backfill",
|
|
28402
|
+
"audit"
|
|
28074
28403
|
];
|
|
28075
|
-
var
|
|
28404
|
+
var MS_PER_DAY2 = 1000 * 60 * 60 * 24;
|
|
28076
28405
|
var MAX_ENTITIES_FETCH = 200;
|
|
28077
28406
|
var DUPLICATE_SIMILARITY_THRESHOLD = 0.85;
|
|
28078
|
-
var
|
|
28407
|
+
var CONCURRENCY_LIMIT2 = 5;
|
|
28079
28408
|
async function runMemoryCleanup(client3, workspaceId, projectId, options) {
|
|
28080
28409
|
const dryRun = options?.dryRun !== false;
|
|
28081
28410
|
const steps = options?.steps ?? ALL_STEPS;
|
|
@@ -28231,6 +28560,44 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
|
|
|
28231
28560
|
});
|
|
28232
28561
|
}
|
|
28233
28562
|
}
|
|
28563
|
+
if (steps.includes("audit")) {
|
|
28564
|
+
try {
|
|
28565
|
+
const auditReport = await runMemoryAudit(client3, workspaceId, projectId, {
|
|
28566
|
+
dryRun,
|
|
28567
|
+
archiveBelow: options?.auditArchiveBelow,
|
|
28568
|
+
deleteBelow: options?.auditDeleteBelow
|
|
28569
|
+
});
|
|
28570
|
+
const low = auditReport.lowest.length > 0 ? auditReport.lowest[0].score : null;
|
|
28571
|
+
report.steps.audit = {
|
|
28572
|
+
scanned: auditReport.summary.scanned,
|
|
28573
|
+
legacyCount: auditReport.summary.legacyCount,
|
|
28574
|
+
buckets: {
|
|
28575
|
+
keep: auditReport.summary.keep,
|
|
28576
|
+
review: auditReport.summary.review,
|
|
28577
|
+
archive: auditReport.summary.archive,
|
|
28578
|
+
delete: auditReport.summary.delete
|
|
28579
|
+
},
|
|
28580
|
+
actions: auditReport.actionsTaken,
|
|
28581
|
+
lowestScore: low,
|
|
28582
|
+
report: auditReport
|
|
28583
|
+
};
|
|
28584
|
+
report.summary.issuesFound += auditReport.summary.review + auditReport.summary.archive + auditReport.summary.delete;
|
|
28585
|
+
if (!dryRun) {
|
|
28586
|
+
report.summary.actionsTaken += auditReport.actionsTaken.flaggedReview + auditReport.actionsTaken.archived + auditReport.actionsTaken.deleted;
|
|
28587
|
+
}
|
|
28588
|
+
for (const err of auditReport.errors) {
|
|
28589
|
+
report.errors.push({
|
|
28590
|
+
step: `audit:${err.step}`,
|
|
28591
|
+
message: err.entityId ? `${err.entityId}: ${err.message}` : err.message
|
|
28592
|
+
});
|
|
28593
|
+
}
|
|
28594
|
+
} catch (err) {
|
|
28595
|
+
report.errors.push({
|
|
28596
|
+
step: "audit",
|
|
28597
|
+
message: err.message
|
|
28598
|
+
});
|
|
28599
|
+
}
|
|
28600
|
+
}
|
|
28234
28601
|
report.healthReport = generateHealthReport(report);
|
|
28235
28602
|
return report;
|
|
28236
28603
|
}
|
|
@@ -28239,7 +28606,7 @@ function runPruneStep(entities, maxAgeDays) {
|
|
|
28239
28606
|
const drafts = entities.filter((e) => e.memory_tier === "draft");
|
|
28240
28607
|
const stale = [];
|
|
28241
28608
|
for (const entity of drafts) {
|
|
28242
|
-
const ageDays = (now - new Date(entity.created_at).getTime()) /
|
|
28609
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY2;
|
|
28243
28610
|
if (ageDays < maxAgeDays)
|
|
28244
28611
|
continue;
|
|
28245
28612
|
const lifecycle2 = evaluateLifecycle(entity);
|
|
@@ -28260,17 +28627,17 @@ async function runOrphanStep(client3, entities, orphanAgeDays) {
|
|
|
28260
28627
|
return false;
|
|
28261
28628
|
if (e.access_count >= 2)
|
|
28262
28629
|
return false;
|
|
28263
|
-
const ageDays = (now - new Date(e.created_at).getTime()) /
|
|
28630
|
+
const ageDays = (now - new Date(e.created_at).getTime()) / MS_PER_DAY2;
|
|
28264
28631
|
return ageDays >= orphanAgeDays;
|
|
28265
28632
|
});
|
|
28266
|
-
for (let i = 0;i < candidates.length; i +=
|
|
28267
|
-
const batch = candidates.slice(i, i +
|
|
28633
|
+
for (let i = 0;i < candidates.length; i += CONCURRENCY_LIMIT2) {
|
|
28634
|
+
const batch = candidates.slice(i, i + CONCURRENCY_LIMIT2);
|
|
28268
28635
|
const results = await Promise.allSettled(batch.map(async (entity) => {
|
|
28269
28636
|
const related = await client3.getRelatedEntities(entity.id);
|
|
28270
28637
|
const totalRelations = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
|
|
28271
28638
|
if (totalRelations > 0)
|
|
28272
28639
|
return null;
|
|
28273
|
-
const ageDays = (now - new Date(entity.created_at).getTime()) /
|
|
28640
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY2;
|
|
28274
28641
|
return {
|
|
28275
28642
|
id: entity.id,
|
|
28276
28643
|
title: entity.title,
|
|
@@ -28299,8 +28666,8 @@ async function runDuplicateStep(client3, entities, workspaceId, projectId) {
|
|
|
28299
28666
|
const flaggedForRemoval = new Set;
|
|
28300
28667
|
const entityMap = new Map(entities.map((e) => [e.id, e]));
|
|
28301
28668
|
const similarityMap = new Map;
|
|
28302
|
-
for (let i = 0;i < entities.length; i +=
|
|
28303
|
-
const batch = entities.slice(i, i +
|
|
28669
|
+
for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT2) {
|
|
28670
|
+
const batch = entities.slice(i, i + CONCURRENCY_LIMIT2);
|
|
28304
28671
|
const results = await Promise.allSettled(batch.map(async (entity) => {
|
|
28305
28672
|
const similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
|
|
28306
28673
|
return { entityId: entity.id, similar };
|
|
@@ -28452,6 +28819,20 @@ function generateHealthReport(report) {
|
|
|
28452
28819
|
`);
|
|
28453
28820
|
}
|
|
28454
28821
|
}
|
|
28822
|
+
if (report.steps.audit) {
|
|
28823
|
+
const a = report.steps.audit;
|
|
28824
|
+
lines.push("## Quality Audit");
|
|
28825
|
+
lines.push(`Scanned ${a.scanned} entities. Legacy signals on ${a.legacyCount}.`);
|
|
28826
|
+
lines.push(`Buckets — keep: ${a.buckets.keep}, review: ${a.buckets.review}, archive: ${a.buckets.archive}, delete: ${a.buckets.delete}.`);
|
|
28827
|
+
if (!report.dryRun) {
|
|
28828
|
+
lines.push(`Actions — flagged: ${a.actions.flaggedReview}, archived: ${a.actions.archived}, deleted: ${a.actions.deleted}.`);
|
|
28829
|
+
}
|
|
28830
|
+
if (a.report.lowest.length > 0) {
|
|
28831
|
+
const worst = a.report.lowest[0];
|
|
28832
|
+
lines.push(`Lowest score: **${worst.score}** — "${worst.title}" (${worst.reasons.slice(0, 2).join(", ") || "—"}).`);
|
|
28833
|
+
}
|
|
28834
|
+
lines.push("");
|
|
28835
|
+
}
|
|
28455
28836
|
if (report.errors.length > 0) {
|
|
28456
28837
|
lines.push("## Errors");
|
|
28457
28838
|
for (const e of report.errors) {
|
|
@@ -28465,6 +28846,85 @@ function generateHealthReport(report) {
|
|
|
28465
28846
|
return lines.join(`
|
|
28466
28847
|
`);
|
|
28467
28848
|
}
|
|
28849
|
+
async function purgeMemories(client3, workspaceId, projectId, options) {
|
|
28850
|
+
const dryRun = options.dryRun !== false;
|
|
28851
|
+
const { filters } = options;
|
|
28852
|
+
const hasFilter = filters.tier || filters.scope || filters.type || filters.olderThanDays !== undefined || filters.maxConfidence !== undefined || filters.tags && filters.tags.length > 0;
|
|
28853
|
+
if (!hasFilter) {
|
|
28854
|
+
throw new Error("At least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags) is required.");
|
|
28855
|
+
}
|
|
28856
|
+
const allMatches = [];
|
|
28857
|
+
let offset = 0;
|
|
28858
|
+
const pageSize = 100;
|
|
28859
|
+
const now = Date.now();
|
|
28860
|
+
while (true) {
|
|
28861
|
+
const result = await client3.listMemoryEntities({
|
|
28862
|
+
workspace_id: workspaceId,
|
|
28863
|
+
project_id: projectId,
|
|
28864
|
+
type: filters.type,
|
|
28865
|
+
scope: filters.scope,
|
|
28866
|
+
tags: filters.tags,
|
|
28867
|
+
limit: pageSize,
|
|
28868
|
+
offset
|
|
28869
|
+
});
|
|
28870
|
+
const entities = result.entities || [];
|
|
28871
|
+
if (entities.length === 0)
|
|
28872
|
+
break;
|
|
28873
|
+
for (const entity of entities) {
|
|
28874
|
+
if (filters.tier && entity.memory_tier !== filters.tier)
|
|
28875
|
+
continue;
|
|
28876
|
+
if (filters.maxConfidence !== undefined && entity.confidence > filters.maxConfidence)
|
|
28877
|
+
continue;
|
|
28878
|
+
if (filters.olderThanDays !== undefined) {
|
|
28879
|
+
const ref = entity.last_accessed_at || entity.created_at;
|
|
28880
|
+
const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY2;
|
|
28881
|
+
if (ageDays < filters.olderThanDays)
|
|
28882
|
+
continue;
|
|
28883
|
+
}
|
|
28884
|
+
allMatches.push(entity);
|
|
28885
|
+
}
|
|
28886
|
+
if (entities.length < pageSize)
|
|
28887
|
+
break;
|
|
28888
|
+
offset += pageSize;
|
|
28889
|
+
}
|
|
28890
|
+
const items = allMatches.map((e) => ({
|
|
28891
|
+
id: e.id,
|
|
28892
|
+
title: e.title,
|
|
28893
|
+
type: e.type,
|
|
28894
|
+
tier: e.memory_tier,
|
|
28895
|
+
confidence: e.confidence,
|
|
28896
|
+
ageDays: Math.round((now - new Date(e.last_accessed_at || e.created_at).getTime()) / MS_PER_DAY2)
|
|
28897
|
+
}));
|
|
28898
|
+
const errors3 = [];
|
|
28899
|
+
let purged = 0;
|
|
28900
|
+
if (!dryRun) {
|
|
28901
|
+
for (let i = 0;i < allMatches.length; i += CONCURRENCY_LIMIT2) {
|
|
28902
|
+
const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT2);
|
|
28903
|
+
const results = await Promise.allSettled(batch.map((e) => client3.deleteMemoryEntity(e.id)));
|
|
28904
|
+
for (let j = 0;j < results.length; j++) {
|
|
28905
|
+
if (results[j].status === "fulfilled") {
|
|
28906
|
+
purged++;
|
|
28907
|
+
} else {
|
|
28908
|
+
errors3.push({
|
|
28909
|
+
entityId: batch[j].id,
|
|
28910
|
+
message: results[j].status === "rejected" ? String(results[j].reason) : "Unknown error"
|
|
28911
|
+
});
|
|
28912
|
+
}
|
|
28913
|
+
}
|
|
28914
|
+
}
|
|
28915
|
+
}
|
|
28916
|
+
return {
|
|
28917
|
+
success: errors3.length === 0,
|
|
28918
|
+
dryRun,
|
|
28919
|
+
timestamp: new Date().toISOString(),
|
|
28920
|
+
workspace: { id: workspaceId, projectId },
|
|
28921
|
+
filters,
|
|
28922
|
+
matched: allMatches.length,
|
|
28923
|
+
purged: dryRun ? 0 : purged,
|
|
28924
|
+
items,
|
|
28925
|
+
errors: errors3
|
|
28926
|
+
};
|
|
28927
|
+
}
|
|
28468
28928
|
|
|
28469
28929
|
// src/onboard.ts
|
|
28470
28930
|
async function onboardNewUser(params) {
|
|
@@ -29060,6 +29520,20 @@ var TOOLS = {
|
|
|
29060
29520
|
estimatedMinutesRemaining: {
|
|
29061
29521
|
type: "number",
|
|
29062
29522
|
description: "Updated time estimate"
|
|
29523
|
+
},
|
|
29524
|
+
actions: {
|
|
29525
|
+
type: "array",
|
|
29526
|
+
items: {
|
|
29527
|
+
type: "object",
|
|
29528
|
+
properties: {
|
|
29529
|
+
description: {
|
|
29530
|
+
type: "string",
|
|
29531
|
+
description: "What was done, e.g. 'Edited CardDetailSheet.tsx — added done toggle'"
|
|
29532
|
+
}
|
|
29533
|
+
},
|
|
29534
|
+
required: ["description"]
|
|
29535
|
+
},
|
|
29536
|
+
description: "Actions performed since last update. Each becomes a visible activity log entry."
|
|
29063
29537
|
}
|
|
29064
29538
|
},
|
|
29065
29539
|
required: ["cardId", "agentIdentifier", "agentName"]
|
|
@@ -29902,7 +30376,7 @@ var TOOLS = {
|
|
|
29902
30376
|
}
|
|
29903
30377
|
},
|
|
29904
30378
|
harmony_cleanup_memories: {
|
|
29905
|
-
description: "Run a unified memory cleanup: prune stale drafts, consolidate similar memories, detect orphans and duplicates, and
|
|
30379
|
+
description: "Run a unified memory cleanup: prune stale drafts, consolidate similar memories, detect orphans and duplicates, backfill embeddings, and optionally run a quality audit. Returns a health report. Dry-run by default — run with dryRun=false to execute.",
|
|
29906
30380
|
inputSchema: {
|
|
29907
30381
|
type: "object",
|
|
29908
30382
|
properties: {
|
|
@@ -29922,9 +30396,16 @@ var TOOLS = {
|
|
|
29922
30396
|
type: "array",
|
|
29923
30397
|
items: {
|
|
29924
30398
|
type: "string",
|
|
29925
|
-
enum: [
|
|
30399
|
+
enum: [
|
|
30400
|
+
"prune",
|
|
30401
|
+
"consolidate",
|
|
30402
|
+
"orphans",
|
|
30403
|
+
"duplicates",
|
|
30404
|
+
"backfill",
|
|
30405
|
+
"audit"
|
|
30406
|
+
]
|
|
29926
30407
|
},
|
|
29927
|
-
description: "Which cleanup steps to run (default: all). Options: prune, consolidate, orphans, duplicates, backfill."
|
|
30408
|
+
description: "Which cleanup steps to run (default: all). Options: prune, consolidate, orphans, duplicates, backfill, audit."
|
|
29928
30409
|
},
|
|
29929
30410
|
maxAgeDays: {
|
|
29930
30411
|
type: "number",
|
|
@@ -29937,6 +30418,94 @@ var TOOLS = {
|
|
|
29937
30418
|
orphanAgeDays: {
|
|
29938
30419
|
type: "number",
|
|
29939
30420
|
description: "Min age in days for orphan detection (default: 14)"
|
|
30421
|
+
},
|
|
30422
|
+
auditArchiveBelow: {
|
|
30423
|
+
type: "number",
|
|
30424
|
+
description: "Audit: archive entities scoring below this (default: 40)"
|
|
30425
|
+
},
|
|
30426
|
+
auditDeleteBelow: {
|
|
30427
|
+
type: "number",
|
|
30428
|
+
description: "Audit: delete entities scoring below this (default: 20)"
|
|
30429
|
+
}
|
|
30430
|
+
},
|
|
30431
|
+
required: []
|
|
30432
|
+
}
|
|
30433
|
+
},
|
|
30434
|
+
harmony_audit_memories: {
|
|
30435
|
+
description: "Rate every memory against state-of-the-art quality standards (confidence, decay, structural completeness, content, tier-age fit, access). Flags legacy entities from before recent optimizations (default confidence, missing embeddings, stuck drafts). Buckets: keep (≥70), review (40-69), archive (20-39), delete (<20). Dry-run by default.",
|
|
30436
|
+
inputSchema: {
|
|
30437
|
+
type: "object",
|
|
30438
|
+
properties: {
|
|
30439
|
+
workspaceId: {
|
|
30440
|
+
type: "string",
|
|
30441
|
+
description: "Workspace ID (optional if context set)"
|
|
30442
|
+
},
|
|
30443
|
+
projectId: {
|
|
30444
|
+
type: "string",
|
|
30445
|
+
description: "Project ID (optional)"
|
|
30446
|
+
},
|
|
30447
|
+
dryRun: {
|
|
30448
|
+
type: "boolean",
|
|
30449
|
+
description: "Preview audit without flagging/archiving/deleting (default: true)"
|
|
30450
|
+
},
|
|
30451
|
+
archiveBelow: {
|
|
30452
|
+
type: "number",
|
|
30453
|
+
description: "Score threshold below which entities are archived (confidence set to 0.25). Default: 40"
|
|
30454
|
+
},
|
|
30455
|
+
deleteBelow: {
|
|
30456
|
+
type: "number",
|
|
30457
|
+
description: "Score threshold below which entities are hard-deleted. Default: 20. Set to 0 to never delete."
|
|
30458
|
+
},
|
|
30459
|
+
limit: {
|
|
30460
|
+
type: "number",
|
|
30461
|
+
description: "Max number of entities to audit (default: 500). Paginated fetch."
|
|
30462
|
+
}
|
|
30463
|
+
},
|
|
30464
|
+
required: []
|
|
30465
|
+
}
|
|
30466
|
+
},
|
|
30467
|
+
harmony_purge_memories: {
|
|
30468
|
+
description: "Bulk-delete memory entities matching filters within a project. Requires at least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags). Dry-run by default — preview what would be deleted before executing.",
|
|
30469
|
+
inputSchema: {
|
|
30470
|
+
type: "object",
|
|
30471
|
+
properties: {
|
|
30472
|
+
workspaceId: {
|
|
30473
|
+
type: "string",
|
|
30474
|
+
description: "Workspace ID (optional if context set)"
|
|
30475
|
+
},
|
|
30476
|
+
projectId: {
|
|
30477
|
+
type: "string",
|
|
30478
|
+
description: "Project ID (required — purge is project-scoped). Falls back to active project context."
|
|
30479
|
+
},
|
|
30480
|
+
dryRun: {
|
|
30481
|
+
type: "boolean",
|
|
30482
|
+
description: "Preview what would be deleted without executing (default: true)"
|
|
30483
|
+
},
|
|
30484
|
+
tier: {
|
|
30485
|
+
type: "string",
|
|
30486
|
+
enum: ["draft", "episode", "reference"],
|
|
30487
|
+
description: 'Filter by memory tier (e.g. "draft")'
|
|
30488
|
+
},
|
|
30489
|
+
scope: {
|
|
30490
|
+
type: "string",
|
|
30491
|
+
description: 'Filter by scope (e.g. "private", "project", "workspace")'
|
|
30492
|
+
},
|
|
30493
|
+
type: {
|
|
30494
|
+
type: "string",
|
|
30495
|
+
description: 'Filter by entity type (e.g. "error", "pattern", "lesson", "decision")'
|
|
30496
|
+
},
|
|
30497
|
+
olderThanDays: {
|
|
30498
|
+
type: "number",
|
|
30499
|
+
description: "Only include entities not accessed in at least this many days"
|
|
30500
|
+
},
|
|
30501
|
+
maxConfidence: {
|
|
30502
|
+
type: "number",
|
|
30503
|
+
description: "Only include entities with confidence at or below this value (e.g. 0.3 for low-confidence junk)"
|
|
30504
|
+
},
|
|
30505
|
+
tags: {
|
|
30506
|
+
type: "array",
|
|
30507
|
+
items: { type: "string" },
|
|
30508
|
+
description: "Only include entities matching these tags"
|
|
29940
30509
|
}
|
|
29941
30510
|
},
|
|
29942
30511
|
required: []
|
|
@@ -30570,12 +31139,20 @@ async function handleToolCall(name, args, deps) {
|
|
|
30570
31139
|
const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
|
|
30571
31140
|
const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
|
|
30572
31141
|
const progressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
|
|
30573
|
-
const
|
|
31142
|
+
const callerActions = args.actions;
|
|
31143
|
+
const now = new Date().toISOString();
|
|
31144
|
+
const callerRecentActions = [
|
|
31145
|
+
...args.recentActions || [],
|
|
31146
|
+
...(callerActions || []).map((a) => ({
|
|
31147
|
+
action: a.description,
|
|
31148
|
+
ts: now
|
|
31149
|
+
}))
|
|
31150
|
+
];
|
|
30574
31151
|
const memSession = getMemorySession(cardId);
|
|
30575
31152
|
let mergedRecentActions;
|
|
30576
31153
|
if (memSession?.dirty) {
|
|
30577
|
-
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions
|
|
30578
|
-
} else if (callerRecentActions) {
|
|
31154
|
+
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions);
|
|
31155
|
+
} else if (callerRecentActions.length > 0) {
|
|
30579
31156
|
mergedRecentActions = callerRecentActions;
|
|
30580
31157
|
}
|
|
30581
31158
|
const result = await client3.updateAgentProgress(cardId, {
|
|
@@ -31451,6 +32028,30 @@ async function handleToolCall(name, args, deps) {
|
|
|
31451
32028
|
message: `Processed ${entitiesProcessed} entities, created ${relationsCreated} new relations.`
|
|
31452
32029
|
};
|
|
31453
32030
|
}
|
|
32031
|
+
case "harmony_audit_memories": {
|
|
32032
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
32033
|
+
if (!workspaceId) {
|
|
32034
|
+
throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
|
|
32035
|
+
}
|
|
32036
|
+
const projectId = args.projectId || deps.getActiveProjectId() || undefined;
|
|
32037
|
+
const report = await runMemoryAudit(client3, workspaceId, projectId, {
|
|
32038
|
+
dryRun: args.dryRun,
|
|
32039
|
+
archiveBelow: args.archiveBelow,
|
|
32040
|
+
deleteBelow: args.deleteBelow,
|
|
32041
|
+
limit: args.limit
|
|
32042
|
+
});
|
|
32043
|
+
return {
|
|
32044
|
+
success: report.success,
|
|
32045
|
+
dryRun: report.dryRun,
|
|
32046
|
+
summary: report.summary,
|
|
32047
|
+
distribution: report.distribution,
|
|
32048
|
+
legacyBreakdown: report.legacyBreakdown,
|
|
32049
|
+
actionsTaken: report.actionsTaken,
|
|
32050
|
+
lowest: report.lowest,
|
|
32051
|
+
errors: report.errors,
|
|
32052
|
+
healthReport: report.healthReport
|
|
32053
|
+
};
|
|
32054
|
+
}
|
|
31454
32055
|
case "harmony_cleanup_memories": {
|
|
31455
32056
|
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
31456
32057
|
if (!workspaceId) {
|
|
@@ -31462,7 +32063,8 @@ async function handleToolCall(name, args, deps) {
|
|
|
31462
32063
|
"consolidate",
|
|
31463
32064
|
"orphans",
|
|
31464
32065
|
"duplicates",
|
|
31465
|
-
"backfill"
|
|
32066
|
+
"backfill",
|
|
32067
|
+
"audit"
|
|
31466
32068
|
];
|
|
31467
32069
|
const rawSteps = args.steps;
|
|
31468
32070
|
const steps = rawSteps?.filter((s) => validSteps.includes(s));
|
|
@@ -31475,7 +32077,9 @@ async function handleToolCall(name, args, deps) {
|
|
|
31475
32077
|
steps,
|
|
31476
32078
|
maxAgeDays: args.maxAgeDays,
|
|
31477
32079
|
minClusterSize: args.minClusterSize,
|
|
31478
|
-
orphanAgeDays: args.orphanAgeDays
|
|
32080
|
+
orphanAgeDays: args.orphanAgeDays,
|
|
32081
|
+
auditArchiveBelow: args.auditArchiveBelow,
|
|
32082
|
+
auditDeleteBelow: args.auditDeleteBelow
|
|
31479
32083
|
});
|
|
31480
32084
|
return {
|
|
31481
32085
|
success: report.success,
|
|
@@ -31485,6 +32089,42 @@ async function handleToolCall(name, args, deps) {
|
|
|
31485
32089
|
healthReport: report.healthReport
|
|
31486
32090
|
};
|
|
31487
32091
|
}
|
|
32092
|
+
case "harmony_purge_memories": {
|
|
32093
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
32094
|
+
if (!workspaceId) {
|
|
32095
|
+
throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
|
|
32096
|
+
}
|
|
32097
|
+
const projectId = args.projectId || deps.getActiveProjectId();
|
|
32098
|
+
if (!projectId) {
|
|
32099
|
+
throw new Error("No project specified. Purge requires a project scope. Use harmony_set_project_context or provide projectId.");
|
|
32100
|
+
}
|
|
32101
|
+
const filters = {};
|
|
32102
|
+
if (args.tier)
|
|
32103
|
+
filters.tier = args.tier;
|
|
32104
|
+
if (args.scope)
|
|
32105
|
+
filters.scope = args.scope;
|
|
32106
|
+
if (args.type)
|
|
32107
|
+
filters.type = args.type;
|
|
32108
|
+
if (args.olderThanDays !== undefined)
|
|
32109
|
+
filters.olderThanDays = args.olderThanDays;
|
|
32110
|
+
if (args.maxConfidence !== undefined)
|
|
32111
|
+
filters.maxConfidence = args.maxConfidence;
|
|
32112
|
+
if (args.tags)
|
|
32113
|
+
filters.tags = args.tags;
|
|
32114
|
+
const report = await purgeMemories(client3, workspaceId, projectId, {
|
|
32115
|
+
dryRun: args.dryRun,
|
|
32116
|
+
filters
|
|
32117
|
+
});
|
|
32118
|
+
return {
|
|
32119
|
+
success: report.success,
|
|
32120
|
+
dryRun: report.dryRun,
|
|
32121
|
+
matched: report.matched,
|
|
32122
|
+
purged: report.purged,
|
|
32123
|
+
items: report.items,
|
|
32124
|
+
errors: report.errors,
|
|
32125
|
+
message: report.dryRun ? `Found ${report.matched} entities matching filters. Run with dryRun=false to delete.` : `Purged ${report.purged} of ${report.matched} matching entities.`
|
|
32126
|
+
};
|
|
32127
|
+
}
|
|
31488
32128
|
default:
|
|
31489
32129
|
throw new Error(`Unknown tool: ${name}`);
|
|
31490
32130
|
}
|
|
@@ -31522,15 +32162,26 @@ class HarmonyMCPServer {
|
|
|
31522
32162
|
const cv = this.server.getClientVersion();
|
|
31523
32163
|
return cv ? { name: cv.name, version: cv.version } : null;
|
|
31524
32164
|
});
|
|
32165
|
+
let exitCode = 0;
|
|
31525
32166
|
const handleShutdown = async () => {
|
|
31526
32167
|
try {
|
|
31527
32168
|
await shutdownAllSessions();
|
|
31528
32169
|
} catch {}
|
|
31529
32170
|
destroyAutoSession();
|
|
31530
|
-
process.exit(
|
|
32171
|
+
process.exit(exitCode);
|
|
31531
32172
|
};
|
|
31532
32173
|
process.on("SIGINT", handleShutdown);
|
|
31533
32174
|
process.on("SIGTERM", handleShutdown);
|
|
32175
|
+
process.on("uncaughtException", (err) => {
|
|
32176
|
+
console.error("MCP server uncaught exception:", err);
|
|
32177
|
+
exitCode = 1;
|
|
32178
|
+
handleShutdown();
|
|
32179
|
+
});
|
|
32180
|
+
process.on("unhandledRejection", (reason) => {
|
|
32181
|
+
console.error("MCP server unhandled rejection:", reason);
|
|
32182
|
+
exitCode = 1;
|
|
32183
|
+
handleShutdown();
|
|
32184
|
+
});
|
|
31534
32185
|
try {
|
|
31535
32186
|
if (isConfigured()) {
|
|
31536
32187
|
const workspaceId = getActiveWorkspaceId();
|