@taiga-ai/mcp-server 0.12.0-dev.421 → 0.13.0-dev.422

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.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The planner's prompt for an initiative request, built as the web builds it
3
+ * (apps/web/src/features/loops/delivery/components/planning/generateBacklogJob.ts):
4
+ * title and description trimmed, the description's inline-image markdown
5
+ * removed and the blank lines it leaves collapsed, joined by a blank line.
6
+ * The planner reads `userPrompt` and nothing else of the request, so the same
7
+ * words must make the same prompt from either surface.
8
+ *
9
+ * The image pattern is the web's (inline-image-doc-ids.ts), which keeps it in
10
+ * lockstep with the API's `extractInlineImageDocIds`: the `![...](...)` form,
11
+ * an exact UUID, and a terminator after `/image`.
12
+ */
13
+ const INLINE_IMAGE_RE =
14
+ /!\[(?:[^\]\\]|\\.)*\]\([^)]*?\/custom-documents\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/image(?=[)?#\s])[^)]*\)/g;
15
+
16
+ /** The description without its inline images, as the web strips it. */
17
+ export function stripInlineImages(markdown: string): string {
18
+ return markdown
19
+ .replace(INLINE_IMAGE_RE, '')
20
+ .replace(/\n{3,}/g, '\n\n')
21
+ .trim();
22
+ }
23
+
24
+ /** The prompt for a title and a description; undefined when both are empty, which asks for a suggestion. */
25
+ export function requestPrompt(title: string | undefined, description: string | undefined): string | undefined {
26
+ const body = stripInlineImages((description ?? '').trim());
27
+ return [(title ?? '').trim(), body].filter(Boolean).join('\n\n') || undefined;
28
+ }
package/src/tools/wait.ts CHANGED
@@ -4,6 +4,7 @@ import { z } from 'zod';
4
4
  import { TaigaApiError, type TaigaClient } from '../client.js';
5
5
  import { READ_ONLY } from './annotations.js';
6
6
  import { isNetworkFailure, runTool, toolResult } from './errors.js';
7
+ import { SETTLED_JOB_STATUSES, jobReport } from './jobs.js';
7
8
  import { criteriaCounts, pickFields } from './summarize.js';
8
9
 
9
10
  /**
@@ -176,6 +177,8 @@ function itemSignature(item: Row): string {
176
177
  item.state,
177
178
  item.planningStatus,
178
179
  item.needsHumanCause,
180
+ // The review gate decides whose turn a landed plan is, while the lifecycle reads the same either way.
181
+ item.autoBuild,
179
182
  lifecycle.stage,
180
183
  lifecycle.turn,
181
184
  lifecycle.cta,
@@ -442,4 +445,39 @@ export function registerWaitTools(server: McpServer, client: TaigaClient, option
442
445
  })
443
446
  );
444
447
  }
448
+
449
+ if (client.hasCapability('products:read')) {
450
+ server.registerTool(
451
+ 'taiga_wait_for_job',
452
+ {
453
+ title: 'Wait for an agent job',
454
+ description:
455
+ 'Wait until an agent job finishes (completed, failed or cancelled): an initiative request, a document, ' +
456
+ 'a refresh. Returns the job as taiga_get_job reads it, with what came of an initiative request, as soon ' +
457
+ 'as it settles (reached: true), or at the timeout with reached: false: call again to keep waiting. Most ' +
458
+ 'jobs take a few minutes.',
459
+ inputSchema: {
460
+ jobId: z.string().uuid().describe('Agent job ID -- a request tool returns it'),
461
+ timeoutSeconds,
462
+ },
463
+ annotations: READ_ONLY,
464
+ },
465
+ (params, extra) =>
466
+ runTool(async () => {
467
+ const result = await waitFor(
468
+ async (signal) => (await client.getAgentJob(params.jobId, signal)).data as Row,
469
+ (job) => (SETTLED_JOB_STATUSES.has(String(job.status)) ? { reached: true } : null),
470
+ (params.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS) * 1000,
471
+ options,
472
+ {
473
+ progressToken: extra._meta?.progressToken,
474
+ sendNotification: extra.sendNotification,
475
+ signal: extra.signal,
476
+ }
477
+ );
478
+ const { now, ...outcome } = result;
479
+ return toolResult({ ...outcome, job: now ? jobReport(now) : null });
480
+ })
481
+ );
482
+ }
445
483
  }