@kody-ade/kody-engine 0.4.72 → 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
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";
|
|
@@ -2013,8 +2013,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
2013
2013
|
|
|
2014
2014
|
// src/kody-cli.ts
|
|
2015
2015
|
import { execFileSync as execFileSync30 } from "child_process";
|
|
2016
|
-
import * as
|
|
2017
|
-
import * as
|
|
2016
|
+
import * as fs33 from "fs";
|
|
2017
|
+
import * as path31 from "path";
|
|
2018
2018
|
|
|
2019
2019
|
// src/dispatch.ts
|
|
2020
2020
|
import * as fs8 from "fs";
|
|
@@ -2455,8 +2455,8 @@ init_issue();
|
|
|
2455
2455
|
|
|
2456
2456
|
// src/executor.ts
|
|
2457
2457
|
import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
|
|
2458
|
-
import * as
|
|
2459
|
-
import * as
|
|
2458
|
+
import * as fs32 from "fs";
|
|
2459
|
+
import * as path30 from "path";
|
|
2460
2460
|
init_events();
|
|
2461
2461
|
|
|
2462
2462
|
// src/lifecycleLabels.ts
|
|
@@ -6659,6 +6659,19 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
|
|
|
6659
6659
|
git2(["pull", "origin", branchName], cwd);
|
|
6660
6660
|
} catch {
|
|
6661
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
|
+
}
|
|
6662
6675
|
return { branch: branchName, created: false };
|
|
6663
6676
|
}
|
|
6664
6677
|
try {
|
|
@@ -7277,10 +7290,10 @@ import * as fs25 from "fs";
|
|
|
7277
7290
|
import * as path24 from "path";
|
|
7278
7291
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
7279
7292
|
var GoalStateError = class extends Error {
|
|
7280
|
-
constructor(
|
|
7281
|
-
super(`Invalid goal state at ${
|
|
7293
|
+
constructor(path33, message) {
|
|
7294
|
+
super(`Invalid goal state at ${path33}:
|
|
7282
7295
|
${message}`);
|
|
7283
|
-
this.path =
|
|
7296
|
+
this.path = path33;
|
|
7284
7297
|
this.name = "GoalStateError";
|
|
7285
7298
|
}
|
|
7286
7299
|
path;
|
|
@@ -9002,6 +9015,240 @@ function buildChildEnv(parent, force) {
|
|
|
9002
9015
|
return out;
|
|
9003
9016
|
}
|
|
9004
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
|
+
|
|
9005
9252
|
// src/scripts/serveFlow.ts
|
|
9006
9253
|
import { spawn as spawn3 } from "child_process";
|
|
9007
9254
|
function parseTarget(positional) {
|
|
@@ -9841,7 +10088,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
9841
10088
|
};
|
|
9842
10089
|
|
|
9843
10090
|
// src/scripts/writeRunSummary.ts
|
|
9844
|
-
import * as
|
|
10091
|
+
import * as fs31 from "fs";
|
|
9845
10092
|
var writeRunSummary = async (ctx, profile) => {
|
|
9846
10093
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
9847
10094
|
if (!summaryPath) return;
|
|
@@ -9863,7 +10110,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
9863
10110
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
9864
10111
|
lines.push("");
|
|
9865
10112
|
try {
|
|
9866
|
-
|
|
10113
|
+
fs31.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
9867
10114
|
`);
|
|
9868
10115
|
} catch {
|
|
9869
10116
|
}
|
|
@@ -9905,6 +10152,7 @@ var preflightScripts = {
|
|
|
9905
10152
|
dispatchJobFileTicks,
|
|
9906
10153
|
runTickScript,
|
|
9907
10154
|
serveFlow,
|
|
10155
|
+
brainServe,
|
|
9908
10156
|
loadGoalState,
|
|
9909
10157
|
handleAbandonedGoal,
|
|
9910
10158
|
deriveGoalPhase,
|
|
@@ -10085,9 +10333,9 @@ async function runExecutable(profileName, input) {
|
|
|
10085
10333
|
data: { ...input.preloadedData ?? {} },
|
|
10086
10334
|
output: { exitCode: 0 }
|
|
10087
10335
|
};
|
|
10088
|
-
const ndjsonDir =
|
|
10336
|
+
const ndjsonDir = path30.join(input.cwd, ".kody");
|
|
10089
10337
|
const invokeAgent = async (prompt) => {
|
|
10090
|
-
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);
|
|
10091
10339
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
10092
10340
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
10093
10341
|
return runAgent({
|
|
@@ -10282,7 +10530,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
10282
10530
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
10283
10531
|
try {
|
|
10284
10532
|
const profilePath = resolveProfilePath(profileName);
|
|
10285
|
-
if (!
|
|
10533
|
+
if (!fs32.existsSync(profilePath)) return null;
|
|
10286
10534
|
return loadProfile(profilePath).inputs;
|
|
10287
10535
|
} catch {
|
|
10288
10536
|
return null;
|
|
@@ -10291,17 +10539,17 @@ function getProfileInputsForChild(profileName, _cwd) {
|
|
|
10291
10539
|
function resolveProfilePath(profileName) {
|
|
10292
10540
|
const found = resolveExecutable(profileName);
|
|
10293
10541
|
if (found) return found;
|
|
10294
|
-
const here =
|
|
10542
|
+
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
10295
10543
|
const candidates = [
|
|
10296
|
-
|
|
10544
|
+
path30.join(here, "executables", profileName, "profile.json"),
|
|
10297
10545
|
// same-dir sibling (dev)
|
|
10298
|
-
|
|
10546
|
+
path30.join(here, "..", "executables", profileName, "profile.json"),
|
|
10299
10547
|
// up one (prod: dist/bin → dist/executables)
|
|
10300
|
-
|
|
10548
|
+
path30.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
10301
10549
|
// fallback
|
|
10302
10550
|
];
|
|
10303
10551
|
for (const c of candidates) {
|
|
10304
|
-
if (
|
|
10552
|
+
if (fs32.existsSync(c)) return c;
|
|
10305
10553
|
}
|
|
10306
10554
|
return candidates[0];
|
|
10307
10555
|
}
|
|
@@ -10401,8 +10649,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
10401
10649
|
var SIGKILL_GRACE_MS = 5e3;
|
|
10402
10650
|
async function runShellEntry(entry, ctx, profile) {
|
|
10403
10651
|
const shellName = entry.shell;
|
|
10404
|
-
const shellPath =
|
|
10405
|
-
if (!
|
|
10652
|
+
const shellPath = path30.join(profile.dir, shellName);
|
|
10653
|
+
if (!fs32.existsSync(shellPath)) {
|
|
10406
10654
|
ctx.skipAgent = true;
|
|
10407
10655
|
ctx.output.exitCode = 99;
|
|
10408
10656
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -10881,9 +11129,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
10881
11129
|
return token;
|
|
10882
11130
|
}
|
|
10883
11131
|
function detectPackageManager2(cwd) {
|
|
10884
|
-
if (
|
|
10885
|
-
if (
|
|
10886
|
-
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";
|
|
10887
11135
|
return "npm";
|
|
10888
11136
|
}
|
|
10889
11137
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -10970,11 +11218,11 @@ function configureGitIdentity(cwd) {
|
|
|
10970
11218
|
}
|
|
10971
11219
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
10972
11220
|
if (!issueNumber) return;
|
|
10973
|
-
const logPath =
|
|
11221
|
+
const logPath = path31.join(cwd, ".kody", "last-run.jsonl");
|
|
10974
11222
|
let tail = "";
|
|
10975
11223
|
try {
|
|
10976
|
-
if (
|
|
10977
|
-
const content =
|
|
11224
|
+
if (fs33.existsSync(logPath)) {
|
|
11225
|
+
const content = fs33.readFileSync(logPath, "utf-8");
|
|
10978
11226
|
tail = content.slice(-3e3);
|
|
10979
11227
|
}
|
|
10980
11228
|
} catch {
|
|
@@ -10999,7 +11247,7 @@ async function runCi(argv) {
|
|
|
10999
11247
|
return 0;
|
|
11000
11248
|
}
|
|
11001
11249
|
const args = parseCiArgs(argv);
|
|
11002
|
-
const cwd = args.cwd ?
|
|
11250
|
+
const cwd = args.cwd ? path31.resolve(args.cwd) : process.cwd();
|
|
11003
11251
|
let earlyConfig;
|
|
11004
11252
|
try {
|
|
11005
11253
|
earlyConfig = loadConfig(cwd);
|
|
@@ -11009,9 +11257,9 @@ async function runCi(argv) {
|
|
|
11009
11257
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
11010
11258
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
11011
11259
|
let manualWorkflowDispatch = false;
|
|
11012
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
11260
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs33.existsSync(dispatchEventPath)) {
|
|
11013
11261
|
try {
|
|
11014
|
-
const evt = JSON.parse(
|
|
11262
|
+
const evt = JSON.parse(fs33.readFileSync(dispatchEventPath, "utf-8"));
|
|
11015
11263
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
11016
11264
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
11017
11265
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -11270,9 +11518,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
11270
11518
|
return result;
|
|
11271
11519
|
}
|
|
11272
11520
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
11273
|
-
const sessionFile =
|
|
11274
|
-
const eventsFile =
|
|
11275
|
-
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)));
|
|
11276
11524
|
if (paths.length === 0) return;
|
|
11277
11525
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
11278
11526
|
try {
|
|
@@ -11310,7 +11558,7 @@ async function runChat(argv) {
|
|
|
11310
11558
|
${CHAT_HELP}`);
|
|
11311
11559
|
return 64;
|
|
11312
11560
|
}
|
|
11313
|
-
const cwd = args.cwd ?
|
|
11561
|
+
const cwd = args.cwd ? path32.resolve(args.cwd) : process.cwd();
|
|
11314
11562
|
const sessionId = args.sessionId;
|
|
11315
11563
|
const unpackedSecrets = unpackAllSecrets();
|
|
11316
11564
|
if (unpackedSecrets > 0) {
|
|
@@ -11362,7 +11610,7 @@ ${CHAT_HELP}`);
|
|
|
11362
11610
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
11363
11611
|
const meta = readMeta(sessionFile);
|
|
11364
11612
|
process.stdout.write(
|
|
11365
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
11613
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs34.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
11366
11614
|
`
|
|
11367
11615
|
);
|
|
11368
11616
|
try {
|
|
@@ -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
|
+
}
|
|
@@ -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).
|
|
@@ -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",
|