@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/index.js
CHANGED
|
@@ -25823,6 +25823,334 @@ async function runLifecycleMaintenance(client3, workspaceId, projectId) {
|
|
|
25823
25823
|
return result;
|
|
25824
25824
|
}
|
|
25825
25825
|
|
|
25826
|
+
// src/memory-audit.ts
|
|
25827
|
+
init_dist();
|
|
25828
|
+
var EMBEDDINGS_MIGRATION_AT = Date.parse("2026-02-18T00:00:00Z");
|
|
25829
|
+
var MS_PER_DAY = 1000 * 60 * 60 * 24;
|
|
25830
|
+
var BATCH_SIZE = 100;
|
|
25831
|
+
var CONCURRENCY_LIMIT = 5;
|
|
25832
|
+
var BOILERPLATE_PATTERNS = [
|
|
25833
|
+
/^todo:?$/i,
|
|
25834
|
+
/^placeholder/i,
|
|
25835
|
+
/^\.\.\.$/,
|
|
25836
|
+
/^untitled/i,
|
|
25837
|
+
/^(note|memo|draft)\s*\d*$/i
|
|
25838
|
+
];
|
|
25839
|
+
function isBoilerplate(title, content) {
|
|
25840
|
+
const t = title.trim();
|
|
25841
|
+
const c = content.trim();
|
|
25842
|
+
if (c.length === 0)
|
|
25843
|
+
return true;
|
|
25844
|
+
for (const pat of BOILERPLATE_PATTERNS) {
|
|
25845
|
+
if (pat.test(t))
|
|
25846
|
+
return true;
|
|
25847
|
+
}
|
|
25848
|
+
return false;
|
|
25849
|
+
}
|
|
25850
|
+
function scoreEntity(entity, relationCount, archiveBelow, deleteBelow) {
|
|
25851
|
+
const now = Date.now();
|
|
25852
|
+
const ageDays = (now - Date.parse(entity.created_at)) / MS_PER_DAY;
|
|
25853
|
+
const effectiveLastAccess = entity.last_accessed_at ?? entity.created_at;
|
|
25854
|
+
const lifecycle2 = evaluateLifecycle({
|
|
25855
|
+
memory_tier: entity.memory_tier,
|
|
25856
|
+
confidence: entity.confidence,
|
|
25857
|
+
access_count: entity.access_count,
|
|
25858
|
+
last_accessed_at: effectiveLastAccess,
|
|
25859
|
+
created_at: entity.created_at
|
|
25860
|
+
});
|
|
25861
|
+
const reasons = [];
|
|
25862
|
+
const legacyReasons = [];
|
|
25863
|
+
const confidence = Math.max(0, Math.min(1, entity.confidence)) * 25;
|
|
25864
|
+
const decay = Math.max(0, Math.min(1, lifecycle2.decay.score)) * 20;
|
|
25865
|
+
if (lifecycle2.decay.score < 0.2)
|
|
25866
|
+
reasons.push(`decay score ${lifecycle2.decay.score.toFixed(2)}`);
|
|
25867
|
+
const hasEmbedding = entity.embedding != null;
|
|
25868
|
+
const hasTags = (entity.tags?.length || 0) >= 1;
|
|
25869
|
+
const hasRelations = relationCount > 0;
|
|
25870
|
+
let structural = 0;
|
|
25871
|
+
if (hasEmbedding)
|
|
25872
|
+
structural += 6;
|
|
25873
|
+
if (hasTags)
|
|
25874
|
+
structural += 4;
|
|
25875
|
+
if (hasRelations)
|
|
25876
|
+
structural += 5;
|
|
25877
|
+
if (!hasEmbedding)
|
|
25878
|
+
reasons.push("no embedding");
|
|
25879
|
+
if (!hasTags)
|
|
25880
|
+
reasons.push("no tags");
|
|
25881
|
+
if (!hasRelations)
|
|
25882
|
+
reasons.push("no relations");
|
|
25883
|
+
let content = 0;
|
|
25884
|
+
const contentLen = entity.content?.length || 0;
|
|
25885
|
+
if (contentLen >= 80)
|
|
25886
|
+
content += 8;
|
|
25887
|
+
const titleOk = entity.title.trim().length >= 4 && !/^(untitled|draft|note)\b/i.test(entity.title.trim());
|
|
25888
|
+
if (titleOk)
|
|
25889
|
+
content += 4;
|
|
25890
|
+
if (!isBoilerplate(entity.title, entity.content))
|
|
25891
|
+
content += 3;
|
|
25892
|
+
if (contentLen < 80)
|
|
25893
|
+
reasons.push(`thin content (${contentLen} chars)`);
|
|
25894
|
+
if (isBoilerplate(entity.title, entity.content))
|
|
25895
|
+
reasons.push("boilerplate title/content");
|
|
25896
|
+
let tierAgeFit = 15;
|
|
25897
|
+
if (entity.memory_tier === "draft" && ageDays > 60 && !entity.promoted_from_id) {
|
|
25898
|
+
tierAgeFit = 0;
|
|
25899
|
+
reasons.push("stuck draft >60d never promoted");
|
|
25900
|
+
}
|
|
25901
|
+
if (entity.promoted_from_id) {
|
|
25902
|
+
tierAgeFit = Math.min(15, tierAgeFit + 5);
|
|
25903
|
+
}
|
|
25904
|
+
const access = Math.min(10, Math.log10((entity.access_count || 0) + 1) * 5);
|
|
25905
|
+
if (entity.access_count === 0 && ageDays > 14)
|
|
25906
|
+
reasons.push("never accessed");
|
|
25907
|
+
const raw = confidence + decay + structural + content + tierAgeFit + access;
|
|
25908
|
+
const score = Math.round(Math.max(0, Math.min(100, raw)));
|
|
25909
|
+
let legacy = false;
|
|
25910
|
+
if (entity.confidence === 1 && entity.access_count === 0 && ageDays > 30) {
|
|
25911
|
+
legacy = true;
|
|
25912
|
+
legacyReasons.push("default confidence never validated");
|
|
25913
|
+
}
|
|
25914
|
+
if (!hasEmbedding && Date.parse(entity.created_at) < EMBEDDINGS_MIGRATION_AT) {
|
|
25915
|
+
legacy = true;
|
|
25916
|
+
legacyReasons.push("pre-embeddings migration");
|
|
25917
|
+
}
|
|
25918
|
+
if (entity.memory_tier === "draft" && ageDays > 60 && !entity.promoted_from_id) {
|
|
25919
|
+
legacy = true;
|
|
25920
|
+
legacyReasons.push("stuck draft");
|
|
25921
|
+
}
|
|
25922
|
+
if (!hasTags && !hasRelations) {
|
|
25923
|
+
legacy = true;
|
|
25924
|
+
legacyReasons.push("no graph presence");
|
|
25925
|
+
}
|
|
25926
|
+
let bucket;
|
|
25927
|
+
if (score < deleteBelow)
|
|
25928
|
+
bucket = "delete";
|
|
25929
|
+
else if (score < archiveBelow)
|
|
25930
|
+
bucket = "archive";
|
|
25931
|
+
else if (score < 70)
|
|
25932
|
+
bucket = "review";
|
|
25933
|
+
else
|
|
25934
|
+
bucket = "keep";
|
|
25935
|
+
return {
|
|
25936
|
+
id: entity.id,
|
|
25937
|
+
title: entity.title,
|
|
25938
|
+
type: entity.type,
|
|
25939
|
+
tier: entity.memory_tier,
|
|
25940
|
+
ageDays: Math.round(ageDays),
|
|
25941
|
+
score,
|
|
25942
|
+
bucket,
|
|
25943
|
+
reasons,
|
|
25944
|
+
legacy,
|
|
25945
|
+
legacyReasons,
|
|
25946
|
+
subScores: {
|
|
25947
|
+
confidence: Math.round(confidence),
|
|
25948
|
+
decay: Math.round(decay),
|
|
25949
|
+
structural,
|
|
25950
|
+
content,
|
|
25951
|
+
tierAgeFit,
|
|
25952
|
+
access: Math.round(access)
|
|
25953
|
+
}
|
|
25954
|
+
};
|
|
25955
|
+
}
|
|
25956
|
+
async function runMemoryAudit(client3, workspaceId, projectId, options) {
|
|
25957
|
+
const dryRun = options?.dryRun !== false;
|
|
25958
|
+
const archiveBelow = options?.archiveBelow ?? 40;
|
|
25959
|
+
const deleteBelow = options?.deleteBelow ?? 20;
|
|
25960
|
+
const limit = options?.limit ?? 500;
|
|
25961
|
+
const report = {
|
|
25962
|
+
success: true,
|
|
25963
|
+
dryRun,
|
|
25964
|
+
timestamp: new Date().toISOString(),
|
|
25965
|
+
workspace: { id: workspaceId, projectId },
|
|
25966
|
+
summary: {
|
|
25967
|
+
totalEntities: 0,
|
|
25968
|
+
scanned: 0,
|
|
25969
|
+
keep: 0,
|
|
25970
|
+
review: 0,
|
|
25971
|
+
archive: 0,
|
|
25972
|
+
delete: 0,
|
|
25973
|
+
legacyCount: 0
|
|
25974
|
+
},
|
|
25975
|
+
actionsTaken: { flaggedReview: 0, archived: 0, deleted: 0 },
|
|
25976
|
+
distribution: { "0-20": 0, "20-40": 0, "40-70": 0, "70-100": 0 },
|
|
25977
|
+
legacyBreakdown: {
|
|
25978
|
+
defaultConfidence: 0,
|
|
25979
|
+
missingEmbedding: 0,
|
|
25980
|
+
stuckDraft: 0,
|
|
25981
|
+
noGraphPresence: 0
|
|
25982
|
+
},
|
|
25983
|
+
lowest: [],
|
|
25984
|
+
errors: [],
|
|
25985
|
+
healthReport: ""
|
|
25986
|
+
};
|
|
25987
|
+
const entities = [];
|
|
25988
|
+
let offset = 0;
|
|
25989
|
+
try {
|
|
25990
|
+
while (entities.length < limit) {
|
|
25991
|
+
const pageSize = Math.min(BATCH_SIZE, limit - entities.length);
|
|
25992
|
+
const result = await client3.listMemoryEntities({
|
|
25993
|
+
workspace_id: workspaceId,
|
|
25994
|
+
project_id: projectId,
|
|
25995
|
+
limit: pageSize,
|
|
25996
|
+
offset
|
|
25997
|
+
});
|
|
25998
|
+
const page = result.entities || [];
|
|
25999
|
+
if (page.length === 0)
|
|
26000
|
+
break;
|
|
26001
|
+
entities.push(...page);
|
|
26002
|
+
if (page.length < pageSize)
|
|
26003
|
+
break;
|
|
26004
|
+
offset += pageSize;
|
|
26005
|
+
}
|
|
26006
|
+
} catch (err) {
|
|
26007
|
+
report.errors.push({
|
|
26008
|
+
step: "fetch",
|
|
26009
|
+
message: `Failed to fetch entities: ${err.message}`
|
|
26010
|
+
});
|
|
26011
|
+
report.success = false;
|
|
26012
|
+
report.healthReport = renderReport(report);
|
|
26013
|
+
return report;
|
|
26014
|
+
}
|
|
26015
|
+
report.summary.totalEntities = entities.length;
|
|
26016
|
+
const relationCounts = new Map;
|
|
26017
|
+
for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT) {
|
|
26018
|
+
const batch = entities.slice(i, i + CONCURRENCY_LIMIT);
|
|
26019
|
+
const results = await Promise.allSettled(batch.map(async (e) => {
|
|
26020
|
+
const related = await client3.getRelatedEntities(e.id);
|
|
26021
|
+
const count = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
|
|
26022
|
+
return { id: e.id, count };
|
|
26023
|
+
}));
|
|
26024
|
+
for (const r of results) {
|
|
26025
|
+
if (r.status === "fulfilled") {
|
|
26026
|
+
relationCounts.set(r.value.id, r.value.count);
|
|
26027
|
+
}
|
|
26028
|
+
}
|
|
26029
|
+
}
|
|
26030
|
+
const audits = [];
|
|
26031
|
+
for (const entity of entities) {
|
|
26032
|
+
const relCount = relationCounts.get(entity.id) ?? 0;
|
|
26033
|
+
const audit = scoreEntity(entity, relCount, archiveBelow, deleteBelow);
|
|
26034
|
+
audits.push(audit);
|
|
26035
|
+
report.summary.scanned++;
|
|
26036
|
+
report.summary[audit.bucket]++;
|
|
26037
|
+
if (audit.legacy)
|
|
26038
|
+
report.summary.legacyCount++;
|
|
26039
|
+
if (audit.score < 20)
|
|
26040
|
+
report.distribution["0-20"]++;
|
|
26041
|
+
else if (audit.score < 40)
|
|
26042
|
+
report.distribution["20-40"]++;
|
|
26043
|
+
else if (audit.score < 70)
|
|
26044
|
+
report.distribution["40-70"]++;
|
|
26045
|
+
else
|
|
26046
|
+
report.distribution["70-100"]++;
|
|
26047
|
+
for (const reason of audit.legacyReasons) {
|
|
26048
|
+
if (reason.startsWith("default confidence"))
|
|
26049
|
+
report.legacyBreakdown.defaultConfidence++;
|
|
26050
|
+
else if (reason.startsWith("pre-embeddings"))
|
|
26051
|
+
report.legacyBreakdown.missingEmbedding++;
|
|
26052
|
+
else if (reason.startsWith("stuck draft"))
|
|
26053
|
+
report.legacyBreakdown.stuckDraft++;
|
|
26054
|
+
else if (reason.startsWith("no graph"))
|
|
26055
|
+
report.legacyBreakdown.noGraphPresence++;
|
|
26056
|
+
}
|
|
26057
|
+
}
|
|
26058
|
+
report.lowest = [...audits].sort((a, b) => a.score - b.score).slice(0, 10);
|
|
26059
|
+
if (!dryRun) {
|
|
26060
|
+
for (const audit of audits) {
|
|
26061
|
+
try {
|
|
26062
|
+
if (audit.bucket === "delete") {
|
|
26063
|
+
await client3.deleteMemoryEntity(audit.id);
|
|
26064
|
+
report.actionsTaken.deleted++;
|
|
26065
|
+
} else if (audit.bucket === "archive") {
|
|
26066
|
+
await client3.updateMemoryEntity(audit.id, {
|
|
26067
|
+
confidence: 0.25,
|
|
26068
|
+
metadata: {
|
|
26069
|
+
audit_archived_at: new Date().toISOString(),
|
|
26070
|
+
audit_score: audit.score,
|
|
26071
|
+
audit_reasons: audit.reasons
|
|
26072
|
+
}
|
|
26073
|
+
});
|
|
26074
|
+
report.actionsTaken.archived++;
|
|
26075
|
+
} else if (audit.bucket === "review") {
|
|
26076
|
+
await client3.updateMemoryEntity(audit.id, {
|
|
26077
|
+
metadata: {
|
|
26078
|
+
needs_review: true,
|
|
26079
|
+
audit_score: audit.score,
|
|
26080
|
+
audit_reasons: audit.reasons,
|
|
26081
|
+
audit_at: new Date().toISOString()
|
|
26082
|
+
}
|
|
26083
|
+
});
|
|
26084
|
+
report.actionsTaken.flaggedReview++;
|
|
26085
|
+
}
|
|
26086
|
+
} catch (err) {
|
|
26087
|
+
report.errors.push({
|
|
26088
|
+
entityId: audit.id,
|
|
26089
|
+
step: audit.bucket,
|
|
26090
|
+
message: err.message
|
|
26091
|
+
});
|
|
26092
|
+
}
|
|
26093
|
+
}
|
|
26094
|
+
}
|
|
26095
|
+
report.healthReport = renderReport(report);
|
|
26096
|
+
return report;
|
|
26097
|
+
}
|
|
26098
|
+
function renderReport(report) {
|
|
26099
|
+
const mode = report.dryRun ? "Dry Run (preview)" : "Executed";
|
|
26100
|
+
const s = report.summary;
|
|
26101
|
+
const lines = [
|
|
26102
|
+
`# Memory Quality Audit
|
|
26103
|
+
`,
|
|
26104
|
+
`**Mode:** ${mode} | **Scanned:** ${s.scanned}/${s.totalEntities} | **Legacy:** ${s.legacyCount}`,
|
|
26105
|
+
"",
|
|
26106
|
+
"## Distribution",
|
|
26107
|
+
`- 70-100 (keep): ${report.distribution["70-100"]}`,
|
|
26108
|
+
`- 40-69 (review): ${report.distribution["40-70"]}`,
|
|
26109
|
+
`- 20-39 (archive): ${report.distribution["20-40"]}`,
|
|
26110
|
+
`- 0-19 (delete): ${report.distribution["0-20"]}`,
|
|
26111
|
+
"",
|
|
26112
|
+
"## Buckets",
|
|
26113
|
+
`- **Keep:** ${s.keep}`,
|
|
26114
|
+
`- **Review:** ${s.review}${!report.dryRun ? ` (flagged ${report.actionsTaken.flaggedReview})` : ""}`,
|
|
26115
|
+
`- **Archive:** ${s.archive}${!report.dryRun ? ` (archived ${report.actionsTaken.archived})` : ""}`,
|
|
26116
|
+
`- **Delete:** ${s.delete}${!report.dryRun ? ` (deleted ${report.actionsTaken.deleted})` : ""}`,
|
|
26117
|
+
""
|
|
26118
|
+
];
|
|
26119
|
+
const l = report.legacyBreakdown;
|
|
26120
|
+
if (s.legacyCount > 0) {
|
|
26121
|
+
lines.push("## Legacy Breakdown");
|
|
26122
|
+
lines.push(`- Default confidence, never validated: ${l.defaultConfidence}`);
|
|
26123
|
+
lines.push(`- Pre-embeddings migration: ${l.missingEmbedding}`);
|
|
26124
|
+
lines.push(`- Stuck drafts (>60d, no promotion): ${l.stuckDraft}`);
|
|
26125
|
+
lines.push(`- No tags + no relations: ${l.noGraphPresence}`);
|
|
26126
|
+
lines.push("");
|
|
26127
|
+
}
|
|
26128
|
+
if (report.lowest.length > 0) {
|
|
26129
|
+
lines.push("## Lowest-Scoring (top 10)");
|
|
26130
|
+
lines.push("| Score | Bucket | Tier | Age | Title | Reasons |");
|
|
26131
|
+
lines.push("|-------|--------|------|-----|-------|---------|");
|
|
26132
|
+
for (const a of report.lowest) {
|
|
26133
|
+
const reasonStr = a.reasons.slice(0, 3).join(", ") || "—";
|
|
26134
|
+
const titleTrunc = a.title.length > 40 ? `${a.title.slice(0, 37)}...` : a.title;
|
|
26135
|
+
lines.push(`| ${a.score} | ${a.bucket} | ${a.tier} | ${a.ageDays}d | ${titleTrunc} | ${reasonStr} |`);
|
|
26136
|
+
}
|
|
26137
|
+
lines.push("");
|
|
26138
|
+
}
|
|
26139
|
+
if (report.errors.length > 0) {
|
|
26140
|
+
lines.push("## Errors");
|
|
26141
|
+
for (const e of report.errors.slice(0, 10)) {
|
|
26142
|
+
lines.push(`- **${e.step}${e.entityId ? ` ${e.entityId}` : ""}:** ${e.message}`);
|
|
26143
|
+
}
|
|
26144
|
+
lines.push("");
|
|
26145
|
+
}
|
|
26146
|
+
if (report.dryRun) {
|
|
26147
|
+
lines.push("---");
|
|
26148
|
+
lines.push("*Run with `dryRun: false` to flag review entries, archive low-quality memories, and delete worst offenders.*");
|
|
26149
|
+
}
|
|
26150
|
+
return lines.join(`
|
|
26151
|
+
`);
|
|
26152
|
+
}
|
|
26153
|
+
|
|
25826
26154
|
// src/memory-cleanup.ts
|
|
25827
26155
|
init_dist();
|
|
25828
26156
|
var ALL_STEPS = [
|
|
@@ -25830,12 +26158,13 @@ var ALL_STEPS = [
|
|
|
25830
26158
|
"consolidate",
|
|
25831
26159
|
"orphans",
|
|
25832
26160
|
"duplicates",
|
|
25833
|
-
"backfill"
|
|
26161
|
+
"backfill",
|
|
26162
|
+
"audit"
|
|
25834
26163
|
];
|
|
25835
|
-
var
|
|
26164
|
+
var MS_PER_DAY2 = 1000 * 60 * 60 * 24;
|
|
25836
26165
|
var MAX_ENTITIES_FETCH = 200;
|
|
25837
26166
|
var DUPLICATE_SIMILARITY_THRESHOLD = 0.85;
|
|
25838
|
-
var
|
|
26167
|
+
var CONCURRENCY_LIMIT2 = 5;
|
|
25839
26168
|
async function runMemoryCleanup(client3, workspaceId, projectId, options) {
|
|
25840
26169
|
const dryRun = options?.dryRun !== false;
|
|
25841
26170
|
const steps = options?.steps ?? ALL_STEPS;
|
|
@@ -25991,6 +26320,44 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
|
|
|
25991
26320
|
});
|
|
25992
26321
|
}
|
|
25993
26322
|
}
|
|
26323
|
+
if (steps.includes("audit")) {
|
|
26324
|
+
try {
|
|
26325
|
+
const auditReport = await runMemoryAudit(client3, workspaceId, projectId, {
|
|
26326
|
+
dryRun,
|
|
26327
|
+
archiveBelow: options?.auditArchiveBelow,
|
|
26328
|
+
deleteBelow: options?.auditDeleteBelow
|
|
26329
|
+
});
|
|
26330
|
+
const low = auditReport.lowest.length > 0 ? auditReport.lowest[0].score : null;
|
|
26331
|
+
report.steps.audit = {
|
|
26332
|
+
scanned: auditReport.summary.scanned,
|
|
26333
|
+
legacyCount: auditReport.summary.legacyCount,
|
|
26334
|
+
buckets: {
|
|
26335
|
+
keep: auditReport.summary.keep,
|
|
26336
|
+
review: auditReport.summary.review,
|
|
26337
|
+
archive: auditReport.summary.archive,
|
|
26338
|
+
delete: auditReport.summary.delete
|
|
26339
|
+
},
|
|
26340
|
+
actions: auditReport.actionsTaken,
|
|
26341
|
+
lowestScore: low,
|
|
26342
|
+
report: auditReport
|
|
26343
|
+
};
|
|
26344
|
+
report.summary.issuesFound += auditReport.summary.review + auditReport.summary.archive + auditReport.summary.delete;
|
|
26345
|
+
if (!dryRun) {
|
|
26346
|
+
report.summary.actionsTaken += auditReport.actionsTaken.flaggedReview + auditReport.actionsTaken.archived + auditReport.actionsTaken.deleted;
|
|
26347
|
+
}
|
|
26348
|
+
for (const err of auditReport.errors) {
|
|
26349
|
+
report.errors.push({
|
|
26350
|
+
step: `audit:${err.step}`,
|
|
26351
|
+
message: err.entityId ? `${err.entityId}: ${err.message}` : err.message
|
|
26352
|
+
});
|
|
26353
|
+
}
|
|
26354
|
+
} catch (err) {
|
|
26355
|
+
report.errors.push({
|
|
26356
|
+
step: "audit",
|
|
26357
|
+
message: err.message
|
|
26358
|
+
});
|
|
26359
|
+
}
|
|
26360
|
+
}
|
|
25994
26361
|
report.healthReport = generateHealthReport(report);
|
|
25995
26362
|
return report;
|
|
25996
26363
|
}
|
|
@@ -25999,7 +26366,7 @@ function runPruneStep(entities, maxAgeDays) {
|
|
|
25999
26366
|
const drafts = entities.filter((e) => e.memory_tier === "draft");
|
|
26000
26367
|
const stale = [];
|
|
26001
26368
|
for (const entity of drafts) {
|
|
26002
|
-
const ageDays = (now - new Date(entity.created_at).getTime()) /
|
|
26369
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY2;
|
|
26003
26370
|
if (ageDays < maxAgeDays)
|
|
26004
26371
|
continue;
|
|
26005
26372
|
const lifecycle2 = evaluateLifecycle(entity);
|
|
@@ -26020,17 +26387,17 @@ async function runOrphanStep(client3, entities, orphanAgeDays) {
|
|
|
26020
26387
|
return false;
|
|
26021
26388
|
if (e.access_count >= 2)
|
|
26022
26389
|
return false;
|
|
26023
|
-
const ageDays = (now - new Date(e.created_at).getTime()) /
|
|
26390
|
+
const ageDays = (now - new Date(e.created_at).getTime()) / MS_PER_DAY2;
|
|
26024
26391
|
return ageDays >= orphanAgeDays;
|
|
26025
26392
|
});
|
|
26026
|
-
for (let i = 0;i < candidates.length; i +=
|
|
26027
|
-
const batch = candidates.slice(i, i +
|
|
26393
|
+
for (let i = 0;i < candidates.length; i += CONCURRENCY_LIMIT2) {
|
|
26394
|
+
const batch = candidates.slice(i, i + CONCURRENCY_LIMIT2);
|
|
26028
26395
|
const results = await Promise.allSettled(batch.map(async (entity) => {
|
|
26029
26396
|
const related = await client3.getRelatedEntities(entity.id);
|
|
26030
26397
|
const totalRelations = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
|
|
26031
26398
|
if (totalRelations > 0)
|
|
26032
26399
|
return null;
|
|
26033
|
-
const ageDays = (now - new Date(entity.created_at).getTime()) /
|
|
26400
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY2;
|
|
26034
26401
|
return {
|
|
26035
26402
|
id: entity.id,
|
|
26036
26403
|
title: entity.title,
|
|
@@ -26059,8 +26426,8 @@ async function runDuplicateStep(client3, entities, workspaceId, projectId) {
|
|
|
26059
26426
|
const flaggedForRemoval = new Set;
|
|
26060
26427
|
const entityMap = new Map(entities.map((e) => [e.id, e]));
|
|
26061
26428
|
const similarityMap = new Map;
|
|
26062
|
-
for (let i = 0;i < entities.length; i +=
|
|
26063
|
-
const batch = entities.slice(i, i +
|
|
26429
|
+
for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT2) {
|
|
26430
|
+
const batch = entities.slice(i, i + CONCURRENCY_LIMIT2);
|
|
26064
26431
|
const results = await Promise.allSettled(batch.map(async (entity) => {
|
|
26065
26432
|
const similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
|
|
26066
26433
|
return { entityId: entity.id, similar };
|
|
@@ -26212,6 +26579,20 @@ function generateHealthReport(report) {
|
|
|
26212
26579
|
`);
|
|
26213
26580
|
}
|
|
26214
26581
|
}
|
|
26582
|
+
if (report.steps.audit) {
|
|
26583
|
+
const a = report.steps.audit;
|
|
26584
|
+
lines.push("## Quality Audit");
|
|
26585
|
+
lines.push(`Scanned ${a.scanned} entities. Legacy signals on ${a.legacyCount}.`);
|
|
26586
|
+
lines.push(`Buckets — keep: ${a.buckets.keep}, review: ${a.buckets.review}, archive: ${a.buckets.archive}, delete: ${a.buckets.delete}.`);
|
|
26587
|
+
if (!report.dryRun) {
|
|
26588
|
+
lines.push(`Actions — flagged: ${a.actions.flaggedReview}, archived: ${a.actions.archived}, deleted: ${a.actions.deleted}.`);
|
|
26589
|
+
}
|
|
26590
|
+
if (a.report.lowest.length > 0) {
|
|
26591
|
+
const worst = a.report.lowest[0];
|
|
26592
|
+
lines.push(`Lowest score: **${worst.score}** — "${worst.title}" (${worst.reasons.slice(0, 2).join(", ") || "—"}).`);
|
|
26593
|
+
}
|
|
26594
|
+
lines.push("");
|
|
26595
|
+
}
|
|
26215
26596
|
if (report.errors.length > 0) {
|
|
26216
26597
|
lines.push("## Errors");
|
|
26217
26598
|
for (const e of report.errors) {
|
|
@@ -26225,6 +26606,85 @@ function generateHealthReport(report) {
|
|
|
26225
26606
|
return lines.join(`
|
|
26226
26607
|
`);
|
|
26227
26608
|
}
|
|
26609
|
+
async function purgeMemories(client3, workspaceId, projectId, options) {
|
|
26610
|
+
const dryRun = options.dryRun !== false;
|
|
26611
|
+
const { filters } = options;
|
|
26612
|
+
const hasFilter = filters.tier || filters.scope || filters.type || filters.olderThanDays !== undefined || filters.maxConfidence !== undefined || filters.tags && filters.tags.length > 0;
|
|
26613
|
+
if (!hasFilter) {
|
|
26614
|
+
throw new Error("At least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags) is required.");
|
|
26615
|
+
}
|
|
26616
|
+
const allMatches = [];
|
|
26617
|
+
let offset = 0;
|
|
26618
|
+
const pageSize = 100;
|
|
26619
|
+
const now = Date.now();
|
|
26620
|
+
while (true) {
|
|
26621
|
+
const result = await client3.listMemoryEntities({
|
|
26622
|
+
workspace_id: workspaceId,
|
|
26623
|
+
project_id: projectId,
|
|
26624
|
+
type: filters.type,
|
|
26625
|
+
scope: filters.scope,
|
|
26626
|
+
tags: filters.tags,
|
|
26627
|
+
limit: pageSize,
|
|
26628
|
+
offset
|
|
26629
|
+
});
|
|
26630
|
+
const entities = result.entities || [];
|
|
26631
|
+
if (entities.length === 0)
|
|
26632
|
+
break;
|
|
26633
|
+
for (const entity of entities) {
|
|
26634
|
+
if (filters.tier && entity.memory_tier !== filters.tier)
|
|
26635
|
+
continue;
|
|
26636
|
+
if (filters.maxConfidence !== undefined && entity.confidence > filters.maxConfidence)
|
|
26637
|
+
continue;
|
|
26638
|
+
if (filters.olderThanDays !== undefined) {
|
|
26639
|
+
const ref = entity.last_accessed_at || entity.created_at;
|
|
26640
|
+
const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY2;
|
|
26641
|
+
if (ageDays < filters.olderThanDays)
|
|
26642
|
+
continue;
|
|
26643
|
+
}
|
|
26644
|
+
allMatches.push(entity);
|
|
26645
|
+
}
|
|
26646
|
+
if (entities.length < pageSize)
|
|
26647
|
+
break;
|
|
26648
|
+
offset += pageSize;
|
|
26649
|
+
}
|
|
26650
|
+
const items = allMatches.map((e) => ({
|
|
26651
|
+
id: e.id,
|
|
26652
|
+
title: e.title,
|
|
26653
|
+
type: e.type,
|
|
26654
|
+
tier: e.memory_tier,
|
|
26655
|
+
confidence: e.confidence,
|
|
26656
|
+
ageDays: Math.round((now - new Date(e.last_accessed_at || e.created_at).getTime()) / MS_PER_DAY2)
|
|
26657
|
+
}));
|
|
26658
|
+
const errors3 = [];
|
|
26659
|
+
let purged = 0;
|
|
26660
|
+
if (!dryRun) {
|
|
26661
|
+
for (let i = 0;i < allMatches.length; i += CONCURRENCY_LIMIT2) {
|
|
26662
|
+
const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT2);
|
|
26663
|
+
const results = await Promise.allSettled(batch.map((e) => client3.deleteMemoryEntity(e.id)));
|
|
26664
|
+
for (let j = 0;j < results.length; j++) {
|
|
26665
|
+
if (results[j].status === "fulfilled") {
|
|
26666
|
+
purged++;
|
|
26667
|
+
} else {
|
|
26668
|
+
errors3.push({
|
|
26669
|
+
entityId: batch[j].id,
|
|
26670
|
+
message: results[j].status === "rejected" ? String(results[j].reason) : "Unknown error"
|
|
26671
|
+
});
|
|
26672
|
+
}
|
|
26673
|
+
}
|
|
26674
|
+
}
|
|
26675
|
+
}
|
|
26676
|
+
return {
|
|
26677
|
+
success: errors3.length === 0,
|
|
26678
|
+
dryRun,
|
|
26679
|
+
timestamp: new Date().toISOString(),
|
|
26680
|
+
workspace: { id: workspaceId, projectId },
|
|
26681
|
+
filters,
|
|
26682
|
+
matched: allMatches.length,
|
|
26683
|
+
purged: dryRun ? 0 : purged,
|
|
26684
|
+
items,
|
|
26685
|
+
errors: errors3
|
|
26686
|
+
};
|
|
26687
|
+
}
|
|
26228
26688
|
|
|
26229
26689
|
// src/onboard.ts
|
|
26230
26690
|
async function onboardNewUser(params) {
|
|
@@ -26820,6 +27280,20 @@ var TOOLS = {
|
|
|
26820
27280
|
estimatedMinutesRemaining: {
|
|
26821
27281
|
type: "number",
|
|
26822
27282
|
description: "Updated time estimate"
|
|
27283
|
+
},
|
|
27284
|
+
actions: {
|
|
27285
|
+
type: "array",
|
|
27286
|
+
items: {
|
|
27287
|
+
type: "object",
|
|
27288
|
+
properties: {
|
|
27289
|
+
description: {
|
|
27290
|
+
type: "string",
|
|
27291
|
+
description: "What was done, e.g. 'Edited CardDetailSheet.tsx — added done toggle'"
|
|
27292
|
+
}
|
|
27293
|
+
},
|
|
27294
|
+
required: ["description"]
|
|
27295
|
+
},
|
|
27296
|
+
description: "Actions performed since last update. Each becomes a visible activity log entry."
|
|
26823
27297
|
}
|
|
26824
27298
|
},
|
|
26825
27299
|
required: ["cardId", "agentIdentifier", "agentName"]
|
|
@@ -27662,7 +28136,7 @@ var TOOLS = {
|
|
|
27662
28136
|
}
|
|
27663
28137
|
},
|
|
27664
28138
|
harmony_cleanup_memories: {
|
|
27665
|
-
description: "Run a unified memory cleanup: prune stale drafts, consolidate similar memories, detect orphans and duplicates, and
|
|
28139
|
+
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.",
|
|
27666
28140
|
inputSchema: {
|
|
27667
28141
|
type: "object",
|
|
27668
28142
|
properties: {
|
|
@@ -27682,9 +28156,16 @@ var TOOLS = {
|
|
|
27682
28156
|
type: "array",
|
|
27683
28157
|
items: {
|
|
27684
28158
|
type: "string",
|
|
27685
|
-
enum: [
|
|
28159
|
+
enum: [
|
|
28160
|
+
"prune",
|
|
28161
|
+
"consolidate",
|
|
28162
|
+
"orphans",
|
|
28163
|
+
"duplicates",
|
|
28164
|
+
"backfill",
|
|
28165
|
+
"audit"
|
|
28166
|
+
]
|
|
27686
28167
|
},
|
|
27687
|
-
description: "Which cleanup steps to run (default: all). Options: prune, consolidate, orphans, duplicates, backfill."
|
|
28168
|
+
description: "Which cleanup steps to run (default: all). Options: prune, consolidate, orphans, duplicates, backfill, audit."
|
|
27688
28169
|
},
|
|
27689
28170
|
maxAgeDays: {
|
|
27690
28171
|
type: "number",
|
|
@@ -27697,6 +28178,94 @@ var TOOLS = {
|
|
|
27697
28178
|
orphanAgeDays: {
|
|
27698
28179
|
type: "number",
|
|
27699
28180
|
description: "Min age in days for orphan detection (default: 14)"
|
|
28181
|
+
},
|
|
28182
|
+
auditArchiveBelow: {
|
|
28183
|
+
type: "number",
|
|
28184
|
+
description: "Audit: archive entities scoring below this (default: 40)"
|
|
28185
|
+
},
|
|
28186
|
+
auditDeleteBelow: {
|
|
28187
|
+
type: "number",
|
|
28188
|
+
description: "Audit: delete entities scoring below this (default: 20)"
|
|
28189
|
+
}
|
|
28190
|
+
},
|
|
28191
|
+
required: []
|
|
28192
|
+
}
|
|
28193
|
+
},
|
|
28194
|
+
harmony_audit_memories: {
|
|
28195
|
+
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.",
|
|
28196
|
+
inputSchema: {
|
|
28197
|
+
type: "object",
|
|
28198
|
+
properties: {
|
|
28199
|
+
workspaceId: {
|
|
28200
|
+
type: "string",
|
|
28201
|
+
description: "Workspace ID (optional if context set)"
|
|
28202
|
+
},
|
|
28203
|
+
projectId: {
|
|
28204
|
+
type: "string",
|
|
28205
|
+
description: "Project ID (optional)"
|
|
28206
|
+
},
|
|
28207
|
+
dryRun: {
|
|
28208
|
+
type: "boolean",
|
|
28209
|
+
description: "Preview audit without flagging/archiving/deleting (default: true)"
|
|
28210
|
+
},
|
|
28211
|
+
archiveBelow: {
|
|
28212
|
+
type: "number",
|
|
28213
|
+
description: "Score threshold below which entities are archived (confidence set to 0.25). Default: 40"
|
|
28214
|
+
},
|
|
28215
|
+
deleteBelow: {
|
|
28216
|
+
type: "number",
|
|
28217
|
+
description: "Score threshold below which entities are hard-deleted. Default: 20. Set to 0 to never delete."
|
|
28218
|
+
},
|
|
28219
|
+
limit: {
|
|
28220
|
+
type: "number",
|
|
28221
|
+
description: "Max number of entities to audit (default: 500). Paginated fetch."
|
|
28222
|
+
}
|
|
28223
|
+
},
|
|
28224
|
+
required: []
|
|
28225
|
+
}
|
|
28226
|
+
},
|
|
28227
|
+
harmony_purge_memories: {
|
|
28228
|
+
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.",
|
|
28229
|
+
inputSchema: {
|
|
28230
|
+
type: "object",
|
|
28231
|
+
properties: {
|
|
28232
|
+
workspaceId: {
|
|
28233
|
+
type: "string",
|
|
28234
|
+
description: "Workspace ID (optional if context set)"
|
|
28235
|
+
},
|
|
28236
|
+
projectId: {
|
|
28237
|
+
type: "string",
|
|
28238
|
+
description: "Project ID (required — purge is project-scoped). Falls back to active project context."
|
|
28239
|
+
},
|
|
28240
|
+
dryRun: {
|
|
28241
|
+
type: "boolean",
|
|
28242
|
+
description: "Preview what would be deleted without executing (default: true)"
|
|
28243
|
+
},
|
|
28244
|
+
tier: {
|
|
28245
|
+
type: "string",
|
|
28246
|
+
enum: ["draft", "episode", "reference"],
|
|
28247
|
+
description: 'Filter by memory tier (e.g. "draft")'
|
|
28248
|
+
},
|
|
28249
|
+
scope: {
|
|
28250
|
+
type: "string",
|
|
28251
|
+
description: 'Filter by scope (e.g. "private", "project", "workspace")'
|
|
28252
|
+
},
|
|
28253
|
+
type: {
|
|
28254
|
+
type: "string",
|
|
28255
|
+
description: 'Filter by entity type (e.g. "error", "pattern", "lesson", "decision")'
|
|
28256
|
+
},
|
|
28257
|
+
olderThanDays: {
|
|
28258
|
+
type: "number",
|
|
28259
|
+
description: "Only include entities not accessed in at least this many days"
|
|
28260
|
+
},
|
|
28261
|
+
maxConfidence: {
|
|
28262
|
+
type: "number",
|
|
28263
|
+
description: "Only include entities with confidence at or below this value (e.g. 0.3 for low-confidence junk)"
|
|
28264
|
+
},
|
|
28265
|
+
tags: {
|
|
28266
|
+
type: "array",
|
|
28267
|
+
items: { type: "string" },
|
|
28268
|
+
description: "Only include entities matching these tags"
|
|
27700
28269
|
}
|
|
27701
28270
|
},
|
|
27702
28271
|
required: []
|
|
@@ -28330,12 +28899,20 @@ async function handleToolCall(name, args, deps) {
|
|
|
28330
28899
|
const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
|
|
28331
28900
|
const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
|
|
28332
28901
|
const progressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
|
|
28333
|
-
const
|
|
28902
|
+
const callerActions = args.actions;
|
|
28903
|
+
const now = new Date().toISOString();
|
|
28904
|
+
const callerRecentActions = [
|
|
28905
|
+
...args.recentActions || [],
|
|
28906
|
+
...(callerActions || []).map((a) => ({
|
|
28907
|
+
action: a.description,
|
|
28908
|
+
ts: now
|
|
28909
|
+
}))
|
|
28910
|
+
];
|
|
28334
28911
|
const memSession = getMemorySession(cardId);
|
|
28335
28912
|
let mergedRecentActions;
|
|
28336
28913
|
if (memSession?.dirty) {
|
|
28337
|
-
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions
|
|
28338
|
-
} else if (callerRecentActions) {
|
|
28914
|
+
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions);
|
|
28915
|
+
} else if (callerRecentActions.length > 0) {
|
|
28339
28916
|
mergedRecentActions = callerRecentActions;
|
|
28340
28917
|
}
|
|
28341
28918
|
const result = await client3.updateAgentProgress(cardId, {
|
|
@@ -29211,6 +29788,30 @@ async function handleToolCall(name, args, deps) {
|
|
|
29211
29788
|
message: `Processed ${entitiesProcessed} entities, created ${relationsCreated} new relations.`
|
|
29212
29789
|
};
|
|
29213
29790
|
}
|
|
29791
|
+
case "harmony_audit_memories": {
|
|
29792
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
29793
|
+
if (!workspaceId) {
|
|
29794
|
+
throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
|
|
29795
|
+
}
|
|
29796
|
+
const projectId = args.projectId || deps.getActiveProjectId() || undefined;
|
|
29797
|
+
const report = await runMemoryAudit(client3, workspaceId, projectId, {
|
|
29798
|
+
dryRun: args.dryRun,
|
|
29799
|
+
archiveBelow: args.archiveBelow,
|
|
29800
|
+
deleteBelow: args.deleteBelow,
|
|
29801
|
+
limit: args.limit
|
|
29802
|
+
});
|
|
29803
|
+
return {
|
|
29804
|
+
success: report.success,
|
|
29805
|
+
dryRun: report.dryRun,
|
|
29806
|
+
summary: report.summary,
|
|
29807
|
+
distribution: report.distribution,
|
|
29808
|
+
legacyBreakdown: report.legacyBreakdown,
|
|
29809
|
+
actionsTaken: report.actionsTaken,
|
|
29810
|
+
lowest: report.lowest,
|
|
29811
|
+
errors: report.errors,
|
|
29812
|
+
healthReport: report.healthReport
|
|
29813
|
+
};
|
|
29814
|
+
}
|
|
29214
29815
|
case "harmony_cleanup_memories": {
|
|
29215
29816
|
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
29216
29817
|
if (!workspaceId) {
|
|
@@ -29222,7 +29823,8 @@ async function handleToolCall(name, args, deps) {
|
|
|
29222
29823
|
"consolidate",
|
|
29223
29824
|
"orphans",
|
|
29224
29825
|
"duplicates",
|
|
29225
|
-
"backfill"
|
|
29826
|
+
"backfill",
|
|
29827
|
+
"audit"
|
|
29226
29828
|
];
|
|
29227
29829
|
const rawSteps = args.steps;
|
|
29228
29830
|
const steps = rawSteps?.filter((s) => validSteps.includes(s));
|
|
@@ -29235,7 +29837,9 @@ async function handleToolCall(name, args, deps) {
|
|
|
29235
29837
|
steps,
|
|
29236
29838
|
maxAgeDays: args.maxAgeDays,
|
|
29237
29839
|
minClusterSize: args.minClusterSize,
|
|
29238
|
-
orphanAgeDays: args.orphanAgeDays
|
|
29840
|
+
orphanAgeDays: args.orphanAgeDays,
|
|
29841
|
+
auditArchiveBelow: args.auditArchiveBelow,
|
|
29842
|
+
auditDeleteBelow: args.auditDeleteBelow
|
|
29239
29843
|
});
|
|
29240
29844
|
return {
|
|
29241
29845
|
success: report.success,
|
|
@@ -29245,6 +29849,42 @@ async function handleToolCall(name, args, deps) {
|
|
|
29245
29849
|
healthReport: report.healthReport
|
|
29246
29850
|
};
|
|
29247
29851
|
}
|
|
29852
|
+
case "harmony_purge_memories": {
|
|
29853
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
29854
|
+
if (!workspaceId) {
|
|
29855
|
+
throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
|
|
29856
|
+
}
|
|
29857
|
+
const projectId = args.projectId || deps.getActiveProjectId();
|
|
29858
|
+
if (!projectId) {
|
|
29859
|
+
throw new Error("No project specified. Purge requires a project scope. Use harmony_set_project_context or provide projectId.");
|
|
29860
|
+
}
|
|
29861
|
+
const filters = {};
|
|
29862
|
+
if (args.tier)
|
|
29863
|
+
filters.tier = args.tier;
|
|
29864
|
+
if (args.scope)
|
|
29865
|
+
filters.scope = args.scope;
|
|
29866
|
+
if (args.type)
|
|
29867
|
+
filters.type = args.type;
|
|
29868
|
+
if (args.olderThanDays !== undefined)
|
|
29869
|
+
filters.olderThanDays = args.olderThanDays;
|
|
29870
|
+
if (args.maxConfidence !== undefined)
|
|
29871
|
+
filters.maxConfidence = args.maxConfidence;
|
|
29872
|
+
if (args.tags)
|
|
29873
|
+
filters.tags = args.tags;
|
|
29874
|
+
const report = await purgeMemories(client3, workspaceId, projectId, {
|
|
29875
|
+
dryRun: args.dryRun,
|
|
29876
|
+
filters
|
|
29877
|
+
});
|
|
29878
|
+
return {
|
|
29879
|
+
success: report.success,
|
|
29880
|
+
dryRun: report.dryRun,
|
|
29881
|
+
matched: report.matched,
|
|
29882
|
+
purged: report.purged,
|
|
29883
|
+
items: report.items,
|
|
29884
|
+
errors: report.errors,
|
|
29885
|
+
message: report.dryRun ? `Found ${report.matched} entities matching filters. Run with dryRun=false to delete.` : `Purged ${report.purged} of ${report.matched} matching entities.`
|
|
29886
|
+
};
|
|
29887
|
+
}
|
|
29248
29888
|
default:
|
|
29249
29889
|
throw new Error(`Unknown tool: ${name}`);
|
|
29250
29890
|
}
|
|
@@ -29282,15 +29922,26 @@ class HarmonyMCPServer {
|
|
|
29282
29922
|
const cv = this.server.getClientVersion();
|
|
29283
29923
|
return cv ? { name: cv.name, version: cv.version } : null;
|
|
29284
29924
|
});
|
|
29925
|
+
let exitCode = 0;
|
|
29285
29926
|
const handleShutdown = async () => {
|
|
29286
29927
|
try {
|
|
29287
29928
|
await shutdownAllSessions();
|
|
29288
29929
|
} catch {}
|
|
29289
29930
|
destroyAutoSession();
|
|
29290
|
-
process.exit(
|
|
29931
|
+
process.exit(exitCode);
|
|
29291
29932
|
};
|
|
29292
29933
|
process.on("SIGINT", handleShutdown);
|
|
29293
29934
|
process.on("SIGTERM", handleShutdown);
|
|
29935
|
+
process.on("uncaughtException", (err) => {
|
|
29936
|
+
console.error("MCP server uncaught exception:", err);
|
|
29937
|
+
exitCode = 1;
|
|
29938
|
+
handleShutdown();
|
|
29939
|
+
});
|
|
29940
|
+
process.on("unhandledRejection", (reason) => {
|
|
29941
|
+
console.error("MCP server unhandled rejection:", reason);
|
|
29942
|
+
exitCode = 1;
|
|
29943
|
+
handleShutdown();
|
|
29944
|
+
});
|
|
29294
29945
|
try {
|
|
29295
29946
|
if (isConfigured()) {
|
|
29296
29947
|
const workspaceId = getActiveWorkspaceId();
|