@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 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 entries = (await client.listQueue(product)).data.filter((entry) => entry.state !== "settled");
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, the create and
2355
- * the board move on backlog:write, queueing on both backlog:write and
2356
- * products:write (the state route checks both for a drop into queue), the
2357
- * resume and the review gate on products:write alone, as their routes are
2358
- * (the item ID they take also comes back from a create or a queue), and the plan verdicts on
2359
- * products:write together with backlog:read, because the plan ID they take
2360
- * comes only from the plan read: a key that cannot read plans has nothing
2361
- * to feed them.
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")) registerBacklogWrites(server, client);
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, references), 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.",
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 registerBacklogWrites(server, client) {
2431
- server.registerTool("taiga_create_backlog_item", {
2432
- title: "Create backlog item",
2433
- description: "Create an initiative in the Backlog column of a product: a title and a brief in Markdown (the problem, what it looks like, what it covers and leaves alone), its category, priority and estimate. It starts nothing: the planner reads it only once it is queued (taiga_queue_backlog_item) or moved to Plan (taiga_move_backlog_item). Returns the new item with its ID.",
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().min(1).max(500).describe("The initiative title, as it appears on the board"),
2437
- description: z.string().min(1).max(2e4).describe("The brief in Markdown: the problem, what it looks like, what it covers and leaves alone"),
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: WRITE
2665
+ annotations: OPERATE
2448
2666
  }, (params) => runTool(async () => {
2449
- const { productId: id, ...body } = params;
2450
- return outcome((await client.createBacklogItem(id, body)).data);
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 to a column, or order it within one: backlog and todo are the idle columns; queue puts it in the line (taiga_queue_backlog_item says more); plan starts planning now; done and canceled take it off the line. 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). The initiative state machine refuses a move that is not allowed (CONFLICT: build cannot be dropped into, an initiative being planned cannot leave plan). A move into queue or plan starts paid work and needs products:write on the key; a key without it is refused here. Ask the person before a move that starts work (queue, plan) or closes a pull request (${PR_CLOSING_STATES.join(", ")} from build). archived: true files the initiative where it stands (pass its current column as state; from any column off the line, never from plan or build) and archived: false brings it back; an archived initiative is left out of lists unless includeArchived asks for it.`,
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(INITIATIVE_TRANSITION_STATES).describe("The column to move into"),
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 (LINE_STARTING_STATES.has(body.state) && !client.hasCapability("products:write")) return toolError("FORBIDDEN", `A move into ${body.state} starts agent work and needs products:write, which this key lacks.`);
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 columns a drop into starts paid work, which the state route gates on products:write. */
2469
- const LINE_STARTING_STATES = /* @__PURE__ */ new Set(["queue", "plan"]);
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: OPERATE
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. Does whatever the named cause needs, so prefer it to taiga_resume_build when the initiative is flagged. Refused (CONFLICT) when nothing is flagged or the cause has no resume. Starts paid work; ask the person first.",
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 (stop is taiga_stop_build; resume and retry_from_failed_step are both taiga_resume_build, the same route; start_over is only in the web UI for now). 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.",
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 list says what it accepts: stop (taiga_stop_build), resume or retry_from_failed_step (both taiga_resume_build), start_over (web UI only for now).",
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: "Stop a run the coding agent is executing: the agent halts, its branch and any draft pull request stay, and the run can be resumed later with taiga_resume_build. Only a run whose actions include stop (taiga_list_builds shows them); the API refuses the rest with CONFLICT. Ask the person before calling this: it interrupts work in progress.",
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** (taiga_create_backlog_item / taiga_queue_backlog_item / taiga_move_backlog_item /",
18289
- "taiga_resume_backlog_item / taiga_set_auto_build / taiga_get_plan / taiga_approve_plan / taiga_reject_plan /",
18290
- "taiga_stop_build / taiga_resume_build): create an initiative, put it in the queue the coding agent works,",
18291
- "move it on the board, decide whether its plan waits for a review (autoBuild: false) or is built at once, read",
18292
- "and approve or reject its plan, stop or resume a run. Queueing, approving and resuming start paid agent work",
18293
- "that pushes commits and opens pull requests; stopping interrupts it. Tell the person what you are about to",
18294
- "start or stop and wait for their yes before calling these; a client that prompts on destructive tools does",
18295
- "that for you. A refusal (CONFLICT) carries the API reason: say it as it is.",
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): the queue",
18298
- "read says what the line is waiting on (a plan to review, a pull request to merge, a halt and what lifts",
18299
- "it). Planning takes minutes and a build up to an hour: wait with the wait tools rather than reading the",
18300
- "same item again and again; each returns when the point is reached, when only a person can move it on,",
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:",