@kody-ade/kody-engine 0.4.565 → 0.4.567
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 +119 -38
- package/dist/implementations/types.ts +2 -0
- 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.567",
|
|
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
|
type: "module",
|
|
@@ -1143,14 +1143,23 @@ function buildVerifyEnv(source = process.env) {
|
|
|
1143
1143
|
env.CI = source.CI ?? "1";
|
|
1144
1144
|
return env;
|
|
1145
1145
|
}
|
|
1146
|
-
function
|
|
1146
|
+
function abortMessage(signal) {
|
|
1147
|
+
const reason = signal.reason;
|
|
1148
|
+
return reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "verification aborted";
|
|
1149
|
+
}
|
|
1150
|
+
function runCommand(command, cwd, signal) {
|
|
1147
1151
|
return new Promise((resolve23) => {
|
|
1148
1152
|
const start = Date.now();
|
|
1153
|
+
if (signal?.aborted) {
|
|
1154
|
+
resolve23({ exitCode: -1, durationMs: 0, tail: abortMessage(signal) });
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1149
1157
|
const child = spawn(command, {
|
|
1150
1158
|
cwd,
|
|
1151
1159
|
shell: true,
|
|
1152
1160
|
env: buildVerifyEnv(),
|
|
1153
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1161
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1162
|
+
detached: process.platform !== "win32"
|
|
1154
1163
|
});
|
|
1155
1164
|
const buffers = [];
|
|
1156
1165
|
let totalSize = 0;
|
|
@@ -1164,24 +1173,46 @@ function runCommand(command, cwd) {
|
|
|
1164
1173
|
};
|
|
1165
1174
|
child.stdout?.on("data", collect);
|
|
1166
1175
|
child.stderr?.on("data", collect);
|
|
1176
|
+
let settled = false;
|
|
1177
|
+
const killTree = (killSignal) => {
|
|
1178
|
+
try {
|
|
1179
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, killSignal);
|
|
1180
|
+
else child.kill(killSignal);
|
|
1181
|
+
} catch {
|
|
1182
|
+
child.kill(killSignal);
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
const finish = (exitCode, extraTail = "") => {
|
|
1186
|
+
if (settled) return;
|
|
1187
|
+
settled = true;
|
|
1188
|
+
clearTimeout(timer);
|
|
1189
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1190
|
+
const output = Buffer.concat(buffers).toString("utf-8");
|
|
1191
|
+
const tail = [output, extraTail].filter(Boolean).join("\n").slice(-TAIL_CHARS);
|
|
1192
|
+
resolve23({ exitCode, durationMs: Date.now() - start, tail });
|
|
1193
|
+
};
|
|
1194
|
+
const terminate = () => {
|
|
1195
|
+
killTree("SIGTERM");
|
|
1196
|
+
setTimeout(() => killTree("SIGKILL"), 5e3).unref();
|
|
1197
|
+
};
|
|
1198
|
+
const onAbort = () => {
|
|
1199
|
+
terminate();
|
|
1200
|
+
finish(-1, signal ? abortMessage(signal) : "verification aborted");
|
|
1201
|
+
};
|
|
1202
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1167
1203
|
const timer = setTimeout(() => {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
if (!child.killed) child.kill("SIGKILL");
|
|
1171
|
-
}, 5e3);
|
|
1204
|
+
terminate();
|
|
1205
|
+
finish(-1, "verification command timed out");
|
|
1172
1206
|
}, COMMAND_TIMEOUT_MS);
|
|
1173
1207
|
child.on("exit", (code) => {
|
|
1174
|
-
|
|
1175
|
-
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
1176
|
-
resolve23({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
1208
|
+
finish(code ?? -1);
|
|
1177
1209
|
});
|
|
1178
1210
|
child.on("error", (err) => {
|
|
1179
|
-
|
|
1180
|
-
resolve23({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
1211
|
+
finish(-1, err.message);
|
|
1181
1212
|
});
|
|
1182
1213
|
});
|
|
1183
1214
|
}
|
|
1184
|
-
async function verifyAll(config, cwd) {
|
|
1215
|
+
async function verifyAll(config, cwd, opts) {
|
|
1185
1216
|
const commands = [];
|
|
1186
1217
|
if (config.quality.typecheck) commands.push({ name: "typecheck", cmd: config.quality.typecheck });
|
|
1187
1218
|
if (config.quality.testUnit) commands.push({ name: "test", cmd: config.quality.testUnit });
|
|
@@ -1190,20 +1221,21 @@ async function verifyAll(config, cwd) {
|
|
|
1190
1221
|
const failed = [];
|
|
1191
1222
|
const details = {};
|
|
1192
1223
|
for (const { name, cmd } of commands) {
|
|
1193
|
-
const result = await runCommand(cmd, cwd);
|
|
1224
|
+
const result = await runCommand(cmd, cwd, opts?.signal);
|
|
1194
1225
|
details[name] = result;
|
|
1195
1226
|
if (result.exitCode !== 0) failed.push(name);
|
|
1196
1227
|
}
|
|
1197
1228
|
return { ok: failed.length === 0, failed, details };
|
|
1198
1229
|
}
|
|
1199
|
-
async function applyTestRetries(initial, testCommand, cwd, runner, testRetries = DEFAULT_TEST_RETRIES) {
|
|
1230
|
+
async function applyTestRetries(initial, testCommand, cwd, runner, testRetries = DEFAULT_TEST_RETRIES, signal) {
|
|
1200
1231
|
if (initial.ok) return { ...initial, recovered: [] };
|
|
1201
1232
|
const recovered = [];
|
|
1202
1233
|
const details = { ...initial.details };
|
|
1203
1234
|
let failed = [...initial.failed];
|
|
1204
1235
|
if (failed.includes("test") && testCommand && testRetries > 0) {
|
|
1205
1236
|
for (let attempt = 1; attempt <= testRetries; attempt++) {
|
|
1206
|
-
|
|
1237
|
+
if (signal?.aborted) break;
|
|
1238
|
+
const retry = await runner(testCommand, cwd, signal);
|
|
1207
1239
|
details[`test (retry ${attempt})`] = retry;
|
|
1208
1240
|
if (retry.exitCode === 0) {
|
|
1209
1241
|
failed = failed.filter((f) => f !== "test");
|
|
@@ -1215,8 +1247,8 @@ async function applyTestRetries(initial, testCommand, cwd, runner, testRetries =
|
|
|
1215
1247
|
return { ok: failed.length === 0, failed, details, recovered };
|
|
1216
1248
|
}
|
|
1217
1249
|
async function verifyAllWithRetry(config, cwd, opts) {
|
|
1218
|
-
const initial = await verifyAll(config, cwd);
|
|
1219
|
-
return applyTestRetries(initial, config.quality.testUnit, cwd, runCommand, opts?.testRetries);
|
|
1250
|
+
const initial = await verifyAll(config, cwd, { signal: opts?.signal });
|
|
1251
|
+
return applyTestRetries(initial, config.quality.testUnit, cwd, runCommand, opts?.testRetries, opts?.signal);
|
|
1220
1252
|
}
|
|
1221
1253
|
function stripAnsi(s) {
|
|
1222
1254
|
return s.replace(ANSI_RE, "");
|
|
@@ -2289,6 +2321,7 @@ function parseWorkflowStep(value) {
|
|
|
2289
2321
|
const target = stringField(raw.target);
|
|
2290
2322
|
const delivery = stringField(raw.delivery);
|
|
2291
2323
|
const targetFact = stringField(raw.targetFact ?? raw.target_fact);
|
|
2324
|
+
const timeoutSeconds = typeof raw.timeoutSeconds === "number" && Number.isInteger(raw.timeoutSeconds) && raw.timeoutSeconds > 0 && raw.timeoutSeconds <= 3600 ? raw.timeoutSeconds : void 0;
|
|
2292
2325
|
const hasInput = Object.hasOwn(raw, "input");
|
|
2293
2326
|
const inputs = parseWorkflowInputBindings(raw.inputs);
|
|
2294
2327
|
const next = parseWorkflowTransitions(raw.next);
|
|
@@ -2304,6 +2337,7 @@ function parseWorkflowStep(value) {
|
|
|
2304
2337
|
...delivery === "pull-request" ? { delivery } : {},
|
|
2305
2338
|
...targetFact ? { targetFact } : {},
|
|
2306
2339
|
...reason ? { reason } : {},
|
|
2340
|
+
...timeoutSeconds ? { timeoutSeconds } : {},
|
|
2307
2341
|
...next ? { next } : {},
|
|
2308
2342
|
...isPlainObject(raw.runWhen) ? { runWhen: raw.runWhen } : {},
|
|
2309
2343
|
...stringList(raw.continueOn ?? raw.continue_on).length > 0 ? { continueOn: stringList(raw.continueOn ?? raw.continue_on) } : {},
|
|
@@ -4722,6 +4756,15 @@ function validateWorkflow(value, options = {}) {
|
|
|
4722
4756
|
if (step.input !== void 0 && step.inputs !== void 0) {
|
|
4723
4757
|
issue(issues, "conflicting_inputs", base, "workflow step cannot declare both input and inputs");
|
|
4724
4758
|
}
|
|
4759
|
+
const timeoutSeconds = step.timeoutSeconds;
|
|
4760
|
+
if (timeoutSeconds !== void 0 && (typeof timeoutSeconds !== "number" || !Number.isInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > 3600)) {
|
|
4761
|
+
issue(
|
|
4762
|
+
issues,
|
|
4763
|
+
"invalid_step_timeout",
|
|
4764
|
+
`${base}.timeoutSeconds`,
|
|
4765
|
+
"workflow step timeoutSeconds must be an integer from 1 to 3600"
|
|
4766
|
+
);
|
|
4767
|
+
}
|
|
4725
4768
|
validateInputBindings(
|
|
4726
4769
|
step.inputs,
|
|
4727
4770
|
`${base}.inputs`,
|
|
@@ -5005,6 +5048,7 @@ var init_workflowValidation = __esm({
|
|
|
5005
5048
|
"delivery",
|
|
5006
5049
|
"targetFact",
|
|
5007
5050
|
"reason",
|
|
5051
|
+
"timeoutSeconds",
|
|
5008
5052
|
"next",
|
|
5009
5053
|
"runWhen",
|
|
5010
5054
|
"continueOn",
|
|
@@ -20877,7 +20921,7 @@ var init_verifyReproFails = __esm({
|
|
|
20877
20921
|
// src/scripts/verifyWithRetry.ts
|
|
20878
20922
|
async function runVerify(ctx) {
|
|
20879
20923
|
try {
|
|
20880
|
-
const result = await verifyAllWithRetry(ctx.config, ctx.cwd);
|
|
20924
|
+
const result = await verifyAllWithRetry(ctx.config, ctx.cwd, { signal: ctx.abortSignal });
|
|
20881
20925
|
ctx.data.verifyOk = result.ok;
|
|
20882
20926
|
ctx.data.verifyReason = result.ok ? "" : summarizeFailure(result);
|
|
20883
20927
|
ctx.data.verifyRecovered = result.recovered ?? [];
|
|
@@ -20922,6 +20966,10 @@ var init_verifyWithRetry = __esm({
|
|
|
20922
20966
|
verifyWithRetry = async (ctx) => {
|
|
20923
20967
|
await runVerify(ctx);
|
|
20924
20968
|
if (ctx.data.verifyOk !== false) return;
|
|
20969
|
+
if (ctx.abortSignal?.aborted) {
|
|
20970
|
+
downgradeActionOnFailure(ctx);
|
|
20971
|
+
return;
|
|
20972
|
+
}
|
|
20925
20973
|
if (!ctx.data.agentDone) {
|
|
20926
20974
|
downgradeActionOnFailure(ctx);
|
|
20927
20975
|
return;
|
|
@@ -20958,6 +21006,10 @@ var init_verifyWithRetry = __esm({
|
|
|
20958
21006
|
process.stderr.write(`[kody] verify retry crashed: ${err instanceof Error ? err.message : String(err)}
|
|
20959
21007
|
`);
|
|
20960
21008
|
}
|
|
21009
|
+
if (ctx.abortSignal?.aborted) {
|
|
21010
|
+
downgradeActionOnFailure(ctx);
|
|
21011
|
+
return;
|
|
21012
|
+
}
|
|
20961
21013
|
await runVerify(ctx);
|
|
20962
21014
|
if (ctx.data.verifyOk === true) {
|
|
20963
21015
|
upgradeActionOnPass(ctx);
|
|
@@ -21961,6 +22013,7 @@ async function runImplementation(profileName, input) {
|
|
|
21961
22013
|
config,
|
|
21962
22014
|
verbose: input.verbose,
|
|
21963
22015
|
quiet: input.quiet,
|
|
22016
|
+
abortSignal: input.abortController?.signal,
|
|
21964
22017
|
// Phase 5 foundation: seed ctx.data with any preloaded values handed
|
|
21965
22018
|
// in by a parent (typically a container loop). Loaders that see
|
|
21966
22019
|
// their field already populated take the fast path and skip the
|
|
@@ -22042,6 +22095,10 @@ async function runImplementation(profileName, input) {
|
|
|
22042
22095
|
const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
|
|
22043
22096
|
const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
|
|
22044
22097
|
const invokeAgent = async (prompt) => {
|
|
22098
|
+
if (input.abortController?.signal.aborted) {
|
|
22099
|
+
const reason = input.abortController.signal.reason;
|
|
22100
|
+
throw reason instanceof Error ? reason : new Error("agent invocation aborted");
|
|
22101
|
+
}
|
|
22045
22102
|
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path51.isAbsolute(p) ? p : path51.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
22046
22103
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
22047
22104
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
@@ -23574,25 +23631,31 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
|
|
|
23574
23631
|
|
|
23575
23632
|
`
|
|
23576
23633
|
);
|
|
23577
|
-
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
...
|
|
23581
|
-
|
|
23582
|
-
|
|
23583
|
-
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
|
|
23589
|
-
|
|
23590
|
-
|
|
23591
|
-
|
|
23592
|
-
|
|
23593
|
-
|
|
23594
|
-
|
|
23595
|
-
|
|
23634
|
+
const stepAbort = workflowStepAbortController(base.abortController, step.timeoutSeconds);
|
|
23635
|
+
try {
|
|
23636
|
+
result = await runJob(child, {
|
|
23637
|
+
...base,
|
|
23638
|
+
abortController: stepAbort.controller,
|
|
23639
|
+
preloadedData: {
|
|
23640
|
+
...chainData,
|
|
23641
|
+
runSubjectType: "capability",
|
|
23642
|
+
runSubjectId: step.capability,
|
|
23643
|
+
runSubjectLabel: step.id,
|
|
23644
|
+
workflowStep: step.id,
|
|
23645
|
+
workflowStepIndex: index + 1,
|
|
23646
|
+
workflowExecutionKey: graphWorkflowExecutionKey(
|
|
23647
|
+
base.preloadedData?.workflowExecutionKey,
|
|
23648
|
+
capability.slug,
|
|
23649
|
+
step.id,
|
|
23650
|
+
state.transitionCounts
|
|
23651
|
+
),
|
|
23652
|
+
workflowStepReason: step.reason,
|
|
23653
|
+
workflowContinueOn: step.continueOn ?? []
|
|
23654
|
+
}
|
|
23655
|
+
});
|
|
23656
|
+
} finally {
|
|
23657
|
+
stepAbort.cleanup();
|
|
23658
|
+
}
|
|
23596
23659
|
finishWorkflowStep(state, step, result);
|
|
23597
23660
|
mergeWorkflowResults(state, result.capabilityResults);
|
|
23598
23661
|
if (result.capabilityOutput && typeof result.capabilityOutput === "object" && !Array.isArray(result.capabilityOutput)) {
|
|
@@ -23900,6 +23963,24 @@ function canContinueWorkflow(step, outcome) {
|
|
|
23900
23963
|
if (!outcome || !step.continueOn || step.continueOn.length === 0) return false;
|
|
23901
23964
|
return step.continueOn.includes(outcome.type);
|
|
23902
23965
|
}
|
|
23966
|
+
function workflowStepAbortController(parent, timeoutSeconds) {
|
|
23967
|
+
if (!timeoutSeconds) return { controller: parent, cleanup: () => void 0 };
|
|
23968
|
+
const controller = new AbortController();
|
|
23969
|
+
const forwardParentAbort = () => controller.abort(parent?.signal.reason);
|
|
23970
|
+
if (parent?.signal.aborted) forwardParentAbort();
|
|
23971
|
+
else parent?.signal.addEventListener("abort", forwardParentAbort, { once: true });
|
|
23972
|
+
const timer = setTimeout(() => {
|
|
23973
|
+
controller.abort(new Error(`workflow step timed out after ${timeoutSeconds}s`));
|
|
23974
|
+
}, timeoutSeconds * 1e3);
|
|
23975
|
+
timer.unref?.();
|
|
23976
|
+
return {
|
|
23977
|
+
controller,
|
|
23978
|
+
cleanup: () => {
|
|
23979
|
+
clearTimeout(timer);
|
|
23980
|
+
parent?.signal.removeEventListener("abort", forwardParentAbort);
|
|
23981
|
+
}
|
|
23982
|
+
};
|
|
23983
|
+
}
|
|
23903
23984
|
function workflowOutcome(result) {
|
|
23904
23985
|
return result.taskState?.core.lastOutcome ?? null;
|
|
23905
23986
|
}
|
|
@@ -456,6 +456,8 @@ export interface Context {
|
|
|
456
456
|
/** Stream-output verbosity. */
|
|
457
457
|
verbose?: boolean
|
|
458
458
|
quiet?: boolean
|
|
459
|
+
/** Cancellation owned by the enclosing job or workflow step. */
|
|
460
|
+
abortSignal?: AbortSignal
|
|
459
461
|
/** Opaque bag scripts populate during preflight (issue, pr, diff, logs, …). */
|
|
460
462
|
data: Record<string, unknown>
|
|
461
463
|
/** Final output the executor returns. */
|
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.567",
|
|
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
|
"type": "module",
|