@kody-ade/kody-engine 0.4.72 → 0.4.74
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
|
@@ -495,9 +495,10 @@ function parseAgentResult(finalText) {
|
|
|
495
495
|
markerMissing: false
|
|
496
496
|
};
|
|
497
497
|
const MARKDOWN_PREFIX = "[\\s>*_#`~\\-]*";
|
|
498
|
-
const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "
|
|
499
|
-
const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b
|
|
500
|
-
const
|
|
498
|
+
const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "s");
|
|
499
|
+
const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b`);
|
|
500
|
+
const scanText = stripFencedCodeBlocks(text);
|
|
501
|
+
const failedMatch = scanText.match(FAILED_RE);
|
|
501
502
|
if (failedMatch) {
|
|
502
503
|
return {
|
|
503
504
|
done: false,
|
|
@@ -510,7 +511,7 @@ function parseAgentResult(finalText) {
|
|
|
510
511
|
markerMissing: false
|
|
511
512
|
};
|
|
512
513
|
}
|
|
513
|
-
const hasDoneMarker = DONE_RE.test(
|
|
514
|
+
const hasDoneMarker = DONE_RE.test(scanText);
|
|
514
515
|
const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text);
|
|
515
516
|
const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text);
|
|
516
517
|
const markerMissing = !hasDoneMarker && !hasCommitMsg && !hasPrSummary;
|
|
@@ -553,6 +554,9 @@ function parseAgentResult(finalText) {
|
|
|
553
554
|
function stripMarkdownEmphasis(s) {
|
|
554
555
|
return s.trim().replace(/^[*_`~]+|[*_`~]+$/g, "").trim();
|
|
555
556
|
}
|
|
557
|
+
function stripFencedCodeBlocks(s) {
|
|
558
|
+
return s.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "");
|
|
559
|
+
}
|
|
556
560
|
function extractBlock(text, startMarker, endMarker) {
|
|
557
561
|
const startIdx = text.search(startMarker);
|
|
558
562
|
if (startIdx === -1) return "";
|
|
@@ -864,7 +868,7 @@ var init_loadPriorArt = __esm({
|
|
|
864
868
|
// package.json
|
|
865
869
|
var package_default = {
|
|
866
870
|
name: "@kody-ade/kody-engine",
|
|
867
|
-
version: "0.4.
|
|
871
|
+
version: "0.4.74",
|
|
868
872
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
869
873
|
license: "MIT",
|
|
870
874
|
type: "module",
|
|
@@ -919,8 +923,8 @@ var package_default = {
|
|
|
919
923
|
|
|
920
924
|
// src/chat-cli.ts
|
|
921
925
|
import { execFileSync as execFileSync31 } from "child_process";
|
|
922
|
-
import * as
|
|
923
|
-
import * as
|
|
926
|
+
import * as fs34 from "fs";
|
|
927
|
+
import * as path32 from "path";
|
|
924
928
|
|
|
925
929
|
// src/chat/events.ts
|
|
926
930
|
import * as fs from "fs";
|
|
@@ -2013,8 +2017,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
2013
2017
|
|
|
2014
2018
|
// src/kody-cli.ts
|
|
2015
2019
|
import { execFileSync as execFileSync30 } from "child_process";
|
|
2016
|
-
import * as
|
|
2017
|
-
import * as
|
|
2020
|
+
import * as fs33 from "fs";
|
|
2021
|
+
import * as path31 from "path";
|
|
2018
2022
|
|
|
2019
2023
|
// src/dispatch.ts
|
|
2020
2024
|
import * as fs8 from "fs";
|
|
@@ -2455,8 +2459,8 @@ init_issue();
|
|
|
2455
2459
|
|
|
2456
2460
|
// src/executor.ts
|
|
2457
2461
|
import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
|
|
2458
|
-
import * as
|
|
2459
|
-
import * as
|
|
2462
|
+
import * as fs32 from "fs";
|
|
2463
|
+
import * as path30 from "path";
|
|
2460
2464
|
init_events();
|
|
2461
2465
|
|
|
2462
2466
|
// src/lifecycleLabels.ts
|
|
@@ -6659,6 +6663,19 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
|
|
|
6659
6663
|
git2(["pull", "origin", branchName], cwd);
|
|
6660
6664
|
} catch {
|
|
6661
6665
|
}
|
|
6666
|
+
if (!baseBranch || baseBranch === defaultBranch2) {
|
|
6667
|
+
try {
|
|
6668
|
+
git2(["merge", "--no-edit", `origin/${defaultBranch2}`], cwd);
|
|
6669
|
+
} catch {
|
|
6670
|
+
try {
|
|
6671
|
+
git2(["merge", "--abort"], cwd);
|
|
6672
|
+
} catch {
|
|
6673
|
+
}
|
|
6674
|
+
throw new Error(
|
|
6675
|
+
`Branch '${branchName}' has merge conflicts with 'origin/${defaultBranch2}'. Resolve manually or delete the branch to start fresh.`
|
|
6676
|
+
);
|
|
6677
|
+
}
|
|
6678
|
+
}
|
|
6662
6679
|
return { branch: branchName, created: false };
|
|
6663
6680
|
}
|
|
6664
6681
|
try {
|
|
@@ -7277,10 +7294,10 @@ import * as fs25 from "fs";
|
|
|
7277
7294
|
import * as path24 from "path";
|
|
7278
7295
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
7279
7296
|
var GoalStateError = class extends Error {
|
|
7280
|
-
constructor(
|
|
7281
|
-
super(`Invalid goal state at ${
|
|
7297
|
+
constructor(path33, message) {
|
|
7298
|
+
super(`Invalid goal state at ${path33}:
|
|
7282
7299
|
${message}`);
|
|
7283
|
-
this.path =
|
|
7300
|
+
this.path = path33;
|
|
7284
7301
|
this.name = "GoalStateError";
|
|
7285
7302
|
}
|
|
7286
7303
|
path;
|
|
@@ -9002,6 +9019,240 @@ function buildChildEnv(parent, force) {
|
|
|
9002
9019
|
return out;
|
|
9003
9020
|
}
|
|
9004
9021
|
|
|
9022
|
+
// src/scripts/brainServe.ts
|
|
9023
|
+
import { createServer } from "http";
|
|
9024
|
+
import * as fs30 from "fs";
|
|
9025
|
+
import * as path29 from "path";
|
|
9026
|
+
var DEFAULT_PORT = 8080;
|
|
9027
|
+
function getApiKey() {
|
|
9028
|
+
const key = (process.env.BRAIN_API_KEY ?? "").trim();
|
|
9029
|
+
if (!key) {
|
|
9030
|
+
throw new Error(
|
|
9031
|
+
"BRAIN_API_KEY env var is required \u2014 set it on the Fly machine before boot."
|
|
9032
|
+
);
|
|
9033
|
+
}
|
|
9034
|
+
return key;
|
|
9035
|
+
}
|
|
9036
|
+
function authOk(req, expected) {
|
|
9037
|
+
const xApiKey = req.headers["x-api-key"]?.trim();
|
|
9038
|
+
if (xApiKey && xApiKey === expected) return true;
|
|
9039
|
+
const auth = req.headers["authorization"]?.trim();
|
|
9040
|
+
if (auth && auth.toLowerCase().startsWith("bearer ")) {
|
|
9041
|
+
return auth.slice(7).trim() === expected;
|
|
9042
|
+
}
|
|
9043
|
+
return false;
|
|
9044
|
+
}
|
|
9045
|
+
function readJsonBody(req) {
|
|
9046
|
+
return new Promise((resolve4, reject) => {
|
|
9047
|
+
const chunks = [];
|
|
9048
|
+
req.on("data", (c) => chunks.push(c));
|
|
9049
|
+
req.on("end", () => {
|
|
9050
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
9051
|
+
if (!raw.trim()) {
|
|
9052
|
+
resolve4({});
|
|
9053
|
+
return;
|
|
9054
|
+
}
|
|
9055
|
+
try {
|
|
9056
|
+
resolve4(JSON.parse(raw));
|
|
9057
|
+
} catch (err) {
|
|
9058
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
9059
|
+
}
|
|
9060
|
+
});
|
|
9061
|
+
req.on("error", reject);
|
|
9062
|
+
});
|
|
9063
|
+
}
|
|
9064
|
+
function sendJson(res, status, body) {
|
|
9065
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
9066
|
+
res.end(JSON.stringify(body));
|
|
9067
|
+
}
|
|
9068
|
+
function writeSseHeaders(res) {
|
|
9069
|
+
res.writeHead(200, {
|
|
9070
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
9071
|
+
"cache-control": "no-cache, no-transform",
|
|
9072
|
+
connection: "keep-alive",
|
|
9073
|
+
"x-accel-buffering": "no"
|
|
9074
|
+
});
|
|
9075
|
+
}
|
|
9076
|
+
function emitSse(res, event) {
|
|
9077
|
+
res.write(`data: ${JSON.stringify(event)}
|
|
9078
|
+
|
|
9079
|
+
`);
|
|
9080
|
+
}
|
|
9081
|
+
var BrainSseSink = class {
|
|
9082
|
+
constructor(res, chatId) {
|
|
9083
|
+
this.res = res;
|
|
9084
|
+
this.chatId = chatId;
|
|
9085
|
+
}
|
|
9086
|
+
res;
|
|
9087
|
+
chatId;
|
|
9088
|
+
async emit(event) {
|
|
9089
|
+
switch (event.event) {
|
|
9090
|
+
case "chat.message": {
|
|
9091
|
+
const content = String(event.payload.content ?? "");
|
|
9092
|
+
if (content.length > 0) {
|
|
9093
|
+
emitSse(this.res, { type: "text", text: content, chatId: this.chatId });
|
|
9094
|
+
}
|
|
9095
|
+
return;
|
|
9096
|
+
}
|
|
9097
|
+
case "chat.tool": {
|
|
9098
|
+
if (event.payload.phase !== "use") return;
|
|
9099
|
+
emitSse(this.res, {
|
|
9100
|
+
type: "tool_use",
|
|
9101
|
+
name: typeof event.payload.name === "string" ? event.payload.name : "tool",
|
|
9102
|
+
input: event.payload.input ?? {},
|
|
9103
|
+
chatId: this.chatId
|
|
9104
|
+
});
|
|
9105
|
+
return;
|
|
9106
|
+
}
|
|
9107
|
+
case "chat.done": {
|
|
9108
|
+
emitSse(this.res, { type: "done", chatId: this.chatId });
|
|
9109
|
+
return;
|
|
9110
|
+
}
|
|
9111
|
+
case "chat.error": {
|
|
9112
|
+
const errMsg2 = typeof event.payload.error === "string" ? event.payload.error : "agent error";
|
|
9113
|
+
emitSse(this.res, { type: "error", error: errMsg2, chatId: this.chatId });
|
|
9114
|
+
return;
|
|
9115
|
+
}
|
|
9116
|
+
// chat.thinking / chat.ready / chat.exit — not part of the Brain protocol.
|
|
9117
|
+
default:
|
|
9118
|
+
return;
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
9121
|
+
};
|
|
9122
|
+
async function handleChatTurn(req, res, chatId, opts) {
|
|
9123
|
+
let body;
|
|
9124
|
+
try {
|
|
9125
|
+
body = await readJsonBody(req);
|
|
9126
|
+
} catch {
|
|
9127
|
+
sendJson(res, 400, { error: "invalid JSON body" });
|
|
9128
|
+
return;
|
|
9129
|
+
}
|
|
9130
|
+
const message = typeof body === "object" && body !== null && "message" in body ? body.message : void 0;
|
|
9131
|
+
if (typeof message !== "string" || !message.trim()) {
|
|
9132
|
+
sendJson(res, 400, { error: "message required" });
|
|
9133
|
+
return;
|
|
9134
|
+
}
|
|
9135
|
+
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
9136
|
+
fs30.mkdirSync(path29.dirname(sessionFile), { recursive: true });
|
|
9137
|
+
appendTurn(sessionFile, {
|
|
9138
|
+
role: "user",
|
|
9139
|
+
content: message,
|
|
9140
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9141
|
+
});
|
|
9142
|
+
writeSseHeaders(res);
|
|
9143
|
+
emitSse(res, { type: "chat", chatId });
|
|
9144
|
+
const sink = new BrainSseSink(res, chatId);
|
|
9145
|
+
try {
|
|
9146
|
+
await opts.runTurn({
|
|
9147
|
+
sessionId: chatId,
|
|
9148
|
+
sessionFile,
|
|
9149
|
+
cwd: opts.cwd,
|
|
9150
|
+
model: opts.model,
|
|
9151
|
+
litellmUrl: opts.litellmUrl,
|
|
9152
|
+
sink
|
|
9153
|
+
});
|
|
9154
|
+
} catch (err) {
|
|
9155
|
+
const errMsg2 = err instanceof Error ? err.message : String(err);
|
|
9156
|
+
process.stderr.write(`[brain-serve] chat turn failed: ${errMsg2}
|
|
9157
|
+
`);
|
|
9158
|
+
try {
|
|
9159
|
+
emitSse(res, { type: "error", error: errMsg2, chatId });
|
|
9160
|
+
} catch {
|
|
9161
|
+
}
|
|
9162
|
+
} finally {
|
|
9163
|
+
try {
|
|
9164
|
+
res.end();
|
|
9165
|
+
} catch {
|
|
9166
|
+
}
|
|
9167
|
+
}
|
|
9168
|
+
}
|
|
9169
|
+
function buildServer(opts) {
|
|
9170
|
+
const runTurn = opts.runTurn ?? runChatTurn;
|
|
9171
|
+
return createServer(async (req, res) => {
|
|
9172
|
+
if (!req.method || !req.url) {
|
|
9173
|
+
sendJson(res, 400, { error: "bad request" });
|
|
9174
|
+
return;
|
|
9175
|
+
}
|
|
9176
|
+
const url = new URL(req.url, `http://localhost`);
|
|
9177
|
+
if (req.method === "GET" && url.pathname === "/healthz") {
|
|
9178
|
+
sendJson(res, 200, { ok: true });
|
|
9179
|
+
return;
|
|
9180
|
+
}
|
|
9181
|
+
if (!authOk(req, opts.apiKey)) {
|
|
9182
|
+
sendJson(res, 401, { error: "unauthorized" });
|
|
9183
|
+
return;
|
|
9184
|
+
}
|
|
9185
|
+
const m = url.pathname.match(/^\/chats\/([^/]+)\/messages\/?$/);
|
|
9186
|
+
if (req.method === "POST" && m) {
|
|
9187
|
+
const chatId = decodeURIComponent(m[1] ?? "");
|
|
9188
|
+
if (!chatId) {
|
|
9189
|
+
sendJson(res, 400, { error: "chatId required" });
|
|
9190
|
+
return;
|
|
9191
|
+
}
|
|
9192
|
+
await handleChatTurn(req, res, chatId, {
|
|
9193
|
+
cwd: opts.cwd,
|
|
9194
|
+
model: opts.model,
|
|
9195
|
+
litellmUrl: opts.litellmUrl,
|
|
9196
|
+
runTurn
|
|
9197
|
+
});
|
|
9198
|
+
return;
|
|
9199
|
+
}
|
|
9200
|
+
sendJson(res, 404, { error: "not found" });
|
|
9201
|
+
});
|
|
9202
|
+
}
|
|
9203
|
+
var brainServe = async (ctx) => {
|
|
9204
|
+
ctx.skipAgent = true;
|
|
9205
|
+
const apiKey = getApiKey();
|
|
9206
|
+
const port = Number(process.env.PORT ?? DEFAULT_PORT);
|
|
9207
|
+
const model = parseProviderModel(ctx.config.agent.model);
|
|
9208
|
+
const usesProxy = needsLitellmProxy(model);
|
|
9209
|
+
let handle = null;
|
|
9210
|
+
if (usesProxy) {
|
|
9211
|
+
process.stdout.write(
|
|
9212
|
+
`[brain-serve] starting LiteLLM proxy for ${model.provider}/${model.model}...
|
|
9213
|
+
`
|
|
9214
|
+
);
|
|
9215
|
+
handle = await startLitellmIfNeeded(model, ctx.cwd);
|
|
9216
|
+
process.stdout.write(
|
|
9217
|
+
`[brain-serve] LiteLLM ready at ${handle?.url ?? LITELLM_DEFAULT_URL}
|
|
9218
|
+
`
|
|
9219
|
+
);
|
|
9220
|
+
}
|
|
9221
|
+
const litellmUrl = usesProxy ? handle?.url ?? LITELLM_DEFAULT_URL : null;
|
|
9222
|
+
const server = buildServer({
|
|
9223
|
+
apiKey,
|
|
9224
|
+
cwd: ctx.cwd,
|
|
9225
|
+
model,
|
|
9226
|
+
litellmUrl
|
|
9227
|
+
});
|
|
9228
|
+
await new Promise((resolve4) => {
|
|
9229
|
+
server.listen(port, "0.0.0.0", () => {
|
|
9230
|
+
process.stdout.write(
|
|
9231
|
+
`[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
|
|
9232
|
+
`
|
|
9233
|
+
);
|
|
9234
|
+
resolve4();
|
|
9235
|
+
});
|
|
9236
|
+
});
|
|
9237
|
+
const shutdown = (signal) => {
|
|
9238
|
+
process.stdout.write(`[brain-serve] ${signal} \u2014 shutting down
|
|
9239
|
+
`);
|
|
9240
|
+
server.close(() => {
|
|
9241
|
+
if (handle) {
|
|
9242
|
+
try {
|
|
9243
|
+
handle.kill();
|
|
9244
|
+
} catch {
|
|
9245
|
+
}
|
|
9246
|
+
}
|
|
9247
|
+
process.exit(0);
|
|
9248
|
+
});
|
|
9249
|
+
};
|
|
9250
|
+
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
9251
|
+
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
9252
|
+
await new Promise(() => {
|
|
9253
|
+
});
|
|
9254
|
+
};
|
|
9255
|
+
|
|
9005
9256
|
// src/scripts/serveFlow.ts
|
|
9006
9257
|
import { spawn as spawn3 } from "child_process";
|
|
9007
9258
|
function parseTarget(positional) {
|
|
@@ -9841,7 +10092,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
9841
10092
|
};
|
|
9842
10093
|
|
|
9843
10094
|
// src/scripts/writeRunSummary.ts
|
|
9844
|
-
import * as
|
|
10095
|
+
import * as fs31 from "fs";
|
|
9845
10096
|
var writeRunSummary = async (ctx, profile) => {
|
|
9846
10097
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
9847
10098
|
if (!summaryPath) return;
|
|
@@ -9863,7 +10114,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
9863
10114
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
9864
10115
|
lines.push("");
|
|
9865
10116
|
try {
|
|
9866
|
-
|
|
10117
|
+
fs31.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
9867
10118
|
`);
|
|
9868
10119
|
} catch {
|
|
9869
10120
|
}
|
|
@@ -9905,6 +10156,7 @@ var preflightScripts = {
|
|
|
9905
10156
|
dispatchJobFileTicks,
|
|
9906
10157
|
runTickScript,
|
|
9907
10158
|
serveFlow,
|
|
10159
|
+
brainServe,
|
|
9908
10160
|
loadGoalState,
|
|
9909
10161
|
handleAbandonedGoal,
|
|
9910
10162
|
deriveGoalPhase,
|
|
@@ -10085,9 +10337,9 @@ async function runExecutable(profileName, input) {
|
|
|
10085
10337
|
data: { ...input.preloadedData ?? {} },
|
|
10086
10338
|
output: { exitCode: 0 }
|
|
10087
10339
|
};
|
|
10088
|
-
const ndjsonDir =
|
|
10340
|
+
const ndjsonDir = path30.join(input.cwd, ".kody");
|
|
10089
10341
|
const invokeAgent = async (prompt) => {
|
|
10090
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
10342
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path30.isAbsolute(p) ? p : path30.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
10091
10343
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
10092
10344
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
10093
10345
|
return runAgent({
|
|
@@ -10282,7 +10534,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
10282
10534
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
10283
10535
|
try {
|
|
10284
10536
|
const profilePath = resolveProfilePath(profileName);
|
|
10285
|
-
if (!
|
|
10537
|
+
if (!fs32.existsSync(profilePath)) return null;
|
|
10286
10538
|
return loadProfile(profilePath).inputs;
|
|
10287
10539
|
} catch {
|
|
10288
10540
|
return null;
|
|
@@ -10291,17 +10543,17 @@ function getProfileInputsForChild(profileName, _cwd) {
|
|
|
10291
10543
|
function resolveProfilePath(profileName) {
|
|
10292
10544
|
const found = resolveExecutable(profileName);
|
|
10293
10545
|
if (found) return found;
|
|
10294
|
-
const here =
|
|
10546
|
+
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
10295
10547
|
const candidates = [
|
|
10296
|
-
|
|
10548
|
+
path30.join(here, "executables", profileName, "profile.json"),
|
|
10297
10549
|
// same-dir sibling (dev)
|
|
10298
|
-
|
|
10550
|
+
path30.join(here, "..", "executables", profileName, "profile.json"),
|
|
10299
10551
|
// up one (prod: dist/bin → dist/executables)
|
|
10300
|
-
|
|
10552
|
+
path30.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
10301
10553
|
// fallback
|
|
10302
10554
|
];
|
|
10303
10555
|
for (const c of candidates) {
|
|
10304
|
-
if (
|
|
10556
|
+
if (fs32.existsSync(c)) return c;
|
|
10305
10557
|
}
|
|
10306
10558
|
return candidates[0];
|
|
10307
10559
|
}
|
|
@@ -10401,8 +10653,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
10401
10653
|
var SIGKILL_GRACE_MS = 5e3;
|
|
10402
10654
|
async function runShellEntry(entry, ctx, profile) {
|
|
10403
10655
|
const shellName = entry.shell;
|
|
10404
|
-
const shellPath =
|
|
10405
|
-
if (!
|
|
10656
|
+
const shellPath = path30.join(profile.dir, shellName);
|
|
10657
|
+
if (!fs32.existsSync(shellPath)) {
|
|
10406
10658
|
ctx.skipAgent = true;
|
|
10407
10659
|
ctx.output.exitCode = 99;
|
|
10408
10660
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -10881,9 +11133,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
10881
11133
|
return token;
|
|
10882
11134
|
}
|
|
10883
11135
|
function detectPackageManager2(cwd) {
|
|
10884
|
-
if (
|
|
10885
|
-
if (
|
|
10886
|
-
if (
|
|
11136
|
+
if (fs33.existsSync(path31.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
11137
|
+
if (fs33.existsSync(path31.join(cwd, "yarn.lock"))) return "yarn";
|
|
11138
|
+
if (fs33.existsSync(path31.join(cwd, "bun.lockb"))) return "bun";
|
|
10887
11139
|
return "npm";
|
|
10888
11140
|
}
|
|
10889
11141
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -10970,11 +11222,11 @@ function configureGitIdentity(cwd) {
|
|
|
10970
11222
|
}
|
|
10971
11223
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
10972
11224
|
if (!issueNumber) return;
|
|
10973
|
-
const logPath =
|
|
11225
|
+
const logPath = path31.join(cwd, ".kody", "last-run.jsonl");
|
|
10974
11226
|
let tail = "";
|
|
10975
11227
|
try {
|
|
10976
|
-
if (
|
|
10977
|
-
const content =
|
|
11228
|
+
if (fs33.existsSync(logPath)) {
|
|
11229
|
+
const content = fs33.readFileSync(logPath, "utf-8");
|
|
10978
11230
|
tail = content.slice(-3e3);
|
|
10979
11231
|
}
|
|
10980
11232
|
} catch {
|
|
@@ -10999,7 +11251,7 @@ async function runCi(argv) {
|
|
|
10999
11251
|
return 0;
|
|
11000
11252
|
}
|
|
11001
11253
|
const args = parseCiArgs(argv);
|
|
11002
|
-
const cwd = args.cwd ?
|
|
11254
|
+
const cwd = args.cwd ? path31.resolve(args.cwd) : process.cwd();
|
|
11003
11255
|
let earlyConfig;
|
|
11004
11256
|
try {
|
|
11005
11257
|
earlyConfig = loadConfig(cwd);
|
|
@@ -11009,9 +11261,9 @@ async function runCi(argv) {
|
|
|
11009
11261
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
11010
11262
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
11011
11263
|
let manualWorkflowDispatch = false;
|
|
11012
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
11264
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs33.existsSync(dispatchEventPath)) {
|
|
11013
11265
|
try {
|
|
11014
|
-
const evt = JSON.parse(
|
|
11266
|
+
const evt = JSON.parse(fs33.readFileSync(dispatchEventPath, "utf-8"));
|
|
11015
11267
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
11016
11268
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
11017
11269
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -11270,9 +11522,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
11270
11522
|
return result;
|
|
11271
11523
|
}
|
|
11272
11524
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
11273
|
-
const sessionFile =
|
|
11274
|
-
const eventsFile =
|
|
11275
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
11525
|
+
const sessionFile = path32.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
11526
|
+
const eventsFile = path32.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
11527
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs34.existsSync(path32.join(cwd, p)));
|
|
11276
11528
|
if (paths.length === 0) return;
|
|
11277
11529
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
11278
11530
|
try {
|
|
@@ -11310,7 +11562,7 @@ async function runChat(argv) {
|
|
|
11310
11562
|
${CHAT_HELP}`);
|
|
11311
11563
|
return 64;
|
|
11312
11564
|
}
|
|
11313
|
-
const cwd = args.cwd ?
|
|
11565
|
+
const cwd = args.cwd ? path32.resolve(args.cwd) : process.cwd();
|
|
11314
11566
|
const sessionId = args.sessionId;
|
|
11315
11567
|
const unpackedSecrets = unpackAllSecrets();
|
|
11316
11568
|
if (unpackedSecrets > 0) {
|
|
@@ -11362,7 +11614,7 @@ ${CHAT_HELP}`);
|
|
|
11362
11614
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
11363
11615
|
const meta = readMeta(sessionFile);
|
|
11364
11616
|
process.stdout.write(
|
|
11365
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
11617
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs34.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
11366
11618
|
`
|
|
11367
11619
|
);
|
|
11368
11620
|
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.74",
|
|
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",
|