@gethmy/mcp 2.13.4 → 2.15.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 +333 -276
- package/dist/index.js +333 -276
- package/dist/lib/api-client.js +170 -140
- package/dist/lib/oauth-refresh.js +350 -0
- package/package.json +7 -3
- package/src/api-client.ts +77 -12
- package/src/auto-session.ts +91 -1
- package/src/server.ts +403 -326
- package/src/skills.ts +2 -2
package/src/server.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
destroyAutoSession,
|
|
23
23
|
initAutoSession,
|
|
24
24
|
markExplicit,
|
|
25
|
+
noteSessionStatus,
|
|
25
26
|
shutdownAllSessions,
|
|
26
27
|
trackActivity,
|
|
27
28
|
untrack,
|
|
@@ -121,6 +122,65 @@ function requireExactlyOneScope(scope: {
|
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Backward-compat dispatch shim (#605). Consolidated tools dropped the old
|
|
127
|
+
* names from the advertised TOOLS list (the token win), but `handleToolCall`
|
|
128
|
+
* still dispatches them so external integrations calling the old names keep
|
|
129
|
+
* working through a deprecation window. Logged once per name to stderr (never
|
|
130
|
+
* stdout — that carries the MCP protocol on stdio).
|
|
131
|
+
*/
|
|
132
|
+
const DEPRECATED_TOOL_ALIASES: Record<string, string> = {
|
|
133
|
+
harmony_get_card_by_short_id: "harmony_get_card",
|
|
134
|
+
harmony_bulk_get_cards: "harmony_get_card",
|
|
135
|
+
harmony_upload_artifact: "harmony_upload",
|
|
136
|
+
harmony_upload_card_attachment: "harmony_upload",
|
|
137
|
+
harmony_request_artifact_upload_url: "harmony_request_upload_url",
|
|
138
|
+
harmony_request_card_attachment_upload_url: "harmony_request_upload_url",
|
|
139
|
+
harmony_finalize_artifact: "harmony_finalize_upload",
|
|
140
|
+
harmony_finalize_card_attachment: "harmony_finalize_upload",
|
|
141
|
+
};
|
|
142
|
+
const _warnedDeprecatedTools = new Set<string>();
|
|
143
|
+
function warnDeprecatedTool(oldName: string): void {
|
|
144
|
+
if (_warnedDeprecatedTools.has(oldName)) return;
|
|
145
|
+
_warnedDeprecatedTools.add(oldName);
|
|
146
|
+
const replacement = DEPRECATED_TOOL_ALIASES[oldName];
|
|
147
|
+
console.error(
|
|
148
|
+
`[harmony-mcp] Tool "${oldName}" is deprecated (still accepted) — ` +
|
|
149
|
+
`use "${replacement}" instead. The old name is no longer advertised and ` +
|
|
150
|
+
`will be removed in a future major version.`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Removed-capability shim (#612). The legacy steps_version=1 "Automation" macro
|
|
156
|
+
* was retired when Harmony committed to the single stage-Playbook model. These
|
|
157
|
+
* tools have no v2 replacement, so — following the #605 hidden-dispatch pattern —
|
|
158
|
+
* they are dropped from the advertised TOOLS list but their switch cases remain,
|
|
159
|
+
* returning a one-time deprecation notice (warned once to stderr) instead of an
|
|
160
|
+
* unknown-tool error, so an integration calling the old name fails legibly.
|
|
161
|
+
*/
|
|
162
|
+
const DEPRECATED_REMOVED_TOOLS: Record<string, string> = {
|
|
163
|
+
harmony_run_playbook:
|
|
164
|
+
"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.",
|
|
165
|
+
harmony_save_card_as_playbook:
|
|
166
|
+
"The steps_version=1 automation macro was removed. Build a stage playbook with harmony_create_playbook instead.",
|
|
167
|
+
};
|
|
168
|
+
const _warnedRemovedTools = new Set<string>();
|
|
169
|
+
function deprecatedRemovedToolResult(name: string): {
|
|
170
|
+
success: false;
|
|
171
|
+
deprecated: true;
|
|
172
|
+
message: string;
|
|
173
|
+
} {
|
|
174
|
+
const message = DEPRECATED_REMOVED_TOOLS[name];
|
|
175
|
+
if (!_warnedRemovedTools.has(name)) {
|
|
176
|
+
_warnedRemovedTools.add(name);
|
|
177
|
+
console.error(
|
|
178
|
+
`[harmony-mcp] Tool "${name}" is deprecated and no longer functional — ${message}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return { success: false, deprecated: true, message };
|
|
182
|
+
}
|
|
183
|
+
|
|
124
184
|
/** PUT raw bytes straight to a Supabase signed upload URL (the token rides in
|
|
125
185
|
* the URL query). Used by the stdio internal handshake; the hosted server can't
|
|
126
186
|
* read local disk, so there the agent drives this PUT itself via the two-step
|
|
@@ -625,49 +685,37 @@ export const TOOLS = {
|
|
|
625
685
|
},
|
|
626
686
|
},
|
|
627
687
|
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",
|
|
688
|
+
description:
|
|
689
|
+
"Fetch one or many cards. Provide exactly one of: `cardId` (UUID, full detail), " +
|
|
690
|
+
"`shortId` (e.g. 42, full detail), or `shortIds` (array, max 100 — compact " +
|
|
691
|
+
"summaries: id, shortId, title, column, priority, assignee, labels, archived, " +
|
|
692
|
+
"plus any short ids not found). `shortId`/`shortIds` need project context. " +
|
|
693
|
+
"Prefer one `shortIds` call over repeated single fetches for multiple cards.",
|
|
639
694
|
inputSchema: {
|
|
640
695
|
type: "object",
|
|
641
696
|
properties: {
|
|
642
|
-
|
|
697
|
+
cardId: {
|
|
643
698
|
type: "string",
|
|
644
|
-
description: "
|
|
699
|
+
description: "Card UUID — full detail for one card.",
|
|
645
700
|
},
|
|
646
701
|
shortId: {
|
|
647
702
|
type: "number",
|
|
648
|
-
description:
|
|
703
|
+
description:
|
|
704
|
+
"Short ID (e.g. 42 for card #42) — full detail, project-scoped.",
|
|
649
705
|
},
|
|
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
706
|
shortIds: {
|
|
664
707
|
type: "array",
|
|
665
708
|
items: { type: "number" },
|
|
666
709
|
description:
|
|
667
|
-
"
|
|
710
|
+
"Short IDs, e.g. [400, 401, 402] — compact summaries in one call. Max 100.",
|
|
711
|
+
},
|
|
712
|
+
projectId: {
|
|
713
|
+
type: "string",
|
|
714
|
+
description:
|
|
715
|
+
"Project ID for shortId/shortIds (optional if context set).",
|
|
668
716
|
},
|
|
669
717
|
},
|
|
670
|
-
required: [
|
|
718
|
+
required: [],
|
|
671
719
|
},
|
|
672
720
|
},
|
|
673
721
|
harmony_bulk_archive_cards: {
|
|
@@ -837,186 +885,162 @@ export const TOOLS = {
|
|
|
837
885
|
required: ["cardId"],
|
|
838
886
|
},
|
|
839
887
|
},
|
|
840
|
-
|
|
888
|
+
harmony_upload: {
|
|
841
889
|
description:
|
|
842
|
-
|
|
890
|
+
'Upload a file in one call. `target: "card_attachment"` attaches a file to a card ' +
|
|
891
|
+
"(max 5MB; PNG/JPEG/GIF/WebP/HEIC/HEIF/PDF/DOC(X)/XLS(X)/TXT/CSV) — requires cardId. " +
|
|
892
|
+
'`target: "artifact"` hosts a self-contained HTML doc (text/html, max 2MB) linked to ' +
|
|
893
|
+
"exactly one of cardId/planId/workspaceId, rendered in-app in a sandboxed iframe. Provide " +
|
|
894
|
+
"the bytes as `filePath` (local, direct-to-storage) or `base64Data` (small-file fallback). " +
|
|
895
|
+
"Returns the attachment/artifact + a signed URL (artifact: use harmony_share_artifact for a " +
|
|
896
|
+
"public link). Large files on the hosted MCP server: use harmony_request_upload_url + " +
|
|
897
|
+
"harmony_finalize_upload instead.",
|
|
843
898
|
inputSchema: {
|
|
844
899
|
type: "object",
|
|
845
900
|
properties: {
|
|
846
|
-
|
|
901
|
+
target: {
|
|
902
|
+
type: "string",
|
|
903
|
+
enum: ["card_attachment", "artifact"],
|
|
904
|
+
description:
|
|
905
|
+
"What to upload: a card file attachment or a hosted HTML artifact.",
|
|
906
|
+
},
|
|
907
|
+
cardId: {
|
|
908
|
+
type: "string",
|
|
909
|
+
description:
|
|
910
|
+
"Card UUID. Required for card_attachment; one of cardId/planId/workspaceId for artifact.",
|
|
911
|
+
},
|
|
912
|
+
planId: {
|
|
913
|
+
type: "string",
|
|
914
|
+
description:
|
|
915
|
+
"Plan UUID (artifact only — link the artifact to a plan).",
|
|
916
|
+
},
|
|
917
|
+
workspaceId: {
|
|
918
|
+
type: "string",
|
|
919
|
+
description: "Workspace UUID (artifact only — standalone artifact).",
|
|
920
|
+
},
|
|
847
921
|
filePath: {
|
|
848
922
|
type: "string",
|
|
849
923
|
description:
|
|
850
|
-
"Absolute path to a local file the
|
|
924
|
+
"Absolute path to a local file the server can read. Mutually exclusive with base64Data.",
|
|
851
925
|
},
|
|
852
926
|
base64Data: {
|
|
853
927
|
type: "string",
|
|
854
928
|
description:
|
|
855
|
-
"Base64-encoded
|
|
929
|
+
"Base64-encoded bytes (a `data:` URL prefix is accepted and stripped). Mutually exclusive with filePath.",
|
|
856
930
|
},
|
|
857
931
|
fileName: {
|
|
858
932
|
type: "string",
|
|
859
933
|
description:
|
|
860
|
-
"File name including extension (
|
|
934
|
+
"File name including extension (card_attachment: required with base64Data, else defaults to the filePath basename).",
|
|
935
|
+
},
|
|
936
|
+
title: {
|
|
937
|
+
type: "string",
|
|
938
|
+
description:
|
|
939
|
+
"Artifact display title (defaults to the file basename).",
|
|
861
940
|
},
|
|
862
941
|
contentType: {
|
|
863
942
|
type: "string",
|
|
864
943
|
description:
|
|
865
|
-
"Optional MIME type (
|
|
944
|
+
"Optional MIME type (card_attachment; inferred from the extension when omitted).",
|
|
866
945
|
},
|
|
867
946
|
},
|
|
868
|
-
required: ["
|
|
947
|
+
required: ["target"],
|
|
869
948
|
},
|
|
870
949
|
},
|
|
871
|
-
|
|
950
|
+
harmony_request_upload_url: {
|
|
872
951
|
description:
|
|
873
|
-
"Step 1 of the large-file / hosted-MCP upload handshake (use when the server can't read
|
|
952
|
+
"Step 1 of the large-file / hosted-MCP upload handshake (use when the server can't read " +
|
|
953
|
+
'local disk). Mints a one-shot signed Storage upload URL. `target: "card_attachment"` ' +
|
|
954
|
+
'(requires cardId, fileName, size; max 5MB) or `target: "artifact"` (one of ' +
|
|
955
|
+
"cardId/planId/workspaceId; text/html, max 2MB). Returns { uploadUrl, token, storagePath, " +
|
|
956
|
+
"[fileType] }. Then PUT the raw bytes to uploadUrl (no bytes through the model context) and " +
|
|
957
|
+
"call harmony_finalize_upload with the storagePath.",
|
|
874
958
|
inputSchema: {
|
|
875
959
|
type: "object",
|
|
876
960
|
properties: {
|
|
877
|
-
|
|
878
|
-
fileName: {
|
|
961
|
+
target: {
|
|
879
962
|
type: "string",
|
|
880
|
-
|
|
963
|
+
enum: ["card_attachment", "artifact"],
|
|
964
|
+
description:
|
|
965
|
+
"What to upload: a card file attachment or a hosted HTML artifact.",
|
|
881
966
|
},
|
|
882
|
-
|
|
967
|
+
cardId: {
|
|
883
968
|
type: "string",
|
|
884
969
|
description:
|
|
885
|
-
"
|
|
886
|
-
},
|
|
887
|
-
size: {
|
|
888
|
-
type: "number",
|
|
889
|
-
description: "File size in bytes (rejected early if over 5MB).",
|
|
970
|
+
"Card UUID. Required for card_attachment; one of cardId/planId/workspaceId for artifact.",
|
|
890
971
|
},
|
|
891
|
-
|
|
892
|
-
|
|
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: {
|
|
972
|
+
planId: { type: "string", description: "Plan UUID (artifact only)." },
|
|
973
|
+
workspaceId: {
|
|
903
974
|
type: "string",
|
|
904
|
-
description:
|
|
905
|
-
"The storagePath returned by harmony_request_card_attachment_upload_url.",
|
|
975
|
+
description: "Workspace UUID (artifact only).",
|
|
906
976
|
},
|
|
907
977
|
fileName: {
|
|
908
978
|
type: "string",
|
|
909
|
-
description:
|
|
979
|
+
description:
|
|
980
|
+
"File name including extension (required for card_attachment).",
|
|
910
981
|
},
|
|
982
|
+
title: { type: "string", description: "Artifact display title." },
|
|
911
983
|
fileType: {
|
|
912
984
|
type: "string",
|
|
913
985
|
description:
|
|
914
|
-
"
|
|
986
|
+
"card_attachment MIME type (inferred from the extension when omitted).",
|
|
915
987
|
},
|
|
916
|
-
|
|
988
|
+
contentType: {
|
|
917
989
|
type: "string",
|
|
918
990
|
description:
|
|
919
|
-
"
|
|
991
|
+
"artifact MIME type; only 'text/html' is accepted (the default).",
|
|
920
992
|
},
|
|
921
993
|
size: {
|
|
922
994
|
type: "number",
|
|
923
995
|
description:
|
|
924
|
-
"
|
|
996
|
+
"File size in bytes (rejected early if over the target's limit).",
|
|
925
997
|
},
|
|
926
998
|
},
|
|
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"],
|
|
999
|
+
required: ["target"],
|
|
939
1000
|
},
|
|
940
1001
|
},
|
|
941
|
-
|
|
1002
|
+
harmony_finalize_upload: {
|
|
942
1003
|
description:
|
|
943
|
-
"
|
|
1004
|
+
"Step 2 of the upload handshake. After PUTting the bytes to the signed uploadUrl, call this " +
|
|
1005
|
+
"with the storagePath to validate and register. The server re-downloads the object and " +
|
|
1006
|
+
"enforces size + content-type (magic-byte sniff, never the declared type) + an optional " +
|
|
1007
|
+
'sha256 integrity check, deleting and failing on any mismatch. `target: "card_attachment"` ' +
|
|
1008
|
+
'(cardId, storagePath, fileName) or `target: "artifact"` (storagePath + the same one of ' +
|
|
1009
|
+
"cardId/planId/workspaceId used for the upload URL). Returns the attachment/artifact + a signed URL.",
|
|
944
1010
|
inputSchema: {
|
|
945
1011
|
type: "object",
|
|
946
1012
|
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: {
|
|
1013
|
+
target: {
|
|
954
1014
|
type: "string",
|
|
1015
|
+
enum: ["card_attachment", "artifact"],
|
|
955
1016
|
description:
|
|
956
|
-
"
|
|
1017
|
+
"What to finalize: a card file attachment or a hosted HTML artifact.",
|
|
957
1018
|
},
|
|
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: {
|
|
1019
|
+
storagePath: {
|
|
964
1020
|
type: "string",
|
|
965
1021
|
description:
|
|
966
|
-
"
|
|
1022
|
+
"The storagePath returned by harmony_request_upload_url.",
|
|
967
1023
|
},
|
|
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: {
|
|
1024
|
+
cardId: {
|
|
979
1025
|
type: "string",
|
|
980
1026
|
description:
|
|
981
|
-
"
|
|
1027
|
+
"Card UUID. Required for card_attachment; scope for artifact.",
|
|
982
1028
|
},
|
|
983
|
-
|
|
984
|
-
planId: { type: "string", description: "Link to this plan (UUID)." },
|
|
1029
|
+
planId: { type: "string", description: "Plan UUID (artifact only)." },
|
|
985
1030
|
workspaceId: {
|
|
986
1031
|
type: "string",
|
|
987
|
-
description:
|
|
988
|
-
"Attach to this workspace as a standalone artifact (UUID).",
|
|
989
|
-
},
|
|
990
|
-
contentType: {
|
|
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).",
|
|
1032
|
+
description: "Workspace UUID (artifact only).",
|
|
997
1033
|
},
|
|
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: {
|
|
1034
|
+
fileName: {
|
|
1009
1035
|
type: "string",
|
|
1010
1036
|
description:
|
|
1011
|
-
"
|
|
1037
|
+
"File name including extension (required for card_attachment).",
|
|
1012
1038
|
},
|
|
1013
|
-
title: { type: "string", description: "
|
|
1014
|
-
|
|
1015
|
-
planId: { type: "string", description: "Link to this plan (UUID)." },
|
|
1016
|
-
workspaceId: {
|
|
1039
|
+
title: { type: "string", description: "Artifact display title." },
|
|
1040
|
+
fileType: {
|
|
1017
1041
|
type: "string",
|
|
1018
1042
|
description:
|
|
1019
|
-
"
|
|
1043
|
+
"card_attachment MIME type; inferred from the extension when omitted.",
|
|
1020
1044
|
},
|
|
1021
1045
|
sha256: {
|
|
1022
1046
|
type: "string",
|
|
@@ -1029,7 +1053,18 @@ export const TOOLS = {
|
|
|
1029
1053
|
"Optional byte size (advisory; re-validated server-side).",
|
|
1030
1054
|
},
|
|
1031
1055
|
},
|
|
1032
|
-
required: ["storagePath"],
|
|
1056
|
+
required: ["target", "storagePath"],
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
harmony_classify_card: {
|
|
1060
|
+
description:
|
|
1061
|
+
"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`.",
|
|
1062
|
+
inputSchema: {
|
|
1063
|
+
type: "object",
|
|
1064
|
+
properties: {
|
|
1065
|
+
cardId: { type: "string", description: "Card UUID" },
|
|
1066
|
+
},
|
|
1067
|
+
required: ["cardId"],
|
|
1033
1068
|
},
|
|
1034
1069
|
},
|
|
1035
1070
|
harmony_share_artifact: {
|
|
@@ -1062,6 +1097,28 @@ export const TOOLS = {
|
|
|
1062
1097
|
required: ["cardId"],
|
|
1063
1098
|
},
|
|
1064
1099
|
},
|
|
1100
|
+
harmony_add_external_link: {
|
|
1101
|
+
description:
|
|
1102
|
+
"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.",
|
|
1103
|
+
inputSchema: {
|
|
1104
|
+
type: "object",
|
|
1105
|
+
properties: {
|
|
1106
|
+
cardId: {
|
|
1107
|
+
type: "string",
|
|
1108
|
+
description: "Card UUID",
|
|
1109
|
+
},
|
|
1110
|
+
url: {
|
|
1111
|
+
type: "string",
|
|
1112
|
+
description: "The URL to attach",
|
|
1113
|
+
},
|
|
1114
|
+
title: {
|
|
1115
|
+
type: "string",
|
|
1116
|
+
description: "Optional human-readable title for the link",
|
|
1117
|
+
},
|
|
1118
|
+
},
|
|
1119
|
+
required: ["cardId", "url"],
|
|
1120
|
+
},
|
|
1121
|
+
},
|
|
1065
1122
|
|
|
1066
1123
|
// Subtask operations
|
|
1067
1124
|
harmony_create_subtask: {
|
|
@@ -1119,7 +1176,7 @@ export const TOOLS = {
|
|
|
1119
1176
|
// Comment operations
|
|
1120
1177
|
harmony_add_comment: {
|
|
1121
1178
|
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.",
|
|
1179
|
+
"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
1180
|
inputSchema: {
|
|
1124
1181
|
type: "object",
|
|
1125
1182
|
properties: {
|
|
@@ -1147,6 +1204,11 @@ export const TOOLS = {
|
|
|
1147
1204
|
type: "string",
|
|
1148
1205
|
description: "Comment id this comment reaffirms",
|
|
1149
1206
|
},
|
|
1207
|
+
replyToId: {
|
|
1208
|
+
type: "string",
|
|
1209
|
+
description:
|
|
1210
|
+
"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.",
|
|
1211
|
+
},
|
|
1150
1212
|
},
|
|
1151
1213
|
required: ["cardId", "body"],
|
|
1152
1214
|
},
|
|
@@ -2101,7 +2163,7 @@ export const TOOLS = {
|
|
|
2101
2163
|
|
|
2102
2164
|
harmony_list_playbook: {
|
|
2103
2165
|
description:
|
|
2104
|
-
"List a workspace's playbooks (reusable process definitions). Returns each playbook's name, version,
|
|
2166
|
+
"List a workspace's playbooks (reusable process definitions). Returns each playbook's name, version, and state. Read-only.",
|
|
2105
2167
|
inputSchema: {
|
|
2106
2168
|
type: "object",
|
|
2107
2169
|
properties: {
|
|
@@ -2126,24 +2188,9 @@ export const TOOLS = {
|
|
|
2126
2188
|
},
|
|
2127
2189
|
},
|
|
2128
2190
|
|
|
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
2191
|
harmony_create_playbook: {
|
|
2145
2192
|
description:
|
|
2146
|
-
"Create a new playbook in a workspace.
|
|
2193
|
+
"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
2194
|
inputSchema: {
|
|
2148
2195
|
type: "object",
|
|
2149
2196
|
properties: {
|
|
@@ -2153,16 +2200,9 @@ export const TOOLS = {
|
|
|
2153
2200
|
},
|
|
2154
2201
|
name: { type: "string", description: "Playbook name" },
|
|
2155
2202
|
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
2203
|
steps: {
|
|
2163
2204
|
type: "array",
|
|
2164
|
-
description:
|
|
2165
|
-
"Steps (steps_version 1: tool-step objects) or stages (steps_version 2: stage objects).",
|
|
2205
|
+
description: "The playbook's ordered stage objects.",
|
|
2166
2206
|
items: { type: "object" },
|
|
2167
2207
|
},
|
|
2168
2208
|
},
|
|
@@ -2184,7 +2224,7 @@ export const TOOLS = {
|
|
|
2184
2224
|
description: { type: "string", description: "New description" },
|
|
2185
2225
|
steps: {
|
|
2186
2226
|
type: "array",
|
|
2187
|
-
description: "New
|
|
2227
|
+
description: "New ordered stage objects.",
|
|
2188
2228
|
items: { type: "object" },
|
|
2189
2229
|
},
|
|
2190
2230
|
enabled: {
|
|
@@ -2201,22 +2241,6 @@ export const TOOLS = {
|
|
|
2201
2241
|
},
|
|
2202
2242
|
},
|
|
2203
2243
|
|
|
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
2244
|
// ============ ONBOARDING TOOLS ============
|
|
2221
2245
|
harmony_signup: {
|
|
2222
2246
|
description:
|
|
@@ -2807,30 +2831,41 @@ async function handleToolCall(
|
|
|
2807
2831
|
return { success: true, ...result, count: result.cards.length };
|
|
2808
2832
|
}
|
|
2809
2833
|
|
|
2834
|
+
// Unified card fetch (#605): cardId (full) | shortId (full) | shortIds (compact).
|
|
2835
|
+
// Old names dispatch here too, behind the deprecation shim.
|
|
2836
|
+
case "harmony_get_card_by_short_id":
|
|
2837
|
+
case "harmony_bulk_get_cards":
|
|
2810
2838
|
case "harmony_get_card": {
|
|
2839
|
+
if (name !== "harmony_get_card") warnDeprecatedTool(name);
|
|
2840
|
+
const hasShortIds = args.shortIds != null;
|
|
2841
|
+
const hasShortId = args.shortId != null;
|
|
2842
|
+
const hasCardId = args.cardId != null;
|
|
2843
|
+
if ([hasShortIds, hasShortId, hasCardId].filter(Boolean).length !== 1) {
|
|
2844
|
+
throw new Error(
|
|
2845
|
+
"Provide exactly one of: cardId (UUID), shortId (number), or shortIds (number[]).",
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
if (hasShortIds) {
|
|
2849
|
+
const shortIds = z
|
|
2850
|
+
.array(z.number().int().positive())
|
|
2851
|
+
.min(1)
|
|
2852
|
+
.max(100)
|
|
2853
|
+
.parse(args.shortIds);
|
|
2854
|
+
const projectId = getProjectId();
|
|
2855
|
+
const result = await client.bulkGetCards(projectId, shortIds);
|
|
2856
|
+
return { success: true, ...result };
|
|
2857
|
+
}
|
|
2858
|
+
if (hasShortId) {
|
|
2859
|
+
const shortId = z.number().int().positive().parse(args.shortId);
|
|
2860
|
+
const projectId = (args.projectId as string) || getProjectId();
|
|
2861
|
+
const result = await client.getCardByShortId(projectId, shortId);
|
|
2862
|
+
return { success: true, ...result };
|
|
2863
|
+
}
|
|
2811
2864
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
2812
2865
|
const result = await client.getCard(cardId);
|
|
2813
2866
|
return { success: true, ...result };
|
|
2814
2867
|
}
|
|
2815
2868
|
|
|
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
2869
|
case "harmony_bulk_archive_cards": {
|
|
2835
2870
|
const shortIds = z
|
|
2836
2871
|
.array(z.number().int().positive())
|
|
@@ -2978,71 +3013,85 @@ async function handleToolCall(
|
|
|
2978
3013
|
return result;
|
|
2979
3014
|
}
|
|
2980
3015
|
|
|
2981
|
-
|
|
2982
|
-
|
|
3016
|
+
// Unified upload (#605): target = card_attachment | artifact. Old names
|
|
3017
|
+
// (harmony_upload_artifact / harmony_upload_card_attachment) dispatch here
|
|
3018
|
+
// behind the deprecation shim; target is inferred from the old name.
|
|
3019
|
+
case "harmony_upload_card_attachment":
|
|
3020
|
+
case "harmony_upload_artifact":
|
|
3021
|
+
case "harmony_upload": {
|
|
3022
|
+
if (name !== "harmony_upload") warnDeprecatedTool(name);
|
|
3023
|
+
const target =
|
|
3024
|
+
name === "harmony_upload_card_attachment"
|
|
3025
|
+
? "card_attachment"
|
|
3026
|
+
: name === "harmony_upload_artifact"
|
|
3027
|
+
? "artifact"
|
|
3028
|
+
: z.enum(["card_attachment", "artifact"]).parse(args.target);
|
|
3029
|
+
|
|
2983
3030
|
const filePath =
|
|
2984
3031
|
args.filePath != null ? z.string().parse(args.filePath) : undefined;
|
|
2985
3032
|
const base64Data =
|
|
2986
3033
|
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
3034
|
if (filePath && base64Data) {
|
|
2995
3035
|
throw new Error("Provide either filePath or base64Data, not both.");
|
|
2996
3036
|
}
|
|
2997
3037
|
|
|
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.`,
|
|
3038
|
+
if (target === "card_attachment") {
|
|
3039
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
3040
|
+
const fileName =
|
|
3041
|
+
args.fileName != null ? z.string().parse(args.fileName) : undefined;
|
|
3042
|
+
const contentType =
|
|
3043
|
+
args.contentType != null
|
|
3044
|
+
? z.string().parse(args.contentType)
|
|
3045
|
+
: undefined;
|
|
3046
|
+
|
|
3047
|
+
if (filePath) {
|
|
3048
|
+
// Server can read the file → upload direct-to-storage via the
|
|
3049
|
+
// handshake (no base64 through the model context or edge-fn body).
|
|
3050
|
+
const bytes = await readFileForUpload(
|
|
3051
|
+
filePath,
|
|
3052
|
+
MAX_ATTACHMENT_SIZE,
|
|
3053
|
+
"attachment",
|
|
3054
|
+
);
|
|
3055
|
+
const resolvedName = fileName || basename(filePath);
|
|
3056
|
+
const signed = await client.requestCardAttachmentUploadUrl(cardId, {
|
|
3057
|
+
fileName: resolvedName,
|
|
3058
|
+
fileType: contentType,
|
|
3059
|
+
size: bytes.byteLength,
|
|
3060
|
+
});
|
|
3061
|
+
await putToSignedUrl(
|
|
3062
|
+
signed.uploadUrl,
|
|
3063
|
+
bytes,
|
|
3064
|
+
contentType || signed.fileType || "application/octet-stream",
|
|
3033
3065
|
);
|
|
3066
|
+
return await client.finalizeCardAttachment(cardId, {
|
|
3067
|
+
storagePath: signed.storagePath,
|
|
3068
|
+
fileName: resolvedName,
|
|
3069
|
+
fileType: contentType || signed.fileType,
|
|
3070
|
+
sha256: sha256Hex(bytes),
|
|
3071
|
+
size: bytes.byteLength,
|
|
3072
|
+
});
|
|
3034
3073
|
}
|
|
3035
|
-
return await client.uploadCardAttachment(cardId, {
|
|
3036
|
-
fileName,
|
|
3037
|
-
data: base64Data,
|
|
3038
|
-
fileType: contentType,
|
|
3039
|
-
});
|
|
3040
|
-
}
|
|
3041
3074
|
|
|
3042
|
-
|
|
3043
|
-
|
|
3075
|
+
if (base64Data) {
|
|
3076
|
+
if (!fileName) {
|
|
3077
|
+
throw new Error("fileName is required when using base64Data.");
|
|
3078
|
+
}
|
|
3079
|
+
if (base64ByteLength(base64Data) > MAX_ATTACHMENT_SIZE) {
|
|
3080
|
+
throw new Error(
|
|
3081
|
+
`File is over the 5MB attachment limit. Use the harmony_request_upload_url + harmony_finalize_upload handshake for large files.`,
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
return await client.uploadCardAttachment(cardId, {
|
|
3085
|
+
fileName,
|
|
3086
|
+
data: base64Data,
|
|
3087
|
+
fileType: contentType,
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
throw new Error("Provide either filePath or base64Data.");
|
|
3092
|
+
}
|
|
3044
3093
|
|
|
3045
|
-
|
|
3094
|
+
// target === "artifact"
|
|
3046
3095
|
const title =
|
|
3047
3096
|
args.title != null ? z.string().parse(args.title) : undefined;
|
|
3048
3097
|
const cardId =
|
|
@@ -3055,17 +3104,7 @@ async function handleToolCall(
|
|
|
3055
3104
|
: undefined;
|
|
3056
3105
|
requireExactlyOneScope({ cardId, planId, workspaceId });
|
|
3057
3106
|
|
|
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
3107
|
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
3108
|
const bytes = await readFileForUpload(
|
|
3070
3109
|
filePath,
|
|
3071
3110
|
MAX_ARTIFACT_SIZE,
|
|
@@ -3095,7 +3134,7 @@ async function handleToolCall(
|
|
|
3095
3134
|
if (base64Data) {
|
|
3096
3135
|
if (base64ByteLength(base64Data) > MAX_ARTIFACT_SIZE) {
|
|
3097
3136
|
throw new Error(
|
|
3098
|
-
`Artifact is over the 2MB limit. Use the
|
|
3137
|
+
`Artifact is over the 2MB limit. Use the harmony_request_upload_url + harmony_finalize_upload handshake for large files.`,
|
|
3099
3138
|
);
|
|
3100
3139
|
}
|
|
3101
3140
|
return await client.uploadArtifact({
|
|
@@ -3110,7 +3149,37 @@ async function handleToolCall(
|
|
|
3110
3149
|
throw new Error("Provide either filePath or base64Data.");
|
|
3111
3150
|
}
|
|
3112
3151
|
|
|
3113
|
-
|
|
3152
|
+
// Unified upload handshake step 1 (#605). Old names shimmed; target inferred.
|
|
3153
|
+
case "harmony_request_artifact_upload_url":
|
|
3154
|
+
case "harmony_request_card_attachment_upload_url":
|
|
3155
|
+
case "harmony_request_upload_url": {
|
|
3156
|
+
if (name !== "harmony_request_upload_url") warnDeprecatedTool(name);
|
|
3157
|
+
const target =
|
|
3158
|
+
name === "harmony_request_card_attachment_upload_url"
|
|
3159
|
+
? "card_attachment"
|
|
3160
|
+
: name === "harmony_request_artifact_upload_url"
|
|
3161
|
+
? "artifact"
|
|
3162
|
+
: z.enum(["card_attachment", "artifact"]).parse(args.target);
|
|
3163
|
+
|
|
3164
|
+
if (target === "card_attachment") {
|
|
3165
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
3166
|
+
const fileName = z.string().parse(args.fileName);
|
|
3167
|
+
const fileType =
|
|
3168
|
+
args.fileType != null ? z.string().parse(args.fileType) : undefined;
|
|
3169
|
+
const size = z.number().positive().parse(args.size);
|
|
3170
|
+
if (size > MAX_ATTACHMENT_SIZE) {
|
|
3171
|
+
throw new Error(
|
|
3172
|
+
`Declared size ${size} bytes is over the ${MAX_ATTACHMENT_SIZE}-byte (5MB) attachment limit.`,
|
|
3173
|
+
);
|
|
3174
|
+
}
|
|
3175
|
+
return await client.requestCardAttachmentUploadUrl(cardId, {
|
|
3176
|
+
fileName,
|
|
3177
|
+
fileType,
|
|
3178
|
+
size,
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
// target === "artifact"
|
|
3114
3183
|
const title =
|
|
3115
3184
|
args.title != null ? z.string().parse(args.title) : undefined;
|
|
3116
3185
|
const cardId =
|
|
@@ -3143,8 +3212,40 @@ async function handleToolCall(
|
|
|
3143
3212
|
});
|
|
3144
3213
|
}
|
|
3145
3214
|
|
|
3146
|
-
|
|
3215
|
+
// Unified upload handshake step 2 (#605). Old names shimmed; target inferred.
|
|
3216
|
+
case "harmony_finalize_artifact":
|
|
3217
|
+
case "harmony_finalize_card_attachment":
|
|
3218
|
+
case "harmony_finalize_upload": {
|
|
3219
|
+
if (name !== "harmony_finalize_upload") warnDeprecatedTool(name);
|
|
3220
|
+
const target =
|
|
3221
|
+
name === "harmony_finalize_card_attachment"
|
|
3222
|
+
? "card_attachment"
|
|
3223
|
+
: name === "harmony_finalize_artifact"
|
|
3224
|
+
? "artifact"
|
|
3225
|
+
: z.enum(["card_attachment", "artifact"]).parse(args.target);
|
|
3147
3226
|
const storagePath = z.string().parse(args.storagePath);
|
|
3227
|
+
|
|
3228
|
+
if (target === "card_attachment") {
|
|
3229
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
3230
|
+
const fileName = z.string().parse(args.fileName);
|
|
3231
|
+
const fileType =
|
|
3232
|
+
args.fileType != null ? z.string().parse(args.fileType) : undefined;
|
|
3233
|
+
const sha256 =
|
|
3234
|
+
args.sha256 != null ? z.string().parse(args.sha256) : undefined;
|
|
3235
|
+
const size =
|
|
3236
|
+
args.size != null
|
|
3237
|
+
? z.number().positive().parse(args.size)
|
|
3238
|
+
: undefined;
|
|
3239
|
+
return await client.finalizeCardAttachment(cardId, {
|
|
3240
|
+
storagePath,
|
|
3241
|
+
fileName,
|
|
3242
|
+
fileType,
|
|
3243
|
+
sha256,
|
|
3244
|
+
size,
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
// target === "artifact"
|
|
3148
3249
|
const title =
|
|
3149
3250
|
args.title != null ? z.string().parse(args.title) : undefined;
|
|
3150
3251
|
const cardId =
|
|
@@ -3171,43 +3272,6 @@ async function handleToolCall(
|
|
|
3171
3272
|
});
|
|
3172
3273
|
}
|
|
3173
3274
|
|
|
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
3275
|
case "harmony_share_artifact": {
|
|
3212
3276
|
const artifactId = z.string().uuid().parse(args.artifactId);
|
|
3213
3277
|
const expiresInDays =
|
|
@@ -3230,6 +3294,16 @@ async function handleToolCall(
|
|
|
3230
3294
|
return result;
|
|
3231
3295
|
}
|
|
3232
3296
|
|
|
3297
|
+
case "harmony_add_external_link": {
|
|
3298
|
+
const cardId = z.string().uuid().parse(args.cardId);
|
|
3299
|
+
const url = z.string().min(1).max(2048).parse(args.url);
|
|
3300
|
+
const title = args.title
|
|
3301
|
+
? z.string().max(200).parse(args.title)
|
|
3302
|
+
: undefined;
|
|
3303
|
+
const result = await client.addExternalLink(cardId, url, title);
|
|
3304
|
+
return { success: true, ...result };
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3233
3307
|
case "harmony_classify_card": {
|
|
3234
3308
|
const cardId = z.string().uuid().parse(args.cardId);
|
|
3235
3309
|
const result = await client.classifyCard(cardId);
|
|
@@ -3307,10 +3381,15 @@ async function handleToolCall(
|
|
|
3307
3381
|
args.confirmsId !== undefined
|
|
3308
3382
|
? z.string().uuid().parse(args.confirmsId)
|
|
3309
3383
|
: undefined;
|
|
3384
|
+
const replyToId =
|
|
3385
|
+
args.replyToId !== undefined
|
|
3386
|
+
? z.string().uuid().parse(args.replyToId)
|
|
3387
|
+
: undefined;
|
|
3310
3388
|
const result = await client.addComment(cardId, body, {
|
|
3311
3389
|
commentType,
|
|
3312
3390
|
supersedesId,
|
|
3313
3391
|
confirmsId,
|
|
3392
|
+
replyToId,
|
|
3314
3393
|
});
|
|
3315
3394
|
return { success: true, ...result };
|
|
3316
3395
|
}
|
|
@@ -3606,15 +3685,24 @@ async function handleToolCall(
|
|
|
3606
3685
|
.map((a) => a.description)
|
|
3607
3686
|
.filter((d): d is string => typeof d === "string" && d.length > 0);
|
|
3608
3687
|
|
|
3688
|
+
const reportedStatus = args.status as
|
|
3689
|
+
| "working"
|
|
3690
|
+
| "blocked"
|
|
3691
|
+
| "waiting"
|
|
3692
|
+
| "paused"
|
|
3693
|
+
| undefined;
|
|
3694
|
+
|
|
3695
|
+
// Keep the auto-session heartbeat in step with the agent's own status
|
|
3696
|
+
// reports: `paused`/`blocked`/`waiting` stop the 60s heartbeat (intentional
|
|
3697
|
+
// non-heartbeat states), a later `working` resumes it (card #608).
|
|
3698
|
+
if (reportedStatus) {
|
|
3699
|
+
noteSessionStatus(cardId, reportedStatus, deps.getScopeId?.());
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3609
3702
|
const result = await client.updateAgentProgress(cardId, {
|
|
3610
3703
|
agentIdentifier,
|
|
3611
3704
|
agentName,
|
|
3612
|
-
status:
|
|
3613
|
-
| "working"
|
|
3614
|
-
| "blocked"
|
|
3615
|
-
| "waiting"
|
|
3616
|
-
| "paused"
|
|
3617
|
-
| undefined,
|
|
3705
|
+
status: reportedStatus,
|
|
3618
3706
|
progressPercent,
|
|
3619
3707
|
currentTask: args.currentTask as string | undefined,
|
|
3620
3708
|
blockers: args.blockers as string[] | undefined,
|
|
@@ -4717,25 +4805,19 @@ async function handleToolCall(
|
|
|
4717
4805
|
return { success: true, playbook: result.playbook, runs: result.runs };
|
|
4718
4806
|
}
|
|
4719
4807
|
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
return
|
|
4724
|
-
}
|
|
4808
|
+
// Deprecated (#612) — dropped from the advertised TOOLS list; the case
|
|
4809
|
+
// remains so an old caller gets a legible notice, not an unknown-tool error.
|
|
4810
|
+
case "harmony_run_playbook":
|
|
4811
|
+
return deprecatedRemovedToolResult("harmony_run_playbook");
|
|
4725
4812
|
|
|
4726
4813
|
case "harmony_create_playbook": {
|
|
4727
4814
|
const workspaceId = (args.workspaceId as string) || getWorkspaceId();
|
|
4728
4815
|
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
4816
|
const result = await client.createPlaybook({
|
|
4734
4817
|
workspaceId,
|
|
4735
4818
|
name,
|
|
4736
4819
|
description: args.description as string | undefined,
|
|
4737
4820
|
steps: args.steps,
|
|
4738
|
-
stepsVersion,
|
|
4739
4821
|
});
|
|
4740
4822
|
return { success: true, playbook: result.playbook };
|
|
4741
4823
|
}
|
|
@@ -4752,14 +4834,9 @@ async function handleToolCall(
|
|
|
4752
4834
|
return { success: true, playbook: result.playbook };
|
|
4753
4835
|
}
|
|
4754
4836
|
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
cardId,
|
|
4759
|
-
name: args.name as string | undefined,
|
|
4760
|
-
});
|
|
4761
|
-
return { success: true, playbook: result.playbook };
|
|
4762
|
-
}
|
|
4837
|
+
// Deprecated (#612) — see harmony_run_playbook above.
|
|
4838
|
+
case "harmony_save_card_as_playbook":
|
|
4839
|
+
return deprecatedRemovedToolResult("harmony_save_card_as_playbook");
|
|
4763
4840
|
|
|
4764
4841
|
// ============ ONBOARDING TOOLS ============
|
|
4765
4842
|
case "harmony_signup": {
|