@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 +199 -4
- package/dist/index.js +199 -4
- package/dist/lib/api-client.js +1 -433
- package/dist/lib/config.js +0 -18
- package/package.json +9 -5
- package/src/api-client.ts +6 -0
- package/src/memory-cleanup.ts +158 -0
- package/src/server.ts +140 -7
- package/dist/lib/active-learning.js +0 -974
- package/dist/lib/auto-session.js +0 -195
- package/dist/lib/cli.js +0 -34964
- package/dist/lib/consolidation.js +0 -388
- package/dist/lib/context-assembly.js +0 -1311
- package/dist/lib/graph-expansion.js +0 -147
- package/dist/lib/http.js +0 -1962
- package/dist/lib/index.js +0 -29527
- package/dist/lib/lifecycle-maintenance.js +0 -672
- package/dist/lib/memory-cleanup.js +0 -1361
- package/dist/lib/onboard.js +0 -2592
- package/dist/lib/prompt-builder.js +0 -481
- package/dist/lib/remote.js +0 -31756
- package/dist/lib/server.js +0 -29524
- package/dist/lib/skills.js +0 -776
- package/dist/lib/tui/agents.js +0 -137
- package/dist/lib/tui/docs.js +0 -1647
- package/dist/lib/tui/setup.js +0 -5828
- package/dist/lib/tui/theme.js +0 -192
- package/dist/lib/tui/writer.js +0 -1173
package/src/memory-cleanup.ts
CHANGED
|
@@ -49,6 +49,43 @@ export interface CleanupOptions {
|
|
|
49
49
|
orphanAgeDays?: number;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Purge types
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
export interface PurgeFilters {
|
|
57
|
+
tier?: "draft" | "episode" | "reference";
|
|
58
|
+
scope?: string;
|
|
59
|
+
type?: string;
|
|
60
|
+
olderThanDays?: number;
|
|
61
|
+
maxConfidence?: number;
|
|
62
|
+
tags?: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface PurgeOptions {
|
|
66
|
+
dryRun?: boolean;
|
|
67
|
+
filters: PurgeFilters;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PurgeReport {
|
|
71
|
+
success: boolean;
|
|
72
|
+
dryRun: boolean;
|
|
73
|
+
timestamp: string;
|
|
74
|
+
workspace: { id: string; projectId: string };
|
|
75
|
+
filters: PurgeFilters;
|
|
76
|
+
matched: number;
|
|
77
|
+
purged: number;
|
|
78
|
+
items: Array<{
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
type: string;
|
|
82
|
+
tier: string;
|
|
83
|
+
confidence: number;
|
|
84
|
+
ageDays: number;
|
|
85
|
+
}>;
|
|
86
|
+
errors: Array<{ entityId: string; message: string }>;
|
|
87
|
+
}
|
|
88
|
+
|
|
52
89
|
interface PruneStepResult {
|
|
53
90
|
staleDraftsFound: number;
|
|
54
91
|
pruned: number;
|
|
@@ -654,3 +691,124 @@ function generateHealthReport(report: CleanupReport): string {
|
|
|
654
691
|
|
|
655
692
|
return lines.join("\n");
|
|
656
693
|
}
|
|
694
|
+
|
|
695
|
+
// ---------------------------------------------------------------------------
|
|
696
|
+
// Purge — filtered bulk deletion
|
|
697
|
+
// ---------------------------------------------------------------------------
|
|
698
|
+
|
|
699
|
+
export async function purgeMemories(
|
|
700
|
+
client: HarmonyApiClient,
|
|
701
|
+
workspaceId: string,
|
|
702
|
+
projectId: string,
|
|
703
|
+
options: PurgeOptions,
|
|
704
|
+
): Promise<PurgeReport> {
|
|
705
|
+
const dryRun = options.dryRun !== false;
|
|
706
|
+
const { filters } = options;
|
|
707
|
+
|
|
708
|
+
// Safety: require at least one narrowing filter
|
|
709
|
+
const hasFilter =
|
|
710
|
+
filters.tier ||
|
|
711
|
+
filters.scope ||
|
|
712
|
+
filters.type ||
|
|
713
|
+
filters.olderThanDays !== undefined ||
|
|
714
|
+
filters.maxConfidence !== undefined ||
|
|
715
|
+
(filters.tags && filters.tags.length > 0);
|
|
716
|
+
|
|
717
|
+
if (!hasFilter) {
|
|
718
|
+
throw new Error(
|
|
719
|
+
"At least one narrowing filter (tier, scope, type, olderThanDays, maxConfidence, tags) is required.",
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Paginate through all matching entities
|
|
724
|
+
const allMatches: MemoryEntity[] = [];
|
|
725
|
+
let offset = 0;
|
|
726
|
+
const pageSize = 100;
|
|
727
|
+
const now = Date.now();
|
|
728
|
+
|
|
729
|
+
while (true) {
|
|
730
|
+
const result = await client.listMemoryEntities({
|
|
731
|
+
workspace_id: workspaceId,
|
|
732
|
+
project_id: projectId,
|
|
733
|
+
type: filters.type,
|
|
734
|
+
scope: filters.scope,
|
|
735
|
+
tags: filters.tags,
|
|
736
|
+
limit: pageSize,
|
|
737
|
+
offset,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
const entities = (result.entities || []) as MemoryEntity[];
|
|
741
|
+
if (entities.length === 0) break;
|
|
742
|
+
|
|
743
|
+
// Client-side filtering for fields the API doesn't support natively
|
|
744
|
+
for (const entity of entities) {
|
|
745
|
+
if (filters.tier && entity.memory_tier !== filters.tier) continue;
|
|
746
|
+
if (
|
|
747
|
+
filters.maxConfidence !== undefined &&
|
|
748
|
+
entity.confidence > filters.maxConfidence
|
|
749
|
+
)
|
|
750
|
+
continue;
|
|
751
|
+
if (filters.olderThanDays !== undefined) {
|
|
752
|
+
const ref = entity.last_accessed_at || entity.created_at;
|
|
753
|
+
const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY;
|
|
754
|
+
if (ageDays < filters.olderThanDays) continue;
|
|
755
|
+
}
|
|
756
|
+
allMatches.push(entity);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (entities.length < pageSize) break;
|
|
760
|
+
offset += pageSize;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Build preview items
|
|
764
|
+
const items = allMatches.map((e) => ({
|
|
765
|
+
id: e.id,
|
|
766
|
+
title: e.title,
|
|
767
|
+
type: e.type,
|
|
768
|
+
tier: e.memory_tier,
|
|
769
|
+
confidence: e.confidence,
|
|
770
|
+
ageDays: Math.round(
|
|
771
|
+
(now - new Date(e.last_accessed_at || e.created_at).getTime()) /
|
|
772
|
+
MS_PER_DAY,
|
|
773
|
+
),
|
|
774
|
+
}));
|
|
775
|
+
|
|
776
|
+
// Execute deletions if not dry-run
|
|
777
|
+
const errors: Array<{ entityId: string; message: string }> = [];
|
|
778
|
+
let purged = 0;
|
|
779
|
+
|
|
780
|
+
if (!dryRun) {
|
|
781
|
+
// Delete in batches to avoid overwhelming the API
|
|
782
|
+
for (let i = 0; i < allMatches.length; i += CONCURRENCY_LIMIT) {
|
|
783
|
+
const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT);
|
|
784
|
+
const results = await Promise.allSettled(
|
|
785
|
+
batch.map((e) => client.deleteMemoryEntity(e.id)),
|
|
786
|
+
);
|
|
787
|
+
for (let j = 0; j < results.length; j++) {
|
|
788
|
+
if (results[j].status === "fulfilled") {
|
|
789
|
+
purged++;
|
|
790
|
+
} else {
|
|
791
|
+
errors.push({
|
|
792
|
+
entityId: batch[j].id,
|
|
793
|
+
message:
|
|
794
|
+
results[j].status === "rejected"
|
|
795
|
+
? String((results[j] as PromiseRejectedResult).reason)
|
|
796
|
+
: "Unknown error",
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
success: errors.length === 0,
|
|
805
|
+
dryRun,
|
|
806
|
+
timestamp: new Date().toISOString(),
|
|
807
|
+
workspace: { id: workspaceId, projectId },
|
|
808
|
+
filters,
|
|
809
|
+
matched: allMatches.length,
|
|
810
|
+
purged: dryRun ? 0 : purged,
|
|
811
|
+
items,
|
|
812
|
+
errors,
|
|
813
|
+
};
|
|
814
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
} from "./context-assembly.js";
|
|
59
59
|
import { autoExpandGraph } from "./graph-expansion.js";
|
|
60
60
|
import { runLifecycleMaintenance } from "./lifecycle-maintenance.js";
|
|
61
|
-
import { runMemoryCleanup } from "./memory-cleanup.js";
|
|
61
|
+
import { runMemoryCleanup, purgeMemories, type PurgeFilters } from "./memory-cleanup.js";
|
|
62
62
|
import { onboardNewUser } from "./onboard.js";
|
|
63
63
|
import type { PromptVariant } from "./prompt-builder.js";
|
|
64
64
|
|
|
@@ -721,6 +721,22 @@ const TOOLS = {
|
|
|
721
721
|
type: "number",
|
|
722
722
|
description: "Updated time estimate",
|
|
723
723
|
},
|
|
724
|
+
actions: {
|
|
725
|
+
type: "array",
|
|
726
|
+
items: {
|
|
727
|
+
type: "object",
|
|
728
|
+
properties: {
|
|
729
|
+
description: {
|
|
730
|
+
type: "string",
|
|
731
|
+
description:
|
|
732
|
+
"What was done, e.g. 'Edited CardDetailSheet.tsx — added done toggle'",
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
required: ["description"],
|
|
736
|
+
},
|
|
737
|
+
description:
|
|
738
|
+
"Actions performed since last update. Each becomes a visible activity log entry.",
|
|
739
|
+
},
|
|
724
740
|
},
|
|
725
741
|
required: ["cardId", "agentIdentifier", "agentName"],
|
|
726
742
|
},
|
|
@@ -1683,6 +1699,60 @@ const TOOLS = {
|
|
|
1683
1699
|
required: [],
|
|
1684
1700
|
},
|
|
1685
1701
|
},
|
|
1702
|
+
harmony_purge_memories: {
|
|
1703
|
+
description:
|
|
1704
|
+
"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.",
|
|
1705
|
+
inputSchema: {
|
|
1706
|
+
type: "object",
|
|
1707
|
+
properties: {
|
|
1708
|
+
workspaceId: {
|
|
1709
|
+
type: "string",
|
|
1710
|
+
description: "Workspace ID (optional if context set)",
|
|
1711
|
+
},
|
|
1712
|
+
projectId: {
|
|
1713
|
+
type: "string",
|
|
1714
|
+
description:
|
|
1715
|
+
"Project ID (required — purge is project-scoped). Falls back to active project context.",
|
|
1716
|
+
},
|
|
1717
|
+
dryRun: {
|
|
1718
|
+
type: "boolean",
|
|
1719
|
+
description:
|
|
1720
|
+
"Preview what would be deleted without executing (default: true)",
|
|
1721
|
+
},
|
|
1722
|
+
tier: {
|
|
1723
|
+
type: "string",
|
|
1724
|
+
enum: ["draft", "episode", "reference"],
|
|
1725
|
+
description: 'Filter by memory tier (e.g. "draft")',
|
|
1726
|
+
},
|
|
1727
|
+
scope: {
|
|
1728
|
+
type: "string",
|
|
1729
|
+
description:
|
|
1730
|
+
'Filter by scope (e.g. "private", "project", "workspace")',
|
|
1731
|
+
},
|
|
1732
|
+
type: {
|
|
1733
|
+
type: "string",
|
|
1734
|
+
description:
|
|
1735
|
+
'Filter by entity type (e.g. "error", "pattern", "lesson", "decision")',
|
|
1736
|
+
},
|
|
1737
|
+
olderThanDays: {
|
|
1738
|
+
type: "number",
|
|
1739
|
+
description:
|
|
1740
|
+
"Only include entities not accessed in at least this many days",
|
|
1741
|
+
},
|
|
1742
|
+
maxConfidence: {
|
|
1743
|
+
type: "number",
|
|
1744
|
+
description:
|
|
1745
|
+
"Only include entities with confidence at or below this value (e.g. 0.3 for low-confidence junk)",
|
|
1746
|
+
},
|
|
1747
|
+
tags: {
|
|
1748
|
+
type: "array",
|
|
1749
|
+
items: { type: "string" },
|
|
1750
|
+
description: "Only include entities matching these tags",
|
|
1751
|
+
},
|
|
1752
|
+
},
|
|
1753
|
+
required: [],
|
|
1754
|
+
},
|
|
1755
|
+
},
|
|
1686
1756
|
};
|
|
1687
1757
|
|
|
1688
1758
|
// Resource URIs
|
|
@@ -2643,18 +2713,26 @@ async function handleToolCall(
|
|
|
2643
2713
|
args.progressPercent !== undefined
|
|
2644
2714
|
? z.number().min(0).max(100).parse(args.progressPercent)
|
|
2645
2715
|
: undefined;
|
|
2646
|
-
//
|
|
2647
|
-
const
|
|
2648
|
-
| {
|
|
2716
|
+
// Convert actions parameter to recentActions format and merge with memory actions
|
|
2717
|
+
const callerActions = args.actions as
|
|
2718
|
+
| { description: string }[]
|
|
2649
2719
|
| undefined;
|
|
2720
|
+
const now = new Date().toISOString();
|
|
2721
|
+
const callerRecentActions: { action: string; ts: string }[] = [
|
|
2722
|
+
...((args.recentActions as { action: string; ts: string }[]) || []),
|
|
2723
|
+
...(callerActions || []).map((a) => ({
|
|
2724
|
+
action: a.description,
|
|
2725
|
+
ts: now,
|
|
2726
|
+
})),
|
|
2727
|
+
];
|
|
2650
2728
|
const memSession = getMemorySession(cardId);
|
|
2651
2729
|
let mergedRecentActions: { action: string; ts: string }[] | undefined;
|
|
2652
2730
|
if (memSession?.dirty) {
|
|
2653
2731
|
mergedRecentActions = mergeMemoryActionsInto(
|
|
2654
2732
|
cardId,
|
|
2655
|
-
callerRecentActions
|
|
2733
|
+
callerRecentActions,
|
|
2656
2734
|
);
|
|
2657
|
-
} else if (callerRecentActions) {
|
|
2735
|
+
} else if (callerRecentActions.length > 0) {
|
|
2658
2736
|
mergedRecentActions = callerRecentActions;
|
|
2659
2737
|
}
|
|
2660
2738
|
|
|
@@ -4056,6 +4134,50 @@ async function handleToolCall(
|
|
|
4056
4134
|
};
|
|
4057
4135
|
}
|
|
4058
4136
|
|
|
4137
|
+
case "harmony_purge_memories": {
|
|
4138
|
+
const workspaceId =
|
|
4139
|
+
(args.workspaceId as string) || deps.getActiveWorkspaceId();
|
|
4140
|
+
if (!workspaceId) {
|
|
4141
|
+
throw new Error(
|
|
4142
|
+
"No workspace specified. Use harmony_set_workspace_context or provide workspaceId.",
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
const projectId =
|
|
4146
|
+
(args.projectId as string) || deps.getActiveProjectId();
|
|
4147
|
+
if (!projectId) {
|
|
4148
|
+
throw new Error(
|
|
4149
|
+
"No project specified. Purge requires a project scope. Use harmony_set_project_context or provide projectId.",
|
|
4150
|
+
);
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
const filters: PurgeFilters = {};
|
|
4154
|
+
if (args.tier) filters.tier = args.tier as PurgeFilters["tier"];
|
|
4155
|
+
if (args.scope) filters.scope = args.scope as string;
|
|
4156
|
+
if (args.type) filters.type = args.type as string;
|
|
4157
|
+
if (args.olderThanDays !== undefined)
|
|
4158
|
+
filters.olderThanDays = args.olderThanDays as number;
|
|
4159
|
+
if (args.maxConfidence !== undefined)
|
|
4160
|
+
filters.maxConfidence = args.maxConfidence as number;
|
|
4161
|
+
if (args.tags) filters.tags = args.tags as string[];
|
|
4162
|
+
|
|
4163
|
+
const report = await purgeMemories(client, workspaceId, projectId, {
|
|
4164
|
+
dryRun: args.dryRun as boolean | undefined,
|
|
4165
|
+
filters,
|
|
4166
|
+
});
|
|
4167
|
+
|
|
4168
|
+
return {
|
|
4169
|
+
success: report.success,
|
|
4170
|
+
dryRun: report.dryRun,
|
|
4171
|
+
matched: report.matched,
|
|
4172
|
+
purged: report.purged,
|
|
4173
|
+
items: report.items,
|
|
4174
|
+
errors: report.errors,
|
|
4175
|
+
message: report.dryRun
|
|
4176
|
+
? `Found ${report.matched} entities matching filters. Run with dryRun=false to delete.`
|
|
4177
|
+
: `Purged ${report.purged} of ${report.matched} matching entities.`,
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4059
4181
|
default:
|
|
4060
4182
|
throw new Error(`Unknown tool: ${name}`);
|
|
4061
4183
|
}
|
|
@@ -4109,6 +4231,7 @@ export class HarmonyMCPServer {
|
|
|
4109
4231
|
);
|
|
4110
4232
|
|
|
4111
4233
|
// Graceful shutdown: end all auto-sessions
|
|
4234
|
+
let exitCode = 0;
|
|
4112
4235
|
const handleShutdown = async () => {
|
|
4113
4236
|
try {
|
|
4114
4237
|
await shutdownAllSessions();
|
|
@@ -4116,10 +4239,20 @@ export class HarmonyMCPServer {
|
|
|
4116
4239
|
// Best-effort
|
|
4117
4240
|
}
|
|
4118
4241
|
destroyAutoSession();
|
|
4119
|
-
process.exit(
|
|
4242
|
+
process.exit(exitCode);
|
|
4120
4243
|
};
|
|
4121
4244
|
process.on("SIGINT", handleShutdown);
|
|
4122
4245
|
process.on("SIGTERM", handleShutdown);
|
|
4246
|
+
process.on("uncaughtException", (err) => {
|
|
4247
|
+
console.error("MCP server uncaught exception:", err);
|
|
4248
|
+
exitCode = 1;
|
|
4249
|
+
handleShutdown();
|
|
4250
|
+
});
|
|
4251
|
+
process.on("unhandledRejection", (reason) => {
|
|
4252
|
+
console.error("MCP server unhandled rejection:", reason);
|
|
4253
|
+
exitCode = 1;
|
|
4254
|
+
handleShutdown();
|
|
4255
|
+
});
|
|
4123
4256
|
|
|
4124
4257
|
// Attempt startup sync (non-blocking, best-effort)
|
|
4125
4258
|
try {
|