@gethmy/mcp 3.6.0 → 3.8.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
@@ -31,7 +31,7 @@ function noteLegacyLocalPin(path) {
31
31
  if (warnedLegacyLocalPin)
32
32
  return;
33
33
  warnedLegacyLocalPin = true;
34
- console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Rename it to ${LOCAL_CONFIG_FILENAME} the fallback that finds it is temporary.`);
34
+ console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Run \`harmony-agent doctor --fix\` in this repo to write ` + `${LOCAL_CONFIG_FILENAME}, or rename the file yourself. ` + `The fallback that finds it is temporary.`);
35
35
  }
36
36
  function noteLocalPinRename(from, to) {
37
37
  console.error(`Harmony: wrote the repo pin to ${to} (the pre-#1082 ${from} is now ignored and can be deleted).`);
@@ -1987,6 +1987,267 @@ var REVIEW_DISALLOWED_TOOLS = [
1987
1987
  "mcp__harmony__harmony_delete_subtask",
1988
1988
  "mcp__harmony__harmony_toggle_subtask"
1989
1989
  ];
1990
+ // ../harmony-shared/dist/runEventSanitize.js
1991
+ var REPLACEMENT = "�";
1992
+ function sanitizeRunEventString(value) {
1993
+ let out = "";
1994
+ for (let i = 0;i < value.length; i++) {
1995
+ const code = value.charCodeAt(i);
1996
+ if (code === 0)
1997
+ continue;
1998
+ if (code >= 55296 && code <= 56319) {
1999
+ const next = value.charCodeAt(i + 1);
2000
+ if (next >= 56320 && next <= 57343) {
2001
+ out += value[i] + value[i + 1];
2002
+ i++;
2003
+ continue;
2004
+ }
2005
+ out += REPLACEMENT;
2006
+ continue;
2007
+ }
2008
+ if (code >= 56320 && code <= 57343) {
2009
+ out += REPLACEMENT;
2010
+ continue;
2011
+ }
2012
+ out += value[i];
2013
+ }
2014
+ return out;
2015
+ }
2016
+ function sanitizeRunEventPayload(payload) {
2017
+ return walk(payload, new Map);
2018
+ }
2019
+ function sanitizeRunEventDraft(draft) {
2020
+ return { ...draft, payload: sanitizeRunEventPayload(draft.payload) };
2021
+ }
2022
+ function walk(value, seen) {
2023
+ if (typeof value === "string")
2024
+ return sanitizeRunEventString(value);
2025
+ if (value === null || typeof value !== "object")
2026
+ return value;
2027
+ const already = seen.get(value);
2028
+ if (already !== undefined)
2029
+ return already;
2030
+ if (Array.isArray(value)) {
2031
+ const out2 = [];
2032
+ seen.set(value, out2);
2033
+ for (const entry of value)
2034
+ out2.push(walk(entry, seen));
2035
+ return out2;
2036
+ }
2037
+ const out = {};
2038
+ seen.set(value, out);
2039
+ for (const [key, entry] of Object.entries(value)) {
2040
+ out[sanitizeRunEventString(key)] = walk(entry, seen);
2041
+ }
2042
+ return out;
2043
+ }
2044
+ // ../harmony-shared/dist/runRedaction.js
2045
+ var MAX_INPUT_CHARS = 2000;
2046
+ var MAX_OUTPUT_CHARS = 4000;
2047
+ var MAX_INPUT_STRING_CHARS = 600;
2048
+ var REDACTION_MARK = "«redacted»";
2049
+ var SENSITIVE_SEGMENTS = [
2050
+ ".ssh",
2051
+ ".gnupg",
2052
+ ".aws",
2053
+ ".codex",
2054
+ ".gemini",
2055
+ ".docker",
2056
+ ".kube",
2057
+ ".hmy",
2058
+ ".harmony-mcp",
2059
+ ".password-store",
2060
+ ".claude",
2061
+ "gh",
2062
+ "gcloud",
2063
+ "op",
2064
+ "anthropic"
2065
+ ];
2066
+ var CONFIG_SCOPED_SEGMENTS = new Set([
2067
+ "gh",
2068
+ "gcloud",
2069
+ "op",
2070
+ "anthropic"
2071
+ ]);
2072
+ var SENSITIVE_BASENAMES = new Set([
2073
+ ".netrc",
2074
+ "_netrc",
2075
+ ".npmrc",
2076
+ ".pgpass",
2077
+ ".git-credentials",
2078
+ ".htpasswd",
2079
+ ".claude.json",
2080
+ "credentials",
2081
+ ".credentials",
2082
+ "credentials.json",
2083
+ ".credentials.json",
2084
+ "credentials.yml",
2085
+ "credentials.yaml",
2086
+ "auth.json",
2087
+ ".auth.json",
2088
+ "secrets",
2089
+ "secrets.json",
2090
+ "secrets.yaml",
2091
+ "secrets.yml",
2092
+ "id_rsa",
2093
+ "id_dsa",
2094
+ "id_ecdsa",
2095
+ "id_ed25519",
2096
+ "known_hosts"
2097
+ ]);
2098
+ var SENSITIVE_EXTENSIONS = [
2099
+ ".pem",
2100
+ ".key",
2101
+ ".p12",
2102
+ ".pfx",
2103
+ ".keystore",
2104
+ ".jks",
2105
+ ".asc",
2106
+ ".gpg"
2107
+ ];
2108
+ function isSensitivePath(rawPath) {
2109
+ if (typeof rawPath !== "string" || rawPath.length === 0)
2110
+ return false;
2111
+ const path = rawPath.trim().toLowerCase();
2112
+ const segments = path.split(/[\\/]+/).filter((s) => s.length > 0);
2113
+ if (segments.length === 0)
2114
+ return false;
2115
+ for (let i = 0;i < segments.length; i++) {
2116
+ const segment = segments[i];
2117
+ if (!SENSITIVE_SEGMENTS.includes(segment))
2118
+ continue;
2119
+ if (CONFIG_SCOPED_SEGMENTS.has(segment)) {
2120
+ if (i > 0 && segments[i - 1] === ".config")
2121
+ return true;
2122
+ continue;
2123
+ }
2124
+ return true;
2125
+ }
2126
+ const basename = segments[segments.length - 1];
2127
+ if (SENSITIVE_BASENAMES.has(basename))
2128
+ return true;
2129
+ if (basename === ".env" || basename.startsWith(".env."))
2130
+ return true;
2131
+ if (basename.endsWith(".env"))
2132
+ return true;
2133
+ if (SENSITIVE_EXTENSIONS.some((ext) => basename.endsWith(ext)))
2134
+ return true;
2135
+ if (/service[-_]?account.*\.json$/.test(basename))
2136
+ return true;
2137
+ return false;
2138
+ }
2139
+ function sensitivePathsIn(input, depth = 0) {
2140
+ if (depth > 6)
2141
+ return [];
2142
+ if (typeof input === "string") {
2143
+ return isSensitivePath(input) ? [input] : [];
2144
+ }
2145
+ if (Array.isArray(input)) {
2146
+ return input.flatMap((item) => sensitivePathsIn(item, depth + 1));
2147
+ }
2148
+ if (input !== null && typeof input === "object") {
2149
+ return Object.values(input).flatMap((value) => sensitivePathsIn(value, depth + 1));
2150
+ }
2151
+ return [];
2152
+ }
2153
+ var SECRET_PATTERNS = [
2154
+ {
2155
+ pattern: /-----BEGIN[^-]*PRIVATE KEY-----[\s\S]*?-----END[^-]*-----/g,
2156
+ replace: REDACTION_MARK
2157
+ },
2158
+ { pattern: /\bhmy_at_[A-Za-z0-9_-]{8,}/g, replace: REDACTION_MARK },
2159
+ { pattern: /\bhmy_[A-Za-z0-9_-]{8,}/g, replace: REDACTION_MARK },
2160
+ { pattern: /\bsk-(?:ant-)?[A-Za-z0-9_-]{16,}/g, replace: REDACTION_MARK },
2161
+ { pattern: /\bgh[pousr]_[A-Za-z0-9]{16,}/g, replace: REDACTION_MARK },
2162
+ { pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}/g, replace: REDACTION_MARK },
2163
+ { pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}/g, replace: REDACTION_MARK },
2164
+ { pattern: /\bAKIA[0-9A-Z]{16}\b/g, replace: REDACTION_MARK },
2165
+ { pattern: /\bAIza[0-9A-Za-z_-]{20,}/g, replace: REDACTION_MARK },
2166
+ {
2167
+ pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
2168
+ replace: REDACTION_MARK
2169
+ },
2170
+ {
2171
+ pattern: /\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
2172
+ replace: `$1 ${REDACTION_MARK}`
2173
+ },
2174
+ {
2175
+ pattern: /(\w{1,32}:\/\/)[^/\s:@]+:[^/\s@]+@/g,
2176
+ replace: `$1${REDACTION_MARK}@`
2177
+ },
2178
+ {
2179
+ pattern: /\b([A-Za-z0-9_]{0,40}(?:TOKEN|SECRET|PASSWORD|PASSWD|APIKEY|API_KEY|ACCESS_KEY|PRIVATE_KEY|CREDENTIAL|AUTH)[A-Za-z0-9_]{0,40})\s*[=:]\s*(?:"[^"]*"|'[^']*'|`[^`]*`|[^\s,;)}\]]+)/gi,
2180
+ replace: `$1=${REDACTION_MARK}`
2181
+ },
2182
+ {
2183
+ pattern: /(--?(?:password|passwd|token|api-?key|secret|auth)(?:=|\s+))(?:"[^"]*"|'[^']*'|[^\s]+)/gi,
2184
+ replace: `$1${REDACTION_MARK}`
2185
+ }
2186
+ ];
2187
+ function redactSecrets(text) {
2188
+ if (typeof text !== "string" || text.length === 0)
2189
+ return text;
2190
+ let out = text;
2191
+ for (const { pattern, replace } of SECRET_PATTERNS) {
2192
+ pattern.lastIndex = 0;
2193
+ out = out.replace(pattern, replace);
2194
+ }
2195
+ return out;
2196
+ }
2197
+ function truncate(text, max, originalLength) {
2198
+ const total = originalLength ?? text.length;
2199
+ if (total <= max)
2200
+ return text;
2201
+ return `${text.slice(0, max)}… [+${total - max} chars]`;
2202
+ }
2203
+ function redactThenTruncate(text, max) {
2204
+ const preCap = max * 4 + 64;
2205
+ const scanned = text.length > preCap ? text.slice(0, preCap) : text;
2206
+ return truncate(redactSecrets(scanned), max, text.length);
2207
+ }
2208
+ function redactStructure(value, depth = 0) {
2209
+ if (depth > 6)
2210
+ return REDACTION_MARK;
2211
+ if (typeof value === "string") {
2212
+ return redactThenTruncate(value, MAX_INPUT_STRING_CHARS);
2213
+ }
2214
+ if (Array.isArray(value)) {
2215
+ return value.slice(0, 20).map((item) => redactStructure(item, depth + 1));
2216
+ }
2217
+ if (value !== null && typeof value === "object") {
2218
+ const out = {};
2219
+ for (const [key, item] of Object.entries(value)) {
2220
+ out[key] = redactStructure(item, depth + 1);
2221
+ }
2222
+ return out;
2223
+ }
2224
+ return value;
2225
+ }
2226
+ function redactToolCall(args) {
2227
+ const sensitive = sensitivePathsIn(args.input);
2228
+ if (sensitive.length > 0) {
2229
+ return { withheld: "sensitive-path" };
2230
+ }
2231
+ const result = {};
2232
+ if (args.input !== undefined) {
2233
+ let input = redactStructure(args.input);
2234
+ let serialized;
2235
+ try {
2236
+ serialized = JSON.stringify(input) ?? "";
2237
+ } catch {
2238
+ serialized = "";
2239
+ input = REDACTION_MARK;
2240
+ }
2241
+ if (serialized.length > MAX_INPUT_CHARS) {
2242
+ input = truncate(serialized, MAX_INPUT_CHARS);
2243
+ }
2244
+ result.input = input;
2245
+ }
2246
+ if (typeof args.output === "string" && args.output.length > 0) {
2247
+ result.output = redactThenTruncate(args.output, MAX_OUTPUT_CHARS);
2248
+ }
2249
+ return result;
2250
+ }
1990
2251
  // ../harmony-shared/dist/stageHandoff.js
1991
2252
  var HANDOFF_MARKER = "harmony:stage-handoff";
1992
2253
  var HANDOFF_BLOCK_RE = new RegExp("```json\\s*\\n//\\s*" + HANDOFF_MARKER + "\\s*\\n([\\s\\S]*?)\\n```", "m");
@@ -2300,6 +2561,15 @@ class HarmonyApiClient {
2300
2561
  async registerWorkspaceAgent(workspaceId, data) {
2301
2562
  return this.request("POST", `/workspaces/${workspaceId}/agents`, data);
2302
2563
  }
2564
+ async reportAgentConfig(workspaceId, agentId, config) {
2565
+ return this.request("POST", `/workspaces/${workspaceId}/agents/${agentId}/reported-config`, { config });
2566
+ }
2567
+ async getWorkspaceModelConfig(workspaceId) {
2568
+ return this.request("GET", `/workspaces/${workspaceId}/model-config`);
2569
+ }
2570
+ async getModelCatalog() {
2571
+ return this.request("GET", "/model-catalog");
2572
+ }
2303
2573
  async listProjects(workspaceId) {
2304
2574
  return this.request("GET", `/workspaces/${workspaceId}/projects`);
2305
2575
  }
@@ -2448,6 +2718,9 @@ class HarmonyApiClient {
2448
2718
  title
2449
2719
  });
2450
2720
  }
2721
+ async removeExternalLink(cardId, linkId) {
2722
+ return this.request("DELETE", `/cards/${cardId}/external-links/${linkId}`);
2723
+ }
2451
2724
  async uploadArtifact(data) {
2452
2725
  return this.request("POST", "/artifacts", data);
2453
2726
  }
@@ -2540,7 +2813,10 @@ class HarmonyApiClient {
2540
2813
  return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
2541
2814
  }
2542
2815
  async appendAgentRunEvents(cardId, data) {
2543
- return this.request("POST", `/cards/${cardId}/agent-run-events`, data);
2816
+ return this.request("POST", `/cards/${cardId}/agent-run-events`, {
2817
+ ...data,
2818
+ events: data.events.map((event) => sanitizeRunEventDraft(event))
2819
+ });
2544
2820
  }
2545
2821
  async getPendingUserMessages(cardId, sessionId, sinceSeq) {
2546
2822
  return this.request("GET", `/cards/${cardId}/agent-messages?sessionId=${sessionId}&sinceSeq=${sinceSeq}`);
@@ -5230,6 +5506,23 @@ var TOOLS = {
5230
5506
  required: ["cardId", "url"]
5231
5507
  }
5232
5508
  },
5509
+ harmony_remove_external_link: {
5510
+ description: "Remove an external reference URL from a card — the counterpart to harmony_add_external_link. Takes the link id from harmony_get_card_external_links, not the URL.",
5511
+ inputSchema: {
5512
+ type: "object",
5513
+ properties: {
5514
+ cardId: {
5515
+ type: "string",
5516
+ description: "Card UUID"
5517
+ },
5518
+ linkId: {
5519
+ type: "string",
5520
+ description: "External link UUID, as returned by harmony_get_card_external_links"
5521
+ }
5522
+ },
5523
+ required: ["cardId", "linkId"]
5524
+ }
5525
+ },
5233
5526
  harmony_create_subtask: {
5234
5527
  description: "Create a subtask on a card",
5235
5528
  inputSchema: {
@@ -7124,6 +7417,11 @@ ${list}
7124
7417
  const result = await client3.addExternalLink(cardId, url, title);
7125
7418
  return { success: true, ...result };
7126
7419
  }
7420
+ case "harmony_remove_external_link": {
7421
+ const cardId = z.string().uuid().parse(args.cardId);
7422
+ const linkId = z.string().uuid().parse(args.linkId);
7423
+ return await client3.removeExternalLink(cardId, linkId);
7424
+ }
7127
7425
  case "harmony_classify_card":
7128
7426
  return deprecatedRemovedToolResult("harmony_classify_card");
7129
7427
  case "harmony_create_subtask": {
@@ -32,7 +32,7 @@ function noteLegacyLocalPin(path) {
32
32
  if (warnedLegacyLocalPin)
33
33
  return;
34
34
  warnedLegacyLocalPin = true;
35
- console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Rename it to ${LOCAL_CONFIG_FILENAME} the fallback that finds it is temporary.`);
35
+ console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Run \`harmony-agent doctor --fix\` in this repo to write ` + `${LOCAL_CONFIG_FILENAME}, or rename the file yourself. ` + `The fallback that finds it is temporary.`);
36
36
  }
37
37
  function noteLocalPinRename(from, to) {
38
38
  console.error(`Harmony: wrote the repo pin to ${to} (the pre-#1082 ${from} is now ignored and can be deleted).`);
@@ -1014,6 +1014,128 @@ var REVIEW_DISALLOWED_TOOLS = [
1014
1014
  "mcp__harmony__harmony_delete_subtask",
1015
1015
  "mcp__harmony__harmony_toggle_subtask"
1016
1016
  ];
1017
+ // ../harmony-shared/dist/runEventSanitize.js
1018
+ var REPLACEMENT = "�";
1019
+ function sanitizeRunEventString(value) {
1020
+ let out = "";
1021
+ for (let i = 0;i < value.length; i++) {
1022
+ const code = value.charCodeAt(i);
1023
+ if (code === 0)
1024
+ continue;
1025
+ if (code >= 55296 && code <= 56319) {
1026
+ const next = value.charCodeAt(i + 1);
1027
+ if (next >= 56320 && next <= 57343) {
1028
+ out += value[i] + value[i + 1];
1029
+ i++;
1030
+ continue;
1031
+ }
1032
+ out += REPLACEMENT;
1033
+ continue;
1034
+ }
1035
+ if (code >= 56320 && code <= 57343) {
1036
+ out += REPLACEMENT;
1037
+ continue;
1038
+ }
1039
+ out += value[i];
1040
+ }
1041
+ return out;
1042
+ }
1043
+ function sanitizeRunEventPayload(payload) {
1044
+ return walk(payload, new Map);
1045
+ }
1046
+ function sanitizeRunEventDraft(draft) {
1047
+ return { ...draft, payload: sanitizeRunEventPayload(draft.payload) };
1048
+ }
1049
+ function walk(value, seen) {
1050
+ if (typeof value === "string")
1051
+ return sanitizeRunEventString(value);
1052
+ if (value === null || typeof value !== "object")
1053
+ return value;
1054
+ const already = seen.get(value);
1055
+ if (already !== undefined)
1056
+ return already;
1057
+ if (Array.isArray(value)) {
1058
+ const out2 = [];
1059
+ seen.set(value, out2);
1060
+ for (const entry of value)
1061
+ out2.push(walk(entry, seen));
1062
+ return out2;
1063
+ }
1064
+ const out = {};
1065
+ seen.set(value, out);
1066
+ for (const [key, entry] of Object.entries(value)) {
1067
+ out[sanitizeRunEventString(key)] = walk(entry, seen);
1068
+ }
1069
+ return out;
1070
+ }
1071
+ // ../harmony-shared/dist/runRedaction.js
1072
+ var REDACTION_MARK = "«redacted»";
1073
+ var CONFIG_SCOPED_SEGMENTS = new Set([
1074
+ "gh",
1075
+ "gcloud",
1076
+ "op",
1077
+ "anthropic"
1078
+ ]);
1079
+ var SENSITIVE_BASENAMES = new Set([
1080
+ ".netrc",
1081
+ "_netrc",
1082
+ ".npmrc",
1083
+ ".pgpass",
1084
+ ".git-credentials",
1085
+ ".htpasswd",
1086
+ ".claude.json",
1087
+ "credentials",
1088
+ ".credentials",
1089
+ "credentials.json",
1090
+ ".credentials.json",
1091
+ "credentials.yml",
1092
+ "credentials.yaml",
1093
+ "auth.json",
1094
+ ".auth.json",
1095
+ "secrets",
1096
+ "secrets.json",
1097
+ "secrets.yaml",
1098
+ "secrets.yml",
1099
+ "id_rsa",
1100
+ "id_dsa",
1101
+ "id_ecdsa",
1102
+ "id_ed25519",
1103
+ "known_hosts"
1104
+ ]);
1105
+ var SECRET_PATTERNS = [
1106
+ {
1107
+ pattern: /-----BEGIN[^-]*PRIVATE KEY-----[\s\S]*?-----END[^-]*-----/g,
1108
+ replace: REDACTION_MARK
1109
+ },
1110
+ { pattern: /\bhmy_at_[A-Za-z0-9_-]{8,}/g, replace: REDACTION_MARK },
1111
+ { pattern: /\bhmy_[A-Za-z0-9_-]{8,}/g, replace: REDACTION_MARK },
1112
+ { pattern: /\bsk-(?:ant-)?[A-Za-z0-9_-]{16,}/g, replace: REDACTION_MARK },
1113
+ { pattern: /\bgh[pousr]_[A-Za-z0-9]{16,}/g, replace: REDACTION_MARK },
1114
+ { pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}/g, replace: REDACTION_MARK },
1115
+ { pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}/g, replace: REDACTION_MARK },
1116
+ { pattern: /\bAKIA[0-9A-Z]{16}\b/g, replace: REDACTION_MARK },
1117
+ { pattern: /\bAIza[0-9A-Za-z_-]{20,}/g, replace: REDACTION_MARK },
1118
+ {
1119
+ pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
1120
+ replace: REDACTION_MARK
1121
+ },
1122
+ {
1123
+ pattern: /\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
1124
+ replace: `$1 ${REDACTION_MARK}`
1125
+ },
1126
+ {
1127
+ pattern: /(\w{1,32}:\/\/)[^/\s:@]+:[^/\s@]+@/g,
1128
+ replace: `$1${REDACTION_MARK}@`
1129
+ },
1130
+ {
1131
+ pattern: /\b([A-Za-z0-9_]{0,40}(?:TOKEN|SECRET|PASSWORD|PASSWD|APIKEY|API_KEY|ACCESS_KEY|PRIVATE_KEY|CREDENTIAL|AUTH)[A-Za-z0-9_]{0,40})\s*[=:]\s*(?:"[^"]*"|'[^']*'|`[^`]*`|[^\s,;)}\]]+)/gi,
1132
+ replace: `$1=${REDACTION_MARK}`
1133
+ },
1134
+ {
1135
+ pattern: /(--?(?:password|passwd|token|api-?key|secret|auth)(?:=|\s+))(?:"[^"]*"|'[^']*'|[^\s]+)/gi,
1136
+ replace: `$1${REDACTION_MARK}`
1137
+ }
1138
+ ];
1017
1139
  // ../harmony-shared/dist/stageHandoff.js
1018
1140
  var HANDOFF_MARKER = "harmony:stage-handoff";
1019
1141
  var HANDOFF_BLOCK_RE = new RegExp("```json\\s*\\n//\\s*" + HANDOFF_MARKER + "\\s*\\n([\\s\\S]*?)\\n```", "m");
@@ -1327,6 +1449,15 @@ class HarmonyApiClient {
1327
1449
  async registerWorkspaceAgent(workspaceId, data) {
1328
1450
  return this.request("POST", `/workspaces/${workspaceId}/agents`, data);
1329
1451
  }
1452
+ async reportAgentConfig(workspaceId, agentId, config) {
1453
+ return this.request("POST", `/workspaces/${workspaceId}/agents/${agentId}/reported-config`, { config });
1454
+ }
1455
+ async getWorkspaceModelConfig(workspaceId) {
1456
+ return this.request("GET", `/workspaces/${workspaceId}/model-config`);
1457
+ }
1458
+ async getModelCatalog() {
1459
+ return this.request("GET", "/model-catalog");
1460
+ }
1330
1461
  async listProjects(workspaceId) {
1331
1462
  return this.request("GET", `/workspaces/${workspaceId}/projects`);
1332
1463
  }
@@ -1475,6 +1606,9 @@ class HarmonyApiClient {
1475
1606
  title
1476
1607
  });
1477
1608
  }
1609
+ async removeExternalLink(cardId, linkId) {
1610
+ return this.request("DELETE", `/cards/${cardId}/external-links/${linkId}`);
1611
+ }
1478
1612
  async uploadArtifact(data) {
1479
1613
  return this.request("POST", "/artifacts", data);
1480
1614
  }
@@ -1567,7 +1701,10 @@ class HarmonyApiClient {
1567
1701
  return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
1568
1702
  }
1569
1703
  async appendAgentRunEvents(cardId, data) {
1570
- return this.request("POST", `/cards/${cardId}/agent-run-events`, data);
1704
+ return this.request("POST", `/cards/${cardId}/agent-run-events`, {
1705
+ ...data,
1706
+ events: data.events.map((event) => sanitizeRunEventDraft(event))
1707
+ });
1571
1708
  }
1572
1709
  async getPendingUserMessages(cardId, sessionId, sinceSeq) {
1573
1710
  return this.request("GET", `/cards/${cardId}/agent-messages?sessionId=${sessionId}&sinceSeq=${sinceSeq}`);
@@ -32,7 +32,7 @@ function noteLegacyLocalPin(path) {
32
32
  if (warnedLegacyLocalPin)
33
33
  return;
34
34
  warnedLegacyLocalPin = true;
35
- console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Rename it to ${LOCAL_CONFIG_FILENAME} the fallback that finds it is temporary.`);
35
+ console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Run \`harmony-agent doctor --fix\` in this repo to write ` + `${LOCAL_CONFIG_FILENAME}, or rename the file yourself. ` + `The fallback that finds it is temporary.`);
36
36
  }
37
37
  function noteLocalPinRename(from, to) {
38
38
  console.error(`Harmony: wrote the repo pin to ${to} (the pre-#1082 ${from} is now ignored and can be deleted).`);
@@ -32,7 +32,7 @@ function noteLegacyLocalPin(path) {
32
32
  if (warnedLegacyLocalPin)
33
33
  return;
34
34
  warnedLegacyLocalPin = true;
35
- console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Rename it to ${LOCAL_CONFIG_FILENAME} the fallback that finds it is temporary.`);
35
+ console.error(`Harmony: this repo is pinned by ${path}, the pre-#1082 name. ` + `Run \`harmony-agent doctor --fix\` in this repo to write ` + `${LOCAL_CONFIG_FILENAME}, or rename the file yourself. ` + `The fallback that finds it is temporary.`);
36
36
  }
37
37
  function noteLocalPinRename(from, to) {
38
38
  console.error(`Harmony: wrote the repo pin to ${to} (the pre-#1082 ${from} is now ignored and can be deleted).`);