@kody-ade/kody-engine 0.4.366 → 0.4.368
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 +158 -61
- package/package.json +1 -1
package/dist/bin/kody.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "@kody-ade/kody-engine",
|
|
18
|
-
version: "0.4.
|
|
18
|
+
version: "0.4.368",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -2666,10 +2666,34 @@ function readCheckRuns(repoSlug, ref, ignoreNames) {
|
|
|
2666
2666
|
],
|
|
2667
2667
|
ghOptions
|
|
2668
2668
|
);
|
|
2669
|
+
let rawStatuses = "";
|
|
2670
|
+
try {
|
|
2671
|
+
rawStatuses = gh(
|
|
2672
|
+
[
|
|
2673
|
+
"api",
|
|
2674
|
+
`repos/${repoSlug}/commits/${sha}/status`,
|
|
2675
|
+
"--jq",
|
|
2676
|
+
".statuses[] | {context, state, target_url}"
|
|
2677
|
+
],
|
|
2678
|
+
ghOptions
|
|
2679
|
+
);
|
|
2680
|
+
} catch {
|
|
2681
|
+
}
|
|
2669
2682
|
const ignore = new Set(ignoreNames.map((n) => n.toLowerCase()));
|
|
2670
2683
|
const checks = raw.split("\n").map((l) => l.trim()).filter(Boolean).map((l) => JSON.parse(l)).filter((c) => !ignore.has(String(c.name).toLowerCase()));
|
|
2684
|
+
const statuses = rawStatuses.split("\n").map((l) => l.trim()).filter(Boolean).map((l) => JSON.parse(l)).filter((status) => !ignore.has(String(status.context).toLowerCase()));
|
|
2671
2685
|
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 }));
|
|
2686
|
+
failing.push(
|
|
2687
|
+
...statuses.filter((status) => ["error", "failure"].includes(String(status.state).toLowerCase())).map((status) => ({
|
|
2688
|
+
name: status.context,
|
|
2689
|
+
conclusion: status.state,
|
|
2690
|
+
detailsUrl: status.target_url ?? ""
|
|
2691
|
+
}))
|
|
2692
|
+
);
|
|
2672
2693
|
const pending = checks.filter((c) => String(c.status).toLowerCase() !== "completed").map((c) => ({ name: c.name, status: c.status }));
|
|
2694
|
+
pending.push(
|
|
2695
|
+
...statuses.filter((status) => String(status.state).toLowerCase() === "pending").map((status) => ({ name: status.context, status: status.state }))
|
|
2696
|
+
);
|
|
2673
2697
|
const state = failing.length > 0 ? "RED" : pending.length > 0 ? "PENDING" : "GREEN";
|
|
2674
2698
|
return { sha, state, failing, pending };
|
|
2675
2699
|
}
|
|
@@ -21803,6 +21827,26 @@ async function ghApp(jwt, apiPath2, method = "GET") {
|
|
|
21803
21827
|
}
|
|
21804
21828
|
return await res.json();
|
|
21805
21829
|
}
|
|
21830
|
+
async function ghAppPage(authToken, apiPath2) {
|
|
21831
|
+
const res = await fetch(`${GH_API}${apiPath2}`, {
|
|
21832
|
+
headers: {
|
|
21833
|
+
Authorization: `Bearer ${authToken}`,
|
|
21834
|
+
Accept: "application/vnd.github+json",
|
|
21835
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
21836
|
+
"User-Agent": "kody-engine"
|
|
21837
|
+
}
|
|
21838
|
+
});
|
|
21839
|
+
if (!res.ok) {
|
|
21840
|
+
const body = await res.text().catch(() => "");
|
|
21841
|
+
throw new Error(
|
|
21842
|
+
`GitHub App API GET ${apiPath2} \u2192 ${res.status} ${res.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`
|
|
21843
|
+
);
|
|
21844
|
+
}
|
|
21845
|
+
return {
|
|
21846
|
+
data: await res.json(),
|
|
21847
|
+
hasNext: /rel="next"/.test(res.headers.get("link") ?? "")
|
|
21848
|
+
};
|
|
21849
|
+
}
|
|
21806
21850
|
function readAppCreds(env = process.env) {
|
|
21807
21851
|
const appId = env.KODY_APP_ID?.trim();
|
|
21808
21852
|
const privateKey = env.KODY_APP_PRIVATE_KEY;
|
|
@@ -21827,6 +21871,36 @@ async function mintAppInstallationToken(creds) {
|
|
|
21827
21871
|
const tok = await ghApp(jwt, `/app/installations/${installationId}/access_tokens`, "POST");
|
|
21828
21872
|
return tok.token;
|
|
21829
21873
|
}
|
|
21874
|
+
async function discoverAppRepositories(creds) {
|
|
21875
|
+
const jwt = buildAppJwt(creds.appId, creds.privateKey);
|
|
21876
|
+
const installations = [];
|
|
21877
|
+
for (let page = 1; ; page++) {
|
|
21878
|
+
const result = await ghAppPage(jwt, `/app/installations?per_page=100&page=${page}`);
|
|
21879
|
+
installations.push(...result.data.filter((item) => Number.isInteger(item.id) && item.id > 0));
|
|
21880
|
+
if (!result.hasNext) break;
|
|
21881
|
+
}
|
|
21882
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21883
|
+
for (const installation of installations) {
|
|
21884
|
+
const token = await mintAppInstallationToken({
|
|
21885
|
+
appId: creds.appId,
|
|
21886
|
+
privateKey: creds.privateKey,
|
|
21887
|
+
installationId: String(installation.id)
|
|
21888
|
+
});
|
|
21889
|
+
for (let page = 1; ; page++) {
|
|
21890
|
+
const result = await ghAppPage(
|
|
21891
|
+
token,
|
|
21892
|
+
`/installation/repositories?per_page=100&page=${page}`
|
|
21893
|
+
);
|
|
21894
|
+
for (const repository of result.data.repositories ?? []) {
|
|
21895
|
+
const repo = repository.full_name?.trim();
|
|
21896
|
+
if (!repo || !/^[^/\s]+\/[^/\s]+$/.test(repo)) continue;
|
|
21897
|
+
byRepo.set(repo.toLowerCase(), { repo, token });
|
|
21898
|
+
}
|
|
21899
|
+
if (!result.hasNext) break;
|
|
21900
|
+
}
|
|
21901
|
+
}
|
|
21902
|
+
return [...byRepo.values()].sort((left, right) => left.repo.localeCompare(right.repo));
|
|
21903
|
+
}
|
|
21830
21904
|
|
|
21831
21905
|
// src/kody-cli.ts
|
|
21832
21906
|
init_companyStore();
|
|
@@ -24643,67 +24717,49 @@ init_registry();
|
|
|
24643
24717
|
// src/servers/pool-serve.ts
|
|
24644
24718
|
import { createServer as createServer5 } from "http";
|
|
24645
24719
|
|
|
24646
|
-
// src/
|
|
24647
|
-
|
|
24648
|
-
|
|
24649
|
-
|
|
24650
|
-
|
|
24651
|
-
|
|
24652
|
-
try {
|
|
24653
|
-
const res = await fetchImpl(STATUS_URL, { headers: { "User-Agent": "kody-engine" } });
|
|
24654
|
-
if (!res.ok) return { degraded: false, label: `http_${res.status}` };
|
|
24655
|
-
const body = await res.json();
|
|
24656
|
-
const actions = (body.components ?? []).find((c) => (c.name ?? "").trim().toLowerCase() === "actions");
|
|
24657
|
-
const label = actions?.status ?? "unknown";
|
|
24658
|
-
const degraded = !!actions && label !== "operational";
|
|
24659
|
-
const probe = { degraded, label };
|
|
24660
|
-
statusCache = { probe, expiresAt: Date.now() + STATUS_CACHE_TTL_MS };
|
|
24661
|
-
return probe;
|
|
24662
|
-
} catch {
|
|
24663
|
-
return { degraded: false, label: "probe_error" };
|
|
24720
|
+
// src/pool/agency-loop-tick.ts
|
|
24721
|
+
function normalizeRepositories(repositories) {
|
|
24722
|
+
const unique = /* @__PURE__ */ new Set();
|
|
24723
|
+
for (const raw of repositories) {
|
|
24724
|
+
const repo = raw.trim().toLowerCase();
|
|
24725
|
+
if (/^[^/\s]+\/[^/\s]+$/.test(repo)) unique.add(repo);
|
|
24664
24726
|
}
|
|
24727
|
+
return [...unique].sort();
|
|
24665
24728
|
}
|
|
24666
|
-
async function
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
|
|
24671
|
-
async function runCapabilityFallbackTick(deps) {
|
|
24672
|
-
if (!await deps.isDegraded()) {
|
|
24673
|
-
return { ran: false, claimed: 0 };
|
|
24674
|
-
}
|
|
24675
|
-
const repos = deps.activeRepos();
|
|
24676
|
-
if (repos.length === 0) {
|
|
24677
|
-
deps.log("GitHub Actions degraded but no active repo pools \u2014 nothing to tick");
|
|
24678
|
-
return { ran: true, claimed: 0 };
|
|
24729
|
+
async function runAgencyLoopTick(deps) {
|
|
24730
|
+
const repositories = normalizeRepositories(await deps.discover());
|
|
24731
|
+
if (repositories.length === 0) {
|
|
24732
|
+
deps.log("no consumer agencies discovered \u2014 nothing to tick");
|
|
24733
|
+
return { discovered: 0, claimed: 0 };
|
|
24679
24734
|
}
|
|
24680
|
-
deps.log(
|
|
24735
|
+
deps.log(
|
|
24736
|
+
`running scheduled fan-out for ${repositories.length} consumer agenc${repositories.length === 1 ? "y" : "ies"}`
|
|
24737
|
+
);
|
|
24681
24738
|
const clock = deps.now ?? Date.now;
|
|
24682
24739
|
let claimed = 0;
|
|
24683
|
-
for (const
|
|
24684
|
-
const [owner, repo] =
|
|
24685
|
-
if (!owner || !repo) continue;
|
|
24740
|
+
for (const repository of repositories) {
|
|
24741
|
+
const [owner, repo] = repository.split("/");
|
|
24686
24742
|
try {
|
|
24687
|
-
const
|
|
24743
|
+
const result = await deps.claim(owner, repo, {
|
|
24688
24744
|
jobId: `sched-${owner}-${repo}-${clock()}`,
|
|
24689
|
-
repo:
|
|
24745
|
+
repo: repository,
|
|
24690
24746
|
runRequest: {
|
|
24691
24747
|
target: { type: "workflow", id: "scheduled-fanout" },
|
|
24692
24748
|
intent: "tick",
|
|
24693
24749
|
source: "schedule"
|
|
24694
24750
|
}
|
|
24695
24751
|
});
|
|
24696
|
-
if (
|
|
24752
|
+
if (result.ok) {
|
|
24697
24753
|
claimed++;
|
|
24698
|
-
deps.log(`[${
|
|
24754
|
+
deps.log(`[${repository}] scheduled fan-out claimed ${result.machineId}`);
|
|
24699
24755
|
} else {
|
|
24700
|
-
deps.log(`[${
|
|
24756
|
+
deps.log(`[${repository}] scheduled fan-out skipped: ${result.reason ?? "runner unavailable"}`);
|
|
24701
24757
|
}
|
|
24702
|
-
} catch (
|
|
24703
|
-
deps.log(`[${
|
|
24758
|
+
} catch (error) {
|
|
24759
|
+
deps.log(`[${repository}] scheduled fan-out error: ${error instanceof Error ? error.message : String(error)}`);
|
|
24704
24760
|
}
|
|
24705
24761
|
}
|
|
24706
|
-
return {
|
|
24762
|
+
return { discovered: repositories.length, claimed };
|
|
24707
24763
|
}
|
|
24708
24764
|
|
|
24709
24765
|
// src/servers/pool-serve.ts
|
|
@@ -25089,8 +25145,9 @@ var PoolRegistry = class {
|
|
|
25089
25145
|
this.cfg = cfg;
|
|
25090
25146
|
this.log = cfg.log ?? (() => {
|
|
25091
25147
|
});
|
|
25092
|
-
this.
|
|
25093
|
-
|
|
25148
|
+
this.resolveGithubToken = cfg.resolveGithubToken ?? (async () => cfg.githubToken);
|
|
25149
|
+
this.resolveFlyToken = cfg.resolveFlyToken ?? (async (owner, repo) => readRepoSecret({
|
|
25150
|
+
githubToken: await this.resolveGithubToken(owner, repo),
|
|
25094
25151
|
masterKey: cfg.masterKey,
|
|
25095
25152
|
owner,
|
|
25096
25153
|
repo,
|
|
@@ -25098,7 +25155,7 @@ var PoolRegistry = class {
|
|
|
25098
25155
|
}));
|
|
25099
25156
|
this.resolvePoolMin = cfg.resolvePoolMin ?? (async (owner, repo) => parsePoolMin(
|
|
25100
25157
|
await readRepoSecret({
|
|
25101
|
-
githubToken:
|
|
25158
|
+
githubToken: await this.resolveGithubToken(owner, repo),
|
|
25102
25159
|
masterKey: cfg.masterKey,
|
|
25103
25160
|
owner,
|
|
25104
25161
|
repo,
|
|
@@ -25110,6 +25167,7 @@ var PoolRegistry = class {
|
|
|
25110
25167
|
cfg;
|
|
25111
25168
|
pools = /* @__PURE__ */ new Map();
|
|
25112
25169
|
poolCreates = /* @__PURE__ */ new Map();
|
|
25170
|
+
resolveGithubToken;
|
|
25113
25171
|
resolveFlyToken;
|
|
25114
25172
|
resolvePoolMin;
|
|
25115
25173
|
log;
|
|
@@ -25162,10 +25220,17 @@ var PoolRegistry = class {
|
|
|
25162
25220
|
async claim(owner, repo, req) {
|
|
25163
25221
|
const pm = await this.getPool(owner, repo);
|
|
25164
25222
|
if (!pm) return { ok: false, reason: "repo has no FLY_API_TOKEN (no pool)" };
|
|
25223
|
+
let githubToken2;
|
|
25224
|
+
try {
|
|
25225
|
+
githubToken2 = await this.resolveGithubToken(owner, repo);
|
|
25226
|
+
} catch (err) {
|
|
25227
|
+
this.log(`[${this.key(owner, repo)}] repository auth failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
25228
|
+
return { ok: false, reason: "repository authentication failed" };
|
|
25229
|
+
}
|
|
25165
25230
|
let allSecrets = {};
|
|
25166
25231
|
try {
|
|
25167
25232
|
const vault = await readRepoSecrets({
|
|
25168
|
-
githubToken:
|
|
25233
|
+
githubToken: githubToken2,
|
|
25169
25234
|
masterKey: this.cfg.masterKey,
|
|
25170
25235
|
owner,
|
|
25171
25236
|
repo
|
|
@@ -25182,7 +25247,7 @@ var PoolRegistry = class {
|
|
|
25182
25247
|
const job = {
|
|
25183
25248
|
jobId: req.jobId,
|
|
25184
25249
|
repo: `${owner}/${repo}`,
|
|
25185
|
-
githubToken:
|
|
25250
|
+
githubToken: githubToken2,
|
|
25186
25251
|
runRequest: req.runRequest,
|
|
25187
25252
|
issueNumber: req.issueNumber,
|
|
25188
25253
|
sessionId: req.sessionId,
|
|
@@ -25344,8 +25409,24 @@ function synthesizeLegacyClaimRequest(input) {
|
|
|
25344
25409
|
async function poolServe() {
|
|
25345
25410
|
const masterRaw = process.env.KODY_MASTER_KEY?.trim();
|
|
25346
25411
|
if (!masterRaw) throw new Error("KODY_MASTER_KEY required for pool-serve");
|
|
25347
|
-
const
|
|
25348
|
-
|
|
25412
|
+
const appCreds = readAppCreds();
|
|
25413
|
+
const fallbackGithubToken = process.env.GITHUB_TOKEN?.trim() ?? "";
|
|
25414
|
+
if (!appCreds && !fallbackGithubToken) {
|
|
25415
|
+
throw new Error("GitHub App credentials or GITHUB_TOKEN required for pool-serve");
|
|
25416
|
+
}
|
|
25417
|
+
const repoTokens = /* @__PURE__ */ new Map();
|
|
25418
|
+
const resolveGithubToken = async (owner, repo) => {
|
|
25419
|
+
const key = `${owner}/${repo}`.toLowerCase();
|
|
25420
|
+
const discovered = repoTokens.get(key);
|
|
25421
|
+
if (discovered) return discovered;
|
|
25422
|
+
if (appCreds) {
|
|
25423
|
+
const token = await mintAppInstallationToken({ ...appCreds, repo: `${owner}/${repo}` });
|
|
25424
|
+
repoTokens.set(key, token);
|
|
25425
|
+
return token;
|
|
25426
|
+
}
|
|
25427
|
+
if (fallbackGithubToken) return fallbackGithubToken;
|
|
25428
|
+
throw new Error(`no unattended GitHub token for ${key}`);
|
|
25429
|
+
};
|
|
25349
25430
|
const master = masterKeyBytes(masterRaw);
|
|
25350
25431
|
const poolApiKey = derivePoolApiKey(master);
|
|
25351
25432
|
const runnerApiKey = deriveRunnerApiKey(master);
|
|
@@ -25358,7 +25439,8 @@ async function poolServe() {
|
|
|
25358
25439
|
const apiPort = envInt2("POOL_API_PORT", 4100);
|
|
25359
25440
|
const healthTimeoutMs = envInt2("POOL_HEALTH_TIMEOUT_MS", 12e4);
|
|
25360
25441
|
const registry = new PoolRegistry({
|
|
25361
|
-
githubToken:
|
|
25442
|
+
githubToken: fallbackGithubToken,
|
|
25443
|
+
resolveGithubToken,
|
|
25362
25444
|
masterKey: master,
|
|
25363
25445
|
base: {
|
|
25364
25446
|
min,
|
|
@@ -25376,16 +25458,30 @@ async function poolServe() {
|
|
|
25376
25458
|
const tick = setInterval(() => {
|
|
25377
25459
|
registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
25378
25460
|
}, refillMs);
|
|
25379
|
-
const
|
|
25380
|
-
|
|
25381
|
-
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25461
|
+
const discoverAgencies = async () => {
|
|
25462
|
+
if (!appCreds) return registry.activeRepos();
|
|
25463
|
+
const repositories = await discoverAppRepositories(appCreds);
|
|
25464
|
+
for (const access of repositories) repoTokens.set(access.repo.toLowerCase(), access.token);
|
|
25465
|
+
return [.../* @__PURE__ */ new Set([...repositories.map((access) => access.repo), ...registry.activeRepos()])];
|
|
25466
|
+
};
|
|
25467
|
+
let agencyTickInFlight = null;
|
|
25468
|
+
const runLoopTick = () => {
|
|
25469
|
+
if (agencyTickInFlight) return agencyTickInFlight;
|
|
25470
|
+
agencyTickInFlight = runAgencyLoopTick({
|
|
25471
|
+
discover: discoverAgencies,
|
|
25385
25472
|
claim: (owner, repo, req) => registry.claim(owner, repo, req),
|
|
25386
25473
|
log
|
|
25387
|
-
}).catch((err) => log(`
|
|
25388
|
-
|
|
25474
|
+
}).catch((err) => log(`agency Loop tick failed: ${err instanceof Error ? err.message : String(err)}`)).finally(() => {
|
|
25475
|
+
agencyTickInFlight = null;
|
|
25476
|
+
});
|
|
25477
|
+
return agencyTickInFlight;
|
|
25478
|
+
};
|
|
25479
|
+
const loopTickEnabled = (process.env.POOL_LOOP_TICK ?? process.env.POOL_CAPABILITY_TICK ?? "1") !== "0";
|
|
25480
|
+
const loopTickMs = envInt2(
|
|
25481
|
+
process.env.POOL_LOOP_TICK_MS ? "POOL_LOOP_TICK_MS" : "POOL_CAPABILITY_TICK_MS",
|
|
25482
|
+
15 * 6e4
|
|
25483
|
+
);
|
|
25484
|
+
const loopTick = loopTickEnabled ? setInterval(() => void runLoopTick(), loopTickMs) : null;
|
|
25389
25485
|
const server = createServer5(async (req, res) => {
|
|
25390
25486
|
try {
|
|
25391
25487
|
if (!req.method || !req.url) return sendJson2(res, 400, { error: "bad request" });
|
|
@@ -25439,10 +25535,11 @@ async function poolServe() {
|
|
|
25439
25535
|
resolve10();
|
|
25440
25536
|
});
|
|
25441
25537
|
});
|
|
25538
|
+
if (loopTickEnabled) void runLoopTick();
|
|
25442
25539
|
const shutdown = (signal) => {
|
|
25443
25540
|
log(`${signal} \u2014 shutting down`);
|
|
25444
25541
|
clearInterval(tick);
|
|
25445
|
-
if (
|
|
25542
|
+
if (loopTick) clearInterval(loopTick);
|
|
25446
25543
|
server.close(() => process.exit(0));
|
|
25447
25544
|
};
|
|
25448
25545
|
process.once("SIGINT", () => shutdown("SIGINT"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.368",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|