@gethmy/mcp 2.2.0 → 2.2.2
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/README.md +5 -7
- package/dist/cli.js +2515 -2134
- package/dist/http.js +6 -4
- package/dist/index.js +8088 -7890
- 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/cli.js +9 -0
- package/dist/lib/onboard.js +36 -0
- package/dist/lib/server.js +150 -169
- package/dist/lib/skills.js +1 -1
- package/dist/lib/tui/setup.js +212 -59
- package/dist/remote.js +7132 -10614
- package/package.json +2 -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/cli.ts +9 -0
- package/src/onboard.ts +93 -0
- package/src/remote.ts +2 -1
- package/src/server.ts +178 -221
- package/src/skills.ts +1 -1
- package/src/tui/setup.ts +249 -67
package/src/server.ts
CHANGED
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
getClient,
|
|
25
25
|
type HarmonyApiClient,
|
|
26
|
-
requestWithBearer,
|
|
27
26
|
resetClient,
|
|
28
27
|
signupUser,
|
|
29
28
|
} from "./api-client.js";
|
|
@@ -59,13 +58,8 @@ import {
|
|
|
59
58
|
} from "./context-assembly.js";
|
|
60
59
|
import { autoExpandGraph } from "./graph-expansion.js";
|
|
61
60
|
import { runLifecycleMaintenance } from "./lifecycle-maintenance.js";
|
|
62
|
-
import {
|
|
63
|
-
|
|
64
|
-
type ColumnData,
|
|
65
|
-
generatePrompt,
|
|
66
|
-
type MemoryData,
|
|
67
|
-
type PromptVariant,
|
|
68
|
-
} from "./prompt-builder.js";
|
|
61
|
+
import { onboardNewUser } from "./onboard.js";
|
|
62
|
+
import type { PromptVariant } from "./prompt-builder.js";
|
|
69
63
|
|
|
70
64
|
/**
|
|
71
65
|
* Dependencies injected into tool handlers.
|
|
@@ -279,18 +273,24 @@ const TOOLS = {
|
|
|
279
273
|
},
|
|
280
274
|
},
|
|
281
275
|
harmony_move_card: {
|
|
282
|
-
description:
|
|
276
|
+
description:
|
|
277
|
+
"Move a card to a different column or position. Provide either columnId (UUID) or columnName (e.g. 'Review', 'Done').",
|
|
283
278
|
inputSchema: {
|
|
284
279
|
type: "object",
|
|
285
280
|
properties: {
|
|
286
281
|
cardId: { type: "string", description: "Card ID to move" },
|
|
287
|
-
columnId: { type: "string", description: "Target column ID" },
|
|
282
|
+
columnId: { type: "string", description: "Target column ID (UUID)" },
|
|
283
|
+
columnName: {
|
|
284
|
+
type: "string",
|
|
285
|
+
description:
|
|
286
|
+
"Target column name (e.g. 'To Do', 'In Progress', 'Review', 'Done'). Used if columnId is not provided.",
|
|
287
|
+
},
|
|
288
288
|
position: {
|
|
289
289
|
type: "number",
|
|
290
290
|
description: "Position in column (0-indexed)",
|
|
291
291
|
},
|
|
292
292
|
},
|
|
293
|
-
required: ["cardId"
|
|
293
|
+
required: ["cardId"],
|
|
294
294
|
},
|
|
295
295
|
},
|
|
296
296
|
harmony_archive_card: {
|
|
@@ -426,14 +426,22 @@ const TOOLS = {
|
|
|
426
426
|
},
|
|
427
427
|
},
|
|
428
428
|
harmony_add_label_to_card: {
|
|
429
|
-
description:
|
|
429
|
+
description:
|
|
430
|
+
"Add a label to a card. Provide labelId directly, or labelName to look up (or auto-create) the label by name.",
|
|
430
431
|
inputSchema: {
|
|
431
432
|
type: "object",
|
|
432
433
|
properties: {
|
|
433
434
|
cardId: { type: "string" },
|
|
434
|
-
labelId: {
|
|
435
|
+
labelId: {
|
|
436
|
+
type: "string",
|
|
437
|
+
description: "Label ID (optional if labelName provided)",
|
|
438
|
+
},
|
|
439
|
+
labelName: {
|
|
440
|
+
type: "string",
|
|
441
|
+
description: "Label name — will look up or create if not found",
|
|
442
|
+
},
|
|
435
443
|
},
|
|
436
|
-
required: ["cardId"
|
|
444
|
+
required: ["cardId"],
|
|
437
445
|
},
|
|
438
446
|
},
|
|
439
447
|
harmony_remove_label_from_card: {
|
|
@@ -1977,6 +1985,27 @@ export function registerHandlers(server: Server, deps: ToolDeps): void {
|
|
|
1977
1985
|
});
|
|
1978
1986
|
}
|
|
1979
1987
|
|
|
1988
|
+
/** Resolve a column name to its ID. Prefers exact match, falls back to substring. */
|
|
1989
|
+
async function resolveColumnByName(
|
|
1990
|
+
client: HarmonyApiClient,
|
|
1991
|
+
projectId: string,
|
|
1992
|
+
columnName: string,
|
|
1993
|
+
): Promise<{ id: string; name: string }> {
|
|
1994
|
+
const board = await client.getBoard(projectId, { summary: true });
|
|
1995
|
+
const columns = board.columns as Array<{ id: string; name: string }>;
|
|
1996
|
+
const lower = columnName.toLowerCase();
|
|
1997
|
+
const col =
|
|
1998
|
+
columns.find((c) => c.name.toLowerCase() === lower) ||
|
|
1999
|
+
columns.find((c) => c.name.toLowerCase().includes(lower));
|
|
2000
|
+
if (!col) {
|
|
2001
|
+
const available = columns.map((c) => c.name).join(", ");
|
|
2002
|
+
throw new Error(
|
|
2003
|
+
`Column "${columnName}" not found. Available columns: ${available}`,
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
return col;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1980
2009
|
async function handleToolCall(
|
|
1981
2010
|
name: string,
|
|
1982
2011
|
args: Record<string, unknown>,
|
|
@@ -2047,21 +2076,41 @@ async function handleToolCall(
|
|
|
2047
2076
|
|
|
2048
2077
|
case "harmony_move_card": {
|
|
2049
2078
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
2050
|
-
const columnId = z.string().uuid().parse(args.columnId);
|
|
2051
2079
|
const position =
|
|
2052
2080
|
args.position !== undefined
|
|
2053
2081
|
? z.number().int().min(0).parse(args.position)
|
|
2054
2082
|
: undefined;
|
|
2083
|
+
|
|
2084
|
+
// Resolve columnId — accept UUID directly or resolve from columnName
|
|
2085
|
+
let columnId: string;
|
|
2086
|
+
let resolvedProjectId: string | undefined;
|
|
2087
|
+
if (args.columnId) {
|
|
2088
|
+
columnId = z.string().uuid().parse(args.columnId);
|
|
2089
|
+
} else if (args.columnName) {
|
|
2090
|
+
const columnName = z.string().parse(args.columnName);
|
|
2091
|
+
const { card: cardForProject } = await client.getCard(cardId);
|
|
2092
|
+
resolvedProjectId = (cardForProject as { project_id?: string })
|
|
2093
|
+
?.project_id;
|
|
2094
|
+
if (!resolvedProjectId) throw new Error("Card has no project");
|
|
2095
|
+
const col = await resolveColumnByName(
|
|
2096
|
+
client,
|
|
2097
|
+
resolvedProjectId,
|
|
2098
|
+
columnName,
|
|
2099
|
+
);
|
|
2100
|
+
columnId = col.id;
|
|
2101
|
+
} else {
|
|
2102
|
+
throw new Error("Either columnId or columnName is required");
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2055
2105
|
const result = await client.moveCard(cardId, columnId, position);
|
|
2056
2106
|
|
|
2057
2107
|
// Auto-end active agent session when moving to Review or Done
|
|
2058
2108
|
let sessionEnded = false;
|
|
2059
2109
|
try {
|
|
2060
2110
|
const { card } = result as { card: { project_id?: string } };
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
});
|
|
2111
|
+
const projectId = card?.project_id || resolvedProjectId;
|
|
2112
|
+
if (projectId) {
|
|
2113
|
+
const board = await client.getBoard(projectId, { summary: true });
|
|
2065
2114
|
const columns = board.columns as Array<{
|
|
2066
2115
|
id: string;
|
|
2067
2116
|
name: string;
|
|
@@ -2184,7 +2233,47 @@ async function handleToolCall(
|
|
|
2184
2233
|
|
|
2185
2234
|
case "harmony_add_label_to_card": {
|
|
2186
2235
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
2187
|
-
|
|
2236
|
+
let labelId = args.labelId
|
|
2237
|
+
? z.string().uuid().parse(args.labelId)
|
|
2238
|
+
: undefined;
|
|
2239
|
+
const labelName = args.labelName
|
|
2240
|
+
? z.string().min(1).max(100).parse(args.labelName)
|
|
2241
|
+
: undefined;
|
|
2242
|
+
|
|
2243
|
+
// If labelName provided without labelId, look up or create the label
|
|
2244
|
+
if (!labelId && labelName) {
|
|
2245
|
+
const { card } = await client.getCard(cardId);
|
|
2246
|
+
const projectId = (card as { project_id?: string }).project_id;
|
|
2247
|
+
if (!projectId) {
|
|
2248
|
+
throw new Error(
|
|
2249
|
+
"Cannot resolve label by name: card has no project_id",
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
if (projectId) {
|
|
2253
|
+
const board = await client.getBoard(projectId, { summary: true });
|
|
2254
|
+
const labels = board.labels as Array<{
|
|
2255
|
+
id: string;
|
|
2256
|
+
name: string;
|
|
2257
|
+
}>;
|
|
2258
|
+
const existing = labels.find(
|
|
2259
|
+
(l) => l.name.toLowerCase() === labelName.toLowerCase(),
|
|
2260
|
+
);
|
|
2261
|
+
if (existing) {
|
|
2262
|
+
labelId = existing.id;
|
|
2263
|
+
} else {
|
|
2264
|
+
const created = await client.createLabel(projectId, {
|
|
2265
|
+
name: labelName,
|
|
2266
|
+
color: "#57b8a5",
|
|
2267
|
+
});
|
|
2268
|
+
labelId = (created as { label: { id: string } }).label.id;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
if (!labelId) {
|
|
2274
|
+
throw new Error("Either labelId or labelName must be provided");
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2188
2277
|
await client.addLabelToCard(cardId, labelId);
|
|
2189
2278
|
return { success: true };
|
|
2190
2279
|
}
|
|
@@ -2369,12 +2458,20 @@ async function handleToolCall(
|
|
|
2369
2458
|
|
|
2370
2459
|
if (addLabels?.length) {
|
|
2371
2460
|
for (const labelName of addLabels) {
|
|
2372
|
-
|
|
2461
|
+
let label = labels.find(
|
|
2373
2462
|
(l) => l.name.toLowerCase() === labelName.toLowerCase(),
|
|
2374
2463
|
);
|
|
2464
|
+
if (!label && projectId) {
|
|
2465
|
+
const created = await client.createLabel(projectId, {
|
|
2466
|
+
name: labelName,
|
|
2467
|
+
color: "#57b8a5",
|
|
2468
|
+
});
|
|
2469
|
+
label = (created as { label: { id: string; name: string } })
|
|
2470
|
+
.label;
|
|
2471
|
+
}
|
|
2375
2472
|
if (label) {
|
|
2376
2473
|
await client.addLabelToCard(cardId, label.id);
|
|
2377
|
-
labelsAdded.push(label.name);
|
|
2474
|
+
labelsAdded.push(label.name ?? labelName);
|
|
2378
2475
|
}
|
|
2379
2476
|
}
|
|
2380
2477
|
}
|
|
@@ -2577,38 +2674,30 @@ async function handleToolCall(
|
|
|
2577
2674
|
await flushMemoryActions(client, cardId);
|
|
2578
2675
|
cleanupMemorySession(cardId);
|
|
2579
2676
|
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2677
|
+
// End the session — tolerate failure (e.g., session already ended or not found)
|
|
2678
|
+
let result: { session: unknown } = { session: null };
|
|
2679
|
+
let sessionEndError: string | null = null;
|
|
2680
|
+
try {
|
|
2681
|
+
result = await client.endAgentSession(cardId, {
|
|
2682
|
+
status: sessionStatus,
|
|
2683
|
+
progressPercent: endProgressPercent,
|
|
2684
|
+
});
|
|
2685
|
+
} catch (err) {
|
|
2686
|
+
sessionEndError =
|
|
2687
|
+
err instanceof Error ? err.message : "Failed to end session";
|
|
2688
|
+
}
|
|
2584
2689
|
|
|
2585
|
-
// Remove from auto-session tracking
|
|
2690
|
+
// Remove from auto-session tracking regardless
|
|
2586
2691
|
untrack(cardId);
|
|
2587
2692
|
|
|
2588
2693
|
let movedTo: string | null = null;
|
|
2589
|
-
const _learningsExtracted = 0;
|
|
2590
2694
|
|
|
2591
|
-
// Get card info for move and learning extraction
|
|
2592
|
-
let _cardTitle = "";
|
|
2593
|
-
let _cardLabels: string[] = [];
|
|
2594
|
-
let _cardDescription = "";
|
|
2595
|
-
let _cardSubtasks: Array<{ title: string; done: boolean }> = [];
|
|
2596
2695
|
try {
|
|
2597
2696
|
const { card } = await client.getCard(cardId);
|
|
2598
2697
|
const typedCard = card as {
|
|
2599
2698
|
project_id?: string;
|
|
2600
|
-
title?: string;
|
|
2601
|
-
description?: string;
|
|
2602
2699
|
labels?: Array<{ id: string; name: string }>;
|
|
2603
|
-
subtasks?: Array<{ title: string; done: boolean }>;
|
|
2604
2700
|
};
|
|
2605
|
-
_cardTitle = typedCard.title || "";
|
|
2606
|
-
_cardLabels = (typedCard.labels || []).map((l) => l.name);
|
|
2607
|
-
_cardDescription = typedCard.description || "";
|
|
2608
|
-
_cardSubtasks = (typedCard.subtasks || []).map((s) => ({
|
|
2609
|
-
title: s.title,
|
|
2610
|
-
done: s.done,
|
|
2611
|
-
}));
|
|
2612
2701
|
const projectId = typedCard.project_id;
|
|
2613
2702
|
|
|
2614
2703
|
// Remove "agent" label when session is completed (not paused)
|
|
@@ -2622,20 +2711,13 @@ async function handleToolCall(
|
|
|
2622
2711
|
}
|
|
2623
2712
|
|
|
2624
2713
|
if (moveToColumn && projectId) {
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
id: string;
|
|
2630
|
-
name: string;
|
|
2631
|
-
}>;
|
|
2632
|
-
const col = columns.find((c) =>
|
|
2633
|
-
c.name.toLowerCase().includes(moveToColumn.toLowerCase()),
|
|
2714
|
+
const col = await resolveColumnByName(
|
|
2715
|
+
client,
|
|
2716
|
+
projectId,
|
|
2717
|
+
moveToColumn,
|
|
2634
2718
|
);
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
movedTo = col.name;
|
|
2638
|
-
}
|
|
2719
|
+
await client.moveCard(cardId, col.id);
|
|
2720
|
+
movedTo = col.name;
|
|
2639
2721
|
}
|
|
2640
2722
|
} catch {
|
|
2641
2723
|
// Card fetch/move failed, continue
|
|
@@ -2661,6 +2743,7 @@ async function handleToolCall(
|
|
|
2661
2743
|
|
|
2662
2744
|
return {
|
|
2663
2745
|
success: true,
|
|
2746
|
+
...(sessionEndError && { sessionEndError }),
|
|
2664
2747
|
movedTo,
|
|
2665
2748
|
learningsExtracted: pipelineResult.learningsExtracted,
|
|
2666
2749
|
feedbackAdjusted: pipelineResult.feedbackAdjusted,
|
|
@@ -2699,14 +2782,11 @@ async function handleToolCall(
|
|
|
2699
2782
|
|
|
2700
2783
|
// Prompt generation
|
|
2701
2784
|
case "harmony_generate_prompt": {
|
|
2702
|
-
//
|
|
2703
|
-
let
|
|
2704
|
-
let columnData: ColumnData | null = null;
|
|
2785
|
+
// Resolve card ID — either directly or via short ID
|
|
2786
|
+
let cardId: string;
|
|
2705
2787
|
|
|
2706
2788
|
if (args.cardId) {
|
|
2707
|
-
|
|
2708
|
-
const cardResult = await client.getCard(cardId);
|
|
2709
|
-
cardData = cardResult.card as CardData;
|
|
2789
|
+
cardId = z.string().uuid().parse(args.cardId);
|
|
2710
2790
|
} else if (args.shortId !== undefined) {
|
|
2711
2791
|
const shortId = z.number().int().positive().parse(args.shortId);
|
|
2712
2792
|
const projectId =
|
|
@@ -2717,35 +2797,12 @@ async function handleToolCall(
|
|
|
2717
2797
|
);
|
|
2718
2798
|
}
|
|
2719
2799
|
const cardResult = await client.getCardByShortId(projectId, shortId);
|
|
2720
|
-
|
|
2800
|
+
cardId = (cardResult.card as { id: string }).id;
|
|
2721
2801
|
} else {
|
|
2722
2802
|
throw new Error("Either cardId or shortId must be provided");
|
|
2723
2803
|
}
|
|
2724
2804
|
|
|
2725
|
-
//
|
|
2726
|
-
const projectIdForBoard =
|
|
2727
|
-
(args.projectId as string) ||
|
|
2728
|
-
getActiveProjectId() ||
|
|
2729
|
-
(cardData as unknown as { project_id: string }).project_id;
|
|
2730
|
-
if (projectIdForBoard) {
|
|
2731
|
-
try {
|
|
2732
|
-
const board = await client.getBoard(projectIdForBoard, {
|
|
2733
|
-
summary: true,
|
|
2734
|
-
});
|
|
2735
|
-
const columnId = (cardData as unknown as { column_id: string })
|
|
2736
|
-
.column_id;
|
|
2737
|
-
const column = (
|
|
2738
|
-
board.columns as Array<{ id: string; name: string }>
|
|
2739
|
-
).find((col) => col.id === columnId);
|
|
2740
|
-
if (column) {
|
|
2741
|
-
columnData = { name: column.name };
|
|
2742
|
-
}
|
|
2743
|
-
} catch {
|
|
2744
|
-
// Column info not available, continue without it
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
|
|
2748
|
-
const variant = (args.variant as PromptVariant) || "execute";
|
|
2805
|
+
// Parse MCP-specific context options
|
|
2749
2806
|
const contextOptions: Record<string, boolean> = {};
|
|
2750
2807
|
if (args.includeSubtasks !== undefined) {
|
|
2751
2808
|
contextOptions.includeSubtasks =
|
|
@@ -2761,89 +2818,24 @@ async function handleToolCall(
|
|
|
2761
2818
|
args.includeDescription === "true";
|
|
2762
2819
|
}
|
|
2763
2820
|
|
|
2764
|
-
//
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
if (workspaceId && cardData.title) {
|
|
2772
|
-
const cardLabels = (cardData.labels || []).map((l) => l.name);
|
|
2773
|
-
const taskContext = [cardData.title, cardData.description || ""]
|
|
2774
|
-
.filter(Boolean)
|
|
2775
|
-
.join(" ");
|
|
2776
|
-
|
|
2777
|
-
const assembled = await assembleContext({
|
|
2778
|
-
workspaceId,
|
|
2779
|
-
projectId: getActiveProjectId() || undefined,
|
|
2780
|
-
taskContext,
|
|
2781
|
-
cardLabels,
|
|
2782
|
-
cardId: cardData.id,
|
|
2783
|
-
client,
|
|
2784
|
-
});
|
|
2785
|
-
|
|
2786
|
-
if (assembled.context) {
|
|
2787
|
-
assembledContextStr = assembled.context;
|
|
2788
|
-
assemblyId = assembled.manifest.assemblyId;
|
|
2789
|
-
cacheManifest(assembled.manifest);
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
} catch {
|
|
2793
|
-
// Context assembly failed, try legacy fallback
|
|
2794
|
-
try {
|
|
2795
|
-
const workspaceId = deps.getActiveWorkspaceId();
|
|
2796
|
-
if (workspaceId && cardData.title) {
|
|
2797
|
-
const memoryResult = await client.searchMemoryEntities(
|
|
2798
|
-
workspaceId,
|
|
2799
|
-
cardData.title,
|
|
2800
|
-
{
|
|
2801
|
-
project_id: getActiveProjectId() || undefined,
|
|
2802
|
-
limit: 5,
|
|
2803
|
-
},
|
|
2804
|
-
);
|
|
2805
|
-
if (memoryResult.entities?.length > 0) {
|
|
2806
|
-
memories = memoryResult.entities.map((e: unknown) => {
|
|
2807
|
-
const entity = e as {
|
|
2808
|
-
id: string;
|
|
2809
|
-
type: string;
|
|
2810
|
-
title: string;
|
|
2811
|
-
content: string;
|
|
2812
|
-
confidence: number;
|
|
2813
|
-
tags: string[];
|
|
2814
|
-
};
|
|
2815
|
-
return {
|
|
2816
|
-
id: entity.id,
|
|
2817
|
-
type: entity.type,
|
|
2818
|
-
title: entity.title,
|
|
2819
|
-
content: entity.content,
|
|
2820
|
-
confidence: entity.confidence,
|
|
2821
|
-
tags: entity.tags || [],
|
|
2822
|
-
};
|
|
2823
|
-
});
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
2826
|
-
} catch {
|
|
2827
|
-
// Memory fetch also failed, continue without memories
|
|
2828
|
-
}
|
|
2829
|
-
}
|
|
2830
|
-
|
|
2831
|
-
const result = generatePrompt({
|
|
2832
|
-
card: cardData,
|
|
2833
|
-
column: columnData,
|
|
2834
|
-
variant,
|
|
2835
|
-
contextOptions,
|
|
2821
|
+
// Delegate to the shared prompt generation pipeline
|
|
2822
|
+
const result = await client.generateCardPrompt({
|
|
2823
|
+
cardId,
|
|
2824
|
+
workspaceId: deps.getActiveWorkspaceId() || "",
|
|
2825
|
+
projectId:
|
|
2826
|
+
(args.projectId as string) || getActiveProjectId() || undefined,
|
|
2827
|
+
variant: (args.variant as PromptVariant) || "execute",
|
|
2836
2828
|
customConstraints: args.customConstraints as string | undefined,
|
|
2837
|
-
|
|
2838
|
-
assembledContext: assembledContextStr,
|
|
2839
|
-
assemblyId,
|
|
2829
|
+
contextOptions,
|
|
2840
2830
|
});
|
|
2841
2831
|
|
|
2832
|
+
// MCP-specific: cache the assembly manifest for the feedback loop
|
|
2833
|
+
if (result.assemblyId) {
|
|
2834
|
+
trackSessionAssembly(cardId, result.assemblyId);
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2842
2837
|
return {
|
|
2843
2838
|
success: true,
|
|
2844
|
-
cardId: cardData.id,
|
|
2845
|
-
shortId: cardData.short_id,
|
|
2846
|
-
title: cardData.title,
|
|
2847
2839
|
...result,
|
|
2848
2840
|
};
|
|
2849
2841
|
}
|
|
@@ -3037,8 +3029,7 @@ async function handleToolCall(
|
|
|
3037
3029
|
// Track memory write action and flush (fire-and-forget)
|
|
3038
3030
|
const updateMemSession = getActiveMemorySession();
|
|
3039
3031
|
if (updateMemSession) {
|
|
3040
|
-
const updateTitle =
|
|
3041
|
-
(updates.title as string) || entityId.slice(0, 8);
|
|
3032
|
+
const updateTitle = (updates.title as string) || entityId.slice(0, 8);
|
|
3042
3033
|
appendMemoryAction(
|
|
3043
3034
|
updateMemSession.cardId,
|
|
3044
3035
|
`Updated memory: ${updateTitle}`,
|
|
@@ -3871,69 +3862,31 @@ async function handleToolCall(
|
|
|
3871
3862
|
const projectName = (args.projectName as string) || "My First Project";
|
|
3872
3863
|
const template = (args.template as string) || "kanban";
|
|
3873
3864
|
const keyName = (args.keyName as string) || "mcp-agent";
|
|
3874
|
-
const apiUrl = deps.getApiUrl();
|
|
3875
3865
|
|
|
3876
|
-
|
|
3877
|
-
const signupResult = await signupUser(apiUrl, {
|
|
3866
|
+
const result = await onboardNewUser({
|
|
3878
3867
|
email,
|
|
3879
3868
|
password,
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
// 2. Create workspace
|
|
3885
|
-
const workspaceResult = await requestWithBearer<{
|
|
3886
|
-
workspace: {
|
|
3887
|
-
id: string;
|
|
3888
|
-
name: string;
|
|
3889
|
-
slug: string;
|
|
3890
|
-
created_at: string;
|
|
3891
|
-
};
|
|
3892
|
-
}>(apiUrl, token, "POST", "/workspaces", {
|
|
3893
|
-
name: workspaceName,
|
|
3894
|
-
});
|
|
3895
|
-
|
|
3896
|
-
// 3. Create project
|
|
3897
|
-
const projectResult = await requestWithBearer<{
|
|
3898
|
-
project: { id: string; name: string; slug: string };
|
|
3899
|
-
columns: unknown[];
|
|
3900
|
-
}>(apiUrl, token, "POST", "/projects", {
|
|
3901
|
-
workspaceId: workspaceResult.workspace.id,
|
|
3902
|
-
name: projectName,
|
|
3869
|
+
fullName,
|
|
3870
|
+
workspaceName,
|
|
3871
|
+
projectName,
|
|
3903
3872
|
template,
|
|
3873
|
+
keyName,
|
|
3874
|
+
apiUrl: deps.getApiUrl(),
|
|
3904
3875
|
});
|
|
3905
3876
|
|
|
3906
|
-
//
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
name: string;
|
|
3911
|
-
prefix: string;
|
|
3912
|
-
created_at: string;
|
|
3913
|
-
};
|
|
3914
|
-
rawKey: string;
|
|
3915
|
-
}>(apiUrl, token, "POST", "/api-keys", {
|
|
3916
|
-
name: keyName,
|
|
3917
|
-
});
|
|
3918
|
-
|
|
3919
|
-
// 5. Save config
|
|
3920
|
-
deps.saveConfig({ apiKey: keyResult.rawKey });
|
|
3921
|
-
deps.setActiveWorkspace(workspaceResult.workspace.id);
|
|
3922
|
-
deps.setActiveProject(projectResult.project.id);
|
|
3923
|
-
|
|
3924
|
-
// 6. Reset client so singleton picks up new key
|
|
3877
|
+
// Save config and reset client
|
|
3878
|
+
deps.saveConfig({ apiKey: result.apiKey.rawKey });
|
|
3879
|
+
deps.setActiveWorkspace(result.workspace.id);
|
|
3880
|
+
deps.setActiveProject(result.project.id);
|
|
3925
3881
|
deps.resetClient();
|
|
3926
3882
|
|
|
3927
3883
|
return {
|
|
3928
3884
|
success: true,
|
|
3929
|
-
user:
|
|
3930
|
-
workspace:
|
|
3931
|
-
project:
|
|
3932
|
-
columns:
|
|
3933
|
-
apiKey:
|
|
3934
|
-
rawKey: keyResult.rawKey,
|
|
3935
|
-
prefix: keyResult.apiKey.prefix,
|
|
3936
|
-
},
|
|
3885
|
+
user: result.user,
|
|
3886
|
+
workspace: result.workspace,
|
|
3887
|
+
project: result.project,
|
|
3888
|
+
columns: result.columns,
|
|
3889
|
+
apiKey: result.apiKey,
|
|
3937
3890
|
message: `Onboarding complete! Account created for ${email}. Workspace "${workspaceName}" and project "${projectName}" are ready. API key saved to config.`,
|
|
3938
3891
|
};
|
|
3939
3892
|
}
|
|
@@ -4053,13 +4006,17 @@ export class HarmonyMCPServer {
|
|
|
4053
4006
|
await this.server.connect(transport);
|
|
4054
4007
|
console.error("Harmony MCP server running on stdio");
|
|
4055
4008
|
|
|
4056
|
-
// Initialize auto-session tracking
|
|
4009
|
+
// Initialize auto-session tracking with MCP client identity detection
|
|
4057
4010
|
const configDeps = createConfigDeps();
|
|
4058
4011
|
initAutoSession(
|
|
4059
4012
|
async (client, cardId, status) => {
|
|
4060
4013
|
await runEndSessionPipeline(client, configDeps, cardId, status);
|
|
4061
4014
|
},
|
|
4062
4015
|
() => getClient(),
|
|
4016
|
+
() => {
|
|
4017
|
+
const cv = this.server.getClientVersion();
|
|
4018
|
+
return cv ? { name: cv.name, version: cv.version } : null;
|
|
4019
|
+
},
|
|
4063
4020
|
);
|
|
4064
4021
|
|
|
4065
4022
|
// Graceful shutdown: end all auto-sessions
|
package/src/skills.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const SKILLS_VERSION = "3";
|
|
|
7
7
|
const VERSION_MARKER_PREFIX = "<!-- skills-version:";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Legacy workflow prompt used by Codex, Cursor
|
|
10
|
+
* Legacy workflow prompt used by Codex, Cursor agents.
|
|
11
11
|
* Claude Code skills use the newer SKILL_DEFINITIONS content instead.
|
|
12
12
|
*/
|
|
13
13
|
export const HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|