@kody-ade/kody-engine 0.4.217 → 0.4.218

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 +55 -11
  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.217",
18
+ version: "0.4.218",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -17831,6 +17831,7 @@ var PoolManager = class {
17831
17831
  }
17832
17832
  deps;
17833
17833
  free = [];
17834
+ claimed = /* @__PURE__ */ new Set();
17834
17835
  booting = 0;
17835
17836
  claimsInFlight = 0;
17836
17837
  refilling = false;
@@ -17847,16 +17848,15 @@ var PoolManager = class {
17847
17848
  }
17848
17849
  /**
17849
17850
  * Resize the warm target at runtime (per-repo, sourced from the repo's vault
17850
- * POOL_MIN). Raising it warms up immediately via refill; lowering it just
17851
- * stops topping up surplus machines drain as they're claimed/auto-destroyed,
17852
- * never force-killed. No-op when unchanged or given a bad value.
17851
+ * POOL_MIN). Raising it warms immediately; lowering it prunes idle surplus
17852
+ * warm machines. No-op when unchanged or given a bad value.
17853
17853
  */
17854
17854
  setMin(min) {
17855
17855
  if (!Number.isInteger(min) || min < 0) return;
17856
17856
  if (min === this.deps.config.min) return;
17857
17857
  this.deps.config.min = min;
17858
17858
  this.log(`min set to ${min}`);
17859
- void this.refill();
17859
+ void this.rebalance("setMin");
17860
17860
  }
17861
17861
  /**
17862
17862
  * Adopt existing pooled machines on owner (re)start: suspended ones become
@@ -17865,12 +17865,17 @@ var PoolManager = class {
17865
17865
  async reconcile() {
17866
17866
  const machines = await this.deps.fly.listPooled(this.deps.config.repoTag);
17867
17867
  this.free = [];
17868
+ const surplus = [];
17868
17869
  for (const m of machines) {
17869
- if ((m.state === "suspended" || m.state === "suspending") && m.private_ip) {
17870
+ if (!isSuspendedWithIp(m)) continue;
17871
+ if (this.warmCapacity() < this.deps.config.min) {
17870
17872
  this.free.push({ id: m.id, privateIp: m.private_ip });
17873
+ } else {
17874
+ surplus.push(m.id);
17871
17875
  }
17872
17876
  }
17873
- this.log(`reconcile: adopted ${this.free.length} suspended machine(s)`);
17877
+ const destroyed = await this.destroySurplus(surplus);
17878
+ this.log(`reconcile: adopted ${this.free.length} suspended machine(s), destroyed ${destroyed} surplus`);
17874
17879
  await this.refill();
17875
17880
  }
17876
17881
  /**
@@ -17887,6 +17892,7 @@ var PoolManager = class {
17887
17892
  const machine = this.free.shift();
17888
17893
  if (!machine) break;
17889
17894
  this.claimsInFlight++;
17895
+ this.claimed.add(machine.id);
17890
17896
  try {
17891
17897
  await this.deps.fly.start(machine.id);
17892
17898
  const healthy = await this.deps.fly.waitHealthy(this.baseUrl(machine), {
@@ -17913,6 +17919,7 @@ var PoolManager = class {
17913
17919
  await this.safeDestroy(machine.id);
17914
17920
  lastReason = errMsg2(err);
17915
17921
  } finally {
17922
+ this.claimed.delete(machine.id);
17916
17923
  this.claimsInFlight--;
17917
17924
  }
17918
17925
  }
@@ -17940,16 +17947,24 @@ var PoolManager = class {
17940
17947
  const before = this.free.length;
17941
17948
  this.free = this.free.filter((f) => liveIds.has(f.id));
17942
17949
  const pruned = before - this.free.length;
17943
- const tracked = new Set(this.free.map((f) => f.id));
17950
+ const destroyedTracked = await this.trimFreeSurplus("resync");
17951
+ const tracked = /* @__PURE__ */ new Set([...this.free.map((f) => f.id), ...this.claimed]);
17944
17952
  let adopted = 0;
17953
+ const surplus = [];
17945
17954
  for (const m of machines) {
17946
- if ((m.state === "suspended" || m.state === "suspending") && m.private_ip && !tracked.has(m.id)) {
17955
+ if (!isSuspendedWithIp(m) || tracked.has(m.id)) continue;
17956
+ if (this.warmCapacity() < this.deps.config.min) {
17947
17957
  this.free.push({ id: m.id, privateIp: m.private_ip });
17958
+ tracked.add(m.id);
17948
17959
  adopted++;
17960
+ } else if (this.booting === 0) {
17961
+ surplus.push(m.id);
17949
17962
  }
17950
17963
  }
17951
- if (pruned > 0 || adopted > 0) {
17952
- this.log(`resync: pruned ${pruned} stale, adopted ${adopted} (free=${this.free.length})`);
17964
+ const destroyedSurplus = await this.destroySurplus(surplus);
17965
+ const destroyed = destroyedTracked + destroyedSurplus;
17966
+ if (pruned > 0 || adopted > 0 || destroyed > 0) {
17967
+ this.log(`resync: pruned ${pruned} stale, adopted ${adopted}, destroyed ${destroyed} surplus (free=${this.free.length})`);
17953
17968
  }
17954
17969
  await this.refill();
17955
17970
  }
@@ -18004,11 +18019,37 @@ var PoolManager = class {
18004
18019
  baseUrl(m) {
18005
18020
  return `http://[${m.privateIp}]:${this.deps.config.port}`;
18006
18021
  }
18022
+ warmCapacity() {
18023
+ return this.free.length + this.booting;
18024
+ }
18025
+ async rebalance(reason) {
18026
+ await this.trimFreeSurplus(reason);
18027
+ await this.refill();
18028
+ }
18029
+ async trimFreeSurplus(reason) {
18030
+ const surplus = [];
18031
+ while (this.free.length > this.deps.config.min) {
18032
+ const machine = this.free.pop();
18033
+ if (machine) surplus.push(machine.id);
18034
+ }
18035
+ const destroyed = await this.destroySurplus(surplus);
18036
+ if (destroyed > 0) this.log(`${reason}: destroyed ${destroyed} surplus free machine(s)`);
18037
+ return destroyed;
18038
+ }
18039
+ async destroySurplus(ids) {
18040
+ let destroyed = 0;
18041
+ for (const id of ids) {
18042
+ if (await this.safeDestroy(id)) destroyed++;
18043
+ }
18044
+ return destroyed;
18045
+ }
18007
18046
  async safeDestroy(id) {
18008
18047
  try {
18009
18048
  await this.deps.fly.destroy(id);
18049
+ return true;
18010
18050
  } catch (err) {
18011
18051
  this.log(`destroy ${id} failed: ${errMsg2(err)}`);
18052
+ return false;
18012
18053
  }
18013
18054
  }
18014
18055
  };
@@ -18027,6 +18068,9 @@ async function defaultPostRun(m, job, cfg) {
18027
18068
  function errMsg2(err) {
18028
18069
  return err instanceof Error ? err.message : String(err);
18029
18070
  }
18071
+ function isSuspendedWithIp(m) {
18072
+ return (m.state === "suspended" || m.state === "suspending") && !!m.private_ip;
18073
+ }
18030
18074
 
18031
18075
  // src/pool/vault.ts
18032
18076
  import { createDecipheriv as createDecipheriv2 } from "crypto";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.217",
3
+ "version": "0.4.218",
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",