@gethmy/mcp 2.24.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -354,15 +354,7 @@ ${lines.join(`
354
354
  `)}`;
355
355
  }
356
356
  function generatePrompt(options) {
357
- const {
358
- card,
359
- column,
360
- variant,
361
- customConstraints,
362
- memories,
363
- assembledContext,
364
- assemblyId
365
- } = options;
357
+ const { card, column, variant, customConstraints, memories, assemblyId } = options;
366
358
  const contextOpts = {
367
359
  includeTitle: true,
368
360
  includeDescription: true,
@@ -440,10 +432,7 @@ ${card.description}`);
440
432
  roleFraming.outputSuggestions.forEach((s) => {
441
433
  sections.push(`- ${s}`);
442
434
  });
443
- if (assembledContext) {
444
- sections.push(`
445
- ${assembledContext}`);
446
- } else if (memories && memories.length > 0) {
435
+ if (memories && memories.length > 0) {
447
436
  sections.push(`
448
437
  ## Relevant Memories`);
449
438
  sections.push(`*${memories.length} memories recalled from knowledge graph:*`);
@@ -454,7 +443,7 @@ ${assembledContext}`);
454
443
  sections.push(memory.content);
455
444
  }
456
445
  }
457
- const oneThingLine = synthesizeOneThing(card, subtasks, links, assembledContext);
446
+ const oneThingLine = synthesizeOneThing(card, subtasks, links);
458
447
  if (oneThingLine) {
459
448
  sections.push(`
460
449
  ## Recommended Next Step
@@ -481,7 +470,7 @@ ${customConstraints}`);
481
470
  *Card #${card.short_id} | Generated for ${variant} mode*`);
482
471
  const prompt = sections.join(`
483
472
  `);
484
- const memoryCount = assembledContext ? (assembledContext.match(/^### /gm) || []).length : memories?.length || 0;
473
+ const memoryCount = memories?.length ?? 0;
485
474
  return {
486
475
  prompt,
487
476
  variant,
@@ -502,40 +491,7 @@ ${customConstraints}`);
502
491
  version: PROMPT_TEMPLATE_VERSION
503
492
  };
504
493
  }
505
- function extractSessionInsights(assembledContext) {
506
- const result = {
507
- lastSessionStatus: null,
508
- lastSessionTask: null,
509
- lastSessionProgress: null,
510
- blockers: [],
511
- procedureNextStep: null
512
- };
513
- const sessionMatches = assembledContext.match(/### Session:.*?\n([\s\S]*?)(?=\n###|\n## |\n---|\n\*Assembly|$)/g);
514
- if (sessionMatches && sessionMatches.length > 0) {
515
- const latest = sessionMatches[0];
516
- if (/Completed work on/i.test(latest)) {
517
- result.lastSessionStatus = "completed";
518
- } else if (/Paused work on|status:\s*paused/i.test(latest)) {
519
- result.lastSessionStatus = "paused";
520
- }
521
- const taskMatch = latest.match(/Final task:\s*(.+)/);
522
- if (taskMatch)
523
- result.lastSessionTask = taskMatch[1].trim();
524
- const progressMatch = latest.match(/Progress:\s*(\d+)%/);
525
- if (progressMatch)
526
- result.lastSessionProgress = parseInt(progressMatch[1], 10);
527
- }
528
- const blockerMatches = assembledContext.match(/(?:blocker|blocked by|blocking):\s*(.+)/gi);
529
- if (blockerMatches) {
530
- result.blockers = blockerMatches.map((m) => m.replace(/(?:blocker|blocked by|blocking):\s*/i, "").trim());
531
- }
532
- const stepMatches = assembledContext.match(/^\d+\.\s+(?!.*\*\*\[key step\]\*\*.*✓)(.+?)(?:\s*\*\*\[key step\]\*\*)?$/gm);
533
- if (stepMatches && stepMatches.length > 0) {
534
- result.procedureNextStep = stepMatches[0].replace(/^\d+\.\s+/, "").replace(/\s*\*\*\[key step\]\*\*.*$/, "").trim();
535
- }
536
- return result;
537
- }
538
- function synthesizeOneThing(card, subtasks, links, assembledContext) {
494
+ function synthesizeOneThing(card, subtasks, links) {
539
495
  if (card.done)
540
496
  return null;
541
497
  const blockers = links.filter((l) => l.display_type === "is_blocked_by" && l.direction === "incoming");
@@ -543,14 +499,6 @@ function synthesizeOneThing(card, subtasks, links, assembledContext) {
543
499
  const blocker = blockers[0];
544
500
  return `Unblock first: resolve #${blocker.target_card.short_id} "${blocker.target_card.title}" which is blocking this card.`;
545
501
  }
546
- const session = assembledContext ? extractSessionInsights(assembledContext) : null;
547
- if (session?.blockers && session.blockers.length > 0) {
548
- return `Resolve blocker: ${session.blockers[0]}`;
549
- }
550
- if (session?.lastSessionStatus === "paused" && session.lastSessionTask) {
551
- const progress = session.lastSessionProgress ? ` (was ${session.lastSessionProgress}% complete)` : "";
552
- return `Resume previous session${progress}: "${session.lastSessionTask}".`;
553
- }
554
502
  if (subtasks.length > 0) {
555
503
  const completed = subtasks.filter((s) => s.completed).length;
556
504
  if (completed === subtasks.length) {
@@ -561,12 +509,6 @@ function synthesizeOneThing(card, subtasks, links, assembledContext) {
561
509
  return `Work on next subtask: "${nextSubtask.title}" (${completed}/${subtasks.length} done).`;
562
510
  }
563
511
  }
564
- if (session?.procedureNextStep) {
565
- return `Follow procedure: ${session.procedureNextStep}`;
566
- }
567
- if (session?.lastSessionStatus === "completed" && session.lastSessionTask) {
568
- return `Previous session completed ("${session.lastSessionTask}"). Review results and continue with remaining work.`;
569
- }
570
512
  if (card.due_date && (card.priority === "urgent" || card.priority === "high")) {
571
513
  return `High-priority task with deadline ${card.due_date}. Start implementation immediately.`;
572
514
  }
@@ -1438,6 +1380,9 @@ import {
1438
1380
  ReadResourceRequestSchema
1439
1381
  } from "@modelcontextprotocol/sdk/types.js";
1440
1382
  import { z } from "zod";
1383
+
1384
+ // src/api-client.ts
1385
+ import { randomUUID as randomUUID2 } from "node:crypto";
1441
1386
  // ../harmony-shared/dist/agentStaleness.js
1442
1387
  var AGENT_HEARTBEAT_LIVENESS_MS = 5 * 60 * 1000;
1443
1388
  var AGENT_MILESTONE_LIVENESS_MS = 30 * 60 * 1000;
@@ -1559,12 +1504,97 @@ var TIMINGS = {
1559
1504
  QUERY_STALE_TIME: 1000 * 60 * 5,
1560
1505
  QUERY_GC_TIME: 1000 * 60 * 60 * 24
1561
1506
  };
1507
+ // ../harmony-shared/dist/declaredGateMetrics.js
1508
+ function declaredGateMetricsFromAgents(agents) {
1509
+ const names = new Set;
1510
+ let known = false;
1511
+ for (const agent of agents) {
1512
+ const declared = agent.declared_gate_metrics;
1513
+ if (!Array.isArray(declared))
1514
+ continue;
1515
+ known = true;
1516
+ for (const name of declared) {
1517
+ if (typeof name === "string" && name.trim())
1518
+ names.add(name.trim());
1519
+ }
1520
+ }
1521
+ return { names, known };
1522
+ }
1523
+ // ../harmony-shared/dist/gateConfigError.js
1524
+ var GATE_CONFIG_ERROR_KEY = "configError";
1525
+ var GATE_CONFIG_ERROR_MARK = Object.freeze({ [GATE_CONFIG_ERROR_KEY]: true });
1562
1526
  // ../harmony-shared/dist/playbookStage.js
1527
+ var DEFAULT_LOOP_MAX_ITERATIONS = 5;
1528
+ function normalizeLoopDef(raw) {
1529
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
1530
+ return null;
1531
+ const obj = raw;
1532
+ if (obj.mode !== "converge" && obj.mode !== "fanout")
1533
+ return null;
1534
+ const mode = obj.mode;
1535
+ const rawMax = obj.max_iterations;
1536
+ const maxInt = typeof rawMax === "number" && Number.isFinite(rawMax) && rawMax >= 1 ? Math.floor(rawMax) : DEFAULT_LOOP_MAX_ITERATIONS;
1537
+ const exitGate = obj.exit_gate && typeof obj.exit_gate === "object" && !Array.isArray(obj.exit_gate) ? obj.exit_gate : null;
1538
+ const def = { mode, max_iterations: maxInt };
1539
+ if (exitGate)
1540
+ def.exit_gate = exitGate;
1541
+ if (obj.item_source && typeof obj.item_source === "object" && !Array.isArray(obj.item_source)) {
1542
+ def.item_source = obj.item_source;
1543
+ }
1544
+ if (typeof obj.concurrency === "number" && obj.concurrency >= 1) {
1545
+ def.concurrency = Math.floor(obj.concurrency);
1546
+ }
1547
+ if (obj.on_item_fail === "continue" || obj.on_item_fail === "halt") {
1548
+ def.on_item_fail = obj.on_item_fail;
1549
+ }
1550
+ return def;
1551
+ }
1552
+ function readStageDefs(def) {
1553
+ if (def.steps_version !== 2)
1554
+ return [];
1555
+ return Array.isArray(def.steps) ? def.steps : [];
1556
+ }
1563
1557
  var STAGE_DAEMON_OWNED_TOOLS = [
1564
1558
  "mcp__harmony__harmony_end_agent_session",
1565
1559
  "mcp__harmony__harmony_start_agent_session",
1566
1560
  "mcp__harmony__harmony_move_card"
1567
1561
  ];
1562
+ function customGateMetric(gate) {
1563
+ if (gate === null || typeof gate !== "object" || Array.isArray(gate)) {
1564
+ return null;
1565
+ }
1566
+ const record = gate;
1567
+ if (record.kind !== "custom")
1568
+ return null;
1569
+ if (record.pendingEngine === true)
1570
+ return null;
1571
+ const metric = typeof record.metric === "string" ? record.metric.trim() : "";
1572
+ return metric ? metric : null;
1573
+ }
1574
+ function referencedGateMetrics(def) {
1575
+ const out = [];
1576
+ for (const stage of readStageDefs(def)) {
1577
+ if (!stage || typeof stage !== "object")
1578
+ continue;
1579
+ const stageId = typeof stage.id === "string" ? stage.id : "";
1580
+ const stageName = typeof stage.name === "string" ? stage.name : stageId;
1581
+ const gateMetric = customGateMetric(stage.gate);
1582
+ if (gateMetric) {
1583
+ out.push({ stageId, stageName, metric: gateMetric, source: "gate" });
1584
+ }
1585
+ const loop = normalizeLoopDef(stage.loop);
1586
+ const loopMetric = loop?.exit_gate ? customGateMetric(loop.exit_gate) : null;
1587
+ if (loopMetric) {
1588
+ out.push({
1589
+ stageId,
1590
+ stageName,
1591
+ metric: loopMetric,
1592
+ source: "loop_exit_gate"
1593
+ });
1594
+ }
1595
+ }
1596
+ return out;
1597
+ }
1568
1598
  // ../harmony-shared/dist/reviewTools.js
1569
1599
  var REVIEW_DISALLOWED_TOOLS = [
1570
1600
  ...STAGE_DAEMON_OWNED_TOOLS,
@@ -1599,6 +1629,19 @@ function getRetryDelay(attempt) {
1599
1629
  return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
1600
1630
  }
1601
1631
  var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1632
+ function buildMemoryQuery(title, description) {
1633
+ const DESCRIPTION_CAP = 600;
1634
+ const trimmedTitle = title.trim();
1635
+ const trimmedBody = (description ?? "").trim();
1636
+ if (!trimmedTitle && !trimmedBody)
1637
+ return "";
1638
+ if (!trimmedTitle)
1639
+ return trimmedBody.slice(0, DESCRIPTION_CAP);
1640
+ if (!trimmedBody)
1641
+ return trimmedTitle;
1642
+ return `${trimmedTitle}
1643
+ ${trimmedBody.slice(0, DESCRIPTION_CAP)}`;
1644
+ }
1602
1645
 
1603
1646
  class Semaphore {
1604
1647
  permits;
@@ -2069,6 +2112,14 @@ class HarmonyApiClient {
2069
2112
  params.set("sinceSeq", String(sinceSeq));
2070
2113
  return this.request("GET", `/cards/${cardId}/agent-messages?${params.toString()}`);
2071
2114
  }
2115
+ async postBudgetDecision(cardId, data) {
2116
+ return this.request("POST", `/cards/${cardId}/budget-decisions`, data);
2117
+ }
2118
+ async getBudgetDecisions(cardId, sinceIso) {
2119
+ const params = new URLSearchParams;
2120
+ params.set("sinceIso", sinceIso);
2121
+ return this.request("GET", `/cards/${cardId}/budget-decisions?${params.toString()}`);
2122
+ }
2072
2123
  async updateAgentProgress(cardId, data) {
2073
2124
  return this.request("POST", `/cards/${cardId}/agent-context`, data);
2074
2125
  }
@@ -2114,6 +2165,8 @@ class HarmonyApiClient {
2114
2165
  params.set("offset", String(options.offset));
2115
2166
  if (options.include_superseded)
2116
2167
  params.set("include_superseded", "true");
2168
+ if (options.consumer)
2169
+ params.set("consumer", options.consumer);
2117
2170
  if (options.include_episodes)
2118
2171
  params.set("include_episodes", "true");
2119
2172
  return this.request("GET", `/memory/entities?${params.toString()}`);
@@ -2169,6 +2222,12 @@ class HarmonyApiClient {
2169
2222
  if (options.topK !== undefined) {
2170
2223
  entities = entities.slice(0, options.topK);
2171
2224
  }
2225
+ if (options.consumer) {
2226
+ const deliveredIds = entities.map((e) => e.id).filter((id) => typeof id === "string");
2227
+ if (deliveredIds.length > 0) {
2228
+ this.batchTouchMemoryEntities(deliveredIds, options.consumer).catch(() => {});
2229
+ }
2230
+ }
2172
2231
  return { entities };
2173
2232
  }
2174
2233
  async deleteMemoryEntity(entityId) {
@@ -2177,9 +2236,10 @@ class HarmonyApiClient {
2177
2236
  async touchMemoryEntity(entityId) {
2178
2237
  return this.request("POST", `/memory/entities/${entityId}/touch`);
2179
2238
  }
2180
- async batchTouchMemoryEntities(entityIds) {
2239
+ async batchTouchMemoryEntities(entityIds, consumer) {
2181
2240
  return this.request("POST", "/memory/entities/batch-touch", {
2182
- entity_ids: entityIds
2241
+ entity_ids: entityIds,
2242
+ ...consumer ? { consumer } : {}
2183
2243
  });
2184
2244
  }
2185
2245
  async createMemoryRelation(data) {
@@ -2205,8 +2265,12 @@ class HarmonyApiClient {
2205
2265
  params.append("tags", tag);
2206
2266
  if (options?.include_superseded)
2207
2267
  params.set("include_superseded", "true");
2268
+ if (options?.consumer)
2269
+ params.set("consumer", options.consumer);
2208
2270
  if (options?.include_episodes)
2209
2271
  params.set("include_episodes", "true");
2272
+ if (options?.assembly_id)
2273
+ params.set("assembly_id", options.assembly_id);
2210
2274
  return this.request("GET", `/memory/search?${params.toString()}`);
2211
2275
  }
2212
2276
  async getVaultIndex(options) {
@@ -2218,6 +2282,8 @@ class HarmonyApiClient {
2218
2282
  params.set("type", options.type);
2219
2283
  if (options.limit !== undefined)
2220
2284
  params.set("limit", String(options.limit));
2285
+ if (options.consumer)
2286
+ params.set("consumer", options.consumer);
2221
2287
  if (options.include_episodes)
2222
2288
  params.set("include_episodes", "true");
2223
2289
  return this.request("GET", `/memory/index?${params.toString()}`);
@@ -2231,6 +2297,8 @@ class HarmonyApiClient {
2231
2297
  params.set("type", options.type);
2232
2298
  if (options.limit !== undefined)
2233
2299
  params.set("limit", String(options.limit));
2300
+ if (options.consumer)
2301
+ params.set("consumer", options.consumer);
2234
2302
  if (options.include_episodes)
2235
2303
  params.set("include_episodes", "true");
2236
2304
  return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
@@ -2290,6 +2358,8 @@ class HarmonyApiClient {
2290
2358
  params.set("type", options.type);
2291
2359
  if (options?.limit !== undefined)
2292
2360
  params.set("limit", String(options.limit));
2361
+ if (options?.consumer)
2362
+ params.set("consumer", options.consumer);
2293
2363
  if (options?.include_episodes)
2294
2364
  params.set("include_episodes", "true");
2295
2365
  return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
@@ -2395,14 +2465,15 @@ class HarmonyApiClient {
2395
2465
  } catch {}
2396
2466
  }
2397
2467
  const variant = options.variant || "execute";
2398
- const assembledContextStr = undefined;
2399
- const assemblyId = undefined;
2468
+ const assemblyId = randomUUID2();
2400
2469
  let memories;
2401
2470
  try {
2402
2471
  if (options.workspaceId && cardData.title) {
2403
- const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
2472
+ const memoryResult = await this.searchMemoryEntities(options.workspaceId, buildMemoryQuery(cardData.title, cardData.description), {
2404
2473
  project_id: options.projectId,
2405
- limit: 5
2474
+ limit: 5,
2475
+ consumer: "agent-prompt",
2476
+ assembly_id: assemblyId
2406
2477
  });
2407
2478
  if (memoryResult.entities?.length > 0) {
2408
2479
  memories = memoryResult.entities.map((e) => ({
@@ -2426,7 +2497,6 @@ class HarmonyApiClient {
2426
2497
  contextOptions: options.contextOptions,
2427
2498
  customConstraints: options.customConstraints,
2428
2499
  memories,
2429
- assembledContext: assembledContextStr,
2430
2500
  assemblyId
2431
2501
  });
2432
2502
  try {
@@ -3340,6 +3410,34 @@ async function onboardNewUser(params) {
3340
3410
  };
3341
3411
  }
3342
3412
 
3413
+ // src/playbook-metric-warnings.ts
3414
+ function playbookMetricWarnings(agents, steps) {
3415
+ if (!Array.isArray(steps))
3416
+ return [];
3417
+ const declared = declaredGateMetricsFromAgents(agents);
3418
+ if (!declared.known)
3419
+ return [];
3420
+ const warnings = [];
3421
+ const seen = new Set;
3422
+ for (const ref of referencedGateMetrics({ steps, steps_version: 2 })) {
3423
+ if (declared.names.has(ref.metric) || seen.has(ref.metric))
3424
+ continue;
3425
+ seen.add(ref.metric);
3426
+ warnings.push(`Gate metric "${ref.metric}" (stage "${ref.stageName}") is not declared by any agent in this workspace — a stage run gating on it will hold until a daemon declares it under agent.playbooks.metrics.${ref.metric}.`);
3427
+ }
3428
+ return warnings;
3429
+ }
3430
+ async function collectPlaybookMetricWarnings(client3, workspaceId, steps) {
3431
+ if (!workspaceId || !Array.isArray(steps))
3432
+ return [];
3433
+ try {
3434
+ const { agents } = await client3.listWorkspaceAgents(workspaceId);
3435
+ return playbookMetricWarnings(agents, steps);
3436
+ } catch {
3437
+ return [];
3438
+ }
3439
+ }
3440
+
3343
3441
  // src/skills.ts
3344
3442
  import {
3345
3443
  existsSync as existsSync4,
@@ -4384,7 +4482,7 @@ var TOOLS = {
4384
4482
  }
4385
4483
  },
4386
4484
  harmony_classify_card: {
4387
- description: "Classify a card with the LLM classifier: sets `intent` (plan/think/implement/review), `complexity_score` (0-10), `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the type label (feature/bug/idea). Call right after creating a card to classify it in-flow. Idempotent; never touches the user-owned `model_override`.",
4485
+ description: "DEPRECATED run sizing now happens at daemon pickup and is run-scoped, so nothing reads `model_tier`, `intent` or `complexity_score` any more; this tool still writes them, but only the type label has an effect. Prefer letting card creation apply the type label. Sets `intent` (plan/think/implement/review), `complexity_score` (0-10), `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the type label (feature/bug/idea). Idempotent; never touches the user-owned `model_override`.",
4388
4486
  inputSchema: {
4389
4487
  type: "object",
4390
4488
  properties: {
@@ -6897,7 +6995,7 @@ ${options}
6897
6995
  if (trimmed.length > 0) {
6898
6996
  const touchIds = trimmed.map(({ entity }) => entity?.id).filter((id) => typeof id === "string");
6899
6997
  if (touchIds.length > 0) {
6900
- client3.batchTouchMemoryEntities(touchIds).catch(() => {});
6998
+ client3.batchTouchMemoryEntities(touchIds, "mcp-tool").catch(() => {});
6901
6999
  }
6902
7000
  }
6903
7001
  let sessionEntities = [];
@@ -7311,7 +7409,12 @@ ${options}
7311
7409
  autoBind: args.autoBind,
7312
7410
  catalogId: args.catalogId
7313
7411
  });
7314
- return { success: true, playbook: result.playbook };
7412
+ const warnings = await collectPlaybookMetricWarnings(client3, workspaceId, args.steps);
7413
+ return {
7414
+ success: true,
7415
+ playbook: result.playbook,
7416
+ ...warnings.length > 0 ? { warnings } : {}
7417
+ };
7315
7418
  }
7316
7419
  case "harmony_update_playbook": {
7317
7420
  const playbookId = z.string().uuid().parse(args.playbookId);
@@ -7324,7 +7427,12 @@ ${options}
7324
7427
  triggerType: args.triggerType,
7325
7428
  ..."autoBind" in args ? { autoBind: args.autoBind } : {}
7326
7429
  });
7327
- return { success: true, playbook: result.playbook };
7430
+ const warnings = await collectPlaybookMetricWarnings(client3, result.playbook?.workspace_id, args.steps);
7431
+ return {
7432
+ success: true,
7433
+ playbook: result.playbook,
7434
+ ...warnings.length > 0 ? { warnings } : {}
7435
+ };
7328
7436
  }
7329
7437
  case "harmony_save_card_as_playbook":
7330
7438
  return deprecatedRemovedToolResult("harmony_save_card_as_playbook");