@kody-ade/kody-engine 0.4.367 → 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 +134 -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",
|
|
@@ -21827,6 +21827,26 @@ async function ghApp(jwt, apiPath2, method = "GET") {
|
|
|
21827
21827
|
}
|
|
21828
21828
|
return await res.json();
|
|
21829
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
|
+
}
|
|
21830
21850
|
function readAppCreds(env = process.env) {
|
|
21831
21851
|
const appId = env.KODY_APP_ID?.trim();
|
|
21832
21852
|
const privateKey = env.KODY_APP_PRIVATE_KEY;
|
|
@@ -21851,6 +21871,36 @@ async function mintAppInstallationToken(creds) {
|
|
|
21851
21871
|
const tok = await ghApp(jwt, `/app/installations/${installationId}/access_tokens`, "POST");
|
|
21852
21872
|
return tok.token;
|
|
21853
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
|
+
}
|
|
21854
21904
|
|
|
21855
21905
|
// src/kody-cli.ts
|
|
21856
21906
|
init_companyStore();
|
|
@@ -24667,67 +24717,49 @@ init_registry();
|
|
|
24667
24717
|
// src/servers/pool-serve.ts
|
|
24668
24718
|
import { createServer as createServer5 } from "http";
|
|
24669
24719
|
|
|
24670
|
-
// src/
|
|
24671
|
-
|
|
24672
|
-
|
|
24673
|
-
|
|
24674
|
-
|
|
24675
|
-
|
|
24676
|
-
try {
|
|
24677
|
-
const res = await fetchImpl(STATUS_URL, { headers: { "User-Agent": "kody-engine" } });
|
|
24678
|
-
if (!res.ok) return { degraded: false, label: `http_${res.status}` };
|
|
24679
|
-
const body = await res.json();
|
|
24680
|
-
const actions = (body.components ?? []).find((c) => (c.name ?? "").trim().toLowerCase() === "actions");
|
|
24681
|
-
const label = actions?.status ?? "unknown";
|
|
24682
|
-
const degraded = !!actions && label !== "operational";
|
|
24683
|
-
const probe = { degraded, label };
|
|
24684
|
-
statusCache = { probe, expiresAt: Date.now() + STATUS_CACHE_TTL_MS };
|
|
24685
|
-
return probe;
|
|
24686
|
-
} catch {
|
|
24687
|
-
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);
|
|
24688
24726
|
}
|
|
24727
|
+
return [...unique].sort();
|
|
24689
24728
|
}
|
|
24690
|
-
async function
|
|
24691
|
-
|
|
24692
|
-
|
|
24693
|
-
|
|
24694
|
-
|
|
24695
|
-
async function runCapabilityFallbackTick(deps) {
|
|
24696
|
-
if (!await deps.isDegraded()) {
|
|
24697
|
-
return { ran: false, claimed: 0 };
|
|
24698
|
-
}
|
|
24699
|
-
const repos = deps.activeRepos();
|
|
24700
|
-
if (repos.length === 0) {
|
|
24701
|
-
deps.log("GitHub Actions degraded but no active repo pools \u2014 nothing to tick");
|
|
24702
|
-
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 };
|
|
24703
24734
|
}
|
|
24704
|
-
deps.log(
|
|
24735
|
+
deps.log(
|
|
24736
|
+
`running scheduled fan-out for ${repositories.length} consumer agenc${repositories.length === 1 ? "y" : "ies"}`
|
|
24737
|
+
);
|
|
24705
24738
|
const clock = deps.now ?? Date.now;
|
|
24706
24739
|
let claimed = 0;
|
|
24707
|
-
for (const
|
|
24708
|
-
const [owner, repo] =
|
|
24709
|
-
if (!owner || !repo) continue;
|
|
24740
|
+
for (const repository of repositories) {
|
|
24741
|
+
const [owner, repo] = repository.split("/");
|
|
24710
24742
|
try {
|
|
24711
|
-
const
|
|
24743
|
+
const result = await deps.claim(owner, repo, {
|
|
24712
24744
|
jobId: `sched-${owner}-${repo}-${clock()}`,
|
|
24713
|
-
repo:
|
|
24745
|
+
repo: repository,
|
|
24714
24746
|
runRequest: {
|
|
24715
24747
|
target: { type: "workflow", id: "scheduled-fanout" },
|
|
24716
24748
|
intent: "tick",
|
|
24717
24749
|
source: "schedule"
|
|
24718
24750
|
}
|
|
24719
24751
|
});
|
|
24720
|
-
if (
|
|
24752
|
+
if (result.ok) {
|
|
24721
24753
|
claimed++;
|
|
24722
|
-
deps.log(`[${
|
|
24754
|
+
deps.log(`[${repository}] scheduled fan-out claimed ${result.machineId}`);
|
|
24723
24755
|
} else {
|
|
24724
|
-
deps.log(`[${
|
|
24756
|
+
deps.log(`[${repository}] scheduled fan-out skipped: ${result.reason ?? "runner unavailable"}`);
|
|
24725
24757
|
}
|
|
24726
|
-
} catch (
|
|
24727
|
-
deps.log(`[${
|
|
24758
|
+
} catch (error) {
|
|
24759
|
+
deps.log(`[${repository}] scheduled fan-out error: ${error instanceof Error ? error.message : String(error)}`);
|
|
24728
24760
|
}
|
|
24729
24761
|
}
|
|
24730
|
-
return {
|
|
24762
|
+
return { discovered: repositories.length, claimed };
|
|
24731
24763
|
}
|
|
24732
24764
|
|
|
24733
24765
|
// src/servers/pool-serve.ts
|
|
@@ -25113,8 +25145,9 @@ var PoolRegistry = class {
|
|
|
25113
25145
|
this.cfg = cfg;
|
|
25114
25146
|
this.log = cfg.log ?? (() => {
|
|
25115
25147
|
});
|
|
25116
|
-
this.
|
|
25117
|
-
|
|
25148
|
+
this.resolveGithubToken = cfg.resolveGithubToken ?? (async () => cfg.githubToken);
|
|
25149
|
+
this.resolveFlyToken = cfg.resolveFlyToken ?? (async (owner, repo) => readRepoSecret({
|
|
25150
|
+
githubToken: await this.resolveGithubToken(owner, repo),
|
|
25118
25151
|
masterKey: cfg.masterKey,
|
|
25119
25152
|
owner,
|
|
25120
25153
|
repo,
|
|
@@ -25122,7 +25155,7 @@ var PoolRegistry = class {
|
|
|
25122
25155
|
}));
|
|
25123
25156
|
this.resolvePoolMin = cfg.resolvePoolMin ?? (async (owner, repo) => parsePoolMin(
|
|
25124
25157
|
await readRepoSecret({
|
|
25125
|
-
githubToken:
|
|
25158
|
+
githubToken: await this.resolveGithubToken(owner, repo),
|
|
25126
25159
|
masterKey: cfg.masterKey,
|
|
25127
25160
|
owner,
|
|
25128
25161
|
repo,
|
|
@@ -25134,6 +25167,7 @@ var PoolRegistry = class {
|
|
|
25134
25167
|
cfg;
|
|
25135
25168
|
pools = /* @__PURE__ */ new Map();
|
|
25136
25169
|
poolCreates = /* @__PURE__ */ new Map();
|
|
25170
|
+
resolveGithubToken;
|
|
25137
25171
|
resolveFlyToken;
|
|
25138
25172
|
resolvePoolMin;
|
|
25139
25173
|
log;
|
|
@@ -25186,10 +25220,17 @@ var PoolRegistry = class {
|
|
|
25186
25220
|
async claim(owner, repo, req) {
|
|
25187
25221
|
const pm = await this.getPool(owner, repo);
|
|
25188
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
|
+
}
|
|
25189
25230
|
let allSecrets = {};
|
|
25190
25231
|
try {
|
|
25191
25232
|
const vault = await readRepoSecrets({
|
|
25192
|
-
githubToken:
|
|
25233
|
+
githubToken: githubToken2,
|
|
25193
25234
|
masterKey: this.cfg.masterKey,
|
|
25194
25235
|
owner,
|
|
25195
25236
|
repo
|
|
@@ -25206,7 +25247,7 @@ var PoolRegistry = class {
|
|
|
25206
25247
|
const job = {
|
|
25207
25248
|
jobId: req.jobId,
|
|
25208
25249
|
repo: `${owner}/${repo}`,
|
|
25209
|
-
githubToken:
|
|
25250
|
+
githubToken: githubToken2,
|
|
25210
25251
|
runRequest: req.runRequest,
|
|
25211
25252
|
issueNumber: req.issueNumber,
|
|
25212
25253
|
sessionId: req.sessionId,
|
|
@@ -25368,8 +25409,24 @@ function synthesizeLegacyClaimRequest(input) {
|
|
|
25368
25409
|
async function poolServe() {
|
|
25369
25410
|
const masterRaw = process.env.KODY_MASTER_KEY?.trim();
|
|
25370
25411
|
if (!masterRaw) throw new Error("KODY_MASTER_KEY required for pool-serve");
|
|
25371
|
-
const
|
|
25372
|
-
|
|
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
|
+
};
|
|
25373
25430
|
const master = masterKeyBytes(masterRaw);
|
|
25374
25431
|
const poolApiKey = derivePoolApiKey(master);
|
|
25375
25432
|
const runnerApiKey = deriveRunnerApiKey(master);
|
|
@@ -25382,7 +25439,8 @@ async function poolServe() {
|
|
|
25382
25439
|
const apiPort = envInt2("POOL_API_PORT", 4100);
|
|
25383
25440
|
const healthTimeoutMs = envInt2("POOL_HEALTH_TIMEOUT_MS", 12e4);
|
|
25384
25441
|
const registry = new PoolRegistry({
|
|
25385
|
-
githubToken:
|
|
25442
|
+
githubToken: fallbackGithubToken,
|
|
25443
|
+
resolveGithubToken,
|
|
25386
25444
|
masterKey: master,
|
|
25387
25445
|
base: {
|
|
25388
25446
|
min,
|
|
@@ -25400,16 +25458,30 @@ async function poolServe() {
|
|
|
25400
25458
|
const tick = setInterval(() => {
|
|
25401
25459
|
registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
25402
25460
|
}, refillMs);
|
|
25403
|
-
const
|
|
25404
|
-
|
|
25405
|
-
|
|
25406
|
-
|
|
25407
|
-
|
|
25408
|
-
|
|
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,
|
|
25409
25472
|
claim: (owner, repo, req) => registry.claim(owner, repo, req),
|
|
25410
25473
|
log
|
|
25411
|
-
}).catch((err) => log(`
|
|
25412
|
-
|
|
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;
|
|
25413
25485
|
const server = createServer5(async (req, res) => {
|
|
25414
25486
|
try {
|
|
25415
25487
|
if (!req.method || !req.url) return sendJson2(res, 400, { error: "bad request" });
|
|
@@ -25463,10 +25535,11 @@ async function poolServe() {
|
|
|
25463
25535
|
resolve10();
|
|
25464
25536
|
});
|
|
25465
25537
|
});
|
|
25538
|
+
if (loopTickEnabled) void runLoopTick();
|
|
25466
25539
|
const shutdown = (signal) => {
|
|
25467
25540
|
log(`${signal} \u2014 shutting down`);
|
|
25468
25541
|
clearInterval(tick);
|
|
25469
|
-
if (
|
|
25542
|
+
if (loopTick) clearInterval(loopTick);
|
|
25470
25543
|
server.close(() => process.exit(0));
|
|
25471
25544
|
};
|
|
25472
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",
|