@kody-ade/kody-engine 0.4.43 → 0.4.45
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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@kody-ade/kody-engine",
|
|
6
|
-
version: "0.4.
|
|
6
|
+
version: "0.4.45",
|
|
7
7
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
8
8
|
license: "MIT",
|
|
9
9
|
type: "module",
|
|
@@ -390,7 +390,27 @@ function formatBytes(bytes) {
|
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
// src/agent.ts
|
|
393
|
+
function classifySubtype(subtype) {
|
|
394
|
+
if (!subtype) return "generic_failed";
|
|
395
|
+
const lower = subtype.toLowerCase();
|
|
396
|
+
if (lower === "success") return "ok";
|
|
397
|
+
if (lower.includes("max_turns") || lower.includes("max-turns")) return "out_of_turns";
|
|
398
|
+
if (lower.includes("rate_limit") || lower.includes("rate-limit")) return "rate_limit";
|
|
399
|
+
if (lower.includes("tool")) return "tool_error";
|
|
400
|
+
if (lower.includes("error")) return "model_error";
|
|
401
|
+
return "generic_failed";
|
|
402
|
+
}
|
|
393
403
|
var DEFAULT_ALLOWED_TOOLS = ["Bash", "Edit", "Read", "Write", "Glob", "Grep"];
|
|
404
|
+
var DEFAULT_TURN_TIMEOUT_MS = 3e5;
|
|
405
|
+
function resolveTurnTimeoutMs(opts) {
|
|
406
|
+
if (opts.maxTurnTimeoutMs !== void 0 && opts.maxTurnTimeoutMs !== null) {
|
|
407
|
+
return opts.maxTurnTimeoutMs > 0 ? opts.maxTurnTimeoutMs : 0;
|
|
408
|
+
}
|
|
409
|
+
const envSec = Number(process.env.KODY_TURN_TIMEOUT_SEC);
|
|
410
|
+
if (Number.isFinite(envSec) && envSec > 0) return Math.floor(envSec * 1e3);
|
|
411
|
+
if (Number.isFinite(envSec) && envSec <= 0) return 0;
|
|
412
|
+
return DEFAULT_TURN_TIMEOUT_MS;
|
|
413
|
+
}
|
|
394
414
|
async function runAgent(opts) {
|
|
395
415
|
const ndjsonDir = opts.ndjsonDir ?? path3.join(opts.cwd, ".kody");
|
|
396
416
|
fs3.mkdirSync(ndjsonDir, { recursive: true });
|
|
@@ -416,10 +436,14 @@ async function runAgent(opts) {
|
|
|
416
436
|
}
|
|
417
437
|
const resultTexts = [];
|
|
418
438
|
let outcome = "failed";
|
|
439
|
+
let outcomeKind = "generic_failed";
|
|
419
440
|
let errorMessage;
|
|
420
441
|
const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
|
|
421
442
|
let messageCount = 0;
|
|
422
443
|
const startedAt = Date.now();
|
|
444
|
+
const turnTimeoutMs = resolveTurnTimeoutMs(opts);
|
|
445
|
+
let ndjsonWriteFailed = false;
|
|
446
|
+
let ndjsonWriteError;
|
|
423
447
|
try {
|
|
424
448
|
const queryOptions = {
|
|
425
449
|
model: opts.model.model,
|
|
@@ -456,12 +480,45 @@ async function runAgent(opts) {
|
|
|
456
480
|
// biome-ignore lint/suspicious/noExplicitAny: SDK options type is narrow; mcpServers is runtime-passthrough.
|
|
457
481
|
options: queryOptions
|
|
458
482
|
});
|
|
459
|
-
|
|
483
|
+
const iterator = typeof result[Symbol.asyncIterator] === "function" ? result[Symbol.asyncIterator]() : result;
|
|
484
|
+
while (true) {
|
|
485
|
+
const nextPromise = iterator.next();
|
|
486
|
+
let timedOut = false;
|
|
487
|
+
let timer;
|
|
488
|
+
let next;
|
|
489
|
+
if (turnTimeoutMs > 0) {
|
|
490
|
+
const timeoutPromise = new Promise((resolve4) => {
|
|
491
|
+
timer = setTimeout(() => {
|
|
492
|
+
timedOut = true;
|
|
493
|
+
resolve4({ done: true, value: void 0 });
|
|
494
|
+
}, turnTimeoutMs);
|
|
495
|
+
});
|
|
496
|
+
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
497
|
+
if (timer) clearTimeout(timer);
|
|
498
|
+
} else {
|
|
499
|
+
next = await nextPromise;
|
|
500
|
+
}
|
|
501
|
+
if (timedOut) {
|
|
502
|
+
outcome = "failed";
|
|
503
|
+
outcomeKind = "stalled";
|
|
504
|
+
errorMessage = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
|
|
505
|
+
if (typeof iterator.return === "function") {
|
|
506
|
+
try {
|
|
507
|
+
await iterator.return(void 0);
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
if (next.done) break;
|
|
514
|
+
const msg = next.value;
|
|
460
515
|
messageCount++;
|
|
461
516
|
try {
|
|
462
517
|
fullLog.write(`${JSON.stringify(msg)}
|
|
463
518
|
`);
|
|
464
|
-
} catch {
|
|
519
|
+
} catch (e) {
|
|
520
|
+
ndjsonWriteFailed = true;
|
|
521
|
+
ndjsonWriteError = e instanceof Error ? e.message : String(e);
|
|
465
522
|
}
|
|
466
523
|
const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
|
|
467
524
|
if (line) process.stdout.write(`${line}
|
|
@@ -481,16 +538,19 @@ async function runAgent(opts) {
|
|
|
481
538
|
if (m.type === "result") {
|
|
482
539
|
if (m.subtype === "success") {
|
|
483
540
|
outcome = "completed";
|
|
541
|
+
outcomeKind = "ok";
|
|
484
542
|
const text = (typeof m.result === "string" ? m.result : "").trim();
|
|
485
543
|
if (text) resultTexts.push(text);
|
|
486
544
|
} else {
|
|
487
545
|
outcome = "failed";
|
|
546
|
+
outcomeKind = classifySubtype(m.subtype);
|
|
488
547
|
errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
|
|
489
548
|
}
|
|
490
549
|
}
|
|
491
550
|
}
|
|
492
551
|
} catch (e) {
|
|
493
552
|
outcome = "failed";
|
|
553
|
+
outcomeKind = "model_error";
|
|
494
554
|
errorMessage = e instanceof Error ? e.message : String(e);
|
|
495
555
|
} finally {
|
|
496
556
|
try {
|
|
@@ -498,9 +558,14 @@ async function runAgent(opts) {
|
|
|
498
558
|
} catch {
|
|
499
559
|
}
|
|
500
560
|
}
|
|
561
|
+
if (ndjsonWriteFailed) {
|
|
562
|
+
process.stderr.write(`[kody agent] NDJSON write failed (post-mortem may be incomplete): ${ndjsonWriteError ?? "unknown error"}
|
|
563
|
+
`);
|
|
564
|
+
}
|
|
501
565
|
const finalText = resultTexts.join("\n\n---\n\n");
|
|
502
566
|
return {
|
|
503
567
|
outcome,
|
|
568
|
+
outcomeKind,
|
|
504
569
|
finalText,
|
|
505
570
|
error: errorMessage,
|
|
506
571
|
ndjsonPath,
|
|
@@ -2018,6 +2083,13 @@ async function checkLitellmHealth(url) {
|
|
|
2018
2083
|
return false;
|
|
2019
2084
|
}
|
|
2020
2085
|
}
|
|
2086
|
+
var DEFAULT_LITELLM_STARTUP_TIMEOUT_SEC = 60;
|
|
2087
|
+
var LITELLM_HEALTH_POLL_INTERVAL_MS = 2e3;
|
|
2088
|
+
function resolveLitellmTimeoutMs() {
|
|
2089
|
+
const envSec = Number(process.env.KODY_LITELLM_TIMEOUT_SEC);
|
|
2090
|
+
if (Number.isFinite(envSec) && envSec > 0) return Math.floor(envSec * 1e3);
|
|
2091
|
+
return DEFAULT_LITELLM_STARTUP_TIMEOUT_SEC * 1e3;
|
|
2092
|
+
}
|
|
2021
2093
|
function generateLitellmConfigYaml(model) {
|
|
2022
2094
|
const apiKeyVar = providerApiKeyEnvVar(model.provider);
|
|
2023
2095
|
return [
|
|
@@ -2063,8 +2135,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
2063
2135
|
env: stripBlockingEnv({ ...process.env, ...dotenvVars })
|
|
2064
2136
|
});
|
|
2065
2137
|
fs10.closeSync(outFd);
|
|
2066
|
-
|
|
2067
|
-
|
|
2138
|
+
const timeoutMs = resolveLitellmTimeoutMs();
|
|
2139
|
+
const deadline = Date.now() + timeoutMs;
|
|
2140
|
+
while (Date.now() < deadline) {
|
|
2141
|
+
await new Promise((r) => setTimeout(r, LITELLM_HEALTH_POLL_INTERVAL_MS));
|
|
2068
2142
|
if (await checkLitellmHealth(url)) {
|
|
2069
2143
|
return {
|
|
2070
2144
|
url,
|
|
@@ -2086,7 +2160,8 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
2086
2160
|
child.kill();
|
|
2087
2161
|
} catch {
|
|
2088
2162
|
}
|
|
2089
|
-
|
|
2163
|
+
const seconds = Math.round(timeoutMs / 1e3);
|
|
2164
|
+
throw new Error(`LiteLLM proxy failed to start within ${seconds}s (KODY_LITELLM_TIMEOUT_SEC overrides). Log tail:
|
|
2090
2165
|
${logTail}`);
|
|
2091
2166
|
}
|
|
2092
2167
|
function readDotenvApiKeys(projectDir) {
|
|
@@ -6875,6 +6950,7 @@ var parseAgentResult2 = async (ctx, profile, agentResult) => {
|
|
|
6875
6950
|
ctx.data.agentFailureReason = parsed.failureReason;
|
|
6876
6951
|
ctx.data.agentMarkerMissing = parsed.markerMissing;
|
|
6877
6952
|
ctx.data.agentOutcome = agentResult.outcome;
|
|
6953
|
+
ctx.data.agentOutcomeKind = agentResult.outcomeKind;
|
|
6878
6954
|
ctx.data.agentError = agentResult.error;
|
|
6879
6955
|
const modeSeg = (ctx.args.mode ?? profile.name).replace(/-/g, "_").toUpperCase();
|
|
6880
6956
|
if (parsed.done) {
|
|
@@ -9100,7 +9176,8 @@ async function runExecutable(profileName, input) {
|
|
|
9100
9176
|
return finishAndEnd({ exitCode: 99, reason: `config error: ${err instanceof Error ? err.message : String(err)}` });
|
|
9101
9177
|
}
|
|
9102
9178
|
}
|
|
9103
|
-
const
|
|
9179
|
+
const perExecutableModel = config.agent.perExecutable?.[profileName];
|
|
9180
|
+
const modelSpec = perExecutableModel ? perExecutableModel : profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
|
|
9104
9181
|
let model;
|
|
9105
9182
|
try {
|
|
9106
9183
|
model = parseProviderModel(modelSpec);
|
|
@@ -9204,6 +9281,7 @@ async function runExecutable(profileName, input) {
|
|
|
9204
9281
|
durationMs: agentResult.durationMs,
|
|
9205
9282
|
outcome: agentResult.outcome === "completed" ? "ok" : "failed",
|
|
9206
9283
|
meta: {
|
|
9284
|
+
kind: agentResult.outcomeKind,
|
|
9207
9285
|
...agentResult.tokens ? { tokens: agentResult.tokens } : {},
|
|
9208
9286
|
...typeof agentResult.messageCount === "number" ? { messageCount: agentResult.messageCount } : {},
|
|
9209
9287
|
...agentResult.error ? { error: agentResult.error } : {}
|
|
@@ -10126,8 +10204,8 @@ async function runScheduledFanOut(cwd, args, opts) {
|
|
|
10126
10204
|
return 99;
|
|
10127
10205
|
}
|
|
10128
10206
|
const config = loadConfig(cwd);
|
|
10129
|
-
|
|
10130
|
-
|
|
10207
|
+
const serial = process.env.KODY_SERIAL_WATCHES === "1";
|
|
10208
|
+
const runWatch = async (match) => {
|
|
10131
10209
|
process.stdout.write(`
|
|
10132
10210
|
\u2192 kody: running watch \`${match.executable}\`
|
|
10133
10211
|
`);
|
|
@@ -10144,13 +10222,27 @@ async function runScheduledFanOut(cwd, args, opts) {
|
|
|
10144
10222
|
`[kody] watch \`${match.executable}\` exited ${result.exitCode}: ${result.reason ?? "(no reason)"}
|
|
10145
10223
|
`
|
|
10146
10224
|
);
|
|
10147
|
-
|
|
10225
|
+
return result.exitCode;
|
|
10148
10226
|
}
|
|
10227
|
+
return 0;
|
|
10149
10228
|
} catch (err) {
|
|
10150
10229
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10151
10230
|
process.stderr.write(`[kody] watch \`${match.executable}\` crashed: ${msg}
|
|
10152
10231
|
`);
|
|
10153
|
-
|
|
10232
|
+
return 99;
|
|
10233
|
+
}
|
|
10234
|
+
};
|
|
10235
|
+
let worstExit = 0;
|
|
10236
|
+
if (serial) {
|
|
10237
|
+
for (const match of matches) {
|
|
10238
|
+
const code = await runWatch(match);
|
|
10239
|
+
if (code > worstExit) worstExit = code;
|
|
10240
|
+
}
|
|
10241
|
+
} else {
|
|
10242
|
+
const settled = await Promise.allSettled(matches.map((m) => runWatch(m)));
|
|
10243
|
+
for (const r of settled) {
|
|
10244
|
+
const code = r.status === "fulfilled" ? r.value : 99;
|
|
10245
|
+
if (code > worstExit) worstExit = code;
|
|
10154
10246
|
}
|
|
10155
10247
|
}
|
|
10156
10248
|
return worstExit;
|
|
@@ -20,34 +20,16 @@
|
|
|
20
20
|
"Read",
|
|
21
21
|
"Grep",
|
|
22
22
|
"Glob",
|
|
23
|
-
"Bash"
|
|
24
|
-
"mcp__playwright"
|
|
23
|
+
"Bash"
|
|
25
24
|
],
|
|
26
25
|
"hooks": ["block-write"],
|
|
27
26
|
"skills": [],
|
|
28
27
|
"commands": [],
|
|
29
28
|
"subagents": [],
|
|
30
29
|
"plugins": [],
|
|
31
|
-
"mcpServers": [
|
|
32
|
-
{
|
|
33
|
-
"name": "playwright",
|
|
34
|
-
"command": "npx",
|
|
35
|
-
"args": ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp"]
|
|
36
|
-
}
|
|
37
|
-
]
|
|
30
|
+
"mcpServers": []
|
|
38
31
|
},
|
|
39
|
-
"cliTools": [
|
|
40
|
-
{
|
|
41
|
-
"name": "playwright",
|
|
42
|
-
"install": {
|
|
43
|
-
"required": false,
|
|
44
|
-
"checkCommand": "ls \"$HOME/.cache/ms-playwright\" 2>/dev/null | grep -q '^chromium'",
|
|
45
|
-
"installCommand": "npx --yes playwright install --with-deps chromium"
|
|
46
|
-
},
|
|
47
|
-
"verify": "ls \"$HOME/.cache/ms-playwright\" 2>/dev/null | grep -q '^chromium'",
|
|
48
|
-
"usage": ""
|
|
49
|
-
}
|
|
50
|
-
],
|
|
32
|
+
"cliTools": [],
|
|
51
33
|
"scripts": {
|
|
52
34
|
"preflight": [
|
|
53
35
|
{
|
|
@@ -21,34 +21,16 @@
|
|
|
21
21
|
"Read",
|
|
22
22
|
"Grep",
|
|
23
23
|
"Glob",
|
|
24
|
-
"Bash"
|
|
25
|
-
"mcp__playwright"
|
|
24
|
+
"Bash"
|
|
26
25
|
],
|
|
27
26
|
"hooks": ["block-write"],
|
|
28
27
|
"skills": [],
|
|
29
28
|
"commands": [],
|
|
30
29
|
"subagents": [],
|
|
31
30
|
"plugins": [],
|
|
32
|
-
"mcpServers": [
|
|
33
|
-
{
|
|
34
|
-
"name": "playwright",
|
|
35
|
-
"command": "npx",
|
|
36
|
-
"args": ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp"]
|
|
37
|
-
}
|
|
38
|
-
]
|
|
31
|
+
"mcpServers": []
|
|
39
32
|
},
|
|
40
|
-
"cliTools": [
|
|
41
|
-
{
|
|
42
|
-
"name": "playwright",
|
|
43
|
-
"install": {
|
|
44
|
-
"required": false,
|
|
45
|
-
"checkCommand": "ls \"$HOME/.cache/ms-playwright\" 2>/dev/null | grep -q '^chromium'",
|
|
46
|
-
"installCommand": "npx --yes playwright install --with-deps chromium"
|
|
47
|
-
},
|
|
48
|
-
"verify": "ls \"$HOME/.cache/ms-playwright\" 2>/dev/null | grep -q '^chromium'",
|
|
49
|
-
"usage": ""
|
|
50
|
-
}
|
|
51
|
-
],
|
|
33
|
+
"cliTools": [],
|
|
52
34
|
"scripts": {
|
|
53
35
|
"preflight": [
|
|
54
36
|
{
|
package/kody.config.schema.json
CHANGED
|
@@ -135,6 +135,14 @@
|
|
|
135
135
|
"description": "Single 'provider/model' string used by kody (single-session pipeline). Use 'claude/...' or 'anthropic/...' for direct Anthropic API; anything else routes through LiteLLM proxy.",
|
|
136
136
|
"examples": ["claude/claude-sonnet-4-6", "minimax/MiniMax-M2.7-highspeed"]
|
|
137
137
|
},
|
|
138
|
+
"perExecutable": {
|
|
139
|
+
"type": "object",
|
|
140
|
+
"description": "Per-executable model override. Wins over agent.model for the matching stage. Example: {\"classify\": \"claude/claude-haiku-4-5-20251001\", \"plan\": \"claude/claude-opus-4-7\"}.",
|
|
141
|
+
"additionalProperties": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"pattern": "^[^/]+/.+$"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
138
146
|
"modelMap": {
|
|
139
147
|
"type": "object",
|
|
140
148
|
"description": "Maps model tiers to 'provider/model' strings. Use 'claude/...' or 'anthropic/...' for direct Anthropic API; anything else routes through LiteLLM proxy.",
|
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.45",
|
|
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",
|