wolfpack-mcp 1.0.59 → 1.0.61
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/agentBuilderTools.js +14 -11
- package/dist/agentSelfTools.js +1 -1
- package/dist/client.js +22 -2
- package/dist/index.js +91 -33
- package/dist/procedureTools.js +119 -8
- package/dist/resolveRadarItemId.js +22 -0
- package/dist/resolveRadarItemId.test.js +31 -0
- package/package.json +1 -1
- package/dist/imageProcessor.js +0 -21
|
@@ -37,16 +37,19 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
name: 'create_agent',
|
|
40
|
-
description: 'Create a new agent from a
|
|
40
|
+
description: 'Create a new agent from a container image. Use list_container_images to see available images.',
|
|
41
41
|
inputSchema: {
|
|
42
42
|
type: 'object',
|
|
43
43
|
properties: {
|
|
44
|
-
|
|
44
|
+
container_image_id: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Container image ID to create the agent from',
|
|
47
|
+
},
|
|
45
48
|
name: { type: 'string', description: 'Display name for the agent' },
|
|
46
49
|
config: { type: 'object', description: 'Optional agent-specific configuration' },
|
|
47
50
|
...ORG_SLUG_PROP,
|
|
48
51
|
},
|
|
49
|
-
required: ['
|
|
52
|
+
required: ['container_image_id', 'name'],
|
|
50
53
|
},
|
|
51
54
|
},
|
|
52
55
|
{
|
|
@@ -71,7 +74,7 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
71
74
|
},
|
|
72
75
|
instructions: {
|
|
73
76
|
type: 'string',
|
|
74
|
-
description: 'Agent-specific instructions
|
|
77
|
+
description: 'Agent-specific instructions appended to the system prompt. Set to null to clear.',
|
|
75
78
|
},
|
|
76
79
|
llm_provider: {
|
|
77
80
|
type: 'string',
|
|
@@ -682,8 +685,8 @@ export const AGENT_BUILDER_TOOLS = [
|
|
|
682
685
|
},
|
|
683
686
|
// ─── Group 7: Discovery ───────────────────────────────────────────────────
|
|
684
687
|
{
|
|
685
|
-
name: '
|
|
686
|
-
description: 'List agent
|
|
688
|
+
name: 'list_container_images',
|
|
689
|
+
description: 'List agent-capable container images assigned to the organisation. Use image IDs when calling create_agent.',
|
|
687
690
|
inputSchema: { type: 'object', properties: { ...ORG_SLUG_PROP } },
|
|
688
691
|
},
|
|
689
692
|
{
|
|
@@ -735,14 +738,14 @@ export async function handleAgentBuilderTool(name, args, client) {
|
|
|
735
738
|
case 'create_agent': {
|
|
736
739
|
const parsed = z
|
|
737
740
|
.object({
|
|
738
|
-
|
|
741
|
+
container_image_id: z.string(),
|
|
739
742
|
name: z.string(),
|
|
740
743
|
config: z.record(z.unknown()).optional(),
|
|
741
744
|
org_slug: orgSlugField,
|
|
742
745
|
})
|
|
743
746
|
.parse(args);
|
|
744
747
|
const agent = await client.createAgent({
|
|
745
|
-
|
|
748
|
+
containerImageId: parsed.container_image_id,
|
|
746
749
|
name: parsed.name,
|
|
747
750
|
config: parsed.config,
|
|
748
751
|
}, resolveOrg(parsed));
|
|
@@ -1250,10 +1253,10 @@ export async function handleAgentBuilderTool(name, args, client) {
|
|
|
1250
1253
|
return { content: [{ type: 'text', text: `Set secret "${secret.name}"` }] };
|
|
1251
1254
|
}
|
|
1252
1255
|
// ─── Discovery ────────────────────────────────────────────────────────────
|
|
1253
|
-
case '
|
|
1256
|
+
case 'list_container_images': {
|
|
1254
1257
|
const parsed = z.object({ org_slug: orgSlugField }).parse(args);
|
|
1255
|
-
const
|
|
1256
|
-
return { content: [{ type: 'text', text: text(
|
|
1258
|
+
const images = await client.listContainerImages(resolveOrg(parsed));
|
|
1259
|
+
return { content: [{ type: 'text', text: text(images) }] };
|
|
1257
1260
|
}
|
|
1258
1261
|
case 'list_llm_models': {
|
|
1259
1262
|
const parsed = z.object({ org_slug: orgSlugField }).parse(args);
|
package/dist/agentSelfTools.js
CHANGED
|
@@ -6,7 +6,7 @@ function text(data) {
|
|
|
6
6
|
export const AGENT_SELF_TOOLS = [
|
|
7
7
|
{
|
|
8
8
|
name: 'get_self',
|
|
9
|
-
description: 'Get your own agent profile with full composed context:
|
|
9
|
+
description: 'Get your own agent profile with full composed context: system prompt, instructions, skills, LLM model, and assigned projects. ' +
|
|
10
10
|
'Use this to understand how you are configured and what capabilities you have. ' +
|
|
11
11
|
'Essential for self-reflection and improvement proposals.',
|
|
12
12
|
inputSchema: { type: 'object', properties: {} },
|
package/dist/client.js
CHANGED
|
@@ -469,6 +469,14 @@ export class WolfpackClient {
|
|
|
469
469
|
const query = params.toString();
|
|
470
470
|
return this.api.get(`/categories${query ? `?${query}` : ''}`);
|
|
471
471
|
}
|
|
472
|
+
// Tag methods
|
|
473
|
+
async listTags(teamSlug) {
|
|
474
|
+
const params = new URLSearchParams();
|
|
475
|
+
if (teamSlug)
|
|
476
|
+
params.append('teamSlug', teamSlug);
|
|
477
|
+
const query = params.toString();
|
|
478
|
+
return this.api.get(`/tags${query ? `?${query}` : ''}`);
|
|
479
|
+
}
|
|
472
480
|
/**
|
|
473
481
|
* Upload an image and get back a URL that can be used in markdown content.
|
|
474
482
|
*/
|
|
@@ -483,6 +491,14 @@ export class WolfpackClient {
|
|
|
483
491
|
const { buffer, contentType } = await this.api.getBuffer(`/images/${team}/${filename}`);
|
|
484
492
|
return { base64: buffer.toString('base64'), mimeType: contentType };
|
|
485
493
|
}
|
|
494
|
+
/**
|
|
495
|
+
* Download a stored file (new /api/files/{fileId}/{filename} URL shape) and
|
|
496
|
+
* return raw base64 + mimeType.
|
|
497
|
+
*/
|
|
498
|
+
async downloadFile(fileId) {
|
|
499
|
+
const { buffer, contentType } = await this.api.getBuffer(`/files/${fileId}`);
|
|
500
|
+
return { base64: buffer.toString('base64'), mimeType: contentType };
|
|
501
|
+
}
|
|
486
502
|
/**
|
|
487
503
|
* Download an issue attachment image and return raw base64 + mimeType.
|
|
488
504
|
*/
|
|
@@ -767,8 +783,8 @@ export class WolfpackClient {
|
|
|
767
783
|
}
|
|
768
784
|
}
|
|
769
785
|
// ─── Agent Builder: Discovery ──────────────────────────────────────────────
|
|
770
|
-
async
|
|
771
|
-
return this.api.get(this.withOrgSlug('/agent-builder/
|
|
786
|
+
async listContainerImages(orgSlug) {
|
|
787
|
+
return this.api.get(this.withOrgSlug('/agent-builder/container-images', orgSlug));
|
|
772
788
|
}
|
|
773
789
|
async listLlmModels(orgSlug) {
|
|
774
790
|
return this.api.get(this.withOrgSlug('/agent-builder/llm-models', orgSlug));
|
|
@@ -826,6 +842,10 @@ export class WolfpackClient {
|
|
|
826
842
|
await this.api.delete(this.withTeamSlug(`/procedures/${procedureId}`, teamSlug));
|
|
827
843
|
return { success: true };
|
|
828
844
|
}
|
|
845
|
+
async startCase(procedureId, data) {
|
|
846
|
+
const { teamSlug, ...body } = data;
|
|
847
|
+
return this.api.post(this.withTeamSlug(`/procedures/${procedureId}/start`, teamSlug), body);
|
|
848
|
+
}
|
|
829
849
|
// ─── Agent Self-Introspection ──────────────────────────────────────────────
|
|
830
850
|
async getSelf() {
|
|
831
851
|
return this.api.get('/self');
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { validateConfig, config } from './config.js';
|
|
|
11
11
|
import { AGENT_BUILDER_TOOLS, handleAgentBuilderTool } from './agentBuilderTools.js';
|
|
12
12
|
import { PROCEDURE_TOOLS, handleProcedureTool } from './procedureTools.js';
|
|
13
13
|
import { AGENT_SELF_TOOLS, handleAgentSelfTool } from './agentSelfTools.js';
|
|
14
|
+
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
14
15
|
// Get current package version
|
|
15
16
|
const require = createRequire(import.meta.url);
|
|
16
17
|
const packageJson = require('../package.json');
|
|
@@ -76,7 +77,10 @@ const ListWorkItemsSchema = z.object({
|
|
|
76
77
|
.string()
|
|
77
78
|
.optional()
|
|
78
79
|
.describe('Filter by priority level (0-4, higher is more important)'),
|
|
79
|
-
radar_item_id: z.coerce
|
|
80
|
+
radar_item_id: z.coerce
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe('Filter by linked radar/initiative item. Accepts refId ("5" or "#r5"), UUID, or "none"/"any"/"all".'),
|
|
80
84
|
created_by_id: z.coerce.string().optional().describe('Filter by creator user ID'),
|
|
81
85
|
updated_by_id: z.coerce.string().optional().describe('Filter by last updater user ID'),
|
|
82
86
|
sort_by: z.coerce
|
|
@@ -135,7 +139,7 @@ const UpdateWorkItemSchema = z.object({
|
|
|
135
139
|
.string()
|
|
136
140
|
.nullable()
|
|
137
141
|
.optional()
|
|
138
|
-
.describe('
|
|
142
|
+
.describe('Link to a radar/initiative item. Accepts refId ("5" or "#r5") or UUID. Use null to unlink.'),
|
|
139
143
|
category_id: z
|
|
140
144
|
.string()
|
|
141
145
|
.nullable()
|
|
@@ -162,7 +166,10 @@ const UpdateWorkItemSchema = z.object({
|
|
|
162
166
|
.nullable()
|
|
163
167
|
.optional()
|
|
164
168
|
.describe('User ID for third assisting user, or null to remove'),
|
|
165
|
-
|
|
169
|
+
tags: z
|
|
170
|
+
.array(z.string())
|
|
171
|
+
.optional()
|
|
172
|
+
.describe('Tag names to apply (replaces existing tags). Use list_tags to see available tags.'),
|
|
166
173
|
closing_comment: z
|
|
167
174
|
.string()
|
|
168
175
|
.nullable()
|
|
@@ -288,7 +295,10 @@ const CreateWorkItemSchema = z.object({
|
|
|
288
295
|
.describe('Size estimate: "S" (small), "M" (medium), "L" (large)'),
|
|
289
296
|
leading_user_id: z.string().optional().describe('User ID to assign as leading user'),
|
|
290
297
|
category_id: z.string().optional().describe('Category ID to organize the work item'),
|
|
291
|
-
radar_item_id: z
|
|
298
|
+
radar_item_id: z
|
|
299
|
+
.string()
|
|
300
|
+
.optional()
|
|
301
|
+
.describe('Link to a radar/initiative item. Accepts refId ("5" or "#r5") or UUID.'),
|
|
292
302
|
});
|
|
293
303
|
const CreateIssueSchema = z.object({
|
|
294
304
|
project_slug: z
|
|
@@ -481,7 +491,7 @@ const UploadImageSchema = z.object({
|
|
|
481
491
|
const DownloadImageSchema = z.object({
|
|
482
492
|
image_url: z
|
|
483
493
|
.string()
|
|
484
|
-
.describe('The image URL path found in content fields (e.g. "/api/files/images/{team}/{filename}").'),
|
|
494
|
+
.describe('The image URL path found in content fields (e.g. "/api/files/{fileId}/{filename}" or legacy "/api/files/images/{team}/{filename}").'),
|
|
485
495
|
});
|
|
486
496
|
// Skill schemas
|
|
487
497
|
const GetSkillSchema = z.object({
|
|
@@ -498,6 +508,13 @@ const ListCategoriesSchema = z.object({
|
|
|
498
508
|
.optional()
|
|
499
509
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
500
510
|
});
|
|
511
|
+
// Tag schemas
|
|
512
|
+
const ListTagsSchema = z.object({
|
|
513
|
+
project_slug: z
|
|
514
|
+
.string()
|
|
515
|
+
.optional()
|
|
516
|
+
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
517
|
+
});
|
|
501
518
|
// Team member schemas
|
|
502
519
|
const ListTeamMembersSchema = z.object({
|
|
503
520
|
project_slug: z
|
|
@@ -708,7 +725,8 @@ class WolfpackMCPServer {
|
|
|
708
725
|
},
|
|
709
726
|
radar_item_id: {
|
|
710
727
|
type: 'string',
|
|
711
|
-
description: 'Filter by linked radar/initiative item
|
|
728
|
+
description: 'Filter by linked radar/initiative item. Accepts refId (e.g. "5" or "#r5"), UUID, ' +
|
|
729
|
+
'or one of "none" (no link), "any" (has any link), "all" (no filter).',
|
|
712
730
|
},
|
|
713
731
|
sort_by: {
|
|
714
732
|
type: 'string',
|
|
@@ -740,7 +758,7 @@ class WolfpackMCPServer {
|
|
|
740
758
|
'(pending→pull first, new→doing, blocked/ready/completed/closed→new→doing, then review when done). ' +
|
|
741
759
|
'PLANNING: Check if the description contains a plan (markdown checklist). If not, APPEND one using update_work_progress - preserve all original description text and add your plan below a "---" separator. ' +
|
|
742
760
|
'FORMS: Procedure-created work items may include formDefinition (field definitions with name, label, type, required, options) and formValues (current values). Use submit_work_item_form to fill in form values. ' +
|
|
743
|
-
'IMAGES: Image references in the description (e.g. ``) can be viewed using the download_image tool.',
|
|
744
762
|
inputSchema: {
|
|
745
763
|
type: 'object',
|
|
746
764
|
properties: {
|
|
@@ -782,7 +800,7 @@ class WolfpackMCPServer {
|
|
|
782
800
|
name: 'update_work_item',
|
|
783
801
|
description: 'Update one or more fields on a work item. Only provide the fields you want to change. ' +
|
|
784
802
|
'Supports: title, description, status, leading_user_id (assignee), assisting_user1/2/3_id, ' +
|
|
785
|
-
'radar_item_id (initiative), category_id, priority (0-4), size (S/M/L),
|
|
803
|
+
'radar_item_id (initiative), category_id, priority (0-4), size (S/M/L), tags, and closing_comment. ' +
|
|
786
804
|
'STATUS WORKFLOW: "pending" (backlog) → "new" (to do) → "doing" (in progress) → "review" (work done) → "ready" (awaiting deployment) → "completed" (deployed). ' +
|
|
787
805
|
'Use "blocked" when work cannot proceed. For "pending" items use pull_work_item first. ' +
|
|
788
806
|
'When moving to "review", add a completion comment. When moving to "blocked", add a comment explaining the blocker.',
|
|
@@ -822,7 +840,7 @@ class WolfpackMCPServer {
|
|
|
822
840
|
},
|
|
823
841
|
radar_item_id: {
|
|
824
842
|
type: ['string', 'null'],
|
|
825
|
-
description: '
|
|
843
|
+
description: 'Link to a radar/initiative item. Accepts refId (e.g. "5" or "#r5") or UUID. Use null to unlink.',
|
|
826
844
|
},
|
|
827
845
|
category_id: {
|
|
828
846
|
type: ['string', 'null'],
|
|
@@ -849,10 +867,10 @@ class WolfpackMCPServer {
|
|
|
849
867
|
type: ['string', 'null'],
|
|
850
868
|
description: 'User ID for third assisting user, or null to remove',
|
|
851
869
|
},
|
|
852
|
-
|
|
870
|
+
tags: {
|
|
853
871
|
type: 'array',
|
|
854
872
|
items: { type: 'string' },
|
|
855
|
-
description: 'Tag
|
|
873
|
+
description: 'Tag names to apply (replaces existing tags). Use list_tags to see available tags.',
|
|
856
874
|
},
|
|
857
875
|
closing_comment: {
|
|
858
876
|
type: ['string', 'null'],
|
|
@@ -945,7 +963,7 @@ class WolfpackMCPServer {
|
|
|
945
963
|
{
|
|
946
964
|
name: 'get_radar_item',
|
|
947
965
|
description: 'Get a specific radar item (initiative) by reference number, including its work items and participants. ' +
|
|
948
|
-
'IMAGES: Image references in the description (e.g. ``) can be viewed using the download_image tool.',
|
|
949
967
|
inputSchema: {
|
|
950
968
|
type: 'object',
|
|
951
969
|
properties: {
|
|
@@ -1078,7 +1096,7 @@ class WolfpackMCPServer {
|
|
|
1078
1096
|
description: 'Get a specific issue by its PREFIXED reference #iN (e.g. #i5). ' +
|
|
1079
1097
|
'IMPORTANT: A bare #N (without prefix) is ALWAYS a work item, NOT an issue. Only use this tool when the user explicitly says "issue" or uses the #i prefix. ' +
|
|
1080
1098
|
'Returns full details including comment and attachment counts. ' +
|
|
1081
|
-
'IMAGES: Image references in the description (e.g. ``) can be viewed using the download_image tool.',
|
|
1082
1100
|
inputSchema: {
|
|
1083
1101
|
type: 'object',
|
|
1084
1102
|
properties: {
|
|
@@ -1145,7 +1163,7 @@ class WolfpackMCPServer {
|
|
|
1145
1163
|
},
|
|
1146
1164
|
radar_item_id: {
|
|
1147
1165
|
type: 'string',
|
|
1148
|
-
description: '
|
|
1166
|
+
description: 'Link to a radar/initiative item. Accepts refId (e.g. "5" or "#r5") or UUID.',
|
|
1149
1167
|
},
|
|
1150
1168
|
},
|
|
1151
1169
|
required: ['title'],
|
|
@@ -1263,7 +1281,7 @@ class WolfpackMCPServer {
|
|
|
1263
1281
|
{
|
|
1264
1282
|
name: 'get_wiki_page',
|
|
1265
1283
|
description: 'Get a specific wiki/documentation page by slug (URL path). Returns full content in markdown. ' +
|
|
1266
|
-
'IMAGES: Image references in the content (e.g. ``) can be viewed using the download_image tool.',
|
|
1267
1285
|
inputSchema: {
|
|
1268
1286
|
type: 'object',
|
|
1269
1287
|
properties: {
|
|
@@ -1315,7 +1333,7 @@ class WolfpackMCPServer {
|
|
|
1315
1333
|
tag_ids: {
|
|
1316
1334
|
type: 'array',
|
|
1317
1335
|
items: { type: 'string' },
|
|
1318
|
-
description: 'Tag IDs to apply to the page (replaces existing tags)
|
|
1336
|
+
description: 'Tag IDs to apply to the page (replaces existing tags)',
|
|
1319
1337
|
},
|
|
1320
1338
|
project_slug: {
|
|
1321
1339
|
type: 'string',
|
|
@@ -1356,7 +1374,7 @@ class WolfpackMCPServer {
|
|
|
1356
1374
|
{
|
|
1357
1375
|
name: 'get_journal_entry',
|
|
1358
1376
|
description: 'Get a specific journal/log entry by reference number. Returns full content. ' +
|
|
1359
|
-
'IMAGES: Image references in the content (e.g. ``) can be viewed using the download_image tool.',
|
|
1360
1378
|
inputSchema: {
|
|
1361
1379
|
type: 'object',
|
|
1362
1380
|
properties: {
|
|
@@ -1419,7 +1437,7 @@ class WolfpackMCPServer {
|
|
|
1419
1437
|
{
|
|
1420
1438
|
name: 'list_work_item_comments',
|
|
1421
1439
|
description: 'List all comments on a work item. Returns comments in chronological order. Requires mcp:comments:read permission. ' +
|
|
1422
|
-
'IMAGES: Image references in comments (e.g. ``) can be viewed using the download_image tool.',
|
|
1423
1441
|
inputSchema: {
|
|
1424
1442
|
type: 'object',
|
|
1425
1443
|
properties: {
|
|
@@ -1435,7 +1453,7 @@ class WolfpackMCPServer {
|
|
|
1435
1453
|
{
|
|
1436
1454
|
name: 'list_issue_comments',
|
|
1437
1455
|
description: 'List all comments on an issue. Returns comments in chronological order. Requires mcp:comments:read permission. ' +
|
|
1438
|
-
'IMAGES: Image references in comments (e.g. ``) can be viewed using the download_image tool.',
|
|
1439
1457
|
inputSchema: {
|
|
1440
1458
|
type: 'object',
|
|
1441
1459
|
properties: {
|
|
@@ -1555,7 +1573,7 @@ class WolfpackMCPServer {
|
|
|
1555
1573
|
{
|
|
1556
1574
|
name: 'download_image',
|
|
1557
1575
|
description: 'Download and view an image referenced in content fields. ' +
|
|
1558
|
-
'Content from work items, issues, wiki pages, journal entries, and comments may contain image references like ``. ' +
|
|
1576
|
+
'Content from work items, issues, wiki pages, journal entries, and comments may contain image references like `` or the legacy ``. ' +
|
|
1559
1577
|
'Issues may also have attachment images with URLs like `/api/teams/{team}/issues/{refId}/attachments/{id}`. ' +
|
|
1560
1578
|
'Use this tool with that URL path to download and view the image. ' +
|
|
1561
1579
|
'Requires mcp:images:read permission.',
|
|
@@ -1564,7 +1582,7 @@ class WolfpackMCPServer {
|
|
|
1564
1582
|
properties: {
|
|
1565
1583
|
image_url: {
|
|
1566
1584
|
type: 'string',
|
|
1567
|
-
description: 'The image URL path found in content fields (e.g. "/api/files/images/{team}/{filename}" or "/api/teams/{team}/issues/{refId}/attachments/{id}").',
|
|
1585
|
+
description: 'The image URL path found in content fields (e.g. "/api/files/{fileId}/{filename}", legacy "/api/files/images/{team}/{filename}", or "/api/teams/{team}/issues/{refId}/attachments/{id}").',
|
|
1568
1586
|
},
|
|
1569
1587
|
},
|
|
1570
1588
|
required: ['image_url'],
|
|
@@ -1585,6 +1603,21 @@ class WolfpackMCPServer {
|
|
|
1585
1603
|
},
|
|
1586
1604
|
},
|
|
1587
1605
|
},
|
|
1606
|
+
// Tag tools
|
|
1607
|
+
{
|
|
1608
|
+
name: 'list_tags',
|
|
1609
|
+
description: 'List all tags available in a project. Returns tag names and colours. ' +
|
|
1610
|
+
'Work items are tagged by name via update_work_item.',
|
|
1611
|
+
inputSchema: {
|
|
1612
|
+
type: 'object',
|
|
1613
|
+
properties: {
|
|
1614
|
+
project_slug: {
|
|
1615
|
+
type: 'string',
|
|
1616
|
+
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
1617
|
+
},
|
|
1618
|
+
},
|
|
1619
|
+
},
|
|
1620
|
+
},
|
|
1588
1621
|
// Team member tools
|
|
1589
1622
|
{
|
|
1590
1623
|
name: 'list_team_members',
|
|
@@ -1924,14 +1957,19 @@ class WolfpackMCPServer {
|
|
|
1924
1957
|
// Work Item handlers
|
|
1925
1958
|
case 'list_work_items': {
|
|
1926
1959
|
const parsed = ListWorkItemsSchema.parse(args);
|
|
1960
|
+
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
1961
|
+
const RADAR_FILTER_PASSTHROUGH = new Set(['none', 'any', 'all']);
|
|
1962
|
+
const radarItemIdFilter = parsed.radar_item_id && !RADAR_FILTER_PASSTHROUGH.has(parsed.radar_item_id)
|
|
1963
|
+
? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
|
|
1964
|
+
: parsed.radar_item_id;
|
|
1927
1965
|
const result = await this.client.listWorkItems({
|
|
1928
|
-
teamSlug
|
|
1966
|
+
teamSlug,
|
|
1929
1967
|
status: parsed.status,
|
|
1930
1968
|
assignedToId: parsed.assigned_to_id,
|
|
1931
1969
|
search: parsed.search,
|
|
1932
1970
|
categoryId: parsed.category_id,
|
|
1933
1971
|
priority: parsed.priority,
|
|
1934
|
-
radarItemId:
|
|
1972
|
+
radarItemId: radarItemIdFilter,
|
|
1935
1973
|
createdById: parsed.created_by_id,
|
|
1936
1974
|
updatedById: parsed.updated_by_id,
|
|
1937
1975
|
sortBy: parsed.sort_by,
|
|
@@ -1999,8 +2037,11 @@ class WolfpackMCPServer {
|
|
|
1999
2037
|
fields.status = parsed.status;
|
|
2000
2038
|
if (parsed.leading_user_id !== undefined)
|
|
2001
2039
|
fields.leadingUserId = parsed.leading_user_id;
|
|
2002
|
-
if (parsed.radar_item_id !== undefined)
|
|
2003
|
-
fields.radarItemId = parsed.radar_item_id
|
|
2040
|
+
if (parsed.radar_item_id !== undefined) {
|
|
2041
|
+
fields.radarItemId = parsed.radar_item_id
|
|
2042
|
+
? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
|
|
2043
|
+
: null;
|
|
2044
|
+
}
|
|
2004
2045
|
if (parsed.category_id !== undefined)
|
|
2005
2046
|
fields.categoryId = parsed.category_id;
|
|
2006
2047
|
if (parsed.priority !== undefined)
|
|
@@ -2013,8 +2054,8 @@ class WolfpackMCPServer {
|
|
|
2013
2054
|
fields.assistingUser2Id = parsed.assisting_user2_id;
|
|
2014
2055
|
if (parsed.assisting_user3_id !== undefined)
|
|
2015
2056
|
fields.assistingUser3Id = parsed.assisting_user3_id;
|
|
2016
|
-
if (parsed.
|
|
2017
|
-
fields.
|
|
2057
|
+
if (parsed.tags !== undefined)
|
|
2058
|
+
fields.tags = parsed.tags;
|
|
2018
2059
|
if (parsed.closing_comment !== undefined)
|
|
2019
2060
|
fields.closingComment = parsed.closing_comment;
|
|
2020
2061
|
const workItem = await this.client.updateWorkItem(parsed.work_item_id, fields, teamSlug);
|
|
@@ -2061,7 +2102,7 @@ class WolfpackMCPServer {
|
|
|
2061
2102
|
changes.push(parsed.assisting_user3_id
|
|
2062
2103
|
? `assisting user 3 updated`
|
|
2063
2104
|
: 'assisting user 3 removed');
|
|
2064
|
-
if (parsed.
|
|
2105
|
+
if (parsed.tags !== undefined)
|
|
2065
2106
|
changes.push('tags updated');
|
|
2066
2107
|
if (parsed.closing_comment !== undefined)
|
|
2067
2108
|
changes.push('closing comment set');
|
|
@@ -2222,6 +2263,10 @@ class WolfpackMCPServer {
|
|
|
2222
2263
|
// Create/Update handlers
|
|
2223
2264
|
case 'create_work_item': {
|
|
2224
2265
|
const parsed = CreateWorkItemSchema.parse(args);
|
|
2266
|
+
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
2267
|
+
const radarItemId = parsed.radar_item_id
|
|
2268
|
+
? await resolveRadarItemId(this.client, parsed.radar_item_id, teamSlug)
|
|
2269
|
+
: undefined;
|
|
2225
2270
|
const workItem = await this.client.createWorkItem({
|
|
2226
2271
|
title: parsed.title,
|
|
2227
2272
|
description: parsed.description,
|
|
@@ -2230,8 +2275,8 @@ class WolfpackMCPServer {
|
|
|
2230
2275
|
size: parsed.size,
|
|
2231
2276
|
leadingUserId: parsed.leading_user_id,
|
|
2232
2277
|
categoryId: parsed.category_id,
|
|
2233
|
-
radarItemId
|
|
2234
|
-
teamSlug
|
|
2278
|
+
radarItemId,
|
|
2279
|
+
teamSlug,
|
|
2235
2280
|
});
|
|
2236
2281
|
return {
|
|
2237
2282
|
content: [
|
|
@@ -2537,13 +2582,18 @@ class WolfpackMCPServer {
|
|
|
2537
2582
|
}
|
|
2538
2583
|
case 'download_image': {
|
|
2539
2584
|
const parsed = DownloadImageSchema.parse(args);
|
|
2540
|
-
// Try
|
|
2585
|
+
// Try stored-file pattern (#1439): /api/files/{fileId}/{filename}
|
|
2586
|
+
const storedFileMatch = parsed.image_url.match(/\/api\/files\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:\/|$)/i);
|
|
2587
|
+
// Try legacy S3-stored image pattern: /api/files/images/{team}/{filename}
|
|
2541
2588
|
const s3Match = parsed.image_url.match(/\/api\/files\/images\/([^/]+)\/(.+)$/);
|
|
2542
2589
|
// Try issue attachment pattern: /api/teams/{team}/issues/{refId}/attachments/{id}
|
|
2543
2590
|
const attachmentMatch = parsed.image_url.match(/\/api\/teams\/([^/]+)\/issues\/(\d+)\/attachments\/([^/]+)$/);
|
|
2544
2591
|
let base64;
|
|
2545
2592
|
let mimeType;
|
|
2546
|
-
if (
|
|
2593
|
+
if (storedFileMatch) {
|
|
2594
|
+
({ base64, mimeType } = await this.client.downloadFile(storedFileMatch[1]));
|
|
2595
|
+
}
|
|
2596
|
+
else if (s3Match) {
|
|
2547
2597
|
const [, team, filename] = s3Match;
|
|
2548
2598
|
({ base64, mimeType } = await this.client.downloadImage(team, filename));
|
|
2549
2599
|
}
|
|
@@ -2552,7 +2602,7 @@ class WolfpackMCPServer {
|
|
|
2552
2602
|
({ base64, mimeType } = await this.client.downloadIssueAttachment(team, refId, attachmentId));
|
|
2553
2603
|
}
|
|
2554
2604
|
else {
|
|
2555
|
-
throw new Error('Invalid image URL. Expected format: /api/files/images/{team}/{filename} or /api/teams/{team}/issues/{refId}/attachments/{id}');
|
|
2605
|
+
throw new Error('Invalid image URL. Expected format: /api/files/{fileId}/{filename}, /api/files/images/{team}/{filename} or /api/teams/{team}/issues/{refId}/attachments/{id}');
|
|
2556
2606
|
}
|
|
2557
2607
|
return {
|
|
2558
2608
|
content: [
|
|
@@ -2572,6 +2622,14 @@ class WolfpackMCPServer {
|
|
|
2572
2622
|
content: [{ type: 'text', text: JSON.stringify(categories, null, 2) }],
|
|
2573
2623
|
};
|
|
2574
2624
|
}
|
|
2625
|
+
// Tag handlers
|
|
2626
|
+
case 'list_tags': {
|
|
2627
|
+
const parsed = ListTagsSchema.parse(args);
|
|
2628
|
+
const tags = await this.client.listTags(parsed.project_slug || this.client.getProjectSlug() || undefined);
|
|
2629
|
+
return {
|
|
2630
|
+
content: [{ type: 'text', text: JSON.stringify(tags, null, 2) }],
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2575
2633
|
// Team member handlers
|
|
2576
2634
|
case 'list_team_members': {
|
|
2577
2635
|
const parsed = ListTeamMembersSchema.parse(args);
|
package/dist/procedureTools.js
CHANGED
|
@@ -3,11 +3,37 @@
|
|
|
3
3
|
* Enables agents to list, view, create, update, and delete procedures.
|
|
4
4
|
*/
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
+
const FORM_FIELD_ITEM_SCHEMA = {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
name: { type: 'string' },
|
|
10
|
+
label: { type: 'string' },
|
|
11
|
+
type: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
enum: ['text', 'number', 'boolean', 'choice', 'date', 'json'],
|
|
14
|
+
},
|
|
15
|
+
required: { type: 'boolean' },
|
|
16
|
+
options: {
|
|
17
|
+
type: 'array',
|
|
18
|
+
items: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
value: { type: 'string' },
|
|
22
|
+
label: { type: 'string' },
|
|
23
|
+
},
|
|
24
|
+
required: ['value', 'label'],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
multiline: { type: 'boolean' },
|
|
28
|
+
},
|
|
29
|
+
required: ['name', 'label', 'type', 'required'],
|
|
30
|
+
};
|
|
6
31
|
export const PROCEDURE_TOOLS = [
|
|
7
32
|
{
|
|
8
33
|
name: 'list_procedures',
|
|
9
|
-
description: 'List procedures in a project. Returns name, status, version, tags,
|
|
10
|
-
'
|
|
34
|
+
description: 'List procedures in a project. Returns name, status, version, tags, case count, and hasForm ' +
|
|
35
|
+
'(true when the procedure declares an input form that must be provided at start_case time). ' +
|
|
36
|
+
'Use get_procedure to see the full workflow definition and form schema.',
|
|
11
37
|
inputSchema: {
|
|
12
38
|
type: 'object',
|
|
13
39
|
properties: {
|
|
@@ -24,10 +50,12 @@ export const PROCEDURE_TOOLS = [
|
|
|
24
50
|
},
|
|
25
51
|
{
|
|
26
52
|
name: 'get_procedure',
|
|
27
|
-
description: 'Get full details of a procedure including its workflow definition (nodes and edges)
|
|
28
|
-
'The definition uses a graph format: nodes have id, type
|
|
29
|
-
'position, and data; edges connect nodes via
|
|
30
|
-
'
|
|
53
|
+
description: 'Get full details of a procedure including its workflow definition (nodes and edges) and its ' +
|
|
54
|
+
'optional input form (formDefinition). The definition uses a graph format: nodes have id, type ' +
|
|
55
|
+
'(start/end/activity/decision/loop/parallel/wait), position, and data; edges connect nodes via ' +
|
|
56
|
+
'source/target IDs. The formDefinition lists input fields (name, label, type, required, options, ' +
|
|
57
|
+
'multiline) validated at start_case time — inspect it before calling start_case so you know which ' +
|
|
58
|
+
'form_values to supply. Accepts UUID or refId (requires project_slug for refId).',
|
|
31
59
|
inputSchema: {
|
|
32
60
|
type: 'object',
|
|
33
61
|
properties: {
|
|
@@ -48,7 +76,8 @@ export const PROCEDURE_TOOLS = [
|
|
|
48
76
|
description: 'Create a new procedure. If no definition is provided, creates a default start→end workflow. ' +
|
|
49
77
|
'The definition is a graph with nodes and edges. Node types: start, end, activity, decision, loop, parallel, wait. ' +
|
|
50
78
|
'Each node needs id, type, position ({x, y}), and data (type-specific config). ' +
|
|
51
|
-
'Edges connect nodes: {id, source, target}.
|
|
79
|
+
'Edges connect nodes: {id, source, target}. Optionally provide form_definition to declare inputs ' +
|
|
80
|
+
'collected each time the procedure is started. The procedure starts as inactive (isActive=false).',
|
|
52
81
|
inputSchema: {
|
|
53
82
|
type: 'object',
|
|
54
83
|
properties: {
|
|
@@ -58,6 +87,11 @@ export const PROCEDURE_TOOLS = [
|
|
|
58
87
|
type: 'object',
|
|
59
88
|
description: 'Workflow definition: { nodes: [{id, type, position: {x, y}, data: {...}}], edges: [{id, source, target}] }',
|
|
60
89
|
},
|
|
90
|
+
form_definition: {
|
|
91
|
+
type: ['array', 'null'],
|
|
92
|
+
description: 'Input form: array of {name, label, type, required, options?, multiline?}. Types: text, number, boolean, choice, date, json. Required fields must be supplied when start_case runs. Scheduled runs ignore the form.',
|
|
93
|
+
items: FORM_FIELD_ITEM_SCHEMA,
|
|
94
|
+
},
|
|
61
95
|
tags: {
|
|
62
96
|
type: 'array',
|
|
63
97
|
items: { type: 'string' },
|
|
@@ -76,6 +110,7 @@ export const PROCEDURE_TOOLS = [
|
|
|
76
110
|
description: 'Update a procedure. Only provide the fields you want to change. ' +
|
|
77
111
|
'Updating the definition auto-increments the version number. ' +
|
|
78
112
|
'Set isActive to true/false to activate/deactivate the procedure. ' +
|
|
113
|
+
'Pass form_definition to replace the input form (or null to remove it). ' +
|
|
79
114
|
'Accepts UUID or refId (requires project_slug for refId).',
|
|
80
115
|
inputSchema: {
|
|
81
116
|
type: 'object',
|
|
@@ -97,6 +132,11 @@ export const PROCEDURE_TOOLS = [
|
|
|
97
132
|
type: 'object',
|
|
98
133
|
description: 'Updated workflow definition: { nodes: [...], edges: [...] }. Changes increment version.',
|
|
99
134
|
},
|
|
135
|
+
form_definition: {
|
|
136
|
+
type: ['array', 'null'],
|
|
137
|
+
description: 'Replace the input form. Same shape as create_procedure.form_definition. Pass null to remove the form entirely.',
|
|
138
|
+
items: FORM_FIELD_ITEM_SCHEMA,
|
|
139
|
+
},
|
|
100
140
|
is_active: { type: 'boolean', description: 'Activate or deactivate the procedure' },
|
|
101
141
|
tags: {
|
|
102
142
|
type: 'array',
|
|
@@ -111,6 +151,45 @@ export const PROCEDURE_TOOLS = [
|
|
|
111
151
|
required: ['procedure_id'],
|
|
112
152
|
},
|
|
113
153
|
},
|
|
154
|
+
{
|
|
155
|
+
name: 'start_case',
|
|
156
|
+
description: 'Start a new case by running an active procedure. If the procedure has a form_definition ' +
|
|
157
|
+
'(see get_procedure), supply form_values: an object keyed by each field.name. Required fields ' +
|
|
158
|
+
'must be present; values are type-checked and unknown keys are dropped server-side. Returns the ' +
|
|
159
|
+
'caseId and refId of the new case. Scheduled procedures cannot be started this way — they fire ' +
|
|
160
|
+
'on their schedule.',
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
procedure_id: {
|
|
165
|
+
type: 'string',
|
|
166
|
+
description: 'Procedure UUID or refId number',
|
|
167
|
+
},
|
|
168
|
+
title: {
|
|
169
|
+
type: 'string',
|
|
170
|
+
description: 'Case title (defaults to the procedure name)',
|
|
171
|
+
},
|
|
172
|
+
priority: {
|
|
173
|
+
type: 'number',
|
|
174
|
+
description: 'Priority: 0 (none), 1 (low), 2 (medium), 3 (high), 4 (urgent)',
|
|
175
|
+
},
|
|
176
|
+
additional_information: {
|
|
177
|
+
type: 'string',
|
|
178
|
+
description: 'Free-text context displayed on the case detail page',
|
|
179
|
+
},
|
|
180
|
+
form_values: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
description: "Input values keyed by field name, matching the procedure's form_definition. Validated server-side: missing required fields → 400; wrong types → 400; unknown keys silently dropped.",
|
|
183
|
+
additionalProperties: true,
|
|
184
|
+
},
|
|
185
|
+
project_slug: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
description: 'Project slug (required when using refId)',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ['procedure_id'],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
114
193
|
];
|
|
115
194
|
export async function handleProcedureTool(name, args, client) {
|
|
116
195
|
const text = (t) => JSON.stringify(t, null, 2);
|
|
@@ -152,6 +231,7 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
152
231
|
name: z.string(),
|
|
153
232
|
description: z.string().optional(),
|
|
154
233
|
definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
234
|
+
form_definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
155
235
|
tags: z.array(z.string()).optional(),
|
|
156
236
|
project_slug: z.string().optional(),
|
|
157
237
|
})
|
|
@@ -160,6 +240,7 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
160
240
|
name: parsed.name,
|
|
161
241
|
description: parsed.description,
|
|
162
242
|
definition: parsed.definition,
|
|
243
|
+
formDefinition: parsed.form_definition,
|
|
163
244
|
tags: parsed.tags,
|
|
164
245
|
teamSlug: parsed.project_slug,
|
|
165
246
|
});
|
|
@@ -180,15 +261,18 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
180
261
|
description: z.string().nullable().optional(),
|
|
181
262
|
information: z.string().nullable().optional(),
|
|
182
263
|
definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
264
|
+
form_definition: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.any().optional()),
|
|
183
265
|
is_active: z.preprocess((v) => (typeof v === 'string' ? v === 'true' : v), z.boolean().optional()),
|
|
184
266
|
tags: z.array(z.string()).optional(),
|
|
185
267
|
project_slug: z.string().optional(),
|
|
186
268
|
})
|
|
187
269
|
.parse(args);
|
|
188
|
-
const { procedure_id, project_slug, is_active, ...rest } = parsed;
|
|
270
|
+
const { procedure_id, project_slug, is_active, form_definition, ...rest } = parsed;
|
|
189
271
|
const data = { ...rest };
|
|
190
272
|
if (is_active !== undefined)
|
|
191
273
|
data.isActive = is_active;
|
|
274
|
+
if (form_definition !== undefined)
|
|
275
|
+
data.formDefinition = form_definition;
|
|
192
276
|
const procedure = await client.updateProcedure(procedure_id, data, project_slug);
|
|
193
277
|
return {
|
|
194
278
|
content: [
|
|
@@ -199,6 +283,33 @@ export async function handleProcedureTool(name, args, client) {
|
|
|
199
283
|
],
|
|
200
284
|
};
|
|
201
285
|
}
|
|
286
|
+
case 'start_case': {
|
|
287
|
+
const parsed = z
|
|
288
|
+
.object({
|
|
289
|
+
procedure_id: z.string(),
|
|
290
|
+
title: z.string().optional(),
|
|
291
|
+
priority: z.coerce.number().optional(),
|
|
292
|
+
additional_information: z.string().optional(),
|
|
293
|
+
form_values: z.preprocess((v) => (typeof v === 'string' ? JSON.parse(v) : v), z.record(z.unknown()).optional()),
|
|
294
|
+
project_slug: z.string().optional(),
|
|
295
|
+
})
|
|
296
|
+
.parse(args);
|
|
297
|
+
const result = await client.startCase(parsed.procedure_id, {
|
|
298
|
+
title: parsed.title,
|
|
299
|
+
priority: parsed.priority,
|
|
300
|
+
additionalInformation: parsed.additional_information,
|
|
301
|
+
formValues: parsed.form_values,
|
|
302
|
+
teamSlug: parsed.project_slug,
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
content: [
|
|
306
|
+
{
|
|
307
|
+
type: 'text',
|
|
308
|
+
text: `Started case #${result.refId} (${result.caseId})\n\n${text(result)}`,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
202
313
|
default:
|
|
203
314
|
return {
|
|
204
315
|
content: [{ type: 'text', text: `Unknown procedure tool: ${name}` }],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// UUID format check — hyphenated 36-char hex; version nibble unconstrained to stay tolerant.
|
|
2
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
// Accept "#r5", "#roadmap-5", "#5", or plain "5" — capture the numeric refId.
|
|
4
|
+
const RADAR_REF_PATTERN = /^#?(?:r|roadmap-)?(\d+)$/i;
|
|
5
|
+
// Resolve a user-supplied radar item reference (UUID, refId, or prefixed refId) to a UUID.
|
|
6
|
+
// stripUuids hides internal UUIDs from agents in responses, so agents pass refIds; the
|
|
7
|
+
// work-item create/update/filter endpoints require a UUID, so we translate at the MCP boundary.
|
|
8
|
+
export async function resolveRadarItemId(client, input, teamSlug) {
|
|
9
|
+
const trimmed = input.trim();
|
|
10
|
+
if (UUID_PATTERN.test(trimmed))
|
|
11
|
+
return trimmed;
|
|
12
|
+
const refMatch = trimmed.match(RADAR_REF_PATTERN);
|
|
13
|
+
if (!refMatch) {
|
|
14
|
+
throw new Error(`Invalid radar_item_id "${input}": expected a UUID, refId number, or prefixed refId like "#r5".`);
|
|
15
|
+
}
|
|
16
|
+
const refId = refMatch[1];
|
|
17
|
+
const radarItem = await client.getRadarItem(refId, teamSlug);
|
|
18
|
+
if (!radarItem) {
|
|
19
|
+
throw new Error(`Radar/initiative item #r${refId} not found.`);
|
|
20
|
+
}
|
|
21
|
+
return radarItem.id;
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
3
|
+
const UUID = '11111111-2222-4333-8444-555555555555';
|
|
4
|
+
function makeClient(item) {
|
|
5
|
+
return {
|
|
6
|
+
getRadarItem: vi.fn(async () => item),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
describe('resolveRadarItemId', () => {
|
|
10
|
+
it('passes UUIDs through without lookup', async () => {
|
|
11
|
+
const client = makeClient(null);
|
|
12
|
+
const result = await resolveRadarItemId(client, UUID);
|
|
13
|
+
expect(result).toBe(UUID);
|
|
14
|
+
expect(client.getRadarItem).not.toHaveBeenCalled();
|
|
15
|
+
});
|
|
16
|
+
it.each(['5', '#5', '#r5', '#roadmap-5', ' 5 '])('resolves refId form %s to UUID via getRadarItem', async (input) => {
|
|
17
|
+
const client = makeClient({ id: UUID, refId: 5 });
|
|
18
|
+
const result = await resolveRadarItemId(client, input, 'my-team');
|
|
19
|
+
expect(result).toBe(UUID);
|
|
20
|
+
expect(client.getRadarItem).toHaveBeenCalledWith('5', 'my-team');
|
|
21
|
+
});
|
|
22
|
+
it('throws when refId cannot be resolved', async () => {
|
|
23
|
+
const client = makeClient(null);
|
|
24
|
+
await expect(resolveRadarItemId(client, '#r99')).rejects.toThrow(/#r99 not found/);
|
|
25
|
+
});
|
|
26
|
+
it('rejects malformed input', async () => {
|
|
27
|
+
const client = makeClient(null);
|
|
28
|
+
await expect(resolveRadarItemId(client, 'not-a-ref')).rejects.toThrow(/Invalid radar_item_id/);
|
|
29
|
+
expect(client.getRadarItem).not.toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
});
|
package/package.json
CHANGED
package/dist/imageProcessor.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const IMAGE_URL_REGEX = /!\[([^\]]*)\]\(\/api\/files\/images\/([^/]+)\/([^)]+)\)/g;
|
|
2
|
-
/**
|
|
3
|
-
* Processes markdown content to replace image URLs with base64 data URIs.
|
|
4
|
-
* Failed fetches or oversized images leave the original URL intact.
|
|
5
|
-
*/
|
|
6
|
-
export async function processContentImages(content, client) {
|
|
7
|
-
if (!content)
|
|
8
|
-
return content;
|
|
9
|
-
const matches = [...content.matchAll(IMAGE_URL_REGEX)];
|
|
10
|
-
if (matches.length === 0)
|
|
11
|
-
return content;
|
|
12
|
-
let result = content;
|
|
13
|
-
for (const match of matches) {
|
|
14
|
-
const [fullMatch, alt, team, filename] = match;
|
|
15
|
-
const dataUri = await client.fetchImageAsBase64(team, filename);
|
|
16
|
-
if (dataUri) {
|
|
17
|
-
result = result.replace(fullMatch, ``);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return result;
|
|
21
|
-
}
|