@kody-ade/kody-engine 0.4.276 → 0.4.278

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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +616 -315
  2. package/package.json +1 -1
package/dist/bin/kody.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.276",
18
+ version: "0.4.278",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -156,14 +156,97 @@ function ghToken(preferRepoToken = false) {
156
156
  function gh(args, options) {
157
157
  const token = ghToken(options?.preferRepoToken);
158
158
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
159
- return execFileSync("gh", args, {
160
- encoding: "utf-8",
161
- timeout: API_TIMEOUT_MS,
162
- cwd: options?.cwd,
163
- env,
164
- input: options?.input,
165
- stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
166
- }).trim();
159
+ const maxRetries = envInt("KODY_GH_RATE_LIMIT_MAX_RETRIES", DEFAULT_RATE_LIMIT_RETRIES);
160
+ const maxWaitMs = envInt("KODY_GH_RATE_LIMIT_MAX_WAIT_MS", DEFAULT_RATE_LIMIT_MAX_WAIT_MS);
161
+ for (let attempt = 0; ; attempt += 1) {
162
+ try {
163
+ return execFileSync("gh", args, {
164
+ encoding: "utf-8",
165
+ timeout: API_TIMEOUT_MS,
166
+ cwd: options?.cwd,
167
+ env,
168
+ input: options?.input,
169
+ stdio: options?.input ? ["pipe", "pipe", "pipe"] : ["inherit", "pipe", "pipe"]
170
+ }).trim();
171
+ } catch (err) {
172
+ if (!isRateLimitError(err) || attempt >= maxRetries) throw err;
173
+ const waitMs = rateLimitWaitMs(token, options?.cwd, attempt + 1);
174
+ if (waitMs > maxWaitMs) {
175
+ process.stderr.write(
176
+ `[kody gh] rate limit on ${formatGhArgs(args)}; reset wait ${formatDuration(waitMs)} exceeds max ${formatDuration(maxWaitMs)}
177
+ `
178
+ );
179
+ throw err;
180
+ }
181
+ process.stderr.write(
182
+ `[kody gh] rate limit on ${formatGhArgs(args)}; retry ${attempt + 1}/${maxRetries} after ${formatDuration(waitMs)}
183
+ `
184
+ );
185
+ sleepSync(waitMs);
186
+ }
187
+ }
188
+ }
189
+ function envInt(name, fallback) {
190
+ const raw = process.env[name]?.trim();
191
+ if (!raw) return fallback;
192
+ const value = Number(raw);
193
+ return Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
194
+ }
195
+ function errorMessage(err) {
196
+ if (err instanceof Error) {
197
+ const stderr = err.stderr;
198
+ if (Buffer.isBuffer(stderr)) return `${err.message}
199
+ ${stderr.toString("utf-8")}`;
200
+ if (typeof stderr === "string") return `${err.message}
201
+ ${stderr}`;
202
+ return err.message;
203
+ }
204
+ return String(err);
205
+ }
206
+ function isRateLimitError(err) {
207
+ const msg = errorMessage(err);
208
+ return /rate limit exceeded|secondary rate limit|abuse detection/i.test(msg) && /HTTP 403|rate limit/i.test(msg);
209
+ }
210
+ function rateLimitWaitMs(token, cwd, retryNumber) {
211
+ const resetMs = readCoreRateLimitResetMs(token, cwd);
212
+ if (typeof resetMs === "number") {
213
+ return Math.max(0, resetMs - Date.now() + 1e3);
214
+ }
215
+ const base = envInt("KODY_GH_RATE_LIMIT_BASE_DELAY_MS", DEFAULT_RATE_LIMIT_BASE_DELAY_MS);
216
+ return base * 2 ** Math.max(0, retryNumber - 1);
217
+ }
218
+ function readCoreRateLimitResetMs(token, cwd) {
219
+ try {
220
+ const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
221
+ const raw = execFileSync("gh", ["api", "rate_limit", "--jq", ".resources.core.reset"], {
222
+ encoding: "utf-8",
223
+ timeout: API_TIMEOUT_MS,
224
+ cwd,
225
+ env,
226
+ stdio: ["ignore", "pipe", "pipe"]
227
+ }).trim();
228
+ const seconds = Number(raw);
229
+ return Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : null;
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
234
+ function sleepSync(ms) {
235
+ if (ms <= 0) return;
236
+ const buffer = new SharedArrayBuffer(4);
237
+ const view = new Int32Array(buffer);
238
+ Atomics.wait(view, 0, 0, ms);
239
+ }
240
+ function formatGhArgs(args) {
241
+ return `gh ${args.slice(0, 4).join(" ")}${args.length > 4 ? " ..." : ""}`;
242
+ }
243
+ function formatDuration(ms) {
244
+ if (ms < 1e3) return `${ms}ms`;
245
+ const seconds = Math.ceil(ms / 1e3);
246
+ if (seconds < 60) return `${seconds}s`;
247
+ const minutes = Math.floor(seconds / 60);
248
+ const rest = seconds % 60;
249
+ return rest ? `${minutes}m${rest}s` : `${minutes}m`;
167
250
  }
168
251
  function getIssue(issueNumber, cwd) {
169
252
  const output = gh(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels,url"], {
@@ -333,11 +416,14 @@ function postPrReviewComment(prNumber, body, cwd) {
333
416
  );
334
417
  }
335
418
  }
336
- var API_TIMEOUT_MS, BotDispatchCommentError, DEFAULT_COMMENT_LIMIT, DEFAULT_COMMENT_MAX_BYTES, VERDICT_HEADING;
419
+ var API_TIMEOUT_MS, DEFAULT_RATE_LIMIT_RETRIES, DEFAULT_RATE_LIMIT_BASE_DELAY_MS, DEFAULT_RATE_LIMIT_MAX_WAIT_MS, BotDispatchCommentError, DEFAULT_COMMENT_LIMIT, DEFAULT_COMMENT_MAX_BYTES, VERDICT_HEADING;
337
420
  var init_issue = __esm({
338
421
  "src/issue.ts"() {
339
422
  "use strict";
340
423
  API_TIMEOUT_MS = 3e4;
424
+ DEFAULT_RATE_LIMIT_RETRIES = 2;
425
+ DEFAULT_RATE_LIMIT_BASE_DELAY_MS = 1e3;
426
+ DEFAULT_RATE_LIMIT_MAX_WAIT_MS = 65 * 60 * 1e3;
341
427
  BotDispatchCommentError = class extends Error {
342
428
  constructor(slug2) {
343
429
  super(
@@ -352,12 +438,25 @@ var init_issue = __esm({
352
438
  }
353
439
  });
354
440
 
441
+ // src/stateBranch.ts
442
+ var STATE_BRANCH;
443
+ var init_stateBranch = __esm({
444
+ "src/stateBranch.ts"() {
445
+ "use strict";
446
+ STATE_BRANCH = "kody-state";
447
+ }
448
+ });
449
+
355
450
  // src/stateRepo.ts
356
451
  import path2 from "path";
357
452
  function is404(err) {
358
453
  const msg = err instanceof Error ? err.message : String(err);
359
454
  return /HTTP 404/i.test(msg) || /Not Found/i.test(msg);
360
455
  }
456
+ function isAlreadyExists(err) {
457
+ const msg = err instanceof Error ? err.message : String(err);
458
+ return /HTTP 422/i.test(msg) || /Reference already exists/i.test(msg);
459
+ }
361
460
  function parseStateRepoSlug(slug2, field = "stateRepo") {
362
461
  const value = slug2.trim();
363
462
  let repoPath = value;
@@ -428,11 +527,41 @@ function apiPath(config, targetPath) {
428
527
  const parsed = parseStateRepo(config);
429
528
  return `/repos/${parsed.owner}/${parsed.repo}/contents/${targetPath}`;
430
529
  }
530
+ function branchApiPath(config, targetPath) {
531
+ return `${apiPath(config, targetPath)}?ref=${encodeURIComponent(STATE_BRANCH)}`;
532
+ }
533
+ function ensureStateBranch(config, cwd) {
534
+ const parsed = parseStateRepo(config);
535
+ const cacheKey2 = `${parsed.owner}/${parsed.repo}:${STATE_BRANCH}`;
536
+ if (ensuredStateBranches.has(cacheKey2)) return;
537
+ try {
538
+ gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
539
+ ensuredStateBranches.add(cacheKey2);
540
+ return;
541
+ } catch (err) {
542
+ if (!is404(err)) throw err;
543
+ }
544
+ const repoRaw = gh(["api", `/repos/${parsed.owner}/${parsed.repo}`], { cwd });
545
+ const defaultBranch = String(JSON.parse(repoRaw).default_branch ?? "").trim();
546
+ if (!defaultBranch) throw new Error(`stateRepo: ${parsed.owner}/${parsed.repo} default branch missing`);
547
+ const refRaw = gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${defaultBranch}`], { cwd });
548
+ const sha = String(JSON.parse(refRaw).object?.sha ?? "").trim();
549
+ if (!sha) throw new Error(`stateRepo: ${parsed.owner}/${parsed.repo} ${defaultBranch} ref sha missing`);
550
+ try {
551
+ gh(["api", "--method", "POST", `/repos/${parsed.owner}/${parsed.repo}/git/refs`, "--input", "-"], {
552
+ cwd,
553
+ input: JSON.stringify({ ref: `refs/heads/${STATE_BRANCH}`, sha })
554
+ });
555
+ } catch (err) {
556
+ if (!isAlreadyExists(err)) throw err;
557
+ }
558
+ ensuredStateBranches.add(cacheKey2);
559
+ }
431
560
  function readStateText(config, cwd, filePath) {
432
561
  const targetPath = stateRepoPath(config, filePath);
433
562
  let raw = "";
434
563
  try {
435
- raw = gh(["api", apiPath(config, targetPath)], { cwd });
564
+ raw = gh(["api", branchApiPath(config, targetPath)], { cwd });
436
565
  } catch (err) {
437
566
  if (is404(err)) return null;
438
567
  throw err;
@@ -457,9 +586,11 @@ function readStateText(config, cwd, filePath) {
457
586
  }
458
587
  function writeStateText(config, cwd, filePath, content, message, sha) {
459
588
  const targetPath = stateRepoPath(config, filePath);
589
+ ensureStateBranch(config, cwd);
460
590
  const payload = {
461
591
  message,
462
- content: Buffer.from(content, "utf-8").toString("base64")
592
+ content: Buffer.from(content, "utf-8").toString("base64"),
593
+ branch: STATE_BRANCH
463
594
  };
464
595
  if (sha) payload.sha = sha;
465
596
  gh(["api", "--method", "PUT", apiPath(config, targetPath), "--input", "-"], {
@@ -490,7 +621,7 @@ function listStateDirectory(config, cwd, dirPath) {
490
621
  const targetPath = stateRepoPath(config, dirPath);
491
622
  let raw = "";
492
623
  try {
493
- raw = gh(["api", apiPath(config, targetPath)], { cwd });
624
+ raw = gh(["api", branchApiPath(config, targetPath)], { cwd });
494
625
  } catch (err) {
495
626
  if (is404(err)) return [];
496
627
  throw err;
@@ -498,10 +629,13 @@ function listStateDirectory(config, cwd, dirPath) {
498
629
  const parsed = JSON.parse(raw);
499
630
  return Array.isArray(parsed) ? parsed : [];
500
631
  }
632
+ var ensuredStateBranches;
501
633
  var init_stateRepo = __esm({
502
634
  "src/stateRepo.ts"() {
503
635
  "use strict";
504
636
  init_issue();
637
+ init_stateBranch();
638
+ ensuredStateBranches = /* @__PURE__ */ new Set();
505
639
  }
506
640
  });
507
641
 
@@ -1356,18 +1490,312 @@ function buildSubmitMcpServer() {
1356
1490
  });
1357
1491
  return { server, getSubmitted: handle.getSubmitted };
1358
1492
  }
1359
- var DESCRIPTION2, INPUT_SCHEMA2;
1360
- var init_submitMcp = __esm({
1361
- "src/submitMcp.ts"() {
1493
+ var DESCRIPTION2, INPUT_SCHEMA2;
1494
+ var init_submitMcp = __esm({
1495
+ "src/submitMcp.ts"() {
1496
+ "use strict";
1497
+ DESCRIPTION2 = "Persist this tick's next state. Call this EXACTLY ONCE, at the very end, when you've finished your work \u2014 it is the ONLY way your decision is saved. Pass your next `cursor` (string), your next `data` (object \u2014 carry prior data forward and mutate what you acted on), and `done` (boolean). After calling it you are finished; do not take further actions.";
1498
+ INPUT_SCHEMA2 = {
1499
+ cursor: z.string().describe('The next cursor value (e.g. "idle"). Must be a non-empty string.'),
1500
+ data: z.record(z.string(), z.unknown()).describe("The next `data` object. Carry forward prior data and mutate only what you acted on this tick."),
1501
+ done: z.boolean().describe(
1502
+ "true only if this capability is permanently finished; evergreen capabilities stay false."
1503
+ )
1504
+ };
1505
+ }
1506
+ });
1507
+
1508
+ // src/dashboardCmsMcp.ts
1509
+ var dashboardCmsMcp_exports = {};
1510
+ __export(dashboardCmsMcp_exports, {
1511
+ DASHBOARD_CMS_MCP_TOOL_NAMES: () => DASHBOARD_CMS_MCP_TOOL_NAMES,
1512
+ buildDashboardCmsMcpServer: () => buildDashboardCmsMcpServer,
1513
+ dashboardCmsToolDefinitions: () => dashboardCmsToolDefinitions
1514
+ });
1515
+ import { createSdkMcpServer as createSdkMcpServer3, tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
1516
+ import { z as z2 } from "zod";
1517
+ function dashboardBaseUrl(opts) {
1518
+ const raw = opts.dashboardUrl?.trim() || process.env.KODY_CMS_DASHBOARD_URL?.trim() || process.env.KODY_DASHBOARD_URL?.trim() || process.env.DASHBOARD_URL?.trim() || "";
1519
+ if (!raw) return null;
1520
+ try {
1521
+ return new URL(raw).origin;
1522
+ } catch {
1523
+ return null;
1524
+ }
1525
+ }
1526
+ function dashboardCmsToken(opts) {
1527
+ return opts.token?.trim() || process.env.KODY_CMS_TOKEN?.trim() || process.env.KODY_DASHBOARD_TOKEN?.trim() || process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || process.env.GITHUB_TOKEN?.trim() || process.env.GH_PAT?.trim() || null;
1528
+ }
1529
+ function cmsHeaders(opts) {
1530
+ const token = dashboardCmsToken(opts);
1531
+ const [owner, repo] = opts.repoSlug.split("/");
1532
+ if (!token) {
1533
+ return {
1534
+ ok: false,
1535
+ error: "missing_cms_token",
1536
+ message: "Set KODY_CMS_TOKEN, KODY_DASHBOARD_TOKEN, KODY_TOKEN, GH_TOKEN, GITHUB_TOKEN, or GH_PAT."
1537
+ };
1538
+ }
1539
+ if (!owner || !repo) {
1540
+ return { ok: false, error: "invalid_repo", message: `Invalid repo slug: ${opts.repoSlug}` };
1541
+ }
1542
+ return {
1543
+ ok: true,
1544
+ headers: {
1545
+ "Content-Type": "application/json",
1546
+ "x-kody-token": token,
1547
+ "x-kody-owner": owner,
1548
+ "x-kody-repo": repo,
1549
+ ...opts.storeRepoUrl?.trim() ? { "x-kody-store-repo-url": opts.storeRepoUrl.trim() } : {},
1550
+ ...opts.storeRef?.trim() ? { "x-kody-store-ref": opts.storeRef.trim() } : {}
1551
+ }
1552
+ };
1553
+ }
1554
+ async function callDashboardCms(opts, path50, init = {}) {
1555
+ const baseUrl = dashboardBaseUrl(opts);
1556
+ if (!baseUrl) {
1557
+ return {
1558
+ ok: false,
1559
+ error: "missing_dashboard_url",
1560
+ message: "Set KODY_CMS_DASHBOARD_URL or KODY_DASHBOARD_URL to the Dashboard origin."
1561
+ };
1562
+ }
1563
+ const headerResult = cmsHeaders(opts);
1564
+ if (!headerResult.ok) return headerResult;
1565
+ try {
1566
+ const res = await fetch(`${baseUrl}${path50}`, {
1567
+ ...init,
1568
+ headers: {
1569
+ ...headerResult.headers,
1570
+ ...init.headers
1571
+ }
1572
+ });
1573
+ const contentType = res.headers.get("content-type") ?? "";
1574
+ const data = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => "");
1575
+ if (!res.ok) {
1576
+ return {
1577
+ ok: false,
1578
+ status: res.status,
1579
+ error: stringFieldFromRecord(data, "error") ?? "cms_request_failed",
1580
+ message: stringFieldFromRecord(data, "message") ?? `Dashboard CMS request failed with ${res.status}.`,
1581
+ data
1582
+ };
1583
+ }
1584
+ return { ok: true, status: res.status, data };
1585
+ } catch (err) {
1586
+ return {
1587
+ ok: false,
1588
+ error: "cms_request_error",
1589
+ message: err instanceof Error ? err.message : String(err)
1590
+ };
1591
+ }
1592
+ }
1593
+ function cmsToolResponse(result) {
1594
+ return {
1595
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1596
+ ...result.ok ? {} : { isError: true }
1597
+ };
1598
+ }
1599
+ function cmsQuery(args) {
1600
+ const params = new URLSearchParams();
1601
+ const q = stringArg(args.q);
1602
+ if (q) params.set("q", q);
1603
+ const limit = numberArg(args.limit);
1604
+ if (limit !== void 0) params.set("limit", String(limit));
1605
+ const offset = numberArg(args.offset);
1606
+ if (offset !== void 0) params.set("offset", String(offset));
1607
+ if (args.filters && typeof args.filters === "object" && !Array.isArray(args.filters)) {
1608
+ params.set("filters", JSON.stringify(args.filters));
1609
+ }
1610
+ if (Array.isArray(args.sort)) {
1611
+ const sort = args.sort.flatMap((entry) => {
1612
+ if (!entry || typeof entry !== "object") return [];
1613
+ const field = stringArg(entry.field);
1614
+ if (!field) return [];
1615
+ const direction = entry.direction === "asc" ? "asc" : "desc";
1616
+ return [`${field}:${direction}`];
1617
+ }).join(",");
1618
+ if (sort) params.set("sort", sort);
1619
+ }
1620
+ const value = params.toString();
1621
+ return value ? `?${value}` : "";
1622
+ }
1623
+ function stringArg(value) {
1624
+ return typeof value === "string" ? value.trim() : "";
1625
+ }
1626
+ function numberArg(value) {
1627
+ if (value === void 0 || value === null || value === "") return void 0;
1628
+ const n = Number(value);
1629
+ return Number.isFinite(n) ? n : void 0;
1630
+ }
1631
+ function documentArg(value) {
1632
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1633
+ return value;
1634
+ }
1635
+ function normalizeCmsDocumentIdInput(input) {
1636
+ const trimmed = stripWrappingQuotes(input.trim());
1637
+ const withoutQuery = trimmed.split(/[?#]/, 1)[0] ?? trimmed;
1638
+ const path50 = parseDocumentPath(withoutQuery);
1639
+ return path50 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1640
+ }
1641
+ function stripWrappingQuotes(value) {
1642
+ let current = value;
1643
+ for (; ; ) {
1644
+ const next = current.replace(/^[`'"]+|[`'"]+$/g, "").trim();
1645
+ if (next === current) return current;
1646
+ current = next;
1647
+ }
1648
+ }
1649
+ function parseDocumentPath(value) {
1650
+ const path50 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1651
+ if (!path50?.includes("/content/entries/")) return null;
1652
+ const parts = path50.split("/").filter(Boolean).map(decodePathPart);
1653
+ const entriesIndex = parts.findIndex((part, index) => part === "content" && parts[index + 1] === "entries");
1654
+ const idPart = parts[entriesIndex + 3];
1655
+ if (!idPart || idPart === "new") return null;
1656
+ return idPart === "edit" ? parts[entriesIndex + 2] ?? null : idPart;
1657
+ }
1658
+ function parseDocumentIdSegment(value) {
1659
+ const parts = value.split("/").filter(Boolean).map(decodePathPart);
1660
+ if (parts.length < 2) return null;
1661
+ const lastPart = parts[parts.length - 1];
1662
+ if (!lastPart || lastPart === "new") return null;
1663
+ return lastPart === "edit" ? parts[parts.length - 2] ?? null : lastPart;
1664
+ }
1665
+ function urlPathname(value) {
1666
+ try {
1667
+ return new URL(value).pathname;
1668
+ } catch {
1669
+ return null;
1670
+ }
1671
+ }
1672
+ function decodePathPart(value) {
1673
+ try {
1674
+ return decodeURIComponent(value);
1675
+ } catch {
1676
+ return value;
1677
+ }
1678
+ }
1679
+ function stringFieldFromRecord(value, field) {
1680
+ return value && typeof value === "object" && !Array.isArray(value) && typeof value[field] === "string" ? String(value[field]) : void 0;
1681
+ }
1682
+ function dashboardCmsToolDefinitions(opts) {
1683
+ const cmsListCollectionsTool = {
1684
+ name: "cms_list_collections",
1685
+ description: "List configured Dashboard CMS collections and their supported operations. Read-only.",
1686
+ inputSchema: {},
1687
+ handler: async () => cmsToolResponse(await callDashboardCms(opts, "/api/kody/cms"))
1688
+ };
1689
+ const cmsListDocumentsTool = {
1690
+ name: "cms_list_documents",
1691
+ description: "List or search Dashboard CMS documents from one configured collection. Read-only.",
1692
+ inputSchema: {
1693
+ collection: z2.string().min(1).describe("CMS collection name."),
1694
+ q: z2.string().optional().describe("Optional search query."),
1695
+ filters: z2.record(z2.string(), z2.unknown()).optional().describe("Optional filter object keyed by field."),
1696
+ sort: z2.array(
1697
+ z2.object({
1698
+ field: z2.string().min(1),
1699
+ direction: z2.enum(["asc", "desc"]).default("desc")
1700
+ })
1701
+ ).optional(),
1702
+ limit: z2.number().int().min(1).max(100).optional(),
1703
+ offset: z2.number().int().min(0).optional()
1704
+ },
1705
+ handler: async (args) => {
1706
+ const collection = encodeURIComponent(stringArg(args.collection));
1707
+ return cmsToolResponse(await callDashboardCms(opts, `/api/kody/cms/${collection}${cmsQuery(args)}`));
1708
+ }
1709
+ };
1710
+ const cmsGetDocumentTool = {
1711
+ name: "cms_get_document",
1712
+ description: "Get one Dashboard CMS document by collection and id. Use a raw id or the content entry URL copied from the Dashboard.",
1713
+ inputSchema: {
1714
+ collection: z2.string().min(1).describe("CMS collection name."),
1715
+ id: z2.string().min(1).describe("Document id. Use cmsDocumentId from cms_list_documents when available.")
1716
+ },
1717
+ handler: async (args) => {
1718
+ const collection = encodeURIComponent(stringArg(args.collection));
1719
+ const id = encodeURIComponent(normalizeCmsDocumentIdInput(stringArg(args.id)));
1720
+ return cmsToolResponse(await callDashboardCms(opts, `/api/kody/cms/${collection}/${id}`));
1721
+ }
1722
+ };
1723
+ const cmsCreateDocumentTool = {
1724
+ name: "cms_create_document",
1725
+ description: "Create one Dashboard CMS document when the caller allows writes and the CMS collection allows create.",
1726
+ inputSchema: {
1727
+ collection: z2.string().min(1).describe("CMS collection name."),
1728
+ data: z2.record(z2.string(), z2.unknown()).describe("Document fields to create.")
1729
+ },
1730
+ handler: async (args) => {
1731
+ const refusal = opts.assertWriteAllowed?.();
1732
+ if (refusal) return { content: [{ type: "text", text: refusal }] };
1733
+ const collection = encodeURIComponent(stringArg(args.collection));
1734
+ return cmsToolResponse(
1735
+ await callDashboardCms(opts, `/api/kody/cms/${collection}`, {
1736
+ method: "POST",
1737
+ body: JSON.stringify(documentArg(args.data))
1738
+ })
1739
+ );
1740
+ }
1741
+ };
1742
+ const cmsUpdateDocumentTool = {
1743
+ name: "cms_update_document",
1744
+ description: "Update one Dashboard CMS document when the caller allows writes and the CMS collection allows update.",
1745
+ inputSchema: {
1746
+ collection: z2.string().min(1).describe("CMS collection name."),
1747
+ id: z2.string().min(1).describe("Document id."),
1748
+ data: z2.record(z2.string(), z2.unknown()).describe("Partial document fields to update.")
1749
+ },
1750
+ handler: async (args) => {
1751
+ const refusal = opts.assertWriteAllowed?.();
1752
+ if (refusal) return { content: [{ type: "text", text: refusal }] };
1753
+ const collection = encodeURIComponent(stringArg(args.collection));
1754
+ const id = encodeURIComponent(normalizeCmsDocumentIdInput(stringArg(args.id)));
1755
+ return cmsToolResponse(
1756
+ await callDashboardCms(opts, `/api/kody/cms/${collection}/${id}`, {
1757
+ method: "PATCH",
1758
+ body: JSON.stringify(documentArg(args.data))
1759
+ })
1760
+ );
1761
+ }
1762
+ };
1763
+ return [
1764
+ cmsListCollectionsTool,
1765
+ cmsListDocumentsTool,
1766
+ cmsGetDocumentTool,
1767
+ cmsCreateDocumentTool,
1768
+ cmsUpdateDocumentTool
1769
+ ];
1770
+ }
1771
+ function buildDashboardCmsMcpServer(opts) {
1772
+ const tools = dashboardCmsToolDefinitions(opts).map(
1773
+ (def) => tool3(
1774
+ def.name,
1775
+ def.description,
1776
+ def.inputSchema,
1777
+ async (args) => def.handler(args)
1778
+ )
1779
+ );
1780
+ return {
1781
+ server: createSdkMcpServer3({
1782
+ name: "kody-cms",
1783
+ version: "0.1.0",
1784
+ tools
1785
+ })
1786
+ };
1787
+ }
1788
+ var DASHBOARD_CMS_MCP_TOOL_NAMES;
1789
+ var init_dashboardCmsMcp = __esm({
1790
+ "src/dashboardCmsMcp.ts"() {
1362
1791
  "use strict";
1363
- DESCRIPTION2 = "Persist this tick's next state. Call this EXACTLY ONCE, at the very end, when you've finished your work \u2014 it is the ONLY way your decision is saved. Pass your next `cursor` (string), your next `data` (object \u2014 carry prior data forward and mutate what you acted on), and `done` (boolean). After calling it you are finished; do not take further actions.";
1364
- INPUT_SCHEMA2 = {
1365
- cursor: z.string().describe('The next cursor value (e.g. "idle"). Must be a non-empty string.'),
1366
- data: z.record(z.string(), z.unknown()).describe("The next `data` object. Carry forward prior data and mutate only what you acted on this tick."),
1367
- done: z.boolean().describe(
1368
- "true only if this capability is permanently finished; evergreen capabilities stay false."
1369
- )
1370
- };
1792
+ DASHBOARD_CMS_MCP_TOOL_NAMES = [
1793
+ "cms_list_collections",
1794
+ "cms_list_documents",
1795
+ "cms_get_document",
1796
+ "cms_create_document",
1797
+ "cms_update_document"
1798
+ ];
1371
1799
  }
1372
1800
  });
1373
1801
 
@@ -1941,8 +2369,8 @@ __export(capabilityMcp_exports, {
1941
2369
  readCheckRuns: () => readCheckRuns,
1942
2370
  readThread: () => readThread
1943
2371
  });
1944
- import { createSdkMcpServer as createSdkMcpServer3, tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
1945
- import { z as z2 } from "zod";
2372
+ import { createSdkMcpServer as createSdkMcpServer4, tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
2373
+ import { z as z3 } from "zod";
1946
2374
  function summarizeCiStatus(rollup) {
1947
2375
  if (!Array.isArray(rollup) || rollup.length === 0) return "UNKNOWN";
1948
2376
  let hasRunning = false;
@@ -2181,132 +2609,12 @@ function isDispatchGated(capability, mode) {
2181
2609
  function trustRefusal(capabilitySlug) {
2182
2610
  return `Not dispatched: capability \`${capabilitySlug ?? "?"}\` is in ASK mode (not trusted for autonomy). Do NOT retry the dispatch. Instead notify the operator (use recommend_to_operator, or rely on the tracking issue that already @-mentions them), then submit_state. To let this capability act on its own, grant it Auto on the dashboard Trust page.`;
2183
2611
  }
2184
- function dashboardBaseUrl() {
2185
- const raw = process.env.KODY_CMS_DASHBOARD_URL?.trim() || process.env.KODY_DASHBOARD_URL?.trim() || process.env.DASHBOARD_URL?.trim() || "";
2186
- if (!raw) return null;
2187
- try {
2188
- const parsed = new URL(raw);
2189
- return parsed.origin;
2190
- } catch {
2191
- return null;
2192
- }
2193
- }
2194
- function dashboardCmsToken() {
2195
- return process.env.KODY_CMS_TOKEN?.trim() || process.env.KODY_DASHBOARD_TOKEN?.trim() || process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || process.env.GITHUB_TOKEN?.trim() || process.env.GH_PAT?.trim() || null;
2196
- }
2197
- function cmsHeaders(opts) {
2198
- const token = dashboardCmsToken();
2199
- const [owner, repo] = opts.repoSlug.split("/");
2200
- if (!token) {
2201
- return {
2202
- ok: false,
2203
- error: "missing_cms_token",
2204
- message: "Set KODY_CMS_TOKEN, KODY_DASHBOARD_TOKEN, KODY_TOKEN, GH_TOKEN, GITHUB_TOKEN, or GH_PAT."
2205
- };
2206
- }
2207
- if (!owner || !repo) {
2208
- return { ok: false, error: "invalid_repo", message: `Invalid repo slug: ${opts.repoSlug}` };
2209
- }
2210
- return {
2211
- ok: true,
2212
- headers: {
2213
- "Content-Type": "application/json",
2214
- "x-kody-token": token,
2215
- "x-kody-owner": owner,
2216
- "x-kody-repo": repo
2217
- }
2218
- };
2219
- }
2220
- async function callDashboardCms(opts, path50, init = {}) {
2221
- const baseUrl = dashboardBaseUrl();
2222
- if (!baseUrl) {
2223
- return {
2224
- ok: false,
2225
- error: "missing_dashboard_url",
2226
- message: "Set KODY_CMS_DASHBOARD_URL or KODY_DASHBOARD_URL to the Dashboard origin."
2227
- };
2228
- }
2229
- const headerResult = cmsHeaders(opts);
2230
- if (!headerResult.ok) return headerResult;
2231
- try {
2232
- const res = await fetch(`${baseUrl}${path50}`, {
2233
- ...init,
2234
- headers: {
2235
- ...headerResult.headers,
2236
- ...init.headers
2237
- }
2238
- });
2239
- const contentType = res.headers.get("content-type") ?? "";
2240
- const data = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => "");
2241
- if (!res.ok) {
2242
- return {
2243
- ok: false,
2244
- status: res.status,
2245
- error: stringFieldFromRecord(data, "error") ?? "cms_request_failed",
2246
- message: stringFieldFromRecord(data, "message") ?? `Dashboard CMS request failed with ${res.status}.`,
2247
- data
2248
- };
2249
- }
2250
- return { ok: true, status: res.status, data };
2251
- } catch (err) {
2252
- return {
2253
- ok: false,
2254
- error: "cms_request_error",
2255
- message: err instanceof Error ? err.message : String(err)
2256
- };
2257
- }
2258
- }
2259
- function cmsToolResponse(result) {
2260
- return {
2261
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2262
- ...result.ok ? {} : { isError: true }
2263
- };
2264
- }
2265
2612
  function assertCmsWriteAllowed(opts) {
2266
2613
  if (isDispatchGated(opts.capabilitySlug, readCapabilityTrustMode(opts.state, opts.repoSlug, opts.capabilitySlug))) {
2267
2614
  return trustRefusal(opts.capabilitySlug);
2268
2615
  }
2269
2616
  return null;
2270
2617
  }
2271
- function cmsQuery(args) {
2272
- const params = new URLSearchParams();
2273
- const q = stringArg(args.q);
2274
- if (q) params.set("q", q);
2275
- const limit = numberArg(args.limit);
2276
- if (limit !== void 0) params.set("limit", String(limit));
2277
- const offset = numberArg(args.offset);
2278
- if (offset !== void 0) params.set("offset", String(offset));
2279
- if (args.filters && typeof args.filters === "object" && !Array.isArray(args.filters)) {
2280
- params.set("filters", JSON.stringify(args.filters));
2281
- }
2282
- if (Array.isArray(args.sort)) {
2283
- const sort = args.sort.flatMap((entry) => {
2284
- if (!entry || typeof entry !== "object") return [];
2285
- const field = stringArg(entry.field);
2286
- if (!field) return [];
2287
- const direction = entry.direction === "asc" ? "asc" : "desc";
2288
- return [`${field}:${direction}`];
2289
- }).join(",");
2290
- if (sort) params.set("sort", sort);
2291
- }
2292
- const value = params.toString();
2293
- return value ? `?${value}` : "";
2294
- }
2295
- function stringArg(value) {
2296
- return typeof value === "string" ? value.trim() : "";
2297
- }
2298
- function numberArg(value) {
2299
- if (value === void 0 || value === null || value === "") return void 0;
2300
- const n = Number(value);
2301
- return Number.isFinite(n) ? n : void 0;
2302
- }
2303
- function documentArg(value) {
2304
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2305
- return value;
2306
- }
2307
- function stringFieldFromRecord(value, field) {
2308
- return value && typeof value === "object" && !Array.isArray(value) && typeof value[field] === "string" ? String(value[field]) : void 0;
2309
- }
2310
2618
  function capabilityToolDefinitions(opts) {
2311
2619
  const workflowFile = opts.workflowFile ?? "kody.yml";
2312
2620
  const listTool = {
@@ -2329,7 +2637,7 @@ function capabilityToolDefinitions(opts) {
2329
2637
  name: `${verb.replace("-", "_")}_pr`,
2330
2638
  description: describe,
2331
2639
  inputSchema: {
2332
- pr: z2.number().int().positive().describe("PR number to repair.")
2640
+ pr: z3.number().int().positive().describe("PR number to repair.")
2333
2641
  },
2334
2642
  handler: async (args) => {
2335
2643
  const pr = Number(args.pr);
@@ -2357,8 +2665,8 @@ function capabilityToolDefinitions(opts) {
2357
2665
  name: "recommend_to_operator",
2358
2666
  description: "Post ONE comment on a PR with the operator @-mention prepended. Use this when a capability is in ASK mode and you want the operator to confirm via the dashboard inbox. The mention handle is substituted from kody.config.json `github.operators` \u2014 do not type it yourself.",
2359
2667
  inputSchema: {
2360
- pr: z2.number().int().positive().describe("PR number to comment on."),
2361
- body: z2.string().min(1).describe("Comment body (markdown). Do not include the operator mention \u2014 the engine prepends it.")
2668
+ pr: z3.number().int().positive().describe("PR number to comment on."),
2669
+ body: z3.string().min(1).describe("Comment body (markdown). Do not include the operator mention \u2014 the engine prepends it.")
2362
2670
  },
2363
2671
  handler: async (args) => {
2364
2672
  const pr = Number(args.pr);
@@ -2372,7 +2680,7 @@ function capabilityToolDefinitions(opts) {
2372
2680
  name: "read_ledger",
2373
2681
  description: "Read any sentinel-fenced JSON manifest stored on a labeled issue. Returns `{found, issueNumber, payload}` where payload is the parsed JSON between `<!-- <label>:start -->` and `<!-- <label>:end -->` sentinels.",
2374
2682
  inputSchema: {
2375
- label: z2.string().min(1).describe("GitHub issue label that identifies the manifest issue.")
2683
+ label: z3.string().min(1).describe("GitHub issue label that identifies the manifest issue.")
2376
2684
  },
2377
2685
  handler: async (args) => {
2378
2686
  const label = String(args.label ?? "");
@@ -2384,8 +2692,8 @@ function capabilityToolDefinitions(opts) {
2384
2692
  name: "read_check_runs",
2385
2693
  description: "Read CI for a branch or commit ref (e.g. 'dev'). Returns {sha, state, failing:[{name,conclusion,detailsUrl}], pending:[{name,status}]}. state is RED (\u22651 check has a terminal-failure conclusion: failure/timed_out/startup_failure/action_required), PENDING (none failed but some still running), or GREEN (all completed, none failed). Kody's own job check-runs (run/kody/capability-tick/\u2026) are excluded by default. This reads the commit's authoritative check-runs \u2014 use it instead of guessing CI health from a run list.",
2386
2694
  inputSchema: {
2387
- ref: z2.string().min(1).describe("Branch name or commit SHA to read CI for (e.g. 'dev')."),
2388
- ignoreNames: z2.array(z2.string()).optional().describe("Check names to exclude (default: Kody's own job names).")
2695
+ ref: z3.string().min(1).describe("Branch name or commit SHA to read CI for (e.g. 'dev')."),
2696
+ ignoreNames: z3.array(z3.string()).optional().describe("Check names to exclude (default: Kody's own job names).")
2389
2697
  },
2390
2698
  handler: async (args) => {
2391
2699
  const ref = String(args.ref ?? "");
@@ -2398,8 +2706,8 @@ function capabilityToolDefinitions(opts) {
2398
2706
  name: "read_thread",
2399
2707
  description: "Read an issue or PR's recent comments + labels + title/state. Returns {number, title, state, labels:[...], comments:[{author, createdAt, body}]} (newest last, body truncated). Use this to read a verdict a dispatched check posted back \u2014 e.g. qa-engineer's report or ui-review's PASS/CONCERNS/FAIL \u2014 on a later tick. Read-only; works for both issues AND PRs.",
2400
2708
  inputSchema: {
2401
- number: z2.number().int().positive().describe("Issue or PR number to read."),
2402
- limit: z2.number().int().positive().optional().describe("Max recent comments to return (default 10).")
2709
+ number: z3.number().int().positive().describe("Issue or PR number to read."),
2710
+ limit: z3.number().int().positive().optional().describe("Max recent comments to return (default 10).")
2403
2711
  },
2404
2712
  handler: async (args) => {
2405
2713
  const number = Number(args.number);
@@ -2412,11 +2720,11 @@ function capabilityToolDefinitions(opts) {
2412
2720
  name: "ensure_issue",
2413
2721
  description: "Idempotently ensure ONE open tracking issue exists for `key`. Searches OPEN issues (issues API, not the laggy search index) for `key`'s hidden marker; if found, returns {created:false, number} and creates NOTHING; otherwise creates the issue (title + body, marker appended) and returns {created:true, number}. This is the anti-duplication primitive: use one stable `key` per recurring finding so re-ticks reuse the same issue. Only take follow-up actions (dispatch/comment) when created===true.",
2414
2722
  inputSchema: {
2415
- key: z2.string().min(1).describe(
2723
+ key: z3.string().min(1).describe(
2416
2724
  "Stable dedup identity for this finding (e.g. 'dev-ci-red', 'docs-drift:<feature>'). Same key across ticks = same issue."
2417
2725
  ),
2418
- title: z2.string().min(1).describe("Issue title (used only on first creation)."),
2419
- body: z2.string().min(1).describe(
2726
+ title: z3.string().min(1).describe("Issue title (used only on first creation)."),
2727
+ body: z3.string().min(1).describe(
2420
2728
  "Issue body markdown (used only on first creation). Include the operator mention verbatim if the capability body has one."
2421
2729
  )
2422
2730
  },
@@ -2432,9 +2740,9 @@ function capabilityToolDefinitions(opts) {
2432
2740
  name: "ensure_comment",
2433
2741
  description: "Idempotently post ONE comment on an issue for `key`. If a comment carrying `key`'s marker already exists on the issue, returns {posted:false}; otherwise posts the comment (marker appended) and returns {posted:true}. Use for a notify/audit comment that must appear exactly once.",
2434
2742
  inputSchema: {
2435
- issue: z2.number().int().positive().describe("Issue number to comment on."),
2436
- key: z2.string().min(1).describe("Stable dedup identity for this comment (e.g. 'dev-ci-red:dispatched')."),
2437
- body: z2.string().min(1).describe("Comment body markdown.")
2743
+ issue: z3.number().int().positive().describe("Issue number to comment on."),
2744
+ key: z3.string().min(1).describe("Stable dedup identity for this comment (e.g. 'dev-ci-red:dispatched')."),
2745
+ body: z3.string().min(1).describe("Comment body markdown.")
2438
2746
  },
2439
2747
  handler: async (args) => {
2440
2748
  const issue = Number(args.issue);
@@ -2448,9 +2756,9 @@ function capabilityToolDefinitions(opts) {
2448
2756
  name: "dispatch_workflow",
2449
2757
  description: "Dispatch a kody.yml workflow_dispatch run for a capability action against an issue (the cross-run bot\u2192engine path; a bot `@kody` comment would be dropped). E.g. dispatch_workflow({capability:'run', issueNumber:<n>}) opens a fix PR from a tracking issue. Returns {ok} or {ok:false,error}.",
2450
2758
  inputSchema: {
2451
- capability: z2.string().min(1).optional().describe("Capability action to run (e.g. 'run')."),
2452
- executable: z2.string().min(1).optional().describe("Deprecated alias for capability."),
2453
- issueNumber: z2.number().int().positive().describe("Issue (or PR) number forwarded as issue_number.")
2759
+ capability: z3.string().min(1).optional().describe("Capability action to run (e.g. 'run')."),
2760
+ executable: z3.string().min(1).optional().describe("Deprecated alias for capability."),
2761
+ issueNumber: z3.number().int().positive().describe("Issue (or PR) number forwarded as issue_number.")
2454
2762
  },
2455
2763
  handler: async (args) => {
2456
2764
  const capability = String(args.capability ?? args.executable ?? "");
@@ -2463,86 +2771,10 @@ function capabilityToolDefinitions(opts) {
2463
2771
  return { content: [{ type: "text", text }] };
2464
2772
  }
2465
2773
  };
2466
- const cmsListCollectionsTool = {
2467
- name: "cms_list_collections",
2468
- description: "List configured Dashboard CMS collections and their supported operations. Read-only.",
2469
- inputSchema: {},
2470
- handler: async () => cmsToolResponse(await callDashboardCms(opts, "/api/kody/cms"))
2471
- };
2472
- const cmsListDocumentsTool = {
2473
- name: "cms_list_documents",
2474
- description: "List or search Dashboard CMS documents from one configured collection. Read-only.",
2475
- inputSchema: {
2476
- collection: z2.string().min(1).describe("CMS collection name."),
2477
- q: z2.string().optional().describe("Optional search query."),
2478
- filters: z2.record(z2.string(), z2.unknown()).optional().describe("Optional filter object keyed by field."),
2479
- sort: z2.array(
2480
- z2.object({
2481
- field: z2.string().min(1),
2482
- direction: z2.enum(["asc", "desc"]).default("desc")
2483
- })
2484
- ).optional(),
2485
- limit: z2.number().int().min(1).max(100).optional(),
2486
- offset: z2.number().int().min(0).optional()
2487
- },
2488
- handler: async (args) => {
2489
- const collection = encodeURIComponent(stringArg(args.collection));
2490
- return cmsToolResponse(await callDashboardCms(opts, `/api/kody/cms/${collection}${cmsQuery(args)}`));
2491
- }
2492
- };
2493
- const cmsGetDocumentTool = {
2494
- name: "cms_get_document",
2495
- description: "Get one Dashboard CMS document by collection and id. Read-only.",
2496
- inputSchema: {
2497
- collection: z2.string().min(1).describe("CMS collection name."),
2498
- id: z2.string().min(1).describe("Document id.")
2499
- },
2500
- handler: async (args) => {
2501
- const collection = encodeURIComponent(stringArg(args.collection));
2502
- const id = encodeURIComponent(stringArg(args.id));
2503
- return cmsToolResponse(await callDashboardCms(opts, `/api/kody/cms/${collection}/${id}`));
2504
- }
2505
- };
2506
- const cmsCreateDocumentTool = {
2507
- name: "cms_create_document",
2508
- description: "Create one Dashboard CMS document when the capability is trusted and the CMS collection allows create.",
2509
- inputSchema: {
2510
- collection: z2.string().min(1).describe("CMS collection name."),
2511
- data: z2.record(z2.string(), z2.unknown()).describe("Document fields to create.")
2512
- },
2513
- handler: async (args) => {
2514
- const refusal = assertCmsWriteAllowed(opts);
2515
- if (refusal) return { content: [{ type: "text", text: refusal }] };
2516
- const collection = encodeURIComponent(stringArg(args.collection));
2517
- return cmsToolResponse(
2518
- await callDashboardCms(opts, `/api/kody/cms/${collection}`, {
2519
- method: "POST",
2520
- body: JSON.stringify(documentArg(args.data))
2521
- })
2522
- );
2523
- }
2524
- };
2525
- const cmsUpdateDocumentTool = {
2526
- name: "cms_update_document",
2527
- description: "Update one Dashboard CMS document when the capability is trusted and the CMS collection allows update.",
2528
- inputSchema: {
2529
- collection: z2.string().min(1).describe("CMS collection name."),
2530
- id: z2.string().min(1).describe("Document id."),
2531
- data: z2.record(z2.string(), z2.unknown()).describe("Partial document fields to update.")
2532
- },
2533
- handler: async (args) => {
2534
- const refusal = assertCmsWriteAllowed(opts);
2535
- if (refusal) return { content: [{ type: "text", text: refusal }] };
2536
- const collection = encodeURIComponent(stringArg(args.collection));
2537
- const id = encodeURIComponent(stringArg(args.id));
2538
- return cmsToolResponse(
2539
- await callDashboardCms(opts, `/api/kody/cms/${collection}/${id}`, {
2540
- method: "PATCH",
2541
- body: JSON.stringify(documentArg(args.data))
2542
- })
2543
- );
2544
- }
2545
- };
2774
+ const cmsTools = dashboardCmsToolDefinitions({
2775
+ repoSlug: opts.repoSlug,
2776
+ assertWriteAllowed: () => assertCmsWriteAllowed(opts)
2777
+ });
2546
2778
  return [
2547
2779
  listTool,
2548
2780
  syncTool,
@@ -2555,24 +2787,20 @@ function capabilityToolDefinitions(opts) {
2555
2787
  ensureIssueTool,
2556
2788
  ensureCommentTool,
2557
2789
  dispatchTool,
2558
- cmsListCollectionsTool,
2559
- cmsListDocumentsTool,
2560
- cmsGetDocumentTool,
2561
- cmsCreateDocumentTool,
2562
- cmsUpdateDocumentTool
2790
+ ...cmsTools
2563
2791
  ];
2564
2792
  }
2565
2793
  function buildCapabilityMcpServer(opts) {
2566
2794
  const definitions = capabilityToolDefinitions(opts);
2567
2795
  const tools = definitions.map(
2568
- (def) => tool3(
2796
+ (def) => tool4(
2569
2797
  def.name,
2570
2798
  def.description,
2571
2799
  def.inputSchema,
2572
2800
  async (args) => def.handler(args)
2573
2801
  )
2574
2802
  );
2575
- const server = createSdkMcpServer3({
2803
+ const server = createSdkMcpServer4({
2576
2804
  name: "kody-capability",
2577
2805
  version: "0.1.0",
2578
2806
  tools
@@ -2583,6 +2811,7 @@ var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, THREAD_BODY_MAX, CHECK_
2583
2811
  var init_capabilityMcp = __esm({
2584
2812
  "src/capabilityMcp.ts"() {
2585
2813
  "use strict";
2814
+ init_dashboardCmsMcp();
2586
2815
  init_issue();
2587
2816
  init_registry();
2588
2817
  init_stateRepo();
@@ -2607,11 +2836,7 @@ var init_capabilityMcp = __esm({
2607
2836
  "ensure_issue",
2608
2837
  "ensure_comment",
2609
2838
  "dispatch_workflow",
2610
- "cms_list_collections",
2611
- "cms_list_documents",
2612
- "cms_get_document",
2613
- "cms_create_document",
2614
- "cms_update_document"
2839
+ ...DASHBOARD_CMS_MCP_TOOL_NAMES
2615
2840
  ];
2616
2841
  }
2617
2842
  });
@@ -2689,8 +2914,8 @@ __export(fetchRepoMcp_exports, {
2689
2914
  buildFetchRepoMcpServer: () => buildFetchRepoMcpServer,
2690
2915
  fetchRepoToolDefinition: () => fetchRepoToolDefinition
2691
2916
  });
2692
- import { createSdkMcpServer as createSdkMcpServer4, tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
2693
- import { z as z3 } from "zod";
2917
+ import { createSdkMcpServer as createSdkMcpServer5, tool as tool5 } from "@anthropic-ai/claude-agent-sdk";
2918
+ import { z as z4 } from "zod";
2694
2919
  function fetchRepoToolDefinition(opts) {
2695
2920
  return {
2696
2921
  name: "fetch_repo",
@@ -2725,10 +2950,10 @@ Use Read/Grep/Glob/Bash at that absolute path to explore it. It now lives in you
2725
2950
  }
2726
2951
  function buildFetchRepoMcpServer(opts) {
2727
2952
  const def = fetchRepoToolDefinition(opts);
2728
- const fetchTool = tool4(def.name, def.description, def.inputSchema, async (args) => {
2953
+ const fetchTool = tool5(def.name, def.description, def.inputSchema, async (args) => {
2729
2954
  return def.handler({ repo: String(args.repo ?? "") });
2730
2955
  });
2731
- return createSdkMcpServer4({
2956
+ return createSdkMcpServer5({
2732
2957
  name: "kody-fetch-repo",
2733
2958
  version: "0.1.0",
2734
2959
  tools: [fetchTool]
@@ -2741,7 +2966,7 @@ var init_fetchRepoMcp = __esm({
2741
2966
  init_repoWorkspace();
2742
2967
  DESCRIPTION3 = 'Clone another GitHub repository into your workspace so you can read and work on it. Pass `repo` as "owner/name" (e.g. "A-Guy-educ/A-Guy"). Returns the absolute path of the clone \u2014 then use your Read/Grep/Glob/Bash tools at that path to inspect it. Already-fetched repos are reused instantly. Use this whenever the user asks about a repository other than your current one \u2014 you are NOT limited to a single repo.';
2743
2968
  INPUT_SCHEMA3 = {
2744
- repo: z3.string().describe('GitHub repository as "owner/name", e.g. "A-Guy-educ/A-Guy".')
2969
+ repo: z4.string().describe('GitHub repository as "owner/name", e.g. "A-Guy-educ/A-Guy".')
2745
2970
  };
2746
2971
  }
2747
2972
  });
@@ -2825,7 +3050,7 @@ async function runAgent(opts) {
2825
3050
  const turnTimeoutMs = resolveTurnTimeoutMs(opts);
2826
3051
  let outcome = "failed";
2827
3052
  let outcomeKind = "generic_failed";
2828
- let errorMessage;
3053
+ let errorMessage2;
2829
3054
  let tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
2830
3055
  let messageCount = 0;
2831
3056
  let finalText = "";
@@ -2841,7 +3066,7 @@ async function runAgent(opts) {
2841
3066
  const resultTexts = [];
2842
3067
  outcome = "failed";
2843
3068
  outcomeKind = "generic_failed";
2844
- errorMessage = void 0;
3069
+ errorMessage2 = void 0;
2845
3070
  tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
2846
3071
  messageCount = 0;
2847
3072
  let sawMutatingTool = false;
@@ -2899,6 +3124,24 @@ async function runAgent(opts) {
2899
3124
  });
2900
3125
  mcpEntries.push(["kody-capability", dutyHandle.server]);
2901
3126
  }
3127
+ if (opts.enableDashboardCmsTool) {
3128
+ const { buildDashboardCmsMcpServer: buildDashboardCmsMcpServer2, DASHBOARD_CMS_MCP_TOOL_NAMES: DASHBOARD_CMS_MCP_TOOL_NAMES2 } = await Promise.resolve().then(() => (init_dashboardCmsMcp(), dashboardCmsMcp_exports));
3129
+ if (!opts.cmsRepoSlug) {
3130
+ throw new Error("enableDashboardCmsTool requires cmsRepoSlug (owner/name)");
3131
+ }
3132
+ const cmsHandle = buildDashboardCmsMcpServer2({
3133
+ repoSlug: opts.cmsRepoSlug,
3134
+ dashboardUrl: opts.cmsDashboardUrl,
3135
+ token: opts.cmsToken,
3136
+ storeRepoUrl: opts.cmsStoreRepoUrl,
3137
+ storeRef: opts.cmsStoreRef
3138
+ });
3139
+ mcpEntries.push(["kody-cms", cmsHandle.server]);
3140
+ for (const toolName of DASHBOARD_CMS_MCP_TOOL_NAMES2) {
3141
+ ;
3142
+ queryOptions.allowedTools.push(`mcp__kody-cms__${toolName}`);
3143
+ }
3144
+ }
2902
3145
  if (opts.enableFetchRepoTool && opts.reposRoot) {
2903
3146
  const { buildFetchRepoMcpServer: buildFetchRepoMcpServer2 } = await Promise.resolve().then(() => (init_fetchRepoMcp(), fetchRepoMcp_exports));
2904
3147
  const fetchServer = buildFetchRepoMcpServer2({
@@ -2979,7 +3222,7 @@ async function runAgent(opts) {
2979
3222
  if (timedOut) {
2980
3223
  outcome = "failed";
2981
3224
  outcomeKind = "stalled";
2982
- errorMessage = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
3225
+ errorMessage2 = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
2983
3226
  if (typeof iterator.return === "function") {
2984
3227
  try {
2985
3228
  await iterator.return(void 0);
@@ -3083,17 +3326,17 @@ async function runAgent(opts) {
3083
3326
  } else {
3084
3327
  outcome = "failed";
3085
3328
  outcomeKind = classifySubtype(m.subtype);
3086
- errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
3329
+ errorMessage2 = `result subtype: ${m.subtype ?? "unknown"}`;
3087
3330
  }
3088
3331
  }
3089
3332
  }
3090
3333
  } catch (e) {
3091
3334
  if (sawTerminalSuccess) {
3092
- errorMessage = e instanceof Error ? e.message : String(e);
3335
+ errorMessage2 = e instanceof Error ? e.message : String(e);
3093
3336
  } else {
3094
3337
  outcome = "failed";
3095
3338
  outcomeKind = "model_error";
3096
- errorMessage = e instanceof Error ? e.message : String(e);
3339
+ errorMessage2 = e instanceof Error ? e.message : String(e);
3097
3340
  }
3098
3341
  } finally {
3099
3342
  try {
@@ -3111,7 +3354,7 @@ async function runAgent(opts) {
3111
3354
  if (outcome === "completed" && sawLoginRequired) {
3112
3355
  outcome = "failed";
3113
3356
  outcomeKind = "model_error";
3114
- errorMessage = "Claude Code reported it is not logged in; refusing to mark agent run successful";
3357
+ errorMessage2 = "Claude Code reported it is not logged in; refusing to mark agent run successful";
3115
3358
  }
3116
3359
  if (outcome === "completed" && !sawMutatingTool) {
3117
3360
  const backendDead = opts.isBackendHealthy ? !await opts.isBackendHealthy() : false;
@@ -3120,14 +3363,14 @@ async function runAgent(opts) {
3120
3363
  outcome = "failed";
3121
3364
  outcomeKind = "model_error";
3122
3365
  noWorkSuccess = true;
3123
- errorMessage = errorMessage ?? (backendDead ? "model backend unreachable after a reported success \u2014 proxy crashed mid-request (hollow success)" : "session reported success but produced no model output (0 output tokens) \u2014 backend likely unreachable");
3366
+ errorMessage2 = errorMessage2 ?? (backendDead ? "model backend unreachable after a reported success \u2014 proxy crashed mid-request (hollow success)" : "session reported success but produced no model output (0 output tokens) \u2014 backend likely unreachable");
3124
3367
  }
3125
3368
  }
3126
- const shouldRetry = outcome === "failed" && attempt < MAX_CONNECTION_RETRIES && !sawMutatingTool && (isTransientConnectionError(errorMessage) || noWorkSuccess);
3369
+ const shouldRetry = outcome === "failed" && attempt < MAX_CONNECTION_RETRIES && !sawMutatingTool && (isTransientConnectionError(errorMessage2) || noWorkSuccess);
3127
3370
  if (!shouldRetry) break;
3128
3371
  const delayMs = CONNECTION_RETRY_BASE_MS * 2 ** attempt;
3129
3372
  process.stderr.write(
3130
- `[kody agent] transient connection error (attempt ${attempt + 1}/${MAX_CONNECTION_RETRIES + 1}); retrying in ${Math.round(delayMs / 1e3)}s: ${errorMessage}
3373
+ `[kody agent] transient connection error (attempt ${attempt + 1}/${MAX_CONNECTION_RETRIES + 1}); retrying in ${Math.round(delayMs / 1e3)}s: ${errorMessage2}
3131
3374
  `
3132
3375
  );
3133
3376
  if (opts.ensureBackend) {
@@ -3146,7 +3389,7 @@ async function runAgent(opts) {
3146
3389
  outcomeKind,
3147
3390
  finalText,
3148
3391
  ...submittedState ? { submittedState } : {},
3149
- error: errorMessage,
3392
+ error: errorMessage2,
3150
3393
  ndjsonPath,
3151
3394
  durationMs: Date.now() - startedAt,
3152
3395
  tokens,
@@ -5771,7 +6014,7 @@ var init_litellm = __esm({
5771
6014
 
5772
6015
  // src/pushWithRetry.ts
5773
6016
  import { execFileSync as execFileSync6 } from "child_process";
5774
- function sleepSync(ms) {
6017
+ function sleepSync2(ms) {
5775
6018
  if (ms <= 0) return;
5776
6019
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
5777
6020
  }
@@ -5832,7 +6075,7 @@ function pushWithRetry(opts = {}) {
5832
6075
  };
5833
6076
  }
5834
6077
  const delay = Math.min(baseBackoff * 2 ** (attempt - 1), MAX_BACKOFF_MS);
5835
- sleepSync(delay);
6078
+ sleepSync2(delay);
5836
6079
  }
5837
6080
  return {
5838
6081
  ok: false,
@@ -6077,6 +6320,13 @@ function jobMetaFromData(data) {
6077
6320
  why: typeof data.jobWhy === "string" ? data.jobWhy : void 0
6078
6321
  };
6079
6322
  }
6323
+ function applyStandaloneTerminalState(state, ctx, profile) {
6324
+ if (profile.lifecycleConfig?.finalize !== true || state.flow?.issueNumber) return;
6325
+ const delivered = ctx.output.exitCode === 0 && !!state.core.prUrl;
6326
+ state.core.phase = delivered ? "shipped" : "failed";
6327
+ state.core.status = delivered ? "succeeded" : "failed";
6328
+ state.core.currentExecutable = null;
6329
+ }
6080
6330
  function synthesizeAction(ctx) {
6081
6331
  const ok = ctx.output.exitCode === 0;
6082
6332
  return {
@@ -6107,6 +6357,7 @@ var init_saveTaskState = __esm({
6107
6357
  });
6108
6358
  if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
6109
6359
  if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
6360
+ applyStandaloneTerminalState(next, ctx, profile);
6110
6361
  writeTaskState(target, number, next, ctx.cwd, ctx.config);
6111
6362
  ctx.data.taskState = next;
6112
6363
  ctx.data.taskStateRendered = renderStateComment(next);
@@ -11436,12 +11687,9 @@ var init_finalizeTerminal = __esm({
11436
11687
  const issueNumber = ctx.args.issue;
11437
11688
  const targetNumber = ctx.data.commentTargetNumber ?? issueNumber;
11438
11689
  if (!targetNumber) return;
11439
- let prUrl;
11440
- try {
11441
- prUrl = readTaskState(target, targetNumber, ctx.cwd, ctx.config).core.prUrl;
11442
- } catch {
11443
- prUrl = void 0;
11444
- }
11690
+ const cachedState = ctx.data.taskState;
11691
+ let state = cachedState;
11692
+ const prUrl = cachedState?.core.prUrl ?? ctx.output.prUrl ?? ctx.data.prResult?.url;
11445
11693
  const delivered = ctx.output.exitCode === 0 && !!prUrl;
11446
11694
  const spec = delivered ? DONE : FAILED;
11447
11695
  const phase = delivered ? "shipped" : "failed";
@@ -11449,12 +11697,28 @@ var init_finalizeTerminal = __esm({
11449
11697
  if (issueNumber) setKodyLabel(issueNumber, spec, ctx.cwd);
11450
11698
  const prNumber = prUrl ? parsePrNumber(prUrl) : null;
11451
11699
  if (prNumber && prNumber !== issueNumber) setKodyLabel(prNumber, spec, ctx.cwd);
11700
+ if (!state) {
11701
+ try {
11702
+ state = readTaskState(target, targetNumber, ctx.cwd, ctx.config);
11703
+ } catch {
11704
+ state = void 0;
11705
+ }
11706
+ }
11707
+ if (!state) return;
11708
+ const alreadyTerminal = state.core.phase === phase && state.core.status === status && state.core.currentExecutable === null;
11709
+ if (alreadyTerminal) return;
11710
+ const next = {
11711
+ ...state,
11712
+ core: {
11713
+ ...state.core,
11714
+ phase,
11715
+ status,
11716
+ currentExecutable: null
11717
+ }
11718
+ };
11719
+ ctx.data.taskState = next;
11452
11720
  try {
11453
- const state = readTaskState(target, targetNumber, ctx.cwd, ctx.config);
11454
- state.core.phase = phase;
11455
- state.core.status = status;
11456
- state.core.currentExecutable = null;
11457
- writeTaskState(target, targetNumber, state, ctx.cwd, ctx.config);
11721
+ writeTaskState(target, targetNumber, next, ctx.cwd, ctx.config);
11458
11722
  } catch (err) {
11459
11723
  process.stderr.write(
11460
11724
  `[kody finalizeTerminal] failed to write terminal state on ${target} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -13119,17 +13383,22 @@ var init_mirrorStateToPr = __esm({
13119
13383
  const issueNumber = ctx.data.commentTargetNumber;
13120
13384
  const issueTarget = ctx.data.commentTargetType;
13121
13385
  if (!issueNumber || issueTarget !== "issue") return;
13122
- const prUrl = ctx.output.prUrl ?? ctx.data.prResult?.url;
13386
+ let state = ctx.data.taskState;
13387
+ const prUrl = ctx.output.prUrl ?? ctx.data.prResult?.url ?? state?.core.prUrl;
13123
13388
  if (!prUrl) return;
13124
13389
  const prNumber = parsePrNumber4(prUrl);
13125
13390
  if (!prNumber) return;
13126
- let state;
13127
- try {
13128
- state = readTaskState("issue", issueNumber, ctx.cwd, ctx.config);
13129
- } catch {
13130
- return;
13391
+ if (!state) {
13392
+ try {
13393
+ state = readTaskState("issue", issueNumber, ctx.cwd, ctx.config);
13394
+ } catch {
13395
+ return;
13396
+ }
13397
+ }
13398
+ if (prUrl && !state.core.prUrl) {
13399
+ state = { ...state, core: { ...state.core, prUrl } };
13400
+ ctx.data.taskState = state;
13131
13401
  }
13132
- if (prUrl && !state.core.prUrl) state.core.prUrl = prUrl;
13133
13402
  try {
13134
13403
  writeTaskState("pr", prNumber, state, ctx.cwd, ctx.config);
13135
13404
  } catch (err) {
@@ -17093,24 +17362,24 @@ function firstRequiredFailure(results, tools) {
17093
17362
  }
17094
17363
  return null;
17095
17364
  }
17096
- function verifyOne(tool5, cwd) {
17097
- const result = { name: tool5.name, present: false, verified: false };
17098
- const checkRes = runShell(tool5.install.checkCommand, cwd);
17365
+ function verifyOne(tool6, cwd) {
17366
+ const result = { name: tool6.name, present: false, verified: false };
17367
+ const checkRes = runShell(tool6.install.checkCommand, cwd);
17099
17368
  let present = checkRes.ok;
17100
- if (!present && tool5.install.installCommand) {
17101
- runShell(tool5.install.installCommand, cwd, 12e4);
17102
- present = runShell(tool5.install.checkCommand, cwd).ok;
17369
+ if (!present && tool6.install.installCommand) {
17370
+ runShell(tool6.install.installCommand, cwd, 12e4);
17371
+ present = runShell(tool6.install.checkCommand, cwd).ok;
17103
17372
  }
17104
17373
  result.present = present;
17105
17374
  if (!present) {
17106
- result.error = `tool "${tool5.name}" not on PATH (check: ${tool5.install.checkCommand})`;
17375
+ result.error = `tool "${tool6.name}" not on PATH (check: ${tool6.install.checkCommand})`;
17107
17376
  return result;
17108
17377
  }
17109
- const verifyRes = runShell(tool5.verify, cwd);
17378
+ const verifyRes = runShell(tool6.verify, cwd);
17110
17379
  result.verified = verifyRes.ok;
17111
17380
  if (!verifyRes.ok) {
17112
17381
  const tail = formatStderrTail(verifyRes.stderr, verifyRes.stdout);
17113
- result.error = `tool "${tool5.name}" failed verify: ${tool5.verify}${tail ? ` \u2014 ${tail}` : ""}`;
17382
+ result.error = `tool "${tool6.name}" failed verify: ${tool6.verify}${tail ? ` \u2014 ${tail}` : ""}`;
17114
17383
  }
17115
17384
  return result;
17116
17385
  }
@@ -18822,6 +19091,12 @@ var CROSS_REPO_PROMPT = [
18822
19091
  "instantly. When the user asks about a different repo \u2014 or to compare repos \u2014",
18823
19092
  "fetch it instead of saying you are scoped to a single repo."
18824
19093
  ].join("\n");
19094
+ var DASHBOARD_CMS_PROMPT = [
19095
+ "# Dashboard CMS",
19096
+ "Use Dashboard CMS tools for CMS content. The Dashboard is the source of truth.",
19097
+ "Do not query Mongo directly for CMS content and do not infer CMS data from repository files.",
19098
+ "Use cms_list_documents first when you need to find content, then pass the raw id or cmsDocumentId to cms_get_document."
19099
+ ].join("\n");
18825
19100
  function buildExecutableCatalog() {
18826
19101
  let discovered;
18827
19102
  try {
@@ -18880,6 +19155,8 @@ async function runChatTurn(opts) {
18880
19155
  const memoryBlock = readMemoryIndexBlock(opts.cwd);
18881
19156
  const instructionsBlock = readInstructionsBlock(opts.cwd);
18882
19157
  const crossRepoBlock = opts.reposRoot ? CROSS_REPO_PROMPT : null;
19158
+ const dashboardCmsEnabled = Boolean(opts.cmsDashboardUrl && opts.cmsRepoSlug && opts.cmsToken);
19159
+ const dashboardCmsBlock = dashboardCmsEnabled ? DASHBOARD_CMS_PROMPT : null;
18883
19160
  const imageBlock = imagePaths.length > 0 ? [
18884
19161
  "# Attached images",
18885
19162
  "The user attached one or more images on this turn. They are saved as",
@@ -18894,6 +19171,7 @@ async function runChatTurn(opts) {
18894
19171
  memoryBlock,
18895
19172
  instructionsBlock,
18896
19173
  crossRepoBlock,
19174
+ dashboardCmsBlock,
18897
19175
  imageBlock,
18898
19176
  catalog,
18899
19177
  artifactAddendum
@@ -18921,6 +19199,14 @@ async function runChatTurn(opts) {
18921
19199
  reposRoot: opts.reposRoot,
18922
19200
  repoToken: opts.repoToken
18923
19201
  } : {},
19202
+ ...dashboardCmsEnabled ? {
19203
+ enableDashboardCmsTool: true,
19204
+ cmsDashboardUrl: opts.cmsDashboardUrl,
19205
+ cmsRepoSlug: opts.cmsRepoSlug,
19206
+ cmsToken: opts.cmsToken,
19207
+ cmsStoreRepoUrl: opts.cmsStoreRepoUrl,
19208
+ cmsStoreRef: opts.cmsStoreRef
19209
+ } : {},
18924
19210
  onProgress: async (ev) => {
18925
19211
  progressSeq += 1;
18926
19212
  if (ev.kind === "thinking") {
@@ -19019,7 +19305,9 @@ function readMemoryIndexBlock(cwd) {
19019
19305
  }
19020
19306
  const trimmed = raw.trim();
19021
19307
  if (!trimmed) return "";
19022
- const body = trimmed.length > MAX_INDEX_BYTES ? trimmed.slice(0, MAX_INDEX_BYTES) + "\n\n_\u2026 (memory index truncated; use recall_search to read more)_" : trimmed;
19308
+ const body = trimmed.length > MAX_INDEX_BYTES ? `${trimmed.slice(0, MAX_INDEX_BYTES)}
19309
+
19310
+ _\u2026 (memory index truncated; use recall_search to read more)_` : trimmed;
19023
19311
  return [
19024
19312
  "# Project memory index (state repo `memory/INDEX.md`)",
19025
19313
  "",
@@ -20647,6 +20935,9 @@ async function handleChatTurn(req, res, chatId, opts) {
20647
20935
  }
20648
20936
  const repo = strField(body, "repo");
20649
20937
  const repoToken = strField(body, "repoToken");
20938
+ const dashboardUrl = strField(body, "dashboardUrl");
20939
+ const storeRepoUrl = strField(body, "storeRepoUrl");
20940
+ const storeRef = strField(body, "storeRef");
20650
20941
  let agentCwd;
20651
20942
  try {
20652
20943
  agentCwd = await ensureRepoCwd({
@@ -20723,7 +21014,14 @@ async function handleChatTurn(req, res, chatId, opts) {
20723
21014
  sink,
20724
21015
  // Let the agent clone + work on OTHER repos via the fetch_repo tool.
20725
21016
  reposRoot: opts.reposRoot,
20726
- repoToken
21017
+ repoToken,
21018
+ ...dashboardUrl && repo && stateToken ? {
21019
+ cmsDashboardUrl: dashboardUrl,
21020
+ cmsRepoSlug: repo,
21021
+ cmsToken: stateToken,
21022
+ cmsStoreRepoUrl: storeRepoUrl,
21023
+ cmsStoreRef: storeRef
21024
+ } : {}
20727
21025
  });
20728
21026
  } catch (err) {
20729
21027
  const errMsg3 = err instanceof Error ? err.message : String(err);
@@ -20980,7 +21278,10 @@ async function proxyToBrainServe(args) {
20980
21278
  body: JSON.stringify({
20981
21279
  message: args.body.message,
20982
21280
  ...args.body.repo ? { repo: args.body.repo } : {},
20983
- ...args.body.repoToken ? { repoToken: args.body.repoToken } : {}
21281
+ ...args.body.repoToken ? { repoToken: args.body.repoToken } : {},
21282
+ ...args.body.dashboardUrl ? { dashboardUrl: args.body.dashboardUrl } : {},
21283
+ ...args.body.storeRepoUrl ? { storeRepoUrl: args.body.storeRepoUrl } : {},
21284
+ ...args.body.storeRef ? { storeRef: args.body.storeRef } : {}
20984
21285
  })
20985
21286
  });
20986
21287
  if (!upstream.ok || !upstream.body) {
@@ -21195,15 +21496,15 @@ function buildMcpHttpServer(opts) {
21195
21496
  transports.set(route.path, transport);
21196
21497
  routes.set(route.path, route.name);
21197
21498
  const mcpServer = new McpServer({ name: route.name, version: route.version });
21198
- for (const tool5 of route.tools) {
21499
+ for (const tool6 of route.tools) {
21199
21500
  mcpServer.registerTool(
21200
- tool5.name,
21501
+ tool6.name,
21201
21502
  {
21202
- description: tool5.description,
21203
- inputSchema: tool5.inputSchema
21503
+ description: tool6.description,
21504
+ inputSchema: tool6.inputSchema
21204
21505
  },
21205
21506
  async (args) => {
21206
- const result = await tool5.handler(args);
21507
+ const result = await tool6.handler(args);
21207
21508
  return {
21208
21509
  content: result.content,
21209
21510
  ...result.isError ? { isError: true } : {}
@@ -22509,7 +22810,7 @@ var PERF_GUEST = {
22509
22810
  medium: { cpu_kind: "performance", cpus: 1, memory_mb: 2048 },
22510
22811
  high: { cpu_kind: "performance", cpus: 2, memory_mb: 4096 }
22511
22812
  };
22512
- function envInt(name, dflt) {
22813
+ function envInt2(name, dflt) {
22513
22814
  const v = Number(process.env[name]);
22514
22815
  return Number.isFinite(v) && v > 0 ? v : dflt;
22515
22816
  }
@@ -22629,10 +22930,10 @@ async function poolServe() {
22629
22930
  const region = process.env.POOL_REGION ?? "fra";
22630
22931
  const perf = process.env.POOL_PERF ?? "medium";
22631
22932
  const guest = PERF_GUEST[perf] ?? PERF_GUEST.medium;
22632
- const min = envInt("POOL_MIN", 2);
22633
- const runnerPort = envInt("RUNNER_PORT", 8080);
22634
- const apiPort = envInt("POOL_API_PORT", 4100);
22635
- const healthTimeoutMs = envInt("POOL_HEALTH_TIMEOUT_MS", 12e4);
22933
+ const min = envInt2("POOL_MIN", 2);
22934
+ const runnerPort = envInt2("RUNNER_PORT", 8080);
22935
+ const apiPort = envInt2("POOL_API_PORT", 4100);
22936
+ const healthTimeoutMs = envInt2("POOL_HEALTH_TIMEOUT_MS", 12e4);
22636
22937
  const registry = new PoolRegistry({
22637
22938
  githubToken,
22638
22939
  masterKey: master,
@@ -22648,12 +22949,12 @@ async function poolServe() {
22648
22949
  },
22649
22950
  log
22650
22951
  });
22651
- const refillMs = envInt("POOL_REFILL_INTERVAL_MS", 6e4);
22952
+ const refillMs = envInt2("POOL_REFILL_INTERVAL_MS", 6e4);
22652
22953
  const tick = setInterval(() => {
22653
22954
  registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
22654
22955
  }, refillMs);
22655
22956
  const dutyTickEnabled = (process.env.POOL_CAPABILITY_TICK ?? "1") !== "0";
22656
- const dutyTickMs = envInt("POOL_CAPABILITY_TICK_MS", 15 * 6e4);
22957
+ const dutyTickMs = envInt2("POOL_CAPABILITY_TICK_MS", 15 * 6e4);
22657
22958
  const dutyTick = dutyTickEnabled ? setInterval(() => {
22658
22959
  runCapabilityFallbackTick({
22659
22960
  isDegraded: () => gitHubActionsDegraded(),