@kody-ade/kody-engine 0.4.367 → 0.4.369

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 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.369",
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",
@@ -1989,6 +1989,7 @@ function parseWorkflowStep(value) {
1989
1989
  const target = stringField(raw.target);
1990
1990
  const targetFact = stringField(raw.targetFact ?? raw.target_fact);
1991
1991
  const cliArgs = raw.cliArgs;
1992
+ const report = parseReportPublication(raw.report);
1992
1993
  return {
1993
1994
  capability,
1994
1995
  ...action && isSafeSlug(action) ? { action } : {},
@@ -2001,7 +2002,35 @@ function parseWorkflowStep(value) {
2001
2002
  ...cliArgs && typeof cliArgs === "object" && !Array.isArray(cliArgs) ? { cliArgs } : {},
2002
2003
  ...isPlainObject(raw.runWhen) ? { runWhen: raw.runWhen } : {},
2003
2004
  ...stringList(raw.continueOn ?? raw.continue_on).length > 0 ? { continueOn: stringList(raw.continueOn ?? raw.continue_on) } : {},
2004
- ...raw.saveReport === true ? { saveReport: true } : {}
2005
+ ...raw.saveReport === true ? { saveReport: true } : {},
2006
+ ...report ? { report } : {}
2007
+ };
2008
+ }
2009
+ function parseReportPublication(value) {
2010
+ if (!isPlainObject(value)) return void 0;
2011
+ const type = stringField(value.type);
2012
+ const owner = stringField(value.owner);
2013
+ if (!type || !/^[a-z0-9][a-z0-9_-]{0,79}$/.test(type) || !owner || !isSafeSlug(owner)) return void 0;
2014
+ const version = typeof value.version === "number" && Number.isInteger(value.version) && value.version > 0 ? value.version : void 0;
2015
+ const slug = stringField(value.slug);
2016
+ const slugFact = stringField(value.slugFact);
2017
+ const title = stringField(value.title);
2018
+ const titleFact = stringField(value.titleFact);
2019
+ const publishWhenFact = stringField(value.publishWhenFact);
2020
+ const reviewStatus = stringField(value.reviewStatus);
2021
+ const reviewArea = stringField(value.reviewArea);
2022
+ if (!slug && !slugFact) return void 0;
2023
+ return {
2024
+ type,
2025
+ ...version ? { version } : {},
2026
+ owner,
2027
+ ...slug ? { slug } : {},
2028
+ ...slugFact ? { slugFact } : {},
2029
+ ...title ? { title } : {},
2030
+ ...titleFact ? { titleFact } : {},
2031
+ ...publishWhenFact ? { publishWhenFact } : {},
2032
+ ...reviewStatus ? { reviewStatus } : {},
2033
+ ...reviewArea ? { reviewArea } : {}
2005
2034
  };
2006
2035
  }
2007
2036
  function isPlainObject(value) {
@@ -15873,6 +15902,106 @@ var init_parseJobStateFromAgentResult = __esm({
15873
15902
  }
15874
15903
  });
15875
15904
 
15905
+ // src/scripts/publishReport.ts
15906
+ function buildRuntimeReportMarkdown(input) {
15907
+ return [
15908
+ "---",
15909
+ `generatedAt: ${yamlString(input.generatedAt)}`,
15910
+ `reportType: ${input.reportType}`,
15911
+ `reportTypeVersion: ${input.reportTypeVersion}`,
15912
+ "producer:",
15913
+ ` model: ${input.owner}`,
15914
+ ` capability: ${input.capability}`,
15915
+ ...input.reviewStatus ? [`reviewStatus: ${input.reviewStatus}`] : [],
15916
+ ...input.reviewArea ? [`reviewArea: ${input.reviewArea}`] : [],
15917
+ "---",
15918
+ `# ${input.title}`,
15919
+ "",
15920
+ input.summary,
15921
+ "",
15922
+ "## Report data",
15923
+ "```json",
15924
+ JSON.stringify(input.data, null, 2),
15925
+ "```",
15926
+ ""
15927
+ ].join("\n");
15928
+ }
15929
+ function parsePublication(value) {
15930
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
15931
+ const raw = value;
15932
+ if (typeof raw.type !== "string" || !SAFE_SLUG.test(raw.type)) return null;
15933
+ if (typeof raw.owner !== "string" || !SAFE_SLUG.test(raw.owner)) return null;
15934
+ return raw;
15935
+ }
15936
+ function latestResult(raw, agentResult) {
15937
+ const results = [];
15938
+ if (Array.isArray(raw)) {
15939
+ for (const item of raw) {
15940
+ const parsed = parseCapabilityResult(item);
15941
+ if (parsed) results.push(parsed);
15942
+ }
15943
+ }
15944
+ if (agentResult?.finalText) results.push(...parseCapabilityResultsFromText(agentResult.finalText));
15945
+ return results.at(-1) ?? null;
15946
+ }
15947
+ function recordField6(value) {
15948
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
15949
+ }
15950
+ function resolveDotted(root, path51) {
15951
+ if (!path51) return void 0;
15952
+ return path51.split(".").reduce((value, key) => recordField6(value)?.[key], root);
15953
+ }
15954
+ function stringValue4(value) {
15955
+ return typeof value === "string" && value.trim() ? value.trim() : null;
15956
+ }
15957
+ function humanize(value) {
15958
+ return value.split(/[-_]+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
15959
+ }
15960
+ function yamlString(value) {
15961
+ return JSON.stringify(value);
15962
+ }
15963
+ var SAFE_SLUG, publishReport;
15964
+ var init_publishReport = __esm({
15965
+ "src/scripts/publishReport.ts"() {
15966
+ "use strict";
15967
+ init_capabilityResult();
15968
+ init_stateRepo();
15969
+ SAFE_SLUG = /^[a-z0-9][a-z0-9_-]{0,79}$/;
15970
+ publishReport = async (ctx, _profile, agentResult) => {
15971
+ const publication = parsePublication(ctx.data.reportPublication);
15972
+ if (!publication) return;
15973
+ const result = latestResult(ctx.data.capabilityResults, agentResult);
15974
+ const stateData = recordField6(recordField6(ctx.data.nextJobState)?.data) ?? {};
15975
+ const data = { ...stateData, ...result?.facts ?? {} };
15976
+ if (publication.publishWhenFact && resolveDotted(data, publication.publishWhenFact) === void 0) return;
15977
+ const slug = publication.slug ?? stringValue4(resolveDotted(data, publication.slugFact));
15978
+ if (!slug || !SAFE_SLUG.test(slug)) return;
15979
+ const title = publication.title ?? stringValue4(resolveDotted(data, publication.titleFact)) ?? humanize(slug);
15980
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
15981
+ const markdown = buildRuntimeReportMarkdown({
15982
+ generatedAt,
15983
+ reportType: publication.type,
15984
+ reportTypeVersion: publication.version ?? 1,
15985
+ owner: publication.owner,
15986
+ capability: stringValue4(ctx.data.jobCapability) ?? stringValue4(ctx.data.capabilitySlug) ?? "unknown",
15987
+ title,
15988
+ summary: result?.summary ?? title,
15989
+ data,
15990
+ ...publication.reviewStatus ? { reviewStatus: publication.reviewStatus } : {},
15991
+ ...publication.reviewArea ? { reviewArea: publication.reviewArea } : {}
15992
+ });
15993
+ const runId = generatedAt.replace(/\.\d{3}Z$/, "Z").replace(/:/g, "-");
15994
+ writeStateText(
15995
+ ctx.config,
15996
+ ctx.cwd,
15997
+ `reports/${slug}/runs/${runId}.md`,
15998
+ markdown,
15999
+ `chore(reports): add ${slug} run`
16000
+ );
16001
+ };
16002
+ }
16003
+ });
16004
+
15876
16005
  // src/scripts/parseReproOutput.ts
15877
16006
  function extractTestPath(text) {
15878
16007
  const m = text.match(/^[\s>*_#`~-]*TEST_PATH[\s>*_#`~-]*\s*:\s*(.+?)\s*$/im);
@@ -19292,6 +19421,7 @@ var init_scripts = __esm({
19292
19421
  init_parseAgentResult();
19293
19422
  init_parseIssueStateFromAgentResult();
19294
19423
  init_parseJobStateFromAgentResult();
19424
+ init_publishReport();
19295
19425
  init_parseReproOutput();
19296
19426
  init_persistArtifacts();
19297
19427
  init_persistFlowState();
@@ -19420,6 +19550,7 @@ var init_scripts = __esm({
19420
19550
  advanceFlow,
19421
19551
  persistFlowState,
19422
19552
  applyCapabilityReports,
19553
+ publishReport,
19423
19554
  recordClassification,
19424
19555
  dispatchClassified,
19425
19556
  notifyTerminal,
@@ -20552,6 +20683,7 @@ var init_executor = __esm({
20552
20683
  "commitAndPush",
20553
20684
  "ensurePr",
20554
20685
  "applyCapabilityReports",
20686
+ "publishReport",
20555
20687
  "openAgentFactoryStatePr"
20556
20688
  ]);
20557
20689
  MAX_CHAIN_HOPS = 60;
@@ -20608,9 +20740,16 @@ function validateJob(input) {
20608
20740
  flavor: j.flavor,
20609
20741
  force: j.force === true,
20610
20742
  saveReport: j.saveReport === true,
20743
+ report: parseReportPublication2(j.report),
20611
20744
  resultTarget: parseCapabilityResultTarget(j.resultTarget)
20612
20745
  };
20613
20746
  }
20747
+ function parseReportPublication2(raw) {
20748
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
20749
+ const report = raw;
20750
+ if (typeof report.type !== "string" || typeof report.owner !== "string") return void 0;
20751
+ return raw;
20752
+ }
20614
20753
  function parseCapabilityResultTarget(raw) {
20615
20754
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
20616
20755
  const target = raw;
@@ -20686,6 +20825,7 @@ async function runCapabilityImplementationStep(valid, profileName, capabilityIde
20686
20825
  preloadedData.selectedImplementation = profileName;
20687
20826
  if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
20688
20827
  if (valid.saveReport === true) preloadedData.jobSaveReport = true;
20828
+ if (valid.report) preloadedData.reportPublication = valid.report;
20689
20829
  if (valid.force === true) preloadedData.jobForce = true;
20690
20830
  if (valid.evidence) preloadedData.capabilityEvidence = { evidence: valid.evidence };
20691
20831
  if (valid.resultTarget) preloadedData.capabilityResultTarget = valid.resultTarget;
@@ -20841,6 +20981,7 @@ function workflowStepToJob(step, parent, chainData) {
20841
20981
  flavor: parent.flavor,
20842
20982
  force: parent.force,
20843
20983
  saveReport: step.saveReport === true || parent.saveReport === true,
20984
+ ...step.report ? { report: step.report } : {},
20844
20985
  ...parent.resultTarget ? { resultTarget: parent.resultTarget } : {}
20845
20986
  };
20846
20987
  }
@@ -21827,6 +21968,26 @@ async function ghApp(jwt, apiPath2, method = "GET") {
21827
21968
  }
21828
21969
  return await res.json();
21829
21970
  }
21971
+ async function ghAppPage(authToken, apiPath2) {
21972
+ const res = await fetch(`${GH_API}${apiPath2}`, {
21973
+ headers: {
21974
+ Authorization: `Bearer ${authToken}`,
21975
+ Accept: "application/vnd.github+json",
21976
+ "X-GitHub-Api-Version": "2022-11-28",
21977
+ "User-Agent": "kody-engine"
21978
+ }
21979
+ });
21980
+ if (!res.ok) {
21981
+ const body = await res.text().catch(() => "");
21982
+ throw new Error(
21983
+ `GitHub App API GET ${apiPath2} \u2192 ${res.status} ${res.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`
21984
+ );
21985
+ }
21986
+ return {
21987
+ data: await res.json(),
21988
+ hasNext: /rel="next"/.test(res.headers.get("link") ?? "")
21989
+ };
21990
+ }
21830
21991
  function readAppCreds(env = process.env) {
21831
21992
  const appId = env.KODY_APP_ID?.trim();
21832
21993
  const privateKey = env.KODY_APP_PRIVATE_KEY;
@@ -21851,6 +22012,36 @@ async function mintAppInstallationToken(creds) {
21851
22012
  const tok = await ghApp(jwt, `/app/installations/${installationId}/access_tokens`, "POST");
21852
22013
  return tok.token;
21853
22014
  }
22015
+ async function discoverAppRepositories(creds) {
22016
+ const jwt = buildAppJwt(creds.appId, creds.privateKey);
22017
+ const installations = [];
22018
+ for (let page = 1; ; page++) {
22019
+ const result = await ghAppPage(jwt, `/app/installations?per_page=100&page=${page}`);
22020
+ installations.push(...result.data.filter((item) => Number.isInteger(item.id) && item.id > 0));
22021
+ if (!result.hasNext) break;
22022
+ }
22023
+ const byRepo = /* @__PURE__ */ new Map();
22024
+ for (const installation of installations) {
22025
+ const token = await mintAppInstallationToken({
22026
+ appId: creds.appId,
22027
+ privateKey: creds.privateKey,
22028
+ installationId: String(installation.id)
22029
+ });
22030
+ for (let page = 1; ; page++) {
22031
+ const result = await ghAppPage(
22032
+ token,
22033
+ `/installation/repositories?per_page=100&page=${page}`
22034
+ );
22035
+ for (const repository of result.data.repositories ?? []) {
22036
+ const repo = repository.full_name?.trim();
22037
+ if (!repo || !/^[^/\s]+\/[^/\s]+$/.test(repo)) continue;
22038
+ byRepo.set(repo.toLowerCase(), { repo, token });
22039
+ }
22040
+ if (!result.hasNext) break;
22041
+ }
22042
+ }
22043
+ return [...byRepo.values()].sort((left, right) => left.repo.localeCompare(right.repo));
22044
+ }
21854
22045
 
21855
22046
  // src/kody-cli.ts
21856
22047
  init_companyStore();
@@ -24667,67 +24858,49 @@ init_registry();
24667
24858
  // src/servers/pool-serve.ts
24668
24859
  import { createServer as createServer5 } from "http";
24669
24860
 
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" };
24861
+ // src/pool/agency-loop-tick.ts
24862
+ function normalizeRepositories(repositories) {
24863
+ const unique = /* @__PURE__ */ new Set();
24864
+ for (const raw of repositories) {
24865
+ const repo = raw.trim().toLowerCase();
24866
+ if (/^[^/\s]+\/[^/\s]+$/.test(repo)) unique.add(repo);
24688
24867
  }
24868
+ return [...unique].sort();
24689
24869
  }
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 };
24870
+ async function runAgencyLoopTick(deps) {
24871
+ const repositories = normalizeRepositories(await deps.discover());
24872
+ if (repositories.length === 0) {
24873
+ deps.log("no consumer agencies discovered \u2014 nothing to tick");
24874
+ return { discovered: 0, claimed: 0 };
24703
24875
  }
24704
- deps.log(`GitHub Actions degraded \u2014 running scheduled fan-out on Fly for ${repos.length} repo(s)`);
24876
+ deps.log(
24877
+ `running scheduled fan-out for ${repositories.length} consumer agenc${repositories.length === 1 ? "y" : "ies"}`
24878
+ );
24705
24879
  const clock = deps.now ?? Date.now;
24706
24880
  let claimed = 0;
24707
- for (const tag of repos) {
24708
- const [owner, repo] = tag.split("/");
24709
- if (!owner || !repo) continue;
24881
+ for (const repository of repositories) {
24882
+ const [owner, repo] = repository.split("/");
24710
24883
  try {
24711
- const res = await deps.claim(owner, repo, {
24884
+ const result = await deps.claim(owner, repo, {
24712
24885
  jobId: `sched-${owner}-${repo}-${clock()}`,
24713
- repo: tag,
24886
+ repo: repository,
24714
24887
  runRequest: {
24715
24888
  target: { type: "workflow", id: "scheduled-fanout" },
24716
24889
  intent: "tick",
24717
24890
  source: "schedule"
24718
24891
  }
24719
24892
  });
24720
- if (res.ok) {
24893
+ if (result.ok) {
24721
24894
  claimed++;
24722
- deps.log(`[${tag}] scheduled fan-out claimed ${res.machineId}`);
24895
+ deps.log(`[${repository}] scheduled fan-out claimed ${result.machineId}`);
24723
24896
  } else {
24724
- deps.log(`[${tag}] scheduled fan-out skipped: ${res.reason ?? "pool unavailable"}`);
24897
+ deps.log(`[${repository}] scheduled fan-out skipped: ${result.reason ?? "runner unavailable"}`);
24725
24898
  }
24726
- } catch (err) {
24727
- deps.log(`[${tag}] scheduled fan-out error: ${err instanceof Error ? err.message : String(err)}`);
24899
+ } catch (error) {
24900
+ deps.log(`[${repository}] scheduled fan-out error: ${error instanceof Error ? error.message : String(error)}`);
24728
24901
  }
24729
24902
  }
24730
- return { ran: true, claimed };
24903
+ return { discovered: repositories.length, claimed };
24731
24904
  }
24732
24905
 
24733
24906
  // src/servers/pool-serve.ts
@@ -25113,8 +25286,9 @@ var PoolRegistry = class {
25113
25286
  this.cfg = cfg;
25114
25287
  this.log = cfg.log ?? (() => {
25115
25288
  });
25116
- this.resolveFlyToken = cfg.resolveFlyToken ?? ((owner, repo) => readRepoSecret({
25117
- githubToken: cfg.githubToken,
25289
+ this.resolveGithubToken = cfg.resolveGithubToken ?? (async () => cfg.githubToken);
25290
+ this.resolveFlyToken = cfg.resolveFlyToken ?? (async (owner, repo) => readRepoSecret({
25291
+ githubToken: await this.resolveGithubToken(owner, repo),
25118
25292
  masterKey: cfg.masterKey,
25119
25293
  owner,
25120
25294
  repo,
@@ -25122,7 +25296,7 @@ var PoolRegistry = class {
25122
25296
  }));
25123
25297
  this.resolvePoolMin = cfg.resolvePoolMin ?? (async (owner, repo) => parsePoolMin(
25124
25298
  await readRepoSecret({
25125
- githubToken: cfg.githubToken,
25299
+ githubToken: await this.resolveGithubToken(owner, repo),
25126
25300
  masterKey: cfg.masterKey,
25127
25301
  owner,
25128
25302
  repo,
@@ -25134,6 +25308,7 @@ var PoolRegistry = class {
25134
25308
  cfg;
25135
25309
  pools = /* @__PURE__ */ new Map();
25136
25310
  poolCreates = /* @__PURE__ */ new Map();
25311
+ resolveGithubToken;
25137
25312
  resolveFlyToken;
25138
25313
  resolvePoolMin;
25139
25314
  log;
@@ -25186,10 +25361,17 @@ var PoolRegistry = class {
25186
25361
  async claim(owner, repo, req) {
25187
25362
  const pm = await this.getPool(owner, repo);
25188
25363
  if (!pm) return { ok: false, reason: "repo has no FLY_API_TOKEN (no pool)" };
25364
+ let githubToken2;
25365
+ try {
25366
+ githubToken2 = await this.resolveGithubToken(owner, repo);
25367
+ } catch (err) {
25368
+ this.log(`[${this.key(owner, repo)}] repository auth failed: ${err instanceof Error ? err.message : String(err)}`);
25369
+ return { ok: false, reason: "repository authentication failed" };
25370
+ }
25189
25371
  let allSecrets = {};
25190
25372
  try {
25191
25373
  const vault = await readRepoSecrets({
25192
- githubToken: this.cfg.githubToken,
25374
+ githubToken: githubToken2,
25193
25375
  masterKey: this.cfg.masterKey,
25194
25376
  owner,
25195
25377
  repo
@@ -25206,7 +25388,7 @@ var PoolRegistry = class {
25206
25388
  const job = {
25207
25389
  jobId: req.jobId,
25208
25390
  repo: `${owner}/${repo}`,
25209
- githubToken: this.cfg.githubToken,
25391
+ githubToken: githubToken2,
25210
25392
  runRequest: req.runRequest,
25211
25393
  issueNumber: req.issueNumber,
25212
25394
  sessionId: req.sessionId,
@@ -25368,8 +25550,24 @@ function synthesizeLegacyClaimRequest(input) {
25368
25550
  async function poolServe() {
25369
25551
  const masterRaw = process.env.KODY_MASTER_KEY?.trim();
25370
25552
  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)");
25553
+ const appCreds = readAppCreds();
25554
+ const fallbackGithubToken = process.env.GITHUB_TOKEN?.trim() ?? "";
25555
+ if (!appCreds && !fallbackGithubToken) {
25556
+ throw new Error("GitHub App credentials or GITHUB_TOKEN required for pool-serve");
25557
+ }
25558
+ const repoTokens = /* @__PURE__ */ new Map();
25559
+ const resolveGithubToken = async (owner, repo) => {
25560
+ const key = `${owner}/${repo}`.toLowerCase();
25561
+ const discovered = repoTokens.get(key);
25562
+ if (discovered) return discovered;
25563
+ if (appCreds) {
25564
+ const token = await mintAppInstallationToken({ ...appCreds, repo: `${owner}/${repo}` });
25565
+ repoTokens.set(key, token);
25566
+ return token;
25567
+ }
25568
+ if (fallbackGithubToken) return fallbackGithubToken;
25569
+ throw new Error(`no unattended GitHub token for ${key}`);
25570
+ };
25373
25571
  const master = masterKeyBytes(masterRaw);
25374
25572
  const poolApiKey = derivePoolApiKey(master);
25375
25573
  const runnerApiKey = deriveRunnerApiKey(master);
@@ -25382,7 +25580,8 @@ async function poolServe() {
25382
25580
  const apiPort = envInt2("POOL_API_PORT", 4100);
25383
25581
  const healthTimeoutMs = envInt2("POOL_HEALTH_TIMEOUT_MS", 12e4);
25384
25582
  const registry = new PoolRegistry({
25385
- githubToken: githubToken2,
25583
+ githubToken: fallbackGithubToken,
25584
+ resolveGithubToken,
25386
25585
  masterKey: master,
25387
25586
  base: {
25388
25587
  min,
@@ -25400,16 +25599,30 @@ async function poolServe() {
25400
25599
  const tick = setInterval(() => {
25401
25600
  registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
25402
25601
  }, 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(),
25602
+ const discoverAgencies = async () => {
25603
+ if (!appCreds) return registry.activeRepos();
25604
+ const repositories = await discoverAppRepositories(appCreds);
25605
+ for (const access of repositories) repoTokens.set(access.repo.toLowerCase(), access.token);
25606
+ return [.../* @__PURE__ */ new Set([...repositories.map((access) => access.repo), ...registry.activeRepos()])];
25607
+ };
25608
+ let agencyTickInFlight = null;
25609
+ const runLoopTick = () => {
25610
+ if (agencyTickInFlight) return agencyTickInFlight;
25611
+ agencyTickInFlight = runAgencyLoopTick({
25612
+ discover: discoverAgencies,
25409
25613
  claim: (owner, repo, req) => registry.claim(owner, repo, req),
25410
25614
  log
25411
- }).catch((err) => log(`capability fallback tick failed: ${err instanceof Error ? err.message : String(err)}`));
25412
- }, capabilityTickMs) : null;
25615
+ }).catch((err) => log(`agency Loop tick failed: ${err instanceof Error ? err.message : String(err)}`)).finally(() => {
25616
+ agencyTickInFlight = null;
25617
+ });
25618
+ return agencyTickInFlight;
25619
+ };
25620
+ const loopTickEnabled = (process.env.POOL_LOOP_TICK ?? process.env.POOL_CAPABILITY_TICK ?? "1") !== "0";
25621
+ const loopTickMs = envInt2(
25622
+ process.env.POOL_LOOP_TICK_MS ? "POOL_LOOP_TICK_MS" : "POOL_CAPABILITY_TICK_MS",
25623
+ 15 * 6e4
25624
+ );
25625
+ const loopTick = loopTickEnabled ? setInterval(() => void runLoopTick(), loopTickMs) : null;
25413
25626
  const server = createServer5(async (req, res) => {
25414
25627
  try {
25415
25628
  if (!req.method || !req.url) return sendJson2(res, 400, { error: "bad request" });
@@ -25463,10 +25676,11 @@ async function poolServe() {
25463
25676
  resolve10();
25464
25677
  });
25465
25678
  });
25679
+ if (loopTickEnabled) void runLoopTick();
25466
25680
  const shutdown = (signal) => {
25467
25681
  log(`${signal} \u2014 shutting down`);
25468
25682
  clearInterval(tick);
25469
- if (capabilityTick) clearInterval(capabilityTick);
25683
+ if (loopTick) clearInterval(loopTick);
25470
25684
  server.close(() => process.exit(0));
25471
25685
  };
25472
25686
  process.once("SIGINT", () => shutdown("SIGINT"));
@@ -500,6 +500,19 @@ export type AnyScript = PreflightScript | PostflightScript
500
500
 
501
501
  export type JobFlavor = "instant" | "scheduled"
502
502
 
503
+ export interface ReportPublicationConfig {
504
+ type: string
505
+ version?: number
506
+ owner: string
507
+ slug?: string
508
+ slugFact?: string
509
+ title?: string
510
+ titleFact?: string
511
+ publishWhenFact?: string
512
+ reviewStatus?: string
513
+ reviewArea?: string
514
+ }
515
+
503
516
  export interface Job {
504
517
  /** Public action the user/operator invoked. Mirrors the capability action. */
505
518
  action?: string
@@ -530,6 +543,8 @@ export interface Job {
530
543
  force?: boolean
531
544
  /** Ask the owning goal/loop to write a report run after its persisted decision. */
532
545
  saveReport?: boolean
546
+ /** Workflow-owned request for the runtime to publish typed capability output. */
547
+ report?: ReportPublicationConfig
533
548
  /** Internal parent context used by postflights to attach neutral capability output. */
534
549
  resultTarget?: CapabilityResultTarget
535
550
  }
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.369",
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",