@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 +60 -13
- package/dist/index.js +60 -13
- package/dist/lib/api-client.js +6 -1
- package/package.json +1 -1
- package/src/api-client.ts +19 -1
- package/src/server.ts +115 -31
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 =
|
|
6046
|
-
const agentIdentifier =
|
|
6047
|
-
const 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 =
|
|
6129
|
-
const agentIdentifier =
|
|
6130
|
-
const agentName =
|
|
6131
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
6041
|
-
const agentIdentifier =
|
|
6042
|
-
const 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 =
|
|
6124
|
-
const agentIdentifier =
|
|
6125
|
-
const agentName =
|
|
6126
|
-
|
|
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 =
|
|
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
|
|
6208
|
+
const endProgressPercent = optionalPercentArg(args.progressPercent, "progressPercent");
|
|
6162
6209
|
await flushMemoryActions(client3, cardId);
|
|
6163
6210
|
cleanupMemorySession(cardId);
|
|
6164
6211
|
let result = { session: null };
|
package/dist/lib/api-client.js
CHANGED
|
@@ -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
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?: {
|
|
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 =
|
|
3360
|
-
const agentIdentifier =
|
|
3361
|
-
.
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
const 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:
|
|
3453
|
-
|
|
3454
|
-
|
|
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 =
|
|
3486
|
-
const agentIdentifier =
|
|
3487
|
-
.
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
const agentName =
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
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:
|
|
3539
|
-
|
|
3540
|
-
|
|
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 =
|
|
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
|
|
3556
|
-
|
|
3557
|
-
|
|
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);
|