@kody-ade/kody-engine 0.4.71 → 0.4.73
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 +378 -89
- package/dist/executables/brain-serve/profile.json +28 -0
- package/dist/executables/goal-scheduler/scheduler.sh +0 -0
- package/dist/executables/plan/prompt.md +13 -1
- package/dist/executables/release-deploy/deploy.sh +0 -0
- package/dist/executables/release-prepare/prepare.sh +0 -0
- package/dist/executables/release-publish/publish.sh +0 -0
- package/dist/executables/resolve/apply-prefer.sh +0 -0
- package/dist/executables/revert/revert.sh +0 -0
- package/dist/executables/run/prompt.md +2 -0
- package/package.json +20 -19
package/dist/bin/kody.js
CHANGED
|
@@ -864,7 +864,7 @@ var init_loadPriorArt = __esm({
|
|
|
864
864
|
// package.json
|
|
865
865
|
var package_default = {
|
|
866
866
|
name: "@kody-ade/kody-engine",
|
|
867
|
-
version: "0.4.
|
|
867
|
+
version: "0.4.73",
|
|
868
868
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
869
869
|
license: "MIT",
|
|
870
870
|
type: "module",
|
|
@@ -919,8 +919,8 @@ var package_default = {
|
|
|
919
919
|
|
|
920
920
|
// src/chat-cli.ts
|
|
921
921
|
import { execFileSync as execFileSync31 } from "child_process";
|
|
922
|
-
import * as
|
|
923
|
-
import * as
|
|
922
|
+
import * as fs34 from "fs";
|
|
923
|
+
import * as path32 from "path";
|
|
924
924
|
|
|
925
925
|
// src/chat/events.ts
|
|
926
926
|
import * as fs from "fs";
|
|
@@ -1722,6 +1722,7 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
1722
1722
|
// src/chat/modes/interactive.ts
|
|
1723
1723
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1724
1724
|
import * as fs6 from "fs";
|
|
1725
|
+
import * as os from "os";
|
|
1725
1726
|
import * as path6 from "path";
|
|
1726
1727
|
|
|
1727
1728
|
// src/chat/inbox.ts
|
|
@@ -1883,6 +1884,45 @@ function commitTurn(cwd, sessionId, verbose) {
|
|
|
1883
1884
|
const eventsRel = path6.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
1884
1885
|
const paths = [sessionRel, eventsRel].filter((p) => fs6.existsSync(path6.join(cwd, p)));
|
|
1885
1886
|
if (paths.length === 0) return;
|
|
1887
|
+
const startBranch = currentBranch2(cwd);
|
|
1888
|
+
const eventsBranch = defaultBranch(cwd) ?? "main";
|
|
1889
|
+
if (startBranch === eventsBranch) {
|
|
1890
|
+
commitPathsAndPush(cwd, paths, sessionId, verbose, "HEAD");
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
const stdio = verbose ? "inherit" : "pipe";
|
|
1894
|
+
const exec = (args) => execFileSync2("git", args, { cwd, stdio });
|
|
1895
|
+
const worktreeDir = fs6.mkdtempSync(path6.join(os.tmpdir(), "kody-events-"));
|
|
1896
|
+
let worktreeAdded = false;
|
|
1897
|
+
try {
|
|
1898
|
+
exec(["fetch", "--quiet", "origin", eventsBranch]);
|
|
1899
|
+
exec(["worktree", "add", "--detach", "--quiet", worktreeDir, `origin/${eventsBranch}`]);
|
|
1900
|
+
worktreeAdded = true;
|
|
1901
|
+
for (const rel of paths) {
|
|
1902
|
+
const src = path6.join(cwd, rel);
|
|
1903
|
+
const dst = path6.join(worktreeDir, rel);
|
|
1904
|
+
fs6.mkdirSync(path6.dirname(dst), { recursive: true });
|
|
1905
|
+
fs6.copyFileSync(src, dst);
|
|
1906
|
+
}
|
|
1907
|
+
commitPathsAndPush(worktreeDir, paths, sessionId, verbose, `HEAD:${eventsBranch}`);
|
|
1908
|
+
} catch (err) {
|
|
1909
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1910
|
+
process.stderr.write(`[kody:chat:interactive] worktree commit failed: ${msg}
|
|
1911
|
+
`);
|
|
1912
|
+
} finally {
|
|
1913
|
+
if (worktreeAdded) {
|
|
1914
|
+
try {
|
|
1915
|
+
exec(["worktree", "remove", "--force", "--quiet", worktreeDir]);
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
try {
|
|
1920
|
+
fs6.rmSync(worktreeDir, { recursive: true, force: true });
|
|
1921
|
+
} catch {
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
function commitPathsAndPush(cwd, paths, sessionId, verbose, pushSpec) {
|
|
1886
1926
|
const stdio = verbose ? "inherit" : "pipe";
|
|
1887
1927
|
const exec = (args) => execFileSync2("git", args, { cwd, stdio });
|
|
1888
1928
|
try {
|
|
@@ -1896,7 +1936,7 @@ function commitTurn(cwd, sessionId, verbose) {
|
|
|
1896
1936
|
}
|
|
1897
1937
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
1898
1938
|
try {
|
|
1899
|
-
exec(["push", "--quiet", "origin",
|
|
1939
|
+
exec(["push", "--quiet", "origin", pushSpec]);
|
|
1900
1940
|
return;
|
|
1901
1941
|
} catch (err) {
|
|
1902
1942
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1910,14 +1950,16 @@ function commitTurn(cwd, sessionId, verbose) {
|
|
|
1910
1950
|
`);
|
|
1911
1951
|
try {
|
|
1912
1952
|
exec(["fetch", "--quiet", "origin"]);
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1953
|
+
const upstream = pushSpec.includes(":") ? `origin/${pushSpec.split(":")[1]}` : (() => {
|
|
1954
|
+
const branch = currentBranch2(cwd);
|
|
1955
|
+
return branch ? `origin/${branch}` : null;
|
|
1956
|
+
})();
|
|
1957
|
+
if (!upstream) {
|
|
1958
|
+
process.stderr.write(`[kody:chat:interactive] cannot rebase: no upstream resolved
|
|
1918
1959
|
`);
|
|
1919
1960
|
return;
|
|
1920
1961
|
}
|
|
1962
|
+
exec(["rebase", "--quiet", upstream]);
|
|
1921
1963
|
} catch (rebaseErr) {
|
|
1922
1964
|
const rmsg = rebaseErr instanceof Error ? rebaseErr.message : String(rebaseErr);
|
|
1923
1965
|
process.stderr.write(`[kody:chat:interactive] rebase failed: ${rmsg}
|
|
@@ -1927,6 +1969,20 @@ function commitTurn(cwd, sessionId, verbose) {
|
|
|
1927
1969
|
}
|
|
1928
1970
|
}
|
|
1929
1971
|
}
|
|
1972
|
+
function defaultBranch(cwd) {
|
|
1973
|
+
try {
|
|
1974
|
+
const out = execFileSync2(
|
|
1975
|
+
"git",
|
|
1976
|
+
["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
|
|
1977
|
+
{ cwd, stdio: ["ignore", "pipe", "ignore"] }
|
|
1978
|
+
);
|
|
1979
|
+
const symbolic = out.toString("utf-8").trim();
|
|
1980
|
+
if (symbolic.startsWith("origin/")) return symbolic.slice("origin/".length);
|
|
1981
|
+
return symbolic || null;
|
|
1982
|
+
} catch {
|
|
1983
|
+
return null;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1930
1986
|
function currentBranch2(cwd) {
|
|
1931
1987
|
try {
|
|
1932
1988
|
const out = execFileSync2("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
@@ -1957,8 +2013,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
1957
2013
|
|
|
1958
2014
|
// src/kody-cli.ts
|
|
1959
2015
|
import { execFileSync as execFileSync30 } from "child_process";
|
|
1960
|
-
import * as
|
|
1961
|
-
import * as
|
|
2016
|
+
import * as fs33 from "fs";
|
|
2017
|
+
import * as path31 from "path";
|
|
1962
2018
|
|
|
1963
2019
|
// src/dispatch.ts
|
|
1964
2020
|
import * as fs8 from "fs";
|
|
@@ -2399,8 +2455,8 @@ init_issue();
|
|
|
2399
2455
|
|
|
2400
2456
|
// src/executor.ts
|
|
2401
2457
|
import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
|
|
2402
|
-
import * as
|
|
2403
|
-
import * as
|
|
2458
|
+
import * as fs32 from "fs";
|
|
2459
|
+
import * as path30 from "path";
|
|
2404
2460
|
init_events();
|
|
2405
2461
|
|
|
2406
2462
|
// src/lifecycleLabels.ts
|
|
@@ -3022,7 +3078,7 @@ function errMsg(err) {
|
|
|
3022
3078
|
// src/litellm.ts
|
|
3023
3079
|
import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
|
|
3024
3080
|
import * as fs10 from "fs";
|
|
3025
|
-
import * as
|
|
3081
|
+
import * as os2 from "os";
|
|
3026
3082
|
import * as path9 from "path";
|
|
3027
3083
|
async function checkLitellmHealth(url) {
|
|
3028
3084
|
try {
|
|
@@ -3070,13 +3126,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3070
3126
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
3071
3127
|
}
|
|
3072
3128
|
}
|
|
3073
|
-
const configPath = path9.join(
|
|
3129
|
+
const configPath = path9.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
3074
3130
|
fs10.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
3075
3131
|
const portMatch = url.match(/:(\d+)/);
|
|
3076
3132
|
const port = portMatch ? portMatch[1] : "4000";
|
|
3077
3133
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
3078
3134
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
3079
|
-
const logPath = path9.join(
|
|
3135
|
+
const logPath = path9.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
3080
3136
|
const outFd = fs10.openSync(logPath, "w");
|
|
3081
3137
|
const child = spawn2(cmd, args, {
|
|
3082
3138
|
stdio: ["ignore", outFd, outFd],
|
|
@@ -3306,13 +3362,13 @@ function commitAndPush(branch, agentMessage, cwd) {
|
|
|
3306
3362
|
}
|
|
3307
3363
|
}
|
|
3308
3364
|
}
|
|
3309
|
-
function hasCommitsAhead(branch,
|
|
3365
|
+
function hasCommitsAhead(branch, defaultBranch2, cwd) {
|
|
3310
3366
|
try {
|
|
3311
|
-
const out = git(["rev-list", "--count", `origin/${
|
|
3367
|
+
const out = git(["rev-list", "--count", `origin/${defaultBranch2}..${branch}`], cwd);
|
|
3312
3368
|
return parseInt(out, 10) > 0;
|
|
3313
3369
|
} catch {
|
|
3314
3370
|
try {
|
|
3315
|
-
const out = git(["rev-list", "--count", `${
|
|
3371
|
+
const out = git(["rev-list", "--count", `${defaultBranch2}..${branch}`], cwd);
|
|
3316
3372
|
return parseInt(out, 10) > 0;
|
|
3317
3373
|
} catch {
|
|
3318
3374
|
return false;
|
|
@@ -3578,7 +3634,7 @@ var advanceFlow = async (ctx, profile) => {
|
|
|
3578
3634
|
|
|
3579
3635
|
// src/scripts/buildSyntheticPlugin.ts
|
|
3580
3636
|
import * as fs12 from "fs";
|
|
3581
|
-
import * as
|
|
3637
|
+
import * as os3 from "os";
|
|
3582
3638
|
import * as path11 from "path";
|
|
3583
3639
|
function getPluginsCatalogRoot() {
|
|
3584
3640
|
const here = path11.dirname(new URL(import.meta.url).pathname);
|
|
@@ -3601,7 +3657,7 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
3601
3657
|
if (!needsSynthetic) return;
|
|
3602
3658
|
const catalog = getPluginsCatalogRoot();
|
|
3603
3659
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3604
|
-
const root = path11.join(
|
|
3660
|
+
const root = path11.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
3605
3661
|
fs12.mkdirSync(path11.join(root, ".claude-plugin"), { recursive: true });
|
|
3606
3662
|
const resolvePart = (bucket, entry) => {
|
|
3607
3663
|
const local = path11.join(profile.dir, bucket, entry);
|
|
@@ -4810,10 +4866,10 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
|
4810
4866
|
// src/scripts/diagMcp.ts
|
|
4811
4867
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
4812
4868
|
import * as fs17 from "fs";
|
|
4813
|
-
import * as
|
|
4869
|
+
import * as os4 from "os";
|
|
4814
4870
|
import * as path17 from "path";
|
|
4815
4871
|
var diagMcp = async (_ctx) => {
|
|
4816
|
-
const home =
|
|
4872
|
+
const home = os4.homedir();
|
|
4817
4873
|
const cacheDir = path17.join(home, ".cache", "ms-playwright");
|
|
4818
4874
|
let entries = [];
|
|
4819
4875
|
try {
|
|
@@ -6474,14 +6530,6 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
|
|
|
6474
6530
|
|
|
6475
6531
|
// src/branch.ts
|
|
6476
6532
|
import { execFileSync as execFileSync15 } from "child_process";
|
|
6477
|
-
var UncommittedChangesError = class extends Error {
|
|
6478
|
-
constructor(branch) {
|
|
6479
|
-
super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
|
|
6480
|
-
this.branch = branch;
|
|
6481
|
-
this.name = "UncommittedChangesError";
|
|
6482
|
-
}
|
|
6483
|
-
branch;
|
|
6484
|
-
};
|
|
6485
6533
|
function git2(args, cwd) {
|
|
6486
6534
|
return execFileSync15("git", args, {
|
|
6487
6535
|
encoding: "utf-8",
|
|
@@ -6498,8 +6546,15 @@ function deriveBranchName(issueNumber, title) {
|
|
|
6498
6546
|
function getCurrentBranch(cwd) {
|
|
6499
6547
|
return git2(["branch", "--show-current"], cwd);
|
|
6500
6548
|
}
|
|
6501
|
-
function
|
|
6502
|
-
|
|
6549
|
+
function resetWorkingTree(cwd) {
|
|
6550
|
+
try {
|
|
6551
|
+
execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
6552
|
+
} catch {
|
|
6553
|
+
}
|
|
6554
|
+
try {
|
|
6555
|
+
execFileSync15("git", ["clean", "-fd"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
|
|
6556
|
+
} catch {
|
|
6557
|
+
}
|
|
6503
6558
|
}
|
|
6504
6559
|
function checkoutPrBranch(prNumber, cwd) {
|
|
6505
6560
|
const env = {
|
|
@@ -6546,14 +6601,13 @@ function mergeBase(baseBranch, cwd) {
|
|
|
6546
6601
|
return "error";
|
|
6547
6602
|
}
|
|
6548
6603
|
}
|
|
6549
|
-
function ensureFeatureBranch(issueNumber, title,
|
|
6604
|
+
function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch) {
|
|
6550
6605
|
const branchName = deriveBranchName(issueNumber, title);
|
|
6606
|
+
resetWorkingTree(cwd);
|
|
6551
6607
|
const current = getCurrentBranch(cwd);
|
|
6552
6608
|
if (current === branchName) {
|
|
6553
|
-
if (hasUncommittedChanges(cwd)) throw new UncommittedChangesError(branchName);
|
|
6554
6609
|
return { branch: branchName, created: false };
|
|
6555
6610
|
}
|
|
6556
|
-
if (hasUncommittedChanges(cwd)) throw new UncommittedChangesError(current || "(detached)");
|
|
6557
6611
|
try {
|
|
6558
6612
|
git2(["fetch", "origin"], cwd);
|
|
6559
6613
|
} catch {
|
|
@@ -6564,7 +6618,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
|
|
|
6564
6618
|
originBranchExists = true;
|
|
6565
6619
|
} catch {
|
|
6566
6620
|
}
|
|
6567
|
-
if (originBranchExists && baseBranch && baseBranch !==
|
|
6621
|
+
if (originBranchExists && baseBranch && baseBranch !== defaultBranch2) {
|
|
6568
6622
|
let baseExists = false;
|
|
6569
6623
|
try {
|
|
6570
6624
|
git2(["rev-parse", "--verify", `origin/${baseBranch}`], cwd);
|
|
@@ -6605,6 +6659,19 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
|
|
|
6605
6659
|
git2(["pull", "origin", branchName], cwd);
|
|
6606
6660
|
} catch {
|
|
6607
6661
|
}
|
|
6662
|
+
if (!baseBranch || baseBranch === defaultBranch2) {
|
|
6663
|
+
try {
|
|
6664
|
+
git2(["merge", "--no-edit", `origin/${defaultBranch2}`], cwd);
|
|
6665
|
+
} catch {
|
|
6666
|
+
try {
|
|
6667
|
+
git2(["merge", "--abort"], cwd);
|
|
6668
|
+
} catch {
|
|
6669
|
+
}
|
|
6670
|
+
throw new Error(
|
|
6671
|
+
`Branch '${branchName}' has merge conflicts with 'origin/${defaultBranch2}'. Resolve manually or delete the branch to start fresh.`
|
|
6672
|
+
);
|
|
6673
|
+
}
|
|
6674
|
+
}
|
|
6608
6675
|
return { branch: branchName, created: false };
|
|
6609
6676
|
}
|
|
6610
6677
|
try {
|
|
@@ -6613,8 +6680,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
|
|
|
6613
6680
|
return { branch: branchName, created: false };
|
|
6614
6681
|
} catch {
|
|
6615
6682
|
}
|
|
6616
|
-
let forkPoint =
|
|
6617
|
-
if (baseBranch && baseBranch !==
|
|
6683
|
+
let forkPoint = defaultBranch2;
|
|
6684
|
+
if (baseBranch && baseBranch !== defaultBranch2) {
|
|
6618
6685
|
try {
|
|
6619
6686
|
git2(["rev-parse", "--verify", `origin/${baseBranch}`], cwd);
|
|
6620
6687
|
forkPoint = baseBranch;
|
|
@@ -6986,11 +7053,11 @@ function detectOwnerRepo(cwd) {
|
|
|
6986
7053
|
if (!m) return null;
|
|
6987
7054
|
return { owner: m[1], repo: m[2] };
|
|
6988
7055
|
}
|
|
6989
|
-
function makeConfig(pm, ownerRepo,
|
|
7056
|
+
function makeConfig(pm, ownerRepo, defaultBranch2) {
|
|
6990
7057
|
return {
|
|
6991
7058
|
$schema: "https://raw.githubusercontent.com/aharonyaircohen/kody-engine/main/kody.config.schema.json",
|
|
6992
7059
|
quality: qualityCommandsFor(pm),
|
|
6993
|
-
git: { defaultBranch },
|
|
7060
|
+
git: { defaultBranch: defaultBranch2 },
|
|
6994
7061
|
github: {
|
|
6995
7062
|
owner: ownerRepo?.owner ?? "OWNER",
|
|
6996
7063
|
repo: ownerRepo?.repo ?? "REPO"
|
|
@@ -7082,12 +7149,12 @@ function performInit(cwd, force) {
|
|
|
7082
7149
|
const skipped = [];
|
|
7083
7150
|
const pm = detectPackageManager(cwd);
|
|
7084
7151
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
7085
|
-
const
|
|
7152
|
+
const defaultBranch2 = defaultBranchFromGit(cwd);
|
|
7086
7153
|
const configPath = path23.join(cwd, "kody.config.json");
|
|
7087
7154
|
if (fs24.existsSync(configPath) && !force) {
|
|
7088
7155
|
skipped.push("kody.config.json");
|
|
7089
7156
|
} else {
|
|
7090
|
-
const cfg = makeConfig(pm, ownerRepo,
|
|
7157
|
+
const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
|
|
7091
7158
|
fs24.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
7092
7159
|
`);
|
|
7093
7160
|
wrote.push("kody.config.json");
|
|
@@ -7223,10 +7290,10 @@ import * as fs25 from "fs";
|
|
|
7223
7290
|
import * as path24 from "path";
|
|
7224
7291
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
7225
7292
|
var GoalStateError = class extends Error {
|
|
7226
|
-
constructor(
|
|
7227
|
-
super(`Invalid goal state at ${
|
|
7293
|
+
constructor(path33, message) {
|
|
7294
|
+
super(`Invalid goal state at ${path33}:
|
|
7228
7295
|
${message}`);
|
|
7229
|
-
this.path =
|
|
7296
|
+
this.path = path33;
|
|
7230
7297
|
this.name = "GoalStateError";
|
|
7231
7298
|
}
|
|
7232
7299
|
path;
|
|
@@ -8797,19 +8864,8 @@ var runFlow = async (ctx) => {
|
|
|
8797
8864
|
process.stderr.write(`[kody runFlow] resolved base branch: ${base} (from --base)
|
|
8798
8865
|
`);
|
|
8799
8866
|
}
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
ctx.data.branch = branchInfo.branch;
|
|
8803
|
-
} catch (err) {
|
|
8804
|
-
if (err instanceof UncommittedChangesError) {
|
|
8805
|
-
ctx.output.exitCode = 5;
|
|
8806
|
-
ctx.output.reason = err.message;
|
|
8807
|
-
ctx.skipAgent = true;
|
|
8808
|
-
tryPost(issueNumber, `\u26A0\uFE0F kody refused to start: ${err.message}`, ctx.cwd);
|
|
8809
|
-
return;
|
|
8810
|
-
}
|
|
8811
|
-
throw err;
|
|
8812
|
-
}
|
|
8867
|
+
const branchInfo = ensureFeatureBranch(issueNumber, issue.title, ctx.config.git.defaultBranch, ctx.cwd, base ?? void 0);
|
|
8868
|
+
ctx.data.branch = branchInfo.branch;
|
|
8813
8869
|
const runUrl = getRunUrl();
|
|
8814
8870
|
const startMsg = runUrl ? `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\`, run ${runUrl}` : `\u2699\uFE0F kody started \u2014 branch \`${ctx.data.branch}\``;
|
|
8815
8871
|
tryPost(issueNumber, startMsg, ctx.cwd);
|
|
@@ -8959,6 +9015,240 @@ function buildChildEnv(parent, force) {
|
|
|
8959
9015
|
return out;
|
|
8960
9016
|
}
|
|
8961
9017
|
|
|
9018
|
+
// src/scripts/brainServe.ts
|
|
9019
|
+
import { createServer } from "http";
|
|
9020
|
+
import * as fs30 from "fs";
|
|
9021
|
+
import * as path29 from "path";
|
|
9022
|
+
var DEFAULT_PORT = 8080;
|
|
9023
|
+
function getApiKey() {
|
|
9024
|
+
const key = (process.env.BRAIN_API_KEY ?? "").trim();
|
|
9025
|
+
if (!key) {
|
|
9026
|
+
throw new Error(
|
|
9027
|
+
"BRAIN_API_KEY env var is required \u2014 set it on the Fly machine before boot."
|
|
9028
|
+
);
|
|
9029
|
+
}
|
|
9030
|
+
return key;
|
|
9031
|
+
}
|
|
9032
|
+
function authOk(req, expected) {
|
|
9033
|
+
const xApiKey = req.headers["x-api-key"]?.trim();
|
|
9034
|
+
if (xApiKey && xApiKey === expected) return true;
|
|
9035
|
+
const auth = req.headers["authorization"]?.trim();
|
|
9036
|
+
if (auth && auth.toLowerCase().startsWith("bearer ")) {
|
|
9037
|
+
return auth.slice(7).trim() === expected;
|
|
9038
|
+
}
|
|
9039
|
+
return false;
|
|
9040
|
+
}
|
|
9041
|
+
function readJsonBody(req) {
|
|
9042
|
+
return new Promise((resolve4, reject) => {
|
|
9043
|
+
const chunks = [];
|
|
9044
|
+
req.on("data", (c) => chunks.push(c));
|
|
9045
|
+
req.on("end", () => {
|
|
9046
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
9047
|
+
if (!raw.trim()) {
|
|
9048
|
+
resolve4({});
|
|
9049
|
+
return;
|
|
9050
|
+
}
|
|
9051
|
+
try {
|
|
9052
|
+
resolve4(JSON.parse(raw));
|
|
9053
|
+
} catch (err) {
|
|
9054
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
9055
|
+
}
|
|
9056
|
+
});
|
|
9057
|
+
req.on("error", reject);
|
|
9058
|
+
});
|
|
9059
|
+
}
|
|
9060
|
+
function sendJson(res, status, body) {
|
|
9061
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
9062
|
+
res.end(JSON.stringify(body));
|
|
9063
|
+
}
|
|
9064
|
+
function writeSseHeaders(res) {
|
|
9065
|
+
res.writeHead(200, {
|
|
9066
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
9067
|
+
"cache-control": "no-cache, no-transform",
|
|
9068
|
+
connection: "keep-alive",
|
|
9069
|
+
"x-accel-buffering": "no"
|
|
9070
|
+
});
|
|
9071
|
+
}
|
|
9072
|
+
function emitSse(res, event) {
|
|
9073
|
+
res.write(`data: ${JSON.stringify(event)}
|
|
9074
|
+
|
|
9075
|
+
`);
|
|
9076
|
+
}
|
|
9077
|
+
var BrainSseSink = class {
|
|
9078
|
+
constructor(res, chatId) {
|
|
9079
|
+
this.res = res;
|
|
9080
|
+
this.chatId = chatId;
|
|
9081
|
+
}
|
|
9082
|
+
res;
|
|
9083
|
+
chatId;
|
|
9084
|
+
async emit(event) {
|
|
9085
|
+
switch (event.event) {
|
|
9086
|
+
case "chat.message": {
|
|
9087
|
+
const content = String(event.payload.content ?? "");
|
|
9088
|
+
if (content.length > 0) {
|
|
9089
|
+
emitSse(this.res, { type: "text", text: content, chatId: this.chatId });
|
|
9090
|
+
}
|
|
9091
|
+
return;
|
|
9092
|
+
}
|
|
9093
|
+
case "chat.tool": {
|
|
9094
|
+
if (event.payload.phase !== "use") return;
|
|
9095
|
+
emitSse(this.res, {
|
|
9096
|
+
type: "tool_use",
|
|
9097
|
+
name: typeof event.payload.name === "string" ? event.payload.name : "tool",
|
|
9098
|
+
input: event.payload.input ?? {},
|
|
9099
|
+
chatId: this.chatId
|
|
9100
|
+
});
|
|
9101
|
+
return;
|
|
9102
|
+
}
|
|
9103
|
+
case "chat.done": {
|
|
9104
|
+
emitSse(this.res, { type: "done", chatId: this.chatId });
|
|
9105
|
+
return;
|
|
9106
|
+
}
|
|
9107
|
+
case "chat.error": {
|
|
9108
|
+
const errMsg2 = typeof event.payload.error === "string" ? event.payload.error : "agent error";
|
|
9109
|
+
emitSse(this.res, { type: "error", error: errMsg2, chatId: this.chatId });
|
|
9110
|
+
return;
|
|
9111
|
+
}
|
|
9112
|
+
// chat.thinking / chat.ready / chat.exit — not part of the Brain protocol.
|
|
9113
|
+
default:
|
|
9114
|
+
return;
|
|
9115
|
+
}
|
|
9116
|
+
}
|
|
9117
|
+
};
|
|
9118
|
+
async function handleChatTurn(req, res, chatId, opts) {
|
|
9119
|
+
let body;
|
|
9120
|
+
try {
|
|
9121
|
+
body = await readJsonBody(req);
|
|
9122
|
+
} catch {
|
|
9123
|
+
sendJson(res, 400, { error: "invalid JSON body" });
|
|
9124
|
+
return;
|
|
9125
|
+
}
|
|
9126
|
+
const message = typeof body === "object" && body !== null && "message" in body ? body.message : void 0;
|
|
9127
|
+
if (typeof message !== "string" || !message.trim()) {
|
|
9128
|
+
sendJson(res, 400, { error: "message required" });
|
|
9129
|
+
return;
|
|
9130
|
+
}
|
|
9131
|
+
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
9132
|
+
fs30.mkdirSync(path29.dirname(sessionFile), { recursive: true });
|
|
9133
|
+
appendTurn(sessionFile, {
|
|
9134
|
+
role: "user",
|
|
9135
|
+
content: message,
|
|
9136
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9137
|
+
});
|
|
9138
|
+
writeSseHeaders(res);
|
|
9139
|
+
emitSse(res, { type: "chat", chatId });
|
|
9140
|
+
const sink = new BrainSseSink(res, chatId);
|
|
9141
|
+
try {
|
|
9142
|
+
await opts.runTurn({
|
|
9143
|
+
sessionId: chatId,
|
|
9144
|
+
sessionFile,
|
|
9145
|
+
cwd: opts.cwd,
|
|
9146
|
+
model: opts.model,
|
|
9147
|
+
litellmUrl: opts.litellmUrl,
|
|
9148
|
+
sink
|
|
9149
|
+
});
|
|
9150
|
+
} catch (err) {
|
|
9151
|
+
const errMsg2 = err instanceof Error ? err.message : String(err);
|
|
9152
|
+
process.stderr.write(`[brain-serve] chat turn failed: ${errMsg2}
|
|
9153
|
+
`);
|
|
9154
|
+
try {
|
|
9155
|
+
emitSse(res, { type: "error", error: errMsg2, chatId });
|
|
9156
|
+
} catch {
|
|
9157
|
+
}
|
|
9158
|
+
} finally {
|
|
9159
|
+
try {
|
|
9160
|
+
res.end();
|
|
9161
|
+
} catch {
|
|
9162
|
+
}
|
|
9163
|
+
}
|
|
9164
|
+
}
|
|
9165
|
+
function buildServer(opts) {
|
|
9166
|
+
const runTurn = opts.runTurn ?? runChatTurn;
|
|
9167
|
+
return createServer(async (req, res) => {
|
|
9168
|
+
if (!req.method || !req.url) {
|
|
9169
|
+
sendJson(res, 400, { error: "bad request" });
|
|
9170
|
+
return;
|
|
9171
|
+
}
|
|
9172
|
+
const url = new URL(req.url, `http://localhost`);
|
|
9173
|
+
if (req.method === "GET" && url.pathname === "/healthz") {
|
|
9174
|
+
sendJson(res, 200, { ok: true });
|
|
9175
|
+
return;
|
|
9176
|
+
}
|
|
9177
|
+
if (!authOk(req, opts.apiKey)) {
|
|
9178
|
+
sendJson(res, 401, { error: "unauthorized" });
|
|
9179
|
+
return;
|
|
9180
|
+
}
|
|
9181
|
+
const m = url.pathname.match(/^\/chats\/([^/]+)\/messages\/?$/);
|
|
9182
|
+
if (req.method === "POST" && m) {
|
|
9183
|
+
const chatId = decodeURIComponent(m[1] ?? "");
|
|
9184
|
+
if (!chatId) {
|
|
9185
|
+
sendJson(res, 400, { error: "chatId required" });
|
|
9186
|
+
return;
|
|
9187
|
+
}
|
|
9188
|
+
await handleChatTurn(req, res, chatId, {
|
|
9189
|
+
cwd: opts.cwd,
|
|
9190
|
+
model: opts.model,
|
|
9191
|
+
litellmUrl: opts.litellmUrl,
|
|
9192
|
+
runTurn
|
|
9193
|
+
});
|
|
9194
|
+
return;
|
|
9195
|
+
}
|
|
9196
|
+
sendJson(res, 404, { error: "not found" });
|
|
9197
|
+
});
|
|
9198
|
+
}
|
|
9199
|
+
var brainServe = async (ctx) => {
|
|
9200
|
+
ctx.skipAgent = true;
|
|
9201
|
+
const apiKey = getApiKey();
|
|
9202
|
+
const port = Number(process.env.PORT ?? DEFAULT_PORT);
|
|
9203
|
+
const model = parseProviderModel(ctx.config.agent.model);
|
|
9204
|
+
const usesProxy = needsLitellmProxy(model);
|
|
9205
|
+
let handle = null;
|
|
9206
|
+
if (usesProxy) {
|
|
9207
|
+
process.stdout.write(
|
|
9208
|
+
`[brain-serve] starting LiteLLM proxy for ${model.provider}/${model.model}...
|
|
9209
|
+
`
|
|
9210
|
+
);
|
|
9211
|
+
handle = await startLitellmIfNeeded(model, ctx.cwd);
|
|
9212
|
+
process.stdout.write(
|
|
9213
|
+
`[brain-serve] LiteLLM ready at ${handle?.url ?? LITELLM_DEFAULT_URL}
|
|
9214
|
+
`
|
|
9215
|
+
);
|
|
9216
|
+
}
|
|
9217
|
+
const litellmUrl = usesProxy ? handle?.url ?? LITELLM_DEFAULT_URL : null;
|
|
9218
|
+
const server = buildServer({
|
|
9219
|
+
apiKey,
|
|
9220
|
+
cwd: ctx.cwd,
|
|
9221
|
+
model,
|
|
9222
|
+
litellmUrl
|
|
9223
|
+
});
|
|
9224
|
+
await new Promise((resolve4) => {
|
|
9225
|
+
server.listen(port, "0.0.0.0", () => {
|
|
9226
|
+
process.stdout.write(
|
|
9227
|
+
`[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
|
|
9228
|
+
`
|
|
9229
|
+
);
|
|
9230
|
+
resolve4();
|
|
9231
|
+
});
|
|
9232
|
+
});
|
|
9233
|
+
const shutdown = (signal) => {
|
|
9234
|
+
process.stdout.write(`[brain-serve] ${signal} \u2014 shutting down
|
|
9235
|
+
`);
|
|
9236
|
+
server.close(() => {
|
|
9237
|
+
if (handle) {
|
|
9238
|
+
try {
|
|
9239
|
+
handle.kill();
|
|
9240
|
+
} catch {
|
|
9241
|
+
}
|
|
9242
|
+
}
|
|
9243
|
+
process.exit(0);
|
|
9244
|
+
});
|
|
9245
|
+
};
|
|
9246
|
+
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
9247
|
+
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
9248
|
+
await new Promise(() => {
|
|
9249
|
+
});
|
|
9250
|
+
};
|
|
9251
|
+
|
|
8962
9252
|
// src/scripts/serveFlow.ts
|
|
8963
9253
|
import { spawn as spawn3 } from "child_process";
|
|
8964
9254
|
function parseTarget(positional) {
|
|
@@ -9798,7 +10088,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
9798
10088
|
};
|
|
9799
10089
|
|
|
9800
10090
|
// src/scripts/writeRunSummary.ts
|
|
9801
|
-
import * as
|
|
10091
|
+
import * as fs31 from "fs";
|
|
9802
10092
|
var writeRunSummary = async (ctx, profile) => {
|
|
9803
10093
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
9804
10094
|
if (!summaryPath) return;
|
|
@@ -9820,7 +10110,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
9820
10110
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
9821
10111
|
lines.push("");
|
|
9822
10112
|
try {
|
|
9823
|
-
|
|
10113
|
+
fs31.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
9824
10114
|
`);
|
|
9825
10115
|
} catch {
|
|
9826
10116
|
}
|
|
@@ -9862,6 +10152,7 @@ var preflightScripts = {
|
|
|
9862
10152
|
dispatchJobFileTicks,
|
|
9863
10153
|
runTickScript,
|
|
9864
10154
|
serveFlow,
|
|
10155
|
+
brainServe,
|
|
9865
10156
|
loadGoalState,
|
|
9866
10157
|
handleAbandonedGoal,
|
|
9867
10158
|
deriveGoalPhase,
|
|
@@ -10042,9 +10333,9 @@ async function runExecutable(profileName, input) {
|
|
|
10042
10333
|
data: { ...input.preloadedData ?? {} },
|
|
10043
10334
|
output: { exitCode: 0 }
|
|
10044
10335
|
};
|
|
10045
|
-
const ndjsonDir =
|
|
10336
|
+
const ndjsonDir = path30.join(input.cwd, ".kody");
|
|
10046
10337
|
const invokeAgent = async (prompt) => {
|
|
10047
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
10338
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path30.isAbsolute(p) ? p : path30.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
10048
10339
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
10049
10340
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
10050
10341
|
return runAgent({
|
|
@@ -10239,7 +10530,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
10239
10530
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
10240
10531
|
try {
|
|
10241
10532
|
const profilePath = resolveProfilePath(profileName);
|
|
10242
|
-
if (!
|
|
10533
|
+
if (!fs32.existsSync(profilePath)) return null;
|
|
10243
10534
|
return loadProfile(profilePath).inputs;
|
|
10244
10535
|
} catch {
|
|
10245
10536
|
return null;
|
|
@@ -10248,17 +10539,17 @@ function getProfileInputsForChild(profileName, _cwd) {
|
|
|
10248
10539
|
function resolveProfilePath(profileName) {
|
|
10249
10540
|
const found = resolveExecutable(profileName);
|
|
10250
10541
|
if (found) return found;
|
|
10251
|
-
const here =
|
|
10542
|
+
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
10252
10543
|
const candidates = [
|
|
10253
|
-
|
|
10544
|
+
path30.join(here, "executables", profileName, "profile.json"),
|
|
10254
10545
|
// same-dir sibling (dev)
|
|
10255
|
-
|
|
10546
|
+
path30.join(here, "..", "executables", profileName, "profile.json"),
|
|
10256
10547
|
// up one (prod: dist/bin → dist/executables)
|
|
10257
|
-
|
|
10548
|
+
path30.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
10258
10549
|
// fallback
|
|
10259
10550
|
];
|
|
10260
10551
|
for (const c of candidates) {
|
|
10261
|
-
if (
|
|
10552
|
+
if (fs32.existsSync(c)) return c;
|
|
10262
10553
|
}
|
|
10263
10554
|
return candidates[0];
|
|
10264
10555
|
}
|
|
@@ -10358,8 +10649,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
10358
10649
|
var SIGKILL_GRACE_MS = 5e3;
|
|
10359
10650
|
async function runShellEntry(entry, ctx, profile) {
|
|
10360
10651
|
const shellName = entry.shell;
|
|
10361
|
-
const shellPath =
|
|
10362
|
-
if (!
|
|
10652
|
+
const shellPath = path30.join(profile.dir, shellName);
|
|
10653
|
+
if (!fs32.existsSync(shellPath)) {
|
|
10363
10654
|
ctx.skipAgent = true;
|
|
10364
10655
|
ctx.output.exitCode = 99;
|
|
10365
10656
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -10521,7 +10812,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
10521
10812
|
process.stderr.write(`[kody container] step ${iteration}: invoking ${child.exec}
|
|
10522
10813
|
`);
|
|
10523
10814
|
if (profile.resetBetweenChildren !== false) {
|
|
10524
|
-
|
|
10815
|
+
resetWorkingTree2(input.cwd);
|
|
10525
10816
|
} else {
|
|
10526
10817
|
process.stderr.write(`[kody container] resetBetweenChildren=false; preserving tracked tree
|
|
10527
10818
|
`);
|
|
@@ -10684,7 +10975,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
10684
10975
|
currentIdx = nextIdx;
|
|
10685
10976
|
}
|
|
10686
10977
|
}
|
|
10687
|
-
function
|
|
10978
|
+
function resetWorkingTree2(cwd) {
|
|
10688
10979
|
try {
|
|
10689
10980
|
execFileSync29("git", ["reset", "--hard", "HEAD"], {
|
|
10690
10981
|
cwd,
|
|
@@ -10780,7 +11071,6 @@ Exit codes (inherited from kody run):
|
|
|
10780
11071
|
2 verify failed (no PR opened \u2014 branch pushed for inspection)
|
|
10781
11072
|
3 no commits to ship
|
|
10782
11073
|
4 PR creation failed
|
|
10783
|
-
5 uncommitted changes on target branch
|
|
10784
11074
|
99 wrapper crashed
|
|
10785
11075
|
`;
|
|
10786
11076
|
function parseCiArgs(argv) {
|
|
@@ -10839,9 +11129,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
10839
11129
|
return token;
|
|
10840
11130
|
}
|
|
10841
11131
|
function detectPackageManager2(cwd) {
|
|
10842
|
-
if (
|
|
10843
|
-
if (
|
|
10844
|
-
if (
|
|
11132
|
+
if (fs33.existsSync(path31.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
11133
|
+
if (fs33.existsSync(path31.join(cwd, "yarn.lock"))) return "yarn";
|
|
11134
|
+
if (fs33.existsSync(path31.join(cwd, "bun.lockb"))) return "bun";
|
|
10845
11135
|
return "npm";
|
|
10846
11136
|
}
|
|
10847
11137
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -10928,11 +11218,11 @@ function configureGitIdentity(cwd) {
|
|
|
10928
11218
|
}
|
|
10929
11219
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
10930
11220
|
if (!issueNumber) return;
|
|
10931
|
-
const logPath =
|
|
11221
|
+
const logPath = path31.join(cwd, ".kody", "last-run.jsonl");
|
|
10932
11222
|
let tail = "";
|
|
10933
11223
|
try {
|
|
10934
|
-
if (
|
|
10935
|
-
const content =
|
|
11224
|
+
if (fs33.existsSync(logPath)) {
|
|
11225
|
+
const content = fs33.readFileSync(logPath, "utf-8");
|
|
10936
11226
|
tail = content.slice(-3e3);
|
|
10937
11227
|
}
|
|
10938
11228
|
} catch {
|
|
@@ -10957,7 +11247,7 @@ async function runCi(argv) {
|
|
|
10957
11247
|
return 0;
|
|
10958
11248
|
}
|
|
10959
11249
|
const args = parseCiArgs(argv);
|
|
10960
|
-
const cwd = args.cwd ?
|
|
11250
|
+
const cwd = args.cwd ? path31.resolve(args.cwd) : process.cwd();
|
|
10961
11251
|
let earlyConfig;
|
|
10962
11252
|
try {
|
|
10963
11253
|
earlyConfig = loadConfig(cwd);
|
|
@@ -10967,9 +11257,9 @@ async function runCi(argv) {
|
|
|
10967
11257
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
10968
11258
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
10969
11259
|
let manualWorkflowDispatch = false;
|
|
10970
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
11260
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs33.existsSync(dispatchEventPath)) {
|
|
10971
11261
|
try {
|
|
10972
|
-
const evt = JSON.parse(
|
|
11262
|
+
const evt = JSON.parse(fs33.readFileSync(dispatchEventPath, "utf-8"));
|
|
10973
11263
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
10974
11264
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
10975
11265
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -11228,9 +11518,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
11228
11518
|
return result;
|
|
11229
11519
|
}
|
|
11230
11520
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
11231
|
-
const sessionFile =
|
|
11232
|
-
const eventsFile =
|
|
11233
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
11521
|
+
const sessionFile = path32.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
11522
|
+
const eventsFile = path32.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
11523
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs34.existsSync(path32.join(cwd, p)));
|
|
11234
11524
|
if (paths.length === 0) return;
|
|
11235
11525
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
11236
11526
|
try {
|
|
@@ -11268,7 +11558,7 @@ async function runChat(argv) {
|
|
|
11268
11558
|
${CHAT_HELP}`);
|
|
11269
11559
|
return 64;
|
|
11270
11560
|
}
|
|
11271
|
-
const cwd = args.cwd ?
|
|
11561
|
+
const cwd = args.cwd ? path32.resolve(args.cwd) : process.cwd();
|
|
11272
11562
|
const sessionId = args.sessionId;
|
|
11273
11563
|
const unpackedSecrets = unpackAllSecrets();
|
|
11274
11564
|
if (unpackedSecrets > 0) {
|
|
@@ -11320,7 +11610,7 @@ ${CHAT_HELP}`);
|
|
|
11320
11610
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
11321
11611
|
const meta = readMeta(sessionFile);
|
|
11322
11612
|
process.stdout.write(
|
|
11323
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
11613
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs34.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
11324
11614
|
`
|
|
11325
11615
|
);
|
|
11326
11616
|
try {
|
|
@@ -11584,7 +11874,6 @@ Exit codes:
|
|
|
11584
11874
|
2 verify failed (no PR opened \u2014 branch pushed for inspection) \u2014 skipped in resolve mode
|
|
11585
11875
|
3 no commits to ship (also the resolve clean-merge short-circuit)
|
|
11586
11876
|
4 PR creation failed
|
|
11587
|
-
5 uncommitted changes on target branch
|
|
11588
11877
|
64 invalid CLI args
|
|
11589
11878
|
99 wrapper crashed
|
|
11590
11879
|
`;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "brain-serve",
|
|
3
|
+
"role": "utility",
|
|
4
|
+
"describe": "Long-lived HTTP server that wraps the Kody chat loop and speaks the Brain SSE protocol. Listens on $PORT (default 8080); auth via $BRAIN_API_KEY. Pairs with the Kody-Dashboard /api/kody/chat/brain proxy. Usage: `kody brain-serve` (runs until SIGINT/SIGTERM).",
|
|
5
|
+
"inputs": [],
|
|
6
|
+
"claudeCode": {
|
|
7
|
+
"model": "inherit",
|
|
8
|
+
"permissionMode": "acceptEdits",
|
|
9
|
+
"maxTurns": null,
|
|
10
|
+
"systemPromptAppend": null,
|
|
11
|
+
"tools": [],
|
|
12
|
+
"hooks": [],
|
|
13
|
+
"skills": [],
|
|
14
|
+
"commands": [],
|
|
15
|
+
"subagents": [],
|
|
16
|
+
"plugins": [],
|
|
17
|
+
"mcpServers": []
|
|
18
|
+
},
|
|
19
|
+
"cliTools": [],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"preflight": [
|
|
22
|
+
{
|
|
23
|
+
"script": "brainServe"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"postflight": []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
File without changes
|
|
@@ -89,7 +89,19 @@ For EACH file you will change or create, include:
|
|
|
89
89
|
- Target state — what will be there after the change, at the same level of specificity.
|
|
90
90
|
- Exact locations of edits (function name, line range if stable, or anchor like "after the `meta` group field, before the closing `fields: []`").
|
|
91
91
|
- For new files: rough shape including exports, key functions with signatures, and top-level module comment. **Do not paste full function bodies** — signatures and 1–2 sentence intent per export are enough for an implementer to write the body. Single-line type/interface declarations and short config snippets are fine.
|
|
92
|
-
- Dependencies touched (imports added/removed
|
|
92
|
+
- Dependencies touched (imports added/removed) — call out per file; list any new third-party packages here AND aggregate them in the `## Dependencies to install` section below.
|
|
93
|
+
|
|
94
|
+
## Dependencies to install
|
|
95
|
+
REQUIRED if the plan introduces any third-party import not already present in
|
|
96
|
+
the repo's manifest (`package.json` / `requirements.txt` / `go.mod` / etc).
|
|
97
|
+
One line per dep with the exact install command the implementer should run,
|
|
98
|
+
picked from the repo's lockfile (`pnpm-lock.yaml` → pnpm, `package-lock.json`
|
|
99
|
+
→ npm, `yarn.lock` → yarn, `bun.lockb` → bun):
|
|
100
|
+
- `pnpm add <pkg>` — runtime dep, used by `<file>`
|
|
101
|
+
- `pnpm add -D <pkg>` — dev/types-only dep (`@types/*`, test tooling)
|
|
102
|
+
If no new deps are needed, write the single line `- none`. Omitting this
|
|
103
|
+
section when new deps ARE needed is a planning failure — the implementer will
|
|
104
|
+
hit a `Cannot find module` typecheck error and have to recover blind.
|
|
93
105
|
|
|
94
106
|
## Algorithms & pseudocode
|
|
95
107
|
REQUIRED for any non-trivial logic (sorting, diffing, state transitions, concurrency, batching, caching, conflict resolution).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -41,6 +41,8 @@ If a prior-art block is present above, READ THE DIFFS — those are failed or su
|
|
|
41
41
|
3. **Build** — Edit/Write to implement the change. Stay within the plan; if you discover the plan was wrong, briefly say so and adjust.
|
|
42
42
|
4. **Test** — for every new module you added and every behavior you changed, write or update tests. If the plan above contains a "Test plan" section, treat it as authoritative: every item there must produce a corresponding test. Before writing a test, open the newest existing file in the same test directory (`tests/int/`, `tests/unit/`, `tests/e2e/`, or sibling `*.test.ts`) and copy its imports, setup hooks, and auth pattern **verbatim**. Do NOT introduce a new test infrastructure (own testcontainers, `fetch` against relative URLs, alternate auth headers) when a working pattern already exists in that directory — divergence from the established pattern is a hard failure even if the test passes locally. Cover at least one happy path and one failure path per change. Skipping tests is a hard failure. A change may only be declared untestable if you can name the specific blocker (e.g., "no fake exists for the X SDK and stubbing it would mock the entire call surface"); vague "this is just config" claims are rejected. Untestable changes go in `PLAN_DEVIATIONS:` with the named blocker.
|
|
43
43
|
5. **Verify** — before declaring DONE, call the `verify` tool (mcp__kody-verify__verify). It runs typecheck/lint/tests with the project's configured commands and returns `{ ok, failures, attemptsRemaining }`. If `ok: true`, you may proceed to DONE. If `ok: false`, read the truncated `failures` list, fix the root cause, commit-equivalent edits, and call `verify` again. You have up to 4 total attempts; the tool will return `locked: true` after that and you must wrap up with FAILED. The postflight verifier runs again after this session ends and is the final ratifier — but it's also the gate that downgrades a self-reported DONE to FAILED if you skipped this step, so calling the tool is strictly cheaper than not.
|
|
44
|
+
|
|
45
|
+
**Allowed fixes between attempts** include installing missing third-party dependencies. If `failures` contains `Cannot find module 'X'` / `Cannot find package 'X'` / `error TS2307` for a NON-relative import (i.e. not `./` or `../`), the fix is to install it with the repo's package manager before the next verify call — `pnpm add X` (runtime) or `pnpm add -D X` (types-only, `@types/*`, dev tooling). Pick the package manager from the repo's lockfile: `pnpm-lock.yaml` → pnpm, `package-lock.json` → npm, `yarn.lock` → yarn, `bun.lockb` → bun. If the plan provided a `## Dependencies to install` section, prefer the exact command it specifies. Do NOT install a dep just to silence a relative-path resolution error — that's a code bug, fix the import path instead.
|
|
44
46
|
6. Your FINAL message must use this exact format (or a single `FAILED: <reason>` line on failure). The `PLAN_DEVIATIONS:` block is REQUIRED whenever a plan was provided.
|
|
45
47
|
|
|
46
48
|
```
|
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.73",
|
|
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,23 @@
|
|
|
12
12
|
"templates",
|
|
13
13
|
"kody.config.schema.json"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"kody": "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 --no-coverage",
|
|
24
|
+
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
25
|
+
"test:all": "vitest run tests --no-coverage",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"lint": "biome check",
|
|
28
|
+
"lint:fix": "biome check --write",
|
|
29
|
+
"format": "biome format --write",
|
|
30
|
+
"prepublishOnly": "pnpm build"
|
|
31
|
+
},
|
|
15
32
|
"dependencies": {
|
|
16
33
|
"@actions/cache": "^6.0.0",
|
|
17
34
|
"@anthropic-ai/claude-agent-sdk": "0.2.119",
|
|
@@ -33,21 +50,5 @@
|
|
|
33
50
|
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
34
51
|
},
|
|
35
52
|
"homepage": "https://github.com/aharonyaircohen/kody-engine",
|
|
36
|
-
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
37
|
-
|
|
38
|
-
"kody": "tsx bin/kody.ts",
|
|
39
|
-
"serve": "tsx bin/kody.ts serve",
|
|
40
|
-
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
41
|
-
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
42
|
-
"build": "tsup && node scripts/copy-assets.cjs",
|
|
43
|
-
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
44
|
-
"pretest": "pnpm check:modularity",
|
|
45
|
-
"test": "vitest run tests/unit tests/int --no-coverage",
|
|
46
|
-
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
47
|
-
"test:all": "vitest run tests --no-coverage",
|
|
48
|
-
"typecheck": "tsc --noEmit",
|
|
49
|
-
"lint": "biome check",
|
|
50
|
-
"lint:fix": "biome check --write",
|
|
51
|
-
"format": "biome format --write"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
53
|
+
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
54
|
+
}
|