@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/dist/lib/server.js
CHANGED
|
@@ -11,7 +11,118 @@ import { consolidateMemories } from "./consolidation.js";
|
|
|
11
11
|
import { assembleContext, cacheManifest, computeRelevanceScore, getCachedManifest, mapToContextEntity, recordContextFeedback, trackSessionAssembly, } from "./context-assembly.js";
|
|
12
12
|
import { autoExpandGraph } from "./graph-expansion.js";
|
|
13
13
|
import { runLifecycleMaintenance } from "./lifecycle-maintenance.js";
|
|
14
|
-
|
|
14
|
+
const memorySessions = new Map();
|
|
15
|
+
function initMemorySession(cardId, agentIdentifier, agentName) {
|
|
16
|
+
memorySessions.set(cardId, {
|
|
17
|
+
cardId,
|
|
18
|
+
agentIdentifier,
|
|
19
|
+
agentName,
|
|
20
|
+
memoryReadCount: 0,
|
|
21
|
+
pendingActions: [],
|
|
22
|
+
allActions: [],
|
|
23
|
+
dirty: false,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function getMemorySession(cardId) {
|
|
27
|
+
return memorySessions.get(cardId);
|
|
28
|
+
}
|
|
29
|
+
function appendMemoryAction(cardId, action) {
|
|
30
|
+
const session = memorySessions.get(cardId);
|
|
31
|
+
if (!session)
|
|
32
|
+
return;
|
|
33
|
+
const truncated = action.length > 512 ? action.slice(0, 509) + "..." : action;
|
|
34
|
+
const entry = { action: truncated, ts: new Date().toISOString() };
|
|
35
|
+
session.pendingActions.push(entry);
|
|
36
|
+
session.dirty = true;
|
|
37
|
+
}
|
|
38
|
+
function incrementMemoryReads(cardId) {
|
|
39
|
+
const session = memorySessions.get(cardId);
|
|
40
|
+
if (!session)
|
|
41
|
+
return;
|
|
42
|
+
session.memoryReadCount++;
|
|
43
|
+
session.dirty = true;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Flush pending memory actions to the backend via updateAgentProgress.
|
|
47
|
+
* Fire-and-forget: errors are logged but never thrown.
|
|
48
|
+
*/
|
|
49
|
+
async function flushMemoryActions(client, cardId) {
|
|
50
|
+
const session = memorySessions.get(cardId);
|
|
51
|
+
if (!session || !session.dirty)
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
// Batch reads into a single summary line
|
|
55
|
+
if (session.memoryReadCount > 0) {
|
|
56
|
+
session.allActions.push({
|
|
57
|
+
action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
|
|
58
|
+
ts: new Date().toISOString(),
|
|
59
|
+
});
|
|
60
|
+
session.memoryReadCount = 0;
|
|
61
|
+
}
|
|
62
|
+
// Move pending writes to allActions
|
|
63
|
+
if (session.pendingActions.length > 0) {
|
|
64
|
+
session.allActions.push(...session.pendingActions);
|
|
65
|
+
session.pendingActions = [];
|
|
66
|
+
}
|
|
67
|
+
// Trim to max 10 (drop oldest)
|
|
68
|
+
if (session.allActions.length > 10) {
|
|
69
|
+
session.allActions = session.allActions.slice(-10);
|
|
70
|
+
}
|
|
71
|
+
await client.updateAgentProgress(cardId, {
|
|
72
|
+
agentIdentifier: session.agentIdentifier,
|
|
73
|
+
agentName: session.agentName,
|
|
74
|
+
recentActions: session.allActions,
|
|
75
|
+
});
|
|
76
|
+
session.dirty = false;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
// Fire-and-forget: log but don't propagate
|
|
80
|
+
console.error("[memory-session] flush failed:", err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Merge memory actions into an existing recentActions array from a progress update.
|
|
85
|
+
* Returns the merged array (caller's actions first, then memory actions appended).
|
|
86
|
+
*/
|
|
87
|
+
function mergeMemoryActionsInto(cardId, callerActions) {
|
|
88
|
+
const session = memorySessions.get(cardId);
|
|
89
|
+
if (!session)
|
|
90
|
+
return callerActions;
|
|
91
|
+
// Flush reads into allActions
|
|
92
|
+
if (session.memoryReadCount > 0) {
|
|
93
|
+
session.allActions.push({
|
|
94
|
+
action: `Recalled ${session.memoryReadCount} memor${session.memoryReadCount === 1 ? "y" : "ies"}`,
|
|
95
|
+
ts: new Date().toISOString(),
|
|
96
|
+
});
|
|
97
|
+
session.memoryReadCount = 0;
|
|
98
|
+
}
|
|
99
|
+
// Move pending to allActions
|
|
100
|
+
if (session.pendingActions.length > 0) {
|
|
101
|
+
session.allActions.push(...session.pendingActions);
|
|
102
|
+
session.pendingActions = [];
|
|
103
|
+
}
|
|
104
|
+
// Merge: caller actions + memory actions, trim to 10
|
|
105
|
+
const merged = [...callerActions, ...session.allActions];
|
|
106
|
+
const trimmed = merged.length > 10 ? merged.slice(-10) : merged;
|
|
107
|
+
// Update allActions to the merged state
|
|
108
|
+
session.allActions = trimmed;
|
|
109
|
+
session.dirty = false;
|
|
110
|
+
return trimmed;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the first (and typically only) active memory session.
|
|
114
|
+
* Memory tools don't carry a cardId, so we find the active session.
|
|
115
|
+
*/
|
|
116
|
+
function getActiveMemorySession() {
|
|
117
|
+
// Return the first active session (there should be at most one)
|
|
118
|
+
for (const session of memorySessions.values()) {
|
|
119
|
+
return session;
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
function cleanupMemorySession(cardId) {
|
|
124
|
+
memorySessions.delete(cardId);
|
|
125
|
+
}
|
|
15
126
|
// Tool definitions
|
|
16
127
|
const TOOLS = {
|
|
17
128
|
// Card operations
|
|
@@ -1597,7 +1708,7 @@ async function handleToolCall(name, args, deps) {
|
|
|
1597
1708
|
const unauthenticatedTools = ["harmony_signup", "harmony_onboard"];
|
|
1598
1709
|
if (!unauthenticatedTools.includes(name) && !deps.isConfigured()) {
|
|
1599
1710
|
throw new Error('Not configured. Run "npx @gethmy/mcp setup" to set your API key.\n' +
|
|
1600
|
-
"You can generate an API key at https://gethmy.com → Settings → API Keys.\n" +
|
|
1711
|
+
"You can generate an API key at https://app.gethmy.com → Settings → API Keys.\n" +
|
|
1601
1712
|
'Or use "harmony_onboard" to create an account and configure automatically.');
|
|
1602
1713
|
}
|
|
1603
1714
|
const client = deps.isConfigured()
|
|
@@ -1944,7 +2055,9 @@ async function handleToolCall(name, args, deps) {
|
|
|
1944
2055
|
estimatedMinutesRemaining: args.estimatedMinutesRemaining,
|
|
1945
2056
|
});
|
|
1946
2057
|
// Mark as explicit so auto-session won't interfere
|
|
1947
|
-
markExplicit(cardId);
|
|
2058
|
+
markExplicit(cardId, { agentIdentifier, agentName });
|
|
2059
|
+
// Initialize memory session tracking for action visibility
|
|
2060
|
+
initMemorySession(cardId, agentIdentifier, agentName);
|
|
1948
2061
|
// Prefetch relevant context (non-blocking, best-effort)
|
|
1949
2062
|
let prefetchedMemoryIds = [];
|
|
1950
2063
|
try {
|
|
@@ -2011,6 +2124,16 @@ async function handleToolCall(name, args, deps) {
|
|
|
2011
2124
|
const progressPercent = args.progressPercent !== undefined
|
|
2012
2125
|
? z.number().min(0).max(100).parse(args.progressPercent)
|
|
2013
2126
|
: undefined;
|
|
2127
|
+
// Merge any pending memory actions into the progress update
|
|
2128
|
+
const callerRecentActions = args.recentActions;
|
|
2129
|
+
const memSession = getMemorySession(cardId);
|
|
2130
|
+
let mergedRecentActions;
|
|
2131
|
+
if (memSession?.dirty) {
|
|
2132
|
+
mergedRecentActions = mergeMemoryActionsInto(cardId, callerRecentActions || []);
|
|
2133
|
+
}
|
|
2134
|
+
else if (callerRecentActions) {
|
|
2135
|
+
mergedRecentActions = callerRecentActions;
|
|
2136
|
+
}
|
|
2014
2137
|
const result = await client.updateAgentProgress(cardId, {
|
|
2015
2138
|
agentIdentifier,
|
|
2016
2139
|
agentName,
|
|
@@ -2019,6 +2142,7 @@ async function handleToolCall(name, args, deps) {
|
|
|
2019
2142
|
currentTask: args.currentTask,
|
|
2020
2143
|
blockers: args.blockers,
|
|
2021
2144
|
estimatedMinutesRemaining: args.estimatedMinutesRemaining,
|
|
2145
|
+
...(mergedRecentActions && { recentActions: mergedRecentActions }),
|
|
2022
2146
|
});
|
|
2023
2147
|
// Mid-session learning extraction (fire-and-forget)
|
|
2024
2148
|
let midSessionLearnings = 0;
|
|
@@ -2049,6 +2173,9 @@ async function handleToolCall(name, args, deps) {
|
|
|
2049
2173
|
const endProgressPercent = args.progressPercent !== undefined
|
|
2050
2174
|
? z.number().min(0).max(100).parse(args.progressPercent)
|
|
2051
2175
|
: undefined;
|
|
2176
|
+
// Final flush of any pending memory actions before ending the session
|
|
2177
|
+
await flushMemoryActions(client, cardId);
|
|
2178
|
+
cleanupMemorySession(cardId);
|
|
2052
2179
|
const result = await client.endAgentSession(cardId, {
|
|
2053
2180
|
status: sessionStatus,
|
|
2054
2181
|
progressPercent: endProgressPercent,
|
|
@@ -2133,13 +2260,10 @@ async function handleToolCall(name, args, deps) {
|
|
|
2133
2260
|
}
|
|
2134
2261
|
// Prompt generation
|
|
2135
2262
|
case "harmony_generate_prompt": {
|
|
2136
|
-
//
|
|
2137
|
-
let
|
|
2138
|
-
let columnData = null;
|
|
2263
|
+
// Resolve card ID — either directly or via short ID
|
|
2264
|
+
let cardId;
|
|
2139
2265
|
if (args.cardId) {
|
|
2140
|
-
|
|
2141
|
-
const cardResult = await client.getCard(cardId);
|
|
2142
|
-
cardData = cardResult.card;
|
|
2266
|
+
cardId = z.string().uuid().parse(args.cardId);
|
|
2143
2267
|
}
|
|
2144
2268
|
else if (args.shortId !== undefined) {
|
|
2145
2269
|
const shortId = z.number().int().positive().parse(args.shortId);
|
|
@@ -2148,32 +2272,12 @@ async function handleToolCall(name, args, deps) {
|
|
|
2148
2272
|
throw new Error("Project ID required when using shortId. Use harmony_set_project_context or provide projectId.");
|
|
2149
2273
|
}
|
|
2150
2274
|
const cardResult = await client.getCardByShortId(projectId, shortId);
|
|
2151
|
-
|
|
2275
|
+
cardId = cardResult.card.id;
|
|
2152
2276
|
}
|
|
2153
2277
|
else {
|
|
2154
2278
|
throw new Error("Either cardId or shortId must be provided");
|
|
2155
2279
|
}
|
|
2156
|
-
//
|
|
2157
|
-
const projectIdForBoard = args.projectId ||
|
|
2158
|
-
getActiveProjectId() ||
|
|
2159
|
-
cardData.project_id;
|
|
2160
|
-
if (projectIdForBoard) {
|
|
2161
|
-
try {
|
|
2162
|
-
const board = await client.getBoard(projectIdForBoard, {
|
|
2163
|
-
summary: true,
|
|
2164
|
-
});
|
|
2165
|
-
const columnId = cardData
|
|
2166
|
-
.column_id;
|
|
2167
|
-
const column = board.columns.find((col) => col.id === columnId);
|
|
2168
|
-
if (column) {
|
|
2169
|
-
columnData = { name: column.name };
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
catch {
|
|
2173
|
-
// Column info not available, continue without it
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
const variant = args.variant || "execute";
|
|
2280
|
+
// Parse MCP-specific context options
|
|
2177
2281
|
const contextOptions = {};
|
|
2178
2282
|
if (args.includeSubtasks !== undefined) {
|
|
2179
2283
|
contextOptions.includeSubtasks =
|
|
@@ -2188,75 +2292,21 @@ async function handleToolCall(name, args, deps) {
|
|
|
2188
2292
|
args.includeDescription === true ||
|
|
2189
2293
|
args.includeDescription === "true";
|
|
2190
2294
|
}
|
|
2191
|
-
//
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
if (workspaceId && cardData.title) {
|
|
2198
|
-
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
2199
|
-
const taskContext = [cardData.title, cardData.description || ""]
|
|
2200
|
-
.filter(Boolean)
|
|
2201
|
-
.join(" ");
|
|
2202
|
-
const assembled = await assembleContext({
|
|
2203
|
-
workspaceId,
|
|
2204
|
-
projectId: getActiveProjectId() || undefined,
|
|
2205
|
-
taskContext,
|
|
2206
|
-
cardLabels,
|
|
2207
|
-
cardId: cardData.id,
|
|
2208
|
-
client,
|
|
2209
|
-
});
|
|
2210
|
-
if (assembled.context) {
|
|
2211
|
-
assembledContextStr = assembled.context;
|
|
2212
|
-
assemblyId = assembled.manifest.assemblyId;
|
|
2213
|
-
cacheManifest(assembled.manifest);
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
catch {
|
|
2218
|
-
// Context assembly failed, try legacy fallback
|
|
2219
|
-
try {
|
|
2220
|
-
const workspaceId = deps.getActiveWorkspaceId();
|
|
2221
|
-
if (workspaceId && cardData.title) {
|
|
2222
|
-
const memoryResult = await client.searchMemoryEntities(workspaceId, cardData.title, {
|
|
2223
|
-
project_id: getActiveProjectId() || undefined,
|
|
2224
|
-
limit: 5,
|
|
2225
|
-
});
|
|
2226
|
-
if (memoryResult.entities?.length > 0) {
|
|
2227
|
-
memories = memoryResult.entities.map((e) => {
|
|
2228
|
-
const entity = e;
|
|
2229
|
-
return {
|
|
2230
|
-
id: entity.id,
|
|
2231
|
-
type: entity.type,
|
|
2232
|
-
title: entity.title,
|
|
2233
|
-
content: entity.content,
|
|
2234
|
-
confidence: entity.confidence,
|
|
2235
|
-
tags: entity.tags || [],
|
|
2236
|
-
};
|
|
2237
|
-
});
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
catch {
|
|
2242
|
-
// Memory fetch also failed, continue without memories
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
const result = generatePrompt({
|
|
2246
|
-
card: cardData,
|
|
2247
|
-
column: columnData,
|
|
2248
|
-
variant,
|
|
2249
|
-
contextOptions,
|
|
2295
|
+
// Delegate to the shared prompt generation pipeline
|
|
2296
|
+
const result = await client.generateCardPrompt({
|
|
2297
|
+
cardId,
|
|
2298
|
+
workspaceId: deps.getActiveWorkspaceId() || "",
|
|
2299
|
+
projectId: args.projectId || getActiveProjectId() || undefined,
|
|
2300
|
+
variant: args.variant || "execute",
|
|
2250
2301
|
customConstraints: args.customConstraints,
|
|
2251
|
-
|
|
2252
|
-
assembledContext: assembledContextStr,
|
|
2253
|
-
assemblyId,
|
|
2302
|
+
contextOptions,
|
|
2254
2303
|
});
|
|
2304
|
+
// MCP-specific: cache the assembly manifest for the feedback loop
|
|
2305
|
+
if (result.assemblyId) {
|
|
2306
|
+
trackSessionAssembly(cardId, result.assemblyId);
|
|
2307
|
+
}
|
|
2255
2308
|
return {
|
|
2256
2309
|
success: true,
|
|
2257
|
-
cardId: cardData.id,
|
|
2258
|
-
shortId: cardData.short_id,
|
|
2259
|
-
title: cardData.title,
|
|
2260
2310
|
...result,
|
|
2261
2311
|
};
|
|
2262
2312
|
}
|
|
@@ -2270,6 +2320,8 @@ async function handleToolCall(name, args, deps) {
|
|
|
2270
2320
|
}
|
|
2271
2321
|
const entityType = args.type || "context";
|
|
2272
2322
|
const entityTags = args.tags || [];
|
|
2323
|
+
// Use session's agent identifier if available, otherwise null
|
|
2324
|
+
const activeMemSession = getActiveMemorySession();
|
|
2273
2325
|
const result = await client.createMemoryEntity({
|
|
2274
2326
|
workspace_id: workspaceId,
|
|
2275
2327
|
project_id: args.projectId || deps.getActiveProjectId() || undefined,
|
|
@@ -2283,7 +2335,7 @@ async function handleToolCall(name, args, deps) {
|
|
|
2283
2335
|
? z.number().min(0).max(1).parse(args.confidence)
|
|
2284
2336
|
: undefined,
|
|
2285
2337
|
tags: entityTags.length > 0 ? entityTags : undefined,
|
|
2286
|
-
agent_identifier:
|
|
2338
|
+
agent_identifier: activeMemSession?.agentIdentifier || undefined,
|
|
2287
2339
|
});
|
|
2288
2340
|
// Fire-and-forget graph expansion: link new entity to semantically similar ones
|
|
2289
2341
|
const newEntityIdForGraph = result.entity?.id;
|
|
@@ -2303,6 +2355,11 @@ async function handleToolCall(name, args, deps) {
|
|
|
2303
2355
|
// Don't block creation if contradiction detection fails
|
|
2304
2356
|
}
|
|
2305
2357
|
}
|
|
2358
|
+
// Track memory write action and flush (fire-and-forget)
|
|
2359
|
+
if (activeMemSession) {
|
|
2360
|
+
appendMemoryAction(activeMemSession.cardId, `Stored memory: ${title}`);
|
|
2361
|
+
flushMemoryActions(client, activeMemSession.cardId).catch(() => { });
|
|
2362
|
+
}
|
|
2306
2363
|
return {
|
|
2307
2364
|
success: true,
|
|
2308
2365
|
...result,
|
|
@@ -2373,6 +2430,11 @@ async function handleToolCall(name, args, deps) {
|
|
|
2373
2430
|
}
|
|
2374
2431
|
})).catch(() => { });
|
|
2375
2432
|
}
|
|
2433
|
+
// Track memory read (batched on flush)
|
|
2434
|
+
const recallMemSession = getActiveMemorySession();
|
|
2435
|
+
if (recallMemSession) {
|
|
2436
|
+
incrementMemoryReads(recallMemSession.cardId);
|
|
2437
|
+
}
|
|
2376
2438
|
return markdown || "No memories found.";
|
|
2377
2439
|
}
|
|
2378
2440
|
case "harmony_update_memory": {
|
|
@@ -2393,11 +2455,35 @@ async function handleToolCall(name, args, deps) {
|
|
|
2393
2455
|
if (args.metadata !== undefined)
|
|
2394
2456
|
updates.metadata = args.metadata;
|
|
2395
2457
|
const result = await client.updateMemoryEntity(entityId, updates);
|
|
2458
|
+
// Track memory write action and flush (fire-and-forget)
|
|
2459
|
+
const updateMemSession = getActiveMemorySession();
|
|
2460
|
+
if (updateMemSession) {
|
|
2461
|
+
const updateTitle = updates.title || entityId.slice(0, 8);
|
|
2462
|
+
appendMemoryAction(updateMemSession.cardId, `Updated memory: ${updateTitle}`);
|
|
2463
|
+
flushMemoryActions(client, updateMemSession.cardId).catch(() => { });
|
|
2464
|
+
}
|
|
2396
2465
|
return { success: true, ...result };
|
|
2397
2466
|
}
|
|
2398
2467
|
case "harmony_forget": {
|
|
2399
2468
|
const entityId = z.string().uuid().parse(args.entityId);
|
|
2469
|
+
// Fetch title before deletion for action tracking
|
|
2470
|
+
let forgetTitle = null;
|
|
2471
|
+
const forgetMemSession = getActiveMemorySession();
|
|
2472
|
+
if (forgetMemSession) {
|
|
2473
|
+
try {
|
|
2474
|
+
const { entity } = await client.getMemoryEntity(entityId);
|
|
2475
|
+
forgetTitle = entity?.title || null;
|
|
2476
|
+
}
|
|
2477
|
+
catch {
|
|
2478
|
+
// Non-fatal: use truncated ID if fetch fails
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2400
2481
|
await client.deleteMemoryEntity(entityId);
|
|
2482
|
+
// Track memory write action and flush (fire-and-forget)
|
|
2483
|
+
if (forgetMemSession) {
|
|
2484
|
+
appendMemoryAction(forgetMemSession.cardId, `Removed memory: ${forgetTitle || entityId.slice(0, 8)}`);
|
|
2485
|
+
flushMemoryActions(client, forgetMemSession.cardId).catch(() => { });
|
|
2486
|
+
}
|
|
2401
2487
|
return { success: true };
|
|
2402
2488
|
}
|
|
2403
2489
|
case "harmony_relate": {
|
|
@@ -2423,6 +2509,12 @@ async function handleToolCall(name, args, deps) {
|
|
|
2423
2509
|
? z.number().min(0).max(1).parse(args.confidence)
|
|
2424
2510
|
: undefined,
|
|
2425
2511
|
});
|
|
2512
|
+
// Track memory write action and flush (fire-and-forget)
|
|
2513
|
+
const relateMemSession = getActiveMemorySession();
|
|
2514
|
+
if (relateMemSession) {
|
|
2515
|
+
appendMemoryAction(relateMemSession.cardId, `Linked memories: ${relationType}`);
|
|
2516
|
+
flushMemoryActions(client, relateMemSession.cardId).catch(() => { });
|
|
2517
|
+
}
|
|
2426
2518
|
return { success: true, ...result };
|
|
2427
2519
|
}
|
|
2428
2520
|
case "harmony_memory_search": {
|
|
@@ -2438,6 +2530,11 @@ async function handleToolCall(name, args, deps) {
|
|
|
2438
2530
|
type: args.type,
|
|
2439
2531
|
limit: args.limit,
|
|
2440
2532
|
});
|
|
2533
|
+
// Track memory read (batched on flush)
|
|
2534
|
+
const searchMemSession = getActiveMemorySession();
|
|
2535
|
+
if (searchMemSession) {
|
|
2536
|
+
incrementMemoryReads(searchMemSession.cardId);
|
|
2537
|
+
}
|
|
2441
2538
|
return markdown || "No memories found.";
|
|
2442
2539
|
}
|
|
2443
2540
|
// Vault index
|
|
@@ -2537,7 +2634,7 @@ async function handleToolCall(name, args, deps) {
|
|
|
2537
2634
|
tasks: args.tasks,
|
|
2538
2635
|
});
|
|
2539
2636
|
// Build URL for viewing the plan
|
|
2540
|
-
const planUrl = `https://gethmy.com/plans/${result.plan.id}`;
|
|
2637
|
+
const planUrl = `https://app.gethmy.com/plans/${result.plan.id}`;
|
|
2541
2638
|
return {
|
|
2542
2639
|
success: true,
|
|
2543
2640
|
planId: result.plan.id,
|
|
@@ -3092,11 +3189,14 @@ export class HarmonyMCPServer {
|
|
|
3092
3189
|
const transport = new StdioServerTransport();
|
|
3093
3190
|
await this.server.connect(transport);
|
|
3094
3191
|
console.error("Harmony MCP server running on stdio");
|
|
3095
|
-
// Initialize auto-session tracking
|
|
3192
|
+
// Initialize auto-session tracking with MCP client identity detection
|
|
3096
3193
|
const configDeps = createConfigDeps();
|
|
3097
3194
|
initAutoSession(async (client, cardId, status) => {
|
|
3098
3195
|
await runEndSessionPipeline(client, configDeps, cardId, status);
|
|
3099
|
-
}, () => getClient())
|
|
3196
|
+
}, () => getClient(), () => {
|
|
3197
|
+
const cv = this.server.getClientVersion();
|
|
3198
|
+
return cv ? { name: cv.name, version: cv.version } : null;
|
|
3199
|
+
});
|
|
3100
3200
|
// Graceful shutdown: end all auto-sessions
|
|
3101
3201
|
const handleShutdown = async () => {
|
|
3102
3202
|
try {
|
package/dist/lib/skills.js
CHANGED
|
@@ -246,7 +246,7 @@ Once you have the plan ID, call \`harmony_get_plan\` to fetch the full plan with
|
|
|
246
246
|
## [Plan Title]
|
|
247
247
|
**Status:** draft/active/archived | **Phase:** plan/execute/verify/done
|
|
248
248
|
**Tasks:** N total (X pending, Y in_progress, Z completed)
|
|
249
|
-
**URL:** https://gethmy.com/plans/{id}
|
|
249
|
+
**URL:** https://app.gethmy.com/plans/{id}
|
|
250
250
|
\\\`\\\`\\\`
|
|
251
251
|
|
|
252
252
|
If plan is in execute phase and tasks already have linked cards, note which tasks have cards and which don't.
|
|
@@ -280,7 +280,7 @@ Only offer **(A) Single card**, **(C) Analyze only**, or **(D) Skip**.
|
|
|
280
280
|
#### Option A — Single card
|
|
281
281
|
1. Call \`harmony_create_card\` with:
|
|
282
282
|
- \`title\`: Plan title
|
|
283
|
-
- \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://gethmy.com/plans/{planId})\`
|
|
283
|
+
- \`description\`: Brief 2-3 sentence summary of the plan + \`\\n\\n[View plan](https://app.gethmy.com/plans/{planId})\`
|
|
284
284
|
- \`priority\`: based on plan task priorities (use highest)
|
|
285
285
|
2. Call \`harmony_update_plan\` to set \`status: "active"\`, \`workflowPhase: "execute"\`
|
|
286
286
|
|
|
@@ -402,7 +402,7 @@ Call \`harmony_create_plan\` with:
|
|
|
402
402
|
### 2B.5 — User Approval
|
|
403
403
|
|
|
404
404
|
Show the user:
|
|
405
|
-
- Plan URL: \`https://gethmy.com/plans/{id}\`
|
|
405
|
+
- Plan URL: \`https://app.gethmy.com/plans/{id}\`
|
|
406
406
|
- Number of tasks created
|
|
407
407
|
- Brief summary
|
|
408
408
|
|
package/dist/lib/tui/setup.js
CHANGED
|
@@ -11,7 +11,7 @@ import { getWriteSummary, writeFilesWithProgress } from "./writer.js";
|
|
|
11
11
|
// Central skills directory for global installation
|
|
12
12
|
const GLOBAL_SKILLS_DIR = join(homedir(), ".agents", "skills");
|
|
13
13
|
// API base URL
|
|
14
|
-
const API_URL = "https://gethmy.com/api";
|
|
14
|
+
const API_URL = "https://app.gethmy.com/api";
|
|
15
15
|
/**
|
|
16
16
|
* Register MCP server using Claude CLI
|
|
17
17
|
* Returns true if successful, false if CLI unavailable or failed
|
|
@@ -422,7 +422,7 @@ export async function runSetup(options = {}) {
|
|
|
422
422
|
if (!validation.valid) {
|
|
423
423
|
spinner.stop(colors.error("API key validation failed"));
|
|
424
424
|
p.log.error(validation.error || "Could not connect to Harmony API");
|
|
425
|
-
p.log.info("Get an API key at: https://gethmy.com/user/keys");
|
|
425
|
+
p.log.info("Get an API key at: https://app.gethmy.com/user/keys");
|
|
426
426
|
process.exit(1);
|
|
427
427
|
}
|
|
428
428
|
if (!userEmail) {
|
|
@@ -765,6 +765,6 @@ export async function runSetup(options = {}) {
|
|
|
765
765
|
}
|
|
766
766
|
console.log("");
|
|
767
767
|
console.log(` ${colors.dim("Add to new project: npx @gethmy/mcp setup")}`);
|
|
768
|
-
console.log(` ${colors.dim("Need help? Visit https://gethmy.com/docs/mcp")}`);
|
|
768
|
+
console.log(` ${colors.dim("Need help? Visit https://app.gethmy.com/docs/mcp")}`);
|
|
769
769
|
console.log("");
|
|
770
770
|
}
|
package/package.json
CHANGED
|
@@ -51,16 +51,16 @@ describe("auto-start", () => {
|
|
|
51
51
|
|
|
52
52
|
expect(client.startAgentSession).toHaveBeenCalledTimes(1);
|
|
53
53
|
expect(client.startAgentSession).toHaveBeenCalledWith(CARD_A, {
|
|
54
|
-
agentIdentifier: "
|
|
55
|
-
agentName: "
|
|
54
|
+
agentIdentifier: "unknown",
|
|
55
|
+
agentName: "Unknown Agent",
|
|
56
56
|
status: "working",
|
|
57
57
|
});
|
|
58
58
|
expect(getActiveSessions().size).toBe(1);
|
|
59
59
|
const session = getActiveSessions().get(CARD_A);
|
|
60
60
|
expect(session).toBeDefined();
|
|
61
61
|
expect(session!.isExplicit).toBe(false);
|
|
62
|
-
expect(session!.agentIdentifier).toBe("
|
|
63
|
-
expect(session!.agentName).toBe("
|
|
62
|
+
expect(session!.agentIdentifier).toBe("unknown");
|
|
63
|
+
expect(session!.agentName).toBe("Unknown Agent");
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test("does NOT trigger on autoStart=false", async () => {
|
|
@@ -109,7 +109,7 @@ describe("auto-start", () => {
|
|
|
109
109
|
|
|
110
110
|
// Should still be tracked despite API error
|
|
111
111
|
expect(getActiveSessions().size).toBe(1);
|
|
112
|
-
expect(getActiveSessions().get(CARD_A)!.agentIdentifier).toBe("
|
|
112
|
+
expect(getActiveSessions().get(CARD_A)!.agentIdentifier).toBe("unknown");
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
test("uses clientGetter when no client in options", async () => {
|
|
@@ -328,8 +328,8 @@ describe("inactivity timeout", () => {
|
|
|
328
328
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 1000,
|
|
329
329
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 1000,
|
|
330
330
|
isExplicit: false,
|
|
331
|
-
agentIdentifier: "
|
|
332
|
-
agentName: "
|
|
331
|
+
agentIdentifier: "unknown",
|
|
332
|
+
agentName: "Unknown Agent",
|
|
333
333
|
});
|
|
334
334
|
|
|
335
335
|
checkInactivity();
|
|
@@ -355,8 +355,8 @@ describe("inactivity timeout", () => {
|
|
|
355
355
|
startedAt: Date.now(),
|
|
356
356
|
lastActivityAt: Date.now(), // just now — well within timeout
|
|
357
357
|
isExplicit: false,
|
|
358
|
-
agentIdentifier: "
|
|
359
|
-
agentName: "
|
|
358
|
+
agentIdentifier: "unknown",
|
|
359
|
+
agentName: "Unknown Agent",
|
|
360
360
|
});
|
|
361
361
|
|
|
362
362
|
checkInactivity();
|
|
@@ -398,8 +398,8 @@ describe("inactivity timeout", () => {
|
|
|
398
398
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
399
399
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
400
400
|
isExplicit: false,
|
|
401
|
-
agentIdentifier: "
|
|
402
|
-
agentName: "
|
|
401
|
+
agentIdentifier: "unknown",
|
|
402
|
+
agentName: "Unknown Agent",
|
|
403
403
|
});
|
|
404
404
|
|
|
405
405
|
// CARD_B — still active
|
|
@@ -408,8 +408,8 @@ describe("inactivity timeout", () => {
|
|
|
408
408
|
startedAt: Date.now() - 1000,
|
|
409
409
|
lastActivityAt: Date.now() - 1000,
|
|
410
410
|
isExplicit: false,
|
|
411
|
-
agentIdentifier: "
|
|
412
|
-
agentName: "
|
|
411
|
+
agentIdentifier: "unknown",
|
|
412
|
+
agentName: "Unknown Agent",
|
|
413
413
|
});
|
|
414
414
|
|
|
415
415
|
// CARD_C — timed out but explicit
|
|
@@ -442,8 +442,8 @@ describe("inactivity timeout", () => {
|
|
|
442
442
|
startedAt: 0,
|
|
443
443
|
lastActivityAt: 0,
|
|
444
444
|
isExplicit: false,
|
|
445
|
-
agentIdentifier: "
|
|
446
|
-
agentName: "
|
|
445
|
+
agentIdentifier: "unknown",
|
|
446
|
+
agentName: "Unknown Agent",
|
|
447
447
|
});
|
|
448
448
|
|
|
449
449
|
// Should not throw
|
|
@@ -464,8 +464,8 @@ describe("inactivity timeout", () => {
|
|
|
464
464
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
465
465
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
466
466
|
isExplicit: false,
|
|
467
|
-
agentIdentifier: "
|
|
468
|
-
agentName: "
|
|
467
|
+
agentIdentifier: "unknown",
|
|
468
|
+
agentName: "Unknown Agent",
|
|
469
469
|
});
|
|
470
470
|
|
|
471
471
|
// Refresh activity
|
|
@@ -495,8 +495,8 @@ describe("markExplicit", () => {
|
|
|
495
495
|
|
|
496
496
|
markExplicit(CARD_A);
|
|
497
497
|
expect(getActiveSessions().get(CARD_A)!.isExplicit).toBe(true);
|
|
498
|
-
// agentIdentifier should remain "
|
|
499
|
-
expect(getActiveSessions().get(CARD_A)!.agentIdentifier).toBe("
|
|
498
|
+
// agentIdentifier should remain "unknown" since no clientInfo was provided
|
|
499
|
+
expect(getActiveSessions().get(CARD_A)!.agentIdentifier).toBe("unknown");
|
|
500
500
|
});
|
|
501
501
|
|
|
502
502
|
test("creates new tracking entry for unknown card", () => {
|
|
@@ -583,8 +583,8 @@ describe("shutdown", () => {
|
|
|
583
583
|
startedAt: Date.now(),
|
|
584
584
|
lastActivityAt: Date.now(),
|
|
585
585
|
isExplicit: false,
|
|
586
|
-
agentIdentifier: "
|
|
587
|
-
agentName: "
|
|
586
|
+
agentIdentifier: "unknown",
|
|
587
|
+
agentName: "Unknown Agent",
|
|
588
588
|
});
|
|
589
589
|
|
|
590
590
|
await shutdownAllSessions();
|
|
@@ -606,8 +606,8 @@ describe("shutdown", () => {
|
|
|
606
606
|
startedAt: Date.now(),
|
|
607
607
|
lastActivityAt: Date.now(),
|
|
608
608
|
isExplicit: false,
|
|
609
|
-
agentIdentifier: "
|
|
610
|
-
agentName: "
|
|
609
|
+
agentIdentifier: "unknown",
|
|
610
|
+
agentName: "Unknown Agent",
|
|
611
611
|
});
|
|
612
612
|
getActiveSessions().set(CARD_B, {
|
|
613
613
|
cardId: CARD_B,
|
|
@@ -633,8 +633,8 @@ describe("shutdown", () => {
|
|
|
633
633
|
startedAt: Date.now(),
|
|
634
634
|
lastActivityAt: Date.now(),
|
|
635
635
|
isExplicit: false,
|
|
636
|
-
agentIdentifier: "
|
|
637
|
-
agentName: "
|
|
636
|
+
agentIdentifier: "unknown",
|
|
637
|
+
agentName: "Unknown Agent",
|
|
638
638
|
});
|
|
639
639
|
|
|
640
640
|
// Should not throw
|
|
@@ -660,16 +660,16 @@ describe("shutdown", () => {
|
|
|
660
660
|
startedAt: Date.now(),
|
|
661
661
|
lastActivityAt: Date.now(),
|
|
662
662
|
isExplicit: false,
|
|
663
|
-
agentIdentifier: "
|
|
664
|
-
agentName: "
|
|
663
|
+
agentIdentifier: "unknown",
|
|
664
|
+
agentName: "Unknown Agent",
|
|
665
665
|
});
|
|
666
666
|
getActiveSessions().set(CARD_B, {
|
|
667
667
|
cardId: CARD_B,
|
|
668
668
|
startedAt: Date.now(),
|
|
669
669
|
lastActivityAt: Date.now(),
|
|
670
670
|
isExplicit: false,
|
|
671
|
-
agentIdentifier: "
|
|
672
|
-
agentName: "
|
|
671
|
+
agentIdentifier: "unknown",
|
|
672
|
+
agentName: "Unknown Agent",
|
|
673
673
|
});
|
|
674
674
|
|
|
675
675
|
await shutdownAllSessions();
|
|
@@ -826,8 +826,8 @@ describe("edge cases", () => {
|
|
|
826
826
|
startedAt: 0,
|
|
827
827
|
lastActivityAt: 0,
|
|
828
828
|
isExplicit: false,
|
|
829
|
-
agentIdentifier: "
|
|
830
|
-
agentName: "
|
|
829
|
+
agentIdentifier: "unknown",
|
|
830
|
+
agentName: "Unknown Agent",
|
|
831
831
|
});
|
|
832
832
|
|
|
833
833
|
checkInactivity();
|
|
@@ -850,8 +850,8 @@ describe("edge cases", () => {
|
|
|
850
850
|
startedAt: 0,
|
|
851
851
|
lastActivityAt: 0,
|
|
852
852
|
isExplicit: false,
|
|
853
|
-
agentIdentifier: "
|
|
854
|
-
agentName: "
|
|
853
|
+
agentIdentifier: "unknown",
|
|
854
|
+
agentName: "Unknown Agent",
|
|
855
855
|
});
|
|
856
856
|
|
|
857
857
|
checkInactivity();
|