@gethmy/mcp 2.1.3 → 2.2.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 +159 -16
- package/dist/index.js +152 -9
- package/dist/lib/auto-session.js +8 -3
- package/dist/lib/cli.js +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/http.js +1 -0
- package/dist/lib/prompt-builder.js +1 -0
- package/dist/lib/remote.js +2 -2
- package/dist/lib/server.js +179 -4
- package/dist/lib/skills.js +3 -3
- package/dist/lib/tui/setup.js +3 -3
- package/package.json +1 -1
- package/src/auto-session.ts +10 -3
- package/src/cli.ts +1 -1
- package/src/config.ts +1 -1
- package/src/http.ts +1 -0
- package/src/prompt-builder.ts +3 -0
- package/src/remote.ts +2 -2
- package/src/server.ts +244 -4
- package/src/skills.ts +3 -3
- package/src/tui/setup.ts +3 -3
package/src/server.ts
CHANGED
|
@@ -85,6 +85,155 @@ export interface ToolDeps {
|
|
|
85
85
|
resetClient: () => void;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// --- Memory Session Tracking ---
|
|
89
|
+
// Tracks memory operations per card session for visibility in AgentCardFooter
|
|
90
|
+
|
|
91
|
+
interface MemorySessionState {
|
|
92
|
+
cardId: string;
|
|
93
|
+
agentIdentifier: string;
|
|
94
|
+
agentName: string;
|
|
95
|
+
memoryReadCount: number;
|
|
96
|
+
pendingActions: { action: string; ts: string }[];
|
|
97
|
+
allActions: { action: string; ts: string }[];
|
|
98
|
+
dirty: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const memorySessions = new Map<string, MemorySessionState>();
|
|
102
|
+
|
|
103
|
+
function initMemorySession(
|
|
104
|
+
cardId: string,
|
|
105
|
+
agentIdentifier: string,
|
|
106
|
+
agentName: string,
|
|
107
|
+
): void {
|
|
108
|
+
memorySessions.set(cardId, {
|
|
109
|
+
cardId,
|
|
110
|
+
agentIdentifier,
|
|
111
|
+
agentName,
|
|
112
|
+
memoryReadCount: 0,
|
|
113
|
+
pendingActions: [],
|
|
114
|
+
allActions: [],
|
|
115
|
+
dirty: false,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getMemorySession(cardId: string): MemorySessionState | undefined {
|
|
120
|
+
return memorySessions.get(cardId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function appendMemoryAction(cardId: string, action: string): void {
|
|
124
|
+
const session = memorySessions.get(cardId);
|
|
125
|
+
if (!session) return;
|
|
126
|
+
const truncated = action.length > 512 ? action.slice(0, 509) + "..." : action;
|
|
127
|
+
const entry = { action: truncated, ts: new Date().toISOString() };
|
|
128
|
+
session.pendingActions.push(entry);
|
|
129
|
+
session.dirty = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function incrementMemoryReads(cardId: string): void {
|
|
133
|
+
const session = memorySessions.get(cardId);
|
|
134
|
+
if (!session) return;
|
|
135
|
+
session.memoryReadCount++;
|
|
136
|
+
session.dirty = true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Flush pending memory actions to the backend via updateAgentProgress.
|
|
141
|
+
* Fire-and-forget: errors are logged but never thrown.
|
|
142
|
+
*/
|
|
143
|
+
async function flushMemoryActions(
|
|
144
|
+
client: HarmonyApiClient,
|
|
145
|
+
cardId: string,
|
|
146
|
+
): Promise<void> {
|
|
147
|
+
const session = memorySessions.get(cardId);
|
|
148
|
+
if (!session || !session.dirty) return;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Batch reads into a single summary line
|
|
152
|
+
if (session.memoryReadCount > 0) {
|
|
153
|
+
session.allActions.push({
|
|
154
|
+
action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
|
|
155
|
+
ts: new Date().toISOString(),
|
|
156
|
+
});
|
|
157
|
+
session.memoryReadCount = 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Move pending writes to allActions
|
|
161
|
+
if (session.pendingActions.length > 0) {
|
|
162
|
+
session.allActions.push(...session.pendingActions);
|
|
163
|
+
session.pendingActions = [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Trim to max 10 (drop oldest)
|
|
167
|
+
if (session.allActions.length > 10) {
|
|
168
|
+
session.allActions = session.allActions.slice(-10);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await client.updateAgentProgress(cardId, {
|
|
172
|
+
agentIdentifier: session.agentIdentifier,
|
|
173
|
+
agentName: session.agentName,
|
|
174
|
+
recentActions: session.allActions,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
session.dirty = false;
|
|
178
|
+
} catch (err) {
|
|
179
|
+
// Fire-and-forget: log but don't propagate
|
|
180
|
+
console.error("[memory-session] flush failed:", err);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Merge memory actions into an existing recentActions array from a progress update.
|
|
186
|
+
* Returns the merged array (caller's actions first, then memory actions appended).
|
|
187
|
+
*/
|
|
188
|
+
function mergeMemoryActionsInto(
|
|
189
|
+
cardId: string,
|
|
190
|
+
callerActions: { action: string; ts: string }[],
|
|
191
|
+
): { action: string; ts: string }[] {
|
|
192
|
+
const session = memorySessions.get(cardId);
|
|
193
|
+
if (!session) return callerActions;
|
|
194
|
+
|
|
195
|
+
// Flush reads into allActions
|
|
196
|
+
if (session.memoryReadCount > 0) {
|
|
197
|
+
session.allActions.push({
|
|
198
|
+
action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
|
|
199
|
+
ts: new Date().toISOString(),
|
|
200
|
+
});
|
|
201
|
+
session.memoryReadCount = 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Move pending to allActions
|
|
205
|
+
if (session.pendingActions.length > 0) {
|
|
206
|
+
session.allActions.push(...session.pendingActions);
|
|
207
|
+
session.pendingActions = [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Merge: caller actions + memory actions, trim to 10
|
|
211
|
+
const merged = [...callerActions, ...session.allActions];
|
|
212
|
+
const trimmed = merged.length > 10 ? merged.slice(-10) : merged;
|
|
213
|
+
|
|
214
|
+
// Update allActions to the merged state
|
|
215
|
+
session.allActions = trimmed;
|
|
216
|
+
session.dirty = false;
|
|
217
|
+
|
|
218
|
+
return trimmed;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get the first (and typically only) active memory session.
|
|
223
|
+
* Memory tools don't carry a cardId, so we find the active session.
|
|
224
|
+
*/
|
|
225
|
+
function getActiveMemorySession(): MemorySessionState | undefined {
|
|
226
|
+
// Return the first active session (there should be at most one)
|
|
227
|
+
for (const session of memorySessions.values()) {
|
|
228
|
+
return session;
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function cleanupMemorySession(cardId: string): void {
|
|
234
|
+
memorySessions.delete(cardId);
|
|
235
|
+
}
|
|
236
|
+
|
|
88
237
|
// Tool definitions
|
|
89
238
|
const TOOLS = {
|
|
90
239
|
// Card operations
|
|
@@ -1838,7 +1987,7 @@ async function handleToolCall(
|
|
|
1838
1987
|
if (!unauthenticatedTools.includes(name) && !deps.isConfigured()) {
|
|
1839
1988
|
throw new Error(
|
|
1840
1989
|
'Not configured. Run "npx @gethmy/mcp setup" to set your API key.\n' +
|
|
1841
|
-
"You can generate an API key at https://gethmy.com → Settings → API Keys.\n" +
|
|
1990
|
+
"You can generate an API key at https://app.gethmy.com → Settings → API Keys.\n" +
|
|
1842
1991
|
'Or use "harmony_onboard" to create an account and configure automatically.',
|
|
1843
1992
|
);
|
|
1844
1993
|
}
|
|
@@ -2267,7 +2416,10 @@ async function handleToolCall(
|
|
|
2267
2416
|
});
|
|
2268
2417
|
|
|
2269
2418
|
// Mark as explicit so auto-session won't interfere
|
|
2270
|
-
markExplicit(cardId);
|
|
2419
|
+
markExplicit(cardId, { agentIdentifier, agentName });
|
|
2420
|
+
|
|
2421
|
+
// Initialize memory session tracking for action visibility
|
|
2422
|
+
initMemorySession(cardId, agentIdentifier, agentName);
|
|
2271
2423
|
|
|
2272
2424
|
// Prefetch relevant context (non-blocking, best-effort)
|
|
2273
2425
|
let prefetchedMemoryIds: string[] = [];
|
|
@@ -2348,6 +2500,21 @@ async function handleToolCall(
|
|
|
2348
2500
|
args.progressPercent !== undefined
|
|
2349
2501
|
? z.number().min(0).max(100).parse(args.progressPercent)
|
|
2350
2502
|
: undefined;
|
|
2503
|
+
// Merge any pending memory actions into the progress update
|
|
2504
|
+
const callerRecentActions = args.recentActions as
|
|
2505
|
+
| { action: string; ts: string }[]
|
|
2506
|
+
| undefined;
|
|
2507
|
+
const memSession = getMemorySession(cardId);
|
|
2508
|
+
let mergedRecentActions: { action: string; ts: string }[] | undefined;
|
|
2509
|
+
if (memSession?.dirty) {
|
|
2510
|
+
mergedRecentActions = mergeMemoryActionsInto(
|
|
2511
|
+
cardId,
|
|
2512
|
+
callerRecentActions || [],
|
|
2513
|
+
);
|
|
2514
|
+
} else if (callerRecentActions) {
|
|
2515
|
+
mergedRecentActions = callerRecentActions;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2351
2518
|
const result = await client.updateAgentProgress(cardId, {
|
|
2352
2519
|
agentIdentifier,
|
|
2353
2520
|
agentName,
|
|
@@ -2363,6 +2530,7 @@ async function handleToolCall(
|
|
|
2363
2530
|
estimatedMinutesRemaining: args.estimatedMinutesRemaining as
|
|
2364
2531
|
| number
|
|
2365
2532
|
| undefined,
|
|
2533
|
+
...(mergedRecentActions && { recentActions: mergedRecentActions }),
|
|
2366
2534
|
});
|
|
2367
2535
|
|
|
2368
2536
|
// Mid-session learning extraction (fire-and-forget)
|
|
@@ -2405,6 +2573,10 @@ async function handleToolCall(
|
|
|
2405
2573
|
? z.number().min(0).max(100).parse(args.progressPercent)
|
|
2406
2574
|
: undefined;
|
|
2407
2575
|
|
|
2576
|
+
// Final flush of any pending memory actions before ending the session
|
|
2577
|
+
await flushMemoryActions(client, cardId);
|
|
2578
|
+
cleanupMemorySession(cardId);
|
|
2579
|
+
|
|
2408
2580
|
const result = await client.endAgentSession(cardId, {
|
|
2409
2581
|
status: sessionStatus,
|
|
2410
2582
|
progressPercent: endProgressPercent,
|
|
@@ -2689,6 +2861,8 @@ async function handleToolCall(
|
|
|
2689
2861
|
}
|
|
2690
2862
|
const entityType = (args.type as string) || "context";
|
|
2691
2863
|
const entityTags = (args.tags as string[]) || [];
|
|
2864
|
+
// Use session's agent identifier if available, otherwise null
|
|
2865
|
+
const activeMemSession = getActiveMemorySession();
|
|
2692
2866
|
const result = await client.createMemoryEntity({
|
|
2693
2867
|
workspace_id: workspaceId,
|
|
2694
2868
|
project_id:
|
|
@@ -2704,7 +2878,7 @@ async function handleToolCall(
|
|
|
2704
2878
|
? z.number().min(0).max(1).parse(args.confidence)
|
|
2705
2879
|
: undefined,
|
|
2706
2880
|
tags: entityTags.length > 0 ? entityTags : undefined,
|
|
2707
|
-
agent_identifier:
|
|
2881
|
+
agent_identifier: activeMemSession?.agentIdentifier || undefined,
|
|
2708
2882
|
});
|
|
2709
2883
|
|
|
2710
2884
|
// Fire-and-forget graph expansion: link new entity to semantically similar ones
|
|
@@ -2748,6 +2922,12 @@ async function handleToolCall(
|
|
|
2748
2922
|
}
|
|
2749
2923
|
}
|
|
2750
2924
|
|
|
2925
|
+
// Track memory write action and flush (fire-and-forget)
|
|
2926
|
+
if (activeMemSession) {
|
|
2927
|
+
appendMemoryAction(activeMemSession.cardId, `Stored memory: ${title}`);
|
|
2928
|
+
flushMemoryActions(client, activeMemSession.cardId).catch(() => {});
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2751
2931
|
return {
|
|
2752
2932
|
success: true,
|
|
2753
2933
|
...result,
|
|
@@ -2830,6 +3010,12 @@ async function handleToolCall(
|
|
|
2830
3010
|
).catch(() => {});
|
|
2831
3011
|
}
|
|
2832
3012
|
|
|
3013
|
+
// Track memory read (batched on flush)
|
|
3014
|
+
const recallMemSession = getActiveMemorySession();
|
|
3015
|
+
if (recallMemSession) {
|
|
3016
|
+
incrementMemoryReads(recallMemSession.cardId);
|
|
3017
|
+
}
|
|
3018
|
+
|
|
2833
3019
|
return markdown || "No memories found.";
|
|
2834
3020
|
}
|
|
2835
3021
|
|
|
@@ -2847,12 +3033,48 @@ async function handleToolCall(
|
|
|
2847
3033
|
updates.confidence = z.number().min(0).max(1).parse(args.confidence);
|
|
2848
3034
|
if (args.metadata !== undefined) updates.metadata = args.metadata;
|
|
2849
3035
|
const result = await client.updateMemoryEntity(entityId, updates);
|
|
3036
|
+
|
|
3037
|
+
// Track memory write action and flush (fire-and-forget)
|
|
3038
|
+
const updateMemSession = getActiveMemorySession();
|
|
3039
|
+
if (updateMemSession) {
|
|
3040
|
+
const updateTitle =
|
|
3041
|
+
(updates.title as string) || entityId.slice(0, 8);
|
|
3042
|
+
appendMemoryAction(
|
|
3043
|
+
updateMemSession.cardId,
|
|
3044
|
+
`Updated memory: ${updateTitle}`,
|
|
3045
|
+
);
|
|
3046
|
+
flushMemoryActions(client, updateMemSession.cardId).catch(() => {});
|
|
3047
|
+
}
|
|
3048
|
+
|
|
2850
3049
|
return { success: true, ...result };
|
|
2851
3050
|
}
|
|
2852
3051
|
|
|
2853
3052
|
case "harmony_forget": {
|
|
2854
3053
|
const entityId = z.string().uuid().parse(args.entityId);
|
|
3054
|
+
|
|
3055
|
+
// Fetch title before deletion for action tracking
|
|
3056
|
+
let forgetTitle: string | null = null;
|
|
3057
|
+
const forgetMemSession = getActiveMemorySession();
|
|
3058
|
+
if (forgetMemSession) {
|
|
3059
|
+
try {
|
|
3060
|
+
const { entity } = await client.getMemoryEntity(entityId);
|
|
3061
|
+
forgetTitle = (entity as { title?: string })?.title || null;
|
|
3062
|
+
} catch {
|
|
3063
|
+
// Non-fatal: use truncated ID if fetch fails
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
|
|
2855
3067
|
await client.deleteMemoryEntity(entityId);
|
|
3068
|
+
|
|
3069
|
+
// Track memory write action and flush (fire-and-forget)
|
|
3070
|
+
if (forgetMemSession) {
|
|
3071
|
+
appendMemoryAction(
|
|
3072
|
+
forgetMemSession.cardId,
|
|
3073
|
+
`Removed memory: ${forgetTitle || entityId.slice(0, 8)}`,
|
|
3074
|
+
);
|
|
3075
|
+
flushMemoryActions(client, forgetMemSession.cardId).catch(() => {});
|
|
3076
|
+
}
|
|
3077
|
+
|
|
2856
3078
|
return { success: true };
|
|
2857
3079
|
}
|
|
2858
3080
|
|
|
@@ -2880,6 +3102,17 @@ async function handleToolCall(
|
|
|
2880
3102
|
? z.number().min(0).max(1).parse(args.confidence)
|
|
2881
3103
|
: undefined,
|
|
2882
3104
|
});
|
|
3105
|
+
|
|
3106
|
+
// Track memory write action and flush (fire-and-forget)
|
|
3107
|
+
const relateMemSession = getActiveMemorySession();
|
|
3108
|
+
if (relateMemSession) {
|
|
3109
|
+
appendMemoryAction(
|
|
3110
|
+
relateMemSession.cardId,
|
|
3111
|
+
`Linked memories: ${relationType}`,
|
|
3112
|
+
);
|
|
3113
|
+
flushMemoryActions(client, relateMemSession.cardId).catch(() => {});
|
|
3114
|
+
}
|
|
3115
|
+
|
|
2883
3116
|
return { success: true, ...result };
|
|
2884
3117
|
}
|
|
2885
3118
|
|
|
@@ -2904,6 +3137,13 @@ async function handleToolCall(
|
|
|
2904
3137
|
limit: args.limit as number | undefined,
|
|
2905
3138
|
},
|
|
2906
3139
|
);
|
|
3140
|
+
|
|
3141
|
+
// Track memory read (batched on flush)
|
|
3142
|
+
const searchMemSession = getActiveMemorySession();
|
|
3143
|
+
if (searchMemSession) {
|
|
3144
|
+
incrementMemoryReads(searchMemSession.cardId);
|
|
3145
|
+
}
|
|
3146
|
+
|
|
2907
3147
|
return markdown || "No memories found.";
|
|
2908
3148
|
}
|
|
2909
3149
|
|
|
@@ -3039,7 +3279,7 @@ async function handleToolCall(
|
|
|
3039
3279
|
});
|
|
3040
3280
|
|
|
3041
3281
|
// Build URL for viewing the plan
|
|
3042
|
-
const planUrl = `https://gethmy.com/plans/${(result.plan as { id: string }).id}`;
|
|
3282
|
+
const planUrl = `https://app.gethmy.com/plans/${(result.plan as { id: string }).id}`;
|
|
3043
3283
|
|
|
3044
3284
|
return {
|
|
3045
3285
|
success: true,
|
package/src/skills.ts
CHANGED
|
@@ -251,7 +251,7 @@ Once you have the plan ID, call \`harmony_get_plan\` to fetch the full plan with
|
|
|
251
251
|
## [Plan Title]
|
|
252
252
|
**Status:** draft/active/archived | **Phase:** plan/execute/verify/done
|
|
253
253
|
**Tasks:** N total (X pending, Y in_progress, Z completed)
|
|
254
|
-
**URL:** https://gethmy.com/plans/{id}
|
|
254
|
+
**URL:** https://app.gethmy.com/plans/{id}
|
|
255
255
|
\\\`\\\`\\\`
|
|
256
256
|
|
|
257
257
|
If plan is in execute phase and tasks already have linked cards, note which tasks have cards and which don't.
|
|
@@ -285,7 +285,7 @@ Only offer **(A) Single card**, **(C) Analyze only**, or **(D) Skip**.
|
|
|
285
285
|
#### Option A — Single card
|
|
286
286
|
1. Call \`harmony_create_card\` with:
|
|
287
287
|
- \`title\`: Plan title
|
|
288
|
-
- \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://gethmy.com/plans/{planId})\`
|
|
288
|
+
- \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://app.gethmy.com/plans/{planId})\`
|
|
289
289
|
- \`priority\`: based on plan task priorities (use highest)
|
|
290
290
|
2. Call \`harmony_update_plan\` to set \`status: "active"\`, \`workflowPhase: "execute"\`
|
|
291
291
|
|
|
@@ -407,7 +407,7 @@ Call \`harmony_create_plan\` with:
|
|
|
407
407
|
### 2B.5 — User Approval
|
|
408
408
|
|
|
409
409
|
Show the user:
|
|
410
|
-
- Plan URL: \`https://gethmy.com/plans/{id}\`
|
|
410
|
+
- Plan URL: \`https://app.gethmy.com/plans/{id}\`
|
|
411
411
|
- Number of tasks created
|
|
412
412
|
- Brief summary
|
|
413
413
|
|
package/src/tui/setup.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface SetupOptions {
|
|
|
41
41
|
const GLOBAL_SKILLS_DIR = join(homedir(), ".agents", "skills");
|
|
42
42
|
|
|
43
43
|
// API base URL
|
|
44
|
-
const API_URL = "https://gethmy.com/api";
|
|
44
|
+
const API_URL = "https://app.gethmy.com/api";
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Register MCP server using Claude CLI
|
|
@@ -536,7 +536,7 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
|
|
|
536
536
|
if (!validation.valid) {
|
|
537
537
|
spinner.stop(colors.error("API key validation failed"));
|
|
538
538
|
p.log.error(validation.error || "Could not connect to Harmony API");
|
|
539
|
-
p.log.info("Get an API key at: https://gethmy.com/user/keys");
|
|
539
|
+
p.log.info("Get an API key at: https://app.gethmy.com/user/keys");
|
|
540
540
|
process.exit(1);
|
|
541
541
|
}
|
|
542
542
|
|
|
@@ -974,7 +974,7 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
|
|
|
974
974
|
console.log("");
|
|
975
975
|
console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
|
|
976
976
|
console.log(
|
|
977
|
-
` ${colors.dim("Need help? Visit https://gethmy.com/docs/mcp")}`,
|
|
977
|
+
` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`,
|
|
978
978
|
);
|
|
979
979
|
console.log("");
|
|
980
980
|
}
|