@kody-ade/kody-engine 0.4.250 → 0.4.251
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 +1422 -1372
- package/package.json +22 -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.
|
|
18
|
+
version: "0.4.251",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative agentAction profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -165,15 +165,18 @@ function gh(args, options) {
|
|
|
165
165
|
}).trim();
|
|
166
166
|
}
|
|
167
167
|
function getIssue(issueNumber, cwd) {
|
|
168
|
-
const output = gh(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels"], { cwd });
|
|
168
|
+
const output = gh(["issue", "view", String(issueNumber), "--json", "number,title,body,comments,labels,url"], { cwd });
|
|
169
169
|
const parsed = JSON.parse(output);
|
|
170
170
|
if (typeof parsed?.title !== "string") {
|
|
171
171
|
throw new Error(`Issue #${issueNumber}: unexpected response shape`);
|
|
172
172
|
}
|
|
173
|
+
const url = typeof parsed.url === "string" ? parsed.url : void 0;
|
|
173
174
|
return {
|
|
174
175
|
number: parsed.number ?? issueNumber,
|
|
175
176
|
title: parsed.title,
|
|
176
177
|
body: parsed.body ?? "",
|
|
178
|
+
url,
|
|
179
|
+
isPullRequest: typeof url === "string" && /\/pull\/\d+$/.test(url),
|
|
177
180
|
comments: (parsed.comments ?? []).map((c) => ({
|
|
178
181
|
body: c.body ?? "",
|
|
179
182
|
author: c.author?.login ?? "unknown",
|
|
@@ -1336,1482 +1339,1521 @@ var init_submitMcp = __esm({
|
|
|
1336
1339
|
}
|
|
1337
1340
|
});
|
|
1338
1341
|
|
|
1339
|
-
// src/agent-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
dispatchWorkflow: () => dispatchWorkflow,
|
|
1346
|
-
ensureComment: () => ensureComment,
|
|
1347
|
-
ensureIssue: () => ensureIssue,
|
|
1348
|
-
isDispatchGated: () => isDispatchGated,
|
|
1349
|
-
parseAgentResponsibilityTrustMode: () => parseAgentResponsibilityTrustMode,
|
|
1350
|
-
readAgentResponsibilityTrustMode: () => readAgentResponsibilityTrustMode,
|
|
1351
|
-
readCheckRuns: () => readCheckRuns,
|
|
1352
|
-
readThread: () => readThread
|
|
1353
|
-
});
|
|
1354
|
-
import { createSdkMcpServer as createSdkMcpServer3, tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
|
|
1355
|
-
import { z as z2 } from "zod";
|
|
1356
|
-
function summarizeCiStatus(rollup) {
|
|
1357
|
-
if (!Array.isArray(rollup) || rollup.length === 0) return "UNKNOWN";
|
|
1358
|
-
let hasRunning = false;
|
|
1359
|
-
for (const check of rollup) {
|
|
1360
|
-
const status = String(check.status ?? "").toUpperCase();
|
|
1361
|
-
const conclusion = String(check.conclusion ?? "").toUpperCase();
|
|
1362
|
-
if (FAIL_CONCLUSIONS.has(conclusion)) return "FAILING";
|
|
1363
|
-
if (!conclusion && RUNNING_STATUSES.has(status)) hasRunning = true;
|
|
1364
|
-
}
|
|
1365
|
-
return hasRunning ? "RUNNING" : "PASSING";
|
|
1366
|
-
}
|
|
1367
|
-
function computeBehindBy(repoSlug, base, head) {
|
|
1342
|
+
// src/agent-responsibilityFolders.ts
|
|
1343
|
+
import * as fs4 from "fs";
|
|
1344
|
+
import * as path6 from "path";
|
|
1345
|
+
function listAgentResponsibilityFolderSlugs(absDir) {
|
|
1346
|
+
if (!fs4.existsSync(absDir)) return [];
|
|
1347
|
+
let entries;
|
|
1368
1348
|
try {
|
|
1369
|
-
|
|
1370
|
-
const n = Number(raw.trim());
|
|
1371
|
-
return Number.isFinite(n) ? n : -1;
|
|
1349
|
+
entries = fs4.readdirSync(absDir, { withFileTypes: true });
|
|
1372
1350
|
} catch {
|
|
1373
|
-
return
|
|
1351
|
+
return [];
|
|
1374
1352
|
}
|
|
1353
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isAgentResponsibilityFolder(path6.join(absDir, e.name))).map((e) => e.name).sort();
|
|
1375
1354
|
}
|
|
1376
|
-
function
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const ciStatus = summarizeCiStatus(p.statusCheckRollup);
|
|
1390
|
-
const mergeable = String(p.mergeable ?? "UNKNOWN").toUpperCase();
|
|
1391
|
-
const behindBy = mergeable === "CONFLICTING" || ciStatus === "FAILING" ? 0 : computeBehindBy(repoSlug, p.baseRefName, p.headRefName);
|
|
1355
|
+
function isAgentResponsibilityFolder(dir) {
|
|
1356
|
+
return fs4.existsSync(path6.join(dir, AGENT_RESPONSIBILITY_PROFILE_FILE)) && fs4.existsSync(path6.join(dir, AGENT_RESPONSIBILITY_BODY_FILE));
|
|
1357
|
+
}
|
|
1358
|
+
function readAgentResponsibilityFolder(root, slug2) {
|
|
1359
|
+
const dir = path6.join(root, slug2);
|
|
1360
|
+
const profilePath = path6.join(dir, AGENT_RESPONSIBILITY_PROFILE_FILE);
|
|
1361
|
+
const bodyPath = path6.join(dir, AGENT_RESPONSIBILITY_BODY_FILE);
|
|
1362
|
+
if (!fs4.existsSync(profilePath) || !fs4.statSync(profilePath).isFile()) return null;
|
|
1363
|
+
if (!fs4.existsSync(bodyPath) || !fs4.statSync(bodyPath).isFile()) return null;
|
|
1364
|
+
try {
|
|
1365
|
+
const rawProfile = JSON.parse(fs4.readFileSync(profilePath, "utf-8"));
|
|
1366
|
+
const rawBody = fs4.readFileSync(bodyPath, "utf-8");
|
|
1367
|
+
const { title, body } = parseAgentResponsibilityBody(rawBody, slug2);
|
|
1392
1368
|
return {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1369
|
+
slug: slug2,
|
|
1370
|
+
dir,
|
|
1371
|
+
profilePath,
|
|
1372
|
+
bodyPath,
|
|
1373
|
+
title,
|
|
1374
|
+
body,
|
|
1375
|
+
rawBody,
|
|
1376
|
+
config: parseAgentResponsibilityConfig(rawProfile),
|
|
1377
|
+
rawProfile
|
|
1402
1378
|
};
|
|
1403
|
-
}
|
|
1379
|
+
} catch {
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1404
1382
|
}
|
|
1405
|
-
function
|
|
1406
|
-
|
|
1383
|
+
function parseAgentResponsibilityConfig(raw) {
|
|
1384
|
+
const tools = stringList(raw.tools ?? raw.agentResponsibilityTools);
|
|
1385
|
+
return {
|
|
1386
|
+
action: stringField(raw.action),
|
|
1387
|
+
agentAction: stringField(raw.agentAction),
|
|
1388
|
+
tickScript: stringField(raw.tickScript),
|
|
1389
|
+
disabled: typeof raw.disabled === "boolean" ? raw.disabled : void 0,
|
|
1390
|
+
agent: stringField(raw.agent),
|
|
1391
|
+
mentions: stringList(raw.mentions).map((m) => m.replace(/^@/, "")),
|
|
1392
|
+
tools,
|
|
1393
|
+
agentActions: stringList(raw.agentActions),
|
|
1394
|
+
capabilityKind: capabilityKindField(raw.capabilityKind),
|
|
1395
|
+
describe: stringField(raw.describe),
|
|
1396
|
+
stage: stringField(raw.stage),
|
|
1397
|
+
readsFrom: stringList(raw.readsFrom ?? raw.reads_from),
|
|
1398
|
+
writesTo: stringList(raw.writesTo ?? raw.writes_to)
|
|
1399
|
+
};
|
|
1407
1400
|
}
|
|
1408
|
-
function
|
|
1409
|
-
const
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
return { ok: true };
|
|
1416
|
-
} catch (err) {
|
|
1417
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1418
|
-
}
|
|
1401
|
+
function parseAgentResponsibilityBody(raw, slug2) {
|
|
1402
|
+
const trimmed = raw.trim();
|
|
1403
|
+
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
1404
|
+
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
1405
|
+
const title = h1 ? h1[1].trim() : humanizeSlug(slug2);
|
|
1406
|
+
const body = stripLeadingH1(raw);
|
|
1407
|
+
return { title, body };
|
|
1419
1408
|
}
|
|
1420
|
-
function
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
const issue = issues.sort((a, b) => a.number - b.number)[0];
|
|
1428
|
-
const body = issue?.body ?? "";
|
|
1429
|
-
const startIdx = body.indexOf(startTag);
|
|
1430
|
-
const endIdx = body.indexOf(endTag);
|
|
1431
|
-
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
1432
|
-
return { found: true, issueNumber: issue?.number, payload: null };
|
|
1433
|
-
}
|
|
1434
|
-
const between = body.slice(startIdx + startTag.length, endIdx);
|
|
1435
|
-
const fenceMatch = between.match(/```json\s*([\s\S]*?)```/);
|
|
1436
|
-
if (!fenceMatch) return { found: true, issueNumber: issue?.number, payload: null };
|
|
1437
|
-
try {
|
|
1438
|
-
return { found: true, issueNumber: issue?.number, payload: JSON.parse(fenceMatch[1]) };
|
|
1439
|
-
} catch {
|
|
1440
|
-
return { found: true, issueNumber: issue?.number, payload: null };
|
|
1441
|
-
}
|
|
1442
|
-
} catch (err) {
|
|
1443
|
-
return { found: false, payload: { error: err instanceof Error ? err.message : String(err) } };
|
|
1409
|
+
function stripLeadingH1(raw) {
|
|
1410
|
+
const lines = raw.replace(/^\uFEFF/, "").split("\n");
|
|
1411
|
+
let i = 0;
|
|
1412
|
+
for (; ; ) {
|
|
1413
|
+
while (i < lines.length && lines[i].trim() === "") i++;
|
|
1414
|
+
if (i < lines.length && /^#\s+.+/.test(lines[i])) i++;
|
|
1415
|
+
else break;
|
|
1444
1416
|
}
|
|
1417
|
+
return lines.slice(i).join("\n");
|
|
1445
1418
|
}
|
|
1446
|
-
function
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1419
|
+
function humanizeSlug(slug2) {
|
|
1420
|
+
return slug2.split(/[-_]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
1421
|
+
}
|
|
1422
|
+
function stringField(value) {
|
|
1423
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
1424
|
+
}
|
|
1425
|
+
function stringList(value) {
|
|
1426
|
+
if (Array.isArray(value)) {
|
|
1427
|
+
return value.map((v) => String(v).trim()).filter(Boolean);
|
|
1452
1428
|
}
|
|
1429
|
+
if (typeof value === "string") {
|
|
1430
|
+
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
1431
|
+
}
|
|
1432
|
+
return [];
|
|
1453
1433
|
}
|
|
1454
|
-
function
|
|
1455
|
-
|
|
1456
|
-
return
|
|
1434
|
+
function capabilityKindField(value) {
|
|
1435
|
+
if (value === "observe" || value === "act" || value === "verify") return value;
|
|
1436
|
+
return void 0;
|
|
1457
1437
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
const loaded = readStateText({ state: state ?? defaultStateForRepoSlug(repoSlug) }, void 0, TRUST_FILE_PATH);
|
|
1465
|
-
return loaded ? parseAgentResponsibilityTrustMode(loaded.content, agentResponsibilitySlug) : "ask";
|
|
1466
|
-
} catch {
|
|
1467
|
-
return "ask";
|
|
1438
|
+
var AGENT_RESPONSIBILITY_PROFILE_FILE, AGENT_RESPONSIBILITY_BODY_FILE;
|
|
1439
|
+
var init_agent_responsibilityFolders = __esm({
|
|
1440
|
+
"src/agent-responsibilityFolders.ts"() {
|
|
1441
|
+
"use strict";
|
|
1442
|
+
AGENT_RESPONSIBILITY_PROFILE_FILE = "profile.json";
|
|
1443
|
+
AGENT_RESPONSIBILITY_BODY_FILE = "agent-responsibility.md";
|
|
1468
1444
|
}
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
// src/companyStore.ts
|
|
1448
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1449
|
+
import * as crypto3 from "crypto";
|
|
1450
|
+
import * as fs5 from "fs";
|
|
1451
|
+
import * as os3 from "os";
|
|
1452
|
+
import * as path7 from "path";
|
|
1453
|
+
function getCompanyStoreRoot() {
|
|
1454
|
+
const store = resolveCompanyStore();
|
|
1455
|
+
if (!store) return null;
|
|
1456
|
+
const key = `${store.repo}#${store.ref}`;
|
|
1457
|
+
if (memo?.key === key) return memo.root;
|
|
1458
|
+
const root = fetchCompanyStore(store.repo, store.ref);
|
|
1459
|
+
memo = { key, root };
|
|
1460
|
+
return root;
|
|
1469
1461
|
}
|
|
1470
|
-
function
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
const
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
return {
|
|
1479
|
-
number,
|
|
1480
|
-
title: meta.title ?? "",
|
|
1481
|
-
state: meta.state ?? "",
|
|
1482
|
-
labels: (meta.labels ?? []).map((l) => l.name ?? "").filter(Boolean),
|
|
1483
|
-
comments
|
|
1462
|
+
function getCompanyStoreAssetRoot(kind) {
|
|
1463
|
+
const root = getCompanyStoreRoot();
|
|
1464
|
+
if (!root) return null;
|
|
1465
|
+
const folderByKind = {
|
|
1466
|
+
agentResponsibilities: "agent-responsibilities",
|
|
1467
|
+
agentActions: "agent-actions",
|
|
1468
|
+
goals: "goals",
|
|
1469
|
+
agents: "agents"
|
|
1484
1470
|
};
|
|
1471
|
+
return path7.join(root, ".kody", folderByKind[kind]);
|
|
1485
1472
|
}
|
|
1486
|
-
function
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
"--jq",
|
|
1495
|
-
".check_runs[] | {name, status, conclusion, details_url}"
|
|
1496
|
-
],
|
|
1497
|
-
ghOptions
|
|
1498
|
-
);
|
|
1499
|
-
const ignore = new Set(ignoreNames.map((n) => n.toLowerCase()));
|
|
1500
|
-
const checks = raw.split("\n").map((l) => l.trim()).filter(Boolean).map((l) => JSON.parse(l)).filter((c) => !ignore.has(String(c.name).toLowerCase()));
|
|
1501
|
-
const failing = checks.filter((c) => CHECK_FAIL_CONCLUSIONS.has(String(c.conclusion ?? "").toUpperCase())).map((c) => ({ name: c.name, conclusion: String(c.conclusion), detailsUrl: c.details_url }));
|
|
1502
|
-
const pending = checks.filter((c) => String(c.status).toLowerCase() !== "completed").map((c) => ({ name: c.name, status: c.status }));
|
|
1503
|
-
const state = failing.length > 0 ? "RED" : pending.length > 0 ? "PENDING" : "GREEN";
|
|
1504
|
-
return { sha, state, failing, pending };
|
|
1473
|
+
function resolveCompanyStore() {
|
|
1474
|
+
const envStore = process.env[STORE_ENV]?.trim();
|
|
1475
|
+
if (!envStore && process.env.VITEST) return null;
|
|
1476
|
+
const repo = envStore || DEFAULT_COMPANY_STORE;
|
|
1477
|
+
if (repo === "0" || repo === "false" || repo === "off") return null;
|
|
1478
|
+
const ref = process.env[REF_ENV]?.trim() || DEFAULT_COMPANY_STORE_REF;
|
|
1479
|
+
if (!ref) return null;
|
|
1480
|
+
return { repo, ref };
|
|
1505
1481
|
}
|
|
1506
|
-
function
|
|
1507
|
-
const
|
|
1482
|
+
function fetchCompanyStore(repo, ref) {
|
|
1483
|
+
const localRoot = localStoreRoot(repo);
|
|
1484
|
+
if (localRoot) return localRoot;
|
|
1485
|
+
const url = repoToGitUrl(repo);
|
|
1486
|
+
const cacheDir = path7.join(cacheRoot(), cacheKey(repo, ref));
|
|
1508
1487
|
try {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
if (!m) return { error: `issue created but could not parse its number from: ${url}` };
|
|
1520
|
-
return { created: true, number: Number(m[1]) };
|
|
1488
|
+
if (isGitHubShorthand(repo)) setupGithubGitAuth();
|
|
1489
|
+
fs5.mkdirSync(path7.dirname(cacheDir), { recursive: true });
|
|
1490
|
+
if (!fs5.existsSync(path7.join(cacheDir, ".git"))) {
|
|
1491
|
+
fs5.rmSync(cacheDir, { recursive: true, force: true });
|
|
1492
|
+
runGit(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
|
|
1493
|
+
}
|
|
1494
|
+
runGit(["-C", cacheDir, "remote", "set-url", "origin", url]);
|
|
1495
|
+
runGit(["-C", cacheDir, "fetch", "--depth=1", "origin", ref]);
|
|
1496
|
+
runGit(["-C", cacheDir, "checkout", "--detach", "FETCH_HEAD"]);
|
|
1497
|
+
return cacheDir;
|
|
1521
1498
|
} catch (err) {
|
|
1522
|
-
|
|
1499
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1500
|
+
process.stderr.write(`[company-store] failed to fetch ${repo}#${ref}: ${msg}
|
|
1501
|
+
`);
|
|
1502
|
+
return null;
|
|
1523
1503
|
}
|
|
1524
1504
|
}
|
|
1525
|
-
function
|
|
1526
|
-
|
|
1505
|
+
function localStoreRoot(repo) {
|
|
1506
|
+
if (!path7.isAbsolute(repo) && !repo.startsWith(".")) return null;
|
|
1507
|
+
const root = path7.resolve(repo);
|
|
1508
|
+
if (!fs5.existsSync(path7.join(root, ".kody"))) return null;
|
|
1509
|
+
return root;
|
|
1510
|
+
}
|
|
1511
|
+
function repoToGitUrl(repo) {
|
|
1512
|
+
if (/^(https?:|ssh:|git@|file:)/.test(repo)) return repo;
|
|
1513
|
+
if (isGitHubShorthand(repo)) {
|
|
1514
|
+
return `https://github.com/${repo}.git`;
|
|
1515
|
+
}
|
|
1516
|
+
return repo;
|
|
1517
|
+
}
|
|
1518
|
+
function isGitHubShorthand(repo) {
|
|
1519
|
+
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo);
|
|
1520
|
+
}
|
|
1521
|
+
function cacheRoot() {
|
|
1522
|
+
return process.env[CACHE_ENV]?.trim() || path7.join(os3.homedir(), ".cache", "kody", "company-store");
|
|
1523
|
+
}
|
|
1524
|
+
function cacheKey(repo, ref) {
|
|
1525
|
+
return crypto3.createHash("sha256").update(`${repo}#${ref}`).digest("hex").slice(0, 24);
|
|
1526
|
+
}
|
|
1527
|
+
function runGit(args) {
|
|
1528
|
+
execFileSync2("git", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1529
|
+
}
|
|
1530
|
+
function setupGithubGitAuth() {
|
|
1531
|
+
const token = process.env.KODY_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.GH_PAT;
|
|
1532
|
+
if (!token) return;
|
|
1527
1533
|
try {
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1534
|
+
execFileSync2("gh", ["auth", "setup-git", "--hostname", "github.com"], {
|
|
1535
|
+
env: { ...process.env, GH_TOKEN: token },
|
|
1536
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
1537
|
+
});
|
|
1538
|
+
} catch {
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
var DEFAULT_COMPANY_STORE, DEFAULT_COMPANY_STORE_REF, STORE_ENV, REF_ENV, CACHE_ENV, memo;
|
|
1542
|
+
var init_companyStore = __esm({
|
|
1543
|
+
"src/companyStore.ts"() {
|
|
1544
|
+
"use strict";
|
|
1545
|
+
DEFAULT_COMPANY_STORE = "aharonyaircohen/kody-company-store";
|
|
1546
|
+
DEFAULT_COMPANY_STORE_REF = "stable";
|
|
1547
|
+
STORE_ENV = "KODY_COMPANY_STORE";
|
|
1548
|
+
REF_ENV = "KODY_COMPANY_STORE_REF";
|
|
1549
|
+
CACHE_ENV = "KODY_COMPANY_STORE_CACHE";
|
|
1550
|
+
memo = null;
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1533
1553
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1554
|
+
// src/registry.ts
|
|
1555
|
+
import * as fs6 from "fs";
|
|
1556
|
+
import * as path8 from "path";
|
|
1557
|
+
function getAgentActionsRoot() {
|
|
1558
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
1559
|
+
const candidates = [
|
|
1560
|
+
path8.join(here, "agent-actions"),
|
|
1561
|
+
// dev: src/
|
|
1562
|
+
path8.join(here, "..", "agent-actions"),
|
|
1563
|
+
// built: dist/bin → dist/agent-actions
|
|
1564
|
+
path8.join(here, "..", "src", "agent-actions")
|
|
1565
|
+
// fallback
|
|
1566
|
+
];
|
|
1567
|
+
for (const c of candidates) {
|
|
1568
|
+
if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
|
|
1538
1569
|
}
|
|
1570
|
+
return candidates[0];
|
|
1539
1571
|
}
|
|
1540
|
-
function
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1572
|
+
function getProjectAgentActionsRoot() {
|
|
1573
|
+
return path8.join(process.cwd(), ".kody", "agent-actions");
|
|
1574
|
+
}
|
|
1575
|
+
function getProjectAgentResponsibilitiesRoot() {
|
|
1576
|
+
return path8.join(process.cwd(), ".kody", "agent-responsibilities");
|
|
1577
|
+
}
|
|
1578
|
+
function getCompanyStoreAgentActionsRoot() {
|
|
1579
|
+
return getCompanyStoreAssetRoot("agentActions");
|
|
1580
|
+
}
|
|
1581
|
+
function getCompanyStoreAgentResponsibilitiesRoot() {
|
|
1582
|
+
return getCompanyStoreAssetRoot("agentResponsibilities");
|
|
1583
|
+
}
|
|
1584
|
+
function getBuiltinAgentResponsibilitiesRoot() {
|
|
1585
|
+
const here = path8.dirname(new URL(import.meta.url).pathname);
|
|
1586
|
+
const candidates = [
|
|
1587
|
+
path8.join(here, "agent-responsibilities"),
|
|
1588
|
+
// dev: src/
|
|
1589
|
+
path8.join(here, "..", "agent-responsibilities"),
|
|
1590
|
+
// built: dist/bin → dist/agent-responsibilities
|
|
1591
|
+
path8.join(here, "..", "src", "agent-responsibilities")
|
|
1592
|
+
// fallback
|
|
1593
|
+
];
|
|
1594
|
+
for (const c of candidates) {
|
|
1595
|
+
if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
|
|
1554
1596
|
}
|
|
1597
|
+
return candidates[0];
|
|
1555
1598
|
}
|
|
1556
|
-
function
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
return true;
|
|
1599
|
+
function getAgentActionRoots() {
|
|
1600
|
+
const storeRoot = getCompanyStoreAgentActionsRoot();
|
|
1601
|
+
return [getProjectAgentActionsRoot(), ...storeRoot ? [storeRoot] : [], getAgentActionsRoot()];
|
|
1560
1602
|
}
|
|
1561
|
-
function
|
|
1562
|
-
|
|
1603
|
+
function getAgentResponsibilityRoots(projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
1604
|
+
const storeRoot = getCompanyStoreAgentResponsibilitiesRoot();
|
|
1605
|
+
return [projectAgentResponsibilitiesRoot, ...storeRoot ? [storeRoot] : [], getBuiltinAgentResponsibilitiesRoot()];
|
|
1563
1606
|
}
|
|
1564
|
-
function
|
|
1565
|
-
const
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
]
|
|
1579
|
-
};
|
|
1580
|
-
}
|
|
1581
|
-
};
|
|
1582
|
-
const makeDispatch = (verb, describe) => ({
|
|
1583
|
-
name: `${verb.replace("-", "_")}_pr`,
|
|
1584
|
-
description: describe,
|
|
1585
|
-
inputSchema: {
|
|
1586
|
-
pr: z2.number().int().positive().describe("PR number to repair.")
|
|
1587
|
-
},
|
|
1588
|
-
handler: async (args) => {
|
|
1589
|
-
const pr = Number(args.pr);
|
|
1590
|
-
if (isDispatchGated(verb, readAgentResponsibilityTrustMode(opts.state, opts.repoSlug, opts.agentResponsibilitySlug))) {
|
|
1591
|
-
return { content: [{ type: "text", text: trustRefusal(opts.agentResponsibilitySlug) }] };
|
|
1607
|
+
function listAgentActions(roots = getAgentActionRoots()) {
|
|
1608
|
+
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
1609
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1610
|
+
const out = [];
|
|
1611
|
+
for (const root of rootList) {
|
|
1612
|
+
if (!fs6.existsSync(root)) continue;
|
|
1613
|
+
const entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
1614
|
+
for (const ent of entries) {
|
|
1615
|
+
if (!ent.isDirectory()) continue;
|
|
1616
|
+
if (seen.has(ent.name)) continue;
|
|
1617
|
+
const profilePath = path8.join(root, ent.name, AGENT_RESPONSIBILITY_PROFILE_FILE);
|
|
1618
|
+
if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
|
|
1619
|
+
out.push({ name: ent.name, profilePath });
|
|
1620
|
+
seen.add(ent.name);
|
|
1592
1621
|
}
|
|
1593
|
-
const result = dispatchVerb(workflowFile, verb, pr);
|
|
1594
|
-
const text = result.ok ? `Dispatched \`${verb}\` on PR #${pr}. The repair runs in its own workflow_dispatch \u2014 wait for the next tick to see the new headSha.` : `Dispatch failed for \`${verb}\` on PR #${pr}: ${result.error}`;
|
|
1595
|
-
return { content: [{ type: "text", text }] };
|
|
1596
|
-
}
|
|
1597
|
-
});
|
|
1598
|
-
const syncTool = makeDispatch(
|
|
1599
|
-
"sync",
|
|
1600
|
-
"Bring a stale PR up to date with its base branch (merges base \u2192 head + pushes). Use when behindBy > 10 AND mergeable !== CONFLICTING AND ciStatus !== FAILING. Returns immediately \u2014 the actual merge runs in a separate workflow."
|
|
1601
|
-
);
|
|
1602
|
-
const fixCiTool = makeDispatch(
|
|
1603
|
-
"fix-ci",
|
|
1604
|
-
"Repair a PR whose CI is failing. Use when ciStatus === FAILING. The repair runs in a separate workflow."
|
|
1605
|
-
);
|
|
1606
|
-
const resolveTool = makeDispatch(
|
|
1607
|
-
"resolve",
|
|
1608
|
-
"Resolve merge conflicts on a PR. Use when mergeable === CONFLICTING. The repair runs in a separate workflow."
|
|
1609
|
-
);
|
|
1610
|
-
const recommendTool = {
|
|
1611
|
-
name: "recommend_to_operator",
|
|
1612
|
-
description: "Post ONE comment on a PR with the operator @-mention prepended. Use this when a agentResponsibility 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.",
|
|
1613
|
-
inputSchema: {
|
|
1614
|
-
pr: z2.number().int().positive().describe("PR number to comment on."),
|
|
1615
|
-
body: z2.string().min(1).describe("Comment body (markdown). Do not include the operator mention \u2014 the engine prepends it.")
|
|
1616
|
-
},
|
|
1617
|
-
handler: async (args) => {
|
|
1618
|
-
const pr = Number(args.pr);
|
|
1619
|
-
const body = String(args.body ?? "");
|
|
1620
|
-
const result = postRecommendation(pr, opts.operatorMention, body, opts.agentResponsibilitySlug);
|
|
1621
|
-
const text = result.ok ? `Recommendation posted on PR #${pr}.` : `Recommendation failed on PR #${pr}: ${result.error}`;
|
|
1622
|
-
return { content: [{ type: "text", text }] };
|
|
1623
|
-
}
|
|
1624
|
-
};
|
|
1625
|
-
const ledgerTool = {
|
|
1626
|
-
name: "read_ledger",
|
|
1627
|
-
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.",
|
|
1628
|
-
inputSchema: {
|
|
1629
|
-
label: z2.string().min(1).describe("GitHub issue label that identifies the manifest issue.")
|
|
1630
|
-
},
|
|
1631
|
-
handler: async (args) => {
|
|
1632
|
-
const label = String(args.label ?? "");
|
|
1633
|
-
const result = readLedger(label);
|
|
1634
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1635
1622
|
}
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
const ignoreNames = Array.isArray(args.ignoreNames) ? args.ignoreNames : DEFAULT_IGNORE_CHECKS;
|
|
1647
|
-
const result = readCheckRuns(opts.repoSlug, ref, ignoreNames);
|
|
1648
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1623
|
+
}
|
|
1624
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
1625
|
+
}
|
|
1626
|
+
function resolveAgentAction(name, roots = getAgentActionRoots()) {
|
|
1627
|
+
if (!isSafeName(name)) return null;
|
|
1628
|
+
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
1629
|
+
for (const root of rootList) {
|
|
1630
|
+
const profilePath = path8.join(root, name, "profile.json");
|
|
1631
|
+
if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
|
|
1632
|
+
return profilePath;
|
|
1649
1633
|
}
|
|
1634
|
+
}
|
|
1635
|
+
return null;
|
|
1636
|
+
}
|
|
1637
|
+
function listAgentResponsibilityActions(projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
1638
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1639
|
+
const out = [];
|
|
1640
|
+
const add = (action) => {
|
|
1641
|
+
if (!isSafeName(action.action) || !isSafeName(action.agentResponsibility) || !isSafeName(action.agentAction)) return;
|
|
1642
|
+
if (seen.has(action.action)) return;
|
|
1643
|
+
seen.add(action.action);
|
|
1644
|
+
out.push(action);
|
|
1650
1645
|
};
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
};
|
|
1665
|
-
const ensureIssueTool = {
|
|
1666
|
-
name: "ensure_issue",
|
|
1667
|
-
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.",
|
|
1668
|
-
inputSchema: {
|
|
1669
|
-
key: z2.string().min(1).describe(
|
|
1670
|
-
"Stable dedup identity for this finding (e.g. 'dev-ci-red', 'docs-drift:<feature>'). Same key across ticks = same issue."
|
|
1671
|
-
),
|
|
1672
|
-
title: z2.string().min(1).describe("Issue title (used only on first creation)."),
|
|
1673
|
-
body: z2.string().min(1).describe(
|
|
1674
|
-
"Issue body markdown (used only on first creation). Include the operator mention verbatim if the agentResponsibility body has one."
|
|
1675
|
-
)
|
|
1676
|
-
},
|
|
1677
|
-
handler: async (args) => {
|
|
1678
|
-
const key = String(args.key ?? "");
|
|
1679
|
-
const title = String(args.title ?? "");
|
|
1680
|
-
const body = String(args.body ?? "");
|
|
1681
|
-
const result = ensureIssue(opts.repoSlug, key, title, body);
|
|
1682
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1683
|
-
}
|
|
1684
|
-
};
|
|
1685
|
-
const ensureCommentTool = {
|
|
1686
|
-
name: "ensure_comment",
|
|
1687
|
-
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.",
|
|
1688
|
-
inputSchema: {
|
|
1689
|
-
issue: z2.number().int().positive().describe("Issue number to comment on."),
|
|
1690
|
-
key: z2.string().min(1).describe("Stable dedup identity for this comment (e.g. 'dev-ci-red:dispatched')."),
|
|
1691
|
-
body: z2.string().min(1).describe("Comment body markdown.")
|
|
1692
|
-
},
|
|
1693
|
-
handler: async (args) => {
|
|
1694
|
-
const issue = Number(args.issue);
|
|
1695
|
-
const key = String(args.key ?? "");
|
|
1696
|
-
const body = String(args.body ?? "");
|
|
1697
|
-
const result = ensureComment(opts.repoSlug, issue, key, body);
|
|
1698
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1699
|
-
}
|
|
1700
|
-
};
|
|
1701
|
-
const dispatchTool = {
|
|
1702
|
-
name: "dispatch_workflow",
|
|
1703
|
-
description: "Dispatch a kody.yml workflow_dispatch run for a agentResponsibility action against an issue (the cross-run bot\u2192engine path; a bot `@kody` comment would be dropped). E.g. dispatch_workflow({agentResponsibility:'run', issueNumber:<n>}) opens a fix PR from a tracking issue. Returns {ok} or {ok:false,error}.",
|
|
1704
|
-
inputSchema: {
|
|
1705
|
-
agentResponsibility: z2.string().min(1).optional().describe("AgentResponsibility action to run (e.g. 'run')."),
|
|
1706
|
-
agentAction: z2.string().min(1).optional().describe("Deprecated alias for agentResponsibility."),
|
|
1707
|
-
issueNumber: z2.number().int().positive().describe("Issue (or PR) number forwarded as issue_number.")
|
|
1708
|
-
},
|
|
1709
|
-
handler: async (args) => {
|
|
1710
|
-
const agentResponsibility = String(args.agentResponsibility ?? args.agentAction ?? "");
|
|
1711
|
-
const issueNumber = Number(args.issueNumber);
|
|
1712
|
-
if (isDispatchGated(
|
|
1713
|
-
agentResponsibility,
|
|
1714
|
-
readAgentResponsibilityTrustMode(opts.state, opts.repoSlug, opts.agentResponsibilitySlug)
|
|
1715
|
-
)) {
|
|
1716
|
-
return { content: [{ type: "text", text: trustRefusal(opts.agentResponsibilitySlug) }] };
|
|
1717
|
-
}
|
|
1718
|
-
const result = dispatchWorkflow(workflowFile, agentResponsibility, issueNumber);
|
|
1719
|
-
const text = result.ok ? `Dispatched agentResponsibility \`${agentResponsibility}\` on #${issueNumber} via workflow_dispatch.` : `Dispatch failed for agentResponsibility \`${agentResponsibility}\` on #${issueNumber}: ${result.error}`;
|
|
1720
|
-
return { content: [{ type: "text", text }] };
|
|
1721
|
-
}
|
|
1722
|
-
};
|
|
1723
|
-
return [
|
|
1724
|
-
listTool,
|
|
1725
|
-
syncTool,
|
|
1726
|
-
fixCiTool,
|
|
1727
|
-
resolveTool,
|
|
1728
|
-
recommendTool,
|
|
1729
|
-
ledgerTool,
|
|
1730
|
-
checkRunsTool,
|
|
1731
|
-
readThreadTool,
|
|
1732
|
-
ensureIssueTool,
|
|
1733
|
-
ensureCommentTool,
|
|
1734
|
-
dispatchTool
|
|
1735
|
-
];
|
|
1646
|
+
const roots = getAgentResponsibilityRoots(projectAgentResponsibilitiesRoot);
|
|
1647
|
+
const executableRoots = getAgentActionRoots();
|
|
1648
|
+
for (const action of listFolderAgentResponsibilityActions(roots[0], "project-folder")) add(action);
|
|
1649
|
+
for (const action of listAgentActionResponsibilityActions(executableRoots[0], "project-agentAction")) add(action);
|
|
1650
|
+
if (roots.length === 3) {
|
|
1651
|
+
for (const action of listFolderAgentResponsibilityActions(roots[1], "company-store")) add(action);
|
|
1652
|
+
for (const action of listAgentActionResponsibilityActions(executableRoots[1], "company-store-agentAction"))
|
|
1653
|
+
add(action);
|
|
1654
|
+
for (const action of listBuiltinAgentResponsibilityActions(roots[2])) add(action);
|
|
1655
|
+
} else {
|
|
1656
|
+
for (const action of listBuiltinAgentResponsibilityActions(roots[1])) add(action);
|
|
1657
|
+
}
|
|
1658
|
+
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
1736
1659
|
}
|
|
1737
|
-
function
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
(def) => tool3(
|
|
1741
|
-
def.name,
|
|
1742
|
-
def.description,
|
|
1743
|
-
def.inputSchema,
|
|
1744
|
-
async (args) => def.handler(args)
|
|
1745
|
-
)
|
|
1746
|
-
);
|
|
1747
|
-
const server = createSdkMcpServer3({
|
|
1748
|
-
name: "kody-agentResponsibility",
|
|
1749
|
-
version: "0.1.0",
|
|
1750
|
-
tools
|
|
1751
|
-
});
|
|
1752
|
-
return { server };
|
|
1660
|
+
function resolveAgentResponsibilityAction(action, projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
1661
|
+
if (!isSafeName(action)) return null;
|
|
1662
|
+
return listAgentResponsibilityActions(projectAgentResponsibilitiesRoot).find((d) => d.action === action) ?? null;
|
|
1753
1663
|
}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
TRUST_FILE_PATH = "state/trust.json";
|
|
1763
|
-
THREAD_BODY_MAX = 4e3;
|
|
1764
|
-
CHECK_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "STARTUP_FAILURE", "ACTION_REQUIRED"]);
|
|
1765
|
-
DEFAULT_IGNORE_CHECKS = ["run", "kody", "agent-responsibility-tick", "agent-ask", "chat"];
|
|
1766
|
-
trackMarker = (key) => `<!-- kody-track:${key} -->`;
|
|
1767
|
-
commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
|
|
1768
|
-
GATE_EXEMPT_DUTIES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
|
|
1769
|
-
AGENT_RESPONSIBILITY_MCP_TOOL_NAMES = [
|
|
1770
|
-
"list_prs_to_repair",
|
|
1771
|
-
"sync_pr",
|
|
1772
|
-
"fix_ci_pr",
|
|
1773
|
-
"resolve_pr",
|
|
1774
|
-
"recommend_to_operator",
|
|
1775
|
-
"read_ledger",
|
|
1776
|
-
"read_check_runs",
|
|
1777
|
-
"read_thread",
|
|
1778
|
-
"ensure_issue",
|
|
1779
|
-
"ensure_comment",
|
|
1780
|
-
"dispatch_workflow"
|
|
1781
|
-
];
|
|
1782
|
-
}
|
|
1783
|
-
});
|
|
1784
|
-
|
|
1785
|
-
// src/repoWorkspace.ts
|
|
1786
|
-
import { spawn as spawn2, spawnSync } from "child_process";
|
|
1787
|
-
import * as fs4 from "fs";
|
|
1788
|
-
import * as path6 from "path";
|
|
1789
|
-
async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
|
|
1790
|
-
const name = repo?.trim();
|
|
1791
|
-
if (!name || !REPO_RE.test(name)) return null;
|
|
1792
|
-
const root = path6.resolve(reposRoot);
|
|
1793
|
-
const dir = path6.resolve(root, name);
|
|
1794
|
-
if (dir !== root && !dir.startsWith(root + path6.sep)) return null;
|
|
1795
|
-
if (fs4.existsSync(path6.join(dir, ".git"))) return dir;
|
|
1796
|
-
const inflight = repoClones.get(dir);
|
|
1797
|
-
if (inflight) {
|
|
1798
|
-
await inflight;
|
|
1799
|
-
return dir;
|
|
1664
|
+
function hasAgentResponsibilityAction(action, projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
1665
|
+
return resolveAgentResponsibilityAction(action, projectAgentResponsibilitiesRoot) !== null;
|
|
1666
|
+
}
|
|
1667
|
+
function resolveAgentResponsibilityFolder(slug2, projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
1668
|
+
if (!isSafeName(slug2)) return null;
|
|
1669
|
+
for (const root of getAgentResponsibilityRoots(projectAgentResponsibilitiesRoot)) {
|
|
1670
|
+
const agentResponsibility = readAgentResponsibilityFolder(root, slug2);
|
|
1671
|
+
if (agentResponsibility) return agentResponsibility;
|
|
1800
1672
|
}
|
|
1801
|
-
|
|
1802
|
-
if (repoClones.get(dir) === p) repoClones.delete(dir);
|
|
1803
|
-
});
|
|
1804
|
-
repoClones.set(dir, p);
|
|
1805
|
-
await p;
|
|
1806
|
-
return dir;
|
|
1673
|
+
return null;
|
|
1807
1674
|
}
|
|
1808
|
-
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1675
|
+
function resolveAgentResponsibilityExecution(agentResponsibility) {
|
|
1676
|
+
const agentAction = agentResponsibility.config.agentAction ?? agentResponsibility.config.agentActions?.[0] ?? (agentResponsibility.config.tickScript ? "agent-responsibility-tick-scripted" : "agent-responsibility-tick");
|
|
1677
|
+
const cliArgs = agentActionDeclaresInput(agentAction, "agentResponsibility") ? { agentResponsibility: agentResponsibility.slug } : {};
|
|
1678
|
+
return { agentAction, cliArgs };
|
|
1811
1679
|
}
|
|
1812
|
-
|
|
1813
|
-
const
|
|
1814
|
-
if (!
|
|
1815
|
-
|
|
1680
|
+
function agentActionDeclaresInput(agentAction, inputName) {
|
|
1681
|
+
const profilePath = resolveAgentAction(agentAction);
|
|
1682
|
+
if (!profilePath) return false;
|
|
1683
|
+
try {
|
|
1684
|
+
const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
|
|
1685
|
+
if (!Array.isArray(raw.inputs)) return false;
|
|
1686
|
+
return raw.inputs.some((entry) => {
|
|
1687
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
|
|
1688
|
+
const input = entry;
|
|
1689
|
+
return input.name === inputName || input.flag === `--${inputName}`;
|
|
1690
|
+
});
|
|
1691
|
+
} catch {
|
|
1692
|
+
return false;
|
|
1816
1693
|
}
|
|
1817
|
-
return dir;
|
|
1818
1694
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
resolve7();
|
|
1845
|
-
});
|
|
1846
|
-
child.on("error", reject);
|
|
1695
|
+
function isSafeName(name) {
|
|
1696
|
+
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
1697
|
+
}
|
|
1698
|
+
function listAgentActionResponsibilityActions(root, source) {
|
|
1699
|
+
if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
|
|
1700
|
+
const out = [];
|
|
1701
|
+
for (const ent of fs6.readdirSync(root, { withFileTypes: true })) {
|
|
1702
|
+
if (!ent.isDirectory() || !isSafeName(ent.name)) continue;
|
|
1703
|
+
const profilePath = path8.join(root, ent.name, AGENT_RESPONSIBILITY_PROFILE_FILE);
|
|
1704
|
+
if (!fs6.existsSync(profilePath) || !fs6.statSync(profilePath).isFile()) continue;
|
|
1705
|
+
try {
|
|
1706
|
+
const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
|
|
1707
|
+
const action = typeof raw.action === "string" && raw.action.trim() ? raw.action.trim() : "";
|
|
1708
|
+
if (!action) continue;
|
|
1709
|
+
if (!PUBLIC_AGENT_ACTION_ACTION_ROLES.has(String(raw.role))) continue;
|
|
1710
|
+
if (!PUBLIC_AGENT_ACTION_CAPABILITY_KINDS.has(String(raw.capabilityKind))) continue;
|
|
1711
|
+
if (!Array.isArray(raw.inputs)) continue;
|
|
1712
|
+
out.push({
|
|
1713
|
+
action,
|
|
1714
|
+
agentResponsibility: ent.name,
|
|
1715
|
+
agentAction: ent.name,
|
|
1716
|
+
cliArgs: {},
|
|
1717
|
+
source,
|
|
1718
|
+
describe: typeof raw.describe === "string" ? raw.describe : void 0,
|
|
1719
|
+
profilePath
|
|
1847
1720
|
});
|
|
1848
|
-
}
|
|
1721
|
+
} catch {
|
|
1722
|
+
}
|
|
1849
1723
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
// src/fetchRepoMcp.ts
|
|
1853
|
-
var fetchRepoMcp_exports = {};
|
|
1854
|
-
__export(fetchRepoMcp_exports, {
|
|
1855
|
-
buildFetchRepoMcpServer: () => buildFetchRepoMcpServer,
|
|
1856
|
-
fetchRepoToolDefinition: () => fetchRepoToolDefinition
|
|
1857
|
-
});
|
|
1858
|
-
import { createSdkMcpServer as createSdkMcpServer4, tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
1859
|
-
import { z as z3 } from "zod";
|
|
1860
|
-
function fetchRepoToolDefinition(opts) {
|
|
1861
|
-
return {
|
|
1862
|
-
name: "fetch_repo",
|
|
1863
|
-
description: DESCRIPTION3,
|
|
1864
|
-
inputSchema: INPUT_SCHEMA3,
|
|
1865
|
-
handler: async (args) => {
|
|
1866
|
-
const repo = String(args.repo ?? "").trim();
|
|
1867
|
-
try {
|
|
1868
|
-
const dir = await fetchRepo({
|
|
1869
|
-
reposRoot: opts.reposRoot,
|
|
1870
|
-
repo,
|
|
1871
|
-
repoToken: opts.repoToken
|
|
1872
|
-
});
|
|
1873
|
-
return {
|
|
1874
|
-
content: [
|
|
1875
|
-
{
|
|
1876
|
-
type: "text",
|
|
1877
|
-
text: `Cloned ${repo} \u2192 ${dir}
|
|
1878
|
-
Use Read/Grep/Glob/Bash at that absolute path to explore it. It now lives in your workspace alongside any other repos you've fetched.`
|
|
1879
|
-
}
|
|
1880
|
-
]
|
|
1881
|
-
};
|
|
1882
|
-
} catch (err) {
|
|
1883
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1884
|
-
return {
|
|
1885
|
-
content: [{ type: "text", text: `Could not fetch ${repo}: ${msg}` }],
|
|
1886
|
-
isError: true
|
|
1887
|
-
};
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
};
|
|
1724
|
+
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
1891
1725
|
}
|
|
1892
|
-
function
|
|
1893
|
-
|
|
1894
|
-
const
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1726
|
+
function listFolderAgentResponsibilityActions(root, source) {
|
|
1727
|
+
if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
|
|
1728
|
+
const out = [];
|
|
1729
|
+
for (const slug2 of listAgentResponsibilityFolderSlugs(root)) {
|
|
1730
|
+
if (!isSafeName(slug2)) continue;
|
|
1731
|
+
const agentResponsibility = readAgentResponsibilityFolder(root, slug2);
|
|
1732
|
+
if (!agentResponsibility) continue;
|
|
1733
|
+
const action = agentResponsibility.config.action ?? slug2;
|
|
1734
|
+
const { agentAction, cliArgs } = resolveAgentResponsibilityExecution(agentResponsibility);
|
|
1735
|
+
out.push({
|
|
1736
|
+
action,
|
|
1737
|
+
agentResponsibility: slug2,
|
|
1738
|
+
agentAction,
|
|
1739
|
+
cliArgs,
|
|
1740
|
+
source,
|
|
1741
|
+
describe: agentResponsibility.config.describe ?? agentResponsibility.title,
|
|
1742
|
+
capabilityKind: agentResponsibility.config.capabilityKind,
|
|
1743
|
+
profilePath: agentResponsibility.profilePath,
|
|
1744
|
+
bodyPath: agentResponsibility.bodyPath
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
1902
1748
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1749
|
+
function listBuiltinAgentResponsibilityActions(root = getBuiltinAgentResponsibilitiesRoot()) {
|
|
1750
|
+
if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
|
|
1751
|
+
const out = [];
|
|
1752
|
+
for (const slug2 of listAgentResponsibilityFolderSlugs(root)) {
|
|
1753
|
+
if (!isSafeName(slug2)) continue;
|
|
1754
|
+
const agentResponsibility = readAgentResponsibilityFolder(root, slug2);
|
|
1755
|
+
if (!agentResponsibility) continue;
|
|
1756
|
+
const action = agentResponsibility.config.action ?? slug2;
|
|
1757
|
+
const agentAction = agentResponsibility.config.agentAction ?? slug2;
|
|
1758
|
+
out.push({
|
|
1759
|
+
action,
|
|
1760
|
+
agentResponsibility: slug2,
|
|
1761
|
+
agentAction,
|
|
1762
|
+
cliArgs: {},
|
|
1763
|
+
source: "builtin",
|
|
1764
|
+
describe: agentResponsibility.config.describe ?? agentResponsibility.title,
|
|
1765
|
+
capabilityKind: agentResponsibility.config.capabilityKind,
|
|
1766
|
+
profilePath: agentResponsibility.profilePath,
|
|
1767
|
+
bodyPath: agentResponsibility.bodyPath
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
1771
|
+
}
|
|
1772
|
+
function getProfileInputs(name, roots = getAgentActionRoots()) {
|
|
1773
|
+
const profilePath = resolveAgentAction(name, roots);
|
|
1774
|
+
if (!profilePath) return null;
|
|
1775
|
+
try {
|
|
1776
|
+
const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
|
|
1777
|
+
if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
|
|
1778
|
+
return raw.inputs;
|
|
1779
|
+
} catch {
|
|
1780
|
+
return null;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
function parseGenericFlags(argv) {
|
|
1784
|
+
const args = {};
|
|
1785
|
+
const positional = [];
|
|
1786
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1787
|
+
const arg = argv[i];
|
|
1788
|
+
if (!arg.startsWith("--")) {
|
|
1789
|
+
positional.push(arg);
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
const key = arg.slice(2);
|
|
1793
|
+
const next = argv[i + 1];
|
|
1794
|
+
let value = true;
|
|
1795
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
1796
|
+
value = next;
|
|
1797
|
+
i++;
|
|
1798
|
+
}
|
|
1799
|
+
args[key] = value;
|
|
1800
|
+
if (key.includes("-")) {
|
|
1801
|
+
const camel = key.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1802
|
+
if (camel !== key && args[camel] === void 0) args[camel] = value;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
if (positional.length > 0) args._ = positional;
|
|
1806
|
+
return args;
|
|
1807
|
+
}
|
|
1808
|
+
var PUBLIC_AGENT_ACTION_ACTION_ROLES, PUBLIC_AGENT_ACTION_CAPABILITY_KINDS;
|
|
1809
|
+
var init_registry = __esm({
|
|
1810
|
+
"src/registry.ts"() {
|
|
1906
1811
|
"use strict";
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
};
|
|
1812
|
+
init_agent_responsibilityFolders();
|
|
1813
|
+
init_companyStore();
|
|
1814
|
+
PUBLIC_AGENT_ACTION_ACTION_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
|
|
1815
|
+
PUBLIC_AGENT_ACTION_CAPABILITY_KINDS = /* @__PURE__ */ new Set(["observe", "act", "verify"]);
|
|
1912
1816
|
}
|
|
1913
1817
|
});
|
|
1914
1818
|
|
|
1915
|
-
// src/agent.ts
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1819
|
+
// src/agent-responsibilityMcp.ts
|
|
1820
|
+
var agent_responsibilityMcp_exports = {};
|
|
1821
|
+
__export(agent_responsibilityMcp_exports, {
|
|
1822
|
+
AGENT_RESPONSIBILITY_MCP_TOOL_NAMES: () => AGENT_RESPONSIBILITY_MCP_TOOL_NAMES,
|
|
1823
|
+
agentResponsibilityToolDefinitions: () => agentResponsibilityToolDefinitions,
|
|
1824
|
+
buildAgentResponsibilityMcpServer: () => buildAgentResponsibilityMcpServer,
|
|
1825
|
+
dispatchWorkflow: () => dispatchWorkflow,
|
|
1826
|
+
ensureComment: () => ensureComment,
|
|
1827
|
+
ensureIssue: () => ensureIssue,
|
|
1828
|
+
isDispatchGated: () => isDispatchGated,
|
|
1829
|
+
parseAgentResponsibilityTrustMode: () => parseAgentResponsibilityTrustMode,
|
|
1830
|
+
readAgentResponsibilityTrustMode: () => readAgentResponsibilityTrustMode,
|
|
1831
|
+
readCheckRuns: () => readCheckRuns,
|
|
1832
|
+
readThread: () => readThread
|
|
1833
|
+
});
|
|
1834
|
+
import { createSdkMcpServer as createSdkMcpServer3, tool as tool3 } from "@anthropic-ai/claude-agent-sdk";
|
|
1835
|
+
import { z as z2 } from "zod";
|
|
1836
|
+
function summarizeCiStatus(rollup) {
|
|
1837
|
+
if (!Array.isArray(rollup) || rollup.length === 0) return "UNKNOWN";
|
|
1838
|
+
let hasRunning = false;
|
|
1839
|
+
for (const check of rollup) {
|
|
1840
|
+
const status = String(check.status ?? "").toUpperCase();
|
|
1841
|
+
const conclusion = String(check.conclusion ?? "").toUpperCase();
|
|
1842
|
+
if (FAIL_CONCLUSIONS.has(conclusion)) return "FAILING";
|
|
1843
|
+
if (!conclusion && RUNNING_STATUSES.has(status)) hasRunning = true;
|
|
1844
|
+
}
|
|
1845
|
+
return hasRunning ? "RUNNING" : "PASSING";
|
|
1932
1846
|
}
|
|
1933
|
-
function
|
|
1934
|
-
|
|
1935
|
-
|
|
1847
|
+
function computeBehindBy(repoSlug, base, head) {
|
|
1848
|
+
try {
|
|
1849
|
+
const raw = gh(["api", `repos/${repoSlug}/compare/${base}...${head}`, "--jq", ".behind_by"]);
|
|
1850
|
+
const n = Number(raw.trim());
|
|
1851
|
+
return Number.isFinite(n) ? n : -1;
|
|
1852
|
+
} catch {
|
|
1853
|
+
return -1;
|
|
1936
1854
|
}
|
|
1937
|
-
const envSec = Number(process.env.KODY_TURN_TIMEOUT_SEC);
|
|
1938
|
-
if (Number.isFinite(envSec) && envSec > 0) return Math.floor(envSec * 1e3);
|
|
1939
|
-
if (Number.isFinite(envSec) && envSec <= 0) return 0;
|
|
1940
|
-
return DEFAULT_TURN_TIMEOUT_MS;
|
|
1941
1855
|
}
|
|
1942
|
-
function
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1856
|
+
function listRepairCandidates(repoSlug) {
|
|
1857
|
+
const raw = gh([
|
|
1858
|
+
"pr",
|
|
1859
|
+
"list",
|
|
1860
|
+
"--state",
|
|
1861
|
+
"open",
|
|
1862
|
+
"--limit",
|
|
1863
|
+
"100",
|
|
1864
|
+
"--json",
|
|
1865
|
+
"number,title,headRefName,headRefOid,baseRefName,isDraft,mergeable,statusCheckRollup,updatedAt"
|
|
1866
|
+
]);
|
|
1867
|
+
const prs = JSON.parse(raw);
|
|
1868
|
+
return prs.filter((p) => !p.isDraft).map((p) => {
|
|
1869
|
+
const ciStatus = summarizeCiStatus(p.statusCheckRollup);
|
|
1870
|
+
const mergeable = String(p.mergeable ?? "UNKNOWN").toUpperCase();
|
|
1871
|
+
const behindBy = mergeable === "CONFLICTING" || ciStatus === "FAILING" ? 0 : computeBehindBy(repoSlug, p.baseRefName, p.headRefName);
|
|
1872
|
+
return {
|
|
1873
|
+
number: p.number,
|
|
1874
|
+
title: p.title,
|
|
1875
|
+
headSha: p.headRefOid,
|
|
1876
|
+
baseRef: p.baseRefName,
|
|
1877
|
+
isDraft: false,
|
|
1878
|
+
mergeable,
|
|
1879
|
+
ciStatus,
|
|
1880
|
+
behindBy,
|
|
1881
|
+
updatedAt: p.updatedAt
|
|
1882
|
+
};
|
|
1883
|
+
});
|
|
1947
1884
|
}
|
|
1948
|
-
function
|
|
1949
|
-
|
|
1950
|
-
if (MUTATING_FILE_TOOLS.has(name)) return true;
|
|
1951
|
-
if (name === "Bash") return BASH_WRITE_VERB.test(String(input?.command ?? ""));
|
|
1952
|
-
return false;
|
|
1885
|
+
function dispatchVerb(workflowFile, repoSlug, agentResponsibility, prNumber) {
|
|
1886
|
+
return dispatchWorkflow(workflowFile, agentResponsibility, prNumber, repoSlug);
|
|
1953
1887
|
}
|
|
1954
|
-
function
|
|
1955
|
-
const
|
|
1956
|
-
const
|
|
1957
|
-
|
|
1958
|
-
|
|
1888
|
+
function postRecommendation(prNumber, mention, message, agentResponsibilitySlug) {
|
|
1889
|
+
const mentioned = mention ? `${mention} ${message}` : message;
|
|
1890
|
+
const body = agentResponsibilitySlug ? `${mentioned}
|
|
1891
|
+
|
|
1892
|
+
<!-- kody-agentResponsibility: ${agentResponsibilitySlug} -->` : mentioned;
|
|
1959
1893
|
try {
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
}
|
|
1964
|
-
} catch {
|
|
1894
|
+
gh(["pr", "comment", String(prNumber), "--body", body]);
|
|
1895
|
+
return { ok: true };
|
|
1896
|
+
} catch (err) {
|
|
1897
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1965
1898
|
}
|
|
1966
|
-
return out;
|
|
1967
1899
|
}
|
|
1968
|
-
|
|
1969
|
-
const
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1900
|
+
function readLedger(label) {
|
|
1901
|
+
const startTag = `<!-- ${label}:start -->`;
|
|
1902
|
+
const endTag = `<!-- ${label}:end -->`;
|
|
1903
|
+
try {
|
|
1904
|
+
const raw = gh(["issue", "list", "--state", "open", "--label", label, "--limit", "5", "--json", "number,body"]);
|
|
1905
|
+
const issues = JSON.parse(raw);
|
|
1906
|
+
if (issues.length === 0) return { found: false, payload: null };
|
|
1907
|
+
const issue = issues.sort((a, b) => a.number - b.number)[0];
|
|
1908
|
+
const body = issue?.body ?? "";
|
|
1909
|
+
const startIdx = body.indexOf(startTag);
|
|
1910
|
+
const endIdx = body.indexOf(endTag);
|
|
1911
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
1912
|
+
return { found: true, issueNumber: issue?.number, payload: null };
|
|
1913
|
+
}
|
|
1914
|
+
const between = body.slice(startIdx + startTag.length, endIdx);
|
|
1915
|
+
const fenceMatch = between.match(/```json\s*([\s\S]*?)```/);
|
|
1916
|
+
if (!fenceMatch) return { found: true, issueNumber: issue?.number, payload: null };
|
|
1917
|
+
try {
|
|
1918
|
+
return { found: true, issueNumber: issue?.number, payload: JSON.parse(fenceMatch[1]) };
|
|
1919
|
+
} catch {
|
|
1920
|
+
return { found: true, issueNumber: issue?.number, payload: null };
|
|
1921
|
+
}
|
|
1922
|
+
} catch (err) {
|
|
1923
|
+
return { found: false, payload: { error: err instanceof Error ? err.message : String(err) } };
|
|
1989
1924
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
1925
|
+
}
|
|
1926
|
+
function parseAgentResponsibilityTrustMode(rawJson, agentResponsibilitySlug) {
|
|
1927
|
+
try {
|
|
1928
|
+
const parsed = JSON.parse(rawJson);
|
|
1929
|
+
return parsed?.agentResponsibilities?.[agentResponsibilitySlug]?.mode === "auto" ? "auto" : "ask";
|
|
1930
|
+
} catch {
|
|
1931
|
+
return "ask";
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
function defaultStateForRepoSlug(repoSlug) {
|
|
1935
|
+
const [owner, repo] = repoSlug.split("/");
|
|
1936
|
+
return { repo: `${owner}/kody-state`, path: repo ?? repoSlug };
|
|
1937
|
+
}
|
|
1938
|
+
function readAgentResponsibilityTrustMode(stateOrRepoSlug, repoSlugOrAgentResponsibilitySlug, maybeAgentResponsibilitySlug) {
|
|
1939
|
+
const state = typeof stateOrRepoSlug === "string" ? void 0 : stateOrRepoSlug;
|
|
1940
|
+
const repoSlug = typeof stateOrRepoSlug === "string" ? stateOrRepoSlug : repoSlugOrAgentResponsibilitySlug ?? "";
|
|
1941
|
+
const agentResponsibilitySlug = typeof stateOrRepoSlug === "string" ? repoSlugOrAgentResponsibilitySlug : maybeAgentResponsibilitySlug;
|
|
1942
|
+
if (!agentResponsibilitySlug) return "ask";
|
|
1943
|
+
try {
|
|
1944
|
+
const loaded = readStateText({ state: state ?? defaultStateForRepoSlug(repoSlug) }, void 0, TRUST_FILE_PATH);
|
|
1945
|
+
return loaded ? parseAgentResponsibilityTrustMode(loaded.content, agentResponsibilitySlug) : "ask";
|
|
1946
|
+
} catch {
|
|
1947
|
+
return "ask";
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
function readThread(repoSlug, number, limit = 10) {
|
|
1951
|
+
const meta = JSON.parse(gh(["api", `repos/${repoSlug}/issues/${number}`]));
|
|
1952
|
+
const rawComments = JSON.parse(gh(["api", `repos/${repoSlug}/issues/${number}/comments?per_page=100`]));
|
|
1953
|
+
const comments = rawComments.slice(-Math.max(1, limit)).map((c) => ({
|
|
1954
|
+
author: c.user?.login ?? "?",
|
|
1955
|
+
createdAt: c.created_at ?? "",
|
|
1956
|
+
body: (c.body ?? "").slice(0, THREAD_BODY_MAX)
|
|
1957
|
+
}));
|
|
1958
|
+
return {
|
|
1959
|
+
number,
|
|
1960
|
+
title: meta.title ?? "",
|
|
1961
|
+
state: meta.state ?? "",
|
|
1962
|
+
labels: (meta.labels ?? []).map((l) => l.name ?? "").filter(Boolean),
|
|
1963
|
+
comments
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
function readCheckRuns(repoSlug, ref, ignoreNames) {
|
|
1967
|
+
const ghOptions = { preferRepoToken: true };
|
|
1968
|
+
const sha = gh(["api", `repos/${repoSlug}/commits/${ref}`, "--jq", ".sha"], ghOptions).trim();
|
|
1969
|
+
const raw = gh(
|
|
1970
|
+
[
|
|
1971
|
+
"api",
|
|
1972
|
+
`repos/${repoSlug}/commits/${sha}/check-runs`,
|
|
1973
|
+
"--paginate",
|
|
1974
|
+
"--jq",
|
|
1975
|
+
".check_runs[] | {name, status, conclusion, details_url}"
|
|
1976
|
+
],
|
|
1977
|
+
ghOptions
|
|
1978
|
+
);
|
|
1979
|
+
const ignore = new Set(ignoreNames.map((n) => n.toLowerCase()));
|
|
1980
|
+
const checks = raw.split("\n").map((l) => l.trim()).filter(Boolean).map((l) => JSON.parse(l)).filter((c) => !ignore.has(String(c.name).toLowerCase()));
|
|
1981
|
+
const failing = checks.filter((c) => CHECK_FAIL_CONCLUSIONS.has(String(c.conclusion ?? "").toUpperCase())).map((c) => ({ name: c.name, conclusion: String(c.conclusion), detailsUrl: c.details_url }));
|
|
1982
|
+
const pending = checks.filter((c) => String(c.status).toLowerCase() !== "completed").map((c) => ({ name: c.name, status: c.status }));
|
|
1983
|
+
const state = failing.length > 0 ? "RED" : pending.length > 0 ? "PENDING" : "GREEN";
|
|
1984
|
+
return { sha, state, failing, pending };
|
|
1985
|
+
}
|
|
1986
|
+
function ensureIssue(repoSlug, key, title, body) {
|
|
1987
|
+
const marker = trackMarker(key);
|
|
1988
|
+
try {
|
|
1989
|
+
const raw = gh(["issue", "list", "-R", repoSlug, "--state", "open", "--limit", "100", "--json", "number,body"]);
|
|
1990
|
+
const issues = JSON.parse(raw);
|
|
1991
|
+
const existing = issues.find((i) => (i.body ?? "").includes(marker));
|
|
1992
|
+
if (existing) return { created: false, number: existing.number };
|
|
1993
|
+
const url = gh(["issue", "create", "-R", repoSlug, "--title", title, "--body-file", "-"], {
|
|
1994
|
+
input: `${body}
|
|
1995
|
+
|
|
1996
|
+
${marker}`
|
|
2006
1997
|
});
|
|
2007
|
-
const
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
1998
|
+
const m = url.trim().match(/\/(\d+)\s*$/);
|
|
1999
|
+
if (!m) return { error: `issue created but could not parse its number from: ${url}` };
|
|
2000
|
+
return { created: true, number: Number(m[1]) };
|
|
2001
|
+
} catch (err) {
|
|
2002
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
function ensureComment(repoSlug, issue, key, body) {
|
|
2006
|
+
const marker = commentMarker(key);
|
|
2007
|
+
try {
|
|
2008
|
+
const raw = gh(["issue", "view", String(issue), "-R", repoSlug, "--json", "comments"]);
|
|
2009
|
+
const parsed = JSON.parse(raw);
|
|
2010
|
+
const already = (parsed.comments ?? []).some((c) => (c.body ?? "").includes(marker));
|
|
2011
|
+
if (already) return { posted: false };
|
|
2012
|
+
gh(["issue", "comment", String(issue), "-R", repoSlug, "--body-file", "-"], { input: `${body}
|
|
2013
|
+
|
|
2014
|
+
${marker}` });
|
|
2015
|
+
return { posted: true };
|
|
2016
|
+
} catch (err) {
|
|
2017
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
function dispatchWorkflow(workflowFile, agentResponsibility, issueNumber, repoSlug) {
|
|
2021
|
+
const expected = expectedDispatchTarget(agentResponsibility);
|
|
2022
|
+
if (repoSlug && expected) {
|
|
2023
|
+
const target = readDispatchTargetKind(repoSlug, issueNumber);
|
|
2024
|
+
if (!target.ok) return target;
|
|
2025
|
+
if (expected === "issue" && target.kind === "pr") {
|
|
2026
|
+
return {
|
|
2027
|
+
ok: false,
|
|
2028
|
+
error: `refusing to dispatch ${agentResponsibility} on PR #${issueNumber}; dispatch the source issue or use a PR action`
|
|
2026
2029
|
};
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2030
|
+
}
|
|
2031
|
+
if (expected === "pr" && target.kind === "issue") {
|
|
2032
|
+
return {
|
|
2033
|
+
ok: false,
|
|
2034
|
+
error: `refusing to dispatch ${agentResponsibility} on issue #${issueNumber}; expected a PR target`
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
try {
|
|
2039
|
+
gh([
|
|
2040
|
+
"workflow",
|
|
2041
|
+
"run",
|
|
2042
|
+
workflowFile,
|
|
2043
|
+
"-f",
|
|
2044
|
+
`agentResponsibility=${agentResponsibility}`,
|
|
2045
|
+
"-f",
|
|
2046
|
+
`issue_number=${issueNumber}`
|
|
2047
|
+
]);
|
|
2048
|
+
return { ok: true };
|
|
2049
|
+
} catch (err) {
|
|
2050
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
function expectedDispatchTarget(agentResponsibility) {
|
|
2054
|
+
const route = resolveAgentResponsibilityAction(agentResponsibility);
|
|
2055
|
+
if (!route) return null;
|
|
2056
|
+
const inputs = getProfileInputs(route.agentAction);
|
|
2057
|
+
const numeric = inputs?.find((input) => input.type === "int" && input.required);
|
|
2058
|
+
if (numeric?.name === "issue") return "issue";
|
|
2059
|
+
if (numeric?.name === "pr") return "pr";
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
function readDispatchTargetKind(repoSlug, issueNumber) {
|
|
2063
|
+
try {
|
|
2064
|
+
const raw = gh(["api", `repos/${repoSlug}/issues/${issueNumber}`]);
|
|
2065
|
+
const parsed = JSON.parse(raw);
|
|
2066
|
+
return { ok: true, kind: parsed.pull_request ? "pr" : "issue" };
|
|
2067
|
+
} catch (err) {
|
|
2068
|
+
return {
|
|
2069
|
+
ok: false,
|
|
2070
|
+
error: `could not verify target #${issueNumber}: ${err instanceof Error ? err.message : String(err)}`
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
function isDispatchGated(agentResponsibility, mode) {
|
|
2075
|
+
if (mode === "auto") return false;
|
|
2076
|
+
if (agentResponsibility && GATE_EXEMPT_DUTIES.has(agentResponsibility)) return false;
|
|
2077
|
+
return true;
|
|
2078
|
+
}
|
|
2079
|
+
function trustRefusal(agentResponsibilitySlug) {
|
|
2080
|
+
return `Not dispatched: agentResponsibility \`${agentResponsibilitySlug ?? "?"}\` 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 agentResponsibility act on its own, grant it Auto on the dashboard Trust page.`;
|
|
2081
|
+
}
|
|
2082
|
+
function agentResponsibilityToolDefinitions(opts) {
|
|
2083
|
+
const workflowFile = opts.workflowFile ?? "kody.yml";
|
|
2084
|
+
const listTool = {
|
|
2085
|
+
name: "list_prs_to_repair",
|
|
2086
|
+
description: "Return open non-draft PRs with the signals you need to pick a repair: number, title, headSha, baseRef, mergeable (CONFLICTING/MERGEABLE/UNKNOWN), ciStatus (PASSING/FAILING/RUNNING/UNKNOWN), behindBy (commits behind base; 0 for PRs that already match conflicts or CI-failure rules), updatedAt. Drafts are excluded. One call returns everything \u2014 do not iterate or paginate.",
|
|
2087
|
+
inputSchema: {},
|
|
2088
|
+
handler: async () => {
|
|
2089
|
+
const candidates = listRepairCandidates(opts.repoSlug);
|
|
2090
|
+
return {
|
|
2091
|
+
content: [
|
|
2092
|
+
{
|
|
2093
|
+
type: "text",
|
|
2094
|
+
text: JSON.stringify({ prs: candidates }, null, 2)
|
|
2095
|
+
}
|
|
2096
|
+
]
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
const makeDispatch = (verb, describe) => ({
|
|
2101
|
+
name: `${verb.replace("-", "_")}_pr`,
|
|
2102
|
+
description: describe,
|
|
2103
|
+
inputSchema: {
|
|
2104
|
+
pr: z2.number().int().positive().describe("PR number to repair.")
|
|
2105
|
+
},
|
|
2106
|
+
handler: async (args) => {
|
|
2107
|
+
const pr = Number(args.pr);
|
|
2108
|
+
if (isDispatchGated(verb, readAgentResponsibilityTrustMode(opts.state, opts.repoSlug, opts.agentResponsibilitySlug))) {
|
|
2109
|
+
return { content: [{ type: "text", text: trustRefusal(opts.agentResponsibilitySlug) }] };
|
|
2051
2110
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
throw new Error(
|
|
2056
|
-
"enableAgentResponsibilityTool requires dutyRepoSlug (owner/name) \u2014 set kody.config.json github.{owner,repo} or GITHUB_REPOSITORY env var"
|
|
2057
|
-
);
|
|
2058
|
-
}
|
|
2059
|
-
const dutyHandle = buildAgentResponsibilityMcpServer2({
|
|
2060
|
-
repoSlug: opts.dutyRepoSlug,
|
|
2061
|
-
state: opts.agentResponsibilityState,
|
|
2062
|
-
operatorMention: opts.agentResponsibilityOperatorMention ?? "",
|
|
2063
|
-
...opts.agentResponsibilitySlug ? { agentResponsibilitySlug: opts.agentResponsibilitySlug } : {}
|
|
2064
|
-
});
|
|
2065
|
-
mcpEntries.push(["kody-agentResponsibility", dutyHandle.server]);
|
|
2066
|
-
}
|
|
2067
|
-
if (opts.enableFetchRepoTool && opts.reposRoot) {
|
|
2068
|
-
const { buildFetchRepoMcpServer: buildFetchRepoMcpServer2 } = await Promise.resolve().then(() => (init_fetchRepoMcp(), fetchRepoMcp_exports));
|
|
2069
|
-
const fetchServer = buildFetchRepoMcpServer2({
|
|
2070
|
-
reposRoot: opts.reposRoot,
|
|
2071
|
-
repoToken: opts.repoToken
|
|
2072
|
-
});
|
|
2073
|
-
mcpEntries.push(["kody-fetch-repo", fetchServer]);
|
|
2074
|
-
queryOptions.allowedTools.push("mcp__kody-fetch-repo__fetch_repo");
|
|
2075
|
-
queryOptions.additionalDirectories = [opts.reposRoot];
|
|
2076
|
-
}
|
|
2077
|
-
if (mcpEntries.length > 0) {
|
|
2078
|
-
queryOptions.mcpServers = Object.fromEntries(mcpEntries);
|
|
2079
|
-
}
|
|
2080
|
-
if (opts.pluginPaths && opts.pluginPaths.length > 0) {
|
|
2081
|
-
queryOptions.plugins = opts.pluginPaths.map((p) => ({ type: "local", path: p }));
|
|
2082
|
-
}
|
|
2083
|
-
if (opts.agents && Object.keys(opts.agents).length > 0) {
|
|
2084
|
-
queryOptions.agents = opts.agents;
|
|
2085
|
-
}
|
|
2086
|
-
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
2087
|
-
queryOptions.maxTurns = opts.maxTurns;
|
|
2088
|
-
}
|
|
2089
|
-
if (opts.reasoningEffort !== void 0 && opts.reasoningEffort !== null) {
|
|
2090
|
-
if (opts.reasoningEffort === "off") {
|
|
2091
|
-
} else {
|
|
2092
|
-
const budget = REASONING_BUDGETS[opts.reasoningEffort];
|
|
2093
|
-
if (budget) queryOptions.maxThinkingTokens = budget;
|
|
2094
|
-
}
|
|
2095
|
-
} else if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
|
|
2096
|
-
queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
|
|
2097
|
-
}
|
|
2098
|
-
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
2099
|
-
const systemPrompt = {
|
|
2100
|
-
type: "preset",
|
|
2101
|
-
preset: "claude_code",
|
|
2102
|
-
append: opts.systemPromptAppend
|
|
2103
|
-
};
|
|
2104
|
-
if (opts.cacheable) systemPrompt.excludeDynamicSections = true;
|
|
2105
|
-
queryOptions.systemPrompt = systemPrompt;
|
|
2106
|
-
} else if (opts.cacheable) {
|
|
2107
|
-
queryOptions.systemPrompt = {
|
|
2108
|
-
type: "preset",
|
|
2109
|
-
preset: "claude_code",
|
|
2110
|
-
excludeDynamicSections: true
|
|
2111
|
-
};
|
|
2112
|
-
}
|
|
2113
|
-
queryOptions.settingSources = opts.settingSources ?? ["project", "local"];
|
|
2114
|
-
const stableBinary = ensureStableClaudeBinary();
|
|
2115
|
-
if (stableBinary) {
|
|
2116
|
-
queryOptions.pathToClaudeCodeExecutable = stableBinary;
|
|
2117
|
-
}
|
|
2118
|
-
const result = query({
|
|
2119
|
-
prompt: opts.prompt,
|
|
2120
|
-
// biome-ignore lint/suspicious/noExplicitAny: SDK options type is narrow; mcpServers is runtime-passthrough.
|
|
2121
|
-
options: queryOptions
|
|
2122
|
-
});
|
|
2123
|
-
const iterator = typeof result[Symbol.asyncIterator] === "function" ? result[Symbol.asyncIterator]() : result;
|
|
2124
|
-
while (true) {
|
|
2125
|
-
const nextPromise = iterator.next();
|
|
2126
|
-
let timedOut = false;
|
|
2127
|
-
let timer;
|
|
2128
|
-
let next;
|
|
2129
|
-
if (turnTimeoutMs > 0) {
|
|
2130
|
-
const timeoutPromise = new Promise((resolve7) => {
|
|
2131
|
-
timer = setTimeout(() => {
|
|
2132
|
-
timedOut = true;
|
|
2133
|
-
resolve7({ done: true, value: void 0 });
|
|
2134
|
-
}, turnTimeoutMs);
|
|
2135
|
-
});
|
|
2136
|
-
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
2137
|
-
if (timer) clearTimeout(timer);
|
|
2138
|
-
} else {
|
|
2139
|
-
next = await nextPromise;
|
|
2140
|
-
}
|
|
2141
|
-
if (timedOut) {
|
|
2142
|
-
outcome = "failed";
|
|
2143
|
-
outcomeKind = "stalled";
|
|
2144
|
-
errorMessage = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
|
|
2145
|
-
if (typeof iterator.return === "function") {
|
|
2146
|
-
try {
|
|
2147
|
-
await iterator.return(void 0);
|
|
2148
|
-
} catch {
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
break;
|
|
2152
|
-
}
|
|
2153
|
-
if (next.done) break;
|
|
2154
|
-
const msg = next.value;
|
|
2155
|
-
messageCount++;
|
|
2156
|
-
if (!ndjsonWriteFailed) {
|
|
2157
|
-
try {
|
|
2158
|
-
fullLog.write(`${JSON.stringify(msg)}
|
|
2159
|
-
`);
|
|
2160
|
-
} catch (e) {
|
|
2161
|
-
ndjsonWriteFailed = true;
|
|
2162
|
-
ndjsonWriteError = e instanceof Error ? e.message : String(e);
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
|
|
2166
|
-
if (line) {
|
|
2167
|
-
if (isClaudeLoginRequiredText(line)) sawLoginRequired = true;
|
|
2168
|
-
process.stdout.write(`${line}
|
|
2169
|
-
`);
|
|
2170
|
-
}
|
|
2171
|
-
const m = msg;
|
|
2172
|
-
if (opts.onProgress) {
|
|
2173
|
-
const blocks = m.message?.content ?? [];
|
|
2174
|
-
for (const block of blocks) {
|
|
2175
|
-
try {
|
|
2176
|
-
if (block.type === "thinking") {
|
|
2177
|
-
const t = block.thinking;
|
|
2178
|
-
if (typeof t === "string" && t.length > 0) {
|
|
2179
|
-
await opts.onProgress({ kind: "thinking", thinking: t });
|
|
2180
|
-
}
|
|
2181
|
-
} else if (block.type === "tool_use") {
|
|
2182
|
-
const b = block;
|
|
2183
|
-
await opts.onProgress({
|
|
2184
|
-
kind: "tool_use",
|
|
2185
|
-
name: b.name ?? "tool",
|
|
2186
|
-
input: b.input,
|
|
2187
|
-
id: b.id
|
|
2188
|
-
});
|
|
2189
|
-
} else if (block.type === "tool_result") {
|
|
2190
|
-
const b = block;
|
|
2191
|
-
const content = typeof b.content === "string" ? b.content : (() => {
|
|
2192
|
-
try {
|
|
2193
|
-
return JSON.stringify(b.content);
|
|
2194
|
-
} catch {
|
|
2195
|
-
return "";
|
|
2196
|
-
}
|
|
2197
|
-
})();
|
|
2198
|
-
await opts.onProgress({
|
|
2199
|
-
kind: "tool_result",
|
|
2200
|
-
toolUseId: b.tool_use_id,
|
|
2201
|
-
content,
|
|
2202
|
-
isError: b.is_error
|
|
2203
|
-
});
|
|
2204
|
-
} else if (block.type === "text") {
|
|
2205
|
-
const b = block;
|
|
2206
|
-
if (typeof b.text === "string" && b.text.length > 0) {
|
|
2207
|
-
await opts.onProgress({ kind: "text", text: b.text });
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
} catch {
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
}
|
|
2214
|
-
const usage = m.usage;
|
|
2215
|
-
if (usage && typeof usage === "object") {
|
|
2216
|
-
const i = Number(usage.input_tokens ?? 0);
|
|
2217
|
-
const o = Number(usage.output_tokens ?? 0);
|
|
2218
|
-
const cr = Number(usage.cache_read_input_tokens ?? 0);
|
|
2219
|
-
const cc = Number(usage.cache_creation_input_tokens ?? 0);
|
|
2220
|
-
if (Number.isFinite(i)) tokens.input += i;
|
|
2221
|
-
if (Number.isFinite(o)) tokens.output += o;
|
|
2222
|
-
if (Number.isFinite(cr)) tokens.cacheRead += cr;
|
|
2223
|
-
if (Number.isFinite(cc)) tokens.cacheCreate += cc;
|
|
2224
|
-
}
|
|
2225
|
-
if (!sawMutatingTool) {
|
|
2226
|
-
const blocks = m.message?.content ?? [];
|
|
2227
|
-
for (const block of blocks) {
|
|
2228
|
-
if (block.type === "tool_use") {
|
|
2229
|
-
const b = block;
|
|
2230
|
-
if (toolMayMutate(b.name, b.input)) {
|
|
2231
|
-
sawMutatingTool = true;
|
|
2232
|
-
break;
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
if (m.type === "result") {
|
|
2238
|
-
if (m.subtype === "success") {
|
|
2239
|
-
outcome = "completed";
|
|
2240
|
-
outcomeKind = "ok";
|
|
2241
|
-
sawTerminalSuccess = true;
|
|
2242
|
-
const text = (typeof m.result === "string" ? m.result : "").trim();
|
|
2243
|
-
if (isClaudeLoginRequiredText(text)) sawLoginRequired = true;
|
|
2244
|
-
if (text) resultTexts.push(text);
|
|
2245
|
-
} else {
|
|
2246
|
-
outcome = "failed";
|
|
2247
|
-
outcomeKind = classifySubtype(m.subtype);
|
|
2248
|
-
errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
} catch (e) {
|
|
2253
|
-
if (sawTerminalSuccess) {
|
|
2254
|
-
errorMessage = e instanceof Error ? e.message : String(e);
|
|
2255
|
-
} else {
|
|
2256
|
-
outcome = "failed";
|
|
2257
|
-
outcomeKind = "model_error";
|
|
2258
|
-
errorMessage = e instanceof Error ? e.message : String(e);
|
|
2259
|
-
}
|
|
2260
|
-
} finally {
|
|
2261
|
-
try {
|
|
2262
|
-
fullLog.end();
|
|
2263
|
-
} catch {
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
if (ndjsonWriteFailed) {
|
|
2267
|
-
process.stderr.write(
|
|
2268
|
-
`[kody agent] NDJSON write failed (post-mortem may be incomplete): ${ndjsonWriteError ?? "unknown error"}
|
|
2269
|
-
`
|
|
2270
|
-
);
|
|
2111
|
+
const result = dispatchVerb(workflowFile, opts.repoSlug, verb, pr);
|
|
2112
|
+
const text = result.ok ? `Dispatched \`${verb}\` on PR #${pr}. The repair runs in its own workflow_dispatch \u2014 wait for the next tick to see the new headSha.` : `Dispatch failed for \`${verb}\` on PR #${pr}: ${result.error}`;
|
|
2113
|
+
return { content: [{ type: "text", text }] };
|
|
2271
2114
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2115
|
+
});
|
|
2116
|
+
const syncTool = makeDispatch(
|
|
2117
|
+
"sync",
|
|
2118
|
+
"Bring a stale PR up to date with its base branch (merges base \u2192 head + pushes). Use when behindBy > 10 AND mergeable !== CONFLICTING AND ciStatus !== FAILING. Returns immediately \u2014 the actual merge runs in a separate workflow."
|
|
2119
|
+
);
|
|
2120
|
+
const fixCiTool = makeDispatch(
|
|
2121
|
+
"fix-ci",
|
|
2122
|
+
"Repair a PR whose CI is failing. Use when ciStatus === FAILING. The repair runs in a separate workflow."
|
|
2123
|
+
);
|
|
2124
|
+
const resolveTool = makeDispatch(
|
|
2125
|
+
"resolve",
|
|
2126
|
+
"Resolve merge conflicts on a PR. Use when mergeable === CONFLICTING. The repair runs in a separate workflow."
|
|
2127
|
+
);
|
|
2128
|
+
const recommendTool = {
|
|
2129
|
+
name: "recommend_to_operator",
|
|
2130
|
+
description: "Post ONE comment on a PR with the operator @-mention prepended. Use this when a agentResponsibility 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.",
|
|
2131
|
+
inputSchema: {
|
|
2132
|
+
pr: z2.number().int().positive().describe("PR number to comment on."),
|
|
2133
|
+
body: z2.string().min(1).describe("Comment body (markdown). Do not include the operator mention \u2014 the engine prepends it.")
|
|
2134
|
+
},
|
|
2135
|
+
handler: async (args) => {
|
|
2136
|
+
const pr = Number(args.pr);
|
|
2137
|
+
const body = String(args.body ?? "");
|
|
2138
|
+
const result = postRecommendation(pr, opts.operatorMention, body, opts.agentResponsibilitySlug);
|
|
2139
|
+
const text = result.ok ? `Recommendation posted on PR #${pr}.` : `Recommendation failed on PR #${pr}: ${result.error}`;
|
|
2140
|
+
return { content: [{ type: "text", text }] };
|
|
2277
2141
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2142
|
+
};
|
|
2143
|
+
const ledgerTool = {
|
|
2144
|
+
name: "read_ledger",
|
|
2145
|
+
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.",
|
|
2146
|
+
inputSchema: {
|
|
2147
|
+
label: z2.string().min(1).describe("GitHub issue label that identifies the manifest issue.")
|
|
2148
|
+
},
|
|
2149
|
+
handler: async (args) => {
|
|
2150
|
+
const label = String(args.label ?? "");
|
|
2151
|
+
const result = readLedger(label);
|
|
2152
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2287
2153
|
}
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2154
|
+
};
|
|
2155
|
+
const checkRunsTool = {
|
|
2156
|
+
name: "read_check_runs",
|
|
2157
|
+
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/agent-responsibility-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.",
|
|
2158
|
+
inputSchema: {
|
|
2159
|
+
ref: z2.string().min(1).describe("Branch name or commit SHA to read CI for (e.g. 'dev')."),
|
|
2160
|
+
ignoreNames: z2.array(z2.string()).optional().describe("Check names to exclude (default: Kody's own job names).")
|
|
2161
|
+
},
|
|
2162
|
+
handler: async (args) => {
|
|
2163
|
+
const ref = String(args.ref ?? "");
|
|
2164
|
+
const ignoreNames = Array.isArray(args.ignoreNames) ? args.ignoreNames : DEFAULT_IGNORE_CHECKS;
|
|
2165
|
+
const result = readCheckRuns(opts.repoSlug, ref, ignoreNames);
|
|
2166
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
const readThreadTool = {
|
|
2170
|
+
name: "read_thread",
|
|
2171
|
+
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.",
|
|
2172
|
+
inputSchema: {
|
|
2173
|
+
number: z2.number().int().positive().describe("Issue or PR number to read."),
|
|
2174
|
+
limit: z2.number().int().positive().optional().describe("Max recent comments to return (default 10).")
|
|
2175
|
+
},
|
|
2176
|
+
handler: async (args) => {
|
|
2177
|
+
const number = Number(args.number);
|
|
2178
|
+
const limit = args.limit ? Number(args.limit) : 10;
|
|
2179
|
+
const result = readThread(opts.repoSlug, number, limit);
|
|
2180
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
const ensureIssueTool = {
|
|
2184
|
+
name: "ensure_issue",
|
|
2185
|
+
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.",
|
|
2186
|
+
inputSchema: {
|
|
2187
|
+
key: z2.string().min(1).describe(
|
|
2188
|
+
"Stable dedup identity for this finding (e.g. 'dev-ci-red', 'docs-drift:<feature>'). Same key across ticks = same issue."
|
|
2189
|
+
),
|
|
2190
|
+
title: z2.string().min(1).describe("Issue title (used only on first creation)."),
|
|
2191
|
+
body: z2.string().min(1).describe(
|
|
2192
|
+
"Issue body markdown (used only on first creation). Include the operator mention verbatim if the agentResponsibility body has one."
|
|
2193
|
+
)
|
|
2194
|
+
},
|
|
2195
|
+
handler: async (args) => {
|
|
2196
|
+
const key = String(args.key ?? "");
|
|
2197
|
+
const title = String(args.title ?? "");
|
|
2198
|
+
const body = String(args.body ?? "");
|
|
2199
|
+
const result = ensureIssue(opts.repoSlug, key, title, body);
|
|
2200
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
const ensureCommentTool = {
|
|
2204
|
+
name: "ensure_comment",
|
|
2205
|
+
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.",
|
|
2206
|
+
inputSchema: {
|
|
2207
|
+
issue: z2.number().int().positive().describe("Issue number to comment on."),
|
|
2208
|
+
key: z2.string().min(1).describe("Stable dedup identity for this comment (e.g. 'dev-ci-red:dispatched')."),
|
|
2209
|
+
body: z2.string().min(1).describe("Comment body markdown.")
|
|
2210
|
+
},
|
|
2211
|
+
handler: async (args) => {
|
|
2212
|
+
const issue = Number(args.issue);
|
|
2213
|
+
const key = String(args.key ?? "");
|
|
2214
|
+
const body = String(args.body ?? "");
|
|
2215
|
+
const result = ensureComment(opts.repoSlug, issue, key, body);
|
|
2216
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
const dispatchTool = {
|
|
2220
|
+
name: "dispatch_workflow",
|
|
2221
|
+
description: "Dispatch a kody.yml workflow_dispatch run for a agentResponsibility action against an issue (the cross-run bot\u2192engine path; a bot `@kody` comment would be dropped). E.g. dispatch_workflow({agentResponsibility:'run', issueNumber:<n>}) opens a fix PR from a tracking issue. Returns {ok} or {ok:false,error}.",
|
|
2222
|
+
inputSchema: {
|
|
2223
|
+
agentResponsibility: z2.string().min(1).optional().describe("AgentResponsibility action to run (e.g. 'run')."),
|
|
2224
|
+
agentAction: z2.string().min(1).optional().describe("Deprecated alias for agentResponsibility."),
|
|
2225
|
+
issueNumber: z2.number().int().positive().describe("Issue (or PR) number forwarded as issue_number.")
|
|
2226
|
+
},
|
|
2227
|
+
handler: async (args) => {
|
|
2228
|
+
const agentResponsibility = String(args.agentResponsibility ?? args.agentAction ?? "");
|
|
2229
|
+
const issueNumber = Number(args.issueNumber);
|
|
2230
|
+
if (isDispatchGated(
|
|
2231
|
+
agentResponsibility,
|
|
2232
|
+
readAgentResponsibilityTrustMode(opts.state, opts.repoSlug, opts.agentResponsibilitySlug)
|
|
2233
|
+
)) {
|
|
2234
|
+
return { content: [{ type: "text", text: trustRefusal(opts.agentResponsibilitySlug) }] };
|
|
2301
2235
|
}
|
|
2236
|
+
const result = dispatchWorkflow(workflowFile, agentResponsibility, issueNumber, opts.repoSlug);
|
|
2237
|
+
const text = result.ok ? `Dispatched agentResponsibility \`${agentResponsibility}\` on #${issueNumber} via workflow_dispatch.` : `Dispatch failed for agentResponsibility \`${agentResponsibility}\` on #${issueNumber}: ${result.error}`;
|
|
2238
|
+
return { content: [{ type: "text", text }] };
|
|
2302
2239
|
}
|
|
2303
|
-
await new Promise((r) => setTimeout(r, delayMs));
|
|
2304
|
-
}
|
|
2305
|
-
const submittedState = getSubmitted?.();
|
|
2306
|
-
return {
|
|
2307
|
-
outcome,
|
|
2308
|
-
outcomeKind,
|
|
2309
|
-
finalText,
|
|
2310
|
-
...submittedState ? { submittedState } : {},
|
|
2311
|
-
error: errorMessage,
|
|
2312
|
-
ndjsonPath,
|
|
2313
|
-
durationMs: Date.now() - startedAt,
|
|
2314
|
-
tokens,
|
|
2315
|
-
messageCount
|
|
2316
2240
|
};
|
|
2241
|
+
return [
|
|
2242
|
+
listTool,
|
|
2243
|
+
syncTool,
|
|
2244
|
+
fixCiTool,
|
|
2245
|
+
resolveTool,
|
|
2246
|
+
recommendTool,
|
|
2247
|
+
ledgerTool,
|
|
2248
|
+
checkRunsTool,
|
|
2249
|
+
readThreadTool,
|
|
2250
|
+
ensureIssueTool,
|
|
2251
|
+
ensureCommentTool,
|
|
2252
|
+
dispatchTool
|
|
2253
|
+
];
|
|
2317
2254
|
}
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2255
|
+
function buildAgentResponsibilityMcpServer(opts) {
|
|
2256
|
+
const definitions = agentResponsibilityToolDefinitions(opts);
|
|
2257
|
+
const tools = definitions.map(
|
|
2258
|
+
(def) => tool3(
|
|
2259
|
+
def.name,
|
|
2260
|
+
def.description,
|
|
2261
|
+
def.inputSchema,
|
|
2262
|
+
async (args) => def.handler(args)
|
|
2263
|
+
)
|
|
2264
|
+
);
|
|
2265
|
+
const server = createSdkMcpServer3({
|
|
2266
|
+
name: "kody-agentResponsibility",
|
|
2267
|
+
version: "0.1.0",
|
|
2268
|
+
tools
|
|
2269
|
+
});
|
|
2270
|
+
return { server };
|
|
2271
|
+
}
|
|
2272
|
+
var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, THREAD_BODY_MAX, CHECK_FAIL_CONCLUSIONS, DEFAULT_IGNORE_CHECKS, trackMarker, commentMarker, GATE_EXEMPT_DUTIES, AGENT_RESPONSIBILITY_MCP_TOOL_NAMES;
|
|
2273
|
+
var init_agent_responsibilityMcp = __esm({
|
|
2274
|
+
"src/agent-responsibilityMcp.ts"() {
|
|
2321
2275
|
"use strict";
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
"
|
|
2336
|
-
"
|
|
2337
|
-
|
|
2276
|
+
init_issue();
|
|
2277
|
+
init_registry();
|
|
2278
|
+
init_stateRepo();
|
|
2279
|
+
FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE", "CANCELLED"]);
|
|
2280
|
+
RUNNING_STATUSES = /* @__PURE__ */ new Set(["IN_PROGRESS", "QUEUED", "PENDING", "WAITING", "REQUESTED"]);
|
|
2281
|
+
TRUST_FILE_PATH = "state/trust.json";
|
|
2282
|
+
THREAD_BODY_MAX = 4e3;
|
|
2283
|
+
CHECK_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "STARTUP_FAILURE", "ACTION_REQUIRED"]);
|
|
2284
|
+
DEFAULT_IGNORE_CHECKS = ["run", "kody", "agent-responsibility-tick", "agent-ask", "chat"];
|
|
2285
|
+
trackMarker = (key) => `<!-- kody-track:${key} -->`;
|
|
2286
|
+
commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
|
|
2287
|
+
GATE_EXEMPT_DUTIES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
|
|
2288
|
+
AGENT_RESPONSIBILITY_MCP_TOOL_NAMES = [
|
|
2289
|
+
"list_prs_to_repair",
|
|
2290
|
+
"sync_pr",
|
|
2291
|
+
"fix_ci_pr",
|
|
2292
|
+
"resolve_pr",
|
|
2293
|
+
"recommend_to_operator",
|
|
2294
|
+
"read_ledger",
|
|
2295
|
+
"read_check_runs",
|
|
2296
|
+
"read_thread",
|
|
2297
|
+
"ensure_issue",
|
|
2298
|
+
"ensure_comment",
|
|
2299
|
+
"dispatch_workflow"
|
|
2300
|
+
];
|
|
2338
2301
|
}
|
|
2339
2302
|
});
|
|
2340
2303
|
|
|
2341
|
-
// src/
|
|
2342
|
-
import
|
|
2343
|
-
import * as
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2304
|
+
// src/repoWorkspace.ts
|
|
2305
|
+
import { spawn as spawn2, spawnSync } from "child_process";
|
|
2306
|
+
import * as fs7 from "fs";
|
|
2307
|
+
import * as path9 from "path";
|
|
2308
|
+
async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
|
|
2309
|
+
const name = repo?.trim();
|
|
2310
|
+
if (!name || !REPO_RE.test(name)) return null;
|
|
2311
|
+
const root = path9.resolve(reposRoot);
|
|
2312
|
+
const dir = path9.resolve(root, name);
|
|
2313
|
+
if (dir !== root && !dir.startsWith(root + path9.sep)) return null;
|
|
2314
|
+
if (fs7.existsSync(path9.join(dir, ".git"))) return dir;
|
|
2315
|
+
const inflight = repoClones.get(dir);
|
|
2316
|
+
if (inflight) {
|
|
2317
|
+
await inflight;
|
|
2318
|
+
return dir;
|
|
2351
2319
|
}
|
|
2352
|
-
|
|
2320
|
+
const p = cloneRepo(name, repoToken, dir).finally(() => {
|
|
2321
|
+
if (repoClones.get(dir) === p) repoClones.delete(dir);
|
|
2322
|
+
});
|
|
2323
|
+
repoClones.set(dir, p);
|
|
2324
|
+
await p;
|
|
2325
|
+
return dir;
|
|
2353
2326
|
}
|
|
2354
|
-
function
|
|
2355
|
-
|
|
2327
|
+
async function ensureRepoCwd(opts) {
|
|
2328
|
+
const dir = await resolveAndClone(opts.reposRoot, opts.repo, opts.repoToken, opts.cloneRepo);
|
|
2329
|
+
return dir ?? opts.baseCwd;
|
|
2356
2330
|
}
|
|
2357
|
-
function
|
|
2358
|
-
const dir =
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
if (!fs6.existsSync(profilePath) || !fs6.statSync(profilePath).isFile()) return null;
|
|
2362
|
-
if (!fs6.existsSync(bodyPath) || !fs6.statSync(bodyPath).isFile()) return null;
|
|
2363
|
-
try {
|
|
2364
|
-
const rawProfile = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
|
|
2365
|
-
const rawBody = fs6.readFileSync(bodyPath, "utf-8");
|
|
2366
|
-
const { title, body } = parseAgentResponsibilityBody(rawBody, slug2);
|
|
2367
|
-
return {
|
|
2368
|
-
slug: slug2,
|
|
2369
|
-
dir,
|
|
2370
|
-
profilePath,
|
|
2371
|
-
bodyPath,
|
|
2372
|
-
title,
|
|
2373
|
-
body,
|
|
2374
|
-
rawBody,
|
|
2375
|
-
config: parseAgentResponsibilityConfig(rawProfile),
|
|
2376
|
-
rawProfile
|
|
2377
|
-
};
|
|
2378
|
-
} catch {
|
|
2379
|
-
return null;
|
|
2331
|
+
async function fetchRepo(opts) {
|
|
2332
|
+
const dir = await resolveAndClone(opts.reposRoot, opts.repo, opts.repoToken, opts.cloneRepo ?? defaultCloneRepo);
|
|
2333
|
+
if (!dir) {
|
|
2334
|
+
throw new Error(`invalid repo "${opts.repo}" \u2014 expected "owner/name" with no path escapes`);
|
|
2380
2335
|
}
|
|
2336
|
+
return dir;
|
|
2381
2337
|
}
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
action: stringField(raw.action),
|
|
2386
|
-
agentAction: stringField(raw.agentAction),
|
|
2387
|
-
tickScript: stringField(raw.tickScript),
|
|
2388
|
-
disabled: typeof raw.disabled === "boolean" ? raw.disabled : void 0,
|
|
2389
|
-
agent: stringField(raw.agent),
|
|
2390
|
-
mentions: stringList(raw.mentions).map((m) => m.replace(/^@/, "")),
|
|
2391
|
-
tools,
|
|
2392
|
-
agentActions: stringList(raw.agentActions),
|
|
2393
|
-
capabilityKind: capabilityKindField(raw.capabilityKind),
|
|
2394
|
-
describe: stringField(raw.describe),
|
|
2395
|
-
stage: stringField(raw.stage),
|
|
2396
|
-
readsFrom: stringList(raw.readsFrom ?? raw.reads_from),
|
|
2397
|
-
writesTo: stringList(raw.writesTo ?? raw.writes_to)
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
function parseAgentResponsibilityBody(raw, slug2) {
|
|
2401
|
-
const trimmed = raw.trim();
|
|
2402
|
-
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
2403
|
-
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
2404
|
-
const title = h1 ? h1[1].trim() : humanizeSlug(slug2);
|
|
2405
|
-
const body = stripLeadingH1(raw);
|
|
2406
|
-
return { title, body };
|
|
2407
|
-
}
|
|
2408
|
-
function stripLeadingH1(raw) {
|
|
2409
|
-
const lines = raw.replace(/^\uFEFF/, "").split("\n");
|
|
2410
|
-
let i = 0;
|
|
2411
|
-
for (; ; ) {
|
|
2412
|
-
while (i < lines.length && lines[i].trim() === "") i++;
|
|
2413
|
-
if (i < lines.length && /^#\s+.+/.test(lines[i])) i++;
|
|
2414
|
-
else break;
|
|
2415
|
-
}
|
|
2416
|
-
return lines.slice(i).join("\n");
|
|
2417
|
-
}
|
|
2418
|
-
function humanizeSlug(slug2) {
|
|
2419
|
-
return slug2.split(/[-_]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
2420
|
-
}
|
|
2421
|
-
function stringField(value) {
|
|
2422
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
2423
|
-
}
|
|
2424
|
-
function stringList(value) {
|
|
2425
|
-
if (Array.isArray(value)) {
|
|
2426
|
-
return value.map((v) => String(v).trim()).filter(Boolean);
|
|
2427
|
-
}
|
|
2428
|
-
if (typeof value === "string") {
|
|
2429
|
-
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
2430
|
-
}
|
|
2431
|
-
return [];
|
|
2432
|
-
}
|
|
2433
|
-
function capabilityKindField(value) {
|
|
2434
|
-
if (value === "observe" || value === "act" || value === "verify") return value;
|
|
2435
|
-
return void 0;
|
|
2436
|
-
}
|
|
2437
|
-
var AGENT_RESPONSIBILITY_PROFILE_FILE, AGENT_RESPONSIBILITY_BODY_FILE;
|
|
2438
|
-
var init_agent_responsibilityFolders = __esm({
|
|
2439
|
-
"src/agent-responsibilityFolders.ts"() {
|
|
2338
|
+
var REPO_RE, repoClones, defaultCloneRepo;
|
|
2339
|
+
var init_repoWorkspace = __esm({
|
|
2340
|
+
"src/repoWorkspace.ts"() {
|
|
2440
2341
|
"use strict";
|
|
2441
|
-
|
|
2442
|
-
|
|
2342
|
+
REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
2343
|
+
repoClones = /* @__PURE__ */ new Map();
|
|
2344
|
+
defaultCloneRepo = (repo, token, dir) => {
|
|
2345
|
+
fs7.mkdirSync(path9.dirname(dir), { recursive: true });
|
|
2346
|
+
const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
|
|
2347
|
+
return new Promise((resolve7, reject) => {
|
|
2348
|
+
const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
|
|
2349
|
+
stdio: "inherit"
|
|
2350
|
+
});
|
|
2351
|
+
child.on("exit", (code) => {
|
|
2352
|
+
if (code !== 0) {
|
|
2353
|
+
reject(new Error(`git clone ${repo} failed (exit ${code})`));
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
try {
|
|
2357
|
+
const name = process.env.GIT_AUTHOR_NAME ?? "Kody Bot";
|
|
2358
|
+
const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
|
|
2359
|
+
spawnSync("git", ["-C", dir, "config", "user.name", name]);
|
|
2360
|
+
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
2361
|
+
} catch {
|
|
2362
|
+
}
|
|
2363
|
+
resolve7();
|
|
2364
|
+
});
|
|
2365
|
+
child.on("error", reject);
|
|
2366
|
+
});
|
|
2367
|
+
};
|
|
2443
2368
|
}
|
|
2444
2369
|
});
|
|
2445
2370
|
|
|
2446
|
-
// src/
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2371
|
+
// src/fetchRepoMcp.ts
|
|
2372
|
+
var fetchRepoMcp_exports = {};
|
|
2373
|
+
__export(fetchRepoMcp_exports, {
|
|
2374
|
+
buildFetchRepoMcpServer: () => buildFetchRepoMcpServer,
|
|
2375
|
+
fetchRepoToolDefinition: () => fetchRepoToolDefinition
|
|
2376
|
+
});
|
|
2377
|
+
import { createSdkMcpServer as createSdkMcpServer4, tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
2378
|
+
import { z as z3 } from "zod";
|
|
2379
|
+
function fetchRepoToolDefinition(opts) {
|
|
2380
|
+
return {
|
|
2381
|
+
name: "fetch_repo",
|
|
2382
|
+
description: DESCRIPTION3,
|
|
2383
|
+
inputSchema: INPUT_SCHEMA3,
|
|
2384
|
+
handler: async (args) => {
|
|
2385
|
+
const repo = String(args.repo ?? "").trim();
|
|
2386
|
+
try {
|
|
2387
|
+
const dir = await fetchRepo({
|
|
2388
|
+
reposRoot: opts.reposRoot,
|
|
2389
|
+
repo,
|
|
2390
|
+
repoToken: opts.repoToken
|
|
2391
|
+
});
|
|
2392
|
+
return {
|
|
2393
|
+
content: [
|
|
2394
|
+
{
|
|
2395
|
+
type: "text",
|
|
2396
|
+
text: `Cloned ${repo} \u2192 ${dir}
|
|
2397
|
+
Use Read/Grep/Glob/Bash at that absolute path to explore it. It now lives in your workspace alongside any other repos you've fetched.`
|
|
2398
|
+
}
|
|
2399
|
+
]
|
|
2400
|
+
};
|
|
2401
|
+
} catch (err) {
|
|
2402
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2403
|
+
return {
|
|
2404
|
+
content: [{ type: "text", text: `Could not fetch ${repo}: ${msg}` }],
|
|
2405
|
+
isError: true
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2469
2409
|
};
|
|
2470
|
-
return path9.join(root, ".kody", folderByKind[kind]);
|
|
2471
2410
|
}
|
|
2472
|
-
function
|
|
2473
|
-
const
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2411
|
+
function buildFetchRepoMcpServer(opts) {
|
|
2412
|
+
const def = fetchRepoToolDefinition(opts);
|
|
2413
|
+
const fetchTool = tool4(def.name, def.description, def.inputSchema, async (args) => {
|
|
2414
|
+
return def.handler({ repo: String(args.repo ?? "") });
|
|
2415
|
+
});
|
|
2416
|
+
return createSdkMcpServer4({
|
|
2417
|
+
name: "kody-fetch-repo",
|
|
2418
|
+
version: "0.1.0",
|
|
2419
|
+
tools: [fetchTool]
|
|
2420
|
+
});
|
|
2480
2421
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
fs7.rmSync(cacheDir, { recursive: true, force: true });
|
|
2491
|
-
runGit(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
|
|
2492
|
-
}
|
|
2493
|
-
runGit(["-C", cacheDir, "remote", "set-url", "origin", url]);
|
|
2494
|
-
runGit(["-C", cacheDir, "fetch", "--depth=1", "origin", ref]);
|
|
2495
|
-
runGit(["-C", cacheDir, "checkout", "--detach", "FETCH_HEAD"]);
|
|
2496
|
-
return cacheDir;
|
|
2497
|
-
} catch (err) {
|
|
2498
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2499
|
-
process.stderr.write(`[company-store] failed to fetch ${repo}#${ref}: ${msg}
|
|
2500
|
-
`);
|
|
2501
|
-
return null;
|
|
2422
|
+
var DESCRIPTION3, INPUT_SCHEMA3;
|
|
2423
|
+
var init_fetchRepoMcp = __esm({
|
|
2424
|
+
"src/fetchRepoMcp.ts"() {
|
|
2425
|
+
"use strict";
|
|
2426
|
+
init_repoWorkspace();
|
|
2427
|
+
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.';
|
|
2428
|
+
INPUT_SCHEMA3 = {
|
|
2429
|
+
repo: z3.string().describe('GitHub repository as "owner/name", e.g. "A-Guy-educ/A-Guy".')
|
|
2430
|
+
};
|
|
2502
2431
|
}
|
|
2432
|
+
});
|
|
2433
|
+
|
|
2434
|
+
// src/agent.ts
|
|
2435
|
+
import * as fs8 from "fs";
|
|
2436
|
+
import * as path10 from "path";
|
|
2437
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
2438
|
+
function classifySubtype(subtype) {
|
|
2439
|
+
if (!subtype) return "generic_failed";
|
|
2440
|
+
const lower = subtype.toLowerCase();
|
|
2441
|
+
if (lower === "success") return "ok";
|
|
2442
|
+
if (lower.includes("max_turns") || lower.includes("max-turns")) return "out_of_turns";
|
|
2443
|
+
if (lower.includes("rate_limit") || lower.includes("rate-limit")) return "rate_limit";
|
|
2444
|
+
if (lower.includes("tool")) return "tool_error";
|
|
2445
|
+
if (lower.includes("error")) return "model_error";
|
|
2446
|
+
return "generic_failed";
|
|
2503
2447
|
}
|
|
2504
|
-
function
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
if (!fs7.existsSync(path9.join(root, ".kody"))) return null;
|
|
2508
|
-
return root;
|
|
2448
|
+
function isClaudeLoginRequiredText(text) {
|
|
2449
|
+
const normalized = text.toLowerCase();
|
|
2450
|
+
return normalized.includes("not logged in") && normalized.includes("/login");
|
|
2509
2451
|
}
|
|
2510
|
-
function
|
|
2511
|
-
if (
|
|
2512
|
-
|
|
2513
|
-
return `https://github.com/${repo}.git`;
|
|
2452
|
+
function resolveTurnTimeoutMs(opts) {
|
|
2453
|
+
if (opts.maxTurnTimeoutMs !== void 0 && opts.maxTurnTimeoutMs !== null) {
|
|
2454
|
+
return opts.maxTurnTimeoutMs > 0 ? opts.maxTurnTimeoutMs : 0;
|
|
2514
2455
|
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
return
|
|
2519
|
-
}
|
|
2520
|
-
function cacheRoot() {
|
|
2521
|
-
return process.env[CACHE_ENV]?.trim() || path9.join(os3.homedir(), ".cache", "kody", "company-store");
|
|
2456
|
+
const envSec = Number(process.env.KODY_TURN_TIMEOUT_SEC);
|
|
2457
|
+
if (Number.isFinite(envSec) && envSec > 0) return Math.floor(envSec * 1e3);
|
|
2458
|
+
if (Number.isFinite(envSec) && envSec <= 0) return 0;
|
|
2459
|
+
return DEFAULT_TURN_TIMEOUT_MS;
|
|
2522
2460
|
}
|
|
2523
|
-
function
|
|
2524
|
-
|
|
2461
|
+
function isTransientConnectionError(msg) {
|
|
2462
|
+
if (!msg) return false;
|
|
2463
|
+
return /ConnectionRefused|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EHOSTUNREACH|ENOTFOUND|socket hang up|Unable to connect to API|fetch failed/i.test(
|
|
2464
|
+
msg
|
|
2465
|
+
);
|
|
2525
2466
|
}
|
|
2526
|
-
function
|
|
2527
|
-
|
|
2467
|
+
function toolMayMutate(name, input) {
|
|
2468
|
+
if (!name) return false;
|
|
2469
|
+
if (MUTATING_FILE_TOOLS.has(name)) return true;
|
|
2470
|
+
if (name === "Bash") return BASH_WRITE_VERB.test(String(input?.command ?? ""));
|
|
2471
|
+
return false;
|
|
2528
2472
|
}
|
|
2529
|
-
function
|
|
2530
|
-
const
|
|
2531
|
-
|
|
2473
|
+
function stripAgentSecrets(env) {
|
|
2474
|
+
const out = { ...env };
|
|
2475
|
+
const raw = out.ALL_SECRETS;
|
|
2476
|
+
delete out.ALL_SECRETS;
|
|
2477
|
+
if (!raw) return out;
|
|
2532
2478
|
try {
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
}
|
|
2479
|
+
const parsed = JSON.parse(raw);
|
|
2480
|
+
for (const key of Object.keys(parsed)) {
|
|
2481
|
+
if (!AGENT_KEEP_SECRETS.has(key)) delete out[key];
|
|
2482
|
+
}
|
|
2537
2483
|
} catch {
|
|
2538
2484
|
}
|
|
2485
|
+
return out;
|
|
2539
2486
|
}
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
//
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
path10.join(here, "..", "agent-actions"),
|
|
2562
|
-
// built: dist/bin → dist/agent-actions
|
|
2563
|
-
path10.join(here, "..", "src", "agent-actions")
|
|
2564
|
-
// fallback
|
|
2565
|
-
];
|
|
2566
|
-
for (const c of candidates) {
|
|
2567
|
-
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
2568
|
-
}
|
|
2569
|
-
return candidates[0];
|
|
2570
|
-
}
|
|
2571
|
-
function getProjectAgentActionsRoot() {
|
|
2572
|
-
return path10.join(process.cwd(), ".kody", "agent-actions");
|
|
2573
|
-
}
|
|
2574
|
-
function getProjectAgentResponsibilitiesRoot() {
|
|
2575
|
-
return path10.join(process.cwd(), ".kody", "agent-responsibilities");
|
|
2576
|
-
}
|
|
2577
|
-
function getCompanyStoreAgentActionsRoot() {
|
|
2578
|
-
return getCompanyStoreAssetRoot("agentActions");
|
|
2579
|
-
}
|
|
2580
|
-
function getCompanyStoreAgentResponsibilitiesRoot() {
|
|
2581
|
-
return getCompanyStoreAssetRoot("agentResponsibilities");
|
|
2582
|
-
}
|
|
2583
|
-
function getBuiltinAgentResponsibilitiesRoot() {
|
|
2584
|
-
const here = path10.dirname(new URL(import.meta.url).pathname);
|
|
2585
|
-
const candidates = [
|
|
2586
|
-
path10.join(here, "agent-responsibilities"),
|
|
2587
|
-
// dev: src/
|
|
2588
|
-
path10.join(here, "..", "agent-responsibilities"),
|
|
2589
|
-
// built: dist/bin → dist/agent-responsibilities
|
|
2590
|
-
path10.join(here, "..", "src", "agent-responsibilities")
|
|
2591
|
-
// fallback
|
|
2592
|
-
];
|
|
2593
|
-
for (const c of candidates) {
|
|
2594
|
-
if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
|
|
2487
|
+
async function runAgent(opts) {
|
|
2488
|
+
const ndjsonDir = opts.ndjsonDir ?? path10.join(opts.cwd, ".kody");
|
|
2489
|
+
fs8.mkdirSync(ndjsonDir, { recursive: true });
|
|
2490
|
+
const ndjsonPath = path10.join(ndjsonDir, "last-run.jsonl");
|
|
2491
|
+
const env = stripAgentSecrets({
|
|
2492
|
+
...process.env,
|
|
2493
|
+
SKIP_HOOKS: "1",
|
|
2494
|
+
HUSKY: "0",
|
|
2495
|
+
CI: process.env.CI ?? "1",
|
|
2496
|
+
// MCP servers are spawned asynchronously by the SDK. With the default
|
|
2497
|
+
// non-blocking behavior, the SDK announces its tool list at session
|
|
2498
|
+
// init while servers are still in `pending`, so their tools never
|
|
2499
|
+
// reach the model. Block until each MCP completes its handshake (or
|
|
2500
|
+
// the timeout below elapses) so the tool list is complete on first
|
|
2501
|
+
// turn.
|
|
2502
|
+
MCP_CONNECTION_NONBLOCKING: process.env.MCP_CONNECTION_NONBLOCKING ?? "false",
|
|
2503
|
+
MCP_TIMEOUT: process.env.MCP_TIMEOUT ?? "60000"
|
|
2504
|
+
});
|
|
2505
|
+
if (opts.litellmUrl) {
|
|
2506
|
+
env.ANTHROPIC_BASE_URL = opts.litellmUrl;
|
|
2507
|
+
env.ANTHROPIC_API_KEY = getAnthropicApiKeyOrDummy();
|
|
2595
2508
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2509
|
+
const startedAt = Date.now();
|
|
2510
|
+
const turnTimeoutMs = resolveTurnTimeoutMs(opts);
|
|
2511
|
+
let outcome = "failed";
|
|
2512
|
+
let outcomeKind = "generic_failed";
|
|
2513
|
+
let errorMessage;
|
|
2514
|
+
let tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
|
|
2515
|
+
let messageCount = 0;
|
|
2516
|
+
let finalText = "";
|
|
2517
|
+
let getSubmitted;
|
|
2518
|
+
for (let attempt = 0; ; attempt++) {
|
|
2519
|
+
let ndjsonWriteFailed = false;
|
|
2520
|
+
let ndjsonWriteError;
|
|
2521
|
+
const fullLog = fs8.createWriteStream(ndjsonPath, { flags: "w" });
|
|
2522
|
+
fullLog.on("error", (err) => {
|
|
2523
|
+
ndjsonWriteFailed = true;
|
|
2524
|
+
ndjsonWriteError = err instanceof Error ? err.message : String(err);
|
|
2525
|
+
});
|
|
2526
|
+
const resultTexts = [];
|
|
2527
|
+
outcome = "failed";
|
|
2528
|
+
outcomeKind = "generic_failed";
|
|
2529
|
+
errorMessage = void 0;
|
|
2530
|
+
tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
|
|
2531
|
+
messageCount = 0;
|
|
2532
|
+
let sawMutatingTool = false;
|
|
2533
|
+
let sawTerminalSuccess = false;
|
|
2534
|
+
let sawLoginRequired = false;
|
|
2535
|
+
let noWorkSuccess = false;
|
|
2536
|
+
try {
|
|
2537
|
+
const queryOptions = {
|
|
2538
|
+
model: opts.model.model,
|
|
2539
|
+
cwd: opts.cwd,
|
|
2540
|
+
// Fresh array (never mutate the shared DEFAULT_ALLOWED_TOOLS const) so
|
|
2541
|
+
// opt-in tools like fetch_repo can be appended below.
|
|
2542
|
+
allowedTools: [...opts.allowedToolsOverride ?? DEFAULT_ALLOWED_TOOLS],
|
|
2543
|
+
permissionMode: opts.permissionModeOverride ?? "acceptEdits",
|
|
2544
|
+
env
|
|
2545
|
+
};
|
|
2546
|
+
const mcpEntries = [];
|
|
2547
|
+
if (opts.mcpServers && opts.mcpServers.length > 0) {
|
|
2548
|
+
for (const s of opts.mcpServers) {
|
|
2549
|
+
const cfg = { command: s.command };
|
|
2550
|
+
if (s.args) cfg.args = s.args;
|
|
2551
|
+
if (s.env) cfg.env = s.env;
|
|
2552
|
+
mcpEntries.push([s.name, cfg]);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
if (opts.enableVerifyTool && opts.verifyConfig) {
|
|
2556
|
+
const { buildVerifyMcpServer: buildVerifyMcpServer2 } = await Promise.resolve().then(() => (init_verifyMcp(), verifyMcp_exports));
|
|
2557
|
+
const verifyServer = buildVerifyMcpServer2({
|
|
2558
|
+
config: opts.verifyConfig,
|
|
2559
|
+
cwd: opts.cwd,
|
|
2560
|
+
agentAction: opts.agentActionName ?? "agent",
|
|
2561
|
+
maxAttempts: typeof opts.verifyToolMaxAttempts === "number" && opts.verifyToolMaxAttempts > 0 ? opts.verifyToolMaxAttempts : void 0
|
|
2562
|
+
});
|
|
2563
|
+
mcpEntries.push(["kody-verify", verifyServer]);
|
|
2564
|
+
}
|
|
2565
|
+
if (opts.enableSubmitTool) {
|
|
2566
|
+
const { buildSubmitMcpServer: buildSubmitMcpServer2 } = await Promise.resolve().then(() => (init_submitMcp(), submitMcp_exports));
|
|
2567
|
+
const submitHandle = buildSubmitMcpServer2();
|
|
2568
|
+
getSubmitted = submitHandle.getSubmitted;
|
|
2569
|
+
mcpEntries.push(["kody-submit", submitHandle.server]);
|
|
2570
|
+
}
|
|
2571
|
+
if (opts.enableAgentResponsibilityTool) {
|
|
2572
|
+
const { buildAgentResponsibilityMcpServer: buildAgentResponsibilityMcpServer2 } = await Promise.resolve().then(() => (init_agent_responsibilityMcp(), agent_responsibilityMcp_exports));
|
|
2573
|
+
if (!opts.dutyRepoSlug) {
|
|
2574
|
+
throw new Error(
|
|
2575
|
+
"enableAgentResponsibilityTool requires dutyRepoSlug (owner/name) \u2014 set kody.config.json github.{owner,repo} or GITHUB_REPOSITORY env var"
|
|
2576
|
+
);
|
|
2577
|
+
}
|
|
2578
|
+
const dutyHandle = buildAgentResponsibilityMcpServer2({
|
|
2579
|
+
repoSlug: opts.dutyRepoSlug,
|
|
2580
|
+
state: opts.agentResponsibilityState,
|
|
2581
|
+
operatorMention: opts.agentResponsibilityOperatorMention ?? "",
|
|
2582
|
+
...opts.agentResponsibilitySlug ? { agentResponsibilitySlug: opts.agentResponsibilitySlug } : {}
|
|
2583
|
+
});
|
|
2584
|
+
mcpEntries.push(["kody-agentResponsibility", dutyHandle.server]);
|
|
2585
|
+
}
|
|
2586
|
+
if (opts.enableFetchRepoTool && opts.reposRoot) {
|
|
2587
|
+
const { buildFetchRepoMcpServer: buildFetchRepoMcpServer2 } = await Promise.resolve().then(() => (init_fetchRepoMcp(), fetchRepoMcp_exports));
|
|
2588
|
+
const fetchServer = buildFetchRepoMcpServer2({
|
|
2589
|
+
reposRoot: opts.reposRoot,
|
|
2590
|
+
repoToken: opts.repoToken
|
|
2591
|
+
});
|
|
2592
|
+
mcpEntries.push(["kody-fetch-repo", fetchServer]);
|
|
2593
|
+
queryOptions.allowedTools.push("mcp__kody-fetch-repo__fetch_repo");
|
|
2594
|
+
queryOptions.additionalDirectories = [opts.reposRoot];
|
|
2595
|
+
}
|
|
2596
|
+
if (mcpEntries.length > 0) {
|
|
2597
|
+
queryOptions.mcpServers = Object.fromEntries(mcpEntries);
|
|
2598
|
+
}
|
|
2599
|
+
if (opts.pluginPaths && opts.pluginPaths.length > 0) {
|
|
2600
|
+
queryOptions.plugins = opts.pluginPaths.map((p) => ({ type: "local", path: p }));
|
|
2601
|
+
}
|
|
2602
|
+
if (opts.agents && Object.keys(opts.agents).length > 0) {
|
|
2603
|
+
queryOptions.agents = opts.agents;
|
|
2604
|
+
}
|
|
2605
|
+
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
2606
|
+
queryOptions.maxTurns = opts.maxTurns;
|
|
2607
|
+
}
|
|
2608
|
+
if (opts.reasoningEffort !== void 0 && opts.reasoningEffort !== null) {
|
|
2609
|
+
if (opts.reasoningEffort === "off") {
|
|
2610
|
+
} else {
|
|
2611
|
+
const budget = REASONING_BUDGETS[opts.reasoningEffort];
|
|
2612
|
+
if (budget) queryOptions.maxThinkingTokens = budget;
|
|
2613
|
+
}
|
|
2614
|
+
} else if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
|
|
2615
|
+
queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
|
|
2616
|
+
}
|
|
2617
|
+
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
2618
|
+
const systemPrompt = {
|
|
2619
|
+
type: "preset",
|
|
2620
|
+
preset: "claude_code",
|
|
2621
|
+
append: opts.systemPromptAppend
|
|
2622
|
+
};
|
|
2623
|
+
if (opts.cacheable) systemPrompt.excludeDynamicSections = true;
|
|
2624
|
+
queryOptions.systemPrompt = systemPrompt;
|
|
2625
|
+
} else if (opts.cacheable) {
|
|
2626
|
+
queryOptions.systemPrompt = {
|
|
2627
|
+
type: "preset",
|
|
2628
|
+
preset: "claude_code",
|
|
2629
|
+
excludeDynamicSections: true
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
queryOptions.settingSources = opts.settingSources ?? ["project", "local"];
|
|
2633
|
+
const stableBinary = ensureStableClaudeBinary();
|
|
2634
|
+
if (stableBinary) {
|
|
2635
|
+
queryOptions.pathToClaudeCodeExecutable = stableBinary;
|
|
2636
|
+
}
|
|
2637
|
+
const result = query({
|
|
2638
|
+
prompt: opts.prompt,
|
|
2639
|
+
// biome-ignore lint/suspicious/noExplicitAny: SDK options type is narrow; mcpServers is runtime-passthrough.
|
|
2640
|
+
options: queryOptions
|
|
2641
|
+
});
|
|
2642
|
+
const iterator = typeof result[Symbol.asyncIterator] === "function" ? result[Symbol.asyncIterator]() : result;
|
|
2643
|
+
while (true) {
|
|
2644
|
+
const nextPromise = iterator.next();
|
|
2645
|
+
let timedOut = false;
|
|
2646
|
+
let timer;
|
|
2647
|
+
let next;
|
|
2648
|
+
if (turnTimeoutMs > 0) {
|
|
2649
|
+
const timeoutPromise = new Promise((resolve7) => {
|
|
2650
|
+
timer = setTimeout(() => {
|
|
2651
|
+
timedOut = true;
|
|
2652
|
+
resolve7({ done: true, value: void 0 });
|
|
2653
|
+
}, turnTimeoutMs);
|
|
2654
|
+
});
|
|
2655
|
+
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
2656
|
+
if (timer) clearTimeout(timer);
|
|
2657
|
+
} else {
|
|
2658
|
+
next = await nextPromise;
|
|
2659
|
+
}
|
|
2660
|
+
if (timedOut) {
|
|
2661
|
+
outcome = "failed";
|
|
2662
|
+
outcomeKind = "stalled";
|
|
2663
|
+
errorMessage = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
|
|
2664
|
+
if (typeof iterator.return === "function") {
|
|
2665
|
+
try {
|
|
2666
|
+
await iterator.return(void 0);
|
|
2667
|
+
} catch {
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
break;
|
|
2671
|
+
}
|
|
2672
|
+
if (next.done) break;
|
|
2673
|
+
const msg = next.value;
|
|
2674
|
+
messageCount++;
|
|
2675
|
+
if (!ndjsonWriteFailed) {
|
|
2676
|
+
try {
|
|
2677
|
+
fullLog.write(`${JSON.stringify(msg)}
|
|
2678
|
+
`);
|
|
2679
|
+
} catch (e) {
|
|
2680
|
+
ndjsonWriteFailed = true;
|
|
2681
|
+
ndjsonWriteError = e instanceof Error ? e.message : String(e);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
|
|
2685
|
+
if (line) {
|
|
2686
|
+
if (isClaudeLoginRequiredText(line)) sawLoginRequired = true;
|
|
2687
|
+
process.stdout.write(`${line}
|
|
2688
|
+
`);
|
|
2689
|
+
}
|
|
2690
|
+
const m = msg;
|
|
2691
|
+
if (opts.onProgress) {
|
|
2692
|
+
const blocks = m.message?.content ?? [];
|
|
2693
|
+
for (const block of blocks) {
|
|
2694
|
+
try {
|
|
2695
|
+
if (block.type === "thinking") {
|
|
2696
|
+
const t = block.thinking;
|
|
2697
|
+
if (typeof t === "string" && t.length > 0) {
|
|
2698
|
+
await opts.onProgress({ kind: "thinking", thinking: t });
|
|
2699
|
+
}
|
|
2700
|
+
} else if (block.type === "tool_use") {
|
|
2701
|
+
const b = block;
|
|
2702
|
+
await opts.onProgress({
|
|
2703
|
+
kind: "tool_use",
|
|
2704
|
+
name: b.name ?? "tool",
|
|
2705
|
+
input: b.input,
|
|
2706
|
+
id: b.id
|
|
2707
|
+
});
|
|
2708
|
+
} else if (block.type === "tool_result") {
|
|
2709
|
+
const b = block;
|
|
2710
|
+
const content = typeof b.content === "string" ? b.content : (() => {
|
|
2711
|
+
try {
|
|
2712
|
+
return JSON.stringify(b.content);
|
|
2713
|
+
} catch {
|
|
2714
|
+
return "";
|
|
2715
|
+
}
|
|
2716
|
+
})();
|
|
2717
|
+
await opts.onProgress({
|
|
2718
|
+
kind: "tool_result",
|
|
2719
|
+
toolUseId: b.tool_use_id,
|
|
2720
|
+
content,
|
|
2721
|
+
isError: b.is_error
|
|
2722
|
+
});
|
|
2723
|
+
} else if (block.type === "text") {
|
|
2724
|
+
const b = block;
|
|
2725
|
+
if (typeof b.text === "string" && b.text.length > 0) {
|
|
2726
|
+
await opts.onProgress({ kind: "text", text: b.text });
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
} catch {
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
const usage = m.usage;
|
|
2734
|
+
if (usage && typeof usage === "object") {
|
|
2735
|
+
const i = Number(usage.input_tokens ?? 0);
|
|
2736
|
+
const o = Number(usage.output_tokens ?? 0);
|
|
2737
|
+
const cr = Number(usage.cache_read_input_tokens ?? 0);
|
|
2738
|
+
const cc = Number(usage.cache_creation_input_tokens ?? 0);
|
|
2739
|
+
if (Number.isFinite(i)) tokens.input += i;
|
|
2740
|
+
if (Number.isFinite(o)) tokens.output += o;
|
|
2741
|
+
if (Number.isFinite(cr)) tokens.cacheRead += cr;
|
|
2742
|
+
if (Number.isFinite(cc)) tokens.cacheCreate += cc;
|
|
2743
|
+
}
|
|
2744
|
+
if (!sawMutatingTool) {
|
|
2745
|
+
const blocks = m.message?.content ?? [];
|
|
2746
|
+
for (const block of blocks) {
|
|
2747
|
+
if (block.type === "tool_use") {
|
|
2748
|
+
const b = block;
|
|
2749
|
+
if (toolMayMutate(b.name, b.input)) {
|
|
2750
|
+
sawMutatingTool = true;
|
|
2751
|
+
break;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
if (m.type === "result") {
|
|
2757
|
+
if (m.subtype === "success") {
|
|
2758
|
+
outcome = "completed";
|
|
2759
|
+
outcomeKind = "ok";
|
|
2760
|
+
sawTerminalSuccess = true;
|
|
2761
|
+
const text = (typeof m.result === "string" ? m.result : "").trim();
|
|
2762
|
+
if (isClaudeLoginRequiredText(text)) sawLoginRequired = true;
|
|
2763
|
+
if (text) resultTexts.push(text);
|
|
2764
|
+
} else {
|
|
2765
|
+
outcome = "failed";
|
|
2766
|
+
outcomeKind = classifySubtype(m.subtype);
|
|
2767
|
+
errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
} catch (e) {
|
|
2772
|
+
if (sawTerminalSuccess) {
|
|
2773
|
+
errorMessage = e instanceof Error ? e.message : String(e);
|
|
2774
|
+
} else {
|
|
2775
|
+
outcome = "failed";
|
|
2776
|
+
outcomeKind = "model_error";
|
|
2777
|
+
errorMessage = e instanceof Error ? e.message : String(e);
|
|
2778
|
+
}
|
|
2779
|
+
} finally {
|
|
2780
|
+
try {
|
|
2781
|
+
fullLog.end();
|
|
2782
|
+
} catch {
|
|
2620
2783
|
}
|
|
2621
2784
|
}
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
}
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
const rootList = typeof roots === "string" ? [roots] : roots;
|
|
2628
|
-
for (const root of rootList) {
|
|
2629
|
-
const profilePath = path10.join(root, name, "profile.json");
|
|
2630
|
-
if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
|
|
2631
|
-
return profilePath;
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
return null;
|
|
2635
|
-
}
|
|
2636
|
-
function listAgentResponsibilityActions(projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
2637
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2638
|
-
const out = [];
|
|
2639
|
-
const add = (action) => {
|
|
2640
|
-
if (!isSafeName(action.action) || !isSafeName(action.agentResponsibility) || !isSafeName(action.agentAction)) return;
|
|
2641
|
-
if (seen.has(action.action)) return;
|
|
2642
|
-
seen.add(action.action);
|
|
2643
|
-
out.push(action);
|
|
2644
|
-
};
|
|
2645
|
-
const roots = getAgentResponsibilityRoots(projectAgentResponsibilitiesRoot);
|
|
2646
|
-
const executableRoots = getAgentActionRoots();
|
|
2647
|
-
for (const action of listFolderAgentResponsibilityActions(roots[0], "project-folder")) add(action);
|
|
2648
|
-
for (const action of listAgentActionResponsibilityActions(executableRoots[0], "project-agentAction")) add(action);
|
|
2649
|
-
if (roots.length === 3) {
|
|
2650
|
-
for (const action of listFolderAgentResponsibilityActions(roots[1], "company-store")) add(action);
|
|
2651
|
-
for (const action of listAgentActionResponsibilityActions(executableRoots[1], "company-store-agentAction"))
|
|
2652
|
-
add(action);
|
|
2653
|
-
for (const action of listBuiltinAgentResponsibilityActions(roots[2])) add(action);
|
|
2654
|
-
} else {
|
|
2655
|
-
for (const action of listBuiltinAgentResponsibilityActions(roots[1])) add(action);
|
|
2656
|
-
}
|
|
2657
|
-
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2658
|
-
}
|
|
2659
|
-
function resolveAgentResponsibilityAction(action, projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
2660
|
-
if (!isSafeName(action)) return null;
|
|
2661
|
-
return listAgentResponsibilityActions(projectAgentResponsibilitiesRoot).find((d) => d.action === action) ?? null;
|
|
2662
|
-
}
|
|
2663
|
-
function hasAgentResponsibilityAction(action, projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
2664
|
-
return resolveAgentResponsibilityAction(action, projectAgentResponsibilitiesRoot) !== null;
|
|
2665
|
-
}
|
|
2666
|
-
function resolveAgentResponsibilityFolder(slug2, projectAgentResponsibilitiesRoot = getProjectAgentResponsibilitiesRoot()) {
|
|
2667
|
-
if (!isSafeName(slug2)) return null;
|
|
2668
|
-
for (const root of getAgentResponsibilityRoots(projectAgentResponsibilitiesRoot)) {
|
|
2669
|
-
const agentResponsibility = readAgentResponsibilityFolder(root, slug2);
|
|
2670
|
-
if (agentResponsibility) return agentResponsibility;
|
|
2671
|
-
}
|
|
2672
|
-
return null;
|
|
2673
|
-
}
|
|
2674
|
-
function resolveAgentResponsibilityExecution(agentResponsibility) {
|
|
2675
|
-
const agentAction = agentResponsibility.config.agentAction ?? agentResponsibility.config.agentActions?.[0] ?? (agentResponsibility.config.tickScript ? "agent-responsibility-tick-scripted" : "agent-responsibility-tick");
|
|
2676
|
-
const cliArgs = agentActionDeclaresInput(agentAction, "agentResponsibility") ? { agentResponsibility: agentResponsibility.slug } : {};
|
|
2677
|
-
return { agentAction, cliArgs };
|
|
2678
|
-
}
|
|
2679
|
-
function agentActionDeclaresInput(agentAction, inputName) {
|
|
2680
|
-
const profilePath = resolveAgentAction(agentAction);
|
|
2681
|
-
if (!profilePath) return false;
|
|
2682
|
-
try {
|
|
2683
|
-
const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
|
|
2684
|
-
if (!Array.isArray(raw.inputs)) return false;
|
|
2685
|
-
return raw.inputs.some((entry) => {
|
|
2686
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
|
|
2687
|
-
const input = entry;
|
|
2688
|
-
return input.name === inputName || input.flag === `--${inputName}`;
|
|
2689
|
-
});
|
|
2690
|
-
} catch {
|
|
2691
|
-
return false;
|
|
2692
|
-
}
|
|
2693
|
-
}
|
|
2694
|
-
function isSafeName(name) {
|
|
2695
|
-
return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
|
|
2696
|
-
}
|
|
2697
|
-
function listAgentActionResponsibilityActions(root, source) {
|
|
2698
|
-
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
2699
|
-
const out = [];
|
|
2700
|
-
for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
|
|
2701
|
-
if (!ent.isDirectory() || !isSafeName(ent.name)) continue;
|
|
2702
|
-
const profilePath = path10.join(root, ent.name, AGENT_RESPONSIBILITY_PROFILE_FILE);
|
|
2703
|
-
if (!fs8.existsSync(profilePath) || !fs8.statSync(profilePath).isFile()) continue;
|
|
2704
|
-
try {
|
|
2705
|
-
const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
|
|
2706
|
-
const action = typeof raw.action === "string" && raw.action.trim() ? raw.action.trim() : "";
|
|
2707
|
-
if (!action) continue;
|
|
2708
|
-
if (!PUBLIC_AGENT_ACTION_ACTION_ROLES.has(String(raw.role))) continue;
|
|
2709
|
-
if (!PUBLIC_AGENT_ACTION_CAPABILITY_KINDS.has(String(raw.capabilityKind))) continue;
|
|
2710
|
-
if (!Array.isArray(raw.inputs)) continue;
|
|
2711
|
-
out.push({
|
|
2712
|
-
action,
|
|
2713
|
-
agentResponsibility: ent.name,
|
|
2714
|
-
agentAction: ent.name,
|
|
2715
|
-
cliArgs: {},
|
|
2716
|
-
source,
|
|
2717
|
-
describe: typeof raw.describe === "string" ? raw.describe : void 0,
|
|
2718
|
-
profilePath
|
|
2719
|
-
});
|
|
2720
|
-
} catch {
|
|
2785
|
+
if (ndjsonWriteFailed) {
|
|
2786
|
+
process.stderr.write(
|
|
2787
|
+
`[kody agent] NDJSON write failed (post-mortem may be incomplete): ${ndjsonWriteError ?? "unknown error"}
|
|
2788
|
+
`
|
|
2789
|
+
);
|
|
2721
2790
|
}
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
const out = [];
|
|
2728
|
-
for (const slug2 of listAgentResponsibilityFolderSlugs(root)) {
|
|
2729
|
-
if (!isSafeName(slug2)) continue;
|
|
2730
|
-
const agentResponsibility = readAgentResponsibilityFolder(root, slug2);
|
|
2731
|
-
if (!agentResponsibility) continue;
|
|
2732
|
-
const action = agentResponsibility.config.action ?? slug2;
|
|
2733
|
-
const { agentAction, cliArgs } = resolveAgentResponsibilityExecution(agentResponsibility);
|
|
2734
|
-
out.push({
|
|
2735
|
-
action,
|
|
2736
|
-
agentResponsibility: slug2,
|
|
2737
|
-
agentAction,
|
|
2738
|
-
cliArgs,
|
|
2739
|
-
source,
|
|
2740
|
-
describe: agentResponsibility.config.describe ?? agentResponsibility.title,
|
|
2741
|
-
capabilityKind: agentResponsibility.config.capabilityKind,
|
|
2742
|
-
profilePath: agentResponsibility.profilePath,
|
|
2743
|
-
bodyPath: agentResponsibility.bodyPath
|
|
2744
|
-
});
|
|
2745
|
-
}
|
|
2746
|
-
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2747
|
-
}
|
|
2748
|
-
function listBuiltinAgentResponsibilityActions(root = getBuiltinAgentResponsibilitiesRoot()) {
|
|
2749
|
-
if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
|
|
2750
|
-
const out = [];
|
|
2751
|
-
for (const slug2 of listAgentResponsibilityFolderSlugs(root)) {
|
|
2752
|
-
if (!isSafeName(slug2)) continue;
|
|
2753
|
-
const agentResponsibility = readAgentResponsibilityFolder(root, slug2);
|
|
2754
|
-
if (!agentResponsibility) continue;
|
|
2755
|
-
const action = agentResponsibility.config.action ?? slug2;
|
|
2756
|
-
const agentAction = agentResponsibility.config.agentAction ?? slug2;
|
|
2757
|
-
out.push({
|
|
2758
|
-
action,
|
|
2759
|
-
agentResponsibility: slug2,
|
|
2760
|
-
agentAction,
|
|
2761
|
-
cliArgs: {},
|
|
2762
|
-
source: "builtin",
|
|
2763
|
-
describe: agentResponsibility.config.describe ?? agentResponsibility.title,
|
|
2764
|
-
capabilityKind: agentResponsibility.config.capabilityKind,
|
|
2765
|
-
profilePath: agentResponsibility.profilePath,
|
|
2766
|
-
bodyPath: agentResponsibility.bodyPath
|
|
2767
|
-
});
|
|
2768
|
-
}
|
|
2769
|
-
return out.sort((a, b) => a.action.localeCompare(b.action));
|
|
2770
|
-
}
|
|
2771
|
-
function getProfileInputs(name, roots = getAgentActionRoots()) {
|
|
2772
|
-
const profilePath = resolveAgentAction(name, roots);
|
|
2773
|
-
if (!profilePath) return null;
|
|
2774
|
-
try {
|
|
2775
|
-
const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
|
|
2776
|
-
if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
|
|
2777
|
-
return raw.inputs;
|
|
2778
|
-
} catch {
|
|
2779
|
-
return null;
|
|
2780
|
-
}
|
|
2781
|
-
}
|
|
2782
|
-
function parseGenericFlags(argv) {
|
|
2783
|
-
const args = {};
|
|
2784
|
-
const positional = [];
|
|
2785
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2786
|
-
const arg = argv[i];
|
|
2787
|
-
if (!arg.startsWith("--")) {
|
|
2788
|
-
positional.push(arg);
|
|
2789
|
-
continue;
|
|
2791
|
+
finalText = resultTexts.join("\n\n---\n\n");
|
|
2792
|
+
if (outcome === "completed" && sawLoginRequired) {
|
|
2793
|
+
outcome = "failed";
|
|
2794
|
+
outcomeKind = "model_error";
|
|
2795
|
+
errorMessage = "Claude Code reported it is not logged in; refusing to mark agent run successful";
|
|
2790
2796
|
}
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
+
if (outcome === "completed" && !sawMutatingTool) {
|
|
2798
|
+
const backendDead = opts.isBackendHealthy ? !await opts.isBackendHealthy() : false;
|
|
2799
|
+
const zeroOutput = tokens.output === 0 && finalText === "";
|
|
2800
|
+
if (backendDead || zeroOutput) {
|
|
2801
|
+
outcome = "failed";
|
|
2802
|
+
outcomeKind = "model_error";
|
|
2803
|
+
noWorkSuccess = true;
|
|
2804
|
+
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");
|
|
2805
|
+
}
|
|
2797
2806
|
}
|
|
2798
|
-
|
|
2799
|
-
if (
|
|
2800
|
-
|
|
2801
|
-
|
|
2807
|
+
const shouldRetry = outcome === "failed" && attempt < MAX_CONNECTION_RETRIES && !sawMutatingTool && (isTransientConnectionError(errorMessage) || noWorkSuccess);
|
|
2808
|
+
if (!shouldRetry) break;
|
|
2809
|
+
const delayMs = CONNECTION_RETRY_BASE_MS * 2 ** attempt;
|
|
2810
|
+
process.stderr.write(
|
|
2811
|
+
`[kody agent] transient connection error (attempt ${attempt + 1}/${MAX_CONNECTION_RETRIES + 1}); retrying in ${Math.round(delayMs / 1e3)}s: ${errorMessage}
|
|
2812
|
+
`
|
|
2813
|
+
);
|
|
2814
|
+
if (opts.ensureBackend) {
|
|
2815
|
+
try {
|
|
2816
|
+
await opts.ensureBackend();
|
|
2817
|
+
} catch (e) {
|
|
2818
|
+
process.stderr.write(`[kody agent] backend recovery failed: ${e instanceof Error ? e.message : String(e)}
|
|
2819
|
+
`);
|
|
2820
|
+
}
|
|
2802
2821
|
}
|
|
2822
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
2803
2823
|
}
|
|
2804
|
-
|
|
2805
|
-
return
|
|
2824
|
+
const submittedState = getSubmitted?.();
|
|
2825
|
+
return {
|
|
2826
|
+
outcome,
|
|
2827
|
+
outcomeKind,
|
|
2828
|
+
finalText,
|
|
2829
|
+
...submittedState ? { submittedState } : {},
|
|
2830
|
+
error: errorMessage,
|
|
2831
|
+
ndjsonPath,
|
|
2832
|
+
durationMs: Date.now() - startedAt,
|
|
2833
|
+
tokens,
|
|
2834
|
+
messageCount
|
|
2835
|
+
};
|
|
2806
2836
|
}
|
|
2807
|
-
var
|
|
2808
|
-
var
|
|
2809
|
-
"src/
|
|
2837
|
+
var DEFAULT_ALLOWED_TOOLS, DEFAULT_TURN_TIMEOUT_MS, MAX_CONNECTION_RETRIES, CONNECTION_RETRY_BASE_MS, MUTATING_FILE_TOOLS, BASH_WRITE_VERB, AGENT_KEEP_SECRETS;
|
|
2838
|
+
var init_agent = __esm({
|
|
2839
|
+
"src/agent.ts"() {
|
|
2810
2840
|
"use strict";
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2841
|
+
init_claudeBinary();
|
|
2842
|
+
init_config();
|
|
2843
|
+
init_format();
|
|
2844
|
+
DEFAULT_ALLOWED_TOOLS = ["Bash", "Edit", "Read", "Write", "Glob", "Grep"];
|
|
2845
|
+
DEFAULT_TURN_TIMEOUT_MS = 6e5;
|
|
2846
|
+
MAX_CONNECTION_RETRIES = 2;
|
|
2847
|
+
CONNECTION_RETRY_BASE_MS = 2e3;
|
|
2848
|
+
MUTATING_FILE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
2849
|
+
BASH_WRITE_VERB = /\b(git\s+(commit|push|merge|rebase|tag|reset|cherry-pick)|gh\s+(pr|issue|release)\s+(create|comment|edit|close|merge|review|reopen)|gh\s+api\b[^|&]*-X\s*(POST|PUT|PATCH|DELETE)|npm\s+publish)\b/i;
|
|
2850
|
+
AGENT_KEEP_SECRETS = /* @__PURE__ */ new Set([
|
|
2851
|
+
"ANTHROPIC_API_KEY",
|
|
2852
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
2853
|
+
"ANTHROPIC_BASE_URL",
|
|
2854
|
+
"GH_TOKEN",
|
|
2855
|
+
"GITHUB_TOKEN"
|
|
2856
|
+
]);
|
|
2815
2857
|
}
|
|
2816
2858
|
});
|
|
2817
2859
|
|
|
@@ -13227,6 +13269,14 @@ var init_runFlow = __esm({
|
|
|
13227
13269
|
cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES
|
|
13228
13270
|
);
|
|
13229
13271
|
ctx.data.issue = { ...issue, commentsFormatted };
|
|
13272
|
+
if (issue.isPullRequest) {
|
|
13273
|
+
ctx.data.commentTargetType = "pr";
|
|
13274
|
+
ctx.data.commentTargetNumber = issueNumber;
|
|
13275
|
+
ctx.skipAgent = true;
|
|
13276
|
+
ctx.output.exitCode = 1;
|
|
13277
|
+
ctx.output.reason = `run target #${issueNumber} is a pull request; dispatch a PR action or the source issue instead`;
|
|
13278
|
+
return;
|
|
13279
|
+
}
|
|
13230
13280
|
ctx.data.commentTargetType = "issue";
|
|
13231
13281
|
ctx.data.commentTargetNumber = issueNumber;
|
|
13232
13282
|
const argBase = resolveBaseOverride(ctx.args.base);
|