@kody-ade/kody-engine 0.4.656 → 0.4.658

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 +96 -51
  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.656",
18
+ version: "0.4.658",
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
  repository: {
@@ -5023,6 +5023,12 @@ async function runAgent(opts) {
5023
5023
  outcomeKind = "model_error";
5024
5024
  errorMessage2 = "Claude Code reported it is not logged in; refusing to mark agent run successful";
5025
5025
  }
5026
+ const providerApiError = outcome === "completed" && tokens.output === 0 && /^API Error:\s*\d+\b/i.test(finalText.trim());
5027
+ if (providerApiError) {
5028
+ outcome = "failed";
5029
+ outcomeKind = "model_error";
5030
+ errorMessage2 = finalText.trim();
5031
+ }
5026
5032
  if (outcome === "completed" && !sawMutatingTool) {
5027
5033
  const hasUsefulSuccess = tokens.output > 0 || finalText !== "" || Boolean(getSubmitted?.());
5028
5034
  if (!hasUsefulSuccess) {
@@ -9519,12 +9525,12 @@ function isSafeConfigActivationChange(before, after) {
9519
9525
  const beforeCompanyRest = { ...beforeCompany };
9520
9526
  const afterCompanyRest = { ...afterCompany };
9521
9527
  for (const field of ACTIVATION_FIELDS) {
9522
- const previous = beforeCompany[field];
9528
+ const previous = beforeCompany[field] === void 0 ? [] : beforeCompany[field];
9523
9529
  const next = afterCompany[field];
9524
9530
  delete beforeCompanyRest[field];
9525
9531
  delete afterCompanyRest[field];
9526
- if (previous === void 0 && next === void 0) continue;
9527
- if (!Array.isArray(previous ?? []) || !Array.isArray(next)) return false;
9532
+ if (beforeCompany[field] === void 0 && next === void 0) continue;
9533
+ if (!Array.isArray(previous) || !Array.isArray(next)) return false;
9528
9534
  if (!next.every((value) => typeof value === "string")) return false;
9529
9535
  if (!previous.every((value) => typeof value === "string")) return false;
9530
9536
  const nextValues = new Set(next);
@@ -9563,6 +9569,9 @@ function isTrustedConfigActivationChange(filePath, deliveryPathAllowlist, delive
9563
9569
  }
9564
9570
  }
9565
9571
  function listChangedFiles(cwd) {
9572
+ return [...new Set(listChangedPathGroups(cwd).flat())];
9573
+ }
9574
+ function listChangedPathGroups(cwd) {
9566
9575
  const raw = execFileSync6("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
9567
9576
  encoding: "utf-8",
9568
9577
  maxBuffer: GIT_MAX_BUFFER_BYTES,
@@ -9571,8 +9580,19 @@ function listChangedFiles(cwd) {
9571
9580
  stdio: ["pipe", "pipe", "pipe"]
9572
9581
  });
9573
9582
  if (!raw) return [];
9574
- const entries = raw.split("\0").filter((e) => e.length > 0);
9575
- return entries.map((e) => e.slice(3)).filter(Boolean);
9583
+ const entries = raw.split("\0");
9584
+ const groups = [];
9585
+ for (let index = 0; index < entries.length - 1; index++) {
9586
+ const entry = entries[index];
9587
+ const paths = [entry.slice(3)];
9588
+ if (/[RC]/.test(entry.slice(0, 2))) {
9589
+ const source = entries[++index];
9590
+ if (!source) throw new Error("git status returned an incomplete rename/copy record");
9591
+ paths.push(source);
9592
+ }
9593
+ groups.push(paths);
9594
+ }
9595
+ return groups;
9576
9596
  }
9577
9597
  function listAllowlistedIgnoredFiles(deliveryPathAllowlist, cwd) {
9578
9598
  if (deliveryPathAllowlist.length === 0) return [];
@@ -9611,27 +9631,26 @@ function normalizeCommitMessage(raw) {
9611
9631
  function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = [], deliveryConfigAllowlist = {}) {
9612
9632
  const ignoredAllowedFiles = listAllowlistedIgnoredFiles(deliveryPathAllowlist, cwd);
9613
9633
  const ignoredAllowedSet = new Set(ignoredAllowedFiles);
9614
- const allChanged = [.../* @__PURE__ */ new Set([...listChangedFiles(cwd), ...ignoredAllowedFiles])];
9615
- const allowedFiles = allChanged.filter(
9616
- (f) => !isForbiddenPath(f, deliveryPathAllowlist) || isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
9617
- );
9618
- const forbiddenFiles = allChanged.filter(
9619
- (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
9634
+ const pathGroups = [...listChangedPathGroups(cwd), ...ignoredAllowedFiles.map((file) => [file])];
9635
+ const blockedGroups = pathGroups.filter(
9636
+ (group) => group.some(
9637
+ (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
9638
+ )
9620
9639
  );
9640
+ const forbiddenFiles = [...new Set(blockedGroups.flat())];
9641
+ const forbiddenSet = new Set(forbiddenFiles);
9642
+ const allowedFiles = [...new Set(pathGroups.flat())].filter((file) => !forbiddenSet.has(file));
9621
9643
  const omittedFiles = forbiddenFiles.filter(isReportableDeliveryOmission);
9622
9644
  const mergeHeadExists = fs30.existsSync(path28.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
9645
+ for (const f of forbiddenFiles) {
9646
+ git(["--literal-pathspecs", "reset", "-q", "--", f], cwd);
9647
+ }
9623
9648
  if (allowedFiles.length === 0 && !mergeHeadExists) {
9624
9649
  return { committed: false, pushed: false, sha: "", message: "", omittedFiles };
9625
9650
  }
9626
- for (const f of forbiddenFiles) {
9627
- try {
9628
- git(["reset", "-q", "--", f], cwd);
9629
- } catch {
9630
- }
9631
- }
9632
9651
  for (const f of allowedFiles) {
9633
9652
  try {
9634
- git(["add", ...ignoredAllowedSet.has(f) ? ["--force"] : [], "--", f], cwd);
9653
+ git(["--literal-pathspecs", "add", ...ignoredAllowedSet.has(f) ? ["--force"] : [], "--", f], cwd);
9635
9654
  } catch {
9636
9655
  }
9637
9656
  }
@@ -9646,8 +9665,8 @@ function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = [], de
9646
9665
  }
9647
9666
  throw err;
9648
9667
  }
9649
- const sha = git(["rev-parse", "HEAD"], cwd).slice(0, 7);
9650
9668
  const pushResult = pushWithRetry({ cwd, branch, setUpstream: true });
9669
+ const sha = git(["rev-parse", "HEAD"], cwd).slice(0, 7);
9651
9670
  if (pushResult.ok) {
9652
9671
  return { committed: true, pushed: true, sha, message, omittedFiles };
9653
9672
  }
@@ -25169,6 +25188,9 @@ function shouldRunCapabilityWorkflow(job, workflow, capabilityIdentity, selected
25169
25188
  return requestedImplementation === selectedImplementation || requestedImplementation === capabilityIdentity || requestedImplementation === job.action;
25170
25189
  }
25171
25190
  async function runCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
25191
+ if (parent.workflowState?.status === "done") {
25192
+ return { exitCode: 0, workflowState: structuredClone(parent.workflowState), usage: parent.workflowState.usage };
25193
+ }
25172
25194
  const invalid = workflowError(workflow, base);
25173
25195
  if (invalid) {
25174
25196
  if (isGraphWorkflow(workflow)) {
@@ -25401,6 +25423,20 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
25401
25423
  return { ...result, exitCode: 64, reason, workflowState: state };
25402
25424
  }
25403
25425
  const label = step.action ?? step.capability;
25426
+ if (!shouldRunWorkflowStep(step, chainData)) {
25427
+ process.stdout.write(
25428
+ `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label} (skipped)
25429
+
25430
+ `
25431
+ );
25432
+ result = { exitCode: 0 };
25433
+ delete chainData.workflowLastResult;
25434
+ delete chainData.workflowLastOutput;
25435
+ delete chainData.workflowLastOutcome;
25436
+ const terminal2 = await advanceGraphWorkflow(step, capability, state, result, chainData, checkpoint, true);
25437
+ if (terminal2) return terminal2;
25438
+ continue;
25439
+ }
25404
25440
  if (step.approval === "required") {
25405
25441
  const approvalState = requireWorkflowStepApproval(state, step.id);
25406
25442
  state.status = approvalState.status;
@@ -25494,42 +25530,47 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
25494
25530
  await checkpoint?.(state);
25495
25531
  return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
25496
25532
  }
25497
- if (!step.next || step.next.length === 0) {
25498
- return completeWorkflowAtTerminal(capability, state, result, checkpoint);
25499
- }
25500
- const resultConditionPaths = workflowResultConditionPaths(step.next);
25501
- if (resultConditionPaths.length > 0 && !result.capabilityResults?.at(-1)) {
25502
- const reason = `workflow step ${step.id} did not emit the structured result required by its conditions: ${resultConditionPaths.join(", ")}`;
25503
- state.status = "blocked";
25504
- state.blocker = reason;
25505
- await checkpoint?.(state);
25506
- return { ...result, exitCode: 64, reason, workflowState: state };
25507
- }
25508
- const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
25509
- if (!transition) {
25510
- const exhausted = exhaustedWorkflowTransitions(step, chainData, state.transitionCounts);
25511
- const reason = exhausted.length > 0 ? `workflow step ${step.id} reached iteration limit: ${exhausted.join(", ")}` : `workflow step ${step.id} has no available connection`;
25512
- state.status = "blocked";
25513
- state.blocker = reason;
25514
- await checkpoint?.(state);
25515
- return { ...result, exitCode: 64, reason, workflowState: state };
25516
- }
25517
- if (transition.maxIterations !== void 0) {
25518
- const key = `${step.id}->${transition.to}`;
25519
- state.transitionCounts[key] = (state.transitionCounts[key] ?? 0) + 1;
25520
- }
25521
- if (transition.to === "$end") {
25522
- return completeWorkflowAtTerminal(capability, state, result, checkpoint);
25523
- }
25524
- state.currentStepId = transition.to;
25525
- state.status = "running";
25526
- delete state.blocker;
25527
- await checkpoint?.(state);
25533
+ const terminal = await advanceGraphWorkflow(step, capability, state, result, chainData, checkpoint);
25534
+ if (terminal) return terminal;
25528
25535
  }
25529
25536
  state.status = "done";
25530
25537
  await checkpoint?.(state);
25531
25538
  return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
25532
25539
  }
25540
+ async function advanceGraphWorkflow(step, capability, state, result, chainData, checkpoint, skipped = false) {
25541
+ if (!step.next || step.next.length === 0) {
25542
+ return completeWorkflowAtTerminal(capability, state, result, checkpoint);
25543
+ }
25544
+ const resultConditionPaths = workflowResultConditionPaths(step.next);
25545
+ if (!skipped && resultConditionPaths.length > 0 && !result.capabilityResults?.at(-1)) {
25546
+ const reason = `workflow step ${step.id} did not emit the structured result required by its conditions: ${resultConditionPaths.join(", ")}`;
25547
+ state.status = "blocked";
25548
+ state.blocker = reason;
25549
+ await checkpoint?.(state);
25550
+ return { ...result, exitCode: 64, reason, workflowState: state };
25551
+ }
25552
+ const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
25553
+ if (!transition) {
25554
+ const exhausted = exhaustedWorkflowTransitions(step, chainData, state.transitionCounts);
25555
+ const reason = exhausted.length > 0 ? `workflow step ${step.id} reached iteration limit: ${exhausted.join(", ")}` : `workflow step ${step.id} has no available connection`;
25556
+ state.status = "blocked";
25557
+ state.blocker = reason;
25558
+ await checkpoint?.(state);
25559
+ return { ...result, exitCode: 64, reason, workflowState: state };
25560
+ }
25561
+ if (transition.maxIterations !== void 0) {
25562
+ const key = `${step.id}->${transition.to}`;
25563
+ state.transitionCounts[key] = (state.transitionCounts[key] ?? 0) + 1;
25564
+ }
25565
+ if (transition.to === "$end") {
25566
+ return completeWorkflowAtTerminal(capability, state, result, checkpoint);
25567
+ }
25568
+ state.currentStepId = transition.to;
25569
+ state.status = "running";
25570
+ delete state.blocker;
25571
+ await checkpoint?.(state);
25572
+ return null;
25573
+ }
25533
25574
  async function completeWorkflowAtTerminal(capability, state, output, checkpoint) {
25534
25575
  const result = output.capabilityResults?.at(-1);
25535
25576
  const customStatus = output.capabilityOutput && typeof output.capabilityOutput === "object" && !Array.isArray(output.capabilityOutput) && typeof output.capabilityOutput.status === "string" ? output.capabilityOutput.status : void 0;
@@ -31719,6 +31760,10 @@ function buildServer2(opts) {
31719
31760
  sendJson3(res, 400, { error: parsed.error });
31720
31761
  return;
31721
31762
  }
31763
+ if (busy) {
31764
+ sendJson3(res, 409, { error: "runner busy" });
31765
+ return;
31766
+ }
31722
31767
  busy = true;
31723
31768
  sendJson3(res, 202, { ok: true, jobId: parsed.job.jobId, started: true });
31724
31769
  void runJob2(parsed.job).catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.656",
3
+ "version": "0.4.658",
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
  "repository": {