@gethmy/mcp 2.13.4 → 2.14.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/README.md +5 -2
- package/dist/cli.js +271 -272
- package/dist/index.js +271 -272
- package/dist/lib/api-client.js +143 -140
- package/dist/lib/oauth-refresh.js +350 -0
- package/package.json +6 -2
- package/src/api-client.ts +13 -12
- package/src/server.ts +387 -320
- package/src/skills.ts +2 -2
package/src/server.ts
CHANGED
|
@@ -121,6 +121,65 @@ function requireExactlyOneScope(scope: {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Backward-compat dispatch shim (#605). Consolidated tools dropped the old
|
|
126
|
+
* names from the advertised TOOLS list (the token win), but `handleToolCall`
|
|
127
|
+
* still dispatches them so external integrations calling the old names keep
|
|
128
|
+
* working through a deprecation window. Logged once per name to stderr (never
|
|
129
|
+
* stdout — that carries the MCP protocol on stdio).
|
|
130
|
+
*/
|
|
131
|
+
const DEPRECATED_TOOL_ALIASES: Record<string, string> = {
|
|
132
|
+
harmony_get_card_by_short_id: "harmony_get_card",
|
|
133
|
+
harmony_bulk_get_cards: "harmony_get_card",
|
|
134
|
+
harmony_upload_artifact: "harmony_upload",
|
|
135
|
+
harmony_upload_card_attachment: "harmony_upload",
|
|
136
|
+
harmony_request_artifact_upload_url: "harmony_request_upload_url",
|
|
137
|
+
harmony_request_card_attachment_upload_url: "harmony_request_upload_url",
|
|
138
|
+
harmony_finalize_artifact: "harmony_finalize_upload",
|
|
139
|
+
harmony_finalize_card_attachment: "harmony_finalize_upload",
|
|
140
|
+
};
|
|
141
|
+
const _warnedDeprecatedTools = new Set<string>();
|
|
142
|
+
function warnDeprecatedTool(oldName: string): void {
|
|
143
|
+
if (_warnedDeprecatedTools.has(oldName)) return;
|
|
144
|
+
_warnedDeprecatedTools.add(oldName);
|
|
145
|
+
const replacement = DEPRECATED_TOOL_ALIASES[oldName];
|
|
146
|
+
console.error(
|
|
147
|
+
`[harmony-mcp] Tool "${oldName}" is deprecated (still accepted) — ` +
|
|
148
|
+
`use "${replacement}" instead. The old name is no longer advertised and ` +
|
|
149
|
+
`will be removed in a future major version.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Removed-capability shim (#612). The legacy steps_version=1 "Automation" macro
|
|
155
|
+
* was retired when Harmony committed to the single stage-Playbook model. These
|
|
156
|
+
* tools have no v2 replacement, so — following the #605 hidden-dispatch pattern —
|
|
157
|
+
* they are dropped from the advertised TOOLS list but their switch cases remain,
|
|
158
|
+
* returning a one-time deprecation notice (warned once to stderr) instead of an
|
|
159
|
+
* unknown-tool error, so an integration calling the old name fails legibly.
|
|
160
|
+
*/
|
|
161
|
+
const DEPRECATED_REMOVED_TOOLS: Record<string, string> = {
|
|
162
|
+
harmony_run_playbook:
|
|
163
|
+
"Playbooks are stage models run by the board and agent daemon as a card moves through their stages — there is no server-side macro to trigger. This tool no longer runs anything.",
|
|
164
|
+
harmony_save_card_as_playbook:
|
|
165
|
+
"The steps_version=1 automation macro was removed. Build a stage playbook with harmony_create_playbook instead.",
|
|
166
|
+
};
|
|
167
|
+
const _warnedRemovedTools = new Set<string>();
|
|
168
|
+
function deprecatedRemovedToolResult(name: string): {
|
|
169
|
+
success: false;
|
|
170
|
+
deprecated: true;
|
|
171
|
+
message: string;
|
|
172
|
+
} {
|
|
173
|
+
const message = DEPRECATED_REMOVED_TOOLS[name];
|
|
174
|
+
if (!_warnedRemovedTools.has(name)) {
|
|
175
|
+
_warnedRemovedTools.add(name);
|
|
176
|
+
console.error(
|
|
177
|
+
`[harmony-mcp] Tool "${name}" is deprecated and no longer functional — ${message}`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return { success: false, deprecated: true, message };
|
|
181
|
+
}
|
|
182
|
+
|
|
124
183
|
/** PUT raw bytes straight to a Supabase signed upload URL (the token rides in
|
|
125
184
|
* the URL query). Used by the stdio internal handshake; the hosted server can't
|
|
126
185
|
* read local disk, so there the agent drives this PUT itself via the two-step
|
|
@@ -625,49 +684,37 @@ export const TOOLS = {
|
|
|
625
684
|
},
|
|
626
685
|
},
|
|
627
686
|
harmony_get_card: {
|
|
628
|
-
description:
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
required: ["cardId"],
|
|
635
|
-
},
|
|
636
|
-
},
|
|
637
|
-
harmony_get_card_by_short_id: {
|
|
638
|
-
description: "Get a card by its short ID (e.g., #42) within a project",
|
|
687
|
+
description:
|
|
688
|
+
"Fetch one or many cards. Provide exactly one of: `cardId` (UUID, full detail), " +
|
|
689
|
+
"`shortId` (e.g. 42, full detail), or `shortIds` (array, max 100 — compact " +
|
|
690
|
+
"summaries: id, shortId, title, column, priority, assignee, labels, archived, " +
|
|
691
|
+
"plus any short ids not found). `shortId`/`shortIds` need project context. " +
|
|
692
|
+
"Prefer one `shortIds` call over repeated single fetches for multiple cards.",
|
|
639
693
|
inputSchema: {
|
|
640
694
|
type: "object",
|
|
641
695
|
properties: {
|
|
642
|
-
|
|
696
|
+
cardId: {
|
|
643
697
|
type: "string",
|
|
644
|
-
description: "
|
|
698
|
+
description: "Card UUID — full detail for one card.",
|
|
645
699
|
},
|
|
646
700
|
shortId: {
|
|
647
701
|
type: "number",
|
|
648
|
-
description:
|
|
702
|
+
description:
|
|
703
|
+
"Short ID (e.g. 42 for card #42) — full detail, project-scoped.",
|
|
649
704
|
},
|
|
650
|
-
},
|
|
651
|
-
required: ["shortId"],
|
|
652
|
-
},
|
|
653
|
-
},
|
|
654
|
-
harmony_bulk_get_cards: {
|
|
655
|
-
description:
|
|
656
|
-
"Fetch multiple cards by short id in one call. Returns compact summaries " +
|
|
657
|
-
"(id, shortId, title, column, priority, assignee, labels, archived) plus " +
|
|
658
|
-
"any short ids not found. Requires project context. Prefer this over " +
|
|
659
|
-
"repeated harmony_get_card_by_short_id when referencing multiple cards.",
|
|
660
|
-
inputSchema: {
|
|
661
|
-
type: "object",
|
|
662
|
-
properties: {
|
|
663
705
|
shortIds: {
|
|
664
706
|
type: "array",
|
|
665
707
|
items: { type: "number" },
|
|
666
708
|
description:
|
|
667
|
-
"
|
|
709
|
+
"Short IDs, e.g. [400, 401, 402] — compact summaries in one call. Max 100.",
|
|
710
|
+
},
|
|
711
|
+
projectId: {
|
|
712
|
+
type: "string",
|
|
713
|
+
description:
|
|
714
|
+
"Project ID for shortId/shortIds (optional if context set).",
|
|
668
715
|
},
|
|
669
716
|
},
|
|
670
|
-
required: [
|
|
717
|
+
required: [],
|
|
671
718
|
},
|
|
672
719
|
},
|
|
673
720
|
harmony_bulk_archive_cards: {
|
|
@@ -837,186 +884,162 @@ export const TOOLS = {
|
|
|
837
884
|
required: ["cardId"],
|
|
838
885
|
},
|
|
839
886
|
},
|
|
840
|
-
|
|
887
|
+
harmony_upload: {
|
|
841
888
|
description:
|
|
842
|
-
|
|
889
|
+
'Upload a file in one call. `target: "card_attachment"` attaches a file to a card ' +
|
|
890
|
+
"(max 5MB; PNG/JPEG/GIF/WebP/HEIC/HEIF/PDF/DOC(X)/XLS(X)/TXT/CSV) — requires cardId. " +
|
|
891
|
+
'`target: "artifact"` hosts a self-contained HTML doc (text/html, max 2MB) linked to ' +
|
|
892
|
+
"exactly one of cardId/planId/workspaceId, rendered in-app in a sandboxed iframe. Provide " +
|
|
893
|
+
"the bytes as `filePath` (local, direct-to-storage) or `base64Data` (small-file fallback). " +
|
|
894
|
+
"Returns the attachment/artifact + a signed URL (artifact: use harmony_share_artifact for a " +
|
|
895
|
+
"public link). Large files on the hosted MCP server: use harmony_request_upload_url + " +
|
|
896
|
+
"harmony_finalize_upload instead.",
|
|
843
897
|
inputSchema: {
|
|
844
898
|
type: "object",
|
|
845
899
|
properties: {
|
|
846
|
-
|
|
900
|
+
target: {
|
|
901
|
+
type: "string",
|
|
902
|
+
enum: ["card_attachment", "artifact"],
|
|
903
|
+
description:
|
|
904
|
+
"What to upload: a card file attachment or a hosted HTML artifact.",
|
|
905
|
+
},
|
|
906
|
+
cardId: {
|
|
907
|
+
type: "string",
|
|
908
|
+
description:
|
|
909
|
+
"Card UUID. Required for card_attachment; one of cardId/planId/workspaceId for artifact.",
|
|
910
|
+
},
|
|
911
|
+
planId: {
|
|
912
|
+
type: "string",
|
|
913
|
+
description:
|
|
914
|
+
"Plan UUID (artifact only — link the artifact to a plan).",
|
|
915
|
+
},
|
|
916
|
+
workspaceId: {
|
|
917
|
+
type: "string",
|
|
918
|
+
description: "Workspace UUID (artifact only — standalone artifact).",
|
|
919
|
+
},
|
|
847
920
|
filePath: {
|
|
848
921
|
type: "string",
|
|
849
922
|
description:
|
|
850
|
-
"Absolute path to a local file the
|
|
923
|
+
"Absolute path to a local file the server can read. Mutually exclusive with base64Data.",
|
|
851
924
|
},
|
|
852
925
|
base64Data: {
|
|
853
926
|
type: "string",
|
|
854
927
|
description:
|
|
855
|
-
"Base64-encoded
|
|
928
|
+
"Base64-encoded bytes (a `data:` URL prefix is accepted and stripped). Mutually exclusive with filePath.",
|
|
856
929
|
},
|
|
857
930
|
fileName: {
|
|
858
931
|
type: "string",
|
|
859
932
|
description:
|
|
860
|
-
"File name including extension (
|
|
933
|
+
"File name including extension (card_attachment: required with base64Data, else defaults to the filePath basename).",
|
|
934
|
+
},
|
|
935
|
+
title: {
|
|
936
|
+
type: "string",
|
|
937
|
+
description:
|
|
938
|
+
"Artifact display title (defaults to the file basename).",
|
|
861
939
|
},
|
|
862
940
|
contentType: {
|
|
863
941
|
type: "string",
|
|
864
942
|
description:
|
|
865
|
-
"Optional MIME type (
|
|
943
|
+
"Optional MIME type (card_attachment; inferred from the extension when omitted).",
|
|
866
944
|
},
|
|
867
945
|
},
|
|
868
|
-
required: ["
|
|
946
|
+
required: ["target"],
|
|
869
947
|
},
|
|
870
948
|
},
|
|
871
|
-
|
|
949
|
+
harmony_request_upload_url: {
|
|
872
950
|
description:
|
|
873
|
-
"Step 1 of the large-file / hosted-MCP upload handshake (use when the server can't read
|
|
951
|
+
"Step 1 of the large-file / hosted-MCP upload handshake (use when the server can't read " +
|
|
952
|
+
'local disk). Mints a one-shot signed Storage upload URL. `target: "card_attachment"` ' +
|
|
953
|
+
'(requires cardId, fileName, size; max 5MB) or `target: "artifact"` (one of ' +
|
|
954
|
+
"cardId/planId/workspaceId; text/html, max 2MB). Returns { uploadUrl, token, storagePath, " +
|
|
955
|
+
"[fileType] }. Then PUT the raw bytes to uploadUrl (no bytes through the model context) and " +
|
|
956
|
+
"call harmony_finalize_upload with the storagePath.",
|
|
874
957
|
inputSchema: {
|
|
875
958
|
type: "object",
|
|
876
959
|
properties: {
|
|
877
|
-
|
|
878
|
-
fileName: {
|
|
960
|
+
target: {
|
|
879
961
|
type: "string",
|
|
880
|
-
|
|
962
|
+
enum: ["card_attachment", "artifact"],
|
|
963
|
+
description:
|
|
964
|
+
"What to upload: a card file attachment or a hosted HTML artifact.",
|
|
881
965
|
},
|
|
882
|
-
|
|
966
|
+
cardId: {
|
|
883
967
|
type: "string",
|
|
884
968
|
description:
|
|
885
|
-
"
|
|
969
|
+
"Card UUID. Required for card_attachment; one of cardId/planId/workspaceId for artifact.",
|
|
886
970
|
},
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
description: "File size in bytes (rejected early if over 5MB).",
|
|
890
|
-
},
|
|
891
|
-
},
|
|
892
|
-
required: ["cardId", "fileName", "size"],
|
|
893
|
-
},
|
|
894
|
-
},
|
|
895
|
-
harmony_finalize_card_attachment: {
|
|
896
|
-
description:
|
|
897
|
-
"Step 2 of the upload handshake. After PUTting the bytes to the signed uploadUrl, call this with storagePath to validate and register the attachment. The server re-downloads the object and enforces size + an allowlisted content-type (magic-byte sniff, never the declared type) + an optional sha256 integrity check, deleting and failing on any mismatch. Returns the attachment + a signed URL.",
|
|
898
|
-
inputSchema: {
|
|
899
|
-
type: "object",
|
|
900
|
-
properties: {
|
|
901
|
-
cardId: { type: "string", description: "Card UUID" },
|
|
902
|
-
storagePath: {
|
|
971
|
+
planId: { type: "string", description: "Plan UUID (artifact only)." },
|
|
972
|
+
workspaceId: {
|
|
903
973
|
type: "string",
|
|
904
|
-
description:
|
|
905
|
-
"The storagePath returned by harmony_request_card_attachment_upload_url.",
|
|
974
|
+
description: "Workspace UUID (artifact only).",
|
|
906
975
|
},
|
|
907
976
|
fileName: {
|
|
908
977
|
type: "string",
|
|
909
|
-
description:
|
|
978
|
+
description:
|
|
979
|
+
"File name including extension (required for card_attachment).",
|
|
910
980
|
},
|
|
981
|
+
title: { type: "string", description: "Artifact display title." },
|
|
911
982
|
fileType: {
|
|
912
983
|
type: "string",
|
|
913
984
|
description:
|
|
914
|
-
"
|
|
985
|
+
"card_attachment MIME type (inferred from the extension when omitted).",
|
|
915
986
|
},
|
|
916
|
-
|
|
987
|
+
contentType: {
|
|
917
988
|
type: "string",
|
|
918
989
|
description:
|
|
919
|
-
"
|
|
990
|
+
"artifact MIME type; only 'text/html' is accepted (the default).",
|
|
920
991
|
},
|
|
921
992
|
size: {
|
|
922
993
|
type: "number",
|
|
923
994
|
description:
|
|
924
|
-
"
|
|
995
|
+
"File size in bytes (rejected early if over the target's limit).",
|
|
925
996
|
},
|
|
926
997
|
},
|
|
927
|
-
required: ["
|
|
928
|
-
},
|
|
929
|
-
},
|
|
930
|
-
harmony_classify_card: {
|
|
931
|
-
description:
|
|
932
|
-
"Classify a card with the LLM classifier: sets `intent` (plan/think/implement/review), `complexity_score` (0-10), `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the type label (feature/bug/idea). Call right after creating a card to classify it in-flow. Idempotent; never touches the user-owned `model_override`.",
|
|
933
|
-
inputSchema: {
|
|
934
|
-
type: "object",
|
|
935
|
-
properties: {
|
|
936
|
-
cardId: { type: "string", description: "Card UUID" },
|
|
937
|
-
},
|
|
938
|
-
required: ["cardId"],
|
|
998
|
+
required: ["target"],
|
|
939
999
|
},
|
|
940
1000
|
},
|
|
941
|
-
|
|
1001
|
+
harmony_finalize_upload: {
|
|
942
1002
|
description:
|
|
943
|
-
"
|
|
1003
|
+
"Step 2 of the upload handshake. After PUTting the bytes to the signed uploadUrl, call this " +
|
|
1004
|
+
"with the storagePath to validate and register. The server re-downloads the object and " +
|
|
1005
|
+
"enforces size + content-type (magic-byte sniff, never the declared type) + an optional " +
|
|
1006
|
+
'sha256 integrity check, deleting and failing on any mismatch. `target: "card_attachment"` ' +
|
|
1007
|
+
'(cardId, storagePath, fileName) or `target: "artifact"` (storagePath + the same one of ' +
|
|
1008
|
+
"cardId/planId/workspaceId used for the upload URL). Returns the attachment/artifact + a signed URL.",
|
|
944
1009
|
inputSchema: {
|
|
945
1010
|
type: "object",
|
|
946
1011
|
properties: {
|
|
947
|
-
|
|
948
|
-
type: "string",
|
|
949
|
-
description: "Display title (defaults to the file basename).",
|
|
950
|
-
},
|
|
951
|
-
cardId: { type: "string", description: "Link to this card (UUID)." },
|
|
952
|
-
planId: { type: "string", description: "Link to this plan (UUID)." },
|
|
953
|
-
workspaceId: {
|
|
1012
|
+
target: {
|
|
954
1013
|
type: "string",
|
|
1014
|
+
enum: ["card_attachment", "artifact"],
|
|
955
1015
|
description:
|
|
956
|
-
"
|
|
1016
|
+
"What to finalize: a card file attachment or a hosted HTML artifact.",
|
|
957
1017
|
},
|
|
958
|
-
|
|
959
|
-
type: "string",
|
|
960
|
-
description:
|
|
961
|
-
"Absolute path to a local .html file the MCP server process can read. Mutually exclusive with base64Data.",
|
|
962
|
-
},
|
|
963
|
-
base64Data: {
|
|
1018
|
+
storagePath: {
|
|
964
1019
|
type: "string",
|
|
965
1020
|
description:
|
|
966
|
-
"
|
|
1021
|
+
"The storagePath returned by harmony_request_upload_url.",
|
|
967
1022
|
},
|
|
968
|
-
|
|
969
|
-
required: [],
|
|
970
|
-
},
|
|
971
|
-
},
|
|
972
|
-
harmony_request_artifact_upload_url: {
|
|
973
|
-
description:
|
|
974
|
-
"Step 1 of the large-file / hosted-MCP artifact upload handshake (use when the server can't read local disk). Mints a one-shot signed Storage upload URL. Provide exactly one of cardId/planId/workspaceId. Returns { uploadUrl, token, storagePath }. Then PUT the HTML bytes to uploadUrl and call harmony_finalize_artifact with storagePath. text/html only, max 2MB.",
|
|
975
|
-
inputSchema: {
|
|
976
|
-
type: "object",
|
|
977
|
-
properties: {
|
|
978
|
-
title: {
|
|
1023
|
+
cardId: {
|
|
979
1024
|
type: "string",
|
|
980
1025
|
description:
|
|
981
|
-
"
|
|
1026
|
+
"Card UUID. Required for card_attachment; scope for artifact.",
|
|
982
1027
|
},
|
|
983
|
-
|
|
984
|
-
planId: { type: "string", description: "Link to this plan (UUID)." },
|
|
1028
|
+
planId: { type: "string", description: "Plan UUID (artifact only)." },
|
|
985
1029
|
workspaceId: {
|
|
986
1030
|
type: "string",
|
|
987
|
-
description:
|
|
988
|
-
"Attach to this workspace as a standalone artifact (UUID).",
|
|
1031
|
+
description: "Workspace UUID (artifact only).",
|
|
989
1032
|
},
|
|
990
|
-
|
|
991
|
-
type: "string",
|
|
992
|
-
description: "MIME type; only 'text/html' is accepted (the default).",
|
|
993
|
-
},
|
|
994
|
-
size: {
|
|
995
|
-
type: "number",
|
|
996
|
-
description: "File size in bytes (rejected early if over 2MB).",
|
|
997
|
-
},
|
|
998
|
-
},
|
|
999
|
-
required: [],
|
|
1000
|
-
},
|
|
1001
|
-
},
|
|
1002
|
-
harmony_finalize_artifact: {
|
|
1003
|
-
description:
|
|
1004
|
-
"Step 2 of the artifact upload handshake. After PUTting the HTML bytes to the signed uploadUrl, call this with storagePath to validate and register the artifact. The server re-downloads the object and enforces size + text/html (magic-byte sniff) + an optional sha256 integrity check, deleting and failing on any mismatch. Pass the same one of cardId/planId/workspaceId used for the upload URL. Returns the artifact + a signed URL; use harmony_share_artifact for a public link.",
|
|
1005
|
-
inputSchema: {
|
|
1006
|
-
type: "object",
|
|
1007
|
-
properties: {
|
|
1008
|
-
storagePath: {
|
|
1033
|
+
fileName: {
|
|
1009
1034
|
type: "string",
|
|
1010
1035
|
description:
|
|
1011
|
-
"
|
|
1036
|
+
"File name including extension (required for card_attachment).",
|
|
1012
1037
|
},
|
|
1013
|
-
title: { type: "string", description: "
|
|
1014
|
-
|
|
1015
|
-
planId: { type: "string", description: "Link to this plan (UUID)." },
|
|
1016
|
-
workspaceId: {
|
|
1038
|
+
title: { type: "string", description: "Artifact display title." },
|
|
1039
|
+
fileType: {
|
|
1017
1040
|
type: "string",
|
|
1018
1041
|
description:
|
|
1019
|
-
"
|
|
1042
|
+
"card_attachment MIME type; inferred from the extension when omitted.",
|
|
1020
1043
|
},
|
|
1021
1044
|
sha256: {
|
|
1022
1045
|
type: "string",
|
|
@@ -1029,7 +1052,18 @@ export const TOOLS = {
|
|
|
1029
1052
|
"Optional byte size (advisory; re-validated server-side).",
|
|
1030
1053
|
},
|
|
1031
1054
|
},
|
|
1032
|
-
required: ["storagePath"],
|
|
1055
|
+
required: ["target", "storagePath"],
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
harmony_classify_card: {
|
|
1059
|
+
description:
|
|
1060
|
+
"Classify a card with the LLM classifier: sets `intent` (plan/think/implement/review), `complexity_score` (0-10), `model_tier` (simple/advanced/research), stamps `classified_at`, and applies the type label (feature/bug/idea). Call right after creating a card to classify it in-flow. Idempotent; never touches the user-owned `model_override`.",
|
|
1061
|
+
inputSchema: {
|
|
1062
|
+
type: "object",
|
|
1063
|
+
properties: {
|
|
1064
|
+
cardId: { type: "string", description: "Card UUID" },
|
|
1065
|
+
},
|
|
1066
|
+
required: ["cardId"],
|
|
1033
1067
|
},
|
|
1034
1068
|
},
|
|
1035
1069
|
harmony_share_artifact: {
|
|
@@ -1062,6 +1096,28 @@ export const TOOLS = {
|
|
|
1062
1096
|
required: ["cardId"],
|
|
1063
1097
|
},
|
|
1064
1098
|
},
|
|
1099
|
+
harmony_add_external_link: {
|
|
1100
|
+
description:
|
|
1101
|
+
"Attach an external reference URL (e.g. a PR/MR link, doc, or dashboard) to a card. Durable — survives description edits. Use this in addition to writing the PR link into the description.",
|
|
1102
|
+
inputSchema: {
|
|
1103
|
+
type: "object",
|
|
1104
|
+
properties: {
|
|
1105
|
+
cardId: {
|
|
1106
|
+
type: "string",
|
|
1107
|
+
description: "Card UUID",
|
|
1108
|
+
},
|
|
1109
|
+
url: {
|
|
1110
|
+
type: "string",
|
|
1111
|
+
description: "The URL to attach",
|
|
1112
|
+
},
|
|
1113
|
+
title: {
|
|
1114
|
+
type: "string",
|
|
1115
|
+
description: "Optional human-readable title for the link",
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
required: ["cardId", "url"],
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1065
1121
|
|
|
1066
1122
|
// Subtask operations
|
|
1067
1123
|
harmony_create_subtask: {
|
|
@@ -1119,7 +1175,7 @@ export const TOOLS = {
|
|
|
1119
1175
|
// Comment operations
|
|
1120
1176
|
harmony_add_comment: {
|
|
1121
1177
|
description:
|
|
1122
|
-
"Post a comment on a card as the agent — converse with the human in the open: report progress, ask a question, record a decision, or note a finding, instead of editing the card description. Set supersedesId to correct an earlier comment, confirmsId to reaffirm one.",
|
|
1178
|
+
"Post a comment on a card as the agent — converse with the human in the open: report progress, ask a question, record a decision, or note a finding, instead of editing the card description. Set supersedesId to correct an earlier comment, confirmsId to reaffirm one. To answer an open question, reply to it with replyToId.",
|
|
1123
1179
|
inputSchema: {
|
|
1124
1180
|
type: "object",
|
|
1125
1181
|
properties: {
|
|
@@ -1147,6 +1203,11 @@ export const TOOLS = {
|
|
|
1147
1203
|
type: "string",
|
|
1148
1204
|
description: "Comment id this comment reaffirms",
|
|
1149
1205
|
},
|
|
1206
|
+
replyToId: {
|
|
1207
|
+
type: "string",
|
|
1208
|
+
description:
|
|
1209
|
+
"Comment id this is a one-level reply to. To answer an open question, reply to it. If you also set supersedesId/confirmsId, it must equal replyToId.",
|
|
1210
|
+
},
|
|
1150
1211
|
},
|
|
1151
1212
|
required: ["cardId", "body"],
|
|
1152
1213
|
},
|
|
@@ -2101,7 +2162,7 @@ export const TOOLS = {
|
|
|
2101
2162
|
|
|
2102
2163
|
harmony_list_playbook: {
|
|
2103
2164
|
description:
|
|
2104
|
-
"List a workspace's playbooks (reusable process definitions). Returns each playbook's name, version,
|
|
2165
|
+
"List a workspace's playbooks (reusable process definitions). Returns each playbook's name, version, and state. Read-only.",
|
|
2105
2166
|
inputSchema: {
|
|
2106
2167
|
type: "object",
|
|
2107
2168
|
properties: {
|
|
@@ -2126,24 +2187,9 @@ export const TOOLS = {
|
|
|
2126
2187
|
},
|
|
2127
2188
|
},
|
|
2128
2189
|
|
|
2129
|
-
harmony_run_playbook: {
|
|
2130
|
-
description:
|
|
2131
|
-
"Run a playbook server-side and return the finalized run. Only legacy automation playbooks (steps_version 1) are runnable; stage playbooks (steps_version 2) are rejected. The server drives every step to completion.",
|
|
2132
|
-
inputSchema: {
|
|
2133
|
-
type: "object",
|
|
2134
|
-
properties: {
|
|
2135
|
-
playbookId: {
|
|
2136
|
-
type: "string",
|
|
2137
|
-
description: "Playbook ID to run (UUID)",
|
|
2138
|
-
},
|
|
2139
|
-
},
|
|
2140
|
-
required: ["playbookId"],
|
|
2141
|
-
},
|
|
2142
|
-
},
|
|
2143
|
-
|
|
2144
2190
|
harmony_create_playbook: {
|
|
2145
2191
|
description:
|
|
2146
|
-
"Create a new playbook in a workspace.
|
|
2192
|
+
"Create a new playbook (a reusable process definition) in a workspace. A playbook is an ordered set of stages bound to board columns, run by people and the agent daemon as a card moves through them.",
|
|
2147
2193
|
inputSchema: {
|
|
2148
2194
|
type: "object",
|
|
2149
2195
|
properties: {
|
|
@@ -2153,16 +2199,9 @@ export const TOOLS = {
|
|
|
2153
2199
|
},
|
|
2154
2200
|
name: { type: "string", description: "Playbook name" },
|
|
2155
2201
|
description: { type: "string", description: "Playbook description" },
|
|
2156
|
-
stepsVersion: {
|
|
2157
|
-
type: "number",
|
|
2158
|
-
enum: [1, 2],
|
|
2159
|
-
description:
|
|
2160
|
-
"1 = automation macro (tool steps), 2 = Method stage model (stage objects). Default 1.",
|
|
2161
|
-
},
|
|
2162
2202
|
steps: {
|
|
2163
2203
|
type: "array",
|
|
2164
|
-
description:
|
|
2165
|
-
"Steps (steps_version 1: tool-step objects) or stages (steps_version 2: stage objects).",
|
|
2204
|
+
description: "The playbook's ordered stage objects.",
|
|
2166
2205
|
items: { type: "object" },
|
|
2167
2206
|
},
|
|
2168
2207
|
},
|
|
@@ -2184,7 +2223,7 @@ export const TOOLS = {
|
|
|
2184
2223
|
description: { type: "string", description: "New description" },
|
|
2185
2224
|
steps: {
|
|
2186
2225
|
type: "array",
|
|
2187
|
-
description: "New
|
|
2226
|
+
description: "New ordered stage objects.",
|
|
2188
2227
|
items: { type: "object" },
|
|
2189
2228
|
},
|
|
2190
2229
|
enabled: {
|
|
@@ -2201,22 +2240,6 @@ export const TOOLS = {
|
|
|
2201
2240
|
},
|
|
2202
2241
|
},
|
|
2203
2242
|
|
|
2204
|
-
harmony_save_card_as_playbook: {
|
|
2205
|
-
description:
|
|
2206
|
-
"Save an existing card as a new steps_version 1 (automation) playbook, seeding one create-card step from the card. Returns the created playbook.",
|
|
2207
|
-
inputSchema: {
|
|
2208
|
-
type: "object",
|
|
2209
|
-
properties: {
|
|
2210
|
-
cardId: { type: "string", description: "Card ID to template (UUID)" },
|
|
2211
|
-
name: {
|
|
2212
|
-
type: "string",
|
|
2213
|
-
description: "Name for the new playbook (defaults to the card title)",
|
|
2214
|
-
},
|
|
2215
|
-
},
|
|
2216
|
-
required: ["cardId"],
|
|
2217
|
-
},
|
|
2218
|
-
},
|
|
2219
|
-
|
|
2220
2243
|
// ============ ONBOARDING TOOLS ============
|
|
2221
2244
|
harmony_signup: {
|
|
2222
2245
|
description:
|
|
@@ -2807,30 +2830,41 @@ async function handleToolCall(
|
|
|
2807
2830
|
return { success: true, ...result, count: result.cards.length };
|
|
2808
2831
|
}
|
|
2809
2832
|
|
|
2833
|
+
// Unified card fetch (#605): cardId (full) | shortId (full) | shortIds (compact).
|
|
2834
|
+
// Old names dispatch here too, behind the deprecation shim.
|
|
2835
|
+
case "harmony_get_card_by_short_id":
|
|
2836
|
+
case "harmony_bulk_get_cards":
|
|
2810
2837
|
case "harmony_get_card": {
|
|
2838
|
+
if (name !== "harmony_get_card") warnDeprecatedTool(name);
|
|
2839
|
+
const hasShortIds = args.shortIds != null;
|
|
2840
|
+
const hasShortId = args.shortId != null;
|
|
2841
|
+
const hasCardId = args.cardId != null;
|
|
2842
|
+
if ([hasShortIds, hasShortId, hasCardId].filter(Boolean).length !== 1) {
|
|
2843
|
+
throw new Error(
|
|
2844
|
+
"Provide exactly one of: cardId (UUID), shortId (number), or shortIds (number[]).",
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
if (hasShortIds) {
|
|
2848
|
+
const shortIds = z
|
|
2849
|
+
.array(z.number().int().positive())
|
|
2850
|
+
.min(1)
|
|
2851
|
+
.max(100)
|
|
2852
|
+
.parse(args.shortIds);
|
|
2853
|
+
const projectId = getProjectId();
|
|
2854
|
+
const result = await client.bulkGetCards(projectId, shortIds);
|
|
2855
|
+
return { success: true, ...result };
|
|
2856
|
+
}
|
|
2857
|
+
if (hasShortId) {
|
|
2858
|
+
const shortId = z.number().int().positive().parse(args.shortId);
|
|
2859
|
+
const projectId = (args.projectId as string) || getProjectId();
|
|
2860
|
+
const result = await client.getCardByShortId(projectId, shortId);
|
|
2861
|
+
return { success: true, ...result };
|
|
2862
|
+
}
|
|
2811
2863
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
2812
2864
|
const result = await client.getCard(cardId);
|
|
2813
2865
|
return { success: true, ...result };
|
|
2814
2866
|
}
|
|
2815
2867
|
|
|
2816
|
-
case "harmony_get_card_by_short_id": {
|
|
2817
|
-
const shortId = z.number().int().positive().parse(args.shortId);
|
|
2818
|
-
const projectId = (args.projectId as string) || getProjectId();
|
|
2819
|
-
const result = await client.getCardByShortId(projectId, shortId);
|
|
2820
|
-
return { success: true, ...result };
|
|
2821
|
-
}
|
|
2822
|
-
|
|
2823
|
-
case "harmony_bulk_get_cards": {
|
|
2824
|
-
const shortIds = z
|
|
2825
|
-
.array(z.number().int().positive())
|
|
2826
|
-
.min(1)
|
|
2827
|
-
.max(100)
|
|
2828
|
-
.parse(args.shortIds);
|
|
2829
|
-
const projectId = getProjectId();
|
|
2830
|
-
const result = await client.bulkGetCards(projectId, shortIds);
|
|
2831
|
-
return { success: true, ...result };
|
|
2832
|
-
}
|
|
2833
|
-
|
|
2834
2868
|
case "harmony_bulk_archive_cards": {
|
|
2835
2869
|
const shortIds = z
|
|
2836
2870
|
.array(z.number().int().positive())
|
|
@@ -2978,71 +3012,85 @@ async function handleToolCall(
|
|
|
2978
3012
|
return result;
|
|
2979
3013
|
}
|
|
2980
3014
|
|
|
2981
|
-
|
|
2982
|
-
|
|
3015
|
+
// Unified upload (#605): target = card_attachment | artifact. Old names
|
|
3016
|
+
// (harmony_upload_artifact / harmony_upload_card_attachment) dispatch here
|
|
3017
|
+
// behind the deprecation shim; target is inferred from the old name.
|
|
3018
|
+
case "harmony_upload_card_attachment":
|
|
3019
|
+
case "harmony_upload_artifact":
|
|
3020
|
+
case "harmony_upload": {
|
|
3021
|
+
if (name !== "harmony_upload") warnDeprecatedTool(name);
|
|
3022
|
+
const target =
|
|
3023
|
+
name === "harmony_upload_card_attachment"
|
|
3024
|
+
? "card_attachment"
|
|
3025
|
+
: name === "harmony_upload_artifact"
|
|
3026
|
+
? "artifact"
|
|
3027
|
+
: z.enum(["card_attachment", "artifact"]).parse(args.target);
|
|
3028
|
+
|
|
2983
3029
|
const filePath =
|
|
2984
3030
|
args.filePath != null ? z.string().parse(args.filePath) : undefined;
|
|
2985
3031
|
const base64Data =
|
|
2986
3032
|
args.base64Data != null ? z.string().parse(args.base64Data) : undefined;
|
|
2987
|
-
const fileName =
|
|
2988
|
-
args.fileName != null ? z.string().parse(args.fileName) : undefined;
|
|
2989
|
-
const contentType =
|
|
2990
|
-
args.contentType != null
|
|
2991
|
-
? z.string().parse(args.contentType)
|
|
2992
|
-
: undefined;
|
|
2993
|
-
|
|
2994
3033
|
if (filePath && base64Data) {
|
|
2995
3034
|
throw new Error("Provide either filePath or base64Data, not both.");
|
|
2996
3035
|
}
|
|
2997
3036
|
|
|
2998
|
-
if (
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
if (base64Data) {
|
|
3027
|
-
if (!fileName) {
|
|
3028
|
-
throw new Error("fileName is required when using base64Data.");
|
|
3029
|
-
}
|
|
3030
|
-
if (base64ByteLength(base64Data) > MAX_ATTACHMENT_SIZE) {
|
|
3031
|
-
throw new Error(
|
|
3032
|
-
`File is over the 5MB attachment limit. Use the harmony_request_card_attachment_upload_url + harmony_finalize_card_attachment handshake for large files.`,
|
|
3037
|
+
if (target === "card_attachment") {
|
|
3038
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
3039
|
+
const fileName =
|
|
3040
|
+
args.fileName != null ? z.string().parse(args.fileName) : undefined;
|
|
3041
|
+
const contentType =
|
|
3042
|
+
args.contentType != null
|
|
3043
|
+
? z.string().parse(args.contentType)
|
|
3044
|
+
: undefined;
|
|
3045
|
+
|
|
3046
|
+
if (filePath) {
|
|
3047
|
+
// Server can read the file → upload direct-to-storage via the
|
|
3048
|
+
// handshake (no base64 through the model context or edge-fn body).
|
|
3049
|
+
const bytes = await readFileForUpload(
|
|
3050
|
+
filePath,
|
|
3051
|
+
MAX_ATTACHMENT_SIZE,
|
|
3052
|
+
"attachment",
|
|
3053
|
+
);
|
|
3054
|
+
const resolvedName = fileName || basename(filePath);
|
|
3055
|
+
const signed = await client.requestCardAttachmentUploadUrl(cardId, {
|
|
3056
|
+
fileName: resolvedName,
|
|
3057
|
+
fileType: contentType,
|
|
3058
|
+
size: bytes.byteLength,
|
|
3059
|
+
});
|
|
3060
|
+
await putToSignedUrl(
|
|
3061
|
+
signed.uploadUrl,
|
|
3062
|
+
bytes,
|
|
3063
|
+
contentType || signed.fileType || "application/octet-stream",
|
|
3033
3064
|
);
|
|
3065
|
+
return await client.finalizeCardAttachment(cardId, {
|
|
3066
|
+
storagePath: signed.storagePath,
|
|
3067
|
+
fileName: resolvedName,
|
|
3068
|
+
fileType: contentType || signed.fileType,
|
|
3069
|
+
sha256: sha256Hex(bytes),
|
|
3070
|
+
size: bytes.byteLength,
|
|
3071
|
+
});
|
|
3034
3072
|
}
|
|
3035
|
-
return await client.uploadCardAttachment(cardId, {
|
|
3036
|
-
fileName,
|
|
3037
|
-
data: base64Data,
|
|
3038
|
-
fileType: contentType,
|
|
3039
|
-
});
|
|
3040
|
-
}
|
|
3041
3073
|
|
|
3042
|
-
|
|
3043
|
-
|
|
3074
|
+
if (base64Data) {
|
|
3075
|
+
if (!fileName) {
|
|
3076
|
+
throw new Error("fileName is required when using base64Data.");
|
|
3077
|
+
}
|
|
3078
|
+
if (base64ByteLength(base64Data) > MAX_ATTACHMENT_SIZE) {
|
|
3079
|
+
throw new Error(
|
|
3080
|
+
`File is over the 5MB attachment limit. Use the harmony_request_upload_url + harmony_finalize_upload handshake for large files.`,
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
return await client.uploadCardAttachment(cardId, {
|
|
3084
|
+
fileName,
|
|
3085
|
+
data: base64Data,
|
|
3086
|
+
fileType: contentType,
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3044
3089
|
|
|
3045
|
-
|
|
3090
|
+
throw new Error("Provide either filePath or base64Data.");
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
// target === "artifact"
|
|
3046
3094
|
const title =
|
|
3047
3095
|
args.title != null ? z.string().parse(args.title) : undefined;
|
|
3048
3096
|
const cardId =
|
|
@@ -3055,17 +3103,7 @@ async function handleToolCall(
|
|
|
3055
3103
|
: undefined;
|
|
3056
3104
|
requireExactlyOneScope({ cardId, planId, workspaceId });
|
|
3057
3105
|
|
|
3058
|
-
const filePath =
|
|
3059
|
-
args.filePath != null ? z.string().parse(args.filePath) : undefined;
|
|
3060
|
-
const base64Data =
|
|
3061
|
-
args.base64Data != null ? z.string().parse(args.base64Data) : undefined;
|
|
3062
|
-
if (filePath && base64Data) {
|
|
3063
|
-
throw new Error("Provide either filePath or base64Data, not both.");
|
|
3064
|
-
}
|
|
3065
|
-
|
|
3066
3106
|
if (filePath) {
|
|
3067
|
-
// Server can read the file → upload direct-to-storage via the handshake
|
|
3068
|
-
// (no base64 through the model context or the edge-fn JSON body).
|
|
3069
3107
|
const bytes = await readFileForUpload(
|
|
3070
3108
|
filePath,
|
|
3071
3109
|
MAX_ARTIFACT_SIZE,
|
|
@@ -3095,7 +3133,7 @@ async function handleToolCall(
|
|
|
3095
3133
|
if (base64Data) {
|
|
3096
3134
|
if (base64ByteLength(base64Data) > MAX_ARTIFACT_SIZE) {
|
|
3097
3135
|
throw new Error(
|
|
3098
|
-
`Artifact is over the 2MB limit. Use the
|
|
3136
|
+
`Artifact is over the 2MB limit. Use the harmony_request_upload_url + harmony_finalize_upload handshake for large files.`,
|
|
3099
3137
|
);
|
|
3100
3138
|
}
|
|
3101
3139
|
return await client.uploadArtifact({
|
|
@@ -3110,7 +3148,37 @@ async function handleToolCall(
|
|
|
3110
3148
|
throw new Error("Provide either filePath or base64Data.");
|
|
3111
3149
|
}
|
|
3112
3150
|
|
|
3113
|
-
|
|
3151
|
+
// Unified upload handshake step 1 (#605). Old names shimmed; target inferred.
|
|
3152
|
+
case "harmony_request_artifact_upload_url":
|
|
3153
|
+
case "harmony_request_card_attachment_upload_url":
|
|
3154
|
+
case "harmony_request_upload_url": {
|
|
3155
|
+
if (name !== "harmony_request_upload_url") warnDeprecatedTool(name);
|
|
3156
|
+
const target =
|
|
3157
|
+
name === "harmony_request_card_attachment_upload_url"
|
|
3158
|
+
? "card_attachment"
|
|
3159
|
+
: name === "harmony_request_artifact_upload_url"
|
|
3160
|
+
? "artifact"
|
|
3161
|
+
: z.enum(["card_attachment", "artifact"]).parse(args.target);
|
|
3162
|
+
|
|
3163
|
+
if (target === "card_attachment") {
|
|
3164
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
3165
|
+
const fileName = z.string().parse(args.fileName);
|
|
3166
|
+
const fileType =
|
|
3167
|
+
args.fileType != null ? z.string().parse(args.fileType) : undefined;
|
|
3168
|
+
const size = z.number().positive().parse(args.size);
|
|
3169
|
+
if (size > MAX_ATTACHMENT_SIZE) {
|
|
3170
|
+
throw new Error(
|
|
3171
|
+
`Declared size ${size} bytes is over the ${MAX_ATTACHMENT_SIZE}-byte (5MB) attachment limit.`,
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
return await client.requestCardAttachmentUploadUrl(cardId, {
|
|
3175
|
+
fileName,
|
|
3176
|
+
fileType,
|
|
3177
|
+
size,
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
// target === "artifact"
|
|
3114
3182
|
const title =
|
|
3115
3183
|
args.title != null ? z.string().parse(args.title) : undefined;
|
|
3116
3184
|
const cardId =
|
|
@@ -3143,8 +3211,40 @@ async function handleToolCall(
|
|
|
3143
3211
|
});
|
|
3144
3212
|
}
|
|
3145
3213
|
|
|
3146
|
-
|
|
3214
|
+
// Unified upload handshake step 2 (#605). Old names shimmed; target inferred.
|
|
3215
|
+
case "harmony_finalize_artifact":
|
|
3216
|
+
case "harmony_finalize_card_attachment":
|
|
3217
|
+
case "harmony_finalize_upload": {
|
|
3218
|
+
if (name !== "harmony_finalize_upload") warnDeprecatedTool(name);
|
|
3219
|
+
const target =
|
|
3220
|
+
name === "harmony_finalize_card_attachment"
|
|
3221
|
+
? "card_attachment"
|
|
3222
|
+
: name === "harmony_finalize_artifact"
|
|
3223
|
+
? "artifact"
|
|
3224
|
+
: z.enum(["card_attachment", "artifact"]).parse(args.target);
|
|
3147
3225
|
const storagePath = z.string().parse(args.storagePath);
|
|
3226
|
+
|
|
3227
|
+
if (target === "card_attachment") {
|
|
3228
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
3229
|
+
const fileName = z.string().parse(args.fileName);
|
|
3230
|
+
const fileType =
|
|
3231
|
+
args.fileType != null ? z.string().parse(args.fileType) : undefined;
|
|
3232
|
+
const sha256 =
|
|
3233
|
+
args.sha256 != null ? z.string().parse(args.sha256) : undefined;
|
|
3234
|
+
const size =
|
|
3235
|
+
args.size != null
|
|
3236
|
+
? z.number().positive().parse(args.size)
|
|
3237
|
+
: undefined;
|
|
3238
|
+
return await client.finalizeCardAttachment(cardId, {
|
|
3239
|
+
storagePath,
|
|
3240
|
+
fileName,
|
|
3241
|
+
fileType,
|
|
3242
|
+
sha256,
|
|
3243
|
+
size,
|
|
3244
|
+
});
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
// target === "artifact"
|
|
3148
3248
|
const title =
|
|
3149
3249
|
args.title != null ? z.string().parse(args.title) : undefined;
|
|
3150
3250
|
const cardId =
|
|
@@ -3171,43 +3271,6 @@ async function handleToolCall(
|
|
|
3171
3271
|
});
|
|
3172
3272
|
}
|
|
3173
3273
|
|
|
3174
|
-
case "harmony_request_card_attachment_upload_url": {
|
|
3175
|
-
const cardId = z.string().uuid().parse(args.cardId);
|
|
3176
|
-
const fileName = z.string().parse(args.fileName);
|
|
3177
|
-
const fileType =
|
|
3178
|
-
args.fileType != null ? z.string().parse(args.fileType) : undefined;
|
|
3179
|
-
const size = z.number().positive().parse(args.size);
|
|
3180
|
-
if (size > MAX_ATTACHMENT_SIZE) {
|
|
3181
|
-
throw new Error(
|
|
3182
|
-
`Declared size ${size} bytes is over the ${MAX_ATTACHMENT_SIZE}-byte (5MB) attachment limit.`,
|
|
3183
|
-
);
|
|
3184
|
-
}
|
|
3185
|
-
return await client.requestCardAttachmentUploadUrl(cardId, {
|
|
3186
|
-
fileName,
|
|
3187
|
-
fileType,
|
|
3188
|
-
size,
|
|
3189
|
-
});
|
|
3190
|
-
}
|
|
3191
|
-
|
|
3192
|
-
case "harmony_finalize_card_attachment": {
|
|
3193
|
-
const cardId = z.string().uuid().parse(args.cardId);
|
|
3194
|
-
const storagePath = z.string().parse(args.storagePath);
|
|
3195
|
-
const fileName = z.string().parse(args.fileName);
|
|
3196
|
-
const fileType =
|
|
3197
|
-
args.fileType != null ? z.string().parse(args.fileType) : undefined;
|
|
3198
|
-
const sha256 =
|
|
3199
|
-
args.sha256 != null ? z.string().parse(args.sha256) : undefined;
|
|
3200
|
-
const size =
|
|
3201
|
-
args.size != null ? z.number().positive().parse(args.size) : undefined;
|
|
3202
|
-
return await client.finalizeCardAttachment(cardId, {
|
|
3203
|
-
storagePath,
|
|
3204
|
-
fileName,
|
|
3205
|
-
fileType,
|
|
3206
|
-
sha256,
|
|
3207
|
-
size,
|
|
3208
|
-
});
|
|
3209
|
-
}
|
|
3210
|
-
|
|
3211
3274
|
case "harmony_share_artifact": {
|
|
3212
3275
|
const artifactId = z.string().uuid().parse(args.artifactId);
|
|
3213
3276
|
const expiresInDays =
|
|
@@ -3230,6 +3293,16 @@ async function handleToolCall(
|
|
|
3230
3293
|
return result;
|
|
3231
3294
|
}
|
|
3232
3295
|
|
|
3296
|
+
case "harmony_add_external_link": {
|
|
3297
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
3298
|
+
const url = z.string().min(1).max(2048).parse(args.url);
|
|
3299
|
+
const title = args.title
|
|
3300
|
+
? z.string().max(200).parse(args.title)
|
|
3301
|
+
: undefined;
|
|
3302
|
+
const result = await client.addExternalLink(cardId, url, title);
|
|
3303
|
+
return { success: true, ...result };
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3233
3306
|
case "harmony_classify_card": {
|
|
3234
3307
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
3235
3308
|
const result = await client.classifyCard(cardId);
|
|
@@ -3307,10 +3380,15 @@ async function handleToolCall(
|
|
|
3307
3380
|
args.confirmsId !== undefined
|
|
3308
3381
|
? z.string().uuid().parse(args.confirmsId)
|
|
3309
3382
|
: undefined;
|
|
3383
|
+
const replyToId =
|
|
3384
|
+
args.replyToId !== undefined
|
|
3385
|
+
? z.string().uuid().parse(args.replyToId)
|
|
3386
|
+
: undefined;
|
|
3310
3387
|
const result = await client.addComment(cardId, body, {
|
|
3311
3388
|
commentType,
|
|
3312
3389
|
supersedesId,
|
|
3313
3390
|
confirmsId,
|
|
3391
|
+
replyToId,
|
|
3314
3392
|
});
|
|
3315
3393
|
return { success: true, ...result };
|
|
3316
3394
|
}
|
|
@@ -4717,25 +4795,19 @@ async function handleToolCall(
|
|
|
4717
4795
|
return { success: true, playbook: result.playbook, runs: result.runs };
|
|
4718
4796
|
}
|
|
4719
4797
|
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
return
|
|
4724
|
-
}
|
|
4798
|
+
// Deprecated (#612) — dropped from the advertised TOOLS list; the case
|
|
4799
|
+
// remains so an old caller gets a legible notice, not an unknown-tool error.
|
|
4800
|
+
case "harmony_run_playbook":
|
|
4801
|
+
return deprecatedRemovedToolResult("harmony_run_playbook");
|
|
4725
4802
|
|
|
4726
4803
|
case "harmony_create_playbook": {
|
|
4727
4804
|
const workspaceId = (args.workspaceId as string) || getWorkspaceId();
|
|
4728
4805
|
const name = z.string().min(1).max(200).parse(args.name);
|
|
4729
|
-
const stepsVersion =
|
|
4730
|
-
args.stepsVersion !== undefined
|
|
4731
|
-
? z.union([z.literal(1), z.literal(2)]).parse(args.stepsVersion)
|
|
4732
|
-
: undefined;
|
|
4733
4806
|
const result = await client.createPlaybook({
|
|
4734
4807
|
workspaceId,
|
|
4735
4808
|
name,
|
|
4736
4809
|
description: args.description as string | undefined,
|
|
4737
4810
|
steps: args.steps,
|
|
4738
|
-
stepsVersion,
|
|
4739
4811
|
});
|
|
4740
4812
|
return { success: true, playbook: result.playbook };
|
|
4741
4813
|
}
|
|
@@ -4752,14 +4824,9 @@ async function handleToolCall(
|
|
|
4752
4824
|
return { success: true, playbook: result.playbook };
|
|
4753
4825
|
}
|
|
4754
4826
|
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
cardId,
|
|
4759
|
-
name: args.name as string | undefined,
|
|
4760
|
-
});
|
|
4761
|
-
return { success: true, playbook: result.playbook };
|
|
4762
|
-
}
|
|
4827
|
+
// Deprecated (#612) — see harmony_run_playbook above.
|
|
4828
|
+
case "harmony_save_card_as_playbook":
|
|
4829
|
+
return deprecatedRemovedToolResult("harmony_save_card_as_playbook");
|
|
4763
4830
|
|
|
4764
4831
|
// ============ ONBOARDING TOOLS ============
|
|
4765
4832
|
case "harmony_signup": {
|