@taiga-ai/mcp-server 0.18.0-dev.447 → 0.18.0-dev.448
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/index.js +93 -37
- package/package.json +1 -1
- package/src/client.test.ts +2 -2
- package/src/client.ts +190 -145
- package/src/server.integration.test.ts +10 -9
- package/src/tools/autonomy.ts +2 -4
- package/src/tools/backlogs.ts +27 -30
- package/src/tools/builds.ts +14 -12
- package/src/tools/catalogue.test.ts +5 -5
- package/src/tools/context.ts +32 -30
- package/src/tools/documents.ts +8 -4
- package/src/tools/hints.ts +5 -2
- package/src/tools/inbox.ts +3 -5
- package/src/tools/jobs.ts +6 -5
- package/src/tools/knowledge.ts +4 -6
- package/src/tools/maintenance.ts +9 -14
- package/src/tools/markdown.ts +13 -5
- package/src/tools/observe.ts +17 -14
- package/src/tools/product-documents.ts +3 -5
- package/src/tools/products.ts +25 -22
- package/src/tools/prompts.ts +7 -13
- package/src/tools/queue.ts +36 -15
- package/src/tools/repositories.ts +7 -8
- package/src/tools/resources.ts +11 -14
- package/src/tools/sections.test.ts +3 -3
- package/src/tools/sections.ts +44 -15
- package/src/tools/setup.ts +21 -25
- package/src/tools/summarize.test.ts +20 -0
- package/src/tools/summarize.ts +17 -13
- package/src/tools/wait.ts +28 -21
- package/taiga-ai-mcp-server-0.18.0-dev.448.tgz +0 -0
- package/tsconfig.json +1 -5
- package/taiga-ai-mcp-server-0.18.0-dev.447.tgz +0 -0
package/src/tools/wait.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { BUILD_RUN_TERMINAL_STATUSES, resolveAutoBuild } from '@taiga/types';
|
|
3
|
+
import type { BacklogItem, BacklogItemLifecycle, BuildRunWithContext } from '@taiga/types';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
import { idInput } from './inputs.js';
|
|
5
|
-
import { TaigaApiError, type TaigaClient } from '../client.js';
|
|
6
|
+
import { TaigaApiError, type TaigaClient, type WithoutTenant } from '../client.js';
|
|
6
7
|
import { READ_ONLY } from './annotations.js';
|
|
7
8
|
import { isNetworkFailure, runTool, toolResult } from './errors.js';
|
|
8
9
|
import { SETTLED_JOB_STATUSES, jobReport } from './jobs.js';
|
|
@@ -43,7 +44,11 @@ const timeoutSeconds = z
|
|
|
43
44
|
'many stop a call at 60 seconds.'
|
|
44
45
|
);
|
|
45
46
|
|
|
46
|
-
type
|
|
47
|
+
type Item = WithoutTenant<BacklogItem>;
|
|
48
|
+
type Run = WithoutTenant<BuildRunWithContext>;
|
|
49
|
+
|
|
50
|
+
/** The pull-request fields an initiative's latest run and a build run both carry. */
|
|
51
|
+
type PullRequestFields = Pick<Run, 'prUrl' | 'prMerged' | 'prState'>;
|
|
47
52
|
|
|
48
53
|
/** Why a wait ended: the condition held, it can no longer hold on its own, or time ran out. */
|
|
49
54
|
type Verdict = { reached: true } | { reached: false; stoppedBecause: string } | null;
|
|
@@ -91,17 +96,17 @@ function isTransient(error: unknown): boolean {
|
|
|
91
96
|
* it, which also keeps a client that resets its timeout on progress from
|
|
92
97
|
* cutting the wait short.
|
|
93
98
|
*/
|
|
94
|
-
async function waitFor(
|
|
95
|
-
read: (signal: AbortSignal) => Promise<
|
|
96
|
-
judge: (now:
|
|
99
|
+
async function waitFor<T>(
|
|
100
|
+
read: (signal: AbortSignal) => Promise<T>,
|
|
101
|
+
judge: (now: T, first: T) => Verdict,
|
|
97
102
|
timeoutMs: number,
|
|
98
103
|
options: WaitOptions,
|
|
99
104
|
progress: Progress
|
|
100
105
|
) {
|
|
101
106
|
const started = Date.now();
|
|
102
107
|
const deadline = started + timeoutMs;
|
|
103
|
-
let first:
|
|
104
|
-
let now:
|
|
108
|
+
let first: T | null = null;
|
|
109
|
+
let now: T | null = null;
|
|
105
110
|
let verdict: Verdict = null;
|
|
106
111
|
let failures = 0;
|
|
107
112
|
for (let attempt = 0; !progress.signal.aborted; attempt += 1) {
|
|
@@ -156,7 +161,7 @@ const SETTLED_RUN: ReadonlySet<string> = new Set(BUILD_RUN_TERMINAL_STATUSES);
|
|
|
156
161
|
* is open; the mirrored state and the merged latch say which. An unknown
|
|
157
162
|
* state (null, just opened) counts as open.
|
|
158
163
|
*/
|
|
159
|
-
function prIsOpen(run:
|
|
164
|
+
function prIsOpen(run: PullRequestFields): boolean {
|
|
160
165
|
return typeof run.prUrl === 'string' && run.prUrl !== '' && run.prMerged !== true && run.prState !== 'closed';
|
|
161
166
|
}
|
|
162
167
|
|
|
@@ -167,11 +172,13 @@ function prIsOpen(run: Row): boolean {
|
|
|
167
172
|
const ITEM_CONDITIONS = ['changed', 'planned', 'your_turn', 'pr_open', 'run_settled', 'done'] as const;
|
|
168
173
|
type ItemCondition = (typeof ITEM_CONDITIONS)[number];
|
|
169
174
|
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
// The API sends a lifecycle on every backlog item (backlogItemSchema); a
|
|
176
|
+
// response without one reads as no lifecycle rather than failing the wait.
|
|
177
|
+
const latestBuildOf = (item: Item) => item.lifecycle?.latestBuild ?? null;
|
|
178
|
+
const lifecycleOf = (item: Item): Partial<BacklogItemLifecycle> => item.lifecycle ?? {};
|
|
172
179
|
|
|
173
180
|
/** What "changed" compares: where the item is, whose turn it is and how its latest run stands. */
|
|
174
|
-
function itemSignature(item:
|
|
181
|
+
function itemSignature(item: Item): string {
|
|
175
182
|
const lifecycle = lifecycleOf(item);
|
|
176
183
|
const run = latestBuildOf(item);
|
|
177
184
|
return JSON.stringify([
|
|
@@ -209,7 +216,7 @@ interface GateDefault {
|
|
|
209
216
|
* the gate cannot be resolved (no gate of its own, and the product's default
|
|
210
217
|
* unreadable) the answer is unknown, and the wait says so rather than guess.
|
|
211
218
|
*/
|
|
212
|
-
function personTurn(item:
|
|
219
|
+
function personTurn(item: Item, gate: GateDefault): 'yes' | 'no' | 'unknown' {
|
|
213
220
|
const lifecycle = lifecycleOf(item);
|
|
214
221
|
if (lifecycle.turn !== 'you') return 'no';
|
|
215
222
|
if (lifecycle.cta !== 'start_build') return 'yes';
|
|
@@ -219,7 +226,7 @@ function personTurn(item: Row, gate: GateDefault): 'yes' | 'no' | 'unknown' {
|
|
|
219
226
|
return resolveAutoBuild({ initiative: own, product: product ?? true }) ? 'no' : 'yes';
|
|
220
227
|
}
|
|
221
228
|
|
|
222
|
-
function itemHolds(until: ItemCondition, item:
|
|
229
|
+
function itemHolds(until: ItemCondition, item: Item, first: Item, gate: GateDefault): boolean {
|
|
223
230
|
const lifecycle = lifecycleOf(item);
|
|
224
231
|
const run = latestBuildOf(item);
|
|
225
232
|
switch (until) {
|
|
@@ -239,7 +246,7 @@ function itemHolds(until: ItemCondition, item: Row, first: Row, gate: GateDefaul
|
|
|
239
246
|
}
|
|
240
247
|
|
|
241
248
|
function judgeItem(until: ItemCondition, gate: GateDefault) {
|
|
242
|
-
return (item:
|
|
249
|
+
return (item: Item, first: Item): Verdict => {
|
|
243
250
|
if (itemHolds(until, item, first, gate)) return { reached: true };
|
|
244
251
|
// "changed" is how a model waits for a person to act on a flagged or
|
|
245
252
|
// finished initiative, so none of the stops below apply to it.
|
|
@@ -276,7 +283,7 @@ function judgeItem(until: ItemCondition, gate: GateDefault) {
|
|
|
276
283
|
}
|
|
277
284
|
|
|
278
285
|
/** The initiative as a wait reports it: where it is, whose turn, its latest run and its criteria. */
|
|
279
|
-
function itemReport(item:
|
|
286
|
+
function itemReport(item: Item) {
|
|
280
287
|
const run = latestBuildOf(item);
|
|
281
288
|
return {
|
|
282
289
|
...pickFields(
|
|
@@ -316,11 +323,11 @@ const RUN_REPORT_FIELDS = [
|
|
|
316
323
|
'actions',
|
|
317
324
|
] as const;
|
|
318
325
|
|
|
319
|
-
const runSignature = (run:
|
|
326
|
+
const runSignature = (run: Run) =>
|
|
320
327
|
JSON.stringify([run.status, run.completedSteps, run.prUrl, run.prState, run.prMerged]);
|
|
321
328
|
|
|
322
329
|
function judgeBuild(until: BuildCondition) {
|
|
323
|
-
return (run:
|
|
330
|
+
return (run: Run, first: Run): Verdict => {
|
|
324
331
|
const holds =
|
|
325
332
|
until === 'changed'
|
|
326
333
|
? runSignature(run) !== runSignature(first)
|
|
@@ -367,14 +374,14 @@ export function registerWaitTools(server: McpServer, client: TaigaClient, option
|
|
|
367
374
|
runTool(async () => {
|
|
368
375
|
const gate: GateDefault = { product: undefined };
|
|
369
376
|
const read = async (signal: AbortSignal) => {
|
|
370
|
-
const item = (await client.getBacklogItem(params.itemId, signal)).data
|
|
377
|
+
const item = (await client.getBacklogItem(params.itemId, signal)).data;
|
|
371
378
|
// The product's default, once, when this initiative has no gate of its own to answer with.
|
|
372
379
|
if (gate.product === undefined && typeof item.autoBuild !== 'boolean') {
|
|
373
380
|
gate.product = client.hasCapability('products:read')
|
|
374
381
|
? await client
|
|
375
382
|
.getProduct(String(item.productId), signal)
|
|
376
383
|
.then((p) => {
|
|
377
|
-
const value =
|
|
384
|
+
const value = p.data.autoBuild;
|
|
378
385
|
return typeof value === 'boolean' ? value : true;
|
|
379
386
|
})
|
|
380
387
|
// Not allowed, or gone: unknown for good. Anything transient
|
|
@@ -427,7 +434,7 @@ export function registerWaitTools(server: McpServer, client: TaigaClient, option
|
|
|
427
434
|
(params, ctx) =>
|
|
428
435
|
runTool(async () => {
|
|
429
436
|
const result = await waitFor(
|
|
430
|
-
async (signal) => (await client.getBuildRun(params.buildId, signal)).data
|
|
437
|
+
async (signal) => (await client.getBuildRun(params.buildId, signal)).data,
|
|
431
438
|
judgeBuild(params.until),
|
|
432
439
|
(params.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS) * 1000,
|
|
433
440
|
options,
|
|
@@ -466,7 +473,7 @@ export function registerWaitTools(server: McpServer, client: TaigaClient, option
|
|
|
466
473
|
(params, ctx) =>
|
|
467
474
|
runTool(async () => {
|
|
468
475
|
const result = await waitFor(
|
|
469
|
-
async (signal) => (await client.getAgentJob(params.jobId, signal)).data
|
|
476
|
+
async (signal) => (await client.getAgentJob(params.jobId, signal)).data,
|
|
470
477
|
(job) => (SETTLED_JOB_STATUSES.has(String(job.status)) ? { reached: true } : null),
|
|
471
478
|
(params.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS) * 1000,
|
|
472
479
|
options,
|
|
Binary file
|
package/tsconfig.json
CHANGED
|
@@ -13,11 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
// @tsconfig/strictest leaves these two out, so every package sets them.
|
|
15
15
|
"verbatimModuleSyntax": true,
|
|
16
|
-
"erasableSyntaxOnly": true
|
|
17
|
-
|
|
18
|
-
// Preset flags this package still fails (PRO-2509). Each line is deleted
|
|
19
|
-
// by the issue named beside it; no new line is added here.
|
|
20
|
-
"noPropertyAccessFromIndexSignature": false // PRO-2519
|
|
16
|
+
"erasableSyntaxOnly": true
|
|
21
17
|
},
|
|
22
18
|
"include": ["src"],
|
|
23
19
|
"exclude": ["node_modules", "dist"],
|
|
Binary file
|