@kody-ade/kody-engine 0.4.145 → 0.4.147

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 +83 -19
  2. package/package.json +1 -1
package/dist/bin/kody.js CHANGED
@@ -1061,7 +1061,7 @@ var init_loadPriorArt = __esm({
1061
1061
  // package.json
1062
1062
  var package_default = {
1063
1063
  name: "@kody-ade/kody-engine",
1064
- version: "0.4.145",
1064
+ version: "0.4.147",
1065
1065
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
1066
1066
  license: "MIT",
1067
1067
  type: "module",
@@ -2700,7 +2700,6 @@ async function emit2(sink, type, sessionId, suffix, payload) {
2700
2700
 
2701
2701
  // src/kody-cli.ts
2702
2702
  import { execFileSync as execFileSync29 } from "child_process";
2703
- import { createHash } from "crypto";
2704
2703
  import * as fs41 from "fs";
2705
2704
  import * as path37 from "path";
2706
2705
 
@@ -10212,6 +10211,65 @@ function timingEqual(a, b) {
10212
10211
  return diff === 0;
10213
10212
  }
10214
10213
 
10214
+ // src/pool/duty-fallback-tick.ts
10215
+ async function runDutyFallbackTick(deps) {
10216
+ if (!await deps.isDegraded()) {
10217
+ return { ran: false, claimed: 0 };
10218
+ }
10219
+ const repos = deps.activeRepos();
10220
+ if (repos.length === 0) {
10221
+ deps.log("GitHub Actions degraded but no active repo pools \u2014 nothing to tick");
10222
+ return { ran: true, claimed: 0 };
10223
+ }
10224
+ deps.log(`GitHub Actions degraded \u2014 running scheduled fan-out on Fly for ${repos.length} repo(s)`);
10225
+ const clock = deps.now ?? Date.now;
10226
+ let claimed = 0;
10227
+ for (const tag of repos) {
10228
+ const [owner, repo] = tag.split("/");
10229
+ if (!owner || !repo) continue;
10230
+ try {
10231
+ const res = await deps.claim(owner, repo, {
10232
+ jobId: `sched-${owner}-${repo}-${clock()}`,
10233
+ repo: tag,
10234
+ mode: "scheduled"
10235
+ });
10236
+ if (res.ok) {
10237
+ claimed++;
10238
+ deps.log(`[${tag}] scheduled fan-out claimed ${res.machineId}`);
10239
+ } else {
10240
+ deps.log(`[${tag}] scheduled fan-out skipped: ${res.reason ?? "pool unavailable"}`);
10241
+ }
10242
+ } catch (err) {
10243
+ deps.log(`[${tag}] scheduled fan-out error: ${err instanceof Error ? err.message : String(err)}`);
10244
+ }
10245
+ }
10246
+ return { ran: true, claimed };
10247
+ }
10248
+
10249
+ // src/github-health.ts
10250
+ var STATUS_URL = "https://www.githubstatus.com/api/v2/components.json";
10251
+ var STATUS_CACHE_TTL_MS = 3e4;
10252
+ var statusCache = null;
10253
+ async function probeActionsStatus(fetchImpl = fetch) {
10254
+ if (statusCache && statusCache.expiresAt > Date.now()) return statusCache.probe;
10255
+ try {
10256
+ const res = await fetchImpl(STATUS_URL, { headers: { "User-Agent": "kody-engine" } });
10257
+ if (!res.ok) return { degraded: false, label: `http_${res.status}` };
10258
+ const body = await res.json();
10259
+ const actions = (body.components ?? []).find((c) => (c.name ?? "").trim().toLowerCase() === "actions");
10260
+ const label = actions?.status ?? "unknown";
10261
+ const degraded = !!actions && label !== "operational";
10262
+ const probe = { degraded, label };
10263
+ statusCache = { probe, expiresAt: Date.now() + STATUS_CACHE_TTL_MS };
10264
+ return probe;
10265
+ } catch {
10266
+ return { degraded: false, label: "probe_error" };
10267
+ }
10268
+ }
10269
+ async function gitHubActionsDegraded(fetchImpl = fetch) {
10270
+ return (await probeActionsStatus(fetchImpl)).degraded;
10271
+ }
10272
+
10215
10273
  // src/scripts/poolServe.ts
10216
10274
  var PERF_GUEST = {
10217
10275
  low: { cpu_kind: "shared", cpus: 2, memory_mb: 2048 },
@@ -10337,6 +10395,16 @@ var poolServe = async (ctx) => {
10337
10395
  const tick = setInterval(() => {
10338
10396
  registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
10339
10397
  }, refillMs);
10398
+ const dutyTickEnabled = (process.env.POOL_DUTY_TICK ?? "1") !== "0";
10399
+ const dutyTickMs = envInt("POOL_DUTY_TICK_MS", 15 * 6e4);
10400
+ const dutyTick = dutyTickEnabled ? setInterval(() => {
10401
+ runDutyFallbackTick({
10402
+ isDegraded: () => gitHubActionsDegraded(),
10403
+ activeRepos: () => registry.activeRepos(),
10404
+ claim: (owner, repo, req) => registry.claim(owner, repo, req),
10405
+ log
10406
+ }).catch((err) => log(`duty fallback tick failed: ${err instanceof Error ? err.message : String(err)}`));
10407
+ }, dutyTickMs) : null;
10340
10408
  const server = createServer2(async (req, res) => {
10341
10409
  try {
10342
10410
  if (!req.method || !req.url) return sendJson2(res, 400, { error: "bad request" });
@@ -10394,6 +10462,7 @@ var poolServe = async (ctx) => {
10394
10462
  const shutdown = (signal) => {
10395
10463
  log(`${signal} \u2014 shutting down`);
10396
10464
  clearInterval(tick);
10465
+ if (dutyTick) clearInterval(dutyTick);
10397
10466
  server.close(() => process.exit(0));
10398
10467
  };
10399
10468
  process.once("SIGINT", () => shutdown("SIGINT"));
@@ -11337,7 +11406,7 @@ function parseJob(body) {
11337
11406
  if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
11338
11407
  const githubToken = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
11339
11408
  if (!githubToken) return { error: "githubToken required" };
11340
- const mode = b.mode === "interactive" ? "interactive" : "issue";
11409
+ const mode = b.mode === "interactive" ? "interactive" : b.mode === "scheduled" ? "scheduled" : "issue";
11341
11410
  const job = { jobId, repo, githubToken, mode };
11342
11411
  if (mode === "issue") {
11343
11412
  const issueNumber = Number(b.issueNumber);
@@ -11345,7 +11414,7 @@ function parseJob(body) {
11345
11414
  return { error: "issueNumber (positive integer) required for issue mode" };
11346
11415
  }
11347
11416
  job.issueNumber = issueNumber;
11348
- } else {
11417
+ } else if (mode === "interactive") {
11349
11418
  const sessionId = typeof b.sessionId === "string" ? b.sessionId.trim() : "";
11350
11419
  if (!sessionId) return { error: "sessionId required for interactive mode" };
11351
11420
  job.sessionId = sessionId;
@@ -11369,11 +11438,15 @@ async function defaultRunJob(job) {
11369
11438
  fs37.mkdirSync(workdir, { recursive: true });
11370
11439
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
11371
11440
  const interactive = job.mode === "interactive";
11441
+ const scheduled = job.mode === "scheduled";
11372
11442
  const childEnv = {
11373
11443
  ...process.env,
11374
11444
  REPO: job.repo,
11375
11445
  REF: branch,
11376
11446
  GITHUB_TOKEN: job.githubToken,
11447
+ // Scheduled mode drives the engine down the same path GitHub Actions' cron
11448
+ // takes (runScheduledFanOut → due duties/goals). Bare `kody` routes on this.
11449
+ ...scheduled ? { GITHUB_EVENT_NAME: "schedule" } : {},
11377
11450
  // GITHUB_REPOSITORY + GH_TOKEN are normally injected by GitHub Actions.
11378
11451
  // The engine's interactive mode needs GITHUB_REPOSITORY to persist
11379
11452
  // chat.ready / events to .kody/events via the Contents API (the durable
@@ -11383,7 +11456,7 @@ async function defaultRunJob(job) {
11383
11456
  GH_TOKEN: job.githubToken,
11384
11457
  // Issue mode bakes ISSUE_NUMBER → `kody run --issue N`. Interactive mode
11385
11458
  // leaves it empty and sets SESSION_ID so the engine boots a chat session.
11386
- ISSUE_NUMBER: interactive ? "" : String(job.issueNumber),
11459
+ ISSUE_NUMBER: interactive || scheduled ? "" : String(job.issueNumber),
11387
11460
  ALL_SECRETS: allSecrets,
11388
11461
  SESSION_ID: job.sessionId ?? "",
11389
11462
  DASHBOARD_URL: job.dashboardUrl ?? "",
@@ -11421,11 +11494,10 @@ async function defaultRunJob(job) {
11421
11494
  const authorEmail = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
11422
11495
  await run("git", ["config", "user.name", authorName], workdir);
11423
11496
  await run("git", ["config", "user.email", authorEmail], workdir);
11424
- const runArgs = interactive ? [] : ["run", "--issue", String(job.issueNumber)];
11425
- process.stdout.write(
11426
- `[runner-serve] job ${job.jobId}: ${interactive ? `interactive session ${job.sessionId}` : `running issue #${job.issueNumber}`}
11427
- `
11428
- );
11497
+ const runArgs = interactive || scheduled ? [] : ["run", "--issue", String(job.issueNumber)];
11498
+ const jobDesc = interactive ? `interactive session ${job.sessionId}` : scheduled ? "scheduled fan-out" : `running issue #${job.issueNumber}`;
11499
+ process.stdout.write(`[runner-serve] job ${job.jobId}: ${jobDesc}
11500
+ `);
11429
11501
  const runCode = await run("kody", runArgs, workdir);
11430
11502
  process.stdout.write(`[runner-serve] job ${job.jobId}: finished (exit ${runCode})
11431
11503
  `);
@@ -13744,15 +13816,7 @@ function resolveAuthToken(env = process.env) {
13744
13816
  const token = picked?.[1];
13745
13817
  if (token && !env.GH_TOKEN) env.GH_TOKEN = token;
13746
13818
  if (token) {
13747
- const trimmed = token.trim();
13748
- const lenDiff = token.length - trimmed.length;
13749
- const sha = createHash("sha256").update(token).digest("hex");
13750
- const shaTrimmed = createHash("sha256").update(trimmed).digest("hex");
13751
- process.stdout.write(
13752
- `\u2192 kody: GH_TOKEN sourced from env.${picked[0]} (length=${token.length}, prefix=${token.slice(0, 4)}, trailingWhitespace=${lenDiff > 0 ? "YES " + lenDiff + " chars" : "no"})
13753
- `
13754
- );
13755
- process.stdout.write(`\u2192 kody: token sha256=${sha.slice(0, 16)}\u2026${sha.slice(-8)} (trimmed sha=${shaTrimmed.slice(0, 16)}\u2026${shaTrimmed.slice(-8)})
13819
+ process.stdout.write(`\u2192 kody: GH_TOKEN sourced from env.${picked[0]}
13756
13820
  `);
13757
13821
  } else {
13758
13822
  process.stdout.write("\u2192 kody: WARNING no auth token found (KODY_TOKEN/GH_TOKEN/GITHUB_TOKEN/GH_PAT all empty)\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.145",
3
+ "version": "0.4.147",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",