@kody-ade/kody-engine 0.4.532 → 0.4.533

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 +70 -9
  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.532",
18
+ version: "0.4.533",
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",
@@ -2816,6 +2816,15 @@ function createStateBackendFromEnv(env = process.env, client) {
2816
2816
  });
2817
2817
  return result;
2818
2818
  },
2819
+ async renewLoopDispatch(tenantId2, idempotencyKey, reservationId, leaseUntil, now) {
2820
+ await transport.mutation(anyApi.agencyModel.renewDispatch, {
2821
+ tenantId: requireTenant(tenantId2),
2822
+ idempotencyKey: requireNonEmpty(idempotencyKey, "idempotencyKey"),
2823
+ reservationId: requireNonEmpty(reservationId, "reservationId"),
2824
+ leaseUntil: requireNonEmpty(leaseUntil, "leaseUntil"),
2825
+ now: requireNonEmpty(now, "now")
2826
+ });
2827
+ },
2819
2828
  async finishLoopDispatch(tenantId2, idempotencyKey, reservationId, status, now, runId) {
2820
2829
  await transport.mutation(anyApi.agencyModel.finishDispatch, {
2821
2830
  tenantId: requireTenant(tenantId2),
@@ -13659,9 +13668,11 @@ var init_loopDefinitions = __esm({
13659
13668
  // src/scripts/dispatchLoops.ts
13660
13669
  import { randomUUID } from "crypto";
13661
13670
  function assertLoopDispatchesSucceeded(results) {
13662
- const failed = results.filter((result) => result.status === "failed");
13663
- if (failed.length === 0) return;
13664
- throw new Error(`Loop dispatch failed: ${failed.map((result) => `${result.loopId}: ${result.reason}`).join("; ")}`);
13671
+ const didNotRun = results.filter((result) => result.status === "failed" || result.status === "blocked");
13672
+ if (didNotRun.length === 0) return;
13673
+ throw new Error(
13674
+ `Loop dispatch did not run: ${didNotRun.map((result) => `${result.loopId}: ${result.reason}`).join("; ")}`
13675
+ );
13665
13676
  }
13666
13677
  async function dispatchLoopsWith(input) {
13667
13678
  const results = [];
@@ -13692,7 +13703,9 @@ async function dispatchLoopsWith(input) {
13692
13703
  now: input.now.toISOString()
13693
13704
  });
13694
13705
  if (!claimed.acquired) {
13695
- results.push({ loopId: loop.id, status: "skipped", reason: claimed.reason ?? "already claimed" });
13706
+ const reason2 = claimed.reason ?? "already claimed";
13707
+ const status2 = reason2 === "duplicate" ? "skipped" : "blocked";
13708
+ results.push({ loopId: loop.id, status: status2, reason: reason2 });
13696
13709
  continue;
13697
13710
  }
13698
13711
  const runId = `loop-${loop.id}-${input.nonce()}`;
@@ -13705,15 +13718,32 @@ async function dispatchLoopsWith(input) {
13705
13718
  startedAt
13706
13719
  };
13707
13720
  await input.backend.createAgencyRun(input.tenantId, "loop", loop.id, running, startedAt);
13708
- let exitCode;
13709
- let reason;
13721
+ const stopLeaseRenewal = startLoopLeaseRenewal({
13722
+ backend: input.backend,
13723
+ tenantId: input.tenantId,
13724
+ idempotencyKey,
13725
+ reservationId
13726
+ });
13727
+ let exitCode = 1;
13728
+ let reason = "target did not complete";
13729
+ let runFailed = false;
13710
13730
  try {
13711
13731
  const result = await input.run(loopJob(loop), runId);
13712
13732
  exitCode = result.exitCode;
13713
13733
  reason = result.reason ?? (exitCode === 0 ? "dispatched" : "target failed");
13714
13734
  } catch (error) {
13735
+ runFailed = true;
13715
13736
  exitCode = 1;
13716
13737
  reason = error instanceof Error ? error.message : String(error);
13738
+ } finally {
13739
+ try {
13740
+ await stopLeaseRenewal();
13741
+ } catch (error) {
13742
+ if (!runFailed && exitCode === 0) {
13743
+ exitCode = 1;
13744
+ reason = `Loop reservation renewal failed: ${error instanceof Error ? error.message : String(error)}`;
13745
+ }
13746
+ }
13717
13747
  }
13718
13748
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
13719
13749
  const succeeded = exitCode === 0;
@@ -13733,6 +13763,36 @@ async function dispatchLoopsWith(input) {
13733
13763
  }
13734
13764
  return results;
13735
13765
  }
13766
+ function startLoopLeaseRenewal(input) {
13767
+ let stopped = false;
13768
+ let renewalError;
13769
+ let pending = Promise.resolve();
13770
+ const timer = setInterval(() => {
13771
+ pending = pending.then(async () => {
13772
+ const now = /* @__PURE__ */ new Date();
13773
+ try {
13774
+ await input.backend.renewLoopDispatch(
13775
+ input.tenantId,
13776
+ input.idempotencyKey,
13777
+ input.reservationId,
13778
+ new Date(now.getTime() + LOOP_DISPATCH_LEASE_MS).toISOString(),
13779
+ now.toISOString()
13780
+ );
13781
+ } catch (error) {
13782
+ renewalError ??= error;
13783
+ }
13784
+ });
13785
+ }, LOOP_DISPATCH_RENEW_INTERVAL_MS);
13786
+ timer.unref?.();
13787
+ return async () => {
13788
+ if (!stopped) {
13789
+ stopped = true;
13790
+ clearInterval(timer);
13791
+ }
13792
+ await pending;
13793
+ if (renewalError) throw renewalError;
13794
+ };
13795
+ }
13736
13796
  function selectRunnableLoops(loops, now, options) {
13737
13797
  return loops.filter(
13738
13798
  (loop) => loop.enabled && (!options.loopId || loop.id === options.loopId) && (options.force || loop.trigger.type === "schedule" && dueSlot(loop, now) !== null)
@@ -13781,14 +13841,15 @@ function repositoryTenant(config) {
13781
13841
  const repo = config.github?.repo?.trim() || envRepo?.trim();
13782
13842
  return owner && repo ? `${owner}/${repo}` : null;
13783
13843
  }
13784
- var LOOP_DISPATCH_LEASE_MS, dispatchLoops;
13844
+ var LOOP_DISPATCH_LEASE_MS, LOOP_DISPATCH_RENEW_INTERVAL_MS, dispatchLoops;
13785
13845
  var init_dispatchLoops = __esm({
13786
13846
  "src/scripts/dispatchLoops.ts"() {
13787
13847
  "use strict";
13788
13848
  init_job();
13789
13849
  init_loopDefinitions();
13790
13850
  init_state_backend();
13791
- LOOP_DISPATCH_LEASE_MS = 6 * 60 * 60 * 1e3;
13851
+ LOOP_DISPATCH_LEASE_MS = 10 * 60 * 1e3;
13852
+ LOOP_DISPATCH_RENEW_INTERVAL_MS = 60 * 1e3;
13792
13853
  dispatchLoops = async (ctx) => {
13793
13854
  const tenantId2 = repositoryTenant(ctx.config);
13794
13855
  if (!tenantId2) throw new Error("Repository identity is required for Loop dispatch");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.532",
3
+ "version": "0.4.533",
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",