@gethmy/mcp 2.24.0 → 2.26.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,100 @@ 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/fanoutSource.js
1524
+ var FANOUT_KEY_MARKER = "harmony:fanout-item";
1525
+ var FANOUT_KEY_RE = new RegExp(`^\\[${FANOUT_KEY_MARKER}\\]:\\s*#(\\S+)\\s*$`, "m");
1526
+ // ../harmony-shared/dist/gateConfigError.js
1527
+ var GATE_CONFIG_ERROR_KEY = "configError";
1528
+ var GATE_CONFIG_ERROR_MARK = Object.freeze({ [GATE_CONFIG_ERROR_KEY]: true });
1562
1529
  // ../harmony-shared/dist/playbookStage.js
1530
+ var DEFAULT_LOOP_MAX_ITERATIONS = 5;
1531
+ function normalizeLoopDef(raw) {
1532
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
1533
+ return null;
1534
+ const obj = raw;
1535
+ if (obj.mode !== "converge" && obj.mode !== "fanout")
1536
+ return null;
1537
+ const mode = obj.mode;
1538
+ const rawMax = obj.max_iterations;
1539
+ const maxInt = typeof rawMax === "number" && Number.isFinite(rawMax) && rawMax >= 1 ? Math.floor(rawMax) : DEFAULT_LOOP_MAX_ITERATIONS;
1540
+ const exitGate = obj.exit_gate && typeof obj.exit_gate === "object" && !Array.isArray(obj.exit_gate) ? obj.exit_gate : null;
1541
+ const def = { mode, max_iterations: maxInt };
1542
+ if (exitGate)
1543
+ def.exit_gate = exitGate;
1544
+ if (obj.item_source && typeof obj.item_source === "object" && !Array.isArray(obj.item_source)) {
1545
+ def.item_source = obj.item_source;
1546
+ }
1547
+ if (typeof obj.concurrency === "number" && obj.concurrency >= 1) {
1548
+ def.concurrency = Math.floor(obj.concurrency);
1549
+ }
1550
+ if (obj.on_item_fail === "continue" || obj.on_item_fail === "halt") {
1551
+ def.on_item_fail = obj.on_item_fail;
1552
+ }
1553
+ return def;
1554
+ }
1555
+ function readStageDefs(def) {
1556
+ if (def.steps_version !== 2)
1557
+ return [];
1558
+ return Array.isArray(def.steps) ? def.steps : [];
1559
+ }
1563
1560
  var STAGE_DAEMON_OWNED_TOOLS = [
1564
1561
  "mcp__harmony__harmony_end_agent_session",
1565
1562
  "mcp__harmony__harmony_start_agent_session",
1566
1563
  "mcp__harmony__harmony_move_card"
1567
1564
  ];
1565
+ function customGateMetric(gate) {
1566
+ if (gate === null || typeof gate !== "object" || Array.isArray(gate)) {
1567
+ return null;
1568
+ }
1569
+ const record = gate;
1570
+ if (record.kind !== "custom")
1571
+ return null;
1572
+ if (record.pendingEngine === true)
1573
+ return null;
1574
+ const metric = typeof record.metric === "string" ? record.metric.trim() : "";
1575
+ return metric ? metric : null;
1576
+ }
1577
+ function referencedGateMetrics(def) {
1578
+ const out = [];
1579
+ for (const stage of readStageDefs(def)) {
1580
+ if (!stage || typeof stage !== "object")
1581
+ continue;
1582
+ const stageId = typeof stage.id === "string" ? stage.id : "";
1583
+ const stageName = typeof stage.name === "string" ? stage.name : stageId;
1584
+ const gateMetric = customGateMetric(stage.gate);
1585
+ if (gateMetric) {
1586
+ out.push({ stageId, stageName, metric: gateMetric, source: "gate" });
1587
+ }
1588
+ const loop = normalizeLoopDef(stage.loop);
1589
+ const loopMetric = loop?.exit_gate ? customGateMetric(loop.exit_gate) : null;
1590
+ if (loopMetric) {
1591
+ out.push({
1592
+ stageId,
1593
+ stageName,
1594
+ metric: loopMetric,
1595
+ source: "loop_exit_gate"
1596
+ });
1597
+ }
1598
+ }
1599
+ return out;
1600
+ }
1568
1601
  // ../harmony-shared/dist/reviewTools.js
1569
1602
  var REVIEW_DISALLOWED_TOOLS = [
1570
1603
  ...STAGE_DAEMON_OWNED_TOOLS,
@@ -1599,6 +1632,19 @@ function getRetryDelay(attempt) {
1599
1632
  return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
1600
1633
  }
1601
1634
  var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1635
+ function buildMemoryQuery(title, description) {
1636
+ const DESCRIPTION_CAP = 600;
1637
+ const trimmedTitle = title.trim();
1638
+ const trimmedBody = (description ?? "").trim();
1639
+ if (!trimmedTitle && !trimmedBody)
1640
+ return "";
1641
+ if (!trimmedTitle)
1642
+ return trimmedBody.slice(0, DESCRIPTION_CAP);
1643
+ if (!trimmedBody)
1644
+ return trimmedTitle;
1645
+ return `${trimmedTitle}
1646
+ ${trimmedBody.slice(0, DESCRIPTION_CAP)}`;
1647
+ }
1602
1648
 
1603
1649
  class Semaphore {
1604
1650
  permits;
@@ -1904,8 +1950,12 @@ class HarmonyApiClient {
1904
1950
  async updateCard(cardId, updates) {
1905
1951
  return this.request("PATCH", `/cards/${cardId}`, updates);
1906
1952
  }
1907
- async claimCard(cardId, agentId) {
1908
- const res = await this.request("PATCH", `/cards/${cardId}`, { assignedAgentId: agentId, ifAssignedAgentNull: true });
1953
+ async claimCard(cardId, agentId, opts) {
1954
+ const res = await this.request("PATCH", `/cards/${cardId}`, {
1955
+ assignedAgentId: agentId,
1956
+ ifAssignedAgentNull: true,
1957
+ ...opts?.requireUnassigned ? { ifAssigneeNull: true } : {}
1958
+ });
1909
1959
  return { claimed: res.claimed !== false };
1910
1960
  }
1911
1961
  async moveCard(cardId, columnId, position) {
@@ -2069,6 +2119,14 @@ class HarmonyApiClient {
2069
2119
  params.set("sinceSeq", String(sinceSeq));
2070
2120
  return this.request("GET", `/cards/${cardId}/agent-messages?${params.toString()}`);
2071
2121
  }
2122
+ async postBudgetDecision(cardId, data) {
2123
+ return this.request("POST", `/cards/${cardId}/budget-decisions`, data);
2124
+ }
2125
+ async getBudgetDecisions(cardId, sinceIso) {
2126
+ const params = new URLSearchParams;
2127
+ params.set("sinceIso", sinceIso);
2128
+ return this.request("GET", `/cards/${cardId}/budget-decisions?${params.toString()}`);
2129
+ }
2072
2130
  async updateAgentProgress(cardId, data) {
2073
2131
  return this.request("POST", `/cards/${cardId}/agent-context`, data);
2074
2132
  }
@@ -2114,6 +2172,8 @@ class HarmonyApiClient {
2114
2172
  params.set("offset", String(options.offset));
2115
2173
  if (options.include_superseded)
2116
2174
  params.set("include_superseded", "true");
2175
+ if (options.consumer)
2176
+ params.set("consumer", options.consumer);
2117
2177
  if (options.include_episodes)
2118
2178
  params.set("include_episodes", "true");
2119
2179
  return this.request("GET", `/memory/entities?${params.toString()}`);
@@ -2169,6 +2229,12 @@ class HarmonyApiClient {
2169
2229
  if (options.topK !== undefined) {
2170
2230
  entities = entities.slice(0, options.topK);
2171
2231
  }
2232
+ if (options.consumer) {
2233
+ const deliveredIds = entities.map((e) => e.id).filter((id) => typeof id === "string");
2234
+ if (deliveredIds.length > 0) {
2235
+ this.batchTouchMemoryEntities(deliveredIds, options.consumer).catch(() => {});
2236
+ }
2237
+ }
2172
2238
  return { entities };
2173
2239
  }
2174
2240
  async deleteMemoryEntity(entityId) {
@@ -2177,9 +2243,10 @@ class HarmonyApiClient {
2177
2243
  async touchMemoryEntity(entityId) {
2178
2244
  return this.request("POST", `/memory/entities/${entityId}/touch`);
2179
2245
  }
2180
- async batchTouchMemoryEntities(entityIds) {
2246
+ async batchTouchMemoryEntities(entityIds, consumer) {
2181
2247
  return this.request("POST", "/memory/entities/batch-touch", {
2182
- entity_ids: entityIds
2248
+ entity_ids: entityIds,
2249
+ ...consumer ? { consumer } : {}
2183
2250
  });
2184
2251
  }
2185
2252
  async createMemoryRelation(data) {
@@ -2205,8 +2272,12 @@ class HarmonyApiClient {
2205
2272
  params.append("tags", tag);
2206
2273
  if (options?.include_superseded)
2207
2274
  params.set("include_superseded", "true");
2275
+ if (options?.consumer)
2276
+ params.set("consumer", options.consumer);
2208
2277
  if (options?.include_episodes)
2209
2278
  params.set("include_episodes", "true");
2279
+ if (options?.assembly_id)
2280
+ params.set("assembly_id", options.assembly_id);
2210
2281
  return this.request("GET", `/memory/search?${params.toString()}`);
2211
2282
  }
2212
2283
  async getVaultIndex(options) {
@@ -2218,6 +2289,8 @@ class HarmonyApiClient {
2218
2289
  params.set("type", options.type);
2219
2290
  if (options.limit !== undefined)
2220
2291
  params.set("limit", String(options.limit));
2292
+ if (options.consumer)
2293
+ params.set("consumer", options.consumer);
2221
2294
  if (options.include_episodes)
2222
2295
  params.set("include_episodes", "true");
2223
2296
  return this.request("GET", `/memory/index?${params.toString()}`);
@@ -2231,6 +2304,8 @@ class HarmonyApiClient {
2231
2304
  params.set("type", options.type);
2232
2305
  if (options.limit !== undefined)
2233
2306
  params.set("limit", String(options.limit));
2307
+ if (options.consumer)
2308
+ params.set("consumer", options.consumer);
2234
2309
  if (options.include_episodes)
2235
2310
  params.set("include_episodes", "true");
2236
2311
  return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
@@ -2290,6 +2365,8 @@ class HarmonyApiClient {
2290
2365
  params.set("type", options.type);
2291
2366
  if (options?.limit !== undefined)
2292
2367
  params.set("limit", String(options.limit));
2368
+ if (options?.consumer)
2369
+ params.set("consumer", options.consumer);
2293
2370
  if (options?.include_episodes)
2294
2371
  params.set("include_episodes", "true");
2295
2372
  return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
@@ -2395,14 +2472,15 @@ class HarmonyApiClient {
2395
2472
  } catch {}
2396
2473
  }
2397
2474
  const variant = options.variant || "execute";
2398
- const assembledContextStr = undefined;
2399
- const assemblyId = undefined;
2475
+ const assemblyId = randomUUID2();
2400
2476
  let memories;
2401
2477
  try {
2402
2478
  if (options.workspaceId && cardData.title) {
2403
- const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
2479
+ const memoryResult = await this.searchMemoryEntities(options.workspaceId, buildMemoryQuery(cardData.title, cardData.description), {
2404
2480
  project_id: options.projectId,
2405
- limit: 5
2481
+ limit: 5,
2482
+ consumer: "agent-prompt",
2483
+ assembly_id: assemblyId
2406
2484
  });
2407
2485
  if (memoryResult.entities?.length > 0) {
2408
2486
  memories = memoryResult.entities.map((e) => ({
@@ -2426,7 +2504,6 @@ class HarmonyApiClient {
2426
2504
  contextOptions: options.contextOptions,
2427
2505
  customConstraints: options.customConstraints,
2428
2506
  memories,
2429
- assembledContext: assembledContextStr,
2430
2507
  assemblyId
2431
2508
  });
2432
2509
  try {
@@ -3340,6 +3417,34 @@ async function onboardNewUser(params) {
3340
3417
  };
3341
3418
  }
3342
3419
 
3420
+ // src/playbook-metric-warnings.ts
3421
+ function playbookMetricWarnings(agents, steps) {
3422
+ if (!Array.isArray(steps))
3423
+ return [];
3424
+ const declared = declaredGateMetricsFromAgents(agents);
3425
+ if (!declared.known)
3426
+ return [];
3427
+ const warnings = [];
3428
+ const seen = new Set;
3429
+ for (const ref of referencedGateMetrics({ steps, steps_version: 2 })) {
3430
+ if (declared.names.has(ref.metric) || seen.has(ref.metric))
3431
+ continue;
3432
+ seen.add(ref.metric);
3433
+ 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}.`);
3434
+ }
3435
+ return warnings;
3436
+ }
3437
+ async function collectPlaybookMetricWarnings(client3, workspaceId, steps) {
3438
+ if (!workspaceId || !Array.isArray(steps))
3439
+ return [];
3440
+ try {
3441
+ const { agents } = await client3.listWorkspaceAgents(workspaceId);
3442
+ return playbookMetricWarnings(agents, steps);
3443
+ } catch {
3444
+ return [];
3445
+ }
3446
+ }
3447
+
3343
3448
  // src/skills.ts
3344
3449
  import {
3345
3450
  existsSync as existsSync4,
@@ -4384,7 +4489,7 @@ var TOOLS = {
4384
4489
  }
4385
4490
  },
4386
4491
  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`.",
4492
+ 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
4493
  inputSchema: {
4389
4494
  type: "object",
4390
4495
  properties: {
@@ -6897,7 +7002,7 @@ ${options}
6897
7002
  if (trimmed.length > 0) {
6898
7003
  const touchIds = trimmed.map(({ entity }) => entity?.id).filter((id) => typeof id === "string");
6899
7004
  if (touchIds.length > 0) {
6900
- client3.batchTouchMemoryEntities(touchIds).catch(() => {});
7005
+ client3.batchTouchMemoryEntities(touchIds, "mcp-tool").catch(() => {});
6901
7006
  }
6902
7007
  }
6903
7008
  let sessionEntities = [];
@@ -7311,7 +7416,12 @@ ${options}
7311
7416
  autoBind: args.autoBind,
7312
7417
  catalogId: args.catalogId
7313
7418
  });
7314
- return { success: true, playbook: result.playbook };
7419
+ const warnings = await collectPlaybookMetricWarnings(client3, workspaceId, args.steps);
7420
+ return {
7421
+ success: true,
7422
+ playbook: result.playbook,
7423
+ ...warnings.length > 0 ? { warnings } : {}
7424
+ };
7315
7425
  }
7316
7426
  case "harmony_update_playbook": {
7317
7427
  const playbookId = z.string().uuid().parse(args.playbookId);
@@ -7324,7 +7434,12 @@ ${options}
7324
7434
  triggerType: args.triggerType,
7325
7435
  ..."autoBind" in args ? { autoBind: args.autoBind } : {}
7326
7436
  });
7327
- return { success: true, playbook: result.playbook };
7437
+ const warnings = await collectPlaybookMetricWarnings(client3, result.playbook?.workspace_id, args.steps);
7438
+ return {
7439
+ success: true,
7440
+ playbook: result.playbook,
7441
+ ...warnings.length > 0 ? { warnings } : {}
7442
+ };
7328
7443
  }
7329
7444
  case "harmony_save_card_as_playbook":
7330
7445
  return deprecatedRemovedToolResult("harmony_save_card_as_playbook");