@gethmy/mcp 2.13.0 → 2.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1928,6 +1928,10 @@ class HarmonyApiClient {
1928
1928
  qs.set("limit", String(opts.limit));
1929
1929
  if (opts?.offset != null)
1930
1930
  qs.set("offset", String(opts.offset));
1931
+ if (opts?.order != null)
1932
+ qs.set("order", opts.order);
1933
+ if (opts?.commentType != null)
1934
+ qs.set("comment_type", opts.commentType);
1931
1935
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
1932
1936
  return this.request("GET", `/cards/${cardId}/comments${suffix}`);
1933
1937
  }
@@ -2293,7 +2297,8 @@ class HarmonyApiClient {
2293
2297
  });
2294
2298
  try {
2295
2299
  const { comments } = await this.getComments(options.cardId, {
2296
- limit: 200
2300
+ limit: 200,
2301
+ order: "desc"
2297
2302
  });
2298
2303
  if (Array.isArray(comments) && comments.length > 0) {
2299
2304
  const section = serializeCommentThread(comments, {
@@ -3529,6 +3534,45 @@ function parseLabelList(raw) {
3529
3534
  }
3530
3535
  return;
3531
3536
  }
3537
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3538
+ function requireStringArg(raw, field, opts = {}) {
3539
+ let value;
3540
+ if (typeof raw === "string")
3541
+ value = raw.trim();
3542
+ else if (typeof raw === "number" || typeof raw === "boolean")
3543
+ value = String(raw);
3544
+ else
3545
+ throw new Error(`${field} is required and must be a string (received ${raw === undefined || raw === null ? "nothing" : typeof raw}).`);
3546
+ if (value.length === 0)
3547
+ throw new Error(`${field} is required and must not be empty.`);
3548
+ if (opts.max && value.length > opts.max)
3549
+ throw new Error(`${field} must be at most ${opts.max} characters (received ${value.length}).`);
3550
+ return value;
3551
+ }
3552
+ function requireUuidArg(raw, field) {
3553
+ const value = requireStringArg(raw, field);
3554
+ if (!UUID_RE.test(value))
3555
+ throw new Error(`${field} must be a valid UUID (received "${value}").`);
3556
+ return value;
3557
+ }
3558
+ function optionalPercentArg(raw, field) {
3559
+ if (raw === undefined || raw === null)
3560
+ return;
3561
+ const n = typeof raw === "number" ? raw : Number(raw);
3562
+ if (!Number.isFinite(n))
3563
+ throw new Error(`${field} must be a number between 0 and 100.`);
3564
+ if (n < 0 || n > 100)
3565
+ throw new Error(`${field} must be between 0 and 100 (received ${n}).`);
3566
+ return n;
3567
+ }
3568
+ function optionalNonNegativeNumberArg(raw, field) {
3569
+ if (raw === undefined || raw === null)
3570
+ return;
3571
+ const n = typeof raw === "number" ? raw : Number(raw);
3572
+ if (!Number.isFinite(n) || n < 0)
3573
+ throw new Error(`${field} must be a non-negative number.`);
3574
+ return n;
3575
+ }
3532
3576
  function initMemorySession(cardId, agentIdentifier, agentName, agentSessionId) {
3533
3577
  memorySessions.set(cardId, {
3534
3578
  cardId,
@@ -5376,7 +5420,6 @@ function registerHandlers(server, deps) {
5376
5420
  const { name, arguments: args } = request.params;
5377
5421
  const toolArgs = args || {};
5378
5422
  const cardIdArg = toolArgs.cardId;
5379
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5380
5423
  if (cardIdArg && UUID_RE.test(cardIdArg) && deps.isConfigured()) {
5381
5424
  const isAutoStartTrigger = AUTO_START_TRIGGERS.has(name);
5382
5425
  const cv = server.getClientVersion?.();
@@ -6042,9 +6085,11 @@ async function handleToolCall(name, args, deps) {
6042
6085
  return { success: true, ...result };
6043
6086
  }
6044
6087
  case "harmony_start_agent_session": {
6045
- const cardId = z.string().uuid().parse(args.cardId);
6046
- const agentIdentifier = z.string().min(1).max(100).parse(args.agentIdentifier);
6047
- const agentName = z.string().min(1).max(100).parse(args.agentName);
6088
+ const cardId = requireUuidArg(args.cardId, "cardId");
6089
+ const agentIdentifier = requireStringArg(args.agentIdentifier, "agentIdentifier", { max: 100 });
6090
+ const agentName = requireStringArg(args.agentName, "agentName", {
6091
+ max: 100
6092
+ });
6048
6093
  const moveToColumn = args.moveToColumn;
6049
6094
  const addLabels = parseLabelList(args.addLabels);
6050
6095
  let movedTo = null;
@@ -6105,7 +6150,7 @@ async function handleToolCall(name, args, deps) {
6105
6150
  agentName,
6106
6151
  status: "working",
6107
6152
  currentTask: args.currentTask,
6108
- estimatedMinutesRemaining: args.estimatedMinutesRemaining,
6153
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
6109
6154
  steerable: args.steerable === true || args.steerable === "true" ? true : undefined
6110
6155
  });
6111
6156
  markExplicit(cardId, {
@@ -6125,10 +6170,12 @@ async function handleToolCall(name, args, deps) {
6125
6170
  };
6126
6171
  }
6127
6172
  case "harmony_update_agent_progress": {
6128
- const cardId = z.string().uuid().parse(args.cardId);
6129
- const agentIdentifier = z.string().min(1).max(100).parse(args.agentIdentifier);
6130
- const agentName = z.string().min(1).max(100).parse(args.agentName);
6131
- const progressPercent = args.progressPercent !== undefined ? z.number().min(0).max(100).parse(args.progressPercent) : undefined;
6173
+ const cardId = requireUuidArg(args.cardId, "cardId");
6174
+ const agentIdentifier = requireStringArg(args.agentIdentifier, "agentIdentifier", { max: 100 });
6175
+ const agentName = requireStringArg(args.agentName, "agentName", {
6176
+ max: 100
6177
+ });
6178
+ const progressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
6132
6179
  const callerActions = args.actions;
6133
6180
  const now = new Date().toISOString();
6134
6181
  const callerRecentActions = [
@@ -6153,17 +6200,17 @@ async function handleToolCall(name, args, deps) {
6153
6200
  progressPercent,
6154
6201
  currentTask: args.currentTask,
6155
6202
  blockers: args.blockers,
6156
- estimatedMinutesRemaining: args.estimatedMinutesRemaining,
6203
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
6157
6204
  ...mergedRecentActions && { recentActions: mergedRecentActions },
6158
6205
  ...runActivity.length > 0 && { runActivity }
6159
6206
  });
6160
6207
  return { success: true, midSessionLearnings: 0, ...result };
6161
6208
  }
6162
6209
  case "harmony_end_agent_session": {
6163
- const cardId = z.string().uuid().parse(args.cardId);
6210
+ const cardId = requireUuidArg(args.cardId, "cardId");
6164
6211
  const moveToColumn = args.moveToColumn;
6165
6212
  const sessionStatus = args.status || "completed";
6166
- const endProgressPercent = args.progressPercent !== undefined ? z.number().min(0).max(100).parse(args.progressPercent) : undefined;
6213
+ const endProgressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
6167
6214
  await flushMemoryActions(client3, cardId);
6168
6215
  cleanupMemorySession(cardId);
6169
6216
  let result = { session: null };
package/dist/index.js CHANGED
@@ -1923,6 +1923,10 @@ class HarmonyApiClient {
1923
1923
  qs.set("limit", String(opts.limit));
1924
1924
  if (opts?.offset != null)
1925
1925
  qs.set("offset", String(opts.offset));
1926
+ if (opts?.order != null)
1927
+ qs.set("order", opts.order);
1928
+ if (opts?.commentType != null)
1929
+ qs.set("comment_type", opts.commentType);
1926
1930
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
1927
1931
  return this.request("GET", `/cards/${cardId}/comments${suffix}`);
1928
1932
  }
@@ -2288,7 +2292,8 @@ class HarmonyApiClient {
2288
2292
  });
2289
2293
  try {
2290
2294
  const { comments } = await this.getComments(options.cardId, {
2291
- limit: 200
2295
+ limit: 200,
2296
+ order: "desc"
2292
2297
  });
2293
2298
  if (Array.isArray(comments) && comments.length > 0) {
2294
2299
  const section = serializeCommentThread(comments, {
@@ -3524,6 +3529,45 @@ function parseLabelList(raw) {
3524
3529
  }
3525
3530
  return;
3526
3531
  }
3532
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3533
+ function requireStringArg(raw, field, opts = {}) {
3534
+ let value;
3535
+ if (typeof raw === "string")
3536
+ value = raw.trim();
3537
+ else if (typeof raw === "number" || typeof raw === "boolean")
3538
+ value = String(raw);
3539
+ else
3540
+ throw new Error(`${field} is required and must be a string (received ${raw === undefined || raw === null ? "nothing" : typeof raw}).`);
3541
+ if (value.length === 0)
3542
+ throw new Error(`${field} is required and must not be empty.`);
3543
+ if (opts.max && value.length > opts.max)
3544
+ throw new Error(`${field} must be at most ${opts.max} characters (received ${value.length}).`);
3545
+ return value;
3546
+ }
3547
+ function requireUuidArg(raw, field) {
3548
+ const value = requireStringArg(raw, field);
3549
+ if (!UUID_RE.test(value))
3550
+ throw new Error(`${field} must be a valid UUID (received "${value}").`);
3551
+ return value;
3552
+ }
3553
+ function optionalPercentArg(raw, field) {
3554
+ if (raw === undefined || raw === null)
3555
+ return;
3556
+ const n = typeof raw === "number" ? raw : Number(raw);
3557
+ if (!Number.isFinite(n))
3558
+ throw new Error(`${field} must be a number between 0 and 100.`);
3559
+ if (n < 0 || n > 100)
3560
+ throw new Error(`${field} must be between 0 and 100 (received ${n}).`);
3561
+ return n;
3562
+ }
3563
+ function optionalNonNegativeNumberArg(raw, field) {
3564
+ if (raw === undefined || raw === null)
3565
+ return;
3566
+ const n = typeof raw === "number" ? raw : Number(raw);
3567
+ if (!Number.isFinite(n) || n < 0)
3568
+ throw new Error(`${field} must be a non-negative number.`);
3569
+ return n;
3570
+ }
3527
3571
  function initMemorySession(cardId, agentIdentifier, agentName, agentSessionId) {
3528
3572
  memorySessions.set(cardId, {
3529
3573
  cardId,
@@ -5371,7 +5415,6 @@ function registerHandlers(server, deps) {
5371
5415
  const { name, arguments: args } = request.params;
5372
5416
  const toolArgs = args || {};
5373
5417
  const cardIdArg = toolArgs.cardId;
5374
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5375
5418
  if (cardIdArg && UUID_RE.test(cardIdArg) && deps.isConfigured()) {
5376
5419
  const isAutoStartTrigger = AUTO_START_TRIGGERS.has(name);
5377
5420
  const cv = server.getClientVersion?.();
@@ -6037,9 +6080,11 @@ async function handleToolCall(name, args, deps) {
6037
6080
  return { success: true, ...result };
6038
6081
  }
6039
6082
  case "harmony_start_agent_session": {
6040
- const cardId = z.string().uuid().parse(args.cardId);
6041
- const agentIdentifier = z.string().min(1).max(100).parse(args.agentIdentifier);
6042
- const agentName = z.string().min(1).max(100).parse(args.agentName);
6083
+ const cardId = requireUuidArg(args.cardId, "cardId");
6084
+ const agentIdentifier = requireStringArg(args.agentIdentifier, "agentIdentifier", { max: 100 });
6085
+ const agentName = requireStringArg(args.agentName, "agentName", {
6086
+ max: 100
6087
+ });
6043
6088
  const moveToColumn = args.moveToColumn;
6044
6089
  const addLabels = parseLabelList(args.addLabels);
6045
6090
  let movedTo = null;
@@ -6100,7 +6145,7 @@ async function handleToolCall(name, args, deps) {
6100
6145
  agentName,
6101
6146
  status: "working",
6102
6147
  currentTask: args.currentTask,
6103
- estimatedMinutesRemaining: args.estimatedMinutesRemaining,
6148
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
6104
6149
  steerable: args.steerable === true || args.steerable === "true" ? true : undefined
6105
6150
  });
6106
6151
  markExplicit(cardId, {
@@ -6120,10 +6165,12 @@ async function handleToolCall(name, args, deps) {
6120
6165
  };
6121
6166
  }
6122
6167
  case "harmony_update_agent_progress": {
6123
- const cardId = z.string().uuid().parse(args.cardId);
6124
- const agentIdentifier = z.string().min(1).max(100).parse(args.agentIdentifier);
6125
- const agentName = z.string().min(1).max(100).parse(args.agentName);
6126
- const progressPercent = args.progressPercent !== undefined ? z.number().min(0).max(100).parse(args.progressPercent) : undefined;
6168
+ const cardId = requireUuidArg(args.cardId, "cardId");
6169
+ const agentIdentifier = requireStringArg(args.agentIdentifier, "agentIdentifier", { max: 100 });
6170
+ const agentName = requireStringArg(args.agentName, "agentName", {
6171
+ max: 100
6172
+ });
6173
+ const progressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
6127
6174
  const callerActions = args.actions;
6128
6175
  const now = new Date().toISOString();
6129
6176
  const callerRecentActions = [
@@ -6148,17 +6195,17 @@ async function handleToolCall(name, args, deps) {
6148
6195
  progressPercent,
6149
6196
  currentTask: args.currentTask,
6150
6197
  blockers: args.blockers,
6151
- estimatedMinutesRemaining: args.estimatedMinutesRemaining,
6198
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(args.estimatedMinutesRemaining, "estimatedMinutesRemaining"),
6152
6199
  ...mergedRecentActions && { recentActions: mergedRecentActions },
6153
6200
  ...runActivity.length > 0 && { runActivity }
6154
6201
  });
6155
6202
  return { success: true, midSessionLearnings: 0, ...result };
6156
6203
  }
6157
6204
  case "harmony_end_agent_session": {
6158
- const cardId = z.string().uuid().parse(args.cardId);
6205
+ const cardId = requireUuidArg(args.cardId, "cardId");
6159
6206
  const moveToColumn = args.moveToColumn;
6160
6207
  const sessionStatus = args.status || "completed";
6161
- const endProgressPercent = args.progressPercent !== undefined ? z.number().min(0).max(100).parse(args.progressPercent) : undefined;
6208
+ const endProgressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
6162
6209
  await flushMemoryActions(client3, cardId);
6163
6210
  cleanupMemorySession(cardId);
6164
6211
  let result = { session: null };
@@ -1375,6 +1375,10 @@ class HarmonyApiClient {
1375
1375
  qs.set("limit", String(opts.limit));
1376
1376
  if (opts?.offset != null)
1377
1377
  qs.set("offset", String(opts.offset));
1378
+ if (opts?.order != null)
1379
+ qs.set("order", opts.order);
1380
+ if (opts?.commentType != null)
1381
+ qs.set("comment_type", opts.commentType);
1378
1382
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
1379
1383
  return this.request("GET", `/cards/${cardId}/comments${suffix}`);
1380
1384
  }
@@ -1740,7 +1744,8 @@ class HarmonyApiClient {
1740
1744
  });
1741
1745
  try {
1742
1746
  const { comments } = await this.getComments(options.cardId, {
1743
- limit: 200
1747
+ limit: 200,
1748
+ order: "desc"
1744
1749
  });
1745
1750
  if (Array.isArray(comments) && comments.length > 0) {
1746
1751
  const section = serializeCommentThread(comments, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/mcp",
3
- "version": "2.13.0",
3
+ "version": "2.13.2",
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
@@ -857,11 +857,25 @@ export class HarmonyApiClient {
857
857
 
858
858
  async getComments(
859
859
  cardId: string,
860
- opts?: { limit?: number; offset?: number },
860
+ opts?: {
861
+ limit?: number;
862
+ offset?: number;
863
+ /**
864
+ * Created-at sort direction. Default server-side is "asc" (oldest first).
865
+ * Pass "desc" so a bounded `limit` window pages the NEWEST comments — what
866
+ * the daemon's handoff and card-context reads need on a chatty card so the
867
+ * most-recent comments aren't paged out (card #531).
868
+ */
869
+ order?: "asc" | "desc";
870
+ /** Filter to a single `comment_type` (e.g. "decision" for handoffs). */
871
+ commentType?: string;
872
+ },
861
873
  ): Promise<{ comments: unknown[] }> {
862
874
  const qs = new URLSearchParams();
863
875
  if (opts?.limit != null) qs.set("limit", String(opts.limit));
864
876
  if (opts?.offset != null) qs.set("offset", String(opts.offset));
877
+ if (opts?.order != null) qs.set("order", opts.order);
878
+ if (opts?.commentType != null) qs.set("comment_type", opts.commentType);
865
879
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
866
880
  return this.request("GET", `/cards/${cardId}/comments${suffix}`);
867
881
  }
@@ -1734,8 +1748,12 @@ export class HarmonyApiClient {
1734
1748
  // Code, in-app builder) gets the recency-ordered thread + conflict rule.
1735
1749
  // Best-effort — never fail prompt generation on a comments fetch error.
1736
1750
  try {
1751
+ // Newest-first window so a chatty card (>200 comments) keeps the recent
1752
+ // thread instead of paging out to the oldest comments (#531). The
1753
+ // serializer re-sorts oldest→newest, so the rendered order is unchanged.
1737
1754
  const { comments } = await this.getComments(options.cardId, {
1738
1755
  limit: 200,
1756
+ order: "desc",
1739
1757
  });
1740
1758
  if (Array.isArray(comments) && comments.length > 0) {
1741
1759
  const section = serializeCommentThread(comments as Comment[], {
package/src/server.ts CHANGED
@@ -244,6 +244,86 @@ function parseLabelList(raw: unknown): string[] | undefined {
244
244
  return undefined;
245
245
  }
246
246
 
247
+ const UUID_RE =
248
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
249
+
250
+ /**
251
+ * Coerce + validate a required string argument from a (possibly lenient) MCP
252
+ * caller, throwing a clear, field-attributed error on failure.
253
+ *
254
+ * The agent-session tools previously parsed these with a bare
255
+ * `z.string().parse(args.foo)`. When the field was missing or arrived as a
256
+ * non-string, Zod surfaced an opaque root-level blob —
257
+ * `[{"expected":"string","received":"undefined","path":[],...}]` — that names
258
+ * neither the offending field nor the received value. To an agent that reads
259
+ * as "the tool rejects every argument shape with an `expected string` error"
260
+ * (card #530). Instead we name the field, report what we got, and (matching
261
+ * the `parseLabelList` philosophy for quirky clients) accept the
262
+ * number/boolean values some MCP clients emit by stringifying scalars.
263
+ */
264
+ function requireStringArg(
265
+ raw: unknown,
266
+ field: string,
267
+ opts: { max?: number } = {},
268
+ ): string {
269
+ let value: string;
270
+ if (typeof raw === "string") value = raw.trim();
271
+ else if (typeof raw === "number" || typeof raw === "boolean")
272
+ value = String(raw);
273
+ else
274
+ throw new Error(
275
+ `${field} is required and must be a string (received ${
276
+ raw === undefined || raw === null ? "nothing" : typeof raw
277
+ }).`,
278
+ );
279
+ if (value.length === 0)
280
+ throw new Error(`${field} is required and must not be empty.`);
281
+ if (opts.max && value.length > opts.max)
282
+ throw new Error(
283
+ `${field} must be at most ${opts.max} characters (received ${value.length}).`,
284
+ );
285
+ return value;
286
+ }
287
+
288
+ /** Coerce + validate a required UUID argument with a field-named error. */
289
+ function requireUuidArg(raw: unknown, field: string): string {
290
+ const value = requireStringArg(raw, field);
291
+ if (!UUID_RE.test(value))
292
+ throw new Error(`${field} must be a valid UUID (received "${value}").`);
293
+ return value;
294
+ }
295
+
296
+ /**
297
+ * Coerce an optional 0–100 percentage. Accepts numeric strings (e.g. "50")
298
+ * because lenient MCP clients stringify scalar arguments; returns `undefined`
299
+ * when absent and throws a clear, field-named error when out of range.
300
+ */
301
+ function optionalPercentArg(raw: unknown, field: string): number | undefined {
302
+ if (raw === undefined || raw === null) return undefined;
303
+ const n = typeof raw === "number" ? raw : Number(raw);
304
+ if (!Number.isFinite(n))
305
+ throw new Error(`${field} must be a number between 0 and 100.`);
306
+ if (n < 0 || n > 100)
307
+ throw new Error(`${field} must be between 0 and 100 (received ${n}).`);
308
+ return n;
309
+ }
310
+
311
+ /**
312
+ * Coerce an optional non-negative number argument (e.g.
313
+ * `estimatedMinutesRemaining`). Accepts numeric strings; returns `undefined`
314
+ * when absent so the field is omitted from the API payload entirely.
315
+ */
316
+ function optionalNonNegativeNumberArg(
317
+ raw: unknown,
318
+ field: string,
319
+ ): number | undefined {
320
+ if (raw === undefined || raw === null) return undefined;
321
+ const n = typeof raw === "number" ? raw : Number(raw);
322
+ if (!Number.isFinite(n) || n < 0)
323
+ throw new Error(`${field} must be a non-negative number.`);
324
+ return n;
325
+ }
326
+
247
327
  function initMemorySession(
248
328
  cardId: string,
249
329
  agentIdentifier: string,
@@ -2369,8 +2449,6 @@ export function registerHandlers(server: Server, deps: ToolDeps): void {
2369
2449
  // Auto-session pre-hook: track activity on card-related tools
2370
2450
  const toolArgs = args || {};
2371
2451
  const cardIdArg = toolArgs.cardId as string | undefined;
2372
- const UUID_RE =
2373
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2374
2452
  if (cardIdArg && UUID_RE.test(cardIdArg) && deps.isConfigured()) {
2375
2453
  const isAutoStartTrigger = AUTO_START_TRIGGERS.has(name);
2376
2454
  // Resolve MCP client identity per request from the in-scope Server. The
@@ -3356,13 +3434,15 @@ async function handleToolCall(
3356
3434
 
3357
3435
  // Agent context operations
3358
3436
  case "harmony_start_agent_session": {
3359
- const cardId = z.string().uuid().parse(args.cardId);
3360
- const agentIdentifier = z
3361
- .string()
3362
- .min(1)
3363
- .max(100)
3364
- .parse(args.agentIdentifier);
3365
- const agentName = z.string().min(1).max(100).parse(args.agentName);
3437
+ const cardId = requireUuidArg(args.cardId, "cardId");
3438
+ const agentIdentifier = requireStringArg(
3439
+ args.agentIdentifier,
3440
+ "agentIdentifier",
3441
+ { max: 100 },
3442
+ );
3443
+ const agentName = requireStringArg(args.agentName, "agentName", {
3444
+ max: 100,
3445
+ });
3366
3446
  const moveToColumn = args.moveToColumn as string | undefined;
3367
3447
  const addLabels = parseLabelList(args.addLabels);
3368
3448
 
@@ -3449,9 +3529,10 @@ async function handleToolCall(
3449
3529
  agentName,
3450
3530
  status: "working",
3451
3531
  currentTask: args.currentTask as string | undefined,
3452
- estimatedMinutesRemaining: args.estimatedMinutesRemaining as
3453
- | number
3454
- | undefined,
3532
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(
3533
+ args.estimatedMinutesRemaining,
3534
+ "estimatedMinutesRemaining",
3535
+ ),
3455
3536
  steerable:
3456
3537
  args.steerable === true || args.steerable === "true"
3457
3538
  ? true
@@ -3482,17 +3563,19 @@ async function handleToolCall(
3482
3563
  }
3483
3564
 
3484
3565
  case "harmony_update_agent_progress": {
3485
- const cardId = z.string().uuid().parse(args.cardId);
3486
- const agentIdentifier = z
3487
- .string()
3488
- .min(1)
3489
- .max(100)
3490
- .parse(args.agentIdentifier);
3491
- const agentName = z.string().min(1).max(100).parse(args.agentName);
3492
- const progressPercent =
3493
- args.progressPercent !== undefined
3494
- ? z.number().min(0).max(100).parse(args.progressPercent)
3495
- : undefined;
3566
+ const cardId = requireUuidArg(args.cardId, "cardId");
3567
+ const agentIdentifier = requireStringArg(
3568
+ args.agentIdentifier,
3569
+ "agentIdentifier",
3570
+ { max: 100 },
3571
+ );
3572
+ const agentName = requireStringArg(args.agentName, "agentName", {
3573
+ max: 100,
3574
+ });
3575
+ const progressPercent = optionalPercentArg(
3576
+ args.progressPercent,
3577
+ "progressPercent",
3578
+ );
3496
3579
  // Convert actions parameter to recentActions format and merge with memory actions
3497
3580
  const callerActions = args.actions as
3498
3581
  | { description: string }[]
@@ -3535,9 +3618,10 @@ async function handleToolCall(
3535
3618
  progressPercent,
3536
3619
  currentTask: args.currentTask as string | undefined,
3537
3620
  blockers: args.blockers as string[] | undefined,
3538
- estimatedMinutesRemaining: args.estimatedMinutesRemaining as
3539
- | number
3540
- | undefined,
3621
+ estimatedMinutesRemaining: optionalNonNegativeNumberArg(
3622
+ args.estimatedMinutesRemaining,
3623
+ "estimatedMinutesRemaining",
3624
+ ),
3541
3625
  ...(mergedRecentActions && { recentActions: mergedRecentActions }),
3542
3626
  ...(runActivity.length > 0 && { runActivity }),
3543
3627
  });
@@ -3547,14 +3631,14 @@ async function handleToolCall(
3547
3631
  }
3548
3632
 
3549
3633
  case "harmony_end_agent_session": {
3550
- const cardId = z.string().uuid().parse(args.cardId);
3634
+ const cardId = requireUuidArg(args.cardId, "cardId");
3551
3635
  const moveToColumn = args.moveToColumn as string | undefined;
3552
3636
  const sessionStatus =
3553
3637
  (args.status as "completed" | "paused") || "completed";
3554
- const endProgressPercent =
3555
- args.progressPercent !== undefined
3556
- ? z.number().min(0).max(100).parse(args.progressPercent)
3557
- : undefined;
3638
+ const endProgressPercent = optionalPercentArg(
3639
+ args.progressPercent,
3640
+ "progressPercent",
3641
+ );
3558
3642
 
3559
3643
  // Final flush of any pending memory actions before ending the session
3560
3644
  await flushMemoryActions(client, cardId);