@kody-ade/kody-engine 0.4.277 → 0.4.279

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 +768 -437
  2. package/package.json +24 -23
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.277",
18
+ version: "0.4.279",
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",
@@ -147,23 +147,106 @@ var init_claudeBinary = __esm({
147
147
  // src/issue.ts
148
148
  import { execFileSync } from "child_process";
149
149
  function ghToken(preferRepoToken = false) {
150
- const githubToken = process.env.GITHUB_TOKEN?.trim();
150
+ const githubToken2 = process.env.GITHUB_TOKEN?.trim();
151
151
  const kodyToken = process.env.KODY_TOKEN?.trim();
152
152
  const ghToken3 = process.env.GH_TOKEN?.trim();
153
153
  const ghPat = process.env.GH_PAT?.trim();
154
- return preferRepoToken ? githubToken || kodyToken || ghToken3 || ghPat : ghPat || ghToken3 || kodyToken || githubToken;
154
+ return preferRepoToken ? githubToken2 || kodyToken || ghToken3 || ghPat : ghPat || ghToken3 || kodyToken || githubToken2;
155
155
  }
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(
@@ -446,8 +532,11 @@ function branchApiPath(config, targetPath) {
446
532
  }
447
533
  function ensureStateBranch(config, cwd) {
448
534
  const parsed = parseStateRepo(config);
535
+ const cacheKey3 = `${parsed.owner}/${parsed.repo}:${STATE_BRANCH}`;
536
+ if (ensuredStateBranches.has(cacheKey3)) return;
449
537
  try {
450
538
  gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
539
+ ensuredStateBranches.add(cacheKey3);
451
540
  return;
452
541
  } catch (err) {
453
542
  if (!is404(err)) throw err;
@@ -466,6 +555,7 @@ function ensureStateBranch(config, cwd) {
466
555
  } catch (err) {
467
556
  if (!isAlreadyExists(err)) throw err;
468
557
  }
558
+ ensuredStateBranches.add(cacheKey3);
469
559
  }
470
560
  function readStateText(config, cwd, filePath) {
471
561
  const targetPath = stateRepoPath(config, filePath);
@@ -539,11 +629,13 @@ function listStateDirectory(config, cwd, dirPath) {
539
629
  const parsed = JSON.parse(raw);
540
630
  return Array.isArray(parsed) ? parsed : [];
541
631
  }
632
+ var ensuredStateBranches;
542
633
  var init_stateRepo = __esm({
543
634
  "src/stateRepo.ts"() {
544
635
  "use strict";
545
636
  init_issue();
546
637
  init_stateBranch();
638
+ ensuredStateBranches = /* @__PURE__ */ new Set();
547
639
  }
548
640
  });
549
641
 
@@ -1116,7 +1208,7 @@ var init_events = __esm({
1116
1208
  // src/verify.ts
1117
1209
  import { spawn } from "child_process";
1118
1210
  function runCommand(command, cwd) {
1119
- return new Promise((resolve8) => {
1211
+ return new Promise((resolve9) => {
1120
1212
  const start = Date.now();
1121
1213
  const child = spawn(command, {
1122
1214
  cwd,
@@ -1145,11 +1237,11 @@ function runCommand(command, cwd) {
1145
1237
  child.on("exit", (code) => {
1146
1238
  clearTimeout(timer);
1147
1239
  const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
1148
- resolve8({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
1240
+ resolve9({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
1149
1241
  });
1150
1242
  child.on("error", (err) => {
1151
1243
  clearTimeout(timer);
1152
- resolve8({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
1244
+ resolve9({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
1153
1245
  });
1154
1246
  });
1155
1247
  }
@@ -1413,6 +1505,300 @@ var init_submitMcp = __esm({
1413
1505
  }
1414
1506
  });
1415
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"() {
1791
+ "use strict";
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
+ ];
1799
+ }
1800
+ });
1801
+
1416
1802
  // src/capabilityFolders.ts
1417
1803
  import * as fs4 from "fs";
1418
1804
  import * as path6 from "path";
@@ -1983,8 +2369,8 @@ __export(capabilityMcp_exports, {
1983
2369
  readCheckRuns: () => readCheckRuns,
1984
2370
  readThread: () => readThread
1985
2371
  });
1986
- import { createSdkMcpServer as createSdkMcpServer3, tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
1987
- 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";
1988
2374
  function summarizeCiStatus(rollup) {
1989
2375
  if (!Array.isArray(rollup) || rollup.length === 0) return "UNKNOWN";
1990
2376
  let hasRunning = false;
@@ -2168,141 +2554,60 @@ ${marker}` });
2168
2554
  } catch (err) {
2169
2555
  return { error: err instanceof Error ? err.message : String(err) };
2170
2556
  }
2171
- }
2172
- function dispatchWorkflow(workflowFile, capability, issueNumber, repoSlug) {
2173
- const expected = expectedDispatchTarget(capability);
2174
- if (repoSlug && expected) {
2175
- const target = readDispatchTargetKind(repoSlug, issueNumber);
2176
- if (!target.ok) return target;
2177
- if (expected === "issue" && target.kind === "pr") {
2178
- return {
2179
- ok: false,
2180
- error: `refusing to dispatch ${capability} on PR #${issueNumber}; dispatch the source issue or use a PR action`
2181
- };
2182
- }
2183
- if (expected === "pr" && target.kind === "issue") {
2184
- return {
2185
- ok: false,
2186
- error: `refusing to dispatch ${capability} on issue #${issueNumber}; expected a PR target`
2187
- };
2188
- }
2189
- }
2190
- try {
2191
- gh(["workflow", "run", workflowFile, "-f", `capability=${capability}`, "-f", `issue_number=${issueNumber}`]);
2192
- return { ok: true };
2193
- } catch (err) {
2194
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
2195
- }
2196
- }
2197
- function expectedDispatchTarget(capability) {
2198
- const route = resolveCapabilityAction(capability);
2199
- if (!route) return null;
2200
- const inputs = getProfileInputs(route.executable);
2201
- const numeric = inputs?.find((input) => input.type === "int" && input.required);
2202
- if (numeric?.name === "issue") return "issue";
2203
- if (numeric?.name === "pr") return "pr";
2204
- return null;
2205
- }
2206
- function readDispatchTargetKind(repoSlug, issueNumber) {
2207
- try {
2208
- const raw = gh(["api", `repos/${repoSlug}/issues/${issueNumber}`]);
2209
- const parsed = JSON.parse(raw);
2210
- return { ok: true, kind: parsed.pull_request ? "pr" : "issue" };
2211
- } catch (err) {
2212
- return {
2213
- ok: false,
2214
- error: `could not verify target #${issueNumber}: ${err instanceof Error ? err.message : String(err)}`
2215
- };
2216
- }
2217
- }
2218
- function isDispatchGated(capability, mode) {
2219
- if (mode === "auto") return false;
2220
- if (capability && GATE_EXEMPT_DUTIES.has(capability)) return false;
2221
- return true;
2222
- }
2223
- function trustRefusal(capabilitySlug) {
2224
- 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.`;
2225
- }
2226
- function dashboardBaseUrl() {
2227
- const raw = process.env.KODY_CMS_DASHBOARD_URL?.trim() || process.env.KODY_DASHBOARD_URL?.trim() || process.env.DASHBOARD_URL?.trim() || "";
2228
- if (!raw) return null;
2229
- try {
2230
- const parsed = new URL(raw);
2231
- return parsed.origin;
2232
- } catch {
2233
- return null;
2234
- }
2235
- }
2236
- function dashboardCmsToken() {
2237
- 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;
2238
- }
2239
- function cmsHeaders(opts) {
2240
- const token = dashboardCmsToken();
2241
- const [owner, repo] = opts.repoSlug.split("/");
2242
- if (!token) {
2243
- return {
2244
- ok: false,
2245
- error: "missing_cms_token",
2246
- message: "Set KODY_CMS_TOKEN, KODY_DASHBOARD_TOKEN, KODY_TOKEN, GH_TOKEN, GITHUB_TOKEN, or GH_PAT."
2247
- };
2248
- }
2249
- if (!owner || !repo) {
2250
- return { ok: false, error: "invalid_repo", message: `Invalid repo slug: ${opts.repoSlug}` };
2251
- }
2252
- return {
2253
- ok: true,
2254
- headers: {
2255
- "Content-Type": "application/json",
2256
- "x-kody-token": token,
2257
- "x-kody-owner": owner,
2258
- "x-kody-repo": repo
2259
- }
2260
- };
2261
- }
2262
- async function callDashboardCms(opts, path50, init = {}) {
2263
- const baseUrl = dashboardBaseUrl();
2264
- if (!baseUrl) {
2265
- return {
2266
- ok: false,
2267
- error: "missing_dashboard_url",
2268
- message: "Set KODY_CMS_DASHBOARD_URL or KODY_DASHBOARD_URL to the Dashboard origin."
2269
- };
2270
- }
2271
- const headerResult = cmsHeaders(opts);
2272
- if (!headerResult.ok) return headerResult;
2273
- try {
2274
- const res = await fetch(`${baseUrl}${path50}`, {
2275
- ...init,
2276
- headers: {
2277
- ...headerResult.headers,
2278
- ...init.headers
2279
- }
2280
- });
2281
- const contentType = res.headers.get("content-type") ?? "";
2282
- const data = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => "");
2283
- if (!res.ok) {
2557
+ }
2558
+ function dispatchWorkflow(workflowFile, capability, issueNumber, repoSlug) {
2559
+ const expected = expectedDispatchTarget(capability);
2560
+ if (repoSlug && expected) {
2561
+ const target = readDispatchTargetKind(repoSlug, issueNumber);
2562
+ if (!target.ok) return target;
2563
+ if (expected === "issue" && target.kind === "pr") {
2284
2564
  return {
2285
2565
  ok: false,
2286
- status: res.status,
2287
- error: stringFieldFromRecord(data, "error") ?? "cms_request_failed",
2288
- message: stringFieldFromRecord(data, "message") ?? `Dashboard CMS request failed with ${res.status}.`,
2289
- data
2566
+ error: `refusing to dispatch ${capability} on PR #${issueNumber}; dispatch the source issue or use a PR action`
2290
2567
  };
2291
2568
  }
2292
- return { ok: true, status: res.status, data };
2569
+ if (expected === "pr" && target.kind === "issue") {
2570
+ return {
2571
+ ok: false,
2572
+ error: `refusing to dispatch ${capability} on issue #${issueNumber}; expected a PR target`
2573
+ };
2574
+ }
2575
+ }
2576
+ try {
2577
+ gh(["workflow", "run", workflowFile, "-f", `capability=${capability}`, "-f", `issue_number=${issueNumber}`]);
2578
+ return { ok: true };
2579
+ } catch (err) {
2580
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
2581
+ }
2582
+ }
2583
+ function expectedDispatchTarget(capability) {
2584
+ const route = resolveCapabilityAction(capability);
2585
+ if (!route) return null;
2586
+ const inputs = getProfileInputs(route.executable);
2587
+ const numeric = inputs?.find((input) => input.type === "int" && input.required);
2588
+ if (numeric?.name === "issue") return "issue";
2589
+ if (numeric?.name === "pr") return "pr";
2590
+ return null;
2591
+ }
2592
+ function readDispatchTargetKind(repoSlug, issueNumber) {
2593
+ try {
2594
+ const raw = gh(["api", `repos/${repoSlug}/issues/${issueNumber}`]);
2595
+ const parsed = JSON.parse(raw);
2596
+ return { ok: true, kind: parsed.pull_request ? "pr" : "issue" };
2293
2597
  } catch (err) {
2294
2598
  return {
2295
2599
  ok: false,
2296
- error: "cms_request_error",
2297
- message: err instanceof Error ? err.message : String(err)
2600
+ error: `could not verify target #${issueNumber}: ${err instanceof Error ? err.message : String(err)}`
2298
2601
  };
2299
2602
  }
2300
2603
  }
2301
- function cmsToolResponse(result) {
2302
- return {
2303
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2304
- ...result.ok ? {} : { isError: true }
2305
- };
2604
+ function isDispatchGated(capability, mode) {
2605
+ if (mode === "auto") return false;
2606
+ if (capability && GATE_EXEMPT_DUTIES.has(capability)) return false;
2607
+ return true;
2608
+ }
2609
+ function trustRefusal(capabilitySlug) {
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.`;
2306
2611
  }
2307
2612
  function assertCmsWriteAllowed(opts) {
2308
2613
  if (isDispatchGated(opts.capabilitySlug, readCapabilityTrustMode(opts.state, opts.repoSlug, opts.capabilitySlug))) {
@@ -2310,45 +2615,6 @@ function assertCmsWriteAllowed(opts) {
2310
2615
  }
2311
2616
  return null;
2312
2617
  }
2313
- function cmsQuery(args) {
2314
- const params = new URLSearchParams();
2315
- const q = stringArg(args.q);
2316
- if (q) params.set("q", q);
2317
- const limit = numberArg(args.limit);
2318
- if (limit !== void 0) params.set("limit", String(limit));
2319
- const offset = numberArg(args.offset);
2320
- if (offset !== void 0) params.set("offset", String(offset));
2321
- if (args.filters && typeof args.filters === "object" && !Array.isArray(args.filters)) {
2322
- params.set("filters", JSON.stringify(args.filters));
2323
- }
2324
- if (Array.isArray(args.sort)) {
2325
- const sort = args.sort.flatMap((entry) => {
2326
- if (!entry || typeof entry !== "object") return [];
2327
- const field = stringArg(entry.field);
2328
- if (!field) return [];
2329
- const direction = entry.direction === "asc" ? "asc" : "desc";
2330
- return [`${field}:${direction}`];
2331
- }).join(",");
2332
- if (sort) params.set("sort", sort);
2333
- }
2334
- const value = params.toString();
2335
- return value ? `?${value}` : "";
2336
- }
2337
- function stringArg(value) {
2338
- return typeof value === "string" ? value.trim() : "";
2339
- }
2340
- function numberArg(value) {
2341
- if (value === void 0 || value === null || value === "") return void 0;
2342
- const n = Number(value);
2343
- return Number.isFinite(n) ? n : void 0;
2344
- }
2345
- function documentArg(value) {
2346
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2347
- return value;
2348
- }
2349
- function stringFieldFromRecord(value, field) {
2350
- return value && typeof value === "object" && !Array.isArray(value) && typeof value[field] === "string" ? String(value[field]) : void 0;
2351
- }
2352
2618
  function capabilityToolDefinitions(opts) {
2353
2619
  const workflowFile = opts.workflowFile ?? "kody.yml";
2354
2620
  const listTool = {
@@ -2371,7 +2637,7 @@ function capabilityToolDefinitions(opts) {
2371
2637
  name: `${verb.replace("-", "_")}_pr`,
2372
2638
  description: describe,
2373
2639
  inputSchema: {
2374
- pr: z2.number().int().positive().describe("PR number to repair.")
2640
+ pr: z3.number().int().positive().describe("PR number to repair.")
2375
2641
  },
2376
2642
  handler: async (args) => {
2377
2643
  const pr = Number(args.pr);
@@ -2399,8 +2665,8 @@ function capabilityToolDefinitions(opts) {
2399
2665
  name: "recommend_to_operator",
2400
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.",
2401
2667
  inputSchema: {
2402
- pr: z2.number().int().positive().describe("PR number to comment on."),
2403
- 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.")
2404
2670
  },
2405
2671
  handler: async (args) => {
2406
2672
  const pr = Number(args.pr);
@@ -2414,7 +2680,7 @@ function capabilityToolDefinitions(opts) {
2414
2680
  name: "read_ledger",
2415
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.",
2416
2682
  inputSchema: {
2417
- 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.")
2418
2684
  },
2419
2685
  handler: async (args) => {
2420
2686
  const label = String(args.label ?? "");
@@ -2426,8 +2692,8 @@ function capabilityToolDefinitions(opts) {
2426
2692
  name: "read_check_runs",
2427
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.",
2428
2694
  inputSchema: {
2429
- ref: z2.string().min(1).describe("Branch name or commit SHA to read CI for (e.g. 'dev')."),
2430
- 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).")
2431
2697
  },
2432
2698
  handler: async (args) => {
2433
2699
  const ref = String(args.ref ?? "");
@@ -2440,8 +2706,8 @@ function capabilityToolDefinitions(opts) {
2440
2706
  name: "read_thread",
2441
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.",
2442
2708
  inputSchema: {
2443
- number: z2.number().int().positive().describe("Issue or PR number to read."),
2444
- 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).")
2445
2711
  },
2446
2712
  handler: async (args) => {
2447
2713
  const number = Number(args.number);
@@ -2454,11 +2720,11 @@ function capabilityToolDefinitions(opts) {
2454
2720
  name: "ensure_issue",
2455
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.",
2456
2722
  inputSchema: {
2457
- key: z2.string().min(1).describe(
2723
+ key: z3.string().min(1).describe(
2458
2724
  "Stable dedup identity for this finding (e.g. 'dev-ci-red', 'docs-drift:<feature>'). Same key across ticks = same issue."
2459
2725
  ),
2460
- title: z2.string().min(1).describe("Issue title (used only on first creation)."),
2461
- 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(
2462
2728
  "Issue body markdown (used only on first creation). Include the operator mention verbatim if the capability body has one."
2463
2729
  )
2464
2730
  },
@@ -2474,9 +2740,9 @@ function capabilityToolDefinitions(opts) {
2474
2740
  name: "ensure_comment",
2475
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.",
2476
2742
  inputSchema: {
2477
- issue: z2.number().int().positive().describe("Issue number to comment on."),
2478
- key: z2.string().min(1).describe("Stable dedup identity for this comment (e.g. 'dev-ci-red:dispatched')."),
2479
- 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.")
2480
2746
  },
2481
2747
  handler: async (args) => {
2482
2748
  const issue = Number(args.issue);
@@ -2490,9 +2756,9 @@ function capabilityToolDefinitions(opts) {
2490
2756
  name: "dispatch_workflow",
2491
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}.",
2492
2758
  inputSchema: {
2493
- capability: z2.string().min(1).optional().describe("Capability action to run (e.g. 'run')."),
2494
- executable: z2.string().min(1).optional().describe("Deprecated alias for capability."),
2495
- 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.")
2496
2762
  },
2497
2763
  handler: async (args) => {
2498
2764
  const capability = String(args.capability ?? args.executable ?? "");
@@ -2505,86 +2771,10 @@ function capabilityToolDefinitions(opts) {
2505
2771
  return { content: [{ type: "text", text }] };
2506
2772
  }
2507
2773
  };
2508
- const cmsListCollectionsTool = {
2509
- name: "cms_list_collections",
2510
- description: "List configured Dashboard CMS collections and their supported operations. Read-only.",
2511
- inputSchema: {},
2512
- handler: async () => cmsToolResponse(await callDashboardCms(opts, "/api/kody/cms"))
2513
- };
2514
- const cmsListDocumentsTool = {
2515
- name: "cms_list_documents",
2516
- description: "List or search Dashboard CMS documents from one configured collection. Read-only.",
2517
- inputSchema: {
2518
- collection: z2.string().min(1).describe("CMS collection name."),
2519
- q: z2.string().optional().describe("Optional search query."),
2520
- filters: z2.record(z2.string(), z2.unknown()).optional().describe("Optional filter object keyed by field."),
2521
- sort: z2.array(
2522
- z2.object({
2523
- field: z2.string().min(1),
2524
- direction: z2.enum(["asc", "desc"]).default("desc")
2525
- })
2526
- ).optional(),
2527
- limit: z2.number().int().min(1).max(100).optional(),
2528
- offset: z2.number().int().min(0).optional()
2529
- },
2530
- handler: async (args) => {
2531
- const collection = encodeURIComponent(stringArg(args.collection));
2532
- return cmsToolResponse(await callDashboardCms(opts, `/api/kody/cms/${collection}${cmsQuery(args)}`));
2533
- }
2534
- };
2535
- const cmsGetDocumentTool = {
2536
- name: "cms_get_document",
2537
- description: "Get one Dashboard CMS document by collection and id. Read-only.",
2538
- inputSchema: {
2539
- collection: z2.string().min(1).describe("CMS collection name."),
2540
- id: z2.string().min(1).describe("Document id.")
2541
- },
2542
- handler: async (args) => {
2543
- const collection = encodeURIComponent(stringArg(args.collection));
2544
- const id = encodeURIComponent(stringArg(args.id));
2545
- return cmsToolResponse(await callDashboardCms(opts, `/api/kody/cms/${collection}/${id}`));
2546
- }
2547
- };
2548
- const cmsCreateDocumentTool = {
2549
- name: "cms_create_document",
2550
- description: "Create one Dashboard CMS document when the capability is trusted and the CMS collection allows create.",
2551
- inputSchema: {
2552
- collection: z2.string().min(1).describe("CMS collection name."),
2553
- data: z2.record(z2.string(), z2.unknown()).describe("Document fields to create.")
2554
- },
2555
- handler: async (args) => {
2556
- const refusal = assertCmsWriteAllowed(opts);
2557
- if (refusal) return { content: [{ type: "text", text: refusal }] };
2558
- const collection = encodeURIComponent(stringArg(args.collection));
2559
- return cmsToolResponse(
2560
- await callDashboardCms(opts, `/api/kody/cms/${collection}`, {
2561
- method: "POST",
2562
- body: JSON.stringify(documentArg(args.data))
2563
- })
2564
- );
2565
- }
2566
- };
2567
- const cmsUpdateDocumentTool = {
2568
- name: "cms_update_document",
2569
- description: "Update one Dashboard CMS document when the capability is trusted and the CMS collection allows update.",
2570
- inputSchema: {
2571
- collection: z2.string().min(1).describe("CMS collection name."),
2572
- id: z2.string().min(1).describe("Document id."),
2573
- data: z2.record(z2.string(), z2.unknown()).describe("Partial document fields to update.")
2574
- },
2575
- handler: async (args) => {
2576
- const refusal = assertCmsWriteAllowed(opts);
2577
- if (refusal) return { content: [{ type: "text", text: refusal }] };
2578
- const collection = encodeURIComponent(stringArg(args.collection));
2579
- const id = encodeURIComponent(stringArg(args.id));
2580
- return cmsToolResponse(
2581
- await callDashboardCms(opts, `/api/kody/cms/${collection}/${id}`, {
2582
- method: "PATCH",
2583
- body: JSON.stringify(documentArg(args.data))
2584
- })
2585
- );
2586
- }
2587
- };
2774
+ const cmsTools = dashboardCmsToolDefinitions({
2775
+ repoSlug: opts.repoSlug,
2776
+ assertWriteAllowed: () => assertCmsWriteAllowed(opts)
2777
+ });
2588
2778
  return [
2589
2779
  listTool,
2590
2780
  syncTool,
@@ -2597,24 +2787,20 @@ function capabilityToolDefinitions(opts) {
2597
2787
  ensureIssueTool,
2598
2788
  ensureCommentTool,
2599
2789
  dispatchTool,
2600
- cmsListCollectionsTool,
2601
- cmsListDocumentsTool,
2602
- cmsGetDocumentTool,
2603
- cmsCreateDocumentTool,
2604
- cmsUpdateDocumentTool
2790
+ ...cmsTools
2605
2791
  ];
2606
2792
  }
2607
2793
  function buildCapabilityMcpServer(opts) {
2608
2794
  const definitions = capabilityToolDefinitions(opts);
2609
2795
  const tools = definitions.map(
2610
- (def) => tool3(
2796
+ (def) => tool4(
2611
2797
  def.name,
2612
2798
  def.description,
2613
2799
  def.inputSchema,
2614
2800
  async (args) => def.handler(args)
2615
2801
  )
2616
2802
  );
2617
- const server = createSdkMcpServer3({
2803
+ const server = createSdkMcpServer4({
2618
2804
  name: "kody-capability",
2619
2805
  version: "0.1.0",
2620
2806
  tools
@@ -2625,6 +2811,7 @@ var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, THREAD_BODY_MAX, CHECK_
2625
2811
  var init_capabilityMcp = __esm({
2626
2812
  "src/capabilityMcp.ts"() {
2627
2813
  "use strict";
2814
+ init_dashboardCmsMcp();
2628
2815
  init_issue();
2629
2816
  init_registry();
2630
2817
  init_stateRepo();
@@ -2649,11 +2836,7 @@ var init_capabilityMcp = __esm({
2649
2836
  "ensure_issue",
2650
2837
  "ensure_comment",
2651
2838
  "dispatch_workflow",
2652
- "cms_list_collections",
2653
- "cms_list_documents",
2654
- "cms_get_document",
2655
- "cms_create_document",
2656
- "cms_update_document"
2839
+ ...DASHBOARD_CMS_MCP_TOOL_NAMES
2657
2840
  ];
2658
2841
  }
2659
2842
  });
@@ -2701,7 +2884,7 @@ var init_repoWorkspace = __esm({
2701
2884
  defaultCloneRepo = (repo, token, dir) => {
2702
2885
  fs7.mkdirSync(path9.dirname(dir), { recursive: true });
2703
2886
  const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
2704
- return new Promise((resolve8, reject) => {
2887
+ return new Promise((resolve9, reject) => {
2705
2888
  const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
2706
2889
  stdio: "inherit"
2707
2890
  });
@@ -2717,7 +2900,7 @@ var init_repoWorkspace = __esm({
2717
2900
  spawnSync("git", ["-C", dir, "config", "user.email", email]);
2718
2901
  } catch {
2719
2902
  }
2720
- resolve8();
2903
+ resolve9();
2721
2904
  });
2722
2905
  child.on("error", reject);
2723
2906
  });
@@ -2731,8 +2914,8 @@ __export(fetchRepoMcp_exports, {
2731
2914
  buildFetchRepoMcpServer: () => buildFetchRepoMcpServer,
2732
2915
  fetchRepoToolDefinition: () => fetchRepoToolDefinition
2733
2916
  });
2734
- import { createSdkMcpServer as createSdkMcpServer4, tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
2735
- 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";
2736
2919
  function fetchRepoToolDefinition(opts) {
2737
2920
  return {
2738
2921
  name: "fetch_repo",
@@ -2767,10 +2950,10 @@ Use Read/Grep/Glob/Bash at that absolute path to explore it. It now lives in you
2767
2950
  }
2768
2951
  function buildFetchRepoMcpServer(opts) {
2769
2952
  const def = fetchRepoToolDefinition(opts);
2770
- const fetchTool = tool4(def.name, def.description, def.inputSchema, async (args) => {
2953
+ const fetchTool = tool5(def.name, def.description, def.inputSchema, async (args) => {
2771
2954
  return def.handler({ repo: String(args.repo ?? "") });
2772
2955
  });
2773
- return createSdkMcpServer4({
2956
+ return createSdkMcpServer5({
2774
2957
  name: "kody-fetch-repo",
2775
2958
  version: "0.1.0",
2776
2959
  tools: [fetchTool]
@@ -2783,7 +2966,7 @@ var init_fetchRepoMcp = __esm({
2783
2966
  init_repoWorkspace();
2784
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.';
2785
2968
  INPUT_SCHEMA3 = {
2786
- 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".')
2787
2970
  };
2788
2971
  }
2789
2972
  });
@@ -2867,7 +3050,7 @@ async function runAgent(opts) {
2867
3050
  const turnTimeoutMs = resolveTurnTimeoutMs(opts);
2868
3051
  let outcome = "failed";
2869
3052
  let outcomeKind = "generic_failed";
2870
- let errorMessage;
3053
+ let errorMessage2;
2871
3054
  let tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
2872
3055
  let messageCount = 0;
2873
3056
  let finalText = "";
@@ -2883,7 +3066,7 @@ async function runAgent(opts) {
2883
3066
  const resultTexts = [];
2884
3067
  outcome = "failed";
2885
3068
  outcomeKind = "generic_failed";
2886
- errorMessage = void 0;
3069
+ errorMessage2 = void 0;
2887
3070
  tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
2888
3071
  messageCount = 0;
2889
3072
  let sawMutatingTool = false;
@@ -2941,6 +3124,24 @@ async function runAgent(opts) {
2941
3124
  });
2942
3125
  mcpEntries.push(["kody-capability", dutyHandle.server]);
2943
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
+ }
2944
3145
  if (opts.enableFetchRepoTool && opts.reposRoot) {
2945
3146
  const { buildFetchRepoMcpServer: buildFetchRepoMcpServer2 } = await Promise.resolve().then(() => (init_fetchRepoMcp(), fetchRepoMcp_exports));
2946
3147
  const fetchServer = buildFetchRepoMcpServer2({
@@ -3007,10 +3208,10 @@ async function runAgent(opts) {
3007
3208
  let timer;
3008
3209
  let next;
3009
3210
  if (turnTimeoutMs > 0) {
3010
- const timeoutPromise = new Promise((resolve8) => {
3211
+ const timeoutPromise = new Promise((resolve9) => {
3011
3212
  timer = setTimeout(() => {
3012
3213
  timedOut = true;
3013
- resolve8({ done: true, value: void 0 });
3214
+ resolve9({ done: true, value: void 0 });
3014
3215
  }, turnTimeoutMs);
3015
3216
  });
3016
3217
  next = await Promise.race([nextPromise, timeoutPromise]);
@@ -3021,7 +3222,7 @@ async function runAgent(opts) {
3021
3222
  if (timedOut) {
3022
3223
  outcome = "failed";
3023
3224
  outcomeKind = "stalled";
3024
- 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`;
3025
3226
  if (typeof iterator.return === "function") {
3026
3227
  try {
3027
3228
  await iterator.return(void 0);
@@ -3125,17 +3326,17 @@ async function runAgent(opts) {
3125
3326
  } else {
3126
3327
  outcome = "failed";
3127
3328
  outcomeKind = classifySubtype(m.subtype);
3128
- errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
3329
+ errorMessage2 = `result subtype: ${m.subtype ?? "unknown"}`;
3129
3330
  }
3130
3331
  }
3131
3332
  }
3132
3333
  } catch (e) {
3133
3334
  if (sawTerminalSuccess) {
3134
- errorMessage = e instanceof Error ? e.message : String(e);
3335
+ errorMessage2 = e instanceof Error ? e.message : String(e);
3135
3336
  } else {
3136
3337
  outcome = "failed";
3137
3338
  outcomeKind = "model_error";
3138
- errorMessage = e instanceof Error ? e.message : String(e);
3339
+ errorMessage2 = e instanceof Error ? e.message : String(e);
3139
3340
  }
3140
3341
  } finally {
3141
3342
  try {
@@ -3153,7 +3354,7 @@ async function runAgent(opts) {
3153
3354
  if (outcome === "completed" && sawLoginRequired) {
3154
3355
  outcome = "failed";
3155
3356
  outcomeKind = "model_error";
3156
- 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";
3157
3358
  }
3158
3359
  if (outcome === "completed" && !sawMutatingTool) {
3159
3360
  const backendDead = opts.isBackendHealthy ? !await opts.isBackendHealthy() : false;
@@ -3162,14 +3363,14 @@ async function runAgent(opts) {
3162
3363
  outcome = "failed";
3163
3364
  outcomeKind = "model_error";
3164
3365
  noWorkSuccess = true;
3165
- 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");
3166
3367
  }
3167
3368
  }
3168
- 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);
3169
3370
  if (!shouldRetry) break;
3170
3371
  const delayMs = CONNECTION_RETRY_BASE_MS * 2 ** attempt;
3171
3372
  process.stderr.write(
3172
- `[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}
3173
3374
  `
3174
3375
  );
3175
3376
  if (opts.ensureBackend) {
@@ -3188,7 +3389,7 @@ async function runAgent(opts) {
3188
3389
  outcomeKind,
3189
3390
  finalText,
3190
3391
  ...submittedState ? { submittedState } : {},
3191
- error: errorMessage,
3392
+ error: errorMessage2,
3192
3393
  ndjsonPath,
3193
3394
  durationMs: Date.now() - startedAt,
3194
3395
  tokens,
@@ -5813,7 +6014,7 @@ var init_litellm = __esm({
5813
6014
 
5814
6015
  // src/pushWithRetry.ts
5815
6016
  import { execFileSync as execFileSync6 } from "child_process";
5816
- function sleepSync(ms) {
6017
+ function sleepSync2(ms) {
5817
6018
  if (ms <= 0) return;
5818
6019
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
5819
6020
  }
@@ -5874,7 +6075,7 @@ function pushWithRetry(opts = {}) {
5874
6075
  };
5875
6076
  }
5876
6077
  const delay = Math.min(baseBackoff * 2 ** (attempt - 1), MAX_BACKOFF_MS);
5877
- sleepSync(delay);
6078
+ sleepSync2(delay);
5878
6079
  }
5879
6080
  return {
5880
6081
  ok: false,
@@ -6119,6 +6320,13 @@ function jobMetaFromData(data) {
6119
6320
  why: typeof data.jobWhy === "string" ? data.jobWhy : void 0
6120
6321
  };
6121
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
+ }
6122
6330
  function synthesizeAction(ctx) {
6123
6331
  const ok = ctx.output.exitCode === 0;
6124
6332
  return {
@@ -6149,6 +6357,7 @@ var init_saveTaskState = __esm({
6149
6357
  });
6150
6358
  if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
6151
6359
  if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
6360
+ applyStandaloneTerminalState(next, ctx, profile);
6152
6361
  writeTaskState(target, number, next, ctx.cwd, ctx.config);
6153
6362
  ctx.data.taskState = next;
6154
6363
  ctx.data.taskStateRendered = renderStateComment(next);
@@ -11478,12 +11687,9 @@ var init_finalizeTerminal = __esm({
11478
11687
  const issueNumber = ctx.args.issue;
11479
11688
  const targetNumber = ctx.data.commentTargetNumber ?? issueNumber;
11480
11689
  if (!targetNumber) return;
11481
- let prUrl;
11482
- try {
11483
- prUrl = readTaskState(target, targetNumber, ctx.cwd, ctx.config).core.prUrl;
11484
- } catch {
11485
- prUrl = void 0;
11486
- }
11690
+ const cachedState = ctx.data.taskState;
11691
+ let state = cachedState;
11692
+ const prUrl = cachedState?.core.prUrl ?? ctx.output.prUrl ?? ctx.data.prResult?.url;
11487
11693
  const delivered = ctx.output.exitCode === 0 && !!prUrl;
11488
11694
  const spec = delivered ? DONE : FAILED;
11489
11695
  const phase = delivered ? "shipped" : "failed";
@@ -11491,12 +11697,28 @@ var init_finalizeTerminal = __esm({
11491
11697
  if (issueNumber) setKodyLabel(issueNumber, spec, ctx.cwd);
11492
11698
  const prNumber = prUrl ? parsePrNumber(prUrl) : null;
11493
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;
11494
11720
  try {
11495
- const state = readTaskState(target, targetNumber, ctx.cwd, ctx.config);
11496
- state.core.phase = phase;
11497
- state.core.status = status;
11498
- state.core.currentExecutable = null;
11499
- writeTaskState(target, targetNumber, state, ctx.cwd, ctx.config);
11721
+ writeTaskState(target, targetNumber, next, ctx.cwd, ctx.config);
11500
11722
  } catch (err) {
11501
11723
  process.stderr.write(
11502
11724
  `[kody finalizeTerminal] failed to write terminal state on ${target} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
@@ -12436,7 +12658,7 @@ function retryDelaysMs() {
12436
12658
  }
12437
12659
  function sleep(ms) {
12438
12660
  if (ms <= 0) return Promise.resolve();
12439
- return new Promise((resolve8) => setTimeout(resolve8, ms));
12661
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
12440
12662
  }
12441
12663
  async function fetchGoalStateWithRetry(config, goalId, cwd) {
12442
12664
  let state = fetchGoalState(config, goalId, cwd);
@@ -13161,17 +13383,22 @@ var init_mirrorStateToPr = __esm({
13161
13383
  const issueNumber = ctx.data.commentTargetNumber;
13162
13384
  const issueTarget = ctx.data.commentTargetType;
13163
13385
  if (!issueNumber || issueTarget !== "issue") return;
13164
- 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;
13165
13388
  if (!prUrl) return;
13166
13389
  const prNumber = parsePrNumber4(prUrl);
13167
13390
  if (!prNumber) return;
13168
- let state;
13169
- try {
13170
- state = readTaskState("issue", issueNumber, ctx.cwd, ctx.config);
13171
- } catch {
13172
- 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;
13173
13401
  }
13174
- if (prUrl && !state.core.prUrl) state.core.prUrl = prUrl;
13175
13402
  try {
13176
13403
  writeTaskState("pr", prNumber, state, ctx.cwd, ctx.config);
13177
13404
  } catch (err) {
@@ -15099,7 +15326,7 @@ var init_previewBuildHelpers = __esm({
15099
15326
  // src/scripts/previewBuildRun.ts
15100
15327
  import { spawn as spawn4 } from "child_process";
15101
15328
  async function runCmd(cmd, args, opts = {}) {
15102
- await new Promise((resolve8, reject) => {
15329
+ await new Promise((resolve9, reject) => {
15103
15330
  const child = spawn4(cmd, args, {
15104
15331
  cwd: opts.cwd,
15105
15332
  env: { ...process.env, ...opts.env ?? {} },
@@ -15111,7 +15338,7 @@ async function runCmd(cmd, args, opts = {}) {
15111
15338
  }
15112
15339
  child.on("error", reject);
15113
15340
  child.on("close", (code) => {
15114
- if (code === 0) resolve8();
15341
+ if (code === 0) resolve9();
15115
15342
  else reject(new Error(`${cmd} ${args.join(" ")} exited ${code}`));
15116
15343
  });
15117
15344
  });
@@ -16235,7 +16462,7 @@ function stripAnsi2(s) {
16235
16462
  return s.replace(ANSI_RE2, "");
16236
16463
  }
16237
16464
  function runCommand2(command, cwd) {
16238
- return new Promise((resolve8) => {
16465
+ return new Promise((resolve9) => {
16239
16466
  const child = spawn5(command, {
16240
16467
  cwd,
16241
16468
  shell: true,
@@ -16262,11 +16489,11 @@ function runCommand2(command, cwd) {
16262
16489
  }, TEST_TIMEOUT_MS);
16263
16490
  child.on("exit", (code) => {
16264
16491
  clearTimeout(timer);
16265
- resolve8({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
16492
+ resolve9({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
16266
16493
  });
16267
16494
  child.on("error", (err) => {
16268
16495
  clearTimeout(timer);
16269
- resolve8({ exitCode: -1, output: err.message });
16496
+ resolve9({ exitCode: -1, output: err.message });
16270
16497
  });
16271
16498
  });
16272
16499
  }
@@ -16671,21 +16898,21 @@ function lineStream(stream) {
16671
16898
  tryDeliver();
16672
16899
  });
16673
16900
  return {
16674
- next: (timeoutMs) => new Promise((resolve8) => {
16901
+ next: (timeoutMs) => new Promise((resolve9) => {
16675
16902
  if (queue.length > 0) {
16676
- resolve8(queue.shift());
16903
+ resolve9(queue.shift());
16677
16904
  return;
16678
16905
  }
16679
16906
  if (ended) {
16680
- resolve8(null);
16907
+ resolve9(null);
16681
16908
  return;
16682
16909
  }
16683
- waiter = resolve8;
16910
+ waiter = resolve9;
16684
16911
  const t = setTimeout(
16685
16912
  () => {
16686
- if (waiter === resolve8) {
16913
+ if (waiter === resolve9) {
16687
16914
  waiter = null;
16688
- resolve8(null);
16915
+ resolve9(null);
16689
16916
  }
16690
16917
  },
16691
16918
  Math.max(0, timeoutMs)
@@ -17068,42 +17295,111 @@ var init_scripts = __esm({
17068
17295
  });
17069
17296
 
17070
17297
  // src/stateWorkspace.ts
17298
+ import { execFileSync as execFileSync24 } from "child_process";
17299
+ import * as crypto3 from "crypto";
17071
17300
  import * as fs43 from "fs";
17301
+ import * as os7 from "os";
17072
17302
  import * as path41 from "path";
17073
17303
  function writeLocalFile(cwd, relativePath, content) {
17074
17304
  const fullPath = path41.join(cwd, relativePath);
17075
17305
  fs43.mkdirSync(path41.dirname(fullPath), { recursive: true });
17076
17306
  fs43.writeFileSync(fullPath, content);
17077
17307
  }
17078
- function hydrateDirectory(config, cwd, stateDir, localDir) {
17079
- const entries = listStateDirectory(config, cwd, stateDir);
17080
- if (entries.length === 0) return;
17081
- for (const entry of entries) {
17082
- if (!entry.name || !entry.type) continue;
17083
- const childState = path41.posix.join(stateDir, entry.name);
17084
- const childLocal = path41.join(localDir, entry.name);
17085
- if (entry.type === "dir") {
17086
- fs43.rmSync(path41.join(cwd, childLocal), { recursive: true, force: true });
17087
- hydrateDirectory(config, cwd, childState, childLocal);
17088
- } else if (entry.type === "file") {
17089
- const file = readStateText(config, cwd, childState);
17090
- if (file) writeLocalFile(cwd, childLocal, file.content);
17091
- }
17308
+ function copyPath(source, target) {
17309
+ const st = fs43.lstatSync(source);
17310
+ fs43.rmSync(target, { recursive: true, force: true });
17311
+ if (st.isSymbolicLink()) return;
17312
+ fs43.mkdirSync(path41.dirname(target), { recursive: true });
17313
+ fs43.cpSync(source, target, { recursive: true, force: true });
17314
+ }
17315
+ function overlayDirectoryChildren(cwd, sourceDir, localDir) {
17316
+ if (!fs43.existsSync(sourceDir)) return;
17317
+ for (const entry of fs43.readdirSync(sourceDir, { withFileTypes: true })) {
17318
+ const source = path41.join(sourceDir, entry.name);
17319
+ const target = path41.join(cwd, localDir, entry.name);
17320
+ copyPath(source, target);
17092
17321
  }
17093
17322
  }
17094
17323
  function hydrateStateWorkspace(config, cwd) {
17324
+ if (process.env.VITEST && process.env[TEST_FETCH_ENV] !== "1") return;
17325
+ const parsed = parseStateRepo(config);
17326
+ const hydrateKey = `${path41.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${STATE_BRANCH}`;
17327
+ if (hydratedWorkspaces.has(hydrateKey)) return;
17328
+ const snapshotRoot = fetchStateSnapshot(parsed);
17095
17329
  for (const mapping of DIR_MAPPINGS) {
17096
- hydrateDirectory(config, cwd, mapping.stateDir, mapping.localDir);
17330
+ overlayDirectoryChildren(cwd, path41.join(snapshotRoot, mapping.stateDir), mapping.localDir);
17097
17331
  }
17098
17332
  for (const mapping of FILE_MAPPINGS) {
17099
- const file = readStateText(config, cwd, mapping.statePath);
17100
- if (file) writeLocalFile(cwd, mapping.localPath, file.content);
17333
+ const source = path41.join(snapshotRoot, mapping.statePath);
17334
+ if (fs43.existsSync(source) && !fs43.lstatSync(source).isSymbolicLink() && fs43.statSync(source).isFile()) {
17335
+ writeLocalFile(cwd, mapping.localPath, fs43.readFileSync(source, "utf-8"));
17336
+ }
17337
+ }
17338
+ hydratedWorkspaces.add(hydrateKey);
17339
+ }
17340
+ function fetchStateSnapshot(parsed) {
17341
+ const cacheDir = path41.join(cacheRoot2(), cacheKey2(parsed));
17342
+ const url = `https://github.com/${parsed.owner}/${parsed.repo}.git`;
17343
+ try {
17344
+ fs43.mkdirSync(path41.dirname(cacheDir), { recursive: true });
17345
+ if (!fs43.existsSync(path41.join(cacheDir, ".git"))) {
17346
+ fs43.rmSync(cacheDir, { recursive: true, force: true });
17347
+ runGit3(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
17348
+ }
17349
+ runGit3(["-C", cacheDir, "remote", "set-url", "origin", url]);
17350
+ runGit3(["-C", cacheDir, "fetch", "--depth=1", "origin", STATE_BRANCH]);
17351
+ runGit3(["-C", cacheDir, "sparse-checkout", "init", "--cone"]);
17352
+ runGit3(["-C", cacheDir, "sparse-checkout", "set", parsed.basePath]);
17353
+ runGit3(["-C", cacheDir, "checkout", "--force", "--detach", "FETCH_HEAD"]);
17354
+ runGit3(["-C", cacheDir, "clean", "-fdx"]);
17355
+ } catch (err) {
17356
+ const msg = err instanceof Error ? err.message : String(err);
17357
+ throw new Error(
17358
+ `stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${STATE_BRANCH}: ${msg}`
17359
+ );
17360
+ }
17361
+ return path41.join(cacheDir, parsed.basePath);
17362
+ }
17363
+ function cacheRoot2() {
17364
+ return process.env[CACHE_ENV2]?.trim() || path41.join(os7.homedir(), ".cache", "kody", "state-repo");
17365
+ }
17366
+ function cacheKey2(parsed) {
17367
+ return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${STATE_BRANCH}#${parsed.basePath}`).digest("hex").slice(0, 24);
17368
+ }
17369
+ function runGit3(args) {
17370
+ try {
17371
+ execFileSync24("git", args, {
17372
+ encoding: "utf-8",
17373
+ env: githubAuthEnv(),
17374
+ stdio: ["ignore", "ignore", "pipe"],
17375
+ timeout: 12e4
17376
+ });
17377
+ } catch (err) {
17378
+ const stderr = err.stderr;
17379
+ const detail = Buffer.isBuffer(stderr) ? stderr.toString("utf-8").trim() : typeof stderr === "string" ? stderr.trim() : "";
17380
+ throw new Error(detail || `git ${args[0] ?? "command"} failed`);
17101
17381
  }
17102
17382
  }
17103
- var DIR_MAPPINGS, FILE_MAPPINGS;
17383
+ function githubAuthEnv() {
17384
+ const token = githubToken();
17385
+ if (!token) return process.env;
17386
+ const encoded = Buffer.from(`x-access-token:${token}`).toString("base64");
17387
+ const existingCount = /^\d+$/.test(process.env.GIT_CONFIG_COUNT ?? "") ? Number(process.env.GIT_CONFIG_COUNT) : 0;
17388
+ return {
17389
+ ...process.env,
17390
+ GIT_CONFIG_COUNT: String(existingCount + 1),
17391
+ [`GIT_CONFIG_KEY_${existingCount}`]: "http.https://github.com/.extraheader",
17392
+ [`GIT_CONFIG_VALUE_${existingCount}`]: `AUTHORIZATION: basic ${encoded}`
17393
+ };
17394
+ }
17395
+ function githubToken() {
17396
+ return process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || process.env.GITHUB_TOKEN?.trim() || process.env.GH_PAT?.trim() || void 0;
17397
+ }
17398
+ var DIR_MAPPINGS, FILE_MAPPINGS, CACHE_ENV2, TEST_FETCH_ENV, hydratedWorkspaces;
17104
17399
  var init_stateWorkspace = __esm({
17105
17400
  "src/stateWorkspace.ts"() {
17106
17401
  "use strict";
17402
+ init_stateBranch();
17107
17403
  init_stateRepo();
17108
17404
  DIR_MAPPINGS = [
17109
17405
  { stateDir: "executables", localDir: path41.join(".kody", "executables") },
@@ -17117,11 +17413,14 @@ var init_stateWorkspace = __esm({
17117
17413
  { statePath: "variables.json", localPath: path41.join(".kody", "variables.json") },
17118
17414
  { statePath: "secrets.enc", localPath: path41.join(".kody", "secrets.enc") }
17119
17415
  ];
17416
+ CACHE_ENV2 = "KODY_STATE_REPO_CACHE";
17417
+ TEST_FETCH_ENV = "KODY_STATE_WORKSPACE_FETCH_FOR_TESTS";
17418
+ hydratedWorkspaces = /* @__PURE__ */ new Set();
17120
17419
  }
17121
17420
  });
17122
17421
 
17123
17422
  // src/tools.ts
17124
- import { execFileSync as execFileSync24 } from "child_process";
17423
+ import { execFileSync as execFileSync25 } from "child_process";
17125
17424
  function verifyCliTools(tools, cwd) {
17126
17425
  const out = [];
17127
17426
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -17135,30 +17434,30 @@ function firstRequiredFailure(results, tools) {
17135
17434
  }
17136
17435
  return null;
17137
17436
  }
17138
- function verifyOne(tool5, cwd) {
17139
- const result = { name: tool5.name, present: false, verified: false };
17140
- const checkRes = runShell(tool5.install.checkCommand, cwd);
17437
+ function verifyOne(tool6, cwd) {
17438
+ const result = { name: tool6.name, present: false, verified: false };
17439
+ const checkRes = runShell(tool6.install.checkCommand, cwd);
17141
17440
  let present = checkRes.ok;
17142
- if (!present && tool5.install.installCommand) {
17143
- runShell(tool5.install.installCommand, cwd, 12e4);
17144
- present = runShell(tool5.install.checkCommand, cwd).ok;
17441
+ if (!present && tool6.install.installCommand) {
17442
+ runShell(tool6.install.installCommand, cwd, 12e4);
17443
+ present = runShell(tool6.install.checkCommand, cwd).ok;
17145
17444
  }
17146
17445
  result.present = present;
17147
17446
  if (!present) {
17148
- result.error = `tool "${tool5.name}" not on PATH (check: ${tool5.install.checkCommand})`;
17447
+ result.error = `tool "${tool6.name}" not on PATH (check: ${tool6.install.checkCommand})`;
17149
17448
  return result;
17150
17449
  }
17151
- const verifyRes = runShell(tool5.verify, cwd);
17450
+ const verifyRes = runShell(tool6.verify, cwd);
17152
17451
  result.verified = verifyRes.ok;
17153
17452
  if (!verifyRes.ok) {
17154
17453
  const tail = formatStderrTail(verifyRes.stderr, verifyRes.stdout);
17155
- result.error = `tool "${tool5.name}" failed verify: ${tool5.verify}${tail ? ` \u2014 ${tail}` : ""}`;
17454
+ result.error = `tool "${tool6.name}" failed verify: ${tool6.verify}${tail ? ` \u2014 ${tail}` : ""}`;
17156
17455
  }
17157
17456
  return result;
17158
17457
  }
17159
17458
  function runShell(cmd, cwd, timeoutMs = 3e4) {
17160
17459
  try {
17161
- const stdout = execFileSync24("sh", ["-c", cmd], {
17460
+ const stdout = execFileSync25("sh", ["-c", cmd], {
17162
17461
  cwd,
17163
17462
  stdio: ["ignore", "pipe", "pipe"],
17164
17463
  timeout: timeoutMs,
@@ -17904,14 +18203,14 @@ async function runShellEntry(entry, ctx, profile) {
17904
18203
  let killTimer;
17905
18204
  let escalateTimer;
17906
18205
  const result = await new Promise(
17907
- (resolve8) => {
18206
+ (resolve9) => {
17908
18207
  let settled = false;
17909
18208
  const settle = (code, signal, spawnErr) => {
17910
18209
  if (settled) return;
17911
18210
  settled = true;
17912
18211
  if (killTimer) clearTimeout(killTimer);
17913
18212
  if (escalateTimer) clearTimeout(escalateTimer);
17914
- resolve8({ code, signal, spawnErr });
18213
+ resolve9({ code, signal, spawnErr });
17915
18214
  };
17916
18215
  child.on("error", (err) => settle(null, null, err));
17917
18216
  child.on("close", (code, signal) => settle(code, signal));
@@ -18864,6 +19163,12 @@ var CROSS_REPO_PROMPT = [
18864
19163
  "instantly. When the user asks about a different repo \u2014 or to compare repos \u2014",
18865
19164
  "fetch it instead of saying you are scoped to a single repo."
18866
19165
  ].join("\n");
19166
+ var DASHBOARD_CMS_PROMPT = [
19167
+ "# Dashboard CMS",
19168
+ "Use Dashboard CMS tools for CMS content. The Dashboard is the source of truth.",
19169
+ "Do not query Mongo directly for CMS content and do not infer CMS data from repository files.",
19170
+ "Use cms_list_documents first when you need to find content, then pass the raw id or cmsDocumentId to cms_get_document."
19171
+ ].join("\n");
18867
19172
  function buildExecutableCatalog() {
18868
19173
  let discovered;
18869
19174
  try {
@@ -18922,6 +19227,8 @@ async function runChatTurn(opts) {
18922
19227
  const memoryBlock = readMemoryIndexBlock(opts.cwd);
18923
19228
  const instructionsBlock = readInstructionsBlock(opts.cwd);
18924
19229
  const crossRepoBlock = opts.reposRoot ? CROSS_REPO_PROMPT : null;
19230
+ const dashboardCmsEnabled = Boolean(opts.cmsDashboardUrl && opts.cmsRepoSlug && opts.cmsToken);
19231
+ const dashboardCmsBlock = dashboardCmsEnabled ? DASHBOARD_CMS_PROMPT : null;
18925
19232
  const imageBlock = imagePaths.length > 0 ? [
18926
19233
  "# Attached images",
18927
19234
  "The user attached one or more images on this turn. They are saved as",
@@ -18936,6 +19243,7 @@ async function runChatTurn(opts) {
18936
19243
  memoryBlock,
18937
19244
  instructionsBlock,
18938
19245
  crossRepoBlock,
19246
+ dashboardCmsBlock,
18939
19247
  imageBlock,
18940
19248
  catalog,
18941
19249
  artifactAddendum
@@ -18963,6 +19271,14 @@ async function runChatTurn(opts) {
18963
19271
  reposRoot: opts.reposRoot,
18964
19272
  repoToken: opts.repoToken
18965
19273
  } : {},
19274
+ ...dashboardCmsEnabled ? {
19275
+ enableDashboardCmsTool: true,
19276
+ cmsDashboardUrl: opts.cmsDashboardUrl,
19277
+ cmsRepoSlug: opts.cmsRepoSlug,
19278
+ cmsToken: opts.cmsToken,
19279
+ cmsStoreRepoUrl: opts.cmsStoreRepoUrl,
19280
+ cmsStoreRef: opts.cmsStoreRef
19281
+ } : {},
18966
19282
  onProgress: async (ev) => {
18967
19283
  progressSeq += 1;
18968
19284
  if (ev.kind === "thinking") {
@@ -19061,7 +19377,9 @@ function readMemoryIndexBlock(cwd) {
19061
19377
  }
19062
19378
  const trimmed = raw.trim();
19063
19379
  if (!trimmed) return "";
19064
- 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;
19380
+ const body = trimmed.length > MAX_INDEX_BYTES ? `${trimmed.slice(0, MAX_INDEX_BYTES)}
19381
+
19382
+ _\u2026 (memory index truncated; use recall_search to read more)_` : trimmed;
19065
19383
  return [
19066
19384
  "# Project memory index (state repo `memory/INDEX.md`)",
19067
19385
  "",
@@ -19131,7 +19449,7 @@ _\u2026 (instructions truncated)_` : trimmed;
19131
19449
  init_config();
19132
19450
 
19133
19451
  // src/kody-cli.ts
19134
- import { execFileSync as execFileSync25 } from "child_process";
19452
+ import { execFileSync as execFileSync26 } from "child_process";
19135
19453
  import * as fs46 from "fs";
19136
19454
  import * as path45 from "path";
19137
19455
 
@@ -19788,7 +20106,7 @@ function recoverCheckoutToken(env = process.env, cwd = process.cwd()) {
19788
20106
  if (env.GITHUB_TOKEN?.trim()) return env.GITHUB_TOKEN.trim();
19789
20107
  let header = "";
19790
20108
  try {
19791
- header = execFileSync25("git", ["config", "--local", "--get", "http.https://github.com/.extraheader"], {
20109
+ header = execFileSync26("git", ["config", "--local", "--get", "http.https://github.com/.extraheader"], {
19792
20110
  cwd,
19793
20111
  encoding: "utf-8",
19794
20112
  stdio: ["ignore", "pipe", "ignore"]
@@ -19850,7 +20168,7 @@ function shouldChainScheduledWatch(match) {
19850
20168
  }
19851
20169
  function shellOut(cmd, args, cwd, stream = true) {
19852
20170
  try {
19853
- execFileSync25(cmd, args, {
20171
+ execFileSync26(cmd, args, {
19854
20172
  cwd,
19855
20173
  stdio: stream ? "inherit" : "pipe",
19856
20174
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -19863,7 +20181,7 @@ function shellOut(cmd, args, cwd, stream = true) {
19863
20181
  }
19864
20182
  function isOnPath(bin) {
19865
20183
  try {
19866
- execFileSync25("which", [bin], { stdio: "pipe" });
20184
+ execFileSync26("which", [bin], { stdio: "pipe" });
19867
20185
  return true;
19868
20186
  } catch {
19869
20187
  return false;
@@ -19904,7 +20222,7 @@ function installLitellmIfNeeded(cwd) {
19904
20222
  } catch {
19905
20223
  }
19906
20224
  try {
19907
- execFileSync25("python3", ["-c", "import litellm"], { stdio: "pipe" });
20225
+ execFileSync26("python3", ["-c", "import litellm"], { stdio: "pipe" });
19908
20226
  process.stdout.write("\u2192 kody: litellm already installed\n");
19909
20227
  return 0;
19910
20228
  } catch {
@@ -19914,16 +20232,16 @@ function installLitellmIfNeeded(cwd) {
19914
20232
  }
19915
20233
  function configureGitIdentity(cwd) {
19916
20234
  try {
19917
- const name = execFileSync25("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
20235
+ const name = execFileSync26("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
19918
20236
  if (name) return;
19919
20237
  } catch {
19920
20238
  }
19921
20239
  try {
19922
- execFileSync25("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
20240
+ execFileSync26("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
19923
20241
  } catch {
19924
20242
  }
19925
20243
  try {
19926
- execFileSync25("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
20244
+ execFileSync26("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
19927
20245
  cwd,
19928
20246
  stdio: "pipe"
19929
20247
  });
@@ -20533,17 +20851,17 @@ function authOk(req, expected) {
20533
20851
  return false;
20534
20852
  }
20535
20853
  function readJsonBody(req) {
20536
- return new Promise((resolve8, reject) => {
20854
+ return new Promise((resolve9, reject) => {
20537
20855
  const chunks = [];
20538
20856
  req.on("data", (c) => chunks.push(c));
20539
20857
  req.on("end", () => {
20540
20858
  const raw = Buffer.concat(chunks).toString("utf-8");
20541
20859
  if (!raw.trim()) {
20542
- resolve8({});
20860
+ resolve9({});
20543
20861
  return;
20544
20862
  }
20545
20863
  try {
20546
- resolve8(JSON.parse(raw));
20864
+ resolve9(JSON.parse(raw));
20547
20865
  } catch (err) {
20548
20866
  reject(err instanceof Error ? err : new Error(String(err)));
20549
20867
  }
@@ -20689,6 +21007,9 @@ async function handleChatTurn(req, res, chatId, opts) {
20689
21007
  }
20690
21008
  const repo = strField(body, "repo");
20691
21009
  const repoToken = strField(body, "repoToken");
21010
+ const dashboardUrl = strField(body, "dashboardUrl");
21011
+ const storeRepoUrl = strField(body, "storeRepoUrl");
21012
+ const storeRef = strField(body, "storeRef");
20692
21013
  let agentCwd;
20693
21014
  try {
20694
21015
  agentCwd = await ensureRepoCwd({
@@ -20765,7 +21086,14 @@ async function handleChatTurn(req, res, chatId, opts) {
20765
21086
  sink,
20766
21087
  // Let the agent clone + work on OTHER repos via the fetch_repo tool.
20767
21088
  reposRoot: opts.reposRoot,
20768
- repoToken
21089
+ repoToken,
21090
+ ...dashboardUrl && repo && stateToken ? {
21091
+ cmsDashboardUrl: dashboardUrl,
21092
+ cmsRepoSlug: repo,
21093
+ cmsToken: stateToken,
21094
+ cmsStoreRepoUrl: storeRepoUrl,
21095
+ cmsStoreRef: storeRef
21096
+ } : {}
20769
21097
  });
20770
21098
  } catch (err) {
20771
21099
  const errMsg3 = err instanceof Error ? err.message : String(err);
@@ -20885,11 +21213,11 @@ async function brainServe(opts) {
20885
21213
  model,
20886
21214
  litellmUrl
20887
21215
  });
20888
- await new Promise((resolve8) => {
21216
+ await new Promise((resolve9) => {
20889
21217
  server.listen(port, "0.0.0.0", () => {
20890
21218
  process.stdout.write(`[brain-serve] listening on 0.0.0.0:${port} (cwd=${opts.cwd})
20891
21219
  `);
20892
- resolve8();
21220
+ resolve9();
20893
21221
  });
20894
21222
  });
20895
21223
  const shutdown = (signal) => {
@@ -21022,7 +21350,10 @@ async function proxyToBrainServe(args) {
21022
21350
  body: JSON.stringify({
21023
21351
  message: args.body.message,
21024
21352
  ...args.body.repo ? { repo: args.body.repo } : {},
21025
- ...args.body.repoToken ? { repoToken: args.body.repoToken } : {}
21353
+ ...args.body.repoToken ? { repoToken: args.body.repoToken } : {},
21354
+ ...args.body.dashboardUrl ? { dashboardUrl: args.body.dashboardUrl } : {},
21355
+ ...args.body.storeRepoUrl ? { storeRepoUrl: args.body.storeRepoUrl } : {},
21356
+ ...args.body.storeRef ? { storeRef: args.body.storeRef } : {}
21026
21357
  })
21027
21358
  });
21028
21359
  if (!upstream.ok || !upstream.body) {
@@ -21139,14 +21470,14 @@ async function startBrainProxy(opts) {
21139
21470
  const { httpServer, handler } = buildBrainProxy(opts);
21140
21471
  const port = opts.port ?? 0;
21141
21472
  const host = opts.host ?? "127.0.0.1";
21142
- await new Promise((resolve8) => httpServer.listen(port, host, () => resolve8()));
21473
+ await new Promise((resolve9) => httpServer.listen(port, host, () => resolve9()));
21143
21474
  const addr = httpServer.address();
21144
21475
  return {
21145
21476
  httpServer,
21146
21477
  port: addr.port,
21147
21478
  url: `http://${host}:${addr.port}`,
21148
- stop: () => new Promise((resolve8) => {
21149
- httpServer.close(() => resolve8());
21479
+ stop: () => new Promise((resolve9) => {
21480
+ httpServer.close(() => resolve9());
21150
21481
  }),
21151
21482
  handler
21152
21483
  };
@@ -21237,15 +21568,15 @@ function buildMcpHttpServer(opts) {
21237
21568
  transports.set(route.path, transport);
21238
21569
  routes.set(route.path, route.name);
21239
21570
  const mcpServer = new McpServer({ name: route.name, version: route.version });
21240
- for (const tool5 of route.tools) {
21571
+ for (const tool6 of route.tools) {
21241
21572
  mcpServer.registerTool(
21242
- tool5.name,
21573
+ tool6.name,
21243
21574
  {
21244
- description: tool5.description,
21245
- inputSchema: tool5.inputSchema
21575
+ description: tool6.description,
21576
+ inputSchema: tool6.inputSchema
21246
21577
  },
21247
21578
  async (args) => {
21248
- const result = await tool5.handler(args);
21579
+ const result = await tool6.handler(args);
21249
21580
  return {
21250
21581
  content: result.content,
21251
21582
  ...result.isError ? { isError: true } : {}
@@ -21296,23 +21627,23 @@ function buildMcpHttpServer(opts) {
21296
21627
  httpServer,
21297
21628
  routes,
21298
21629
  port,
21299
- stop: () => new Promise((resolve8) => {
21630
+ stop: () => new Promise((resolve9) => {
21300
21631
  let pending = transports.size;
21301
21632
  if (pending === 0) {
21302
- httpServer.close(() => resolve8());
21633
+ httpServer.close(() => resolve9());
21303
21634
  return;
21304
21635
  }
21305
21636
  for (const transport of transports.values()) {
21306
21637
  void transport.close().finally(() => {
21307
21638
  pending--;
21308
- if (pending === 0) httpServer.close(() => resolve8());
21639
+ if (pending === 0) httpServer.close(() => resolve9());
21309
21640
  });
21310
21641
  }
21311
21642
  })
21312
21643
  };
21313
21644
  }
21314
21645
  function listenMcpHttpServer(server, host = "127.0.0.1") {
21315
- return new Promise((resolve8, reject) => {
21646
+ return new Promise((resolve9, reject) => {
21316
21647
  server.httpServer.once("error", reject);
21317
21648
  server.httpServer.listen(server.port, host, () => {
21318
21649
  server.httpServer.off("error", reject);
@@ -21320,7 +21651,7 @@ function listenMcpHttpServer(server, host = "127.0.0.1") {
21320
21651
  if (addr && typeof addr === "object") {
21321
21652
  server.port = addr.port;
21322
21653
  }
21323
- resolve8();
21654
+ resolve9();
21324
21655
  });
21325
21656
  });
21326
21657
  }
@@ -21409,7 +21740,7 @@ import * as fs50 from "fs";
21409
21740
  import * as path49 from "path";
21410
21741
 
21411
21742
  // src/chat/inbox.ts
21412
- import { execFileSync as execFileSync26 } from "child_process";
21743
+ import { execFileSync as execFileSync27 } from "child_process";
21413
21744
  var DEFAULT_POLL_MS = 3e3;
21414
21745
  async function waitForNextUserMessage(opts) {
21415
21746
  const pollMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
@@ -21433,13 +21764,13 @@ async function waitForNextUserMessage(opts) {
21433
21764
  try {
21434
21765
  const branch = currentBranch(opts.cwd);
21435
21766
  if (branch) {
21436
- execFileSync26("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
21437
- execFileSync26("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
21767
+ execFileSync27("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
21768
+ execFileSync27("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
21438
21769
  cwd: opts.cwd,
21439
21770
  stdio: "pipe"
21440
21771
  });
21441
21772
  } else {
21442
- execFileSync26("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
21773
+ execFileSync27("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
21443
21774
  }
21444
21775
  } catch (err) {
21445
21776
  const msg = err instanceof Error ? err.message : String(err);
@@ -21461,11 +21792,11 @@ async function waitForNextUserMessage(opts) {
21461
21792
  }
21462
21793
  }
21463
21794
  function sleep3(ms) {
21464
- return new Promise((resolve8) => setTimeout(resolve8, ms));
21795
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
21465
21796
  }
21466
21797
  function currentBranch(cwd) {
21467
21798
  try {
21468
- const out = execFileSync26("git", ["symbolic-ref", "--short", "HEAD"], {
21799
+ const out = execFileSync27("git", ["symbolic-ref", "--short", "HEAD"], {
21469
21800
  cwd,
21470
21801
  stdio: ["ignore", "pipe", "ignore"]
21471
21802
  });
@@ -22551,7 +22882,7 @@ var PERF_GUEST = {
22551
22882
  medium: { cpu_kind: "performance", cpus: 1, memory_mb: 2048 },
22552
22883
  high: { cpu_kind: "performance", cpus: 2, memory_mb: 4096 }
22553
22884
  };
22554
- function envInt(name, dflt) {
22885
+ function envInt2(name, dflt) {
22555
22886
  const v = Number(process.env[name]);
22556
22887
  return Number.isFinite(v) && v > 0 ? v : dflt;
22557
22888
  }
@@ -22564,14 +22895,14 @@ function sendJson2(res, status, body) {
22564
22895
  res.end(JSON.stringify(body));
22565
22896
  }
22566
22897
  function readJsonBody2(req) {
22567
- return new Promise((resolve8, reject) => {
22898
+ return new Promise((resolve9, reject) => {
22568
22899
  const chunks = [];
22569
22900
  req.on("data", (c) => chunks.push(c));
22570
22901
  req.on("end", () => {
22571
22902
  const raw = Buffer.concat(chunks).toString("utf-8");
22572
- if (!raw.trim()) return resolve8({});
22903
+ if (!raw.trim()) return resolve9({});
22573
22904
  try {
22574
- resolve8(JSON.parse(raw));
22905
+ resolve9(JSON.parse(raw));
22575
22906
  } catch (err) {
22576
22907
  reject(err instanceof Error ? err : new Error(String(err)));
22577
22908
  }
@@ -22662,8 +22993,8 @@ function synthesizeLegacyClaimRequest(input) {
22662
22993
  async function poolServe() {
22663
22994
  const masterRaw = process.env.KODY_MASTER_KEY?.trim();
22664
22995
  if (!masterRaw) throw new Error("KODY_MASTER_KEY required for pool-serve");
22665
- const githubToken = process.env.GITHUB_TOKEN?.trim();
22666
- if (!githubToken) throw new Error("GITHUB_TOKEN required for pool-serve (reads per-repo vaults)");
22996
+ const githubToken2 = process.env.GITHUB_TOKEN?.trim();
22997
+ if (!githubToken2) throw new Error("GITHUB_TOKEN required for pool-serve (reads per-repo vaults)");
22667
22998
  const master = masterKeyBytes(masterRaw);
22668
22999
  const poolApiKey = derivePoolApiKey(master);
22669
23000
  const runnerApiKey = deriveRunnerApiKey(master);
@@ -22671,12 +23002,12 @@ async function poolServe() {
22671
23002
  const region = process.env.POOL_REGION ?? "fra";
22672
23003
  const perf = process.env.POOL_PERF ?? "medium";
22673
23004
  const guest = PERF_GUEST[perf] ?? PERF_GUEST.medium;
22674
- const min = envInt("POOL_MIN", 2);
22675
- const runnerPort = envInt("RUNNER_PORT", 8080);
22676
- const apiPort = envInt("POOL_API_PORT", 4100);
22677
- const healthTimeoutMs = envInt("POOL_HEALTH_TIMEOUT_MS", 12e4);
23005
+ const min = envInt2("POOL_MIN", 2);
23006
+ const runnerPort = envInt2("RUNNER_PORT", 8080);
23007
+ const apiPort = envInt2("POOL_API_PORT", 4100);
23008
+ const healthTimeoutMs = envInt2("POOL_HEALTH_TIMEOUT_MS", 12e4);
22678
23009
  const registry = new PoolRegistry({
22679
- githubToken,
23010
+ githubToken: githubToken2,
22680
23011
  masterKey: master,
22681
23012
  base: {
22682
23013
  min,
@@ -22690,12 +23021,12 @@ async function poolServe() {
22690
23021
  },
22691
23022
  log
22692
23023
  });
22693
- const refillMs = envInt("POOL_REFILL_INTERVAL_MS", 6e4);
23024
+ const refillMs = envInt2("POOL_REFILL_INTERVAL_MS", 6e4);
22694
23025
  const tick = setInterval(() => {
22695
23026
  registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
22696
23027
  }, refillMs);
22697
23028
  const dutyTickEnabled = (process.env.POOL_CAPABILITY_TICK ?? "1") !== "0";
22698
- const dutyTickMs = envInt("POOL_CAPABILITY_TICK_MS", 15 * 6e4);
23029
+ const dutyTickMs = envInt2("POOL_CAPABILITY_TICK_MS", 15 * 6e4);
22699
23030
  const dutyTick = dutyTickEnabled ? setInterval(() => {
22700
23031
  runCapabilityFallbackTick({
22701
23032
  isDegraded: () => gitHubActionsDegraded(),
@@ -22753,10 +23084,10 @@ async function poolServe() {
22753
23084
  }
22754
23085
  });
22755
23086
  const apiHost = process.env.POOL_API_HOST ?? "::";
22756
- await new Promise((resolve8) => {
23087
+ await new Promise((resolve9) => {
22757
23088
  server.listen(apiPort, apiHost, () => {
22758
23089
  log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
22759
- resolve8();
23090
+ resolve9();
22760
23091
  });
22761
23092
  });
22762
23093
  const shutdown = (signal) => {
@@ -22795,17 +23126,17 @@ function authOk2(req, expected) {
22795
23126
  return false;
22796
23127
  }
22797
23128
  function readJsonBody3(req) {
22798
- return new Promise((resolve8, reject) => {
23129
+ return new Promise((resolve9, reject) => {
22799
23130
  const chunks = [];
22800
23131
  req.on("data", (c) => chunks.push(c));
22801
23132
  req.on("end", () => {
22802
23133
  const raw = Buffer.concat(chunks).toString("utf-8");
22803
23134
  if (!raw.trim()) {
22804
- resolve8({});
23135
+ resolve9({});
22805
23136
  return;
22806
23137
  }
22807
23138
  try {
22808
- resolve8(JSON.parse(raw));
23139
+ resolve9(JSON.parse(raw));
22809
23140
  } catch (err) {
22810
23141
  reject(err instanceof Error ? err : new Error(String(err)));
22811
23142
  }
@@ -22824,8 +23155,8 @@ function parseJob(body) {
22824
23155
  if (!jobId) return { error: "jobId required" };
22825
23156
  const repo = typeof b.repo === "string" ? b.repo.trim() : "";
22826
23157
  if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
22827
- const githubToken = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
22828
- if (!githubToken) return { error: "githubToken required" };
23158
+ const githubToken2 = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
23159
+ if (!githubToken2) return { error: "githubToken required" };
22829
23160
  const action = typeof b.action === "string" && b.action.trim() ? b.action.trim() : void 0;
22830
23161
  const message = typeof b.message === "string" && b.message.trim() ? b.message.trim() : void 0;
22831
23162
  const mode = b.mode === "interactive" ? "interactive" : b.mode === "scheduled" ? "scheduled" : "issue";
@@ -22837,7 +23168,7 @@ function parseJob(body) {
22837
23168
  message
22838
23169
  });
22839
23170
  if ("error" in runRequest) return { error: runRequest.error };
22840
- const job = { jobId, repo, githubToken, runRequest: runRequest.request };
23171
+ const job = { jobId, repo, githubToken: githubToken2, runRequest: runRequest.request };
22841
23172
  if (job.runRequest.target.type === "issue") {
22842
23173
  job.issueNumber = job.runRequest.target.id;
22843
23174
  } else if (job.runRequest.target.type === "chat") {
@@ -22939,13 +23270,13 @@ async function defaultRunJob(job) {
22939
23270
  ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
22940
23271
  ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
22941
23272
  };
22942
- const run = (cmd, args, cwd) => new Promise((resolve8) => {
23273
+ const run = (cmd, args, cwd) => new Promise((resolve9) => {
22943
23274
  const child = spawn8(cmd, args, { stdio: "inherit", env: childEnv, cwd });
22944
- child.on("exit", (code) => resolve8(code ?? 0));
23275
+ child.on("exit", (code) => resolve9(code ?? 0));
22945
23276
  child.on("error", (err) => {
22946
23277
  process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
22947
23278
  `);
22948
- resolve8(1);
23279
+ resolve9(1);
22949
23280
  });
22950
23281
  });
22951
23282
  process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
@@ -23021,11 +23352,11 @@ async function runnerServe() {
23021
23352
  const port = Number(process.env.PORT ?? DEFAULT_PORT2);
23022
23353
  const server = buildServer2({ apiKey });
23023
23354
  const host = process.env.RUNNER_HOST ?? "::";
23024
- await new Promise((resolve8) => {
23355
+ await new Promise((resolve9) => {
23025
23356
  server.listen(port, host, () => {
23026
23357
  process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
23027
23358
  `);
23028
- resolve8();
23359
+ resolve9();
23029
23360
  });
23030
23361
  });
23031
23362
  const shutdown = (signal) => {
@@ -23094,14 +23425,14 @@ async function serve(opts) {
23094
23425
  `);
23095
23426
  const args = ["--dangerously-skip-permissions", "--model", model.model];
23096
23427
  const child = spawn9("claude", args, { stdio: "inherit", env: editorEnv, cwd: opts.cwd });
23097
- const exitCode = await new Promise((resolve8) => {
23098
- child.on("exit", (code) => resolve8(code ?? 0));
23428
+ const exitCode = await new Promise((resolve9) => {
23429
+ child.on("exit", (code) => resolve9(code ?? 0));
23099
23430
  child.on("error", (err) => {
23100
23431
  process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
23101
23432
  `);
23102
23433
  process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
23103
23434
  `);
23104
- resolve8(1);
23435
+ resolve9(1);
23105
23436
  });
23106
23437
  });
23107
23438
  killProxy();