@gethmy/mcp 2.2.0 → 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 +24292 -24173
- package/dist/index.js +24520 -24401
- package/dist/lib/__tests__/auto-session.test.js +33 -33
- package/dist/lib/api-client.js +116 -0
- package/dist/lib/auto-session.js +41 -5
- package/dist/lib/server.js +21 -96
- 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 +54 -4
- package/src/remote.ts +2 -1
- package/src/server.ts +25 -119
|
@@ -29,16 +29,16 @@ describe("auto-start", () => {
|
|
|
29
29
|
await trackActivity(CARD_A, { autoStart: true, client: client });
|
|
30
30
|
expect(client.startAgentSession).toHaveBeenCalledTimes(1);
|
|
31
31
|
expect(client.startAgentSession).toHaveBeenCalledWith(CARD_A, {
|
|
32
|
-
agentIdentifier: "
|
|
33
|
-
agentName: "
|
|
32
|
+
agentIdentifier: "unknown",
|
|
33
|
+
agentName: "Unknown Agent",
|
|
34
34
|
status: "working",
|
|
35
35
|
});
|
|
36
36
|
expect(getActiveSessions().size).toBe(1);
|
|
37
37
|
const session = getActiveSessions().get(CARD_A);
|
|
38
38
|
expect(session).toBeDefined();
|
|
39
39
|
expect(session.isExplicit).toBe(false);
|
|
40
|
-
expect(session.agentIdentifier).toBe("
|
|
41
|
-
expect(session.agentName).toBe("
|
|
40
|
+
expect(session.agentIdentifier).toBe("unknown");
|
|
41
|
+
expect(session.agentName).toBe("Unknown Agent");
|
|
42
42
|
});
|
|
43
43
|
test("does NOT trigger on autoStart=false", async () => {
|
|
44
44
|
const client = makeMockClient();
|
|
@@ -68,7 +68,7 @@ describe("auto-start", () => {
|
|
|
68
68
|
await trackActivity(CARD_A, { autoStart: true, client: client });
|
|
69
69
|
// Should still be tracked despite API error
|
|
70
70
|
expect(getActiveSessions().size).toBe(1);
|
|
71
|
-
expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("
|
|
71
|
+
expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("unknown");
|
|
72
72
|
});
|
|
73
73
|
test("uses clientGetter when no client in options", async () => {
|
|
74
74
|
const client = makeMockClient();
|
|
@@ -222,8 +222,8 @@ describe("inactivity timeout", () => {
|
|
|
222
222
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 1000,
|
|
223
223
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 1000,
|
|
224
224
|
isExplicit: false,
|
|
225
|
-
agentIdentifier: "
|
|
226
|
-
agentName: "
|
|
225
|
+
agentIdentifier: "unknown",
|
|
226
|
+
agentName: "Unknown Agent",
|
|
227
227
|
});
|
|
228
228
|
checkInactivity();
|
|
229
229
|
// autoEndSession is fire-and-forget in checkInactivity, wait for it
|
|
@@ -241,8 +241,8 @@ describe("inactivity timeout", () => {
|
|
|
241
241
|
startedAt: Date.now(),
|
|
242
242
|
lastActivityAt: Date.now(), // just now — well within timeout
|
|
243
243
|
isExplicit: false,
|
|
244
|
-
agentIdentifier: "
|
|
245
|
-
agentName: "
|
|
244
|
+
agentIdentifier: "unknown",
|
|
245
|
+
agentName: "Unknown Agent",
|
|
246
246
|
});
|
|
247
247
|
checkInactivity();
|
|
248
248
|
expect(client.endAgentSession).not.toHaveBeenCalled();
|
|
@@ -273,8 +273,8 @@ describe("inactivity timeout", () => {
|
|
|
273
273
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
274
274
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
275
275
|
isExplicit: false,
|
|
276
|
-
agentIdentifier: "
|
|
277
|
-
agentName: "
|
|
276
|
+
agentIdentifier: "unknown",
|
|
277
|
+
agentName: "Unknown Agent",
|
|
278
278
|
});
|
|
279
279
|
// CARD_B — still active
|
|
280
280
|
getActiveSessions().set(CARD_B, {
|
|
@@ -282,8 +282,8 @@ describe("inactivity timeout", () => {
|
|
|
282
282
|
startedAt: Date.now() - 1000,
|
|
283
283
|
lastActivityAt: Date.now() - 1000,
|
|
284
284
|
isExplicit: false,
|
|
285
|
-
agentIdentifier: "
|
|
286
|
-
agentName: "
|
|
285
|
+
agentIdentifier: "unknown",
|
|
286
|
+
agentName: "Unknown Agent",
|
|
287
287
|
});
|
|
288
288
|
// CARD_C — timed out but explicit
|
|
289
289
|
getActiveSessions().set(CARD_C, {
|
|
@@ -312,8 +312,8 @@ describe("inactivity timeout", () => {
|
|
|
312
312
|
startedAt: 0,
|
|
313
313
|
lastActivityAt: 0,
|
|
314
314
|
isExplicit: false,
|
|
315
|
-
agentIdentifier: "
|
|
316
|
-
agentName: "
|
|
315
|
+
agentIdentifier: "unknown",
|
|
316
|
+
agentName: "Unknown Agent",
|
|
317
317
|
});
|
|
318
318
|
// Should not throw
|
|
319
319
|
checkInactivity();
|
|
@@ -330,8 +330,8 @@ describe("inactivity timeout", () => {
|
|
|
330
330
|
startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
331
331
|
lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
|
|
332
332
|
isExplicit: false,
|
|
333
|
-
agentIdentifier: "
|
|
334
|
-
agentName: "
|
|
333
|
+
agentIdentifier: "unknown",
|
|
334
|
+
agentName: "Unknown Agent",
|
|
335
335
|
});
|
|
336
336
|
// Refresh activity
|
|
337
337
|
await trackActivity(CARD_A, { autoStart: false });
|
|
@@ -351,8 +351,8 @@ describe("markExplicit", () => {
|
|
|
351
351
|
expect(getActiveSessions().get(CARD_A).isExplicit).toBe(false);
|
|
352
352
|
markExplicit(CARD_A);
|
|
353
353
|
expect(getActiveSessions().get(CARD_A).isExplicit).toBe(true);
|
|
354
|
-
// agentIdentifier should remain "
|
|
355
|
-
expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("
|
|
354
|
+
// agentIdentifier should remain "unknown" since no clientInfo was provided
|
|
355
|
+
expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("unknown");
|
|
356
356
|
});
|
|
357
357
|
test("creates new tracking entry for unknown card", () => {
|
|
358
358
|
initAutoSession(mock(async () => { }), () => makeMockClient());
|
|
@@ -406,8 +406,8 @@ describe("shutdown", () => {
|
|
|
406
406
|
startedAt: Date.now(),
|
|
407
407
|
lastActivityAt: Date.now(),
|
|
408
408
|
isExplicit: false,
|
|
409
|
-
agentIdentifier: "
|
|
410
|
-
agentName: "
|
|
409
|
+
agentIdentifier: "unknown",
|
|
410
|
+
agentName: "Unknown Agent",
|
|
411
411
|
});
|
|
412
412
|
await shutdownAllSessions();
|
|
413
413
|
expect(client.endAgentSession).toHaveBeenCalledWith(CARD_A, {
|
|
@@ -425,8 +425,8 @@ describe("shutdown", () => {
|
|
|
425
425
|
startedAt: Date.now(),
|
|
426
426
|
lastActivityAt: Date.now(),
|
|
427
427
|
isExplicit: false,
|
|
428
|
-
agentIdentifier: "
|
|
429
|
-
agentName: "
|
|
428
|
+
agentIdentifier: "unknown",
|
|
429
|
+
agentName: "Unknown Agent",
|
|
430
430
|
});
|
|
431
431
|
getActiveSessions().set(CARD_B, {
|
|
432
432
|
cardId: CARD_B,
|
|
@@ -449,8 +449,8 @@ describe("shutdown", () => {
|
|
|
449
449
|
startedAt: Date.now(),
|
|
450
450
|
lastActivityAt: Date.now(),
|
|
451
451
|
isExplicit: false,
|
|
452
|
-
agentIdentifier: "
|
|
453
|
-
agentName: "
|
|
452
|
+
agentIdentifier: "unknown",
|
|
453
|
+
agentName: "Unknown Agent",
|
|
454
454
|
});
|
|
455
455
|
// Should not throw
|
|
456
456
|
await shutdownAllSessions();
|
|
@@ -473,16 +473,16 @@ describe("shutdown", () => {
|
|
|
473
473
|
startedAt: Date.now(),
|
|
474
474
|
lastActivityAt: Date.now(),
|
|
475
475
|
isExplicit: false,
|
|
476
|
-
agentIdentifier: "
|
|
477
|
-
agentName: "
|
|
476
|
+
agentIdentifier: "unknown",
|
|
477
|
+
agentName: "Unknown Agent",
|
|
478
478
|
});
|
|
479
479
|
getActiveSessions().set(CARD_B, {
|
|
480
480
|
cardId: CARD_B,
|
|
481
481
|
startedAt: Date.now(),
|
|
482
482
|
lastActivityAt: Date.now(),
|
|
483
483
|
isExplicit: false,
|
|
484
|
-
agentIdentifier: "
|
|
485
|
-
agentName: "
|
|
484
|
+
agentIdentifier: "unknown",
|
|
485
|
+
agentName: "Unknown Agent",
|
|
486
486
|
});
|
|
487
487
|
await shutdownAllSessions();
|
|
488
488
|
// Both should be removed from tracking despite first API error
|
|
@@ -595,8 +595,8 @@ describe("edge cases", () => {
|
|
|
595
595
|
startedAt: 0,
|
|
596
596
|
lastActivityAt: 0,
|
|
597
597
|
isExplicit: false,
|
|
598
|
-
agentIdentifier: "
|
|
599
|
-
agentName: "
|
|
598
|
+
agentIdentifier: "unknown",
|
|
599
|
+
agentName: "Unknown Agent",
|
|
600
600
|
});
|
|
601
601
|
checkInactivity();
|
|
602
602
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -615,8 +615,8 @@ describe("edge cases", () => {
|
|
|
615
615
|
startedAt: 0,
|
|
616
616
|
lastActivityAt: 0,
|
|
617
617
|
isExplicit: false,
|
|
618
|
-
agentIdentifier: "
|
|
619
|
-
agentName: "
|
|
618
|
+
agentIdentifier: "unknown",
|
|
619
|
+
agentName: "Unknown Agent",
|
|
620
620
|
});
|
|
621
621
|
checkInactivity();
|
|
622
622
|
await new Promise((r) => setTimeout(r, 50));
|
package/dist/lib/api-client.js
CHANGED
|
@@ -536,6 +536,122 @@ export class HarmonyApiClient {
|
|
|
536
536
|
async generateApiKey(name) {
|
|
537
537
|
return this.request("POST", "/api-keys", { name });
|
|
538
538
|
}
|
|
539
|
+
// ============ PROMPT GENERATION ============
|
|
540
|
+
/**
|
|
541
|
+
* Generate a prompt for a card with full memory context assembly.
|
|
542
|
+
*
|
|
543
|
+
* This is the shared entry point for prompt generation — used by the MCP
|
|
544
|
+
* server tool handler and the agent daemon. It fetches the card, assembles
|
|
545
|
+
* relevant memories, and produces a role-framed prompt.
|
|
546
|
+
*/
|
|
547
|
+
async generateCardPrompt(options) {
|
|
548
|
+
const { assembleContext, cacheManifest, generatePrompt } = await loadPromptModules();
|
|
549
|
+
// Fetch card data
|
|
550
|
+
const cardResult = await this.getCard(options.cardId);
|
|
551
|
+
const cardData = cardResult.card;
|
|
552
|
+
// Try to get column info
|
|
553
|
+
let columnData = null;
|
|
554
|
+
const projectIdForBoard = options.projectId || cardData.project_id;
|
|
555
|
+
if (projectIdForBoard) {
|
|
556
|
+
try {
|
|
557
|
+
const board = await this.getBoard(projectIdForBoard, { summary: true });
|
|
558
|
+
const column = board.columns.find((col) => col.id === cardData.column_id);
|
|
559
|
+
if (column) {
|
|
560
|
+
columnData = { name: column.name };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
// Column info not available, continue without it
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const variant = options.variant || "execute";
|
|
568
|
+
// Assemble memory context
|
|
569
|
+
let assembledContextStr;
|
|
570
|
+
let assemblyId;
|
|
571
|
+
let memories;
|
|
572
|
+
try {
|
|
573
|
+
if (options.workspaceId && cardData.title) {
|
|
574
|
+
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
575
|
+
const taskContext = [cardData.title, cardData.description || ""]
|
|
576
|
+
.filter(Boolean)
|
|
577
|
+
.join(" ");
|
|
578
|
+
const assembled = await assembleContext({
|
|
579
|
+
workspaceId: options.workspaceId,
|
|
580
|
+
projectId: options.projectId,
|
|
581
|
+
taskContext,
|
|
582
|
+
cardLabels,
|
|
583
|
+
cardId: cardData.id,
|
|
584
|
+
client: this,
|
|
585
|
+
});
|
|
586
|
+
if (assembled.context) {
|
|
587
|
+
assembledContextStr = assembled.context;
|
|
588
|
+
assemblyId = assembled.manifest.assemblyId;
|
|
589
|
+
cacheManifest(assembled.manifest);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
// Context assembly failed, try legacy fallback
|
|
595
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
596
|
+
console.debug(`[generateCardPrompt] Context assembly failed: ${msg}`);
|
|
597
|
+
try {
|
|
598
|
+
if (options.workspaceId && cardData.title) {
|
|
599
|
+
const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
|
|
600
|
+
project_id: options.projectId,
|
|
601
|
+
limit: 5,
|
|
602
|
+
});
|
|
603
|
+
if (memoryResult.entities?.length > 0) {
|
|
604
|
+
memories = memoryResult.entities.map((e) => ({
|
|
605
|
+
id: e.id,
|
|
606
|
+
type: e.type,
|
|
607
|
+
title: e.title,
|
|
608
|
+
content: e.content,
|
|
609
|
+
confidence: e.confidence,
|
|
610
|
+
tags: e.tags || [],
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch (fallbackErr) {
|
|
616
|
+
const fallbackMsg = fallbackErr instanceof Error
|
|
617
|
+
? fallbackErr.message
|
|
618
|
+
: String(fallbackErr);
|
|
619
|
+
console.debug(`[generateCardPrompt] Memory fallback also failed: ${fallbackMsg}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const result = generatePrompt({
|
|
623
|
+
card: cardData,
|
|
624
|
+
column: columnData,
|
|
625
|
+
variant,
|
|
626
|
+
contextOptions: options.contextOptions,
|
|
627
|
+
customConstraints: options.customConstraints,
|
|
628
|
+
memories,
|
|
629
|
+
assembledContext: assembledContextStr,
|
|
630
|
+
assemblyId,
|
|
631
|
+
});
|
|
632
|
+
return {
|
|
633
|
+
...result,
|
|
634
|
+
cardId: cardData.id,
|
|
635
|
+
shortId: cardData.short_id,
|
|
636
|
+
title: cardData.title,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// Cached dynamic imports for context-assembly and prompt-builder
|
|
641
|
+
let _promptModules = null;
|
|
642
|
+
async function loadPromptModules() {
|
|
643
|
+
if (!_promptModules) {
|
|
644
|
+
const [ca, pb] = await Promise.all([
|
|
645
|
+
import("./context-assembly.js"),
|
|
646
|
+
import("./prompt-builder.js"),
|
|
647
|
+
]);
|
|
648
|
+
_promptModules = {
|
|
649
|
+
assembleContext: ca.assembleContext,
|
|
650
|
+
cacheManifest: ca.cacheManifest,
|
|
651
|
+
generatePrompt: pb.generatePrompt,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return _promptModules;
|
|
539
655
|
}
|
|
540
656
|
// Singleton instance
|
|
541
657
|
let client = null;
|
package/dist/lib/auto-session.js
CHANGED
|
@@ -4,7 +4,36 @@
|
|
|
4
4
|
* Automatically detects agent session boundaries by monitoring tool calls.
|
|
5
5
|
* Sessions auto-start when card-mutating tools are called, and auto-end
|
|
6
6
|
* after 10 minutes of inactivity or when a different card is worked on.
|
|
7
|
+
*
|
|
8
|
+
* Agent identity is resolved from the MCP client's `initialize` handshake
|
|
9
|
+
* (clientInfo.name), so "Claude Code", "Cursor", "Windsurf", etc. are
|
|
10
|
+
* detected automatically — no hardcoded fallback needed.
|
|
7
11
|
*/
|
|
12
|
+
/** Well-known MCP client names → human-friendly display names */
|
|
13
|
+
const CLIENT_DISPLAY_NAMES = {
|
|
14
|
+
"claude-code": "Claude Code",
|
|
15
|
+
"claude-desktop": "Claude Desktop",
|
|
16
|
+
cursor: "Cursor",
|
|
17
|
+
windsurf: "Windsurf",
|
|
18
|
+
cline: "Cline",
|
|
19
|
+
continue: "Continue",
|
|
20
|
+
"codex-cli": "OpenAI Codex",
|
|
21
|
+
zed: "Zed",
|
|
22
|
+
"gemini-cli": "Gemini CLI",
|
|
23
|
+
};
|
|
24
|
+
/** Derive a slug-style identifier from a client name */
|
|
25
|
+
function toIdentifier(name) {
|
|
26
|
+
return name.toLowerCase().replace(/\s+/g, "-");
|
|
27
|
+
}
|
|
28
|
+
/** Resolve agent identity from MCP client info */
|
|
29
|
+
export function resolveAgentIdentity(info) {
|
|
30
|
+
if (!info?.name) {
|
|
31
|
+
return { agentIdentifier: "unknown", agentName: "Unknown Agent" };
|
|
32
|
+
}
|
|
33
|
+
const key = toIdentifier(info.name);
|
|
34
|
+
const displayName = CLIENT_DISPLAY_NAMES[key] ?? info.name; // use raw name if not in map
|
|
35
|
+
return { agentIdentifier: key, agentName: displayName };
|
|
36
|
+
}
|
|
8
37
|
/** Tools that trigger auto-start of a session */
|
|
9
38
|
export const AUTO_START_TRIGGERS = new Set([
|
|
10
39
|
"harmony_generate_prompt",
|
|
@@ -21,14 +50,17 @@ const activeSessions = new Map();
|
|
|
21
50
|
let inactivityTimer = null;
|
|
22
51
|
let endCallback = null;
|
|
23
52
|
let clientGetter = null;
|
|
53
|
+
let clientInfoGetter = null;
|
|
24
54
|
/**
|
|
25
55
|
* Initialize auto-session tracking.
|
|
26
56
|
* @param callback Called when an auto-session ends (runs the learning pipeline)
|
|
27
57
|
* @param getClient Function to get the current API client
|
|
58
|
+
* @param getClientInfo Function to get MCP client identity from the initialize handshake
|
|
28
59
|
*/
|
|
29
|
-
export function initAutoSession(callback, getClient) {
|
|
60
|
+
export function initAutoSession(callback, getClient, getClientInfo) {
|
|
30
61
|
endCallback = callback;
|
|
31
62
|
clientGetter = getClient;
|
|
63
|
+
clientInfoGetter = getClientInfo ?? null;
|
|
32
64
|
if (inactivityTimer)
|
|
33
65
|
clearInterval(inactivityTimer);
|
|
34
66
|
inactivityTimer = setInterval(checkInactivity, CHECK_INTERVAL_MS);
|
|
@@ -60,11 +92,14 @@ export async function trackActivity(cardId, options) {
|
|
|
60
92
|
for (const otherCardId of toEnd) {
|
|
61
93
|
await autoEndSession(client, otherCardId, "completed");
|
|
62
94
|
}
|
|
95
|
+
// Resolve agent identity from MCP client info
|
|
96
|
+
const info = clientInfoGetter?.() ?? null;
|
|
97
|
+
const { agentIdentifier, agentName } = resolveAgentIdentity(info);
|
|
63
98
|
// Start a new auto-session
|
|
64
99
|
try {
|
|
65
100
|
await client.startAgentSession(cardId, {
|
|
66
|
-
agentIdentifier
|
|
67
|
-
agentName
|
|
101
|
+
agentIdentifier,
|
|
102
|
+
agentName,
|
|
68
103
|
status: "working",
|
|
69
104
|
});
|
|
70
105
|
}
|
|
@@ -76,8 +111,8 @@ export async function trackActivity(cardId, options) {
|
|
|
76
111
|
startedAt: now,
|
|
77
112
|
lastActivityAt: now,
|
|
78
113
|
isExplicit: false,
|
|
79
|
-
agentIdentifier
|
|
80
|
-
agentName
|
|
114
|
+
agentIdentifier,
|
|
115
|
+
agentName,
|
|
81
116
|
});
|
|
82
117
|
}
|
|
83
118
|
/**
|
|
@@ -134,6 +169,7 @@ export function destroyAutoSession() {
|
|
|
134
169
|
activeSessions.clear();
|
|
135
170
|
endCallback = null;
|
|
136
171
|
clientGetter = null;
|
|
172
|
+
clientInfoGetter = null;
|
|
137
173
|
}
|
|
138
174
|
/**
|
|
139
175
|
* Get a snapshot of active sessions (for testing/debugging).
|
package/dist/lib/server.js
CHANGED
|
@@ -11,7 +11,6 @@ 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
|
-
import { generatePrompt, } from "./prompt-builder.js";
|
|
15
14
|
const memorySessions = new Map();
|
|
16
15
|
function initMemorySession(cardId, agentIdentifier, agentName) {
|
|
17
16
|
memorySessions.set(cardId, {
|
|
@@ -2261,13 +2260,10 @@ async function handleToolCall(name, args, deps) {
|
|
|
2261
2260
|
}
|
|
2262
2261
|
// Prompt generation
|
|
2263
2262
|
case "harmony_generate_prompt": {
|
|
2264
|
-
//
|
|
2265
|
-
let
|
|
2266
|
-
let columnData = null;
|
|
2263
|
+
// Resolve card ID — either directly or via short ID
|
|
2264
|
+
let cardId;
|
|
2267
2265
|
if (args.cardId) {
|
|
2268
|
-
|
|
2269
|
-
const cardResult = await client.getCard(cardId);
|
|
2270
|
-
cardData = cardResult.card;
|
|
2266
|
+
cardId = z.string().uuid().parse(args.cardId);
|
|
2271
2267
|
}
|
|
2272
2268
|
else if (args.shortId !== undefined) {
|
|
2273
2269
|
const shortId = z.number().int().positive().parse(args.shortId);
|
|
@@ -2276,32 +2272,12 @@ async function handleToolCall(name, args, deps) {
|
|
|
2276
2272
|
throw new Error("Project ID required when using shortId. Use harmony_set_project_context or provide projectId.");
|
|
2277
2273
|
}
|
|
2278
2274
|
const cardResult = await client.getCardByShortId(projectId, shortId);
|
|
2279
|
-
|
|
2275
|
+
cardId = cardResult.card.id;
|
|
2280
2276
|
}
|
|
2281
2277
|
else {
|
|
2282
2278
|
throw new Error("Either cardId or shortId must be provided");
|
|
2283
2279
|
}
|
|
2284
|
-
//
|
|
2285
|
-
const projectIdForBoard = args.projectId ||
|
|
2286
|
-
getActiveProjectId() ||
|
|
2287
|
-
cardData.project_id;
|
|
2288
|
-
if (projectIdForBoard) {
|
|
2289
|
-
try {
|
|
2290
|
-
const board = await client.getBoard(projectIdForBoard, {
|
|
2291
|
-
summary: true,
|
|
2292
|
-
});
|
|
2293
|
-
const columnId = cardData
|
|
2294
|
-
.column_id;
|
|
2295
|
-
const column = board.columns.find((col) => col.id === columnId);
|
|
2296
|
-
if (column) {
|
|
2297
|
-
columnData = { name: column.name };
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
catch {
|
|
2301
|
-
// Column info not available, continue without it
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
const variant = args.variant || "execute";
|
|
2280
|
+
// Parse MCP-specific context options
|
|
2305
2281
|
const contextOptions = {};
|
|
2306
2282
|
if (args.includeSubtasks !== undefined) {
|
|
2307
2283
|
contextOptions.includeSubtasks =
|
|
@@ -2316,75 +2292,21 @@ async function handleToolCall(name, args, deps) {
|
|
|
2316
2292
|
args.includeDescription === true ||
|
|
2317
2293
|
args.includeDescription === "true";
|
|
2318
2294
|
}
|
|
2319
|
-
//
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
if (workspaceId && cardData.title) {
|
|
2326
|
-
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
2327
|
-
const taskContext = [cardData.title, cardData.description || ""]
|
|
2328
|
-
.filter(Boolean)
|
|
2329
|
-
.join(" ");
|
|
2330
|
-
const assembled = await assembleContext({
|
|
2331
|
-
workspaceId,
|
|
2332
|
-
projectId: getActiveProjectId() || undefined,
|
|
2333
|
-
taskContext,
|
|
2334
|
-
cardLabels,
|
|
2335
|
-
cardId: cardData.id,
|
|
2336
|
-
client,
|
|
2337
|
-
});
|
|
2338
|
-
if (assembled.context) {
|
|
2339
|
-
assembledContextStr = assembled.context;
|
|
2340
|
-
assemblyId = assembled.manifest.assemblyId;
|
|
2341
|
-
cacheManifest(assembled.manifest);
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
catch {
|
|
2346
|
-
// Context assembly failed, try legacy fallback
|
|
2347
|
-
try {
|
|
2348
|
-
const workspaceId = deps.getActiveWorkspaceId();
|
|
2349
|
-
if (workspaceId && cardData.title) {
|
|
2350
|
-
const memoryResult = await client.searchMemoryEntities(workspaceId, cardData.title, {
|
|
2351
|
-
project_id: getActiveProjectId() || undefined,
|
|
2352
|
-
limit: 5,
|
|
2353
|
-
});
|
|
2354
|
-
if (memoryResult.entities?.length > 0) {
|
|
2355
|
-
memories = memoryResult.entities.map((e) => {
|
|
2356
|
-
const entity = e;
|
|
2357
|
-
return {
|
|
2358
|
-
id: entity.id,
|
|
2359
|
-
type: entity.type,
|
|
2360
|
-
title: entity.title,
|
|
2361
|
-
content: entity.content,
|
|
2362
|
-
confidence: entity.confidence,
|
|
2363
|
-
tags: entity.tags || [],
|
|
2364
|
-
};
|
|
2365
|
-
});
|
|
2366
|
-
}
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
catch {
|
|
2370
|
-
// Memory fetch also failed, continue without memories
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
const result = generatePrompt({
|
|
2374
|
-
card: cardData,
|
|
2375
|
-
column: columnData,
|
|
2376
|
-
variant,
|
|
2377
|
-
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",
|
|
2378
2301
|
customConstraints: args.customConstraints,
|
|
2379
|
-
|
|
2380
|
-
assembledContext: assembledContextStr,
|
|
2381
|
-
assemblyId,
|
|
2302
|
+
contextOptions,
|
|
2382
2303
|
});
|
|
2304
|
+
// MCP-specific: cache the assembly manifest for the feedback loop
|
|
2305
|
+
if (result.assemblyId) {
|
|
2306
|
+
trackSessionAssembly(cardId, result.assemblyId);
|
|
2307
|
+
}
|
|
2383
2308
|
return {
|
|
2384
2309
|
success: true,
|
|
2385
|
-
cardId: cardData.id,
|
|
2386
|
-
shortId: cardData.short_id,
|
|
2387
|
-
title: cardData.title,
|
|
2388
2310
|
...result,
|
|
2389
2311
|
};
|
|
2390
2312
|
}
|
|
@@ -3267,11 +3189,14 @@ export class HarmonyMCPServer {
|
|
|
3267
3189
|
const transport = new StdioServerTransport();
|
|
3268
3190
|
await this.server.connect(transport);
|
|
3269
3191
|
console.error("Harmony MCP server running on stdio");
|
|
3270
|
-
// Initialize auto-session tracking
|
|
3192
|
+
// Initialize auto-session tracking with MCP client identity detection
|
|
3271
3193
|
const configDeps = createConfigDeps();
|
|
3272
3194
|
initAutoSession(async (client, cardId, status) => {
|
|
3273
3195
|
await runEndSessionPipeline(client, configDeps, cardId, status);
|
|
3274
|
-
}, () => getClient())
|
|
3196
|
+
}, () => getClient(), () => {
|
|
3197
|
+
const cv = this.server.getClientVersion();
|
|
3198
|
+
return cv ? { name: cv.name, version: cv.version } : null;
|
|
3199
|
+
});
|
|
3275
3200
|
// Graceful shutdown: end all auto-sessions
|
|
3276
3201
|
const handleShutdown = async () => {
|
|
3277
3202
|
try {
|