wolfpack-mcp 1.0.94 → 1.0.96

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 CHANGED
@@ -357,11 +357,11 @@ Get a specific radar item.
357
357
 
358
358
  ### Tags
359
359
 
360
- Tags label work items, issues, wiki pages and journal entries, and are filed under nestable groups. Project admins define them; a key with `mcp:tags:manage` lets an admin's agent keep them in step with an external system (for example, one tag per CRM customer account keyed on `external_id`). There is no delete tool — removing a tag is done in the browser.
360
+ Tags label work items, issues, wiki pages and journal entries, and are filed under nestable groups. Project admins define them; a key with `mcp:tags:manage` lets an admin's agent keep them in step with an external system (for example, one tag per CRM customer account keyed on `external_id`). There is no delete tool — archive instead (`update_tag` with `archived: true`); removing a tag outright is done in the browser.
361
361
 
362
362
  #### `list_tags`
363
363
 
364
- List a project's tags with id, name, colour, `externalId` and the group each is filed under. Any key that can read a tagged module may list them.
364
+ List a project's tags with id, name, colour, `externalId`, the group each is filed under and whether it is `archived`. Any key that can read a tagged module may list them.
365
365
 
366
366
  - `project_slug` (optional): Project slug
367
367
 
@@ -375,10 +375,10 @@ List a project's tags with id, name, colour, `externalId` and the group each is
375
375
 
376
376
  #### `update_tag`
377
377
 
378
- Only the fields given change; pass `null` for `colour`, `group` or `external_id` to clear it.
378
+ Only the fields given change; pass `null` for `colour`, `group` or `external_id` to clear it. `archived: true` is the soft delete: the tag stays on the items that carry it and keeps its page, but no picker offers it and it cannot be applied; `archived: false` restores it. A sync uses this for accounts the source system has dropped.
379
379
 
380
380
  - `tag` (required): Tag id or current name
381
- - `name`, `colour`, `group`, `external_id` (optional)
381
+ - `name`, `colour`, `group`, `external_id`, `archived` (optional)
382
382
  - `project_slug` (optional): Project slug
383
383
 
384
384
  #### `list_tag_groups`
@@ -40,12 +40,25 @@ export const AGENT_BUILDER_TOOLS = [
40
40
  // ─── Group 1: Agent CRUD ─────────────────────────────────────────────────
41
41
  {
42
42
  name: 'list_agents',
43
- description: 'List all agents in the organisation. Returns name, status, template, and assigned projects.',
44
- inputSchema: { type: 'object', properties: { ...ORG_SLUG_PROP } },
43
+ description: 'List agents. Returns name, status, template, and assigned projects. ' +
44
+ 'scope "org" (the default) lists the organisation\'s agents; scope "personal" lists your own ' +
45
+ 'My Agents from My Space, which belong to no organisation. Call it twice to see both.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ scope: {
50
+ type: 'string',
51
+ enum: ['org', 'personal'],
52
+ description: "Which agents to list: the organisation's (default) or your own personal ones.",
53
+ },
54
+ ...ORG_SLUG_PROP,
55
+ },
56
+ },
45
57
  },
46
58
  {
47
59
  name: 'get_agent',
48
- description: 'Get full details for an agent including config, instructions, LLM model, and linked skills.',
60
+ description: 'Get full details for an agent including config, instructions, LLM model, and linked skills. ' +
61
+ 'Takes an organisation agent or one of your own personal agents.',
49
62
  inputSchema: {
50
63
  type: 'object',
51
64
  properties: {
@@ -57,7 +70,10 @@ export const AGENT_BUILDER_TOOLS = [
57
70
  },
58
71
  {
59
72
  name: 'create_agent',
60
- description: 'Create a new agent from a container image. Use list_container_images to see available images.',
73
+ description: 'Create a new agent from a container image. Use list_container_images to see available images. ' +
74
+ 'With personal=true, creates one of your own My Agents instead of an organisation agent: ' +
75
+ 'it needs a handle, and only an image opened to personal use (list_container_images with ' +
76
+ 'personal=true) qualifies.',
61
77
  inputSchema: {
62
78
  type: 'object',
63
79
  properties: {
@@ -67,6 +83,14 @@ export const AGENT_BUILDER_TOOLS = [
67
83
  },
68
84
  name: { type: 'string', description: 'Display name for the agent' },
69
85
  config: { type: 'object', description: 'Optional agent-specific configuration' },
86
+ personal: {
87
+ type: 'boolean',
88
+ description: 'Create one of your own personal agents (My Space > My Agents) rather than an organisation agent.',
89
+ },
90
+ handle: {
91
+ type: 'string',
92
+ description: 'Handle for a personal agent — it becomes "@<your username>/<handle>". Required with personal=true, ignored otherwise.',
93
+ },
70
94
  ...ORG_SLUG_PROP,
71
95
  },
72
96
  required: ['container_image_id', 'name'],
@@ -798,8 +822,19 @@ export const AGENT_BUILDER_TOOLS = [
798
822
  // ─── Group 7: Discovery ───────────────────────────────────────────────────
799
823
  {
800
824
  name: 'list_container_images',
801
- description: 'List agent-capable container images assigned to the organisation. Use image IDs when calling create_agent.',
802
- inputSchema: { type: 'object', properties: { ...ORG_SLUG_PROP } },
825
+ description: 'List agent-capable container images assigned to the organisation. Use image IDs when calling create_agent. ' +
826
+ 'With personal=true, lists the images a personal agent may run instead — the bring-your-own-token ' +
827
+ 'images opened to personal use, which are not an organisation catalogue.',
828
+ inputSchema: {
829
+ type: 'object',
830
+ properties: {
831
+ personal: {
832
+ type: 'boolean',
833
+ description: 'List the images available to personal agents rather than the organisation catalogue.',
834
+ },
835
+ ...ORG_SLUG_PROP,
836
+ },
837
+ },
803
838
  },
804
839
  {
805
840
  name: 'list_llm_models',
@@ -837,8 +872,10 @@ export async function handleAgentBuilderTool(name, args, client) {
837
872
  }
838
873
  // ─── Agent CRUD ────────────────────────────────────────────────────────
839
874
  case 'list_agents': {
840
- const parsed = z.object({ org_slug: orgSlugField }).parse(args);
841
- const agents = await client.listAgents(resolveOrg(parsed));
875
+ const parsed = z
876
+ .object({ scope: z.enum(['org', 'personal']).optional(), org_slug: orgSlugField })
877
+ .parse(args);
878
+ const agents = await client.listAgents(resolveOrg(parsed), parsed.scope);
842
879
  return { content: [{ type: 'text', text: text(agents) }] };
843
880
  }
844
881
  case 'get_agent': {
@@ -854,13 +891,21 @@ export async function handleAgentBuilderTool(name, args, client) {
854
891
  container_image_id: z.string(),
855
892
  name: z.string(),
856
893
  config: z.record(z.unknown()).optional(),
894
+ personal: z.boolean().optional(),
895
+ handle: z.string().optional(),
857
896
  org_slug: orgSlugField,
897
+ })
898
+ .refine((v) => !v.personal || !!v.handle, {
899
+ message: 'handle is required with personal=true',
900
+ path: ['handle'],
858
901
  })
859
902
  .parse(args);
860
903
  const agent = await client.createAgent({
861
904
  containerImageId: parsed.container_image_id,
862
905
  name: parsed.name,
863
906
  config: parsed.config,
907
+ personal: parsed.personal,
908
+ handleName: parsed.handle,
864
909
  }, resolveOrg(parsed));
865
910
  return {
866
911
  content: [
@@ -1427,8 +1472,10 @@ export async function handleAgentBuilderTool(name, args, client) {
1427
1472
  }
1428
1473
  // ─── Discovery ────────────────────────────────────────────────────────────
1429
1474
  case 'list_container_images': {
1430
- const parsed = z.object({ org_slug: orgSlugField }).parse(args);
1431
- const images = await client.listContainerImages(resolveOrg(parsed));
1475
+ const parsed = z
1476
+ .object({ personal: z.boolean().optional(), org_slug: orgSlugField })
1477
+ .parse(args);
1478
+ const images = await client.listContainerImages(resolveOrg(parsed), parsed.personal);
1432
1479
  return { content: [{ type: 'text', text: text(images) }] };
1433
1480
  }
1434
1481
  case 'list_llm_models': {
@@ -144,3 +144,51 @@ describe('schedule pre-flight over MCP (#2083)', () => {
144
144
  expect(Object.keys(properties)).not.toContain('scheduling_enabled');
145
145
  });
146
146
  });
147
+ // #2273 — My Agents reached over MCP. There is no second tool set: a personal
148
+ // agent is addressed by the same tools and the same agent_id as an org one, so
149
+ // what these cover is the three tools that had to learn the difference.
150
+ describe('personal agents over MCP (#2273)', () => {
151
+ function fake(method, result) {
152
+ const fn = vi.fn().mockResolvedValue(result);
153
+ return { client: { [method]: fn }, fn };
154
+ }
155
+ it('list_agents offers both scopes and defaults to neither being sent', async () => {
156
+ const scope = toolNamed('list_agents').inputSchema.properties.scope;
157
+ expect(scope.enum).toEqual(['org', 'personal']);
158
+ const { client, fn } = fake('listAgents', []);
159
+ await handleAgentBuilderTool('list_agents', {}, client);
160
+ expect(fn.mock.calls[0][1]).toBeUndefined();
161
+ });
162
+ it('list_agents passes the personal scope through', async () => {
163
+ const { client, fn } = fake('listAgents', [{ id: 'agent-1' }]);
164
+ await handleAgentBuilderTool('list_agents', { scope: 'personal' }, client);
165
+ expect(fn.mock.calls[0][1]).toBe('personal');
166
+ });
167
+ it('create_agent sends the personal flag and the handle', async () => {
168
+ const { client, fn } = fake('createAgent', { userId: 'agent_1' });
169
+ await handleAgentBuilderTool('create_agent', {
170
+ container_image_id: 'image-1',
171
+ name: 'Raiven',
172
+ personal: true,
173
+ handle: 'raiven',
174
+ }, client);
175
+ expect(fn.mock.calls[0][0]).toMatchObject({ personal: true, handleName: 'raiven' });
176
+ });
177
+ // A personal agent's handle is required, so say so here rather than spending a
178
+ // round trip to learn it from the backend.
179
+ it('create_agent refuses personal without a handle', async () => {
180
+ const { client, fn } = fake('createAgent', { userId: 'agent_1' });
181
+ await expect(handleAgentBuilderTool('create_agent', { container_image_id: 'image-1', name: 'Raiven', personal: true }, client)).rejects.toThrow('handle is required with personal=true');
182
+ expect(fn).not.toHaveBeenCalled();
183
+ });
184
+ it('create_agent stays organisational when the flag is left off', async () => {
185
+ const { client, fn } = fake('createAgent', { userId: 'agent_1' });
186
+ await handleAgentBuilderTool('create_agent', { container_image_id: 'image-1', name: 'Scout' }, client);
187
+ expect(fn.mock.calls[0][0].personal).toBeUndefined();
188
+ });
189
+ it('list_container_images asks for the personal catalogue', async () => {
190
+ const { client, fn } = fake('listContainerImages', []);
191
+ await handleAgentBuilderTool('list_container_images', { personal: true }, client);
192
+ expect(fn.mock.calls[0][1]).toBe(true);
193
+ });
194
+ });
package/dist/client.js CHANGED
@@ -490,6 +490,9 @@ export class WolfpackClient {
490
490
  async createWorkItemComment(workItemId, data, teamSlug) {
491
491
  return this.api.post(this.withTeamSlug(`/work-items/${workItemId}/comments`, teamSlug), data);
492
492
  }
493
+ async askWorkItemQuestion(workItemId, question, teamSlug) {
494
+ return this.api.post(this.withTeamSlug(`/work-items/${workItemId}/question`, teamSlug), { question });
495
+ }
493
496
  async createIssueComment(issueId, data, teamSlug) {
494
497
  return this.api.post(this.withTeamSlug(`/issues/${issueId}/comments`, teamSlug), data);
495
498
  }
@@ -673,8 +676,9 @@ export class WolfpackClient {
673
676
  return response.organisations;
674
677
  }
675
678
  // ─── Agent Builder: Agent CRUD ─────────────────────────────────────────────
676
- async listAgents(orgSlug) {
677
- return this.api.get(this.withOrgSlug('/agents', orgSlug));
679
+ async listAgents(orgSlug, scope) {
680
+ const path = scope === 'personal' ? '/agents?scope=personal' : '/agents';
681
+ return this.api.get(this.withOrgSlug(path, orgSlug));
678
682
  }
679
683
  async getAgent(agentId, orgSlug) {
680
684
  try {
@@ -866,8 +870,11 @@ export class WolfpackClient {
866
870
  }
867
871
  }
868
872
  // ─── Agent Builder: Discovery ──────────────────────────────────────────────
869
- async listContainerImages(orgSlug) {
870
- return this.api.get(this.withOrgSlug('/agent-builder/container-images', orgSlug));
873
+ async listContainerImages(orgSlug, personal) {
874
+ const path = personal
875
+ ? '/agent-builder/container-images?personal=true'
876
+ : '/agent-builder/container-images';
877
+ return this.api.get(this.withOrgSlug(path, orgSlug));
871
878
  }
872
879
  async listLlmModels(orgSlug) {
873
880
  return this.api.get(this.withOrgSlug('/agent-builder/llm-models', orgSlug));
package/dist/index.js CHANGED
@@ -538,6 +538,14 @@ const CreateWorkItemCommentSchema = z.object({
538
538
  .optional()
539
539
  .describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
540
540
  });
541
+ const AskWorkItemQuestionSchema = z.object({
542
+ work_item_id: refIdString().describe('The work item refId (number)'),
543
+ question: z.string().describe('The question (markdown)'),
544
+ project_slug: z
545
+ .string()
546
+ .optional()
547
+ .describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
548
+ });
541
549
  const CreateIssueCommentSchema = z.object({
542
550
  issue_id: refIdString().describe('The issue refId (number)'),
543
551
  content: z.string().describe('Comment content (markdown)'),
@@ -627,6 +635,10 @@ const UpdateTagSchema = z.object({
627
635
  .optional()
628
636
  .describe('Tag group name or id, or null to move the tag out of its group'),
629
637
  external_id: z.string().nullable().optional().describe('External id, or null to clear'),
638
+ archived: z
639
+ .boolean()
640
+ .optional()
641
+ .describe('true archives the tag (soft delete), false restores it'),
630
642
  });
631
643
  const CreateTagGroupSchema = z.object({
632
644
  project_slug: z
@@ -863,6 +875,9 @@ class WolfpackMCPServer {
863
875
  'assigned_to_id you pass; the response says so when this applies. ' +
864
876
  'AGENTS: when more than one item is claimable, the response also states the order to take ' +
865
877
  'them in (bug fixes first, then higher priority, then oldest) and which to pull next. ' +
878
+ 'AGENTS: blockedBy on each item names the prerequisites of it that have not finished. An item ' +
879
+ 'with a non-empty blockedBy is NOT claimable however approved it is — take the work it names ' +
880
+ 'first; the response says so too. ' +
866
881
  'TERMINOLOGY: "board" and "kanban" are synonymous - both refer to the Kanban board of work items. ' +
867
882
  'The board has columns: "new" (to do), "doing" (in progress), "review" (pending review), "ready" (code done, awaiting deployment), "blocked", "completed" (deployed). ' +
868
883
  'The "backlog" or "pending" status represents items not yet on the board. ' +
@@ -1807,6 +1822,8 @@ class WolfpackMCPServer {
1807
1822
  '2) Completion summaries when moving to "review" (what was done, files changed, testing notes). ' +
1808
1823
  '3) Important observations or decisions that should be visible in the activity history. ' +
1809
1824
  'USE DESCRIPTION (update_work_progress) FOR: Plans, checklists, and progress tracking that need to be updated over time. ' +
1825
+ 'NOT FOR QUESTIONS you need answered before you can continue the item you are working: a reply cannot reach a ' +
1826
+ 'running session, so a question asked here is one you keep working past. Use ask_work_item_question, which pauses the work. ' +
1810
1827
  'Comments are typically not used in personal projects. ' +
1811
1828
  CONTENT_LINKING_HELP,
1812
1829
  inputSchema: {
@@ -1825,6 +1842,35 @@ class WolfpackMCPServer {
1825
1842
  required: ['work_item_id', 'content'],
1826
1843
  },
1827
1844
  },
1845
+ {
1846
+ name: 'ask_work_item_question',
1847
+ description: 'Ask a question about the work item you are working on and PAUSE until it is answered. ' +
1848
+ 'For when you cannot sensibly continue without an answer from a person — an ambiguous requirement, a decision ' +
1849
+ 'that is theirs to make, something only they know. The question is posted as a comment addressed to the ' +
1850
+ 'item\'s creator, and the item is marked as waiting: it stays in "doing" with you, your schedule does not ' +
1851
+ 'wake you for it until someone else comments (that comment is the answer), and the session ending on it is ' +
1852
+ 'not counted as an abandonment. AFTER CALLING THIS, STOP: commit and push anything worth keeping, make sure ' +
1853
+ 'the plan in the description records where you got to, and end your session without taking other work — ' +
1854
+ 'a reply cannot reach a running session, and pull_work_item refuses while the item waits. When you are ' +
1855
+ "woken again, get_work_item shows the question and its answer. Only the item's leading user may ask, and " +
1856
+ 'only while the item is in "doing". In a chat session ask in your reply instead — the next message is the answer. ' +
1857
+ 'Requires mcp:work_items:update permission.',
1858
+ inputSchema: {
1859
+ type: 'object',
1860
+ properties: {
1861
+ work_item_id: { type: 'string', description: 'The work item refId (number)' },
1862
+ question: {
1863
+ type: 'string',
1864
+ description: 'The question (markdown). Give the context a reader needs to answer it in one reply: what you found, the options you see, what you would do by default.',
1865
+ },
1866
+ project_slug: {
1867
+ type: 'string',
1868
+ description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
1869
+ },
1870
+ },
1871
+ required: ['work_item_id', 'question'],
1872
+ },
1873
+ },
1828
1874
  {
1829
1875
  name: 'create_issue_comment',
1830
1876
  description: 'Add a comment to an issue. Requires mcp:comments:create permission. ' +
@@ -1986,8 +2032,8 @@ class WolfpackMCPServer {
1986
2032
  // Tag tools
1987
2033
  {
1988
2034
  name: 'list_tags',
1989
- description: 'List all tags available in a project: id, name, colour, external id and the group each is filed under. ' +
1990
- 'Items are tagged by name via update_work_item / update_issue. ' +
2035
+ description: 'List all tags of a project: id, name, colour, external id, the group each is filed under, and whether it is archived. ' +
2036
+ 'Items are tagged by name via update_work_item / update_issue; archived tags cannot be applied. ' +
1991
2037
  'A sync from an external system (e.g. a CRM) keys on externalId.',
1992
2038
  inputSchema: {
1993
2039
  type: 'object',
@@ -2026,8 +2072,9 @@ class WolfpackMCPServer {
2026
2072
  },
2027
2073
  {
2028
2074
  name: 'update_tag',
2029
- description: 'Rename, recolour, regroup or re-key a tag. Project admins only (mcp:tags:manage). ' +
2030
- 'Only the fields given change; pass null for colour, group or external_id to clear it.',
2075
+ description: 'Rename, recolour, regroup, re-key or archive a tag. Project admins only (mcp:tags:manage). ' +
2076
+ 'Only the fields given change; pass null for colour, group or external_id to clear it. ' +
2077
+ 'archived: true is the soft delete — the tag stays on the items that carry it but no picker offers it; archived: false restores it.',
2031
2078
  inputSchema: {
2032
2079
  type: 'object',
2033
2080
  properties: {
@@ -2046,6 +2093,10 @@ class WolfpackMCPServer {
2046
2093
  type: ['string', 'null'],
2047
2094
  description: 'External id, or null to clear',
2048
2095
  },
2096
+ archived: {
2097
+ type: 'boolean',
2098
+ description: 'true archives the tag (soft delete), false restores it',
2099
+ },
2049
2100
  },
2050
2101
  required: ['tag'],
2051
2102
  },
@@ -2500,7 +2551,7 @@ class WolfpackMCPServer {
2500
2551
  const workItem = await this.client.getWorkItem(parsed.work_item_id, parsed.project_slug || this.client.getProjectSlug() || undefined);
2501
2552
  if (workItem) {
2502
2553
  let text = JSON.stringify(stripUuids(workItem), null, 2);
2503
- const reminders = getWorkItemReminders(workItem.status, workItem.description, workItem.approved);
2554
+ const reminders = getWorkItemReminders(workItem.status, workItem.description, workItem.approved, workItem.question);
2504
2555
  if (reminders.length > 0) {
2505
2556
  text = `${reminders.join('\n\n')}\n\n${text}`;
2506
2557
  }
@@ -3077,6 +3128,19 @@ class WolfpackMCPServer {
3077
3128
  ],
3078
3129
  };
3079
3130
  }
3131
+ case 'ask_work_item_question': {
3132
+ const parsed = AskWorkItemQuestionSchema.parse(args);
3133
+ const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
3134
+ const result = await this.client.askWorkItemQuestion(parsed.work_item_id, parsed.question, teamSlug);
3135
+ return {
3136
+ content: [
3137
+ {
3138
+ type: 'text',
3139
+ text: `${result.notice}\n\n${JSON.stringify(stripUuids(result.comment), null, 2)}`,
3140
+ },
3141
+ ],
3142
+ };
3143
+ }
3080
3144
  case 'create_issue_comment': {
3081
3145
  const parsed = CreateIssueCommentSchema.parse(args);
3082
3146
  const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
@@ -3256,6 +3320,7 @@ class WolfpackMCPServer {
3256
3320
  colour: parsed.colour,
3257
3321
  group: parsed.group,
3258
3322
  externalId: parsed.external_id,
3323
+ archived: parsed.archived,
3259
3324
  }, parsed.project_slug || this.client.getProjectSlug() || undefined);
3260
3325
  return {
3261
3326
  content: [{ type: 'text', text: JSON.stringify(tag, null, 2) }],
@@ -30,9 +30,23 @@ const STATUS_REMINDERS = {
30
30
  'starting, so the board shows the work is under way again. If you are reviewing it, leave it where it is.',
31
31
  };
32
32
  // Reminders to prepend to a get_work_item response, most urgent first.
33
- export function getWorkItemReminders(status, description, approved) {
33
+ export function getWorkItemReminders(status, description, approved, question) {
34
34
  const reminders = [];
35
- if (status === 'pending' && approved === false) {
35
+ // A question replaces the status reminder: "doing" would say to finish the work,
36
+ // and the work is paused until someone answers (#2289)
37
+ if (status === 'doing' && question) {
38
+ if (question.answer === null) {
39
+ reminders.push('REMINDER: This work item is waiting for an answer to the question its leading agent asked ' +
40
+ '(see "question" below). Nobody has answered yet. If you are that agent, do not work on it ' +
41
+ 'and do not take other work: end your session, and your schedule wakes you once the answer is in.');
42
+ }
43
+ else {
44
+ reminders.push('REMINDER: The question asked on this work item has been answered — the answer is in ' +
45
+ '"question.answer" below, and the comments carry any discussion since. Resume the work with ' +
46
+ 'that answer; the plan in the description says where it got to.');
47
+ }
48
+ }
49
+ else if (status === 'pending' && approved === false) {
36
50
  // Overrides the generic pending reminder: approval only gates unassigned items
37
51
  reminders.push('REMINDER: This backlog item has NOT been approved. If it is assigned to you, that assignment ' +
38
52
  'overrides approval — pull it with pull_work_item and start work. If it is not assigned to you, ' +
@@ -55,6 +55,17 @@ describe('getWorkItemReminders', () => {
55
55
  expect(reminders).toEqual([expect.stringContaining('set status to "doing"')]);
56
56
  expect(reminders[0]).toContain('If you are reviewing it, leave it where it is');
57
57
  });
58
+ // #2289: a question outranks the "doing" reminder — the work is paused until answered
59
+ it('tells an agent whose question is unanswered to stop and wait', () => {
60
+ const reminders = getWorkItemReminders('doing', PLAN, undefined, { answer: null });
61
+ expect(reminders).toEqual([expect.stringContaining('waiting for an answer')]);
62
+ expect(reminders[0]).toContain('end your session');
63
+ });
64
+ it('points an agent whose question was answered at the answer', () => {
65
+ const reminders = getWorkItemReminders('doing', PLAN, undefined, { answer: 'Option B.' });
66
+ expect(reminders).toEqual([expect.stringContaining('has been answered')]);
67
+ expect(reminders[0]).toContain('"question.answer"');
68
+ });
58
69
  it('adds the no-plan reminder when the description has no plan', () => {
59
70
  const reminders = getWorkItemReminders('new', 'Just a description');
60
71
  expect(reminders).toHaveLength(2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfpack-mcp",
3
- "version": "1.0.94",
3
+ "version": "1.0.96",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",