@gethmy/mcp 2.3.2 → 2.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -28465,6 +28465,85 @@ function generateHealthReport(report) {
28465
28465
  return lines.join(`
28466
28466
  `);
28467
28467
  }
28468
+ async function purgeMemories(client3, workspaceId, projectId, options) {
28469
+ const dryRun = options.dryRun !== false;
28470
+ const { filters } = options;
28471
+ const hasFilter = filters.tier || filters.scope || filters.type || filters.olderThanDays !== undefined || filters.maxConfidence !== undefined || filters.tags && filters.tags.length > 0;
28472
+ if (!hasFilter) {
28473
+ throw new Error("At least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags) is required.");
28474
+ }
28475
+ const allMatches = [];
28476
+ let offset = 0;
28477
+ const pageSize = 100;
28478
+ const now = Date.now();
28479
+ while (true) {
28480
+ const result = await client3.listMemoryEntities({
28481
+ workspace_id: workspaceId,
28482
+ project_id: projectId,
28483
+ type: filters.type,
28484
+ scope: filters.scope,
28485
+ tags: filters.tags,
28486
+ limit: pageSize,
28487
+ offset
28488
+ });
28489
+ const entities = result.entities || [];
28490
+ if (entities.length === 0)
28491
+ break;
28492
+ for (const entity of entities) {
28493
+ if (filters.tier && entity.memory_tier !== filters.tier)
28494
+ continue;
28495
+ if (filters.maxConfidence !== undefined && entity.confidence > filters.maxConfidence)
28496
+ continue;
28497
+ if (filters.olderThanDays !== undefined) {
28498
+ const ref = entity.last_accessed_at || entity.created_at;
28499
+ const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY;
28500
+ if (ageDays < filters.olderThanDays)
28501
+ continue;
28502
+ }
28503
+ allMatches.push(entity);
28504
+ }
28505
+ if (entities.length < pageSize)
28506
+ break;
28507
+ offset += pageSize;
28508
+ }
28509
+ const items = allMatches.map((e) => ({
28510
+ id: e.id,
28511
+ title: e.title,
28512
+ type: e.type,
28513
+ tier: e.memory_tier,
28514
+ confidence: e.confidence,
28515
+ ageDays: Math.round((now - new Date(e.last_accessed_at || e.created_at).getTime()) / MS_PER_DAY)
28516
+ }));
28517
+ const errors3 = [];
28518
+ let purged = 0;
28519
+ if (!dryRun) {
28520
+ for (let i = 0;i < allMatches.length; i += CONCURRENCY_LIMIT) {
28521
+ const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT);
28522
+ const results = await Promise.allSettled(batch.map((e) => client3.deleteMemoryEntity(e.id)));
28523
+ for (let j = 0;j < results.length; j++) {
28524
+ if (results[j].status === "fulfilled") {
28525
+ purged++;
28526
+ } else {
28527
+ errors3.push({
28528
+ entityId: batch[j].id,
28529
+ message: results[j].status === "rejected" ? String(results[j].reason) : "Unknown error"
28530
+ });
28531
+ }
28532
+ }
28533
+ }
28534
+ }
28535
+ return {
28536
+ success: errors3.length === 0,
28537
+ dryRun,
28538
+ timestamp: new Date().toISOString(),
28539
+ workspace: { id: workspaceId, projectId },
28540
+ filters,
28541
+ matched: allMatches.length,
28542
+ purged: dryRun ? 0 : purged,
28543
+ items,
28544
+ errors: errors3
28545
+ };
28546
+ }
28468
28547
 
28469
28548
  // src/onboard.ts
28470
28549
  async function onboardNewUser(params) {
@@ -29060,6 +29139,20 @@ var TOOLS = {
29060
29139
  estimatedMinutesRemaining: {
29061
29140
  type: "number",
29062
29141
  description: "Updated time estimate"
29142
+ },
29143
+ actions: {
29144
+ type: "array",
29145
+ items: {
29146
+ type: "object",
29147
+ properties: {
29148
+ description: {
29149
+ type: "string",
29150
+ description: "What was done, e.g. 'Edited CardDetailSheet.tsx — added done toggle'"
29151
+ }
29152
+ },
29153
+ required: ["description"]
29154
+ },
29155
+ description: "Actions performed since last update. Each becomes a visible activity log entry."
29063
29156
  }
29064
29157
  },
29065
29158
  required: ["cardId", "agentIdentifier", "agentName"]
@@ -29941,6 +30034,53 @@ var TOOLS = {
29941
30034
  },
29942
30035
  required: []
29943
30036
  }
30037
+ },
30038
+ harmony_purge_memories: {
30039
+ 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.",
30040
+ inputSchema: {
30041
+ type: "object",
30042
+ properties: {
30043
+ workspaceId: {
30044
+ type: "string",
30045
+ description: "Workspace ID (optional if context set)"
30046
+ },
30047
+ projectId: {
30048
+ type: "string",
30049
+ description: "Project ID (required — purge is project-scoped). Falls back to active project context."
30050
+ },
30051
+ dryRun: {
30052
+ type: "boolean",
30053
+ description: "Preview what would be deleted without executing (default: true)"
30054
+ },
30055
+ tier: {
30056
+ type: "string",
30057
+ enum: ["draft", "episode", "reference"],
30058
+ description: 'Filter by memory tier (e.g. "draft")'
30059
+ },
30060
+ scope: {
30061
+ type: "string",
30062
+ description: 'Filter by scope (e.g. "private", "project", "workspace")'
30063
+ },
30064
+ type: {
30065
+ type: "string",
30066
+ description: 'Filter by entity type (e.g. "error", "pattern", "lesson", "decision")'
30067
+ },
30068
+ olderThanDays: {
30069
+ type: "number",
30070
+ description: "Only include entities not accessed in at least this many days"
30071
+ },
30072
+ maxConfidence: {
30073
+ type: "number",
30074
+ description: "Only include entities with confidence at or below this value (e.g. 0.3 for low-confidence junk)"
30075
+ },
30076
+ tags: {
30077
+ type: "array",
30078
+ items: { type: "string" },
30079
+ description: "Only include entities matching these tags"
30080
+ }
30081
+ },
30082
+ required: []
30083
+ }
29944
30084
  }
29945
30085
  };
29946
30086
  var RESOURCES = [
@@ -30570,12 +30710,20 @@ async function handleToolCall(name, args, deps) {
30570
30710
  const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
30571
30711
  const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
30572
30712
  const progressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
30573
- const callerRecentActions = args.recentActions;
30713
+ const callerActions = args.actions;
30714
+ const now = new Date().toISOString();
30715
+ const callerRecentActions = [
30716
+ ...args.recentActions || [],
30717
+ ...(callerActions || []).map((a) => ({
30718
+ action: a.description,
30719
+ ts: now
30720
+ }))
30721
+ ];
30574
30722
  const memSession = getMemorySession(cardId);
30575
30723
  let mergedRecentActions;
30576
30724
  if (memSession?.dirty) {
30577
- mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions || []);
30578
- } else if (callerRecentActions) {
30725
+ mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions);
30726
+ } else if (callerRecentActions.length > 0) {
30579
30727
  mergedRecentActions = callerRecentActions;
30580
30728
  }
30581
30729
  const result = await client3.updateAgentProgress(cardId, {
@@ -31485,6 +31633,42 @@ async function handleToolCall(name, args, deps) {
31485
31633
  healthReport: report.healthReport
31486
31634
  };
31487
31635
  }
31636
+ case "harmony_purge_memories": {
31637
+ const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
31638
+ if (!workspaceId) {
31639
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
31640
+ }
31641
+ const projectId = args.projectId || deps.getActiveProjectId();
31642
+ if (!projectId) {
31643
+ throw new Error("No project specified. Purge requires a project scope. Use harmony_set_project_context or provide projectId.");
31644
+ }
31645
+ const filters = {};
31646
+ if (args.tier)
31647
+ filters.tier = args.tier;
31648
+ if (args.scope)
31649
+ filters.scope = args.scope;
31650
+ if (args.type)
31651
+ filters.type = args.type;
31652
+ if (args.olderThanDays !== undefined)
31653
+ filters.olderThanDays = args.olderThanDays;
31654
+ if (args.maxConfidence !== undefined)
31655
+ filters.maxConfidence = args.maxConfidence;
31656
+ if (args.tags)
31657
+ filters.tags = args.tags;
31658
+ const report = await purgeMemories(client3, workspaceId, projectId, {
31659
+ dryRun: args.dryRun,
31660
+ filters
31661
+ });
31662
+ return {
31663
+ success: report.success,
31664
+ dryRun: report.dryRun,
31665
+ matched: report.matched,
31666
+ purged: report.purged,
31667
+ items: report.items,
31668
+ errors: report.errors,
31669
+ message: report.dryRun ? `Found ${report.matched} entities matching filters. Run with dryRun=false to delete.` : `Purged ${report.purged} of ${report.matched} matching entities.`
31670
+ };
31671
+ }
31488
31672
  default:
31489
31673
  throw new Error(`Unknown tool: ${name}`);
31490
31674
  }
@@ -31522,15 +31706,26 @@ class HarmonyMCPServer {
31522
31706
  const cv = this.server.getClientVersion();
31523
31707
  return cv ? { name: cv.name, version: cv.version } : null;
31524
31708
  });
31709
+ let exitCode = 0;
31525
31710
  const handleShutdown = async () => {
31526
31711
  try {
31527
31712
  await shutdownAllSessions();
31528
31713
  } catch {}
31529
31714
  destroyAutoSession();
31530
- process.exit(0);
31715
+ process.exit(exitCode);
31531
31716
  };
31532
31717
  process.on("SIGINT", handleShutdown);
31533
31718
  process.on("SIGTERM", handleShutdown);
31719
+ process.on("uncaughtException", (err) => {
31720
+ console.error("MCP server uncaught exception:", err);
31721
+ exitCode = 1;
31722
+ handleShutdown();
31723
+ });
31724
+ process.on("unhandledRejection", (reason) => {
31725
+ console.error("MCP server unhandled rejection:", reason);
31726
+ exitCode = 1;
31727
+ handleShutdown();
31728
+ });
31534
31729
  try {
31535
31730
  if (isConfigured()) {
31536
31731
  const workspaceId = getActiveWorkspaceId();
package/dist/index.js CHANGED
@@ -26225,6 +26225,85 @@ function generateHealthReport(report) {
26225
26225
  return lines.join(`
26226
26226
  `);
26227
26227
  }
26228
+ async function purgeMemories(client3, workspaceId, projectId, options) {
26229
+ const dryRun = options.dryRun !== false;
26230
+ const { filters } = options;
26231
+ const hasFilter = filters.tier || filters.scope || filters.type || filters.olderThanDays !== undefined || filters.maxConfidence !== undefined || filters.tags && filters.tags.length > 0;
26232
+ if (!hasFilter) {
26233
+ throw new Error("At least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags) is required.");
26234
+ }
26235
+ const allMatches = [];
26236
+ let offset = 0;
26237
+ const pageSize = 100;
26238
+ const now = Date.now();
26239
+ while (true) {
26240
+ const result = await client3.listMemoryEntities({
26241
+ workspace_id: workspaceId,
26242
+ project_id: projectId,
26243
+ type: filters.type,
26244
+ scope: filters.scope,
26245
+ tags: filters.tags,
26246
+ limit: pageSize,
26247
+ offset
26248
+ });
26249
+ const entities = result.entities || [];
26250
+ if (entities.length === 0)
26251
+ break;
26252
+ for (const entity of entities) {
26253
+ if (filters.tier && entity.memory_tier !== filters.tier)
26254
+ continue;
26255
+ if (filters.maxConfidence !== undefined && entity.confidence > filters.maxConfidence)
26256
+ continue;
26257
+ if (filters.olderThanDays !== undefined) {
26258
+ const ref = entity.last_accessed_at || entity.created_at;
26259
+ const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY;
26260
+ if (ageDays < filters.olderThanDays)
26261
+ continue;
26262
+ }
26263
+ allMatches.push(entity);
26264
+ }
26265
+ if (entities.length < pageSize)
26266
+ break;
26267
+ offset += pageSize;
26268
+ }
26269
+ const items = allMatches.map((e) => ({
26270
+ id: e.id,
26271
+ title: e.title,
26272
+ type: e.type,
26273
+ tier: e.memory_tier,
26274
+ confidence: e.confidence,
26275
+ ageDays: Math.round((now - new Date(e.last_accessed_at || e.created_at).getTime()) / MS_PER_DAY)
26276
+ }));
26277
+ const errors3 = [];
26278
+ let purged = 0;
26279
+ if (!dryRun) {
26280
+ for (let i = 0;i < allMatches.length; i += CONCURRENCY_LIMIT) {
26281
+ const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT);
26282
+ const results = await Promise.allSettled(batch.map((e) => client3.deleteMemoryEntity(e.id)));
26283
+ for (let j = 0;j < results.length; j++) {
26284
+ if (results[j].status === "fulfilled") {
26285
+ purged++;
26286
+ } else {
26287
+ errors3.push({
26288
+ entityId: batch[j].id,
26289
+ message: results[j].status === "rejected" ? String(results[j].reason) : "Unknown error"
26290
+ });
26291
+ }
26292
+ }
26293
+ }
26294
+ }
26295
+ return {
26296
+ success: errors3.length === 0,
26297
+ dryRun,
26298
+ timestamp: new Date().toISOString(),
26299
+ workspace: { id: workspaceId, projectId },
26300
+ filters,
26301
+ matched: allMatches.length,
26302
+ purged: dryRun ? 0 : purged,
26303
+ items,
26304
+ errors: errors3
26305
+ };
26306
+ }
26228
26307
 
26229
26308
  // src/onboard.ts
26230
26309
  async function onboardNewUser(params) {
@@ -26820,6 +26899,20 @@ var TOOLS = {
26820
26899
  estimatedMinutesRemaining: {
26821
26900
  type: "number",
26822
26901
  description: "Updated time estimate"
26902
+ },
26903
+ actions: {
26904
+ type: "array",
26905
+ items: {
26906
+ type: "object",
26907
+ properties: {
26908
+ description: {
26909
+ type: "string",
26910
+ description: "What was done, e.g. 'Edited CardDetailSheet.tsx — added done toggle'"
26911
+ }
26912
+ },
26913
+ required: ["description"]
26914
+ },
26915
+ description: "Actions performed since last update. Each becomes a visible activity log entry."
26823
26916
  }
26824
26917
  },
26825
26918
  required: ["cardId", "agentIdentifier", "agentName"]
@@ -27701,6 +27794,53 @@ var TOOLS = {
27701
27794
  },
27702
27795
  required: []
27703
27796
  }
27797
+ },
27798
+ harmony_purge_memories: {
27799
+ 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.",
27800
+ inputSchema: {
27801
+ type: "object",
27802
+ properties: {
27803
+ workspaceId: {
27804
+ type: "string",
27805
+ description: "Workspace ID (optional if context set)"
27806
+ },
27807
+ projectId: {
27808
+ type: "string",
27809
+ description: "Project ID (required — purge is project-scoped). Falls back to active project context."
27810
+ },
27811
+ dryRun: {
27812
+ type: "boolean",
27813
+ description: "Preview what would be deleted without executing (default: true)"
27814
+ },
27815
+ tier: {
27816
+ type: "string",
27817
+ enum: ["draft", "episode", "reference"],
27818
+ description: 'Filter by memory tier (e.g. "draft")'
27819
+ },
27820
+ scope: {
27821
+ type: "string",
27822
+ description: 'Filter by scope (e.g. "private", "project", "workspace")'
27823
+ },
27824
+ type: {
27825
+ type: "string",
27826
+ description: 'Filter by entity type (e.g. "error", "pattern", "lesson", "decision")'
27827
+ },
27828
+ olderThanDays: {
27829
+ type: "number",
27830
+ description: "Only include entities not accessed in at least this many days"
27831
+ },
27832
+ maxConfidence: {
27833
+ type: "number",
27834
+ description: "Only include entities with confidence at or below this value (e.g. 0.3 for low-confidence junk)"
27835
+ },
27836
+ tags: {
27837
+ type: "array",
27838
+ items: { type: "string" },
27839
+ description: "Only include entities matching these tags"
27840
+ }
27841
+ },
27842
+ required: []
27843
+ }
27704
27844
  }
27705
27845
  };
27706
27846
  var RESOURCES = [
@@ -28330,12 +28470,20 @@ async function handleToolCall(name, args, deps) {
28330
28470
  const agentIdentifier = exports_external.string().min(1).max(100).parse(args.agentIdentifier);
28331
28471
  const agentName = exports_external.string().min(1).max(100).parse(args.agentName);
28332
28472
  const progressPercent = args.progressPercent !== undefined ? exports_external.number().min(0).max(100).parse(args.progressPercent) : undefined;
28333
- const callerRecentActions = args.recentActions;
28473
+ const callerActions = args.actions;
28474
+ const now = new Date().toISOString();
28475
+ const callerRecentActions = [
28476
+ ...args.recentActions || [],
28477
+ ...(callerActions || []).map((a) => ({
28478
+ action: a.description,
28479
+ ts: now
28480
+ }))
28481
+ ];
28334
28482
  const memSession = getMemorySession(cardId);
28335
28483
  let mergedRecentActions;
28336
28484
  if (memSession?.dirty) {
28337
- mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions || []);
28338
- } else if (callerRecentActions) {
28485
+ mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions);
28486
+ } else if (callerRecentActions.length > 0) {
28339
28487
  mergedRecentActions = callerRecentActions;
28340
28488
  }
28341
28489
  const result = await client3.updateAgentProgress(cardId, {
@@ -29245,6 +29393,42 @@ async function handleToolCall(name, args, deps) {
29245
29393
  healthReport: report.healthReport
29246
29394
  };
29247
29395
  }
29396
+ case "harmony_purge_memories": {
29397
+ const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
29398
+ if (!workspaceId) {
29399
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
29400
+ }
29401
+ const projectId = args.projectId || deps.getActiveProjectId();
29402
+ if (!projectId) {
29403
+ throw new Error("No project specified. Purge requires a project scope. Use harmony_set_project_context or provide projectId.");
29404
+ }
29405
+ const filters = {};
29406
+ if (args.tier)
29407
+ filters.tier = args.tier;
29408
+ if (args.scope)
29409
+ filters.scope = args.scope;
29410
+ if (args.type)
29411
+ filters.type = args.type;
29412
+ if (args.olderThanDays !== undefined)
29413
+ filters.olderThanDays = args.olderThanDays;
29414
+ if (args.maxConfidence !== undefined)
29415
+ filters.maxConfidence = args.maxConfidence;
29416
+ if (args.tags)
29417
+ filters.tags = args.tags;
29418
+ const report = await purgeMemories(client3, workspaceId, projectId, {
29419
+ dryRun: args.dryRun,
29420
+ filters
29421
+ });
29422
+ return {
29423
+ success: report.success,
29424
+ dryRun: report.dryRun,
29425
+ matched: report.matched,
29426
+ purged: report.purged,
29427
+ items: report.items,
29428
+ errors: report.errors,
29429
+ message: report.dryRun ? `Found ${report.matched} entities matching filters. Run with dryRun=false to delete.` : `Purged ${report.purged} of ${report.matched} matching entities.`
29430
+ };
29431
+ }
29248
29432
  default:
29249
29433
  throw new Error(`Unknown tool: ${name}`);
29250
29434
  }
@@ -29282,15 +29466,26 @@ class HarmonyMCPServer {
29282
29466
  const cv = this.server.getClientVersion();
29283
29467
  return cv ? { name: cv.name, version: cv.version } : null;
29284
29468
  });
29469
+ let exitCode = 0;
29285
29470
  const handleShutdown = async () => {
29286
29471
  try {
29287
29472
  await shutdownAllSessions();
29288
29473
  } catch {}
29289
29474
  destroyAutoSession();
29290
- process.exit(0);
29475
+ process.exit(exitCode);
29291
29476
  };
29292
29477
  process.on("SIGINT", handleShutdown);
29293
29478
  process.on("SIGTERM", handleShutdown);
29479
+ process.on("uncaughtException", (err) => {
29480
+ console.error("MCP server uncaught exception:", err);
29481
+ exitCode = 1;
29482
+ handleShutdown();
29483
+ });
29484
+ process.on("unhandledRejection", (reason) => {
29485
+ console.error("MCP server unhandled rejection:", reason);
29486
+ exitCode = 1;
29487
+ handleShutdown();
29488
+ });
29294
29489
  try {
29295
29490
  if (isConfigured()) {
29296
29491
  const workspaceId = getActiveWorkspaceId();