@taiga-ai/mcp-server 0.25.0-dev.476 → 0.25.0-dev.477
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
CHANGED
|
@@ -981,6 +981,19 @@ const INITIATIVE_STATES = [
|
|
|
981
981
|
"done",
|
|
982
982
|
"canceled"
|
|
983
983
|
];
|
|
984
|
+
/**
|
|
985
|
+
* The states the line works, and the only ones that carry its flag. One copy
|
|
986
|
+
* for every reader that means "on the line": the flag the API clears when an
|
|
987
|
+
* initiative leaves it, the remediation settle on the same departure, and the
|
|
988
|
+
* MCP's account of where an initiative stands (PRO-2840). Everything else
|
|
989
|
+
* (backlog, todo, done, canceled) is off it. The lifecycle derivation does not
|
|
990
|
+
* read it: it derives the stage from the run alone (PRO-2828).
|
|
991
|
+
*/
|
|
992
|
+
const LINE_STATES = [
|
|
993
|
+
"queue",
|
|
994
|
+
"plan",
|
|
995
|
+
"build"
|
|
996
|
+
];
|
|
984
997
|
/** Finished work: settled by a person or a merge, or archived. The one
|
|
985
998
|
* predicate every "can this still move / plan / build" guard reads. */
|
|
986
999
|
function isSettledOrArchived(item) {
|
|
@@ -4273,7 +4286,7 @@ const ITEM_OUTCOME_FIELDS = [
|
|
|
4273
4286
|
* needsHumanCause is only the line's flag; a plan that failed outside the line
|
|
4274
4287
|
* leaves it null, and the lifecycle is what says why and what moves it on.
|
|
4275
4288
|
*/
|
|
4276
|
-
const LIFECYCLE_MEANING = "situation says in a sentence where it stands and what moves it on. lifecycle.cta names the next call: start_plan taiga_queue_initiative, retry_plan taiga_plan_again (planFailureCause says why), start_build taiga_approve_plan, resume_build taiga_resume_initiative, retry_build a fresh run (web only), review_pr and view_pr the pull request. needsHumanCause is set only when the line stopped on it.";
|
|
4289
|
+
const LIFECYCLE_MEANING = "situation says in a sentence where it stands and what moves it on. lifecycle.cta names the next call: start_plan taiga_queue_initiative, retry_plan taiga_plan_again (planFailureCause says why), start_build taiga_approve_plan, resume_build taiga_resume_initiative, retry_build a fresh run (web only; off the line, taiga_queue_initiative instead, as situation says), review_pr and view_pr the pull request. needsHumanCause is set only when the line stopped on it.";
|
|
4277
4290
|
/** The move each call to action is, in the tools' words. */
|
|
4278
4291
|
const CTA_MOVE = {
|
|
4279
4292
|
start_plan: "taiga_queue_initiative puts it on the line to be planned and built (paid work: ask first)",
|
|
@@ -4284,6 +4297,10 @@ const CTA_MOVE = {
|
|
|
4284
4297
|
review_pr: "a person reviews and merges the pull request",
|
|
4285
4298
|
view_pr: "the line merges the pull request once its checks pass"
|
|
4286
4299
|
};
|
|
4300
|
+
/** The move for an initiative off the line whose last run did not land: there is no line to take it off (PRO-2840). */
|
|
4301
|
+
const OFF_THE_LINE_MOVE = "taiga_queue_initiative puts it back on the line (paid work: ask first)";
|
|
4302
|
+
/** What happens to a queued initiative whose last run did not land: the line plans it again (PRO-2840). */
|
|
4303
|
+
const REQUEUED_MOVE = "the line plans it again when its turn comes";
|
|
4287
4304
|
/** How a run stands, as the end of "its latest run ...", by disposition. */
|
|
4288
4305
|
const RUN_STANDS = {
|
|
4289
4306
|
in_flight: "is running",
|
|
@@ -4317,6 +4334,11 @@ function situationOf(item) {
|
|
|
4317
4334
|
const parts = [];
|
|
4318
4335
|
let stage = STAGE[life.stage] ?? life.stage;
|
|
4319
4336
|
if (life.stage === "planning" && life.turn === "you") stage = `Planning stopped${typeof life.planFailureCause === "string" ? ` (${life.planFailureCause})` : ""}`;
|
|
4337
|
+
const column = typeof item["state"] === "string" ? item["state"] : null;
|
|
4338
|
+
const offTheLine = column !== null && !LINE_STATES.includes(column);
|
|
4339
|
+
const stalled = life.stage === "building" && life.turn !== "agent";
|
|
4340
|
+
const requeued = stalled && column === "queue";
|
|
4341
|
+
if (stalled) stage = requeued ? "Queued" : offTheLine ? "Off the line" : column === null ? "Its build stopped" : "On the line, but its build stopped";
|
|
4320
4342
|
const build = life.latestBuild;
|
|
4321
4343
|
if (build && typeof build.status === "string" && life.stage !== "done" && life.stage !== "canceled") {
|
|
4322
4344
|
const pr = typeof build.prNumber === "number" ? ` (PR #${build.prNumber})` : "";
|
|
@@ -4336,8 +4358,9 @@ function situationOf(item) {
|
|
|
4336
4358
|
}
|
|
4337
4359
|
const turn = life.turn === "you" ? "A person's turn" : life.turn === "agent" ? "The agent's turn" : null;
|
|
4338
4360
|
const handedBack = handbackSaid(build?.reviewEscalation);
|
|
4339
|
-
const move = life.cta === "review_pr" && handedBack ? `a person should ${handbackNextStep(build.reviewEscalation)}` : typeof life.cta === "string" ? CTA_MOVE[life.cta] : void 0;
|
|
4340
|
-
if (
|
|
4361
|
+
const move = life.cta === "review_pr" && handedBack ? `a person should ${handbackNextStep(build.reviewEscalation)}` : stalled && offTheLine ? OFF_THE_LINE_MOVE : typeof life.cta === "string" ? CTA_MOVE[life.cta] : void 0;
|
|
4362
|
+
if (requeued) parts.push(`Nothing to do: ${REQUEUED_MOVE}.`);
|
|
4363
|
+
else if (life.cta === "view_pr") parts.push(`Nothing to do: ${move}.`);
|
|
4341
4364
|
else if (turn && move) parts.push(`${turn}: ${move}.`);
|
|
4342
4365
|
else if (turn) parts.push(`${turn}.`);
|
|
4343
4366
|
else if (move) parts.push(`Next: ${move}.`);
|
package/package.json
CHANGED
|
@@ -7978,12 +7978,67 @@ describe('what the third agent review found (PRO-2800)', () => {
|
|
|
7978
7978
|
);
|
|
7979
7979
|
|
|
7980
7980
|
const said =
|
|
7981
|
-
"
|
|
7981
|
+
"On the line, but its build stopped; its latest run had its pull request closed without merging (PR #28). A person's turn: a new " +
|
|
7982
7982
|
'run starts over from the web; taiga_move_initiative takes it off the line.';
|
|
7983
7983
|
expect(got.situation).toBe(said);
|
|
7984
7984
|
expect(listed.data[0].situation).toBe(said);
|
|
7985
7985
|
});
|
|
7986
7986
|
|
|
7987
|
+
it.each([
|
|
7988
|
+
['todo', 'Off the line', 'taiga_queue_initiative puts it back on the line (paid work: ask first)'],
|
|
7989
|
+
['build', 'On the line, but its build stopped', 'a new run starts over from the web'],
|
|
7990
|
+
['queue', 'Queued', 'Nothing to do: the line plans it again when its turn comes.'],
|
|
7991
|
+
])('says where a %s initiative with a dead run is, from its column (PRO-2840)', async (state, where, move) => {
|
|
7992
|
+
const item = {
|
|
7993
|
+
id: ITEM_ID,
|
|
7994
|
+
title: 'Add login',
|
|
7995
|
+
state,
|
|
7996
|
+
lifecycle: {
|
|
7997
|
+
turn: 'you',
|
|
7998
|
+
stage: 'building',
|
|
7999
|
+
cta: 'retry_build',
|
|
8000
|
+
planFailureCause: null,
|
|
8001
|
+
latestBuild: { status: 'closed', prNumber: 28 },
|
|
8002
|
+
},
|
|
8003
|
+
};
|
|
8004
|
+
const { client } = await bootMcpPair({
|
|
8005
|
+
capabilities: ['backlog:read'],
|
|
8006
|
+
routes: { [`GET /v1/backlog/${ITEM_ID}`]: { data: item } },
|
|
8007
|
+
});
|
|
8008
|
+
|
|
8009
|
+
const got = parseToolResult(
|
|
8010
|
+
await client.callTool({ name: 'taiga_get_initiative', arguments: { initiativeId: ITEM_ID } })
|
|
8011
|
+
);
|
|
8012
|
+
|
|
8013
|
+
expect(got.situation).toMatch(new RegExp(`^${where}; its latest run had its pull request closed`));
|
|
8014
|
+
expect(got.situation).toContain(move);
|
|
8015
|
+
expect(got.situation).not.toMatch(/Being built/);
|
|
8016
|
+
});
|
|
8017
|
+
|
|
8018
|
+
it('says Being built only while the agent is building it (PRO-2840)', async () => {
|
|
8019
|
+
const item = {
|
|
8020
|
+
id: ITEM_ID,
|
|
8021
|
+
title: 'Add login',
|
|
8022
|
+
lifecycle: {
|
|
8023
|
+
turn: 'agent',
|
|
8024
|
+
stage: 'building',
|
|
8025
|
+
cta: null,
|
|
8026
|
+
planFailureCause: null,
|
|
8027
|
+
latestBuild: { status: 'implementing', prNumber: null },
|
|
8028
|
+
},
|
|
8029
|
+
};
|
|
8030
|
+
const { client } = await bootMcpPair({
|
|
8031
|
+
capabilities: ['backlog:read'],
|
|
8032
|
+
routes: { [`GET /v1/backlog/${ITEM_ID}`]: { data: item } },
|
|
8033
|
+
});
|
|
8034
|
+
|
|
8035
|
+
const got = parseToolResult(
|
|
8036
|
+
await client.callTool({ name: 'taiga_get_initiative', arguments: { initiativeId: ITEM_ID } })
|
|
8037
|
+
);
|
|
8038
|
+
|
|
8039
|
+
expect(got.situation).toMatch(/^Being built; its latest run is running\. The agent's turn\./);
|
|
8040
|
+
});
|
|
8041
|
+
|
|
7987
8042
|
it("keeps the run's own reason beside the halt that stopped the line", async () => {
|
|
7988
8043
|
const item = {
|
|
7989
8044
|
id: ITEM_ID,
|
|
@@ -8002,7 +8057,9 @@ describe('what the third agent review found (PRO-2800)', () => {
|
|
|
8002
8057
|
);
|
|
8003
8058
|
|
|
8004
8059
|
expect(got.situation).toContain('The pull request was closed by alice.');
|
|
8005
|
-
expect(got.situation).toMatch(
|
|
8060
|
+
expect(got.situation).toMatch(
|
|
8061
|
+
/^Its build stopped\. The line stopped on it: .+\. The pull request was closed by alice\./
|
|
8062
|
+
);
|
|
8006
8063
|
});
|
|
8007
8064
|
|
|
8008
8065
|
it("leaves the keys of a document's sections as their writers named them", async () => {
|
package/src/tools/backlogs.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type BacklogItem,
|
|
7
7
|
BUILD_RUN_STATUSES,
|
|
8
8
|
INITIATIVE_STATES,
|
|
9
|
+
LINE_STATES,
|
|
9
10
|
PLAN_REJECTION_COMMENT_MAX_CHARS,
|
|
10
11
|
PR_CLOSING_STATES,
|
|
11
12
|
USER_DROP_STATES,
|
|
@@ -68,8 +69,9 @@ const ITEM_OUTCOME_FIELDS = [
|
|
|
68
69
|
const LIFECYCLE_MEANING =
|
|
69
70
|
'situation says in a sentence where it stands and what moves it on. lifecycle.cta names the next call: ' +
|
|
70
71
|
'start_plan taiga_queue_initiative, retry_plan taiga_plan_again (planFailureCause says why), start_build ' +
|
|
71
|
-
'taiga_approve_plan, resume_build taiga_resume_initiative, retry_build a fresh run (web only
|
|
72
|
-
'
|
|
72
|
+
'taiga_approve_plan, resume_build taiga_resume_initiative, retry_build a fresh run (web only; off the line, ' +
|
|
73
|
+
'taiga_queue_initiative instead, as situation says), review_pr and view_pr the pull request. needsHumanCause ' +
|
|
74
|
+
'is set only when the line stopped on it.';
|
|
73
75
|
|
|
74
76
|
/** The move each call to action is, in the tools' words. */
|
|
75
77
|
const CTA_MOVE: Record<string, string> = {
|
|
@@ -83,6 +85,12 @@ const CTA_MOVE: Record<string, string> = {
|
|
|
83
85
|
view_pr: 'the line merges the pull request once its checks pass',
|
|
84
86
|
};
|
|
85
87
|
|
|
88
|
+
/** The move for an initiative off the line whose last run did not land: there is no line to take it off (PRO-2840). */
|
|
89
|
+
const OFF_THE_LINE_MOVE = 'taiga_queue_initiative puts it back on the line (paid work: ask first)';
|
|
90
|
+
|
|
91
|
+
/** What happens to a queued initiative whose last run did not land: the line plans it again (PRO-2840). */
|
|
92
|
+
const REQUEUED_MOVE = 'the line plans it again when its turn comes';
|
|
93
|
+
|
|
86
94
|
/** How a run stands, as the end of "its latest run ...", by disposition. */
|
|
87
95
|
const RUN_STANDS: Record<string, string> = {
|
|
88
96
|
in_flight: 'is running',
|
|
@@ -123,6 +131,26 @@ export function situationOf(item: Record<string, unknown>): string | null {
|
|
|
123
131
|
if (life.stage === 'planning' && life.turn === 'you') {
|
|
124
132
|
stage = `Planning stopped${typeof life.planFailureCause === 'string' ? ` (${life.planFailureCause})` : ''}`;
|
|
125
133
|
}
|
|
134
|
+
// Being built only while the agent is at it (PRO-2840). With its run failed,
|
|
135
|
+
// stopped or its pull request closed, nothing is building: the run's outcome
|
|
136
|
+
// follows and the turn says whose move it is. The lifecycle derives this
|
|
137
|
+
// stage from the run alone, so an initiative already moved back to the
|
|
138
|
+
// backlog or todo is said to be off the line, which only its column knows.
|
|
139
|
+
// Queued, the line plans it again when its turn comes (activation replaces a dead run), so it is nobody's
|
|
140
|
+
// move. Without a column (an old row) it is said neutrally.
|
|
141
|
+
const column = typeof item['state'] === 'string' ? item['state'] : null;
|
|
142
|
+
const offTheLine = column !== null && !(LINE_STATES as readonly string[]).includes(column);
|
|
143
|
+
const stalled = life.stage === 'building' && life.turn !== 'agent';
|
|
144
|
+
const requeued = stalled && column === 'queue';
|
|
145
|
+
if (stalled) {
|
|
146
|
+
stage = requeued
|
|
147
|
+
? 'Queued'
|
|
148
|
+
: offTheLine
|
|
149
|
+
? 'Off the line'
|
|
150
|
+
: column === null
|
|
151
|
+
? 'Its build stopped'
|
|
152
|
+
: 'On the line, but its build stopped';
|
|
153
|
+
}
|
|
126
154
|
const build = life.latestBuild as { status?: unknown; prNumber?: unknown; prMerged?: unknown } | null | undefined;
|
|
127
155
|
if (build && typeof build.status === 'string' && life.stage !== 'done' && life.stage !== 'canceled') {
|
|
128
156
|
const pr = typeof build.prNumber === 'number' ? ` (PR #${build.prNumber})` : '';
|
|
@@ -151,10 +179,13 @@ export function situationOf(item: Record<string, unknown>): string | null {
|
|
|
151
179
|
const move =
|
|
152
180
|
life.cta === 'review_pr' && handedBack
|
|
153
181
|
? `a person should ${handbackNextStep((build as { reviewEscalation?: unknown }).reviewEscalation)}`
|
|
154
|
-
:
|
|
155
|
-
?
|
|
156
|
-
:
|
|
157
|
-
|
|
182
|
+
: stalled && offTheLine
|
|
183
|
+
? OFF_THE_LINE_MOVE
|
|
184
|
+
: typeof life.cta === 'string'
|
|
185
|
+
? CTA_MOVE[life.cta]
|
|
186
|
+
: undefined;
|
|
187
|
+
if (requeued) parts.push(`Nothing to do: ${REQUEUED_MOVE}.`);
|
|
188
|
+
else if (life.cta === 'view_pr') parts.push(`Nothing to do: ${move}.`);
|
|
158
189
|
else if (turn && move) parts.push(`${turn}: ${move}.`);
|
|
159
190
|
else if (turn) parts.push(`${turn}.`);
|
|
160
191
|
else if (move) parts.push(`Next: ${move}.`);
|
|
Binary file
|
|
Binary file
|