@kody-ade/kody-engine 0.4.187 → 0.4.189

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
@@ -1309,7 +1309,7 @@ var init_loadPriorArt = __esm({
1309
1309
  // package.json
1310
1310
  var package_default = {
1311
1311
  name: "@kody-ade/kody-engine",
1312
- version: "0.4.187",
1312
+ version: "0.4.189",
1313
1313
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
1314
1314
  license: "MIT",
1315
1315
  type: "module",
@@ -2215,11 +2215,15 @@ async function runAgent(opts) {
2215
2215
  );
2216
2216
  }
2217
2217
  finalText = resultTexts.join("\n\n---\n\n");
2218
- if (outcome === "completed" && !sawMutatingTool && tokens.output === 0 && finalText === "") {
2219
- outcome = "failed";
2220
- outcomeKind = "model_error";
2221
- noWorkSuccess = true;
2222
- errorMessage = errorMessage ?? "session reported success but produced no model output (0 output tokens) \u2014 backend likely unreachable";
2218
+ if (outcome === "completed" && !sawMutatingTool) {
2219
+ const backendDead = opts.isBackendHealthy ? !await opts.isBackendHealthy() : false;
2220
+ const zeroOutput = tokens.output === 0 && finalText === "";
2221
+ if (backendDead || zeroOutput) {
2222
+ outcome = "failed";
2223
+ outcomeKind = "model_error";
2224
+ noWorkSuccess = true;
2225
+ errorMessage = errorMessage ?? (backendDead ? "model backend unreachable after a reported success \u2014 proxy crashed mid-request (hollow success)" : "session reported success but produced no model output (0 output tokens) \u2014 backend likely unreachable");
2226
+ }
2223
2227
  }
2224
2228
  const shouldRetry = outcome === "failed" && attempt < MAX_CONNECTION_RETRIES && !sawMutatingTool && (isTransientConnectionError(errorMessage) || noWorkSuccess);
2225
2229
  if (!shouldRetry) break;
@@ -4281,8 +4285,9 @@ ${tail}
4281
4285
  spawnProxy();
4282
4286
  return waitForHealth();
4283
4287
  };
4288
+ const isHealthy = () => checkLitellmHealth(url);
4284
4289
  if (await checkLitellmHealth(url)) {
4285
- return { url, kill: killChild, ensureHealthy };
4290
+ return { url, kill: killChild, isHealthy, ensureHealthy };
4286
4291
  }
4287
4292
  spawnProxy();
4288
4293
  if (!await waitForHealth()) {
@@ -4294,7 +4299,7 @@ ${tail}
4294
4299
  ${tail}`
4295
4300
  );
4296
4301
  }
4297
- return { url, kill: killChild, ensureHealthy };
4302
+ return { url, kill: killChild, isHealthy, ensureHealthy };
4298
4303
  }
4299
4304
  function readDotenvApiKeys(projectDir) {
4300
4305
  const dotenvPath = path14.join(projectDir, ".env");
@@ -12360,10 +12365,79 @@ async function runCmd(cmd, args, opts = {}) {
12360
12365
  });
12361
12366
  }
12362
12367
 
12368
+ // src/scripts/previewBuildNamespace.ts
12369
+ var NSC_OIDC_AUDIENCE = "https://namespace.so";
12370
+ var REQ_TIMEOUT_MS = 15e3;
12371
+ var NSC_INSTALL = `
12372
+ set -euo pipefail
12373
+ if [ ! -x /usr/local/bin/nsc ]; then
12374
+ ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
12375
+ OS=$(uname -s | tr '[:upper:]' '[:lower:]')
12376
+ curl -fsSL "https://get.namespace.so/packages/nsc/latest?arch=\${ARCH}&os=\${OS}" -o /tmp/nsc.tar.gz
12377
+ mkdir -p /tmp/nsc-extract
12378
+ tar -xzf /tmp/nsc.tar.gz -C /tmp/nsc-extract
12379
+ NSC_BIN=$(find /tmp/nsc-extract -type f -name nsc | head -1)
12380
+ sudo install -m 0755 "$NSC_BIN" /usr/local/bin/nsc
12381
+ fi
12382
+ /usr/local/bin/nsc version
12383
+ `;
12384
+ async function fetchGithubOidcToken(audience) {
12385
+ const url = process.env.ACTIONS_ID_TOKEN_REQUEST_URL?.trim();
12386
+ const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN?.trim();
12387
+ if (!url || !requestToken) return null;
12388
+ const res = await fetch(`${url}&audience=${encodeURIComponent(audience)}`, {
12389
+ headers: { Authorization: `Bearer ${requestToken}` },
12390
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12391
+ });
12392
+ if (!res.ok) {
12393
+ throw new Error(`OIDC token request failed: ${res.status} ${res.statusText}`);
12394
+ }
12395
+ const data = await res.json();
12396
+ if (!data.value) throw new Error("OIDC token response missing `value`");
12397
+ return data.value;
12398
+ }
12399
+ async function setupNamespaceBuilder(opts) {
12400
+ try {
12401
+ await runCmd("bash", ["-c", NSC_INSTALL]);
12402
+ const jwt = await fetchGithubOidcToken(NSC_OIDC_AUDIENCE);
12403
+ if (!jwt) {
12404
+ console.warn(
12405
+ "[preview-build] no GitHub OIDC token (id-token: write missing?) \u2014 local docker build"
12406
+ );
12407
+ return null;
12408
+ }
12409
+ await runCmd("nsc", [
12410
+ "auth",
12411
+ "exchange-oidc-token",
12412
+ "--tenant_id",
12413
+ opts.tenantId,
12414
+ "--token",
12415
+ jwt
12416
+ ]);
12417
+ await runCmd("nsc", [
12418
+ "docker",
12419
+ "buildx",
12420
+ "setup",
12421
+ "--name",
12422
+ opts.builderName
12423
+ ]);
12424
+ console.log(
12425
+ `[preview-build] Namespace remote builder ready (${opts.builderName})`
12426
+ );
12427
+ return opts.builderName;
12428
+ } catch (err) {
12429
+ console.warn(
12430
+ "[preview-build] Namespace setup failed \u2014 falling back to local docker:",
12431
+ err instanceof Error ? err.message : String(err)
12432
+ );
12433
+ return null;
12434
+ }
12435
+ }
12436
+
12363
12437
  // src/scripts/runPreviewBuild.ts
12364
12438
  var FLY_MACHINES = "https://api.machines.dev/v1";
12365
12439
  var FLY_GRAPHQL = "https://api.fly.io/graphql";
12366
- var REQ_TIMEOUT_MS = 3e4;
12440
+ var REQ_TIMEOUT_MS2 = 3e4;
12367
12441
  function bundledDockerfilePath(mode) {
12368
12442
  const here = path36.dirname(fileURLToPath(import.meta.url));
12369
12443
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
@@ -12387,7 +12461,7 @@ async function ghJSON(url, token) {
12387
12461
  Accept: "application/vnd.github+json",
12388
12462
  "X-GitHub-Api-Version": "2022-11-28"
12389
12463
  },
12390
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12464
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12391
12465
  });
12392
12466
  if (!res.ok) {
12393
12467
  throw new Error(`GitHub ${url}: ${res.status} ${res.statusText}`);
@@ -12406,7 +12480,7 @@ async function fetchVaultDoc(repo, ghToken4, masterKey) {
12406
12480
  async function flyAppExists(name, token) {
12407
12481
  const res = await fetch(`${FLY_MACHINES}/apps/${encodeURIComponent(name)}`, {
12408
12482
  headers: flyHeaders(token),
12409
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12483
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12410
12484
  });
12411
12485
  if (res.status === 404) return false;
12412
12486
  if (!res.ok) {
@@ -12419,7 +12493,7 @@ async function flyCreateApp(name, orgSlug, token) {
12419
12493
  method: "POST",
12420
12494
  headers: flyHeaders(token),
12421
12495
  body: JSON.stringify({ app_name: name, org_slug: orgSlug }),
12422
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12496
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12423
12497
  });
12424
12498
  if (res.status === 422) return;
12425
12499
  if (!res.ok) {
@@ -12438,7 +12512,7 @@ async function flyAllocateSharedIps(appName, token) {
12438
12512
  method: "POST",
12439
12513
  headers: flyHeaders(token),
12440
12514
  body: JSON.stringify({ query: mutation, variables: { appId: appName } }),
12441
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12515
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12442
12516
  });
12443
12517
  if (!res.ok) {
12444
12518
  throw new Error(`allocateSharedIps ${appName}: ${res.status}`);
@@ -12456,7 +12530,7 @@ async function flyListMachines(appName, token) {
12456
12530
  `${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines`,
12457
12531
  {
12458
12532
  headers: flyHeaders(token),
12459
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12533
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12460
12534
  }
12461
12535
  );
12462
12536
  if (res.status === 404) return [];
@@ -12472,7 +12546,7 @@ async function flyDestroyMachine(appName, machineId, token) {
12472
12546
  {
12473
12547
  method: "POST",
12474
12548
  headers: flyHeaders(token),
12475
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12549
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12476
12550
  }
12477
12551
  ).catch(() => void 0);
12478
12552
  const res = await fetch(
@@ -12480,7 +12554,7 @@ async function flyDestroyMachine(appName, machineId, token) {
12480
12554
  {
12481
12555
  method: "DELETE",
12482
12556
  headers: flyHeaders(token),
12483
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12557
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12484
12558
  }
12485
12559
  );
12486
12560
  if (res.status === 404) return;
@@ -12533,7 +12607,7 @@ async function flyCreatePreviewMachine(args, token) {
12533
12607
  method: "POST",
12534
12608
  headers: flyHeaders(token),
12535
12609
  body: JSON.stringify(body),
12536
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12610
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12537
12611
  }
12538
12612
  );
12539
12613
  if (res.ok) {
@@ -12560,7 +12634,7 @@ async function postOrUpdatePreviewComment(args) {
12560
12634
  };
12561
12635
  const listRes = await fetch(`${base}?per_page=100`, {
12562
12636
  headers,
12563
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12637
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12564
12638
  }).catch(() => null);
12565
12639
  let existingId = null;
12566
12640
  if (listRes && listRes.ok) {
@@ -12575,7 +12649,7 @@ async function postOrUpdatePreviewComment(args) {
12575
12649
  method: "PATCH",
12576
12650
  headers,
12577
12651
  body: JSON.stringify({ body: args.body }),
12578
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12652
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12579
12653
  }
12580
12654
  );
12581
12655
  return;
@@ -12584,7 +12658,7 @@ async function postOrUpdatePreviewComment(args) {
12584
12658
  method: "POST",
12585
12659
  headers,
12586
12660
  body: JSON.stringify({ body: args.body }),
12587
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
12661
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
12588
12662
  });
12589
12663
  }
12590
12664
  var runPreviewBuild = async (ctx, _profile, _args) => {
@@ -12628,6 +12702,7 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
12628
12702
  }
12629
12703
  const orgSlug = doc.secrets?.FLY_ORG_SLUG?.value?.trim() || (process.env.FLY_ORG_SLUG ?? "personal").trim();
12630
12704
  const region = doc.secrets?.FLY_DEFAULT_REGION?.value?.trim() || (process.env.FLY_REGION ?? "fra").trim();
12705
+ const nscTenantId = doc.secrets?.NSC_TENANT_ID?.value?.trim() || "";
12631
12706
  console.log(
12632
12707
  `[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`
12633
12708
  );
@@ -12691,22 +12766,42 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
12691
12766
  ["login", "registry.fly.io", "-u", "x", "--password-stdin"],
12692
12767
  { input: flyToken, cwd: ctx.cwd }
12693
12768
  );
12694
- const buildArgs = [
12695
- "build",
12696
- "-f",
12697
- "Dockerfile.preview",
12698
- "-t",
12699
- `registry.fly.io/${appName}:${tag}`
12700
- ];
12701
- if (baseImage) buildArgs.push("--build-arg", `BASE_IMAGE=${baseImage}`);
12702
- buildArgs.push(".");
12703
- await runCmd("docker", buildArgs, {
12704
- cwd: ctx.cwd,
12705
- env: { DOCKER_BUILDKIT: "1" }
12706
- });
12707
- await runCmd("docker", ["push", `registry.fly.io/${appName}:${tag}`], {
12708
- cwd: ctx.cwd
12709
- });
12769
+ const imageRef = `registry.fly.io/${appName}:${tag}`;
12770
+ const nsBuilder = nscTenantId ? await setupNamespaceBuilder({
12771
+ tenantId: nscTenantId,
12772
+ builderName: `kody-preview-${pr}`
12773
+ }) : null;
12774
+ if (nsBuilder) {
12775
+ const a = [
12776
+ "buildx",
12777
+ "build",
12778
+ "--builder",
12779
+ nsBuilder,
12780
+ "-f",
12781
+ "Dockerfile.preview",
12782
+ "-t",
12783
+ imageRef,
12784
+ "--push"
12785
+ ];
12786
+ if (baseImage) a.push("--build-arg", `BASE_IMAGE=${baseImage}`);
12787
+ a.push(".");
12788
+ await runCmd("docker", a, { cwd: ctx.cwd });
12789
+ } else {
12790
+ const buildArgs = [
12791
+ "build",
12792
+ "-f",
12793
+ "Dockerfile.preview",
12794
+ "-t",
12795
+ imageRef
12796
+ ];
12797
+ if (baseImage) buildArgs.push("--build-arg", `BASE_IMAGE=${baseImage}`);
12798
+ buildArgs.push(".");
12799
+ await runCmd("docker", buildArgs, {
12800
+ cwd: ctx.cwd,
12801
+ env: { DOCKER_BUILDKIT: "1" }
12802
+ });
12803
+ await runCmd("docker", ["push", imageRef], { cwd: ctx.cwd });
12804
+ }
12710
12805
  const stale = await flyListMachines(appName, flyToken);
12711
12806
  for (const m of stale) {
12712
12807
  await flyDestroyMachine(appName, m.id, flyToken).catch(() => void 0);
@@ -14187,6 +14282,9 @@ async function runExecutable(profileName, input) {
14187
14282
  // On a connection drop mid-run, restart the (possibly crashed) proxy
14188
14283
  // before the agent retries. No-op for direct-Anthropic runs (lm null).
14189
14284
  ensureBackend: lm ? () => lm.ensureHealthy().then(() => void 0) : void 0,
14285
+ // Pure liveness probe so the agent can spot a hollow "success" (proxy
14286
+ // crashed mid-request, SDK still reported success). No-op when lm null.
14287
+ isBackendHealthy: lm ? () => lm.isHealthy() : void 0,
14190
14288
  verbose: input.verbose,
14191
14289
  quiet: input.quiet,
14192
14290
  ndjsonDir,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.187",
3
+ "version": "0.4.189",
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",
@@ -12,6 +12,24 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
+ "scripts": {
16
+ "kody:run": "tsx bin/kody.ts",
17
+ "serve": "tsx bin/kody.ts serve",
18
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
19
+ "serve:claude": "tsx bin/kody.ts serve claude",
20
+ "build": "tsup && node scripts/copy-assets.cjs",
21
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
22
+ "pretest": "pnpm check:modularity",
23
+ "test": "vitest run tests/unit tests/int --coverage",
24
+ "test:smoke": "vitest run tests/smoke --no-coverage",
25
+ "test:e2e": "vitest run tests/e2e --no-coverage",
26
+ "test:all": "vitest run tests --no-coverage",
27
+ "typecheck": "tsc --noEmit",
28
+ "lint": "biome check",
29
+ "lint:fix": "biome check --write",
30
+ "format": "biome format --write",
31
+ "prepublishOnly": "pnpm build"
32
+ },
15
33
  "dependencies": {
16
34
  "@actions/cache": "^6.0.0",
17
35
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -34,22 +52,5 @@
34
52
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
35
53
  },
36
54
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
37
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
38
- "scripts": {
39
- "kody:run": "tsx bin/kody.ts",
40
- "serve": "tsx bin/kody.ts serve",
41
- "serve:vscode": "tsx bin/kody.ts serve vscode",
42
- "serve:claude": "tsx bin/kody.ts serve claude",
43
- "build": "tsup && node scripts/copy-assets.cjs",
44
- "check:modularity": "tsx scripts/check-script-modularity.ts",
45
- "pretest": "pnpm check:modularity",
46
- "test": "vitest run tests/unit tests/int --coverage",
47
- "test:smoke": "vitest run tests/smoke --no-coverage",
48
- "test:e2e": "vitest run tests/e2e --no-coverage",
49
- "test:all": "vitest run tests --no-coverage",
50
- "typecheck": "tsc --noEmit",
51
- "lint": "biome check",
52
- "lint:fix": "biome check --write",
53
- "format": "biome format --write"
54
- }
55
- }
55
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
56
+ }
@@ -77,6 +77,7 @@ jobs:
77
77
  pull-requests: write
78
78
  contents: write
79
79
  actions: read
80
+ id-token: write # OIDC: lets preview-build federate into Namespace remote builders
80
81
  steps:
81
82
  - uses: actions/checkout@v4
82
83
  with: