@gethmy/mcp 2.1.3 → 2.2.1
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 +2279 -2017
- package/dist/index.js +24656 -24394
- package/dist/lib/__tests__/auto-session.test.js +33 -33
- package/dist/lib/api-client.js +116 -0
- package/dist/lib/auto-session.js +49 -8
- 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 +200 -100
- package/dist/lib/skills.js +3 -3
- package/dist/lib/tui/setup.js +3 -3
- package/package.json +1 -1
- package/src/__tests__/auto-session.test.ts +33 -33
- package/src/api-client.ts +205 -0
- package/src/auto-session.ts +64 -7
- 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 +3 -2
- package/src/server.ts +267 -121
- package/src/skills.ts +3 -3
- package/src/tui/setup.ts +3 -3
package/src/server.ts
CHANGED
|
@@ -59,13 +59,7 @@ import {
|
|
|
59
59
|
} from "./context-assembly.js";
|
|
60
60
|
import { autoExpandGraph } from "./graph-expansion.js";
|
|
61
61
|
import { runLifecycleMaintenance } from "./lifecycle-maintenance.js";
|
|
62
|
-
import {
|
|
63
|
-
type CardData,
|
|
64
|
-
type ColumnData,
|
|
65
|
-
generatePrompt,
|
|
66
|
-
type MemoryData,
|
|
67
|
-
type PromptVariant,
|
|
68
|
-
} from "./prompt-builder.js";
|
|
62
|
+
import type { PromptVariant } from "./prompt-builder.js";
|
|
69
63
|
|
|
70
64
|
/**
|
|
71
65
|
* Dependencies injected into tool handlers.
|
|
@@ -85,6 +79,155 @@ export interface ToolDeps {
|
|
|
85
79
|
resetClient: () => void;
|
|
86
80
|
}
|
|
87
81
|
|
|
82
|
+
// --- Memory Session Tracking ---
|
|
83
|
+
// Tracks memory operations per card session for visibility in AgentCardFooter
|
|
84
|
+
|
|
85
|
+
interface MemorySessionState {
|
|
86
|
+
cardId: string;
|
|
87
|
+
agentIdentifier: string;
|
|
88
|
+
agentName: string;
|
|
89
|
+
memoryReadCount: number;
|
|
90
|
+
pendingActions: { action: string; ts: string }[];
|
|
91
|
+
allActions: { action: string; ts: string }[];
|
|
92
|
+
dirty: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const memorySessions = new Map<string, MemorySessionState>();
|
|
96
|
+
|
|
97
|
+
function initMemorySession(
|
|
98
|
+
cardId: string,
|
|
99
|
+
agentIdentifier: string,
|
|
100
|
+
agentName: string,
|
|
101
|
+
): void {
|
|
102
|
+
memorySessions.set(cardId, {
|
|
103
|
+
cardId,
|
|
104
|
+
agentIdentifier,
|
|
105
|
+
agentName,
|
|
106
|
+
memoryReadCount: 0,
|
|
107
|
+
pendingActions: [],
|
|
108
|
+
allActions: [],
|
|
109
|
+
dirty: false,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getMemorySession(cardId: string): MemorySessionState | undefined {
|
|
114
|
+
return memorySessions.get(cardId);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function appendMemoryAction(cardId: string, action: string): void {
|
|
118
|
+
const session = memorySessions.get(cardId);
|
|
119
|
+
if (!session) return;
|
|
120
|
+
const truncated = action.length > 512 ? action.slice(0, 509) + "..." : action;
|
|
121
|
+
const entry = { action: truncated, ts: new Date().toISOString() };
|
|
122
|
+
session.pendingActions.push(entry);
|
|
123
|
+
session.dirty = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function incrementMemoryReads(cardId: string): void {
|
|
127
|
+
const session = memorySessions.get(cardId);
|
|
128
|
+
if (!session) return;
|
|
129
|
+
session.memoryReadCount++;
|
|
130
|
+
session.dirty = true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Flush pending memory actions to the backend via updateAgentProgress.
|
|
135
|
+
* Fire-and-forget: errors are logged but never thrown.
|
|
136
|
+
*/
|
|
137
|
+
async function flushMemoryActions(
|
|
138
|
+
client: HarmonyApiClient,
|
|
139
|
+
cardId: string,
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
const session = memorySessions.get(cardId);
|
|
142
|
+
if (!session || !session.dirty) return;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// Batch reads into a single summary line
|
|
146
|
+
if (session.memoryReadCount > 0) {
|
|
147
|
+
session.allActions.push({
|
|
148
|
+
action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
|
|
149
|
+
ts: new Date().toISOString(),
|
|
150
|
+
});
|
|
151
|
+
session.memoryReadCount = 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Move pending writes to allActions
|
|
155
|
+
if (session.pendingActions.length > 0) {
|
|
156
|
+
session.allActions.push(...session.pendingActions);
|
|
157
|
+
session.pendingActions = [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Trim to max 10 (drop oldest)
|
|
161
|
+
if (session.allActions.length > 10) {
|
|
162
|
+
session.allActions = session.allActions.slice(-10);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await client.updateAgentProgress(cardId, {
|
|
166
|
+
agentIdentifier: session.agentIdentifier,
|
|
167
|
+
agentName: session.agentName,
|
|
168
|
+
recentActions: session.allActions,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
session.dirty = false;
|
|
172
|
+
} catch (err) {
|
|
173
|
+
// Fire-and-forget: log but don't propagate
|
|
174
|
+
console.error("[memory-session] flush failed:", err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Merge memory actions into an existing recentActions array from a progress update.
|
|
180
|
+
* Returns the merged array (caller's actions first, then memory actions appended).
|
|
181
|
+
*/
|
|
182
|
+
function mergeMemoryActionsInto(
|
|
183
|
+
cardId: string,
|
|
184
|
+
callerActions: { action: string; ts: string }[],
|
|
185
|
+
): { action: string; ts: string }[] {
|
|
186
|
+
const session = memorySessions.get(cardId);
|
|
187
|
+
if (!session) return callerActions;
|
|
188
|
+
|
|
189
|
+
// Flush reads into allActions
|
|
190
|
+
if (session.memoryReadCount > 0) {
|
|
191
|
+
session.allActions.push({
|
|
192
|
+
action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
|
|
193
|
+
ts: new Date().toISOString(),
|
|
194
|
+
});
|
|
195
|
+
session.memoryReadCount = 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Move pending to allActions
|
|
199
|
+
if (session.pendingActions.length > 0) {
|
|
200
|
+
session.allActions.push(...session.pendingActions);
|
|
201
|
+
session.pendingActions = [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Merge: caller actions + memory actions, trim to 10
|
|
205
|
+
const merged = [...callerActions, ...session.allActions];
|
|
206
|
+
const trimmed = merged.length > 10 ? merged.slice(-10) : merged;
|
|
207
|
+
|
|
208
|
+
// Update allActions to the merged state
|
|
209
|
+
session.allActions = trimmed;
|
|
210
|
+
session.dirty = false;
|
|
211
|
+
|
|
212
|
+
return trimmed;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get the first (and typically only) active memory session.
|
|
217
|
+
* Memory tools don't carry a cardId, so we find the active session.
|
|
218
|
+
*/
|
|
219
|
+
function getActiveMemorySession(): MemorySessionState | undefined {
|
|
220
|
+
// Return the first active session (there should be at most one)
|
|
221
|
+
for (const session of memorySessions.values()) {
|
|
222
|
+
return session;
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function cleanupMemorySession(cardId: string): void {
|
|
228
|
+
memorySessions.delete(cardId);
|
|
229
|
+
}
|
|
230
|
+
|
|
88
231
|
// Tool definitions
|
|
89
232
|
const TOOLS = {
|
|
90
233
|
// Card operations
|
|
@@ -1838,7 +1981,7 @@ async function handleToolCall(
|
|
|
1838
1981
|
if (!unauthenticatedTools.includes(name) && !deps.isConfigured()) {
|
|
1839
1982
|
throw new Error(
|
|
1840
1983
|
'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" +
|
|
1984
|
+
"You can generate an API key at https://app.gethmy.com → Settings → API Keys.\n" +
|
|
1842
1985
|
'Or use "harmony_onboard" to create an account and configure automatically.',
|
|
1843
1986
|
);
|
|
1844
1987
|
}
|
|
@@ -2267,7 +2410,10 @@ async function handleToolCall(
|
|
|
2267
2410
|
});
|
|
2268
2411
|
|
|
2269
2412
|
// Mark as explicit so auto-session won't interfere
|
|
2270
|
-
markExplicit(cardId);
|
|
2413
|
+
markExplicit(cardId, { agentIdentifier, agentName });
|
|
2414
|
+
|
|
2415
|
+
// Initialize memory session tracking for action visibility
|
|
2416
|
+
initMemorySession(cardId, agentIdentifier, agentName);
|
|
2271
2417
|
|
|
2272
2418
|
// Prefetch relevant context (non-blocking, best-effort)
|
|
2273
2419
|
let prefetchedMemoryIds: string[] = [];
|
|
@@ -2348,6 +2494,21 @@ async function handleToolCall(
|
|
|
2348
2494
|
args.progressPercent !== undefined
|
|
2349
2495
|
? z.number().min(0).max(100).parse(args.progressPercent)
|
|
2350
2496
|
: undefined;
|
|
2497
|
+
// Merge any pending memory actions into the progress update
|
|
2498
|
+
const callerRecentActions = args.recentActions as
|
|
2499
|
+
| { action: string; ts: string }[]
|
|
2500
|
+
| undefined;
|
|
2501
|
+
const memSession = getMemorySession(cardId);
|
|
2502
|
+
let mergedRecentActions: { action: string; ts: string }[] | undefined;
|
|
2503
|
+
if (memSession?.dirty) {
|
|
2504
|
+
mergedRecentActions = mergeMemoryActionsInto(
|
|
2505
|
+
cardId,
|
|
2506
|
+
callerRecentActions || [],
|
|
2507
|
+
);
|
|
2508
|
+
} else if (callerRecentActions) {
|
|
2509
|
+
mergedRecentActions = callerRecentActions;
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2351
2512
|
const result = await client.updateAgentProgress(cardId, {
|
|
2352
2513
|
agentIdentifier,
|
|
2353
2514
|
agentName,
|
|
@@ -2363,6 +2524,7 @@ async function handleToolCall(
|
|
|
2363
2524
|
estimatedMinutesRemaining: args.estimatedMinutesRemaining as
|
|
2364
2525
|
| number
|
|
2365
2526
|
| undefined,
|
|
2527
|
+
...(mergedRecentActions && { recentActions: mergedRecentActions }),
|
|
2366
2528
|
});
|
|
2367
2529
|
|
|
2368
2530
|
// Mid-session learning extraction (fire-and-forget)
|
|
@@ -2405,6 +2567,10 @@ async function handleToolCall(
|
|
|
2405
2567
|
? z.number().min(0).max(100).parse(args.progressPercent)
|
|
2406
2568
|
: undefined;
|
|
2407
2569
|
|
|
2570
|
+
// Final flush of any pending memory actions before ending the session
|
|
2571
|
+
await flushMemoryActions(client, cardId);
|
|
2572
|
+
cleanupMemorySession(cardId);
|
|
2573
|
+
|
|
2408
2574
|
const result = await client.endAgentSession(cardId, {
|
|
2409
2575
|
status: sessionStatus,
|
|
2410
2576
|
progressPercent: endProgressPercent,
|
|
@@ -2527,14 +2693,11 @@ async function handleToolCall(
|
|
|
2527
2693
|
|
|
2528
2694
|
// Prompt generation
|
|
2529
2695
|
case "harmony_generate_prompt": {
|
|
2530
|
-
//
|
|
2531
|
-
let
|
|
2532
|
-
let columnData: ColumnData | null = null;
|
|
2696
|
+
// Resolve card ID — either directly or via short ID
|
|
2697
|
+
let cardId: string;
|
|
2533
2698
|
|
|
2534
2699
|
if (args.cardId) {
|
|
2535
|
-
|
|
2536
|
-
const cardResult = await client.getCard(cardId);
|
|
2537
|
-
cardData = cardResult.card as CardData;
|
|
2700
|
+
cardId = z.string().uuid().parse(args.cardId);
|
|
2538
2701
|
} else if (args.shortId !== undefined) {
|
|
2539
2702
|
const shortId = z.number().int().positive().parse(args.shortId);
|
|
2540
2703
|
const projectId =
|
|
@@ -2545,35 +2708,12 @@ async function handleToolCall(
|
|
|
2545
2708
|
);
|
|
2546
2709
|
}
|
|
2547
2710
|
const cardResult = await client.getCardByShortId(projectId, shortId);
|
|
2548
|
-
|
|
2711
|
+
cardId = (cardResult.card as { id: string }).id;
|
|
2549
2712
|
} else {
|
|
2550
2713
|
throw new Error("Either cardId or shortId must be provided");
|
|
2551
2714
|
}
|
|
2552
2715
|
|
|
2553
|
-
//
|
|
2554
|
-
const projectIdForBoard =
|
|
2555
|
-
(args.projectId as string) ||
|
|
2556
|
-
getActiveProjectId() ||
|
|
2557
|
-
(cardData as unknown as { project_id: string }).project_id;
|
|
2558
|
-
if (projectIdForBoard) {
|
|
2559
|
-
try {
|
|
2560
|
-
const board = await client.getBoard(projectIdForBoard, {
|
|
2561
|
-
summary: true,
|
|
2562
|
-
});
|
|
2563
|
-
const columnId = (cardData as unknown as { column_id: string })
|
|
2564
|
-
.column_id;
|
|
2565
|
-
const column = (
|
|
2566
|
-
board.columns as Array<{ id: string; name: string }>
|
|
2567
|
-
).find((col) => col.id === columnId);
|
|
2568
|
-
if (column) {
|
|
2569
|
-
columnData = { name: column.name };
|
|
2570
|
-
}
|
|
2571
|
-
} catch {
|
|
2572
|
-
// Column info not available, continue without it
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
|
-
const variant = (args.variant as PromptVariant) || "execute";
|
|
2716
|
+
// Parse MCP-specific context options
|
|
2577
2717
|
const contextOptions: Record<string, boolean> = {};
|
|
2578
2718
|
if (args.includeSubtasks !== undefined) {
|
|
2579
2719
|
contextOptions.includeSubtasks =
|
|
@@ -2589,89 +2729,24 @@ async function handleToolCall(
|
|
|
2589
2729
|
args.includeDescription === "true";
|
|
2590
2730
|
}
|
|
2591
2731
|
|
|
2592
|
-
//
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
if (workspaceId && cardData.title) {
|
|
2600
|
-
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
2601
|
-
const taskContext = [cardData.title, cardData.description || ""]
|
|
2602
|
-
.filter(Boolean)
|
|
2603
|
-
.join(" ");
|
|
2604
|
-
|
|
2605
|
-
const assembled = await assembleContext({
|
|
2606
|
-
workspaceId,
|
|
2607
|
-
projectId: getActiveProjectId() || undefined,
|
|
2608
|
-
taskContext,
|
|
2609
|
-
cardLabels,
|
|
2610
|
-
cardId: cardData.id,
|
|
2611
|
-
client,
|
|
2612
|
-
});
|
|
2613
|
-
|
|
2614
|
-
if (assembled.context) {
|
|
2615
|
-
assembledContextStr = assembled.context;
|
|
2616
|
-
assemblyId = assembled.manifest.assemblyId;
|
|
2617
|
-
cacheManifest(assembled.manifest);
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
} catch {
|
|
2621
|
-
// Context assembly failed, try legacy fallback
|
|
2622
|
-
try {
|
|
2623
|
-
const workspaceId = deps.getActiveWorkspaceId();
|
|
2624
|
-
if (workspaceId && cardData.title) {
|
|
2625
|
-
const memoryResult = await client.searchMemoryEntities(
|
|
2626
|
-
workspaceId,
|
|
2627
|
-
cardData.title,
|
|
2628
|
-
{
|
|
2629
|
-
project_id: getActiveProjectId() || undefined,
|
|
2630
|
-
limit: 5,
|
|
2631
|
-
},
|
|
2632
|
-
);
|
|
2633
|
-
if (memoryResult.entities?.length > 0) {
|
|
2634
|
-
memories = memoryResult.entities.map((e: unknown) => {
|
|
2635
|
-
const entity = e as {
|
|
2636
|
-
id: string;
|
|
2637
|
-
type: string;
|
|
2638
|
-
title: string;
|
|
2639
|
-
content: string;
|
|
2640
|
-
confidence: number;
|
|
2641
|
-
tags: string[];
|
|
2642
|
-
};
|
|
2643
|
-
return {
|
|
2644
|
-
id: entity.id,
|
|
2645
|
-
type: entity.type,
|
|
2646
|
-
title: entity.title,
|
|
2647
|
-
content: entity.content,
|
|
2648
|
-
confidence: entity.confidence,
|
|
2649
|
-
tags: entity.tags || [],
|
|
2650
|
-
};
|
|
2651
|
-
});
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
} catch {
|
|
2655
|
-
// Memory fetch also failed, continue without memories
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
|
|
2659
|
-
const result = generatePrompt({
|
|
2660
|
-
card: cardData,
|
|
2661
|
-
column: columnData,
|
|
2662
|
-
variant,
|
|
2663
|
-
contextOptions,
|
|
2732
|
+
// Delegate to the shared prompt generation pipeline
|
|
2733
|
+
const result = await client.generateCardPrompt({
|
|
2734
|
+
cardId,
|
|
2735
|
+
workspaceId: deps.getActiveWorkspaceId() || "",
|
|
2736
|
+
projectId:
|
|
2737
|
+
(args.projectId as string) || getActiveProjectId() || undefined,
|
|
2738
|
+
variant: (args.variant as PromptVariant) || "execute",
|
|
2664
2739
|
customConstraints: args.customConstraints as string | undefined,
|
|
2665
|
-
|
|
2666
|
-
assembledContext: assembledContextStr,
|
|
2667
|
-
assemblyId,
|
|
2740
|
+
contextOptions,
|
|
2668
2741
|
});
|
|
2669
2742
|
|
|
2743
|
+
// MCP-specific: cache the assembly manifest for the feedback loop
|
|
2744
|
+
if (result.assemblyId) {
|
|
2745
|
+
trackSessionAssembly(cardId, result.assemblyId);
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2670
2748
|
return {
|
|
2671
2749
|
success: true,
|
|
2672
|
-
cardId: cardData.id,
|
|
2673
|
-
shortId: cardData.short_id,
|
|
2674
|
-
title: cardData.title,
|
|
2675
2750
|
...result,
|
|
2676
2751
|
};
|
|
2677
2752
|
}
|
|
@@ -2689,6 +2764,8 @@ async function handleToolCall(
|
|
|
2689
2764
|
}
|
|
2690
2765
|
const entityType = (args.type as string) || "context";
|
|
2691
2766
|
const entityTags = (args.tags as string[]) || [];
|
|
2767
|
+
// Use session's agent identifier if available, otherwise null
|
|
2768
|
+
const activeMemSession = getActiveMemorySession();
|
|
2692
2769
|
const result = await client.createMemoryEntity({
|
|
2693
2770
|
workspace_id: workspaceId,
|
|
2694
2771
|
project_id:
|
|
@@ -2704,7 +2781,7 @@ async function handleToolCall(
|
|
|
2704
2781
|
? z.number().min(0).max(1).parse(args.confidence)
|
|
2705
2782
|
: undefined,
|
|
2706
2783
|
tags: entityTags.length > 0 ? entityTags : undefined,
|
|
2707
|
-
agent_identifier:
|
|
2784
|
+
agent_identifier: activeMemSession?.agentIdentifier || undefined,
|
|
2708
2785
|
});
|
|
2709
2786
|
|
|
2710
2787
|
// Fire-and-forget graph expansion: link new entity to semantically similar ones
|
|
@@ -2748,6 +2825,12 @@ async function handleToolCall(
|
|
|
2748
2825
|
}
|
|
2749
2826
|
}
|
|
2750
2827
|
|
|
2828
|
+
// Track memory write action and flush (fire-and-forget)
|
|
2829
|
+
if (activeMemSession) {
|
|
2830
|
+
appendMemoryAction(activeMemSession.cardId, `Stored memory: ${title}`);
|
|
2831
|
+
flushMemoryActions(client, activeMemSession.cardId).catch(() => {});
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2751
2834
|
return {
|
|
2752
2835
|
success: true,
|
|
2753
2836
|
...result,
|
|
@@ -2830,6 +2913,12 @@ async function handleToolCall(
|
|
|
2830
2913
|
).catch(() => {});
|
|
2831
2914
|
}
|
|
2832
2915
|
|
|
2916
|
+
// Track memory read (batched on flush)
|
|
2917
|
+
const recallMemSession = getActiveMemorySession();
|
|
2918
|
+
if (recallMemSession) {
|
|
2919
|
+
incrementMemoryReads(recallMemSession.cardId);
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2833
2922
|
return markdown || "No memories found.";
|
|
2834
2923
|
}
|
|
2835
2924
|
|
|
@@ -2847,12 +2936,47 @@ async function handleToolCall(
|
|
|
2847
2936
|
updates.confidence = z.number().min(0).max(1).parse(args.confidence);
|
|
2848
2937
|
if (args.metadata !== undefined) updates.metadata = args.metadata;
|
|
2849
2938
|
const result = await client.updateMemoryEntity(entityId, updates);
|
|
2939
|
+
|
|
2940
|
+
// Track memory write action and flush (fire-and-forget)
|
|
2941
|
+
const updateMemSession = getActiveMemorySession();
|
|
2942
|
+
if (updateMemSession) {
|
|
2943
|
+
const updateTitle = (updates.title as string) || entityId.slice(0, 8);
|
|
2944
|
+
appendMemoryAction(
|
|
2945
|
+
updateMemSession.cardId,
|
|
2946
|
+
`Updated memory: ${updateTitle}`,
|
|
2947
|
+
);
|
|
2948
|
+
flushMemoryActions(client, updateMemSession.cardId).catch(() => {});
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2850
2951
|
return { success: true, ...result };
|
|
2851
2952
|
}
|
|
2852
2953
|
|
|
2853
2954
|
case "harmony_forget": {
|
|
2854
2955
|
const entityId = z.string().uuid().parse(args.entityId);
|
|
2956
|
+
|
|
2957
|
+
// Fetch title before deletion for action tracking
|
|
2958
|
+
let forgetTitle: string | null = null;
|
|
2959
|
+
const forgetMemSession = getActiveMemorySession();
|
|
2960
|
+
if (forgetMemSession) {
|
|
2961
|
+
try {
|
|
2962
|
+
const { entity } = await client.getMemoryEntity(entityId);
|
|
2963
|
+
forgetTitle = (entity as { title?: string })?.title || null;
|
|
2964
|
+
} catch {
|
|
2965
|
+
// Non-fatal: use truncated ID if fetch fails
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2855
2969
|
await client.deleteMemoryEntity(entityId);
|
|
2970
|
+
|
|
2971
|
+
// Track memory write action and flush (fire-and-forget)
|
|
2972
|
+
if (forgetMemSession) {
|
|
2973
|
+
appendMemoryAction(
|
|
2974
|
+
forgetMemSession.cardId,
|
|
2975
|
+
`Removed memory: ${forgetTitle || entityId.slice(0, 8)}`,
|
|
2976
|
+
);
|
|
2977
|
+
flushMemoryActions(client, forgetMemSession.cardId).catch(() => {});
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2856
2980
|
return { success: true };
|
|
2857
2981
|
}
|
|
2858
2982
|
|
|
@@ -2880,6 +3004,17 @@ async function handleToolCall(
|
|
|
2880
3004
|
? z.number().min(0).max(1).parse(args.confidence)
|
|
2881
3005
|
: undefined,
|
|
2882
3006
|
});
|
|
3007
|
+
|
|
3008
|
+
// Track memory write action and flush (fire-and-forget)
|
|
3009
|
+
const relateMemSession = getActiveMemorySession();
|
|
3010
|
+
if (relateMemSession) {
|
|
3011
|
+
appendMemoryAction(
|
|
3012
|
+
relateMemSession.cardId,
|
|
3013
|
+
`Linked memories: ${relationType}`,
|
|
3014
|
+
);
|
|
3015
|
+
flushMemoryActions(client, relateMemSession.cardId).catch(() => {});
|
|
3016
|
+
}
|
|
3017
|
+
|
|
2883
3018
|
return { success: true, ...result };
|
|
2884
3019
|
}
|
|
2885
3020
|
|
|
@@ -2904,6 +3039,13 @@ async function handleToolCall(
|
|
|
2904
3039
|
limit: args.limit as number | undefined,
|
|
2905
3040
|
},
|
|
2906
3041
|
);
|
|
3042
|
+
|
|
3043
|
+
// Track memory read (batched on flush)
|
|
3044
|
+
const searchMemSession = getActiveMemorySession();
|
|
3045
|
+
if (searchMemSession) {
|
|
3046
|
+
incrementMemoryReads(searchMemSession.cardId);
|
|
3047
|
+
}
|
|
3048
|
+
|
|
2907
3049
|
return markdown || "No memories found.";
|
|
2908
3050
|
}
|
|
2909
3051
|
|
|
@@ -3039,7 +3181,7 @@ async function handleToolCall(
|
|
|
3039
3181
|
});
|
|
3040
3182
|
|
|
3041
3183
|
// Build URL for viewing the plan
|
|
3042
|
-
const planUrl = `https://gethmy.com/plans/${(result.plan as { id: string }).id}`;
|
|
3184
|
+
const planUrl = `https://app.gethmy.com/plans/${(result.plan as { id: string }).id}`;
|
|
3043
3185
|
|
|
3044
3186
|
return {
|
|
3045
3187
|
success: true,
|
|
@@ -3813,13 +3955,17 @@ export class HarmonyMCPServer {
|
|
|
3813
3955
|
await this.server.connect(transport);
|
|
3814
3956
|
console.error("Harmony MCP server running on stdio");
|
|
3815
3957
|
|
|
3816
|
-
// Initialize auto-session tracking
|
|
3958
|
+
// Initialize auto-session tracking with MCP client identity detection
|
|
3817
3959
|
const configDeps = createConfigDeps();
|
|
3818
3960
|
initAutoSession(
|
|
3819
3961
|
async (client, cardId, status) => {
|
|
3820
3962
|
await runEndSessionPipeline(client, configDeps, cardId, status);
|
|
3821
3963
|
},
|
|
3822
3964
|
() => getClient(),
|
|
3965
|
+
() => {
|
|
3966
|
+
const cv = this.server.getClientVersion();
|
|
3967
|
+
return cv ? { name: cv.name, version: cv.version } : null;
|
|
3968
|
+
},
|
|
3823
3969
|
);
|
|
3824
3970
|
|
|
3825
3971
|
// Graceful shutdown: end all auto-sessions
|
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
|
}
|