@kody-ade/kody-engine 0.4.146 → 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 +82 -9
  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.146",
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",
@@ -10211,6 +10211,65 @@ function timingEqual(a, b) {
10211
10211
  return diff === 0;
10212
10212
  }
10213
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
+
10214
10273
  // src/scripts/poolServe.ts
10215
10274
  var PERF_GUEST = {
10216
10275
  low: { cpu_kind: "shared", cpus: 2, memory_mb: 2048 },
@@ -10336,6 +10395,16 @@ var poolServe = async (ctx) => {
10336
10395
  const tick = setInterval(() => {
10337
10396
  registry.resyncAll().catch((err) => log(`resync tick failed: ${err instanceof Error ? err.message : String(err)}`));
10338
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;
10339
10408
  const server = createServer2(async (req, res) => {
10340
10409
  try {
10341
10410
  if (!req.method || !req.url) return sendJson2(res, 400, { error: "bad request" });
@@ -10393,6 +10462,7 @@ var poolServe = async (ctx) => {
10393
10462
  const shutdown = (signal) => {
10394
10463
  log(`${signal} \u2014 shutting down`);
10395
10464
  clearInterval(tick);
10465
+ if (dutyTick) clearInterval(dutyTick);
10396
10466
  server.close(() => process.exit(0));
10397
10467
  };
10398
10468
  process.once("SIGINT", () => shutdown("SIGINT"));
@@ -11336,7 +11406,7 @@ function parseJob(body) {
11336
11406
  if (!/^[^/\s]+\/[^/\s]+$/.test(repo)) return { error: "repo must be 'owner/name'" };
11337
11407
  const githubToken = typeof b.githubToken === "string" ? b.githubToken.trim() : "";
11338
11408
  if (!githubToken) return { error: "githubToken required" };
11339
- const mode = b.mode === "interactive" ? "interactive" : "issue";
11409
+ const mode = b.mode === "interactive" ? "interactive" : b.mode === "scheduled" ? "scheduled" : "issue";
11340
11410
  const job = { jobId, repo, githubToken, mode };
11341
11411
  if (mode === "issue") {
11342
11412
  const issueNumber = Number(b.issueNumber);
@@ -11344,7 +11414,7 @@ function parseJob(body) {
11344
11414
  return { error: "issueNumber (positive integer) required for issue mode" };
11345
11415
  }
11346
11416
  job.issueNumber = issueNumber;
11347
- } else {
11417
+ } else if (mode === "interactive") {
11348
11418
  const sessionId = typeof b.sessionId === "string" ? b.sessionId.trim() : "";
11349
11419
  if (!sessionId) return { error: "sessionId required for interactive mode" };
11350
11420
  job.sessionId = sessionId;
@@ -11368,11 +11438,15 @@ async function defaultRunJob(job) {
11368
11438
  fs37.mkdirSync(workdir, { recursive: true });
11369
11439
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
11370
11440
  const interactive = job.mode === "interactive";
11441
+ const scheduled = job.mode === "scheduled";
11371
11442
  const childEnv = {
11372
11443
  ...process.env,
11373
11444
  REPO: job.repo,
11374
11445
  REF: branch,
11375
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" } : {},
11376
11450
  // GITHUB_REPOSITORY + GH_TOKEN are normally injected by GitHub Actions.
11377
11451
  // The engine's interactive mode needs GITHUB_REPOSITORY to persist
11378
11452
  // chat.ready / events to .kody/events via the Contents API (the durable
@@ -11382,7 +11456,7 @@ async function defaultRunJob(job) {
11382
11456
  GH_TOKEN: job.githubToken,
11383
11457
  // Issue mode bakes ISSUE_NUMBER → `kody run --issue N`. Interactive mode
11384
11458
  // leaves it empty and sets SESSION_ID so the engine boots a chat session.
11385
- ISSUE_NUMBER: interactive ? "" : String(job.issueNumber),
11459
+ ISSUE_NUMBER: interactive || scheduled ? "" : String(job.issueNumber),
11386
11460
  ALL_SECRETS: allSecrets,
11387
11461
  SESSION_ID: job.sessionId ?? "",
11388
11462
  DASHBOARD_URL: job.dashboardUrl ?? "",
@@ -11420,11 +11494,10 @@ async function defaultRunJob(job) {
11420
11494
  const authorEmail = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
11421
11495
  await run("git", ["config", "user.name", authorName], workdir);
11422
11496
  await run("git", ["config", "user.email", authorEmail], workdir);
11423
- const runArgs = interactive ? [] : ["run", "--issue", String(job.issueNumber)];
11424
- process.stdout.write(
11425
- `[runner-serve] job ${job.jobId}: ${interactive ? `interactive session ${job.sessionId}` : `running issue #${job.issueNumber}`}
11426
- `
11427
- );
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
+ `);
11428
11501
  const runCode = await run("kody", runArgs, workdir);
11429
11502
  process.stdout.write(`[runner-serve] job ${job.jobId}: finished (exit ${runCode})
11430
11503
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.146",
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",