@gethmy/mcp 2.15.0 → 2.17.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
@@ -1826,6 +1826,10 @@ class HarmonyApiClient {
1826
1826
  async updateCard(cardId, updates) {
1827
1827
  return this.request("PATCH", `/cards/${cardId}`, updates);
1828
1828
  }
1829
+ async claimCard(cardId, agentId) {
1830
+ const res = await this.request("PATCH", `/cards/${cardId}`, { assignedAgentId: agentId, ifAssignedAgentNull: true });
1831
+ return { claimed: res.claimed !== false };
1832
+ }
1829
1833
  async moveCard(cardId, columnId, position) {
1830
1834
  return this.request("POST", `/cards/${cardId}/move`, {
1831
1835
  columnId,
@@ -2028,6 +2032,8 @@ class HarmonyApiClient {
2028
2032
  params.set("offset", String(options.offset));
2029
2033
  if (options.include_superseded)
2030
2034
  params.set("include_superseded", "true");
2035
+ if (options.include_episodes)
2036
+ params.set("include_episodes", "true");
2031
2037
  return this.request("GET", `/memory/entities?${params.toString()}`);
2032
2038
  }
2033
2039
  async getMemoryEntity(entityId) {
@@ -2043,7 +2049,8 @@ class HarmonyApiClient {
2043
2049
  const search = await this.searchMemoryEntities(options.workspaceId, options.query, {
2044
2050
  project_id: options.projectId,
2045
2051
  type: options.type?.length === 1 ? options.type[0] : undefined,
2046
- limit: fetchLimit
2052
+ limit: fetchLimit,
2053
+ include_episodes: options.includeEpisodes
2047
2054
  });
2048
2055
  entities = search.entities ?? [];
2049
2056
  } else {
@@ -2054,7 +2061,8 @@ class HarmonyApiClient {
2054
2061
  type: options.type?.length === 1 ? options.type[0] : undefined,
2055
2062
  tags: options.tags,
2056
2063
  min_confidence: options.minConfidence,
2057
- limit: fetchLimit
2064
+ limit: fetchLimit,
2065
+ include_episodes: options.includeEpisodes
2058
2066
  });
2059
2067
  entities = list.entities ?? [];
2060
2068
  }
@@ -2115,6 +2123,8 @@ class HarmonyApiClient {
2115
2123
  params.append("tags", tag);
2116
2124
  if (options?.include_superseded)
2117
2125
  params.set("include_superseded", "true");
2126
+ if (options?.include_episodes)
2127
+ params.set("include_episodes", "true");
2118
2128
  return this.request("GET", `/memory/search?${params.toString()}`);
2119
2129
  }
2120
2130
  async getVaultIndex(options) {
@@ -2126,6 +2136,8 @@ class HarmonyApiClient {
2126
2136
  params.set("type", options.type);
2127
2137
  if (options.limit !== undefined)
2128
2138
  params.set("limit", String(options.limit));
2139
+ if (options.include_episodes)
2140
+ params.set("include_episodes", "true");
2129
2141
  return this.request("GET", `/memory/index?${params.toString()}`);
2130
2142
  }
2131
2143
  async getVaultIndexMarkdown(options) {
@@ -2137,6 +2149,8 @@ class HarmonyApiClient {
2137
2149
  params.set("type", options.type);
2138
2150
  if (options.limit !== undefined)
2139
2151
  params.set("limit", String(options.limit));
2152
+ if (options.include_episodes)
2153
+ params.set("include_episodes", "true");
2140
2154
  return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
2141
2155
  accept: "text/markdown"
2142
2156
  });
@@ -2194,6 +2208,8 @@ class HarmonyApiClient {
2194
2208
  params.set("type", options.type);
2195
2209
  if (options?.limit !== undefined)
2196
2210
  params.set("limit", String(options.limit));
2211
+ if (options?.include_episodes)
2212
+ params.set("include_episodes", "true");
2197
2213
  return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
2198
2214
  accept: "text/markdown"
2199
2215
  });
@@ -4829,6 +4845,10 @@ var TOOLS = {
4829
4845
  include_superseded: {
4830
4846
  type: "boolean",
4831
4847
  description: "Include rows whose `superseded_at` is set (default false). Use when investigating supersede chains."
4848
+ },
4849
+ include_episodes: {
4850
+ type: "boolean",
4851
+ description: "Include agent-run episodes — the daemon's per-run implement/review captures (default false). Excluded by default so recall surfaces curated, durable memory. Set true to audit the agent's run history."
4832
4852
  }
4833
4853
  }
4834
4854
  }
@@ -5014,7 +5034,11 @@ var TOOLS = {
5014
5034
  type: "string",
5015
5035
  description: "Project ID (optional)"
5016
5036
  },
5017
- limit: { type: "number", description: "Max results (default: 20)" }
5037
+ limit: { type: "number", description: "Max results (default: 20)" },
5038
+ include_episodes: {
5039
+ type: "boolean",
5040
+ description: "Include agent-run episodes — the daemon's per-run captures (default false). Excluded by default so search surfaces curated memory."
5041
+ }
5018
5042
  },
5019
5043
  required: ["query"]
5020
5044
  }
@@ -5055,6 +5079,10 @@ var TOOLS = {
5055
5079
  limit: {
5056
5080
  type: "number",
5057
5081
  description: "Max entities to return (default: 200)"
5082
+ },
5083
+ include_episodes: {
5084
+ type: "boolean",
5085
+ description: "Include agent-run episodes — the daemon's per-run captures (default false). Excluded by default so the index shows curated vault content, not run noise."
5058
5086
  }
5059
5087
  },
5060
5088
  required: []
@@ -6335,11 +6363,11 @@ async function handleToolCall(name, args, deps) {
6335
6363
  }
6336
6364
  case "harmony_generate_prompt": {
6337
6365
  let cardId;
6366
+ const projectId = args.projectId || deps.getActiveProjectId() || undefined;
6338
6367
  if (args.cardId) {
6339
6368
  cardId = z.string().uuid().parse(args.cardId);
6340
6369
  } else if (args.shortId !== undefined) {
6341
6370
  const shortId = z.number().int().positive().parse(args.shortId);
6342
- const projectId = args.projectId || deps.getActiveProjectId();
6343
6371
  if (!projectId) {
6344
6372
  throw new Error("Project ID required when using shortId. Use harmony_set_project_context or provide projectId.");
6345
6373
  }
@@ -6348,23 +6376,40 @@ async function handleToolCall(name, args, deps) {
6348
6376
  } else {
6349
6377
  throw new Error("Either cardId or shortId must be provided");
6350
6378
  }
6351
- let cardTitle = "";
6352
- let cardDescription = "";
6379
+ const variant = args.variant || undefined;
6380
+ const customConstraints = typeof args.customConstraints === "string" ? args.customConstraints : undefined;
6381
+ const contextOptions = {
6382
+ includeSubtasks: args.includeSubtasks !== false,
6383
+ includeLinks: args.includeLinks !== false,
6384
+ includeDescription: args.includeDescription !== false
6385
+ };
6353
6386
  try {
6354
- const { card } = await client3.getCard(cardId);
6355
- const typedCard = card;
6356
- cardTitle = typedCard.title || "";
6357
- cardDescription = typedCard.description || "";
6358
- } catch {}
6359
- const taskBlock = [cardTitle, cardDescription].filter(Boolean).join(`
6387
+ const result = await client3.generateCardPrompt({
6388
+ cardId,
6389
+ workspaceId: deps.getActiveWorkspaceId() || "",
6390
+ projectId,
6391
+ variant,
6392
+ customConstraints,
6393
+ contextOptions
6394
+ });
6395
+ return { success: true, ...result };
6396
+ } catch (err) {
6397
+ const msg = err instanceof Error ? err.message : String(err);
6398
+ console.debug(`[harmony_generate_prompt] falling back: ${msg}`);
6399
+ let cardTitle = "";
6400
+ let cardDescription = "";
6401
+ try {
6402
+ const { card } = await client3.getCard(cardId);
6403
+ const typedCard = card;
6404
+ cardTitle = typedCard.title || "";
6405
+ cardDescription = typedCard.description || "";
6406
+ } catch {}
6407
+ const taskBlock = [cardTitle, cardDescription].filter(Boolean).join(`
6360
6408
 
6361
6409
  `);
6362
- const prompt = `Here is the task: ${taskBlock || "(no description available)"}`;
6363
- return {
6364
- success: true,
6365
- prompt,
6366
- cardId
6367
- };
6410
+ const prompt = `Here is the task: ${taskBlock || "(no description available)"}`;
6411
+ return { success: true, prompt, cardId };
6412
+ }
6368
6413
  }
6369
6414
  case "harmony_remember": {
6370
6415
  const title = z.string().min(1).max(300).parse(args.title);
@@ -6465,6 +6510,7 @@ async function handleToolCall(name, args, deps) {
6465
6510
  const topK = args.top_k;
6466
6511
  const budgetTokens = args.budget_tokens;
6467
6512
  const includeSuperseded = Boolean(args.include_superseded);
6513
+ const includeEpisodes = Boolean(args.include_episodes);
6468
6514
  const queryText = args.query;
6469
6515
  const fetchLimit = Math.min(Math.max(requestedLimit ?? 20, topK ?? 0, 50), 200);
6470
6516
  const projectId = args.projectId || deps.getActiveProjectId() || undefined;
@@ -6481,7 +6527,8 @@ async function handleToolCall(name, args, deps) {
6481
6527
  type: args.type,
6482
6528
  limit: fetchLimit,
6483
6529
  tags: requestedTags && requestedTags.length > 0 ? requestedTags : undefined,
6484
- include_superseded: includeSuperseded
6530
+ include_superseded: includeSuperseded,
6531
+ include_episodes: includeEpisodes
6485
6532
  });
6486
6533
  entities = searchResult.entities ?? [];
6487
6534
  const minConfidence = args.minConfidence;
@@ -6504,7 +6551,8 @@ async function handleToolCall(name, args, deps) {
6504
6551
  tags: args.tags,
6505
6552
  min_confidence: args.minConfidence,
6506
6553
  limit: fetchLimit,
6507
- include_superseded: includeSuperseded
6554
+ include_superseded: includeSuperseded,
6555
+ include_episodes: includeEpisodes
6508
6556
  });
6509
6557
  entities = listed;
6510
6558
  if (excludeSessionFromLongTerm) {
@@ -6724,7 +6772,8 @@ async function handleToolCall(name, args, deps) {
6724
6772
  const markdown = await client3.searchMemoryEntitiesMarkdown(workspaceId, query, {
6725
6773
  project_id: args.projectId || deps.getActiveProjectId() || undefined,
6726
6774
  type: args.type,
6727
- limit: args.limit
6775
+ limit: args.limit,
6776
+ include_episodes: Boolean(args.include_episodes)
6728
6777
  });
6729
6778
  const searchMemSession = getActiveMemorySession();
6730
6779
  if (searchMemSession) {
@@ -6741,7 +6790,8 @@ async function handleToolCall(name, args, deps) {
6741
6790
  workspace_id: workspaceId,
6742
6791
  project_id: args.projectId || deps.getActiveProjectId() || undefined,
6743
6792
  type: args.type,
6744
- limit: args.limit
6793
+ limit: args.limit,
6794
+ include_episodes: Boolean(args.include_episodes)
6745
6795
  });
6746
6796
  return markdown || "No entities found.";
6747
6797
  }
package/dist/index.js CHANGED
@@ -1821,6 +1821,10 @@ class HarmonyApiClient {
1821
1821
  async updateCard(cardId, updates) {
1822
1822
  return this.request("PATCH", `/cards/${cardId}`, updates);
1823
1823
  }
1824
+ async claimCard(cardId, agentId) {
1825
+ const res = await this.request("PATCH", `/cards/${cardId}`, { assignedAgentId: agentId, ifAssignedAgentNull: true });
1826
+ return { claimed: res.claimed !== false };
1827
+ }
1824
1828
  async moveCard(cardId, columnId, position) {
1825
1829
  return this.request("POST", `/cards/${cardId}/move`, {
1826
1830
  columnId,
@@ -2023,6 +2027,8 @@ class HarmonyApiClient {
2023
2027
  params.set("offset", String(options.offset));
2024
2028
  if (options.include_superseded)
2025
2029
  params.set("include_superseded", "true");
2030
+ if (options.include_episodes)
2031
+ params.set("include_episodes", "true");
2026
2032
  return this.request("GET", `/memory/entities?${params.toString()}`);
2027
2033
  }
2028
2034
  async getMemoryEntity(entityId) {
@@ -2038,7 +2044,8 @@ class HarmonyApiClient {
2038
2044
  const search = await this.searchMemoryEntities(options.workspaceId, options.query, {
2039
2045
  project_id: options.projectId,
2040
2046
  type: options.type?.length === 1 ? options.type[0] : undefined,
2041
- limit: fetchLimit
2047
+ limit: fetchLimit,
2048
+ include_episodes: options.includeEpisodes
2042
2049
  });
2043
2050
  entities = search.entities ?? [];
2044
2051
  } else {
@@ -2049,7 +2056,8 @@ class HarmonyApiClient {
2049
2056
  type: options.type?.length === 1 ? options.type[0] : undefined,
2050
2057
  tags: options.tags,
2051
2058
  min_confidence: options.minConfidence,
2052
- limit: fetchLimit
2059
+ limit: fetchLimit,
2060
+ include_episodes: options.includeEpisodes
2053
2061
  });
2054
2062
  entities = list.entities ?? [];
2055
2063
  }
@@ -2110,6 +2118,8 @@ class HarmonyApiClient {
2110
2118
  params.append("tags", tag);
2111
2119
  if (options?.include_superseded)
2112
2120
  params.set("include_superseded", "true");
2121
+ if (options?.include_episodes)
2122
+ params.set("include_episodes", "true");
2113
2123
  return this.request("GET", `/memory/search?${params.toString()}`);
2114
2124
  }
2115
2125
  async getVaultIndex(options) {
@@ -2121,6 +2131,8 @@ class HarmonyApiClient {
2121
2131
  params.set("type", options.type);
2122
2132
  if (options.limit !== undefined)
2123
2133
  params.set("limit", String(options.limit));
2134
+ if (options.include_episodes)
2135
+ params.set("include_episodes", "true");
2124
2136
  return this.request("GET", `/memory/index?${params.toString()}`);
2125
2137
  }
2126
2138
  async getVaultIndexMarkdown(options) {
@@ -2132,6 +2144,8 @@ class HarmonyApiClient {
2132
2144
  params.set("type", options.type);
2133
2145
  if (options.limit !== undefined)
2134
2146
  params.set("limit", String(options.limit));
2147
+ if (options.include_episodes)
2148
+ params.set("include_episodes", "true");
2135
2149
  return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
2136
2150
  accept: "text/markdown"
2137
2151
  });
@@ -2189,6 +2203,8 @@ class HarmonyApiClient {
2189
2203
  params.set("type", options.type);
2190
2204
  if (options?.limit !== undefined)
2191
2205
  params.set("limit", String(options.limit));
2206
+ if (options?.include_episodes)
2207
+ params.set("include_episodes", "true");
2192
2208
  return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
2193
2209
  accept: "text/markdown"
2194
2210
  });
@@ -4824,6 +4840,10 @@ var TOOLS = {
4824
4840
  include_superseded: {
4825
4841
  type: "boolean",
4826
4842
  description: "Include rows whose `superseded_at` is set (default false). Use when investigating supersede chains."
4843
+ },
4844
+ include_episodes: {
4845
+ type: "boolean",
4846
+ description: "Include agent-run episodes — the daemon's per-run implement/review captures (default false). Excluded by default so recall surfaces curated, durable memory. Set true to audit the agent's run history."
4827
4847
  }
4828
4848
  }
4829
4849
  }
@@ -5009,7 +5029,11 @@ var TOOLS = {
5009
5029
  type: "string",
5010
5030
  description: "Project ID (optional)"
5011
5031
  },
5012
- limit: { type: "number", description: "Max results (default: 20)" }
5032
+ limit: { type: "number", description: "Max results (default: 20)" },
5033
+ include_episodes: {
5034
+ type: "boolean",
5035
+ description: "Include agent-run episodes — the daemon's per-run captures (default false). Excluded by default so search surfaces curated memory."
5036
+ }
5013
5037
  },
5014
5038
  required: ["query"]
5015
5039
  }
@@ -5050,6 +5074,10 @@ var TOOLS = {
5050
5074
  limit: {
5051
5075
  type: "number",
5052
5076
  description: "Max entities to return (default: 200)"
5077
+ },
5078
+ include_episodes: {
5079
+ type: "boolean",
5080
+ description: "Include agent-run episodes — the daemon's per-run captures (default false). Excluded by default so the index shows curated vault content, not run noise."
5053
5081
  }
5054
5082
  },
5055
5083
  required: []
@@ -6330,11 +6358,11 @@ async function handleToolCall(name, args, deps) {
6330
6358
  }
6331
6359
  case "harmony_generate_prompt": {
6332
6360
  let cardId;
6361
+ const projectId = args.projectId || deps.getActiveProjectId() || undefined;
6333
6362
  if (args.cardId) {
6334
6363
  cardId = z.string().uuid().parse(args.cardId);
6335
6364
  } else if (args.shortId !== undefined) {
6336
6365
  const shortId = z.number().int().positive().parse(args.shortId);
6337
- const projectId = args.projectId || deps.getActiveProjectId();
6338
6366
  if (!projectId) {
6339
6367
  throw new Error("Project ID required when using shortId. Use harmony_set_project_context or provide projectId.");
6340
6368
  }
@@ -6343,23 +6371,40 @@ async function handleToolCall(name, args, deps) {
6343
6371
  } else {
6344
6372
  throw new Error("Either cardId or shortId must be provided");
6345
6373
  }
6346
- let cardTitle = "";
6347
- let cardDescription = "";
6374
+ const variant = args.variant || undefined;
6375
+ const customConstraints = typeof args.customConstraints === "string" ? args.customConstraints : undefined;
6376
+ const contextOptions = {
6377
+ includeSubtasks: args.includeSubtasks !== false,
6378
+ includeLinks: args.includeLinks !== false,
6379
+ includeDescription: args.includeDescription !== false
6380
+ };
6348
6381
  try {
6349
- const { card } = await client3.getCard(cardId);
6350
- const typedCard = card;
6351
- cardTitle = typedCard.title || "";
6352
- cardDescription = typedCard.description || "";
6353
- } catch {}
6354
- const taskBlock = [cardTitle, cardDescription].filter(Boolean).join(`
6382
+ const result = await client3.generateCardPrompt({
6383
+ cardId,
6384
+ workspaceId: deps.getActiveWorkspaceId() || "",
6385
+ projectId,
6386
+ variant,
6387
+ customConstraints,
6388
+ contextOptions
6389
+ });
6390
+ return { success: true, ...result };
6391
+ } catch (err) {
6392
+ const msg = err instanceof Error ? err.message : String(err);
6393
+ console.debug(`[harmony_generate_prompt] falling back: ${msg}`);
6394
+ let cardTitle = "";
6395
+ let cardDescription = "";
6396
+ try {
6397
+ const { card } = await client3.getCard(cardId);
6398
+ const typedCard = card;
6399
+ cardTitle = typedCard.title || "";
6400
+ cardDescription = typedCard.description || "";
6401
+ } catch {}
6402
+ const taskBlock = [cardTitle, cardDescription].filter(Boolean).join(`
6355
6403
 
6356
6404
  `);
6357
- const prompt = `Here is the task: ${taskBlock || "(no description available)"}`;
6358
- return {
6359
- success: true,
6360
- prompt,
6361
- cardId
6362
- };
6405
+ const prompt = `Here is the task: ${taskBlock || "(no description available)"}`;
6406
+ return { success: true, prompt, cardId };
6407
+ }
6363
6408
  }
6364
6409
  case "harmony_remember": {
6365
6410
  const title = z.string().min(1).max(300).parse(args.title);
@@ -6460,6 +6505,7 @@ async function handleToolCall(name, args, deps) {
6460
6505
  const topK = args.top_k;
6461
6506
  const budgetTokens = args.budget_tokens;
6462
6507
  const includeSuperseded = Boolean(args.include_superseded);
6508
+ const includeEpisodes = Boolean(args.include_episodes);
6463
6509
  const queryText = args.query;
6464
6510
  const fetchLimit = Math.min(Math.max(requestedLimit ?? 20, topK ?? 0, 50), 200);
6465
6511
  const projectId = args.projectId || deps.getActiveProjectId() || undefined;
@@ -6476,7 +6522,8 @@ async function handleToolCall(name, args, deps) {
6476
6522
  type: args.type,
6477
6523
  limit: fetchLimit,
6478
6524
  tags: requestedTags && requestedTags.length > 0 ? requestedTags : undefined,
6479
- include_superseded: includeSuperseded
6525
+ include_superseded: includeSuperseded,
6526
+ include_episodes: includeEpisodes
6480
6527
  });
6481
6528
  entities = searchResult.entities ?? [];
6482
6529
  const minConfidence = args.minConfidence;
@@ -6499,7 +6546,8 @@ async function handleToolCall(name, args, deps) {
6499
6546
  tags: args.tags,
6500
6547
  min_confidence: args.minConfidence,
6501
6548
  limit: fetchLimit,
6502
- include_superseded: includeSuperseded
6549
+ include_superseded: includeSuperseded,
6550
+ include_episodes: includeEpisodes
6503
6551
  });
6504
6552
  entities = listed;
6505
6553
  if (excludeSessionFromLongTerm) {
@@ -6719,7 +6767,8 @@ async function handleToolCall(name, args, deps) {
6719
6767
  const markdown = await client3.searchMemoryEntitiesMarkdown(workspaceId, query, {
6720
6768
  project_id: args.projectId || deps.getActiveProjectId() || undefined,
6721
6769
  type: args.type,
6722
- limit: args.limit
6770
+ limit: args.limit,
6771
+ include_episodes: Boolean(args.include_episodes)
6723
6772
  });
6724
6773
  const searchMemSession = getActiveMemorySession();
6725
6774
  if (searchMemSession) {
@@ -6736,7 +6785,8 @@ async function handleToolCall(name, args, deps) {
6736
6785
  workspace_id: workspaceId,
6737
6786
  project_id: args.projectId || deps.getActiveProjectId() || undefined,
6738
6787
  type: args.type,
6739
- limit: args.limit
6788
+ limit: args.limit,
6789
+ include_episodes: Boolean(args.include_episodes)
6740
6790
  });
6741
6791
  return markdown || "No entities found.";
6742
6792
  }
@@ -1273,6 +1273,10 @@ class HarmonyApiClient {
1273
1273
  async updateCard(cardId, updates) {
1274
1274
  return this.request("PATCH", `/cards/${cardId}`, updates);
1275
1275
  }
1276
+ async claimCard(cardId, agentId) {
1277
+ const res = await this.request("PATCH", `/cards/${cardId}`, { assignedAgentId: agentId, ifAssignedAgentNull: true });
1278
+ return { claimed: res.claimed !== false };
1279
+ }
1276
1280
  async moveCard(cardId, columnId, position) {
1277
1281
  return this.request("POST", `/cards/${cardId}/move`, {
1278
1282
  columnId,
@@ -1475,6 +1479,8 @@ class HarmonyApiClient {
1475
1479
  params.set("offset", String(options.offset));
1476
1480
  if (options.include_superseded)
1477
1481
  params.set("include_superseded", "true");
1482
+ if (options.include_episodes)
1483
+ params.set("include_episodes", "true");
1478
1484
  return this.request("GET", `/memory/entities?${params.toString()}`);
1479
1485
  }
1480
1486
  async getMemoryEntity(entityId) {
@@ -1490,7 +1496,8 @@ class HarmonyApiClient {
1490
1496
  const search = await this.searchMemoryEntities(options.workspaceId, options.query, {
1491
1497
  project_id: options.projectId,
1492
1498
  type: options.type?.length === 1 ? options.type[0] : undefined,
1493
- limit: fetchLimit
1499
+ limit: fetchLimit,
1500
+ include_episodes: options.includeEpisodes
1494
1501
  });
1495
1502
  entities = search.entities ?? [];
1496
1503
  } else {
@@ -1501,7 +1508,8 @@ class HarmonyApiClient {
1501
1508
  type: options.type?.length === 1 ? options.type[0] : undefined,
1502
1509
  tags: options.tags,
1503
1510
  min_confidence: options.minConfidence,
1504
- limit: fetchLimit
1511
+ limit: fetchLimit,
1512
+ include_episodes: options.includeEpisodes
1505
1513
  });
1506
1514
  entities = list.entities ?? [];
1507
1515
  }
@@ -1562,6 +1570,8 @@ class HarmonyApiClient {
1562
1570
  params.append("tags", tag);
1563
1571
  if (options?.include_superseded)
1564
1572
  params.set("include_superseded", "true");
1573
+ if (options?.include_episodes)
1574
+ params.set("include_episodes", "true");
1565
1575
  return this.request("GET", `/memory/search?${params.toString()}`);
1566
1576
  }
1567
1577
  async getVaultIndex(options) {
@@ -1573,6 +1583,8 @@ class HarmonyApiClient {
1573
1583
  params.set("type", options.type);
1574
1584
  if (options.limit !== undefined)
1575
1585
  params.set("limit", String(options.limit));
1586
+ if (options.include_episodes)
1587
+ params.set("include_episodes", "true");
1576
1588
  return this.request("GET", `/memory/index?${params.toString()}`);
1577
1589
  }
1578
1590
  async getVaultIndexMarkdown(options) {
@@ -1584,6 +1596,8 @@ class HarmonyApiClient {
1584
1596
  params.set("type", options.type);
1585
1597
  if (options.limit !== undefined)
1586
1598
  params.set("limit", String(options.limit));
1599
+ if (options.include_episodes)
1600
+ params.set("include_episodes", "true");
1587
1601
  return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
1588
1602
  accept: "text/markdown"
1589
1603
  });
@@ -1641,6 +1655,8 @@ class HarmonyApiClient {
1641
1655
  params.set("type", options.type);
1642
1656
  if (options?.limit !== undefined)
1643
1657
  params.set("limit", String(options.limit));
1658
+ if (options?.include_episodes)
1659
+ params.set("include_episodes", "true");
1644
1660
  return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
1645
1661
  accept: "text/markdown"
1646
1662
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/mcp",
3
- "version": "2.15.0",
3
+ "version": "2.17.0",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/api-client.ts CHANGED
@@ -625,6 +625,25 @@ export class HarmonyApiClient {
625
625
  return this.request("PATCH", `/cards/${cardId}`, updates);
626
626
  }
627
627
 
628
+ /**
629
+ * Compare-and-set claim of a card for a virtual agent: sets assigned_agent_id
630
+ * only if it is currently NULL (server-side guard). Returns `{ claimed: false }`
631
+ * when another daemon already owns it. Used by the agent-agnostic review pickup.
632
+ */
633
+ async claimCard(
634
+ cardId: string,
635
+ agentId: string,
636
+ ): Promise<{ claimed: boolean }> {
637
+ const res = await this.request<{ card?: unknown; claimed?: boolean }>(
638
+ "PATCH",
639
+ `/cards/${cardId}`,
640
+ { assignedAgentId: agentId, ifAssignedAgentNull: true },
641
+ );
642
+ // Success body is `{ card }` (no `claimed` key) → won. Lost-race body is
643
+ // `{ claimed: false }`.
644
+ return { claimed: res.claimed !== false };
645
+ }
646
+
628
647
  async moveCard(
629
648
  cardId: string,
630
649
  columnId: string,
@@ -1166,6 +1185,9 @@ export class HarmonyApiClient {
1166
1185
  limit?: number;
1167
1186
  offset?: number;
1168
1187
  include_superseded?: boolean;
1188
+ // Opt in to agent-run episodes (default excluded, #677). Only the daemon's
1189
+ // own rolling-episode lookup should set this.
1190
+ include_episodes?: boolean;
1169
1191
  }): Promise<{ entities: unknown[]; count: number }> {
1170
1192
  const params = new URLSearchParams();
1171
1193
  params.set("workspace_id", options.workspace_id);
@@ -1182,6 +1204,7 @@ export class HarmonyApiClient {
1182
1204
  if (options.offset !== undefined)
1183
1205
  params.set("offset", String(options.offset));
1184
1206
  if (options.include_superseded) params.set("include_superseded", "true");
1207
+ if (options.include_episodes) params.set("include_episodes", "true");
1185
1208
  return this.request("GET", `/memory/entities?${params.toString()}`);
1186
1209
  }
1187
1210
 
@@ -1230,6 +1253,9 @@ export class HarmonyApiClient {
1230
1253
  tags?: string[];
1231
1254
  minConfidence?: number;
1232
1255
  topK?: number;
1256
+ // Opt in to agent-run episodes (default excluded server-side, #677). The
1257
+ // daemon sets this when looking up its own rolling implement/review episode.
1258
+ includeEpisodes?: boolean;
1233
1259
  }): Promise<{ entities: unknown[] }> {
1234
1260
  // Over-fetch beyond topK so client-side filters (multi-type, memory_tier,
1235
1261
  // tags) have headroom — matches the MCP server's recall path (server.ts).
@@ -1246,6 +1272,7 @@ export class HarmonyApiClient {
1246
1272
  project_id: options.projectId,
1247
1273
  type: options.type?.length === 1 ? options.type[0] : undefined,
1248
1274
  limit: fetchLimit,
1275
+ include_episodes: options.includeEpisodes,
1249
1276
  },
1250
1277
  );
1251
1278
  entities = (search.entities ?? []) as Array<Record<string, unknown>>;
@@ -1258,6 +1285,7 @@ export class HarmonyApiClient {
1258
1285
  tags: options.tags,
1259
1286
  min_confidence: options.minConfidence,
1260
1287
  limit: fetchLimit,
1288
+ include_episodes: options.includeEpisodes,
1261
1289
  });
1262
1290
  entities = (list.entities ?? []) as Array<Record<string, unknown>>;
1263
1291
  }
@@ -1341,6 +1369,7 @@ export class HarmonyApiClient {
1341
1369
  limit?: number;
1342
1370
  tags?: string[];
1343
1371
  include_superseded?: boolean;
1372
+ include_episodes?: boolean;
1344
1373
  },
1345
1374
  ): Promise<{ entities: unknown[]; count: number }> {
1346
1375
  const params = new URLSearchParams();
@@ -1354,6 +1383,7 @@ export class HarmonyApiClient {
1354
1383
  // matches against the canonical `tags_normalized` column (#299).
1355
1384
  for (const tag of options?.tags ?? []) params.append("tags", tag);
1356
1385
  if (options?.include_superseded) params.set("include_superseded", "true");
1386
+ if (options?.include_episodes) params.set("include_episodes", "true");
1357
1387
  return this.request("GET", `/memory/search?${params.toString()}`);
1358
1388
  }
1359
1389
 
@@ -1364,12 +1394,14 @@ export class HarmonyApiClient {
1364
1394
  project_id?: string;
1365
1395
  type?: string;
1366
1396
  limit?: number;
1397
+ include_episodes?: boolean;
1367
1398
  }): Promise<{ entities: unknown[]; count: number }> {
1368
1399
  const params = new URLSearchParams();
1369
1400
  params.set("workspace_id", options.workspace_id);
1370
1401
  if (options.project_id) params.set("project_id", options.project_id);
1371
1402
  if (options.type) params.set("type", options.type);
1372
1403
  if (options.limit !== undefined) params.set("limit", String(options.limit));
1404
+ if (options.include_episodes) params.set("include_episodes", "true");
1373
1405
  return this.request("GET", `/memory/index?${params.toString()}`);
1374
1406
  }
1375
1407
 
@@ -1378,12 +1410,14 @@ export class HarmonyApiClient {
1378
1410
  project_id?: string;
1379
1411
  type?: string;
1380
1412
  limit?: number;
1413
+ include_episodes?: boolean;
1381
1414
  }): Promise<string> {
1382
1415
  const params = new URLSearchParams();
1383
1416
  params.set("workspace_id", options.workspace_id);
1384
1417
  if (options.project_id) params.set("project_id", options.project_id);
1385
1418
  if (options.type) params.set("type", options.type);
1386
1419
  if (options.limit !== undefined) params.set("limit", String(options.limit));
1420
+ if (options.include_episodes) params.set("include_episodes", "true");
1387
1421
  return this.requestRaw(
1388
1422
  "GET",
1389
1423
  `/memory/index?${params.toString()}`,
@@ -1470,6 +1504,7 @@ export class HarmonyApiClient {
1470
1504
  project_id?: string;
1471
1505
  type?: string;
1472
1506
  limit?: number;
1507
+ include_episodes?: boolean;
1473
1508
  },
1474
1509
  ): Promise<string> {
1475
1510
  const params = new URLSearchParams();
@@ -1479,6 +1514,7 @@ export class HarmonyApiClient {
1479
1514
  if (options?.type) params.set("type", options.type);
1480
1515
  if (options?.limit !== undefined)
1481
1516
  params.set("limit", String(options.limit));
1517
+ if (options?.include_episodes) params.set("include_episodes", "true");
1482
1518
  return this.requestRaw(
1483
1519
  "GET",
1484
1520
  `/memory/search?${params.toString()}`,
package/src/server.ts CHANGED
@@ -1743,6 +1743,11 @@ export const TOOLS = {
1743
1743
  description:
1744
1744
  "Include rows whose `superseded_at` is set (default false). Use when investigating supersede chains.",
1745
1745
  },
1746
+ include_episodes: {
1747
+ type: "boolean",
1748
+ description:
1749
+ "Include agent-run episodes — the daemon's per-run implement/review captures (default false). Excluded by default so recall surfaces curated, durable memory. Set true to audit the agent's run history.",
1750
+ },
1746
1751
  },
1747
1752
  },
1748
1753
  },
@@ -1934,6 +1939,11 @@ export const TOOLS = {
1934
1939
  description: "Project ID (optional)",
1935
1940
  },
1936
1941
  limit: { type: "number", description: "Max results (default: 20)" },
1942
+ include_episodes: {
1943
+ type: "boolean",
1944
+ description:
1945
+ "Include agent-run episodes — the daemon's per-run captures (default false). Excluded by default so search surfaces curated memory.",
1946
+ },
1937
1947
  },
1938
1948
  required: ["query"],
1939
1949
  },
@@ -1978,6 +1988,11 @@ export const TOOLS = {
1978
1988
  type: "number",
1979
1989
  description: "Max entities to return (default: 200)",
1980
1990
  },
1991
+ include_episodes: {
1992
+ type: "boolean",
1993
+ description:
1994
+ "Include agent-run episodes — the daemon's per-run captures (default false). Excluded by default so the index shows curated vault content, not run noise.",
1995
+ },
1981
1996
  },
1982
1997
  required: [],
1983
1998
  },
@@ -3824,17 +3839,16 @@ async function handleToolCall(
3824
3839
  }
3825
3840
 
3826
3841
  // Prompt generation
3827
- // TODO Phase 1: rebuild full context assembly per docs/superpowers/plans/2026-05-07-memory-architecture-v2.md §10
3828
3842
  case "harmony_generate_prompt": {
3829
3843
  // Resolve card ID — either directly or via short ID
3830
3844
  let cardId: string;
3845
+ const projectId =
3846
+ (args.projectId as string) || deps.getActiveProjectId() || undefined;
3831
3847
 
3832
3848
  if (args.cardId) {
3833
3849
  cardId = z.string().uuid().parse(args.cardId);
3834
3850
  } else if (args.shortId !== undefined) {
3835
3851
  const shortId = z.number().int().positive().parse(args.shortId);
3836
- const projectId =
3837
- (args.projectId as string) || deps.getActiveProjectId();
3838
3852
  if (!projectId) {
3839
3853
  throw new Error(
3840
3854
  "Project ID required when using shortId. Use harmony_set_project_context or provide projectId.",
@@ -3846,29 +3860,58 @@ async function handleToolCall(
3846
3860
  throw new Error("Either cardId or shortId must be provided");
3847
3861
  }
3848
3862
 
3849
- // Phase 0: minimal placeholder prompt with no context assembly.
3850
- // Fetch just the card title/description so the agent has the basics.
3851
- let cardTitle = "";
3852
- let cardDescription = "";
3863
+ const variant =
3864
+ (args.variant as "analysis" | "draft" | "execute") || undefined;
3865
+ const customConstraints =
3866
+ typeof args.customConstraints === "string"
3867
+ ? args.customConstraints
3868
+ : undefined;
3869
+
3870
+ // Context toggles default ON — a card's reference fields (linked cards,
3871
+ // external URLs, attachments) are hydrated inside generateCardPrompt so
3872
+ // the prompt carries the links the agent needs to fetch related content.
3873
+ const contextOptions = {
3874
+ includeSubtasks: args.includeSubtasks !== false,
3875
+ includeLinks: args.includeLinks !== false,
3876
+ includeDescription: args.includeDescription !== false,
3877
+ };
3878
+
3853
3879
  try {
3854
- const { card } = await client.getCard(cardId);
3855
- const typedCard = card as { title?: string; description?: string };
3856
- cardTitle = typedCard.title || "";
3857
- cardDescription = typedCard.description || "";
3858
- } catch {
3859
- // Card fetch failed; return an empty task description.
3860
- }
3880
+ const result = await client.generateCardPrompt({
3881
+ cardId,
3882
+ workspaceId: deps.getActiveWorkspaceId() || "",
3883
+ projectId,
3884
+ variant,
3885
+ customConstraints,
3886
+ contextOptions,
3887
+ });
3888
+ return { success: true, ...result };
3889
+ } catch (err) {
3890
+ // Graceful fallback: if full context assembly fails (e.g. an offline
3891
+ // sub-fetch), still return a usable title/description prompt so the
3892
+ // Harmony command never dead-ends. The reference-link hydration is
3893
+ // best-effort inside generateCardPrompt and won't reach here.
3894
+ const msg = err instanceof Error ? err.message : String(err);
3895
+ console.debug(`[harmony_generate_prompt] falling back: ${msg}`);
3896
+
3897
+ let cardTitle = "";
3898
+ let cardDescription = "";
3899
+ try {
3900
+ const { card } = await client.getCard(cardId);
3901
+ const typedCard = card as { title?: string; description?: string };
3902
+ cardTitle = typedCard.title || "";
3903
+ cardDescription = typedCard.description || "";
3904
+ } catch {
3905
+ // Card fetch failed; return an empty task description.
3906
+ }
3861
3907
 
3862
- const taskBlock = [cardTitle, cardDescription]
3863
- .filter(Boolean)
3864
- .join("\n\n");
3865
- const prompt = `Here is the task: ${taskBlock || "(no description available)"}`;
3908
+ const taskBlock = [cardTitle, cardDescription]
3909
+ .filter(Boolean)
3910
+ .join("\n\n");
3911
+ const prompt = `Here is the task: ${taskBlock || "(no description available)"}`;
3866
3912
 
3867
- return {
3868
- success: true,
3869
- prompt,
3870
- cardId,
3871
- };
3913
+ return { success: true, prompt, cardId };
3914
+ }
3872
3915
  }
3873
3916
 
3874
3917
  // Memory / Knowledge Graph operations
@@ -4080,6 +4123,8 @@ async function handleToolCall(
4080
4123
  const topK = args.top_k as number | undefined;
4081
4124
  const budgetTokens = args.budget_tokens as number | undefined;
4082
4125
  const includeSuperseded = Boolean(args.include_superseded);
4126
+ // #677: agent-run episodes are excluded from recall by default; opt in.
4127
+ const includeEpisodes = Boolean(args.include_episodes);
4083
4128
  const queryText = args.query as string | undefined;
4084
4129
 
4085
4130
  // Over-fetch when top_k is smaller than limit so the rescorer has
@@ -4138,6 +4183,7 @@ async function handleToolCall(
4138
4183
  ? requestedTags
4139
4184
  : undefined,
4140
4185
  include_superseded: includeSuperseded,
4186
+ include_episodes: includeEpisodes,
4141
4187
  },
4142
4188
  );
4143
4189
  entities = (searchResult.entities ?? []) as any[];
@@ -4171,6 +4217,7 @@ async function handleToolCall(
4171
4217
  min_confidence: args.minConfidence as number | undefined,
4172
4218
  limit: fetchLimit,
4173
4219
  include_superseded: includeSuperseded,
4220
+ include_episodes: includeEpisodes,
4174
4221
  });
4175
4222
  entities = listed as any[];
4176
4223
  if (excludeSessionFromLongTerm) {
@@ -4515,6 +4562,7 @@ async function handleToolCall(
4515
4562
  undefined,
4516
4563
  type: args.type as string | undefined,
4517
4564
  limit: args.limit as number | undefined,
4565
+ include_episodes: Boolean(args.include_episodes),
4518
4566
  },
4519
4567
  );
4520
4568
 
@@ -4542,6 +4590,7 @@ async function handleToolCall(
4542
4590
  (args.projectId as string) || deps.getActiveProjectId() || undefined,
4543
4591
  type: args.type as string | undefined,
4544
4592
  limit: args.limit as number | undefined,
4593
+ include_episodes: Boolean(args.include_episodes),
4545
4594
  });
4546
4595
  return markdown || "No entities found.";
4547
4596
  }