@kody-ade/kody-engine 0.4.151 → 0.4.153

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
@@ -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.151",
1064
+ version: "0.4.153",
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",
@@ -1952,6 +1952,14 @@ async function runAgent(opts) {
1952
1952
  `[kody agent] transient connection error (attempt ${attempt + 1}/${MAX_CONNECTION_RETRIES + 1}); retrying in ${Math.round(delayMs / 1e3)}s: ${errorMessage}
1953
1953
  `
1954
1954
  );
1955
+ if (opts.ensureBackend) {
1956
+ try {
1957
+ await opts.ensureBackend();
1958
+ } catch (e) {
1959
+ process.stderr.write(`[kody agent] backend recovery failed: ${e instanceof Error ? e.message : String(e)}
1960
+ `);
1961
+ }
1962
+ }
1955
1963
  await new Promise((r) => setTimeout(r, delayMs));
1956
1964
  }
1957
1965
  const submittedState = getSubmitted?.();
@@ -3850,65 +3858,85 @@ function generateLitellmConfigYaml(model) {
3850
3858
  ""
3851
3859
  ].join("\n");
3852
3860
  }
3853
- async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL) {
3854
- if (!needsLitellmProxy(model)) return null;
3855
- if (await checkLitellmHealth(url)) {
3856
- return { url, kill: () => {
3857
- } };
3858
- }
3859
- let cmd = "litellm";
3861
+ function resolveLitellmCommand() {
3860
3862
  try {
3861
3863
  execFileSync4("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
3864
+ return "litellm";
3862
3865
  } catch {
3863
3866
  try {
3864
3867
  execFileSync4("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
3865
- cmd = "python3";
3868
+ return "python3";
3866
3869
  } catch {
3867
3870
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
3868
3871
  }
3869
3872
  }
3870
- const configPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3871
- fs14.writeFileSync(configPath, generateLitellmConfigYaml(model));
3873
+ }
3874
+ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL) {
3875
+ if (!needsLitellmProxy(model)) return null;
3876
+ const cmd = resolveLitellmCommand();
3872
3877
  const portMatch = url.match(/:(\d+)/);
3873
3878
  const port = portMatch ? portMatch[1] : "4000";
3874
- const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
3875
- const dotenvVars = readDotenvApiKeys(projectDir);
3876
- const logPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3877
- const outFd = fs14.openSync(logPath, "w");
3878
- const child = spawn3(cmd, args, {
3879
- stdio: ["ignore", outFd, outFd],
3880
- detached: true,
3881
- env: stripBlockingEnv({ ...process.env, ...dotenvVars })
3882
- });
3883
- fs14.closeSync(outFd);
3884
- const timeoutMs = resolveLitellmTimeoutMs();
3885
- const deadline = Date.now() + timeoutMs;
3886
- while (Date.now() < deadline) {
3887
- await new Promise((r) => setTimeout(r, LITELLM_HEALTH_POLL_INTERVAL_MS));
3888
- if (await checkLitellmHealth(url)) {
3889
- return {
3890
- url,
3891
- kill: () => {
3892
- try {
3893
- child.kill();
3894
- } catch {
3895
- }
3896
- }
3897
- };
3879
+ const childEnv = stripBlockingEnv({ ...process.env, ...readDotenvApiKeys(projectDir) });
3880
+ let child;
3881
+ let logPath;
3882
+ const spawnProxy = () => {
3883
+ const configPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3884
+ fs14.writeFileSync(configPath, generateLitellmConfigYaml(model));
3885
+ const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
3886
+ const nextLogPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3887
+ const outFd = fs14.openSync(nextLogPath, "w");
3888
+ child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
3889
+ fs14.closeSync(outFd);
3890
+ logPath = nextLogPath;
3891
+ };
3892
+ const waitForHealth = async () => {
3893
+ const deadline = Date.now() + resolveLitellmTimeoutMs();
3894
+ while (Date.now() < deadline) {
3895
+ await new Promise((r) => setTimeout(r, LITELLM_HEALTH_POLL_INTERVAL_MS));
3896
+ if (await checkLitellmHealth(url)) return true;
3898
3897
  }
3898
+ return false;
3899
+ };
3900
+ const readLogTail = () => {
3901
+ if (!logPath) return "";
3902
+ try {
3903
+ return fs14.readFileSync(logPath, "utf-8").slice(-2e3);
3904
+ } catch {
3905
+ return "";
3906
+ }
3907
+ };
3908
+ const killChild = () => {
3909
+ try {
3910
+ child?.kill();
3911
+ } catch {
3912
+ }
3913
+ };
3914
+ const ensureHealthy = async () => {
3915
+ if (await checkLitellmHealth(url)) return true;
3916
+ const tail = readLogTail();
3917
+ process.stderr.write(
3918
+ `[kody litellm] proxy unreachable mid-run; restarting.${tail ? ` Last log:
3919
+ ${tail}
3920
+ ` : "\n"}`
3921
+ );
3922
+ killChild();
3923
+ spawnProxy();
3924
+ return waitForHealth();
3925
+ };
3926
+ if (await checkLitellmHealth(url)) {
3927
+ return { url, kill: killChild, ensureHealthy };
3899
3928
  }
3900
- let logTail = "";
3901
- try {
3902
- logTail = fs14.readFileSync(logPath, "utf-8").slice(-2e3);
3903
- } catch {
3904
- }
3905
- try {
3906
- child.kill();
3907
- } catch {
3929
+ spawnProxy();
3930
+ if (!await waitForHealth()) {
3931
+ const tail = readLogTail();
3932
+ killChild();
3933
+ const seconds = Math.round(resolveLitellmTimeoutMs() / 1e3);
3934
+ throw new Error(
3935
+ `LiteLLM proxy failed to start within ${seconds}s (KODY_LITELLM_TIMEOUT_SEC overrides). Log tail:
3936
+ ${tail}`
3937
+ );
3908
3938
  }
3909
- const seconds = Math.round(timeoutMs / 1e3);
3910
- throw new Error(`LiteLLM proxy failed to start within ${seconds}s (KODY_LITELLM_TIMEOUT_SEC overrides). Log tail:
3911
- ${logTail}`);
3939
+ return { url, kill: killChild, ensureHealthy };
3912
3940
  }
3913
3941
  function readDotenvApiKeys(projectDir) {
3914
3942
  const dotenvPath = path13.join(projectDir, ".env");
@@ -12097,6 +12125,11 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
12097
12125
  // src/scripts/syncFlow.ts
12098
12126
  import { execFileSync as execFileSync25 } from "child_process";
12099
12127
  init_issue();
12128
+ var DONE2 = {
12129
+ label: "kody:done",
12130
+ color: "0e8a16",
12131
+ description: "kody: PR ready for human review/merge"
12132
+ };
12100
12133
  var syncFlow = async (ctx, _profile, args) => {
12101
12134
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
12102
12135
  const prNumber = ctx.args.pr;
@@ -12134,6 +12167,7 @@ var syncFlow = async (ctx, _profile, args) => {
12134
12167
  if (announceOnSuccess) {
12135
12168
  ctx.output.exitCode = 0;
12136
12169
  ctx.output.reason = `already up to date with origin/${baseBranch}`;
12170
+ restoreDone(prNumber, ctx.cwd);
12137
12171
  }
12138
12172
  return;
12139
12173
  }
@@ -12148,8 +12182,15 @@ var syncFlow = async (ctx, _profile, args) => {
12148
12182
  if (announceOnSuccess) {
12149
12183
  ctx.output.exitCode = 0;
12150
12184
  ctx.output.reason = `merged origin/${baseBranch} into ${ctx.data.branch}`;
12185
+ restoreDone(prNumber, ctx.cwd);
12151
12186
  }
12152
12187
  };
12188
+ function restoreDone(prNumber, cwd) {
12189
+ try {
12190
+ setKodyLabel(prNumber, DONE2, cwd);
12191
+ } catch {
12192
+ }
12193
+ }
12153
12194
  function bail2(ctx, prNumber, reason) {
12154
12195
  ctx.skipAgent = true;
12155
12196
  ctx.output.exitCode = 1;
@@ -13093,7 +13134,10 @@ async function runExecutable(profileName, input) {
13093
13134
  try {
13094
13135
  model = parseProviderModel(modelSpec);
13095
13136
  } catch (err) {
13096
- return finishAndEnd({ exitCode: 99, reason: `agent.model invalid: ${err instanceof Error ? err.message : String(err)}` });
13137
+ return finishAndEnd({
13138
+ exitCode: 99,
13139
+ reason: `agent.model invalid: ${err instanceof Error ? err.message : String(err)}`
13140
+ });
13097
13141
  }
13098
13142
  let litellm = null;
13099
13143
  try {
@@ -13137,11 +13181,15 @@ async function runExecutable(profileName, input) {
13137
13181
  const syntheticPath = ctx.data.syntheticPluginPath;
13138
13182
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
13139
13183
  const agents = loadSubagents(profile);
13184
+ const lm = litellm;
13140
13185
  return runAgent({
13141
13186
  prompt,
13142
13187
  model,
13143
13188
  cwd: input.cwd,
13144
- litellmUrl: litellm?.url ?? null,
13189
+ litellmUrl: lm?.url ?? null,
13190
+ // On a connection drop mid-run, restart the (possibly crashed) proxy
13191
+ // before the agent retries. No-op for direct-Anthropic runs (lm null).
13192
+ ensureBackend: lm ? () => lm.ensureHealthy().then(() => void 0) : void 0,
13145
13193
  verbose: input.verbose,
13146
13194
  quiet: input.quiet,
13147
13195
  ndjsonDir,
@@ -13211,7 +13259,10 @@ async function runExecutable(profileName, input) {
13211
13259
  } else if (!ctx.skipAgent) {
13212
13260
  const prompt = ctx.data.prompt;
13213
13261
  if (!prompt) {
13214
- return finishAndEnd({ exitCode: 99, reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)" });
13262
+ return finishAndEnd({
13263
+ exitCode: 99,
13264
+ reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)"
13265
+ });
13215
13266
  }
13216
13267
  emitEvent(input.cwd, { executable: profileName, kind: "agent_start" });
13217
13268
  agentResult = await invokeAgent(prompt);
@@ -13272,10 +13323,7 @@ async function runExecutable(profileName, input) {
13272
13323
  const runId = resolveRunId2();
13273
13324
  const dir = pathMod.join(input.cwd, ".kody", "runs", runId, "crashes");
13274
13325
  fsMod.mkdirSync(dir, { recursive: true });
13275
- const file = pathMod.join(
13276
- dir,
13277
- `${label.replace(/[^a-zA-Z0-9_-]/g, "_")}-${Date.now()}.json`
13278
- );
13326
+ const file = pathMod.join(dir, `${label.replace(/[^a-zA-Z0-9_-]/g, "_")}-${Date.now()}.json`);
13279
13327
  fsMod.writeFileSync(
13280
13328
  file,
13281
13329
  JSON.stringify(
@@ -13315,10 +13363,8 @@ async function runExecutable(profileName, input) {
13315
13363
  try {
13316
13364
  const missing2 = verifyTaskArtifacts(taskArtifacts.absDir);
13317
13365
  if (missing2.length > 0) {
13318
- process.stderr.write(
13319
- `[task-artifacts] task ${taskArtifacts.taskId} missing: ${missing2.join(", ")}
13320
- `
13321
- );
13366
+ process.stderr.write(`[task-artifacts] task ${taskArtifacts.taskId} missing: ${missing2.join(", ")}
13367
+ `);
13322
13368
  }
13323
13369
  } catch {
13324
13370
  }
@@ -27,6 +27,14 @@
27
27
  "cliTools": [],
28
28
  "scripts": {
29
29
  "preflight": [
30
+ {
31
+ "script": "setLifecycleLabel",
32
+ "with": {
33
+ "label": "kody:syncing",
34
+ "color": "c5def5",
35
+ "description": "kody: syncing PR with base"
36
+ }
37
+ },
30
38
  {
31
39
  "script": "syncFlow",
32
40
  "with": { "announceOnSuccess": true }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.151",
3
+ "version": "0.4.153",
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",