@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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +134 -61
  2. 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.367",
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/github-health.ts
24671
- var STATUS_URL = "https://www.githubstatus.com/api/v2/components.json";
24672
- var STATUS_CACHE_TTL_MS = 3e4;
24673
- var statusCache = null;
24674
- async function probeActionsStatus(fetchImpl = fetch) {
24675
- if (statusCache && statusCache.expiresAt > Date.now()) return statusCache.probe;
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 gitHubActionsDegraded(fetchImpl = fetch) {
24691
- return (await probeActionsStatus(fetchImpl)).degraded;
24692
- }
24693
-
24694
- // src/pool/capability-fallback-tick.ts
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(`GitHub Actions degraded \u2014 running scheduled fan-out on Fly for ${repos.length} repo(s)`);
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 tag of repos) {
24708
- const [owner, repo] = tag.split("/");
24709
- if (!owner || !repo) continue;
24740
+ for (const repository of repositories) {
24741
+ const [owner, repo] = repository.split("/");
24710
24742
  try {
24711
- const res = await deps.claim(owner, repo, {
24743
+ const result = await deps.claim(owner, repo, {
24712
24744
  jobId: `sched-${owner}-${repo}-${clock()}`,
24713
- repo: tag,
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 (res.ok) {
24752
+ if (result.ok) {
24721
24753
  claimed++;
24722
- deps.log(`[${tag}] scheduled fan-out claimed ${res.machineId}`);
24754
+ deps.log(`[${repository}] scheduled fan-out claimed ${result.machineId}`);
24723
24755
  } else {
24724
- deps.log(`[${tag}] scheduled fan-out skipped: ${res.reason ?? "pool unavailable"}`);
24756
+ deps.log(`[${repository}] scheduled fan-out skipped: ${result.reason ?? "runner unavailable"}`);
24725
24757
  }
24726
- } catch (err) {
24727
- deps.log(`[${tag}] scheduled fan-out error: ${err instanceof Error ? err.message : String(err)}`);
24758
+ } catch (error) {
24759
+ deps.log(`[${repository}] scheduled fan-out error: ${error instanceof Error ? error.message : String(error)}`);
24728
24760
  }
24729
24761
  }
24730
- return { ran: true, claimed };
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.resolveFlyToken = cfg.resolveFlyToken ?? ((owner, repo) => readRepoSecret({
25117
- githubToken: cfg.githubToken,
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: cfg.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: this.cfg.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: this.cfg.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 githubToken2 = process.env.GITHUB_TOKEN?.trim();
25372
- if (!githubToken2) throw new Error("GITHUB_TOKEN required for pool-serve (reads per-repo vaults)");
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: githubToken2,
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 capabilityTickEnabled = (process.env.POOL_CAPABILITY_TICK ?? "1") !== "0";
25404
- const capabilityTickMs = envInt2("POOL_CAPABILITY_TICK_MS", 15 * 6e4);
25405
- const capabilityTick = capabilityTickEnabled ? setInterval(() => {
25406
- runCapabilityFallbackTick({
25407
- isDegraded: () => gitHubActionsDegraded(),
25408
- activeRepos: () => registry.activeRepos(),
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(`capability fallback tick failed: ${err instanceof Error ? err.message : String(err)}`));
25412
- }, capabilityTickMs) : null;
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 (capabilityTick) clearInterval(capabilityTick);
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.367",
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",