@kody-ade/kody-engine 0.4.277 → 0.4.278
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/kody.js +560 -301
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "@kody-ade/kody-engine",
|
|
18
|
-
version: "0.4.
|
|
18
|
+
version: "0.4.278",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -156,14 +156,97 @@ function ghToken(preferRepoToken = false) {
|
|
|
156
156
|
function gh(args, options) {
|
|
157
157
|
const token = ghToken(options?.preferRepoToken);
|
|
158
158
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 cacheKey2 = `${parsed.owner}/${parsed.repo}:${STATE_BRANCH}`;
|
|
536
|
+
if (ensuredStateBranches.has(cacheKey2)) return;
|
|
449
537
|
try {
|
|
450
538
|
gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${STATE_BRANCH}`], { cwd });
|
|
539
|
+
ensuredStateBranches.add(cacheKey2);
|
|
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(cacheKey2);
|
|
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
|
|
|
@@ -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
|
|
1987
|
-
import { z as
|
|
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;
|
|
@@ -2223,132 +2609,12 @@ function isDispatchGated(capability, mode) {
|
|
|
2223
2609
|
function trustRefusal(capabilitySlug) {
|
|
2224
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.`;
|
|
2225
2611
|
}
|
|
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) {
|
|
2284
|
-
return {
|
|
2285
|
-
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
|
|
2290
|
-
};
|
|
2291
|
-
}
|
|
2292
|
-
return { ok: true, status: res.status, data };
|
|
2293
|
-
} catch (err) {
|
|
2294
|
-
return {
|
|
2295
|
-
ok: false,
|
|
2296
|
-
error: "cms_request_error",
|
|
2297
|
-
message: err instanceof Error ? err.message : String(err)
|
|
2298
|
-
};
|
|
2299
|
-
}
|
|
2300
|
-
}
|
|
2301
|
-
function cmsToolResponse(result) {
|
|
2302
|
-
return {
|
|
2303
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2304
|
-
...result.ok ? {} : { isError: true }
|
|
2305
|
-
};
|
|
2306
|
-
}
|
|
2307
2612
|
function assertCmsWriteAllowed(opts) {
|
|
2308
2613
|
if (isDispatchGated(opts.capabilitySlug, readCapabilityTrustMode(opts.state, opts.repoSlug, opts.capabilitySlug))) {
|
|
2309
2614
|
return trustRefusal(opts.capabilitySlug);
|
|
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:
|
|
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:
|
|
2403
|
-
body:
|
|
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:
|
|
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:
|
|
2430
|
-
ignoreNames:
|
|
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:
|
|
2444
|
-
limit:
|
|
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:
|
|
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:
|
|
2461
|
-
body:
|
|
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:
|
|
2478
|
-
key:
|
|
2479
|
-
body:
|
|
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:
|
|
2494
|
-
executable:
|
|
2495
|
-
issueNumber:
|
|
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
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
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
|
-
|
|
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) =>
|
|
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 =
|
|
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
|
-
|
|
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
|
});
|
|
@@ -2731,8 +2914,8 @@ __export(fetchRepoMcp_exports, {
|
|
|
2731
2914
|
buildFetchRepoMcpServer: () => buildFetchRepoMcpServer,
|
|
2732
2915
|
fetchRepoToolDefinition: () => fetchRepoToolDefinition
|
|
2733
2916
|
});
|
|
2734
|
-
import { createSdkMcpServer as
|
|
2735
|
-
import { z as
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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({
|
|
@@ -3021,7 +3222,7 @@ async function runAgent(opts) {
|
|
|
3021
3222
|
if (timedOut) {
|
|
3022
3223
|
outcome = "failed";
|
|
3023
3224
|
outcomeKind = "stalled";
|
|
3024
|
-
|
|
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
|
-
|
|
3329
|
+
errorMessage2 = `result subtype: ${m.subtype ?? "unknown"}`;
|
|
3129
3330
|
}
|
|
3130
3331
|
}
|
|
3131
3332
|
}
|
|
3132
3333
|
} catch (e) {
|
|
3133
3334
|
if (sawTerminalSuccess) {
|
|
3134
|
-
|
|
3335
|
+
errorMessage2 = e instanceof Error ? e.message : String(e);
|
|
3135
3336
|
} else {
|
|
3136
3337
|
outcome = "failed";
|
|
3137
3338
|
outcomeKind = "model_error";
|
|
3138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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: ${
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
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
|
-
|
|
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)}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
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) {
|
|
@@ -17135,24 +17362,24 @@ function firstRequiredFailure(results, tools) {
|
|
|
17135
17362
|
}
|
|
17136
17363
|
return null;
|
|
17137
17364
|
}
|
|
17138
|
-
function verifyOne(
|
|
17139
|
-
const result = { name:
|
|
17140
|
-
const checkRes = runShell(
|
|
17365
|
+
function verifyOne(tool6, cwd) {
|
|
17366
|
+
const result = { name: tool6.name, present: false, verified: false };
|
|
17367
|
+
const checkRes = runShell(tool6.install.checkCommand, cwd);
|
|
17141
17368
|
let present = checkRes.ok;
|
|
17142
|
-
if (!present &&
|
|
17143
|
-
runShell(
|
|
17144
|
-
present = runShell(
|
|
17369
|
+
if (!present && tool6.install.installCommand) {
|
|
17370
|
+
runShell(tool6.install.installCommand, cwd, 12e4);
|
|
17371
|
+
present = runShell(tool6.install.checkCommand, cwd).ok;
|
|
17145
17372
|
}
|
|
17146
17373
|
result.present = present;
|
|
17147
17374
|
if (!present) {
|
|
17148
|
-
result.error = `tool "${
|
|
17375
|
+
result.error = `tool "${tool6.name}" not on PATH (check: ${tool6.install.checkCommand})`;
|
|
17149
17376
|
return result;
|
|
17150
17377
|
}
|
|
17151
|
-
const verifyRes = runShell(
|
|
17378
|
+
const verifyRes = runShell(tool6.verify, cwd);
|
|
17152
17379
|
result.verified = verifyRes.ok;
|
|
17153
17380
|
if (!verifyRes.ok) {
|
|
17154
17381
|
const tail = formatStderrTail(verifyRes.stderr, verifyRes.stdout);
|
|
17155
|
-
result.error = `tool "${
|
|
17382
|
+
result.error = `tool "${tool6.name}" failed verify: ${tool6.verify}${tail ? ` \u2014 ${tail}` : ""}`;
|
|
17156
17383
|
}
|
|
17157
17384
|
return result;
|
|
17158
17385
|
}
|
|
@@ -18864,6 +19091,12 @@ var CROSS_REPO_PROMPT = [
|
|
|
18864
19091
|
"instantly. When the user asks about a different repo \u2014 or to compare repos \u2014",
|
|
18865
19092
|
"fetch it instead of saying you are scoped to a single repo."
|
|
18866
19093
|
].join("\n");
|
|
19094
|
+
var DASHBOARD_CMS_PROMPT = [
|
|
19095
|
+
"# Dashboard CMS",
|
|
19096
|
+
"Use Dashboard CMS tools for CMS content. The Dashboard is the source of truth.",
|
|
19097
|
+
"Do not query Mongo directly for CMS content and do not infer CMS data from repository files.",
|
|
19098
|
+
"Use cms_list_documents first when you need to find content, then pass the raw id or cmsDocumentId to cms_get_document."
|
|
19099
|
+
].join("\n");
|
|
18867
19100
|
function buildExecutableCatalog() {
|
|
18868
19101
|
let discovered;
|
|
18869
19102
|
try {
|
|
@@ -18922,6 +19155,8 @@ async function runChatTurn(opts) {
|
|
|
18922
19155
|
const memoryBlock = readMemoryIndexBlock(opts.cwd);
|
|
18923
19156
|
const instructionsBlock = readInstructionsBlock(opts.cwd);
|
|
18924
19157
|
const crossRepoBlock = opts.reposRoot ? CROSS_REPO_PROMPT : null;
|
|
19158
|
+
const dashboardCmsEnabled = Boolean(opts.cmsDashboardUrl && opts.cmsRepoSlug && opts.cmsToken);
|
|
19159
|
+
const dashboardCmsBlock = dashboardCmsEnabled ? DASHBOARD_CMS_PROMPT : null;
|
|
18925
19160
|
const imageBlock = imagePaths.length > 0 ? [
|
|
18926
19161
|
"# Attached images",
|
|
18927
19162
|
"The user attached one or more images on this turn. They are saved as",
|
|
@@ -18936,6 +19171,7 @@ async function runChatTurn(opts) {
|
|
|
18936
19171
|
memoryBlock,
|
|
18937
19172
|
instructionsBlock,
|
|
18938
19173
|
crossRepoBlock,
|
|
19174
|
+
dashboardCmsBlock,
|
|
18939
19175
|
imageBlock,
|
|
18940
19176
|
catalog,
|
|
18941
19177
|
artifactAddendum
|
|
@@ -18963,6 +19199,14 @@ async function runChatTurn(opts) {
|
|
|
18963
19199
|
reposRoot: opts.reposRoot,
|
|
18964
19200
|
repoToken: opts.repoToken
|
|
18965
19201
|
} : {},
|
|
19202
|
+
...dashboardCmsEnabled ? {
|
|
19203
|
+
enableDashboardCmsTool: true,
|
|
19204
|
+
cmsDashboardUrl: opts.cmsDashboardUrl,
|
|
19205
|
+
cmsRepoSlug: opts.cmsRepoSlug,
|
|
19206
|
+
cmsToken: opts.cmsToken,
|
|
19207
|
+
cmsStoreRepoUrl: opts.cmsStoreRepoUrl,
|
|
19208
|
+
cmsStoreRef: opts.cmsStoreRef
|
|
19209
|
+
} : {},
|
|
18966
19210
|
onProgress: async (ev) => {
|
|
18967
19211
|
progressSeq += 1;
|
|
18968
19212
|
if (ev.kind === "thinking") {
|
|
@@ -19061,7 +19305,9 @@ function readMemoryIndexBlock(cwd) {
|
|
|
19061
19305
|
}
|
|
19062
19306
|
const trimmed = raw.trim();
|
|
19063
19307
|
if (!trimmed) return "";
|
|
19064
|
-
const body = trimmed.length > MAX_INDEX_BYTES ? trimmed.slice(0, MAX_INDEX_BYTES)
|
|
19308
|
+
const body = trimmed.length > MAX_INDEX_BYTES ? `${trimmed.slice(0, MAX_INDEX_BYTES)}
|
|
19309
|
+
|
|
19310
|
+
_\u2026 (memory index truncated; use recall_search to read more)_` : trimmed;
|
|
19065
19311
|
return [
|
|
19066
19312
|
"# Project memory index (state repo `memory/INDEX.md`)",
|
|
19067
19313
|
"",
|
|
@@ -20689,6 +20935,9 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
20689
20935
|
}
|
|
20690
20936
|
const repo = strField(body, "repo");
|
|
20691
20937
|
const repoToken = strField(body, "repoToken");
|
|
20938
|
+
const dashboardUrl = strField(body, "dashboardUrl");
|
|
20939
|
+
const storeRepoUrl = strField(body, "storeRepoUrl");
|
|
20940
|
+
const storeRef = strField(body, "storeRef");
|
|
20692
20941
|
let agentCwd;
|
|
20693
20942
|
try {
|
|
20694
20943
|
agentCwd = await ensureRepoCwd({
|
|
@@ -20765,7 +21014,14 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
20765
21014
|
sink,
|
|
20766
21015
|
// Let the agent clone + work on OTHER repos via the fetch_repo tool.
|
|
20767
21016
|
reposRoot: opts.reposRoot,
|
|
20768
|
-
repoToken
|
|
21017
|
+
repoToken,
|
|
21018
|
+
...dashboardUrl && repo && stateToken ? {
|
|
21019
|
+
cmsDashboardUrl: dashboardUrl,
|
|
21020
|
+
cmsRepoSlug: repo,
|
|
21021
|
+
cmsToken: stateToken,
|
|
21022
|
+
cmsStoreRepoUrl: storeRepoUrl,
|
|
21023
|
+
cmsStoreRef: storeRef
|
|
21024
|
+
} : {}
|
|
20769
21025
|
});
|
|
20770
21026
|
} catch (err) {
|
|
20771
21027
|
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
@@ -21022,7 +21278,10 @@ async function proxyToBrainServe(args) {
|
|
|
21022
21278
|
body: JSON.stringify({
|
|
21023
21279
|
message: args.body.message,
|
|
21024
21280
|
...args.body.repo ? { repo: args.body.repo } : {},
|
|
21025
|
-
...args.body.repoToken ? { repoToken: args.body.repoToken } : {}
|
|
21281
|
+
...args.body.repoToken ? { repoToken: args.body.repoToken } : {},
|
|
21282
|
+
...args.body.dashboardUrl ? { dashboardUrl: args.body.dashboardUrl } : {},
|
|
21283
|
+
...args.body.storeRepoUrl ? { storeRepoUrl: args.body.storeRepoUrl } : {},
|
|
21284
|
+
...args.body.storeRef ? { storeRef: args.body.storeRef } : {}
|
|
21026
21285
|
})
|
|
21027
21286
|
});
|
|
21028
21287
|
if (!upstream.ok || !upstream.body) {
|
|
@@ -21237,15 +21496,15 @@ function buildMcpHttpServer(opts) {
|
|
|
21237
21496
|
transports.set(route.path, transport);
|
|
21238
21497
|
routes.set(route.path, route.name);
|
|
21239
21498
|
const mcpServer = new McpServer({ name: route.name, version: route.version });
|
|
21240
|
-
for (const
|
|
21499
|
+
for (const tool6 of route.tools) {
|
|
21241
21500
|
mcpServer.registerTool(
|
|
21242
|
-
|
|
21501
|
+
tool6.name,
|
|
21243
21502
|
{
|
|
21244
|
-
description:
|
|
21245
|
-
inputSchema:
|
|
21503
|
+
description: tool6.description,
|
|
21504
|
+
inputSchema: tool6.inputSchema
|
|
21246
21505
|
},
|
|
21247
21506
|
async (args) => {
|
|
21248
|
-
const result = await
|
|
21507
|
+
const result = await tool6.handler(args);
|
|
21249
21508
|
return {
|
|
21250
21509
|
content: result.content,
|
|
21251
21510
|
...result.isError ? { isError: true } : {}
|
|
@@ -22551,7 +22810,7 @@ var PERF_GUEST = {
|
|
|
22551
22810
|
medium: { cpu_kind: "performance", cpus: 1, memory_mb: 2048 },
|
|
22552
22811
|
high: { cpu_kind: "performance", cpus: 2, memory_mb: 4096 }
|
|
22553
22812
|
};
|
|
22554
|
-
function
|
|
22813
|
+
function envInt2(name, dflt) {
|
|
22555
22814
|
const v = Number(process.env[name]);
|
|
22556
22815
|
return Number.isFinite(v) && v > 0 ? v : dflt;
|
|
22557
22816
|
}
|
|
@@ -22671,10 +22930,10 @@ async function poolServe() {
|
|
|
22671
22930
|
const region = process.env.POOL_REGION ?? "fra";
|
|
22672
22931
|
const perf = process.env.POOL_PERF ?? "medium";
|
|
22673
22932
|
const guest = PERF_GUEST[perf] ?? PERF_GUEST.medium;
|
|
22674
|
-
const min =
|
|
22675
|
-
const runnerPort =
|
|
22676
|
-
const apiPort =
|
|
22677
|
-
const healthTimeoutMs =
|
|
22933
|
+
const min = envInt2("POOL_MIN", 2);
|
|
22934
|
+
const runnerPort = envInt2("RUNNER_PORT", 8080);
|
|
22935
|
+
const apiPort = envInt2("POOL_API_PORT", 4100);
|
|
22936
|
+
const healthTimeoutMs = envInt2("POOL_HEALTH_TIMEOUT_MS", 12e4);
|
|
22678
22937
|
const registry = new PoolRegistry({
|
|
22679
22938
|
githubToken,
|
|
22680
22939
|
masterKey: master,
|
|
@@ -22690,12 +22949,12 @@ async function poolServe() {
|
|
|
22690
22949
|
},
|
|
22691
22950
|
log
|
|
22692
22951
|
});
|
|
22693
|
-
const refillMs =
|
|
22952
|
+
const refillMs = envInt2("POOL_REFILL_INTERVAL_MS", 6e4);
|
|
22694
22953
|
const tick = setInterval(() => {
|
|
22695
22954
|
registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
22696
22955
|
}, refillMs);
|
|
22697
22956
|
const dutyTickEnabled = (process.env.POOL_CAPABILITY_TICK ?? "1") !== "0";
|
|
22698
|
-
const dutyTickMs =
|
|
22957
|
+
const dutyTickMs = envInt2("POOL_CAPABILITY_TICK_MS", 15 * 6e4);
|
|
22699
22958
|
const dutyTick = dutyTickEnabled ? setInterval(() => {
|
|
22700
22959
|
runCapabilityFallbackTick({
|
|
22701
22960
|
isDegraded: () => gitHubActionsDegraded(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.278",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|