@gethmy/mcp 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cli.js +80 -23
  2. package/dist/index.js +80 -23
  3. package/dist/lib/active-learning.js +939 -787
  4. package/dist/lib/api-client.js +2527 -638
  5. package/dist/lib/auto-session.js +177 -196
  6. package/dist/lib/cli.js +34954 -128
  7. package/dist/lib/config.js +235 -201
  8. package/dist/lib/consolidation.js +374 -289
  9. package/dist/lib/context-assembly.js +1265 -838
  10. package/dist/lib/graph-expansion.js +139 -155
  11. package/dist/lib/http.js +1917 -130
  12. package/dist/lib/index.js +29525 -5
  13. package/dist/lib/lifecycle-maintenance.js +663 -79
  14. package/dist/lib/memory-cleanup.js +1316 -381
  15. package/dist/lib/onboard.js +2588 -32
  16. package/dist/lib/prompt-builder.js +438 -445
  17. package/dist/lib/remote.js +31733 -143
  18. package/dist/lib/server.js +29389 -3216
  19. package/dist/lib/skills.js +315 -132
  20. package/dist/lib/tui/agents.js +128 -107
  21. package/dist/lib/tui/docs.js +1590 -687
  22. package/dist/lib/tui/setup.js +5698 -804
  23. package/dist/lib/tui/theme.js +183 -86
  24. package/dist/lib/tui/writer.js +1149 -176
  25. package/package.json +2 -2
  26. package/src/api-client.ts +37 -1
  27. package/src/memory-cleanup.ts +92 -52
  28. package/src/server.ts +16 -1
  29. package/dist/lib/__tests__/active-learning.test.js +0 -386
  30. package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
  31. package/dist/lib/__tests__/auto-session.test.js +0 -661
  32. package/dist/lib/__tests__/context-assembly.test.js +0 -362
  33. package/dist/lib/__tests__/graph-expansion.test.js +0 -150
  34. package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
  35. package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
  36. package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
  37. package/dist/lib/__tests__/pattern-detection.test.js +0 -295
  38. package/dist/lib/__tests__/prompt-builder.test.js +0 -418
package/dist/cli.js CHANGED
@@ -27273,6 +27273,12 @@ class HarmonyApiClient {
27273
27273
  async endAgentSession(cardId, data) {
27274
27274
  return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
27275
27275
  }
27276
+ async flushActivityLog(cardId, data) {
27277
+ return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
27278
+ }
27279
+ async getActivityLog(cardId, sessionId) {
27280
+ return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
27281
+ }
27276
27282
  async getAgentSession(cardId, options) {
27277
27283
  const params = new URLSearchParams;
27278
27284
  if (options?.includeEnded)
@@ -28066,6 +28072,10 @@ var ALL_STEPS = [
28066
28072
  "duplicates",
28067
28073
  "backfill"
28068
28074
  ];
28075
+ var MS_PER_DAY = 1000 * 60 * 60 * 24;
28076
+ var MAX_ENTITIES_FETCH = 200;
28077
+ var DUPLICATE_SIMILARITY_THRESHOLD = 0.85;
28078
+ var CONCURRENCY_LIMIT = 5;
28069
28079
  async function runMemoryCleanup(client3, workspaceId, projectId, options) {
28070
28080
  const dryRun = options?.dryRun !== false;
28071
28081
  const steps = options?.steps ?? ALL_STEPS;
@@ -28087,7 +28097,7 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
28087
28097
  const listResult = await client3.listMemoryEntities({
28088
28098
  workspace_id: workspaceId,
28089
28099
  project_id: projectId,
28090
- limit: 200
28100
+ limit: MAX_ENTITIES_FETCH
28091
28101
  });
28092
28102
  entities = listResult.entities || [];
28093
28103
  report.summary.totalEntities = entities.length;
@@ -28108,7 +28118,12 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
28108
28118
  try {
28109
28119
  await client3.deleteMemoryEntity(item.id);
28110
28120
  report.steps.prune.pruned++;
28111
- } catch {}
28121
+ } catch (err) {
28122
+ report.errors.push({
28123
+ step: "prune",
28124
+ message: `Failed to delete ${item.id}: ${err.message}`
28125
+ });
28126
+ }
28112
28127
  }
28113
28128
  report.summary.actionsTaken += report.steps.prune.pruned;
28114
28129
  }
@@ -28150,7 +28165,12 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
28150
28165
  try {
28151
28166
  await client3.deleteMemoryEntity(item.id);
28152
28167
  report.steps.orphans.removed++;
28153
- } catch {}
28168
+ } catch (err) {
28169
+ report.errors.push({
28170
+ step: "orphans",
28171
+ message: `Failed to delete ${item.id}: ${err.message}`
28172
+ });
28173
+ }
28154
28174
  }
28155
28175
  report.summary.actionsTaken += report.steps.orphans.removed;
28156
28176
  }
@@ -28170,7 +28190,12 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
28170
28190
  try {
28171
28191
  await client3.deleteMemoryEntity(pair.removeId);
28172
28192
  report.steps.duplicates.resolved++;
28173
- } catch {}
28193
+ } catch (err) {
28194
+ report.errors.push({
28195
+ step: "duplicates",
28196
+ message: `Failed to delete ${pair.removeId}: ${err.message}`
28197
+ });
28198
+ }
28174
28199
  }
28175
28200
  report.summary.actionsTaken += report.steps.duplicates.resolved;
28176
28201
  }
@@ -28214,7 +28239,7 @@ function runPruneStep(entities, maxAgeDays) {
28214
28239
  const drafts = entities.filter((e) => e.memory_tier === "draft");
28215
28240
  const stale = [];
28216
28241
  for (const entity of drafts) {
28217
- const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
28242
+ const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY;
28218
28243
  if (ageDays < maxAgeDays)
28219
28244
  continue;
28220
28245
  const lifecycle2 = evaluateLifecycle(entity);
@@ -28235,26 +28260,32 @@ async function runOrphanStep(client3, entities, orphanAgeDays) {
28235
28260
  return false;
28236
28261
  if (e.access_count >= 2)
28237
28262
  return false;
28238
- const ageDays = (now - new Date(e.created_at).getTime()) / (1000 * 60 * 60 * 24);
28263
+ const ageDays = (now - new Date(e.created_at).getTime()) / MS_PER_DAY;
28239
28264
  return ageDays >= orphanAgeDays;
28240
28265
  });
28241
- for (const entity of candidates) {
28242
- try {
28266
+ for (let i = 0;i < candidates.length; i += CONCURRENCY_LIMIT) {
28267
+ const batch = candidates.slice(i, i + CONCURRENCY_LIMIT);
28268
+ const results = await Promise.allSettled(batch.map(async (entity) => {
28243
28269
  const related = await client3.getRelatedEntities(entity.id);
28244
28270
  const totalRelations = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
28245
28271
  if (totalRelations > 0)
28246
- continue;
28247
- const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
28248
- result.items.push({
28272
+ return null;
28273
+ const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY;
28274
+ return {
28249
28275
  id: entity.id,
28250
28276
  title: entity.title,
28251
28277
  type: entity.type,
28252
28278
  tier: entity.memory_tier,
28253
28279
  ageDays: Math.round(ageDays),
28254
28280
  accessCount: entity.access_count
28255
- });
28256
- result.orphansFound++;
28257
- } catch {}
28281
+ };
28282
+ }));
28283
+ for (const r of results) {
28284
+ if (r.status === "fulfilled" && r.value) {
28285
+ result.items.push(r.value);
28286
+ result.orphansFound++;
28287
+ }
28288
+ }
28258
28289
  }
28259
28290
  return result;
28260
28291
  }
@@ -28266,15 +28297,24 @@ async function runDuplicateStep(client3, entities, workspaceId, projectId) {
28266
28297
  };
28267
28298
  const seenPairs = new Set;
28268
28299
  const flaggedForRemoval = new Set;
28300
+ const entityMap = new Map(entities.map((e) => [e.id, e]));
28301
+ const similarityMap = new Map;
28302
+ for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT) {
28303
+ const batch = entities.slice(i, i + CONCURRENCY_LIMIT);
28304
+ const results = await Promise.allSettled(batch.map(async (entity) => {
28305
+ const similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
28306
+ return { entityId: entity.id, similar };
28307
+ }));
28308
+ for (const r of results) {
28309
+ if (r.status === "fulfilled") {
28310
+ similarityMap.set(r.value.entityId, r.value.similar);
28311
+ }
28312
+ }
28313
+ }
28269
28314
  for (const entity of entities) {
28270
28315
  if (flaggedForRemoval.has(entity.id))
28271
28316
  continue;
28272
- let similar;
28273
- try {
28274
- similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
28275
- } catch {
28276
- continue;
28277
- }
28317
+ const similar = similarityMap.get(entity.id) || [];
28278
28318
  for (const match of similar) {
28279
28319
  if (flaggedForRemoval.has(match.id))
28280
28320
  continue;
@@ -28283,10 +28323,10 @@ async function runDuplicateStep(client3, entities, workspaceId, projectId) {
28283
28323
  continue;
28284
28324
  seenPairs.add(pairKey);
28285
28325
  const sim = titleSimilarity(entity.title, match.title);
28286
- if (sim < 0.85)
28326
+ if (sim < DUPLICATE_SIMILARITY_THRESHOLD)
28287
28327
  continue;
28288
28328
  const entityScore = entityQualityScore(entity);
28289
- const matchEntity = entities.find((e) => e.id === match.id);
28329
+ const matchEntity = entityMap.get(match.id);
28290
28330
  const matchScore = matchEntity ? entityQualityScore(matchEntity) : match.confidence;
28291
28331
  const [keep, remove] = entityScore >= matchScore ? [entity, { id: match.id, title: match.title }] : [{ id: match.id, title: match.title }, entity];
28292
28332
  flaggedForRemoval.add(remove.id);
@@ -28335,6 +28375,10 @@ function generateHealthReport(report) {
28335
28375
  `**Mode:** ${mode} | **Entities:** ${report.summary.totalEntities} | **Issues:** ${report.summary.issuesFound} | **Actions:** ${report.summary.actionsTaken}`,
28336
28376
  ""
28337
28377
  ];
28378
+ if (report.summary.totalEntities >= MAX_ENTITIES_FETCH) {
28379
+ lines.push(`> **Note:** Entity count hit the ${MAX_ENTITIES_FETCH} fetch limit. Some entities may not have been analyzed.
28380
+ `);
28381
+ }
28338
28382
  if (report.steps.prune) {
28339
28383
  const p = report.steps.prune;
28340
28384
  lines.push("## Stale Drafts");
@@ -31413,9 +31457,22 @@ async function handleToolCall(name, args, deps) {
31413
31457
  throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
31414
31458
  }
31415
31459
  const projectId = args.projectId || deps.getActiveProjectId() || undefined;
31460
+ const validSteps = [
31461
+ "prune",
31462
+ "consolidate",
31463
+ "orphans",
31464
+ "duplicates",
31465
+ "backfill"
31466
+ ];
31467
+ const rawSteps = args.steps;
31468
+ const steps = rawSteps?.filter((s) => validSteps.includes(s));
31469
+ if (rawSteps && steps && steps.length < rawSteps.length) {
31470
+ const invalid = rawSteps.filter((s) => !validSteps.includes(s));
31471
+ console.warn(`Unknown cleanup steps ignored: ${invalid.join(", ")}`);
31472
+ }
31416
31473
  const report = await runMemoryCleanup(client3, workspaceId, projectId, {
31417
31474
  dryRun: args.dryRun,
31418
- steps: args.steps,
31475
+ steps,
31419
31476
  maxAgeDays: args.maxAgeDays,
31420
31477
  minClusterSize: args.minClusterSize,
31421
31478
  orphanAgeDays: args.orphanAgeDays
package/dist/index.js CHANGED
@@ -25033,6 +25033,12 @@ class HarmonyApiClient {
25033
25033
  async endAgentSession(cardId, data) {
25034
25034
  return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
25035
25035
  }
25036
+ async flushActivityLog(cardId, data) {
25037
+ return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
25038
+ }
25039
+ async getActivityLog(cardId, sessionId) {
25040
+ return this.request("GET", `/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`);
25041
+ }
25036
25042
  async getAgentSession(cardId, options) {
25037
25043
  const params = new URLSearchParams;
25038
25044
  if (options?.includeEnded)
@@ -25826,6 +25832,10 @@ var ALL_STEPS = [
25826
25832
  "duplicates",
25827
25833
  "backfill"
25828
25834
  ];
25835
+ var MS_PER_DAY = 1000 * 60 * 60 * 24;
25836
+ var MAX_ENTITIES_FETCH = 200;
25837
+ var DUPLICATE_SIMILARITY_THRESHOLD = 0.85;
25838
+ var CONCURRENCY_LIMIT = 5;
25829
25839
  async function runMemoryCleanup(client3, workspaceId, projectId, options) {
25830
25840
  const dryRun = options?.dryRun !== false;
25831
25841
  const steps = options?.steps ?? ALL_STEPS;
@@ -25847,7 +25857,7 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
25847
25857
  const listResult = await client3.listMemoryEntities({
25848
25858
  workspace_id: workspaceId,
25849
25859
  project_id: projectId,
25850
- limit: 200
25860
+ limit: MAX_ENTITIES_FETCH
25851
25861
  });
25852
25862
  entities = listResult.entities || [];
25853
25863
  report.summary.totalEntities = entities.length;
@@ -25868,7 +25878,12 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
25868
25878
  try {
25869
25879
  await client3.deleteMemoryEntity(item.id);
25870
25880
  report.steps.prune.pruned++;
25871
- } catch {}
25881
+ } catch (err) {
25882
+ report.errors.push({
25883
+ step: "prune",
25884
+ message: `Failed to delete ${item.id}: ${err.message}`
25885
+ });
25886
+ }
25872
25887
  }
25873
25888
  report.summary.actionsTaken += report.steps.prune.pruned;
25874
25889
  }
@@ -25910,7 +25925,12 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
25910
25925
  try {
25911
25926
  await client3.deleteMemoryEntity(item.id);
25912
25927
  report.steps.orphans.removed++;
25913
- } catch {}
25928
+ } catch (err) {
25929
+ report.errors.push({
25930
+ step: "orphans",
25931
+ message: `Failed to delete ${item.id}: ${err.message}`
25932
+ });
25933
+ }
25914
25934
  }
25915
25935
  report.summary.actionsTaken += report.steps.orphans.removed;
25916
25936
  }
@@ -25930,7 +25950,12 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
25930
25950
  try {
25931
25951
  await client3.deleteMemoryEntity(pair.removeId);
25932
25952
  report.steps.duplicates.resolved++;
25933
- } catch {}
25953
+ } catch (err) {
25954
+ report.errors.push({
25955
+ step: "duplicates",
25956
+ message: `Failed to delete ${pair.removeId}: ${err.message}`
25957
+ });
25958
+ }
25934
25959
  }
25935
25960
  report.summary.actionsTaken += report.steps.duplicates.resolved;
25936
25961
  }
@@ -25974,7 +25999,7 @@ function runPruneStep(entities, maxAgeDays) {
25974
25999
  const drafts = entities.filter((e) => e.memory_tier === "draft");
25975
26000
  const stale = [];
25976
26001
  for (const entity of drafts) {
25977
- const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
26002
+ const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY;
25978
26003
  if (ageDays < maxAgeDays)
25979
26004
  continue;
25980
26005
  const lifecycle2 = evaluateLifecycle(entity);
@@ -25995,26 +26020,32 @@ async function runOrphanStep(client3, entities, orphanAgeDays) {
25995
26020
  return false;
25996
26021
  if (e.access_count >= 2)
25997
26022
  return false;
25998
- const ageDays = (now - new Date(e.created_at).getTime()) / (1000 * 60 * 60 * 24);
26023
+ const ageDays = (now - new Date(e.created_at).getTime()) / MS_PER_DAY;
25999
26024
  return ageDays >= orphanAgeDays;
26000
26025
  });
26001
- for (const entity of candidates) {
26002
- try {
26026
+ for (let i = 0;i < candidates.length; i += CONCURRENCY_LIMIT) {
26027
+ const batch = candidates.slice(i, i + CONCURRENCY_LIMIT);
26028
+ const results = await Promise.allSettled(batch.map(async (entity) => {
26003
26029
  const related = await client3.getRelatedEntities(entity.id);
26004
26030
  const totalRelations = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
26005
26031
  if (totalRelations > 0)
26006
- continue;
26007
- const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
26008
- result.items.push({
26032
+ return null;
26033
+ const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY;
26034
+ return {
26009
26035
  id: entity.id,
26010
26036
  title: entity.title,
26011
26037
  type: entity.type,
26012
26038
  tier: entity.memory_tier,
26013
26039
  ageDays: Math.round(ageDays),
26014
26040
  accessCount: entity.access_count
26015
- });
26016
- result.orphansFound++;
26017
- } catch {}
26041
+ };
26042
+ }));
26043
+ for (const r of results) {
26044
+ if (r.status === "fulfilled" && r.value) {
26045
+ result.items.push(r.value);
26046
+ result.orphansFound++;
26047
+ }
26048
+ }
26018
26049
  }
26019
26050
  return result;
26020
26051
  }
@@ -26026,15 +26057,24 @@ async function runDuplicateStep(client3, entities, workspaceId, projectId) {
26026
26057
  };
26027
26058
  const seenPairs = new Set;
26028
26059
  const flaggedForRemoval = new Set;
26060
+ const entityMap = new Map(entities.map((e) => [e.id, e]));
26061
+ const similarityMap = new Map;
26062
+ for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT) {
26063
+ const batch = entities.slice(i, i + CONCURRENCY_LIMIT);
26064
+ const results = await Promise.allSettled(batch.map(async (entity) => {
26065
+ const similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
26066
+ return { entityId: entity.id, similar };
26067
+ }));
26068
+ for (const r of results) {
26069
+ if (r.status === "fulfilled") {
26070
+ similarityMap.set(r.value.entityId, r.value.similar);
26071
+ }
26072
+ }
26073
+ }
26029
26074
  for (const entity of entities) {
26030
26075
  if (flaggedForRemoval.has(entity.id))
26031
26076
  continue;
26032
- let similar;
26033
- try {
26034
- similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
26035
- } catch {
26036
- continue;
26037
- }
26077
+ const similar = similarityMap.get(entity.id) || [];
26038
26078
  for (const match of similar) {
26039
26079
  if (flaggedForRemoval.has(match.id))
26040
26080
  continue;
@@ -26043,10 +26083,10 @@ async function runDuplicateStep(client3, entities, workspaceId, projectId) {
26043
26083
  continue;
26044
26084
  seenPairs.add(pairKey);
26045
26085
  const sim = titleSimilarity(entity.title, match.title);
26046
- if (sim < 0.85)
26086
+ if (sim < DUPLICATE_SIMILARITY_THRESHOLD)
26047
26087
  continue;
26048
26088
  const entityScore = entityQualityScore(entity);
26049
- const matchEntity = entities.find((e) => e.id === match.id);
26089
+ const matchEntity = entityMap.get(match.id);
26050
26090
  const matchScore = matchEntity ? entityQualityScore(matchEntity) : match.confidence;
26051
26091
  const [keep, remove] = entityScore >= matchScore ? [entity, { id: match.id, title: match.title }] : [{ id: match.id, title: match.title }, entity];
26052
26092
  flaggedForRemoval.add(remove.id);
@@ -26095,6 +26135,10 @@ function generateHealthReport(report) {
26095
26135
  `**Mode:** ${mode} | **Entities:** ${report.summary.totalEntities} | **Issues:** ${report.summary.issuesFound} | **Actions:** ${report.summary.actionsTaken}`,
26096
26136
  ""
26097
26137
  ];
26138
+ if (report.summary.totalEntities >= MAX_ENTITIES_FETCH) {
26139
+ lines.push(`> **Note:** Entity count hit the ${MAX_ENTITIES_FETCH} fetch limit. Some entities may not have been analyzed.
26140
+ `);
26141
+ }
26098
26142
  if (report.steps.prune) {
26099
26143
  const p = report.steps.prune;
26100
26144
  lines.push("## Stale Drafts");
@@ -29173,9 +29217,22 @@ async function handleToolCall(name, args, deps) {
29173
29217
  throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
29174
29218
  }
29175
29219
  const projectId = args.projectId || deps.getActiveProjectId() || undefined;
29220
+ const validSteps = [
29221
+ "prune",
29222
+ "consolidate",
29223
+ "orphans",
29224
+ "duplicates",
29225
+ "backfill"
29226
+ ];
29227
+ const rawSteps = args.steps;
29228
+ const steps = rawSteps?.filter((s) => validSteps.includes(s));
29229
+ if (rawSteps && steps && steps.length < rawSteps.length) {
29230
+ const invalid = rawSteps.filter((s) => !validSteps.includes(s));
29231
+ console.warn(`Unknown cleanup steps ignored: ${invalid.join(", ")}`);
29232
+ }
29176
29233
  const report = await runMemoryCleanup(client3, workspaceId, projectId, {
29177
29234
  dryRun: args.dryRun,
29178
- steps: args.steps,
29235
+ steps,
29179
29236
  maxAgeDays: args.maxAgeDays,
29180
29237
  minClusterSize: args.minClusterSize,
29181
29238
  orphanAgeDays: args.orphanAgeDays