@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/index.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
  }
@@ -1433,6 +1375,9 @@ import {
1433
1375
  ReadResourceRequestSchema
1434
1376
  } from "@modelcontextprotocol/sdk/types.js";
1435
1377
  import { z } from "zod";
1378
+
1379
+ // src/api-client.ts
1380
+ import { randomUUID as randomUUID2 } from "node:crypto";
1436
1381
  // ../harmony-shared/dist/agentStaleness.js
1437
1382
  var AGENT_HEARTBEAT_LIVENESS_MS = 5 * 60 * 1000;
1438
1383
  var AGENT_MILESTONE_LIVENESS_MS = 30 * 60 * 1000;
@@ -1554,12 +1499,100 @@ var TIMINGS = {
1554
1499
  QUERY_STALE_TIME: 1000 * 60 * 5,
1555
1500
  QUERY_GC_TIME: 1000 * 60 * 60 * 24
1556
1501
  };
1502
+ // ../harmony-shared/dist/declaredGateMetrics.js
1503
+ function declaredGateMetricsFromAgents(agents) {
1504
+ const names = new Set;
1505
+ let known = false;
1506
+ for (const agent of agents) {
1507
+ const declared = agent.declared_gate_metrics;
1508
+ if (!Array.isArray(declared))
1509
+ continue;
1510
+ known = true;
1511
+ for (const name of declared) {
1512
+ if (typeof name === "string" && name.trim())
1513
+ names.add(name.trim());
1514
+ }
1515
+ }
1516
+ return { names, known };
1517
+ }
1518
+ // ../harmony-shared/dist/fanoutSource.js
1519
+ var FANOUT_KEY_MARKER = "harmony:fanout-item";
1520
+ var FANOUT_KEY_RE = new RegExp(`^\\[${FANOUT_KEY_MARKER}\\]:\\s*#(\\S+)\\s*$`, "m");
1521
+ // ../harmony-shared/dist/gateConfigError.js
1522
+ var GATE_CONFIG_ERROR_KEY = "configError";
1523
+ var GATE_CONFIG_ERROR_MARK = Object.freeze({ [GATE_CONFIG_ERROR_KEY]: true });
1557
1524
  // ../harmony-shared/dist/playbookStage.js
1525
+ var DEFAULT_LOOP_MAX_ITERATIONS = 5;
1526
+ function normalizeLoopDef(raw) {
1527
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
1528
+ return null;
1529
+ const obj = raw;
1530
+ if (obj.mode !== "converge" && obj.mode !== "fanout")
1531
+ return null;
1532
+ const mode = obj.mode;
1533
+ const rawMax = obj.max_iterations;
1534
+ const maxInt = typeof rawMax === "number" && Number.isFinite(rawMax) && rawMax >= 1 ? Math.floor(rawMax) : DEFAULT_LOOP_MAX_ITERATIONS;
1535
+ const exitGate = obj.exit_gate && typeof obj.exit_gate === "object" && !Array.isArray(obj.exit_gate) ? obj.exit_gate : null;
1536
+ const def = { mode, max_iterations: maxInt };
1537
+ if (exitGate)
1538
+ def.exit_gate = exitGate;
1539
+ if (obj.item_source && typeof obj.item_source === "object" && !Array.isArray(obj.item_source)) {
1540
+ def.item_source = obj.item_source;
1541
+ }
1542
+ if (typeof obj.concurrency === "number" && obj.concurrency >= 1) {
1543
+ def.concurrency = Math.floor(obj.concurrency);
1544
+ }
1545
+ if (obj.on_item_fail === "continue" || obj.on_item_fail === "halt") {
1546
+ def.on_item_fail = obj.on_item_fail;
1547
+ }
1548
+ return def;
1549
+ }
1550
+ function readStageDefs(def) {
1551
+ if (def.steps_version !== 2)
1552
+ return [];
1553
+ return Array.isArray(def.steps) ? def.steps : [];
1554
+ }
1558
1555
  var STAGE_DAEMON_OWNED_TOOLS = [
1559
1556
  "mcp__harmony__harmony_end_agent_session",
1560
1557
  "mcp__harmony__harmony_start_agent_session",
1561
1558
  "mcp__harmony__harmony_move_card"
1562
1559
  ];
1560
+ function customGateMetric(gate) {
1561
+ if (gate === null || typeof gate !== "object" || Array.isArray(gate)) {
1562
+ return null;
1563
+ }
1564
+ const record = gate;
1565
+ if (record.kind !== "custom")
1566
+ return null;
1567
+ if (record.pendingEngine === true)
1568
+ return null;
1569
+ const metric = typeof record.metric === "string" ? record.metric.trim() : "";
1570
+ return metric ? metric : null;
1571
+ }
1572
+ function referencedGateMetrics(def) {
1573
+ const out = [];
1574
+ for (const stage of readStageDefs(def)) {
1575
+ if (!stage || typeof stage !== "object")
1576
+ continue;
1577
+ const stageId = typeof stage.id === "string" ? stage.id : "";
1578
+ const stageName = typeof stage.name === "string" ? stage.name : stageId;
1579
+ const gateMetric = customGateMetric(stage.gate);
1580
+ if (gateMetric) {
1581
+ out.push({ stageId, stageName, metric: gateMetric, source: "gate" });
1582
+ }
1583
+ const loop = normalizeLoopDef(stage.loop);
1584
+ const loopMetric = loop?.exit_gate ? customGateMetric(loop.exit_gate) : null;
1585
+ if (loopMetric) {
1586
+ out.push({
1587
+ stageId,
1588
+ stageName,
1589
+ metric: loopMetric,
1590
+ source: "loop_exit_gate"
1591
+ });
1592
+ }
1593
+ }
1594
+ return out;
1595
+ }
1563
1596
  // ../harmony-shared/dist/reviewTools.js
1564
1597
  var REVIEW_DISALLOWED_TOOLS = [
1565
1598
  ...STAGE_DAEMON_OWNED_TOOLS,
@@ -1594,6 +1627,19 @@ function getRetryDelay(attempt) {
1594
1627
  return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
1595
1628
  }
1596
1629
  var sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1630
+ function buildMemoryQuery(title, description) {
1631
+ const DESCRIPTION_CAP = 600;
1632
+ const trimmedTitle = title.trim();
1633
+ const trimmedBody = (description ?? "").trim();
1634
+ if (!trimmedTitle && !trimmedBody)
1635
+ return "";
1636
+ if (!trimmedTitle)
1637
+ return trimmedBody.slice(0, DESCRIPTION_CAP);
1638
+ if (!trimmedBody)
1639
+ return trimmedTitle;
1640
+ return `${trimmedTitle}
1641
+ ${trimmedBody.slice(0, DESCRIPTION_CAP)}`;
1642
+ }
1597
1643
 
1598
1644
  class Semaphore {
1599
1645
  permits;
@@ -1899,8 +1945,12 @@ class HarmonyApiClient {
1899
1945
  async updateCard(cardId, updates) {
1900
1946
  return this.request("PATCH", `/cards/${cardId}`, updates);
1901
1947
  }
1902
- async claimCard(cardId, agentId) {
1903
- const res = await this.request("PATCH", `/cards/${cardId}`, { assignedAgentId: agentId, ifAssignedAgentNull: true });
1948
+ async claimCard(cardId, agentId, opts) {
1949
+ const res = await this.request("PATCH", `/cards/${cardId}`, {
1950
+ assignedAgentId: agentId,
1951
+ ifAssignedAgentNull: true,
1952
+ ...opts?.requireUnassigned ? { ifAssigneeNull: true } : {}
1953
+ });
1904
1954
  return { claimed: res.claimed !== false };
1905
1955
  }
1906
1956
  async moveCard(cardId, columnId, position) {
@@ -2064,6 +2114,14 @@ class HarmonyApiClient {
2064
2114
  params.set("sinceSeq", String(sinceSeq));
2065
2115
  return this.request("GET", `/cards/${cardId}/agent-messages?${params.toString()}`);
2066
2116
  }
2117
+ async postBudgetDecision(cardId, data) {
2118
+ return this.request("POST", `/cards/${cardId}/budget-decisions`, data);
2119
+ }
2120
+ async getBudgetDecisions(cardId, sinceIso) {
2121
+ const params = new URLSearchParams;
2122
+ params.set("sinceIso", sinceIso);
2123
+ return this.request("GET", `/cards/${cardId}/budget-decisions?${params.toString()}`);
2124
+ }
2067
2125
  async updateAgentProgress(cardId, data) {
2068
2126
  return this.request("POST", `/cards/${cardId}/agent-context`, data);
2069
2127
  }
@@ -2109,6 +2167,8 @@ class HarmonyApiClient {
2109
2167
  params.set("offset", String(options.offset));
2110
2168
  if (options.include_superseded)
2111
2169
  params.set("include_superseded", "true");
2170
+ if (options.consumer)
2171
+ params.set("consumer", options.consumer);
2112
2172
  if (options.include_episodes)
2113
2173
  params.set("include_episodes", "true");
2114
2174
  return this.request("GET", `/memory/entities?${params.toString()}`);
@@ -2164,6 +2224,12 @@ class HarmonyApiClient {
2164
2224
  if (options.topK !== undefined) {
2165
2225
  entities = entities.slice(0, options.topK);
2166
2226
  }
2227
+ if (options.consumer) {
2228
+ const deliveredIds = entities.map((e) => e.id).filter((id) => typeof id === "string");
2229
+ if (deliveredIds.length > 0) {
2230
+ this.batchTouchMemoryEntities(deliveredIds, options.consumer).catch(() => {});
2231
+ }
2232
+ }
2167
2233
  return { entities };
2168
2234
  }
2169
2235
  async deleteMemoryEntity(entityId) {
@@ -2172,9 +2238,10 @@ class HarmonyApiClient {
2172
2238
  async touchMemoryEntity(entityId) {
2173
2239
  return this.request("POST", `/memory/entities/${entityId}/touch`);
2174
2240
  }
2175
- async batchTouchMemoryEntities(entityIds) {
2241
+ async batchTouchMemoryEntities(entityIds, consumer) {
2176
2242
  return this.request("POST", "/memory/entities/batch-touch", {
2177
- entity_ids: entityIds
2243
+ entity_ids: entityIds,
2244
+ ...consumer ? { consumer } : {}
2178
2245
  });
2179
2246
  }
2180
2247
  async createMemoryRelation(data) {
@@ -2200,8 +2267,12 @@ class HarmonyApiClient {
2200
2267
  params.append("tags", tag);
2201
2268
  if (options?.include_superseded)
2202
2269
  params.set("include_superseded", "true");
2270
+ if (options?.consumer)
2271
+ params.set("consumer", options.consumer);
2203
2272
  if (options?.include_episodes)
2204
2273
  params.set("include_episodes", "true");
2274
+ if (options?.assembly_id)
2275
+ params.set("assembly_id", options.assembly_id);
2205
2276
  return this.request("GET", `/memory/search?${params.toString()}`);
2206
2277
  }
2207
2278
  async getVaultIndex(options) {
@@ -2213,6 +2284,8 @@ class HarmonyApiClient {
2213
2284
  params.set("type", options.type);
2214
2285
  if (options.limit !== undefined)
2215
2286
  params.set("limit", String(options.limit));
2287
+ if (options.consumer)
2288
+ params.set("consumer", options.consumer);
2216
2289
  if (options.include_episodes)
2217
2290
  params.set("include_episodes", "true");
2218
2291
  return this.request("GET", `/memory/index?${params.toString()}`);
@@ -2226,6 +2299,8 @@ class HarmonyApiClient {
2226
2299
  params.set("type", options.type);
2227
2300
  if (options.limit !== undefined)
2228
2301
  params.set("limit", String(options.limit));
2302
+ if (options.consumer)
2303
+ params.set("consumer", options.consumer);
2229
2304
  if (options.include_episodes)
2230
2305
  params.set("include_episodes", "true");
2231
2306
  return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
@@ -2285,6 +2360,8 @@ class HarmonyApiClient {
2285
2360
  params.set("type", options.type);
2286
2361
  if (options?.limit !== undefined)
2287
2362
  params.set("limit", String(options.limit));
2363
+ if (options?.consumer)
2364
+ params.set("consumer", options.consumer);
2288
2365
  if (options?.include_episodes)
2289
2366
  params.set("include_episodes", "true");
2290
2367
  return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
@@ -2390,14 +2467,15 @@ class HarmonyApiClient {
2390
2467
  } catch {}
2391
2468
  }
2392
2469
  const variant = options.variant || "execute";
2393
- const assembledContextStr = undefined;
2394
- const assemblyId = undefined;
2470
+ const assemblyId = randomUUID2();
2395
2471
  let memories;
2396
2472
  try {
2397
2473
  if (options.workspaceId && cardData.title) {
2398
- const memoryResult = await this.searchMemoryEntities(options.workspaceId, cardData.title, {
2474
+ const memoryResult = await this.searchMemoryEntities(options.workspaceId, buildMemoryQuery(cardData.title, cardData.description), {
2399
2475
  project_id: options.projectId,
2400
- limit: 5
2476
+ limit: 5,
2477
+ consumer: "agent-prompt",
2478
+ assembly_id: assemblyId
2401
2479
  });
2402
2480
  if (memoryResult.entities?.length > 0) {
2403
2481
  memories = memoryResult.entities.map((e) => ({
@@ -2421,7 +2499,6 @@ class HarmonyApiClient {
2421
2499
  contextOptions: options.contextOptions,
2422
2500
  customConstraints: options.customConstraints,
2423
2501
  memories,
2424
- assembledContext: assembledContextStr,
2425
2502
  assemblyId
2426
2503
  });
2427
2504
  try {
@@ -3335,6 +3412,34 @@ async function onboardNewUser(params) {
3335
3412
  };
3336
3413
  }
3337
3414
 
3415
+ // src/playbook-metric-warnings.ts
3416
+ function playbookMetricWarnings(agents, steps) {
3417
+ if (!Array.isArray(steps))
3418
+ return [];
3419
+ const declared = declaredGateMetricsFromAgents(agents);
3420
+ if (!declared.known)
3421
+ return [];
3422
+ const warnings = [];
3423
+ const seen = new Set;
3424
+ for (const ref of referencedGateMetrics({ steps, steps_version: 2 })) {
3425
+ if (declared.names.has(ref.metric) || seen.has(ref.metric))
3426
+ continue;
3427
+ seen.add(ref.metric);
3428
+ 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}.`);
3429
+ }
3430
+ return warnings;
3431
+ }
3432
+ async function collectPlaybookMetricWarnings(client3, workspaceId, steps) {
3433
+ if (!workspaceId || !Array.isArray(steps))
3434
+ return [];
3435
+ try {
3436
+ const { agents } = await client3.listWorkspaceAgents(workspaceId);
3437
+ return playbookMetricWarnings(agents, steps);
3438
+ } catch {
3439
+ return [];
3440
+ }
3441
+ }
3442
+
3338
3443
  // src/skills.ts
3339
3444
  import {
3340
3445
  existsSync as existsSync4,
@@ -4379,7 +4484,7 @@ var TOOLS = {
4379
4484
  }
4380
4485
  },
4381
4486
  harmony_classify_card: {
4382
- 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`.",
4487
+ 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`.",
4383
4488
  inputSchema: {
4384
4489
  type: "object",
4385
4490
  properties: {
@@ -6892,7 +6997,7 @@ ${options}
6892
6997
  if (trimmed.length > 0) {
6893
6998
  const touchIds = trimmed.map(({ entity }) => entity?.id).filter((id) => typeof id === "string");
6894
6999
  if (touchIds.length > 0) {
6895
- client3.batchTouchMemoryEntities(touchIds).catch(() => {});
7000
+ client3.batchTouchMemoryEntities(touchIds, "mcp-tool").catch(() => {});
6896
7001
  }
6897
7002
  }
6898
7003
  let sessionEntities = [];
@@ -7306,7 +7411,12 @@ ${options}
7306
7411
  autoBind: args.autoBind,
7307
7412
  catalogId: args.catalogId
7308
7413
  });
7309
- return { success: true, playbook: result.playbook };
7414
+ const warnings = await collectPlaybookMetricWarnings(client3, workspaceId, args.steps);
7415
+ return {
7416
+ success: true,
7417
+ playbook: result.playbook,
7418
+ ...warnings.length > 0 ? { warnings } : {}
7419
+ };
7310
7420
  }
7311
7421
  case "harmony_update_playbook": {
7312
7422
  const playbookId = z.string().uuid().parse(args.playbookId);
@@ -7319,7 +7429,12 @@ ${options}
7319
7429
  triggerType: args.triggerType,
7320
7430
  ..."autoBind" in args ? { autoBind: args.autoBind } : {}
7321
7431
  });
7322
- return { success: true, playbook: result.playbook };
7432
+ const warnings = await collectPlaybookMetricWarnings(client3, result.playbook?.workspace_id, args.steps);
7433
+ return {
7434
+ success: true,
7435
+ playbook: result.playbook,
7436
+ ...warnings.length > 0 ? { warnings } : {}
7437
+ };
7323
7438
  }
7324
7439
  case "harmony_save_card_as_playbook":
7325
7440
  return deprecatedRemovedToolResult("harmony_save_card_as_playbook");