@taiga-ai/mcp-server 0.12.0-rc.419 → 0.13.0-dev.423
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 +351 -87
- package/package.json +1 -1
- package/src/client.ts +34 -21
- package/src/server.integration.test.ts +295 -92
- package/src/tools/annotations.ts +6 -0
- package/src/tools/backlogs.ts +119 -59
- package/src/tools/builds.ts +13 -29
- package/src/tools/index.ts +2 -0
- package/src/tools/instructions.ts +19 -13
- package/src/tools/jobs.ts +89 -0
- package/src/tools/queue.ts +61 -4
- package/src/tools/request-prompt.test.ts +32 -0
- package/src/tools/request-prompt.ts +28 -0
- package/src/tools/wait.ts +38 -0
- package/taiga-ai-mcp-server-0.13.0-dev.423.tgz +0 -0
- package/taiga-ai-mcp-server-0.12.0-rc.419.tgz +0 -0
package/dist/index.js
CHANGED
|
@@ -211,6 +211,21 @@ var TaigaClient = class {
|
|
|
211
211
|
const qs = params.toString();
|
|
212
212
|
return this.request("GET", `/products/${seg(productId)}/documents${qs ? `?${qs}` : ""}`);
|
|
213
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Stop or start the product's line. Stopping halts pulls and builds and
|
|
216
|
+
* stops the plan and build in flight; starting picks the interrupted work
|
|
217
|
+
* back up.
|
|
218
|
+
*/
|
|
219
|
+
async setLineStopped(productId, stopped) {
|
|
220
|
+
return this.request("PUT", `/products/${seg(productId)}/line`, { stopped });
|
|
221
|
+
}
|
|
222
|
+
/** Ask an agent for work: the job is queued and runs in the background. */
|
|
223
|
+
async createAgentJob(body) {
|
|
224
|
+
return this.request("POST", "/agent-jobs", body);
|
|
225
|
+
}
|
|
226
|
+
async getAgentJob(id, signal) {
|
|
227
|
+
return this.request("GET", `/agent-jobs/${seg(id)}`, void 0, { signal });
|
|
228
|
+
}
|
|
214
229
|
async getProductDocument(productId, type, origin) {
|
|
215
230
|
const qs = origin ? `?${new URLSearchParams({ origin })}` : "";
|
|
216
231
|
return this.request("GET", `/products/${seg(productId)}/documents/${seg(type)}${qs}`);
|
|
@@ -229,12 +244,6 @@ var TaigaClient = class {
|
|
|
229
244
|
async getBacklogItem(itemId, signal) {
|
|
230
245
|
return this.request("GET", `/backlog/${seg(itemId)}`, void 0, { signal });
|
|
231
246
|
}
|
|
232
|
-
async createBacklogItem(productId, body) {
|
|
233
|
-
return this.request("POST", `/products/${seg(productId)}/backlog`, {
|
|
234
|
-
...body,
|
|
235
|
-
productId
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
247
|
/** A person's move on the line board: the column dropped into, and what to follow there. */
|
|
239
248
|
async transitionInitiative(itemId, body) {
|
|
240
249
|
return this.request("PUT", `/backlog/${seg(itemId)}/state`, body);
|
|
@@ -283,9 +292,6 @@ var TaigaClient = class {
|
|
|
283
292
|
async stopBuildRun(id) {
|
|
284
293
|
return this.request("POST", `/builds/${seg(id)}/stop`);
|
|
285
294
|
}
|
|
286
|
-
async resumeBuildRun(id) {
|
|
287
|
-
return this.request("POST", `/builds/${seg(id)}/resume`);
|
|
288
|
-
}
|
|
289
295
|
async listBuildRunSteps(id) {
|
|
290
296
|
return this.request("GET", `/builds/${seg(id)}/steps`);
|
|
291
297
|
}
|
|
@@ -329,13 +335,6 @@ const READ_ONLY = {
|
|
|
329
335
|
idempotentHint: true,
|
|
330
336
|
openWorldHint: false
|
|
331
337
|
};
|
|
332
|
-
/** A write that adds or edits a row and starts nothing: a created initiative, a moved one. */
|
|
333
|
-
const WRITE = {
|
|
334
|
-
readOnlyHint: false,
|
|
335
|
-
destructiveHint: false,
|
|
336
|
-
idempotentHint: false,
|
|
337
|
-
openWorldHint: false
|
|
338
|
-
};
|
|
339
338
|
/**
|
|
340
339
|
* A write that starts or stops paid work, or opens the door to it: queueing
|
|
341
340
|
* an initiative, approving a plan, resuming or stopping a run. Each starts
|
|
@@ -347,6 +346,14 @@ const OPERATE = {
|
|
|
347
346
|
idempotentHint: false,
|
|
348
347
|
openWorldHint: false
|
|
349
348
|
};
|
|
349
|
+
/**
|
|
350
|
+
* An OPERATE write that sets a value rather than adding one: the same call
|
|
351
|
+
* twice leaves things as one call does (a review gate set, a line paused).
|
|
352
|
+
*/
|
|
353
|
+
const OPERATE_SETTING = {
|
|
354
|
+
...OPERATE,
|
|
355
|
+
idempotentHint: true
|
|
356
|
+
};
|
|
350
357
|
//#endregion
|
|
351
358
|
//#region src/tools/inputs.ts
|
|
352
359
|
/** The inputs several tools share, so a product or a page is named the same way everywhere. */
|
|
@@ -617,15 +624,7 @@ const PR_CLOSING_STATES = [
|
|
|
617
624
|
"todo",
|
|
618
625
|
"canceled"
|
|
619
626
|
];
|
|
620
|
-
|
|
621
|
-
* What `PUT /backlog/:id/state` accepts: the drops, plus `plan`.
|
|
622
|
-
*
|
|
623
|
-
* `plan` is not a column a person drops into any more, but it is still a state
|
|
624
|
-
* the endpoint takes -- it is how the Build station is told to start over
|
|
625
|
-
* ("Plan again"). One endpoint for both because they are the same write: the
|
|
626
|
-
* initiative's state, decided by a person.
|
|
627
|
-
*/
|
|
628
|
-
const INITIATIVE_TRANSITION_STATES = [...USER_DROP_STATES, "plan"];
|
|
627
|
+
[...USER_DROP_STATES];
|
|
629
628
|
/** Longest rejection comment on a plan (PRO-1324): a paragraph of direction,
|
|
630
629
|
* small enough that the prompt section it becomes stays a section. */
|
|
631
630
|
const PLAN_REJECTION_COMMENT_MAX_CHARS = 2e3;
|
|
@@ -965,6 +964,91 @@ const BUILD_RUN_ACTIVE_STATUSES = BUILD_RUN_STATUSES.filter((s) => runDispositio
|
|
|
965
964
|
*/
|
|
966
965
|
const BUILD_RUN_TERMINAL_STATUSES = BUILD_RUN_STATUSES.filter((s) => runDisposition(s) !== "in_flight");
|
|
967
966
|
[...BUILD_RUN_ACTIVE_STATUSES.filter((s) => s !== "reviewing")];
|
|
967
|
+
//#endregion
|
|
968
|
+
//#region ../../packages/types/dist/agents.js
|
|
969
|
+
/**
|
|
970
|
+
* Why a PROMPTED `generate_backlog` job planned nothing.
|
|
971
|
+
*
|
|
972
|
+
* A prompted add asks the planner a question, and "no new initiative" is one of
|
|
973
|
+
* its honest answers -- the backlog already covers the request, the request is
|
|
974
|
+
* not a piece of work, or it is outside what this project builds. None of those
|
|
975
|
+
* is a failed job, so each arrives as a completed one carrying the reason.
|
|
976
|
+
*
|
|
977
|
+
* The INITIAL bulk generate has no prompt and no such answer: an empty backlog
|
|
978
|
+
* there is a broken run and still fails.
|
|
979
|
+
*/
|
|
980
|
+
const BACKLOG_GENERATION_EMPTY_OUTCOMES = [
|
|
981
|
+
"already_covered",
|
|
982
|
+
"invalid_request",
|
|
983
|
+
"out_of_scope",
|
|
984
|
+
"needs_decision"
|
|
985
|
+
];
|
|
986
|
+
function isRecord$1(value) {
|
|
987
|
+
return typeof value === "object" && value !== null;
|
|
988
|
+
}
|
|
989
|
+
function isStringOrNull(value) {
|
|
990
|
+
return value === null || typeof value === "string";
|
|
991
|
+
}
|
|
992
|
+
function isDecisionReason(value) {
|
|
993
|
+
if (!isRecord$1(value)) return false;
|
|
994
|
+
const { policyCode, quotedSpan, conflict, alternative, overridePath } = value;
|
|
995
|
+
return [
|
|
996
|
+
policyCode,
|
|
997
|
+
quotedSpan,
|
|
998
|
+
conflict,
|
|
999
|
+
alternative,
|
|
1000
|
+
overridePath
|
|
1001
|
+
].every((f) => typeof f === "string");
|
|
1002
|
+
}
|
|
1003
|
+
function isCoveringInitiative(value) {
|
|
1004
|
+
return isRecord$1(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.title === "string" && typeof value.priority === "number";
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Narrow an `agent_jobs.result` blob to {@link BacklogGenerationEmptyResult}.
|
|
1008
|
+
*
|
|
1009
|
+
* The blob is whatever the worker stored, and the reader navigates on
|
|
1010
|
+
* `coveredInitiative.id` and renders `message`, so EVERY field is checked: a
|
|
1011
|
+
* blob that only has the right `outcome` is not this shape, and narrowing it
|
|
1012
|
+
* would hand the page an undefined id to navigate to.
|
|
1013
|
+
*/
|
|
1014
|
+
function isBacklogGenerationEmptyResult(value) {
|
|
1015
|
+
if (!isRecord$1(value)) return false;
|
|
1016
|
+
const { outcome, message, coveredByPriority, coveredById, coveredInitiative, reason } = value;
|
|
1017
|
+
return typeof outcome === "string" && BACKLOG_GENERATION_EMPTY_OUTCOMES.includes(outcome) && isStringOrNull(message) && (coveredByPriority === null || typeof coveredByPriority === "number" && Number.isInteger(coveredByPriority)) && isStringOrNull(coveredById) && (coveredInitiative === null || isCoveringInitiative(coveredInitiative)) && (reason === null || reason === void 0 || isDecisionReason(reason));
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* The initiatives a `generate_backlog` job created, from its stored result
|
|
1021
|
+
* (PRO-2483), or null when the result does not say: a job finished before the
|
|
1022
|
+
* worker recorded them, a verdict that created nothing, or a blob that is not
|
|
1023
|
+
* this shape. Every entry must be a non-empty string, or none is trusted --
|
|
1024
|
+
* the reader navigates on them.
|
|
1025
|
+
*/
|
|
1026
|
+
function createdInitiativeIdsOf(result) {
|
|
1027
|
+
if (!isRecord$1(result)) return null;
|
|
1028
|
+
const ids = result.createdInitiativeIds;
|
|
1029
|
+
if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string" && id.length > 0)) return null;
|
|
1030
|
+
return ids;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* The initiatives a `generate_backlog` run added, each with the title it was
|
|
1034
|
+
* created with, as the worker writes them on `agent_jobs.result.createdInitiatives`
|
|
1035
|
+
* (PRO-2496), in the order they were created: what a reader needs to NAME
|
|
1036
|
+
* them, where {@link createdInitiativeIdsOf} (PRO-2483) only links them.
|
|
1037
|
+
*
|
|
1038
|
+
* Null when the result carries no such list: a run from before PRO-2496, which
|
|
1039
|
+
* may still carry the ids alone, a run that added nothing (see
|
|
1040
|
+
* {@link isBacklogGenerationEmptyResult}), or a run still going. A list with a malformed entry is null too rather than partly
|
|
1041
|
+
* read, so a reader never links an undefined id.
|
|
1042
|
+
*/
|
|
1043
|
+
function createdInitiativesOf(result) {
|
|
1044
|
+
if (!isRecord$1(result)) return null;
|
|
1045
|
+
const list = result.createdInitiatives;
|
|
1046
|
+
if (!Array.isArray(list) || list.length === 0) return null;
|
|
1047
|
+
return list.every((entry) => isRecord$1(entry) && typeof entry.id === "string" && entry.id.length > 0 && typeof entry.title === "string") ? list.map((entry) => ({
|
|
1048
|
+
id: entry.id,
|
|
1049
|
+
title: entry.title
|
|
1050
|
+
})) : null;
|
|
1051
|
+
}
|
|
968
1052
|
[...TURN_EVENT_TRIGGERS];
|
|
969
1053
|
//#endregion
|
|
970
1054
|
//#region ../../packages/types/dist/maintenance.js
|
|
@@ -1259,7 +1343,8 @@ Object.freeze({
|
|
|
1259
1343
|
"privacy_assessment",
|
|
1260
1344
|
"threat_model",
|
|
1261
1345
|
"technology_decisions",
|
|
1262
|
-
"architecture"
|
|
1346
|
+
"architecture",
|
|
1347
|
+
"data_flow"
|
|
1263
1348
|
]
|
|
1264
1349
|
},
|
|
1265
1350
|
service_blueprint: {
|
|
@@ -2162,6 +2247,105 @@ function registerDocumentTools(server, client) {
|
|
|
2162
2247
|
}
|
|
2163
2248
|
}
|
|
2164
2249
|
//#endregion
|
|
2250
|
+
//#region src/tools/jobs.ts
|
|
2251
|
+
/** What a job says about itself: its kind, how far it got, and why it failed. */
|
|
2252
|
+
const JOB_FIELDS = [
|
|
2253
|
+
"id",
|
|
2254
|
+
"type",
|
|
2255
|
+
"documentType",
|
|
2256
|
+
"targetId",
|
|
2257
|
+
"status",
|
|
2258
|
+
"progress",
|
|
2259
|
+
"progressMessage",
|
|
2260
|
+
"error",
|
|
2261
|
+
"queuePosition",
|
|
2262
|
+
"createdAt",
|
|
2263
|
+
"startedAt",
|
|
2264
|
+
"completedAt",
|
|
2265
|
+
"cancelledAt"
|
|
2266
|
+
];
|
|
2267
|
+
/** A job that will not change again: it finished, failed, or was cancelled. */
|
|
2268
|
+
const SETTLED_JOB_STATUSES = /* @__PURE__ */ new Set([
|
|
2269
|
+
"completed",
|
|
2270
|
+
"failed",
|
|
2271
|
+
"cancelled"
|
|
2272
|
+
]);
|
|
2273
|
+
/**
|
|
2274
|
+
* What an initiative request came to, read off its finished job: the
|
|
2275
|
+
* initiatives it added, or the answer that added none (the backlog already
|
|
2276
|
+
* covers it, it is not work to build, it is outside the product, or one of
|
|
2277
|
+
* the organization's policies does not allow it as asked, with the policy's
|
|
2278
|
+
* own words and the way to decide otherwise). Null while the job runs, and
|
|
2279
|
+
* for a job that is not an initiative request.
|
|
2280
|
+
*/
|
|
2281
|
+
function requestOutcome(job) {
|
|
2282
|
+
if (job.type !== "generate_backlog" || job.status !== "completed") return null;
|
|
2283
|
+
const created = createdInitiativesOf(job.result);
|
|
2284
|
+
if (created) return {
|
|
2285
|
+
outcome: "created",
|
|
2286
|
+
initiatives: created
|
|
2287
|
+
};
|
|
2288
|
+
const ids = createdInitiativeIdsOf(job.result);
|
|
2289
|
+
if (ids && ids.length > 0) return {
|
|
2290
|
+
outcome: "created",
|
|
2291
|
+
initiatives: ids.map((id) => ({ id }))
|
|
2292
|
+
};
|
|
2293
|
+
if (isBacklogGenerationEmptyResult(job.result)) {
|
|
2294
|
+
const result = job.result;
|
|
2295
|
+
return {
|
|
2296
|
+
outcome: result.outcome,
|
|
2297
|
+
message: result.message,
|
|
2298
|
+
coveredBy: result.coveredInitiative,
|
|
2299
|
+
policy: result.reason ?? null
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
return {
|
|
2303
|
+
outcome: "unknown",
|
|
2304
|
+
note: "The job finished without a result this server can read."
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
/** A job as the tools hand it over: its own fields, and for an initiative request, what came of it. */
|
|
2308
|
+
function jobReport(job) {
|
|
2309
|
+
const outcome = requestOutcome(job);
|
|
2310
|
+
return {
|
|
2311
|
+
...pickFields([job], JOB_FIELDS)[0],
|
|
2312
|
+
...outcome ? { outcome } : {}
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
function registerJobTools(server, client) {
|
|
2316
|
+
if (!client.hasCapability("products:read")) return;
|
|
2317
|
+
server.registerTool("taiga_get_job", {
|
|
2318
|
+
title: "Get an agent job",
|
|
2319
|
+
description: "Read one agent job: background work a person asked an agent for, such as an initiative request (taiga_request_initiatives). It says where the job is (pending, processing, completed, failed, cancelled), its progress and any error. A finished initiative request also says what came of it: the initiatives it added, or why it added none (already covered by an existing initiative, not readable as work, outside the product, or not allowed by a policy, with the policy, the line it runs into and the way to decide otherwise). taiga_wait_for_job waits for a job to finish.",
|
|
2320
|
+
inputSchema: { jobId: z.string().uuid().describe("Agent job ID -- a request tool returns it") },
|
|
2321
|
+
annotations: READ_ONLY
|
|
2322
|
+
}, (params) => runTool(async () => toolResult(jobReport((await client.getAgentJob(params.jobId)).data))));
|
|
2323
|
+
}
|
|
2324
|
+
//#endregion
|
|
2325
|
+
//#region src/tools/request-prompt.ts
|
|
2326
|
+
/**
|
|
2327
|
+
* The planner's prompt for an initiative request, built as the web builds it
|
|
2328
|
+
* (apps/web/src/features/loops/delivery/components/planning/generateBacklogJob.ts):
|
|
2329
|
+
* title and description trimmed, the description's inline-image markdown
|
|
2330
|
+
* removed and the blank lines it leaves collapsed, joined by a blank line.
|
|
2331
|
+
* The planner reads `userPrompt` and nothing else of the request, so the same
|
|
2332
|
+
* words must make the same prompt from either surface.
|
|
2333
|
+
*
|
|
2334
|
+
* The image pattern is the web's (inline-image-doc-ids.ts), which keeps it in
|
|
2335
|
+
* lockstep with the API's `extractInlineImageDocIds`: the `` form,
|
|
2336
|
+
* an exact UUID, and a terminator after `/image`.
|
|
2337
|
+
*/
|
|
2338
|
+
const INLINE_IMAGE_RE = /!\[(?:[^\]\\]|\\.)*\]\([^)]*?\/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;
|
|
2339
|
+
/** The description without its inline images, as the web strips it. */
|
|
2340
|
+
function stripInlineImages(markdown) {
|
|
2341
|
+
return markdown.replace(INLINE_IMAGE_RE, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
2342
|
+
}
|
|
2343
|
+
/** The prompt for a title and a description; undefined when both are empty, which asks for a suggestion. */
|
|
2344
|
+
function requestPrompt(title, description) {
|
|
2345
|
+
const body = stripInlineImages((description ?? "").trim());
|
|
2346
|
+
return [(title ?? "").trim(), body].filter(Boolean).join("\n\n") || void 0;
|
|
2347
|
+
}
|
|
2348
|
+
//#endregion
|
|
2165
2349
|
//#region src/tools/queue.ts
|
|
2166
2350
|
/** How many entries the read names; past this an entry keeps its backlogItemId only. */
|
|
2167
2351
|
const NAMED_ENTRIES = 25;
|
|
@@ -2219,7 +2403,8 @@ function waitingOn(rows) {
|
|
|
2219
2403
|
* rather than failing the queue.
|
|
2220
2404
|
*/
|
|
2221
2405
|
async function queueView(client, product, options = { name: "all" }) {
|
|
2222
|
-
const
|
|
2406
|
+
const [queue, paused] = await Promise.all([client.listQueue(product), linePausedSince(client, product)]);
|
|
2407
|
+
const entries = queue.data.filter((entry) => entry.state !== "settled");
|
|
2223
2408
|
const canName = client.hasCapability("backlog:read");
|
|
2224
2409
|
const blocking = blockingIndex(entries, (entry) => entry.state);
|
|
2225
2410
|
const toName = !canName ? [] : options.name === "all" ? entries.slice(0, NAMED_ENTRIES).map((_, index) => index) : blocking >= 0 ? [blocking] : [];
|
|
@@ -2270,20 +2455,52 @@ async function queueView(client, product, options = { name: "all" }) {
|
|
|
2270
2455
|
return row;
|
|
2271
2456
|
});
|
|
2272
2457
|
const note = unnamed === 0 ? null : !canName ? "Entries carry backlogItemId only: naming them needs backlog:read, which this key lacks." : entries.length > NAMED_ENTRIES ? `Only the first ${NAMED_ENTRIES} entries are named; the rest carry backlogItemId only.` : "An entry whose initiative could not be read carries backlogItemId only; taiga_get_backlog_item reads it.";
|
|
2458
|
+
const line = typeof paused === "string" ? `The line is paused (since ${paused}): nothing is planned or built until a person resumes it.` : null;
|
|
2273
2459
|
return {
|
|
2460
|
+
linePaused: paused === null ? null : typeof paused === "string",
|
|
2274
2461
|
entries: rows,
|
|
2275
|
-
waitingOn: waitingOn(rows),
|
|
2462
|
+
waitingOn: line ?? waitingOn(rows),
|
|
2276
2463
|
...note ? { note } : {}
|
|
2277
2464
|
};
|
|
2278
2465
|
}
|
|
2466
|
+
/**
|
|
2467
|
+
* When the product's line was paused, false when it runs, null when the
|
|
2468
|
+
* product cannot be read (the queue is still worth reading without it).
|
|
2469
|
+
*/
|
|
2470
|
+
async function linePausedSince(client, product) {
|
|
2471
|
+
try {
|
|
2472
|
+
const row = (await client.getProduct(product)).data;
|
|
2473
|
+
return typeof row.lineStoppedAt === "string" ? row.lineStoppedAt : false;
|
|
2474
|
+
} catch (error) {
|
|
2475
|
+
if (error instanceof TaigaApiError || isNetworkFailure(error)) return null;
|
|
2476
|
+
throw error;
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2279
2479
|
function registerQueueTools(server, client) {
|
|
2280
2480
|
if (!client.hasCapability("products:read")) return;
|
|
2281
2481
|
server.registerTool("taiga_get_queue", {
|
|
2282
2482
|
title: "Get the product queue",
|
|
2283
|
-
description: "Read a product's queue, the line the coding agent works one initiative at a time: each entry in order with its title, where it is (planning, building, in review) and whose turn it is, a halted entry with why it stopped and what lifts it, and waitingOn, one sentence saying what the line is waiting for. The line advances only when the active entry's pull request merges, so an entry waiting on a review holds everything behind it. Use this when a queued initiative is not moving.",
|
|
2483
|
+
description: "Read a product's queue, the line the coding agent works one initiative at a time: each entry in order with its title, where it is (planning, building, in review) and whose turn it is, a halted entry with why it stopped and what lifts it, linePaused (null when the product could not be read), and waitingOn, one sentence saying what the line is waiting for. The line advances only when the active entry's pull request merges, so an entry waiting on a review holds everything behind it. Use this when a queued initiative is not moving.",
|
|
2284
2484
|
inputSchema: { productId },
|
|
2285
2485
|
annotations: READ_ONLY
|
|
2286
2486
|
}, (params) => runTool(async () => toolResult(await queueView(client, params.productId))));
|
|
2487
|
+
if (!client.hasCapability("products:write")) return;
|
|
2488
|
+
server.registerTool("taiga_pause_line", {
|
|
2489
|
+
title: "Pause or resume a product's line",
|
|
2490
|
+
description: "Pause the product's whole line, or resume it. Pausing cancels the plan being written and stops the build in flight where it stands (both are flagged), and nothing more is planned or built; initiatives keep their places. Resuming plans the interrupted initiative again from scratch, carries the interrupted build on from its first incomplete step, and the line picks up where it was; a build you had stopped yourself before pausing stays stopped. Idempotent. Pausing interrupts work and resuming starts it: read taiga_get_queue first to see what it would interrupt, and ask the person.",
|
|
2491
|
+
inputSchema: {
|
|
2492
|
+
productId,
|
|
2493
|
+
paused: z.boolean().describe("true pauses the line, false resumes it")
|
|
2494
|
+
},
|
|
2495
|
+
annotations: OPERATE_SETTING
|
|
2496
|
+
}, (params) => runTool(async () => {
|
|
2497
|
+
const row = (await client.setLineStopped(params.productId, params.paused)).data;
|
|
2498
|
+
return toolResult({
|
|
2499
|
+
productId: row.id,
|
|
2500
|
+
linePaused: typeof row.lineStoppedAt === "string",
|
|
2501
|
+
lineStoppedAt: row.lineStoppedAt ?? null
|
|
2502
|
+
});
|
|
2503
|
+
}));
|
|
2287
2504
|
}
|
|
2288
2505
|
//#endregion
|
|
2289
2506
|
//#region src/tools/backlogs.ts
|
|
@@ -2351,23 +2568,32 @@ const outcomeOf = (item) => {
|
|
|
2351
2568
|
const outcome = (item) => toolResult(outcomeOf(item));
|
|
2352
2569
|
/**
|
|
2353
2570
|
* Each tool is registered under the capability its route requires, so a
|
|
2354
|
-
* key sees exactly what it may call: reads on backlog:read
|
|
2355
|
-
*
|
|
2356
|
-
*
|
|
2357
|
-
* resume and the review gate on products:write alone, as
|
|
2358
|
-
* (the item ID they take also comes back from a
|
|
2359
|
-
* products:write together with backlog:read,
|
|
2360
|
-
* comes only from the plan read
|
|
2361
|
-
*
|
|
2571
|
+
* key sees exactly what it may call: reads on backlog:read; the board move on
|
|
2572
|
+
* backlog:write; queueing on both backlog:write and products:write (the
|
|
2573
|
+
* state route checks both for a drop into queue); an initiative request
|
|
2574
|
+
* (an agent job), the resume and the review gate on products:write alone, as
|
|
2575
|
+
* their routes are (the item ID they take also comes back from a request or a
|
|
2576
|
+
* queue); the plan verdicts on products:write together with backlog:read,
|
|
2577
|
+
* because the plan ID they take comes only from the plan read; and Plan again
|
|
2578
|
+
* on the state route's two (backlog:write, then products:write for a move
|
|
2579
|
+
* into plan) together with backlog:read, because it reads the initiative's
|
|
2580
|
+
* place at the Build station first.
|
|
2581
|
+
*
|
|
2582
|
+
* The tools follow how Taiga is meant to be used (docs.tai.ga, Deliver):
|
|
2583
|
+
* new work is described and an agent writes the initiative; the queue is
|
|
2584
|
+
* the only way to start work; a run is stopped by moving its initiative out
|
|
2585
|
+
* of Build and carried on from the initiative.
|
|
2362
2586
|
*/
|
|
2363
2587
|
function registerBacklogTools(server, client) {
|
|
2364
2588
|
const may = (capability) => client.hasCapability(capability);
|
|
2365
2589
|
if (may("backlog:read")) registerBacklogReads(server, client);
|
|
2366
|
-
if (may("backlog:write"))
|
|
2590
|
+
if (may("backlog:write")) registerMove(server, client);
|
|
2591
|
+
if (may("products:write")) registerRequest(server, client);
|
|
2367
2592
|
if (may("backlog:write") && may("products:write")) registerQueue(server, client);
|
|
2368
2593
|
if (may("products:write")) registerResume(server, client);
|
|
2369
2594
|
if (may("products:write")) registerReviewGate(server, client);
|
|
2370
2595
|
if (may("backlog:read") && may("products:write")) registerPlanVerdicts(server, client);
|
|
2596
|
+
if (may("backlog:read") && may("backlog:write") && may("products:write")) registerPlanAgain(server, client);
|
|
2371
2597
|
}
|
|
2372
2598
|
function registerBacklogReads(server, client) {
|
|
2373
2599
|
server.registerTool("taiga_list_backlog_items", {
|
|
@@ -2403,7 +2629,7 @@ function registerBacklogReads(server, client) {
|
|
|
2403
2629
|
}));
|
|
2404
2630
|
server.registerTool("taiga_get_backlog_item", {
|
|
2405
2631
|
title: "Get backlog item",
|
|
2406
|
-
description: "Get one backlog item in full: description, acceptance criteria (and which are met), technical context (patterns, constraints,
|
|
2632
|
+
description: "Get one backlog item in full: description, acceptance criteria (and which are met), technical context (patterns, constraints, and the assumptions to verify before starting), dependencies and source documents. Read this before implementing an item, and read its runs with taiga_list_builds to see what the coding agent already did.",
|
|
2407
2633
|
inputSchema: { itemId: z.string().uuid().describe("Backlog item ID -- get from taiga_list_backlog_items") },
|
|
2408
2634
|
annotations: READ_ONLY
|
|
2409
2635
|
}, (params) => runTool(async () => {
|
|
@@ -2427,46 +2653,65 @@ function registerBacklogReads(server, client) {
|
|
|
2427
2653
|
});
|
|
2428
2654
|
}));
|
|
2429
2655
|
}
|
|
2430
|
-
function
|
|
2431
|
-
server.registerTool("
|
|
2432
|
-
title: "
|
|
2433
|
-
description: "
|
|
2656
|
+
function registerRequest(server, client) {
|
|
2657
|
+
server.registerTool("taiga_request_initiatives", {
|
|
2658
|
+
title: "Request new initiatives",
|
|
2659
|
+
description: "Add work the way Taiga adds it: describe it, and an agent turns the request into initiatives in the product's Backlog. The agent reads the product documents and its repository, expands the request into a full brief, and sizes it: one piece of work stays one initiative, a program of work comes back as an ordered set with its dependencies linked. It can also add nothing, and says why: an existing initiative already covers the request, the request is not readable as work, it is outside what the product builds, or one of the organization's policies does not allow it as asked (named, with the line it runs into and the way to decide otherwise). Leave both title and description out and the agent suggests the one next initiative the product most needs. It runs in the background, a few minutes: this returns the job, and taiga_wait_for_job or taiga_get_job (both need products:read) say what came of it. One request per product at a time (CONFLICT while one runs). Inline images in the description are left out; the web is where a request carries images. It starts nothing on the line: new initiatives land in Backlog. Runs an agent; ask the person first.",
|
|
2434
2660
|
inputSchema: {
|
|
2435
2661
|
productId,
|
|
2436
|
-
title: z.string().
|
|
2437
|
-
description: z.string().
|
|
2438
|
-
category: z.enum([
|
|
2439
|
-
"feature",
|
|
2440
|
-
"bug",
|
|
2441
|
-
"tech_debt",
|
|
2442
|
-
"improvement"
|
|
2443
|
-
]).optional().describe("What kind of work (default feature)"),
|
|
2444
|
-
priority: z.number().int().min(1).optional().describe("1 is the most urgent (default 3)"),
|
|
2445
|
-
estimate: z.number().int().min(0).optional().describe("Story points, 0 for unestimated (default 0)")
|
|
2662
|
+
title: z.string().max(500).optional().describe("What the work is, in a line"),
|
|
2663
|
+
description: z.string().max(2e4).optional().describe("The request in Markdown: the problem, what it should look like, what it covers and leaves alone")
|
|
2446
2664
|
},
|
|
2447
|
-
annotations:
|
|
2665
|
+
annotations: OPERATE
|
|
2448
2666
|
}, (params) => runTool(async () => {
|
|
2449
|
-
const
|
|
2450
|
-
|
|
2667
|
+
const userPrompt = requestPrompt(params.title, params.description);
|
|
2668
|
+
const job = (await client.createAgentJob({
|
|
2669
|
+
scope: "product",
|
|
2670
|
+
type: "generate_backlog",
|
|
2671
|
+
targetId: params.productId,
|
|
2672
|
+
...userPrompt ? { metadata: { userPrompt } } : {}
|
|
2673
|
+
})).data;
|
|
2674
|
+
return toolResult(jobReport(job));
|
|
2451
2675
|
}));
|
|
2676
|
+
}
|
|
2677
|
+
function registerMove(server, client) {
|
|
2452
2678
|
server.registerTool("taiga_move_backlog_item", {
|
|
2453
2679
|
title: "Move backlog item on the board",
|
|
2454
|
-
description: `Move an initiative
|
|
2680
|
+
description: `Move an initiative on the board, or order it within a column: backlog and todo are yours and the line never moves an initiative out of them; queue puts it in the line (taiga_queue_backlog_item says more); done and canceled finish it by hand. The queue is the only way to start work: there is no "plan this now", and nothing is moved into Build by a person. Moving an initiative out of Build stops the plan being written or the run in flight: that is how work is stopped, and it throws that work away. A move to ${PR_CLOSING_STATES.join(", ")} CLOSES a pull request the initiative is still waiting on (a move to queue keeps it, and done leaves it to be merged); Taiga leaves a comment on the pull request saying why. The state machine refuses a move that is not allowed (CONFLICT). A move into queue starts paid work and needs products:write. Ask the person before a move that starts work, stops work or closes a pull request. archived: true files the initiative where it stands (pass its current column as state; never from the Build station, plan or build) and archived: false brings it back; archived work is left out of lists unless includeArchived asks for it.`,
|
|
2455
2681
|
inputSchema: {
|
|
2456
2682
|
itemId,
|
|
2457
|
-
state: z.enum(
|
|
2683
|
+
state: z.enum(USER_DROP_STATES).describe("The column to move into"),
|
|
2458
2684
|
afterId: z.string().uuid().nullable().optional().describe("The initiative to follow in that column; null for the head; omit to keep rank order"),
|
|
2459
2685
|
archived: z.boolean().optional().describe("true files the initiative where it stands, false brings it back; omit to leave as is")
|
|
2460
2686
|
},
|
|
2461
2687
|
annotations: OPERATE
|
|
2462
2688
|
}, (params) => runTool(async () => {
|
|
2463
2689
|
const { itemId: id, ...body } = params;
|
|
2464
|
-
if (
|
|
2690
|
+
if (body.state === "queue" && !client.hasCapability("products:write")) return toolError("FORBIDDEN", "A move into queue starts agent work and needs products:write, which this key lacks.");
|
|
2465
2691
|
return outcome((await client.transitionInitiative(id, body)).data);
|
|
2466
2692
|
}));
|
|
2467
2693
|
}
|
|
2468
|
-
/** The
|
|
2469
|
-
const
|
|
2694
|
+
/** The two halves of the Build station, where Plan again is offered. */
|
|
2695
|
+
const BUILD_STATION = /* @__PURE__ */ new Set(["plan", "build"]);
|
|
2696
|
+
function registerPlanAgain(server, client) {
|
|
2697
|
+
server.registerTool("taiga_plan_again", {
|
|
2698
|
+
title: "Plan an initiative again",
|
|
2699
|
+
description: "Supersede the plan of the initiative at the Build station and ask the planner for a new one: for a plan that failed, a plan you do not want built, or a run whose direction was wrong once it has stopped. The earlier plan is kept, and every run records which plan version it built; a pull request still open stays open. Offered only when the turn is yours: refused (CONFLICT) while an agent holds it (planning, building, reviewing). Also refused while a dependency is still in flight or a UI direction has no chosen concept, unless force says to plan anyway, which only the person decides. Anywhere off the Build station, put the initiative in the queue instead (taiga_queue_backlog_item): the queue is the only way to start work. To send a plan back with direction, taiga_reject_plan. Starts a planner run; ask the person first.",
|
|
2700
|
+
inputSchema: {
|
|
2701
|
+
itemId,
|
|
2702
|
+
force: z.boolean().optional().describe("Plan anyway past an unsettled dependency or an unchosen UI direction; only when the person says so")
|
|
2703
|
+
},
|
|
2704
|
+
annotations: OPERATE
|
|
2705
|
+
}, (params) => runTool(async () => {
|
|
2706
|
+
const item = (await client.getBacklogItem(params.itemId)).data;
|
|
2707
|
+
if (!BUILD_STATION.has(String(item.state))) return toolError("CONFLICT", `This initiative is in ${String(item.state)}, not at the Build station. The queue is the only way to start work: put it in the queue with taiga_queue_backlog_item and it is planned when its turn comes.`);
|
|
2708
|
+
const body = {
|
|
2709
|
+
state: "plan",
|
|
2710
|
+
...params.force ? { force: true } : {}
|
|
2711
|
+
};
|
|
2712
|
+
return outcome((await client.transitionInitiative(params.itemId, body)).data);
|
|
2713
|
+
}));
|
|
2714
|
+
}
|
|
2470
2715
|
/** Whether a failure is the API's (refused, missing, unreachable) rather than a bug here. */
|
|
2471
2716
|
const isApiFailure$1 = (error) => error instanceof TaigaApiError || isNetworkFailure(error);
|
|
2472
2717
|
/**
|
|
@@ -2541,7 +2786,7 @@ function registerReviewGate(server, client) {
|
|
|
2541
2786
|
itemId,
|
|
2542
2787
|
autoBuild: autoBuildInput
|
|
2543
2788
|
},
|
|
2544
|
-
annotations:
|
|
2789
|
+
annotations: OPERATE_SETTING
|
|
2545
2790
|
}, (params) => runTool(async () => {
|
|
2546
2791
|
const item = (await client.setAutoBuild(params.itemId, params.autoBuild)).data;
|
|
2547
2792
|
const gate = await reviewGate(client, item);
|
|
@@ -2555,7 +2800,7 @@ function registerReviewGate(server, client) {
|
|
|
2555
2800
|
function registerResume(server, client) {
|
|
2556
2801
|
server.registerTool("taiga_resume_backlog_item", {
|
|
2557
2802
|
title: "Resume backlog item",
|
|
2558
|
-
description: "Resume an initiative the line stopped on (needsHumanCause is set): a failed or cancelled plan is planned again, a stopped or failed run resumes from its first incomplete step, a refused build start is cleared and retried.
|
|
2803
|
+
description: "Resume an initiative the line stopped on (needsHumanCause is set): a failed or cancelled plan is planned again, a stopped or failed run resumes from its first incomplete step (the steps already committed are kept), a refused build start is cleared and retried. This is the way to carry work on: a run is the record of an attempt, and decisions about the work are made on the initiative. Refused (CONFLICT) when nothing is flagged or the cause has no resume. Starts paid work; ask the person first.",
|
|
2559
2804
|
inputSchema: { itemId },
|
|
2560
2805
|
annotations: OPERATE
|
|
2561
2806
|
}, (params) => runTool(async () => outcome((await client.resumeInitiative(params.itemId)).data)));
|
|
@@ -2677,7 +2922,7 @@ function registerBuildTools(server, client) {
|
|
|
2677
2922
|
if (!client.hasCapability("products:read")) return;
|
|
2678
2923
|
server.registerTool("taiga_list_builds", {
|
|
2679
2924
|
title: "List build runs",
|
|
2680
|
-
description: "List the coding agent's runs, newest first: status, branch, pull request, step progress and the actions a person may take on each
|
|
2925
|
+
description: "List the coding agent's runs, newest first: status, branch, pull request, step progress and the actions a person may take on each. A run is the record of an attempt: work is carried on from its initiative (resume and retry_from_failed_step are taiga_resume_backlog_item; start_over is in the web only for now) and stopped by moving the initiative out of Build; a maintenance run, which has no initiative, is retried from its finding. With productId, the runs of that product, optionally of one backlog item; without it, the runs of every product the key can see, each row naming its product, and active: true narrows to the runs executing right now. Use this to see what Taiga already built or is building, then read one run with taiga_get_build. A run of a deleted initiative keeps its title.",
|
|
2681
2926
|
inputSchema: {
|
|
2682
2927
|
productId: productId.optional(),
|
|
2683
2928
|
backlogItemId: z.string().uuid().optional().describe("Only this backlog item's runs -- get from taiga_list_backlog_items; needs productId"),
|
|
@@ -2696,7 +2941,7 @@ function registerBuildTools(server, client) {
|
|
|
2696
2941
|
}));
|
|
2697
2942
|
server.registerTool("taiga_get_build", {
|
|
2698
2943
|
title: "Get build run",
|
|
2699
|
-
description: "Get one run with its steps: the run (status, pull request, hardening, metrics) and each step's title, status, commit, files changed, checks, any error and headline, the first paragraph of what the agent reported. Pass full: true for each step's complete instructions and report, several thousand tokens on a long run. Use this to pick up where the coding agent stopped, or to understand the pull request it opened. Its actions
|
|
2944
|
+
description: "Get one run with its steps: the run (status, pull request, hardening, metrics) and each step's title, status, commit, files changed, checks, any error and headline, the first paragraph of what the agent reported. Pass full: true for each step's complete instructions and report, several thousand tokens on a long run. Use this to pick up where the coding agent stopped, or to understand the pull request it opened. Its actions name what the initiative accepts for it: resume and retry_from_failed_step are taiga_resume_backlog_item, start_over is in the web only for now; to stop it, move the initiative out of Build. A maintenance run has no initiative and is retried from its finding.",
|
|
2700
2945
|
inputSchema: {
|
|
2701
2946
|
buildId,
|
|
2702
2947
|
full: z.boolean().optional().describe("Carry each step's complete instructions and report (default false: a summary)")
|
|
@@ -2720,20 +2965,12 @@ function registerBuildTools(server, client) {
|
|
|
2720
2965
|
if (!client.hasCapability("products:write")) return;
|
|
2721
2966
|
server.registerTool("taiga_stop_build", {
|
|
2722
2967
|
title: "Stop a build run",
|
|
2723
|
-
description: "
|
|
2968
|
+
description: "The emergency brake: stop a run the coding agent is executing when its initiative cannot be moved. The way to stop work is on the initiative: move it out of Build (taiga_move_backlog_item), which stops the plan or run in flight. Use this only for a run whose initiative does not offer that (it is finished, canceled or gone) yet the run is still going. The agent halts, its branch and any draft pull request stay. Only a run whose actions include stop; the API refuses the rest with CONFLICT. Ask the person before calling this: it interrupts work in progress.",
|
|
2724
2969
|
inputSchema: { buildId },
|
|
2725
2970
|
annotations: OPERATE
|
|
2726
2971
|
}, (params) => runTool(async () => {
|
|
2727
2972
|
return toolResult(pickFields([(await client.stopBuildRun(params.buildId)).data], RUN_OUTCOME_FIELDS)[0]);
|
|
2728
2973
|
}));
|
|
2729
|
-
server.registerTool("taiga_resume_build", {
|
|
2730
|
-
title: "Resume a build run",
|
|
2731
|
-
description: "Resume a stopped or failed run from its first incomplete step: the agent starts again on the same branch, which spends tokens and may push commits and open a pull request. This is the route behind both the resume and the retry_from_failed_step actions: the API accepts a run whose actions include one of them and refuses the rest with CONFLICT. start_over (a new run) is web-only for now. For an initiative the line stopped on, taiga_resume_backlog_item does whatever its cause needs instead. Ask the person before calling this.",
|
|
2732
|
-
inputSchema: { buildId },
|
|
2733
|
-
annotations: OPERATE
|
|
2734
|
-
}, (params) => runTool(async () => {
|
|
2735
|
-
return toolResult(pickFields([(await client.resumeBuildRun(params.buildId)).data], RUN_OUTCOME_FIELDS)[0]);
|
|
2736
|
-
}));
|
|
2737
2974
|
}
|
|
2738
2975
|
//#endregion
|
|
2739
2976
|
//#region src/tools/context.ts
|
|
@@ -18008,6 +18245,7 @@ function itemSignature(item) {
|
|
|
18008
18245
|
item.state,
|
|
18009
18246
|
item.planningStatus,
|
|
18010
18247
|
item.needsHumanCause,
|
|
18248
|
+
item.autoBuild,
|
|
18011
18249
|
lifecycle.stage,
|
|
18012
18250
|
lifecycle.turn,
|
|
18013
18251
|
lifecycle.cta,
|
|
@@ -18205,6 +18443,25 @@ function registerWaitTools(server, client, options) {
|
|
|
18205
18443
|
run: now ? pickFields([now], RUN_REPORT_FIELDS)[0] : null
|
|
18206
18444
|
});
|
|
18207
18445
|
}));
|
|
18446
|
+
if (client.hasCapability("products:read")) server.registerTool("taiga_wait_for_job", {
|
|
18447
|
+
title: "Wait for an agent job",
|
|
18448
|
+
description: "Wait until an agent job finishes (completed, failed or cancelled): an initiative request, a document, a refresh. Returns the job as taiga_get_job reads it, with what came of an initiative request, as soon as it settles (reached: true), or at the timeout with reached: false: call again to keep waiting. Most jobs take a few minutes.",
|
|
18449
|
+
inputSchema: {
|
|
18450
|
+
jobId: z.string().uuid().describe("Agent job ID -- a request tool returns it"),
|
|
18451
|
+
timeoutSeconds
|
|
18452
|
+
},
|
|
18453
|
+
annotations: READ_ONLY
|
|
18454
|
+
}, (params, extra) => runTool(async () => {
|
|
18455
|
+
const { now, ...outcome } = await waitFor(async (signal) => (await client.getAgentJob(params.jobId, signal)).data, (job) => SETTLED_JOB_STATUSES.has(String(job.status)) ? { reached: true } : null, (params.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS) * 1e3, options, {
|
|
18456
|
+
progressToken: extra._meta?.progressToken,
|
|
18457
|
+
sendNotification: extra.sendNotification,
|
|
18458
|
+
signal: extra.signal
|
|
18459
|
+
});
|
|
18460
|
+
return toolResult({
|
|
18461
|
+
...outcome,
|
|
18462
|
+
job: now ? jobReport(now) : null
|
|
18463
|
+
});
|
|
18464
|
+
}));
|
|
18208
18465
|
}
|
|
18209
18466
|
//#endregion
|
|
18210
18467
|
//#region src/tools/index.ts
|
|
@@ -18240,6 +18497,7 @@ function registerAllTools(server, client, options = {}) {
|
|
|
18240
18497
|
registerBacklogTools(server, client);
|
|
18241
18498
|
registerBuildTools(server, client);
|
|
18242
18499
|
registerQueueTools(server, client);
|
|
18500
|
+
registerJobTools(server, client);
|
|
18243
18501
|
registerWaitTools(server, client, options.wait ?? DEFAULT_WAIT_OPTIONS);
|
|
18244
18502
|
registerContextTools(server, client);
|
|
18245
18503
|
registerResources(server, client);
|
|
@@ -18285,20 +18543,26 @@ const SERVER_INSTRUCTIONS = [
|
|
|
18285
18543
|
"for them: runs, steps, commits and pull requests. taiga_list_builds without a product lists the runs of",
|
|
18286
18544
|
"every product the key can see, and active: true the ones executing now.",
|
|
18287
18545
|
"",
|
|
18288
|
-
"**Operating the line**
|
|
18289
|
-
"
|
|
18290
|
-
"
|
|
18291
|
-
"
|
|
18292
|
-
"
|
|
18293
|
-
"
|
|
18294
|
-
"
|
|
18295
|
-
"
|
|
18546
|
+
"**Operating the line** works the way Taiga is meant to be used. New work is described, not written:",
|
|
18547
|
+
"taiga_request_initiatives asks an agent, which reads the product and its repository, expands the request",
|
|
18548
|
+
"into initiatives in the Backlog (or says why it added none: already covered, not work, out of scope, or a",
|
|
18549
|
+
"policy that does not allow it) -- follow it with taiga_wait_for_job. The queue is the only way to start",
|
|
18550
|
+
"work (taiga_queue_backlog_item): Taiga plans and builds one initiative at a time, in queue order, and starts",
|
|
18551
|
+
"the next when the pull request merges. Whether a landed plan waits for a review is the review gate",
|
|
18552
|
+
"(taiga_set_auto_build, or autoBuild on the queue); read it with taiga_get_plan and answer with",
|
|
18553
|
+
"taiga_approve_plan or taiga_reject_plan. Decisions about the work are made on the initiative: stop work by",
|
|
18554
|
+
"moving it out of Build (taiga_move_backlog_item), carry it on with taiga_resume_backlog_item, ask for a new",
|
|
18555
|
+
"plan with taiga_plan_again; a run is the record of an attempt. taiga_pause_line pauses the whole line.",
|
|
18556
|
+
"taiga_stop_build is the emergency brake for a run whose initiative cannot be moved. Queueing, approving,",
|
|
18557
|
+
"resuming and requesting start paid agent work; stopping and pausing interrupt it. Tell the person what you",
|
|
18558
|
+
"are about to start or stop and wait for their yes; a client that prompts on destructive tools does that",
|
|
18559
|
+
"for you. A refusal (CONFLICT) carries the API reason: say it as it is.",
|
|
18296
18560
|
"",
|
|
18297
|
-
"**Watching the line** (taiga_get_queue / taiga_wait_for_backlog_item / taiga_wait_for_build
|
|
18298
|
-
"read says what the line is waiting on (a
|
|
18299
|
-
"it). Planning takes minutes and a build up to an
|
|
18300
|
-
"same item again and again; each returns when the
|
|
18301
|
-
"or at its timeout (call again to keep waiting).",
|
|
18561
|
+
"**Watching the line** (taiga_get_queue / taiga_wait_for_backlog_item / taiga_wait_for_build /",
|
|
18562
|
+
"taiga_get_job / taiga_wait_for_job): the queue read says what the line is waiting on (a paused line, a plan",
|
|
18563
|
+
"to review, a pull request to merge, a halt and what lifts it). Planning takes minutes and a build up to an",
|
|
18564
|
+
"hour: wait with the wait tools rather than reading the same item again and again; each returns when the",
|
|
18565
|
+
"point is reached, when only a person can move it on, or at its timeout (call again to keep waiting).",
|
|
18302
18566
|
"",
|
|
18303
18567
|
"**Sections.** Policies and instructions are maps of sections. Each section carries its text and two",
|
|
18304
18568
|
"provenance fields you can read past: `source` says where the text originally came from (template:",
|