@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.
- package/dist/bin/kody.js +96 -51
- 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.
|
|
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 (
|
|
9527
|
-
if (!Array.isArray(previous
|
|
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")
|
|
9575
|
-
|
|
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
|
|
9615
|
-
const
|
|
9616
|
-
(
|
|
9617
|
-
|
|
9618
|
-
|
|
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
|
-
|
|
25498
|
-
|
|
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.
|
|
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": {
|