@kody-ade/kody-engine 0.4.613 → 0.4.617
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/README.md +6 -0
- package/dist/bin/kody.js +185 -212
- package/dist/runtime-services/goal-scheduler/scheduler.sh +0 -0
- package/package.json +32 -31
package/README.md
CHANGED
|
@@ -115,6 +115,12 @@ downstream workflows or modify `.github/workflows/*`.
|
|
|
115
115
|
|
|
116
116
|
The consumer workflow listens on `issue_comment` for `@kody ...` dispatch and `workflow_dispatch` for manual runs and chat mode. Convex owns scheduled Loop wakeups.
|
|
117
117
|
|
|
118
|
+
Live chat transcripts remain in Kody's canonical backend. In GitHub Actions,
|
|
119
|
+
the Engine uses the workflow's short-lived OIDC identity to call Dashboard's
|
|
120
|
+
repository-scoped backend endpoint; consumer repositories must not receive
|
|
121
|
+
`CONVEX_URL` or `KODY_SERVICE_KEY`. Local and server runtimes may connect
|
|
122
|
+
directly with those two variables when no GitHub Actions identity exists.
|
|
123
|
+
|
|
118
124
|
## Commands
|
|
119
125
|
|
|
120
126
|
```
|
package/dist/bin/kody.js
CHANGED
|
@@ -15,9 +15,13 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "@kody-ade/kody-engine",
|
|
18
|
-
version: "0.4.
|
|
18
|
+
version: "0.4.617",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
|
+
repository: {
|
|
22
|
+
type: "git",
|
|
23
|
+
url: "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
24
|
+
},
|
|
21
25
|
type: "module",
|
|
22
26
|
bin: {
|
|
23
27
|
"kody-engine": "dist/bin/kody.js"
|
|
@@ -72,10 +76,6 @@ var init_package = __esm({
|
|
|
72
76
|
engines: {
|
|
73
77
|
node: ">=22"
|
|
74
78
|
},
|
|
75
|
-
repository: {
|
|
76
|
-
type: "git",
|
|
77
|
-
url: "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
78
|
-
},
|
|
79
79
|
homepage: "https://github.com/aharonyaircohen/kody-engine",
|
|
80
80
|
bugs: "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
81
81
|
};
|
|
@@ -2322,7 +2322,9 @@ function parseDeliveryConfigAllowlist(raw) {
|
|
|
2322
2322
|
if (filePath !== "kody.config.json" || !Array.isArray(paths) || paths.length === 0 || paths.length > 32) {
|
|
2323
2323
|
throw new Error("contract.json deliveryConfigAllowlist contains an unsupported config file or path list");
|
|
2324
2324
|
}
|
|
2325
|
-
if (!paths.every(
|
|
2325
|
+
if (!paths.every(
|
|
2326
|
+
(value) => typeof value === "string" && /^[A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)*$/.test(value)
|
|
2327
|
+
)) {
|
|
2326
2328
|
throw new Error("contract.json deliveryConfigAllowlist must contain safe dotted config paths");
|
|
2327
2329
|
}
|
|
2328
2330
|
parsed[filePath] = [...new Set(paths)];
|
|
@@ -2367,9 +2369,7 @@ function parseCapabilityRequirements(raw) {
|
|
|
2367
2369
|
if (raw.githubTestToken !== void 0 && typeof raw.githubTestToken !== "boolean") {
|
|
2368
2370
|
throw new Error("contract.json requirements.githubTestToken must be boolean");
|
|
2369
2371
|
}
|
|
2370
|
-
if (raw.qaAccountCredentials !== void 0 && (!Array.isArray(raw.qaAccountCredentials) || raw.qaAccountCredentials.length === 0 || !raw.qaAccountCredentials.every(
|
|
2371
|
-
(name) => typeof name === "string" && /^[A-Z][A-Z0-9_]{0,127}$/.test(name)
|
|
2372
|
-
))) {
|
|
2372
|
+
if (raw.qaAccountCredentials !== void 0 && (!Array.isArray(raw.qaAccountCredentials) || raw.qaAccountCredentials.length === 0 || !raw.qaAccountCredentials.every((name) => typeof name === "string" && /^[A-Z][A-Z0-9_]{0,127}$/.test(name)))) {
|
|
2373
2373
|
throw new Error("contract.json requirements.qaAccountCredentials must contain valid credential names");
|
|
2374
2374
|
}
|
|
2375
2375
|
if (raw.qaAccountModelSettings !== void 0 && !isPlainObject(raw.qaAccountModelSettings)) {
|
|
@@ -8910,9 +8910,7 @@ function abortUnfinishedGitOps(cwd) {
|
|
|
8910
8910
|
return aborted;
|
|
8911
8911
|
}
|
|
8912
8912
|
function isExplicitlyAllowed(p, allowlist) {
|
|
8913
|
-
return allowlist.some(
|
|
8914
|
-
(entry) => entry.endsWith("/**") ? p.startsWith(entry.slice(0, -2)) : p === entry
|
|
8915
|
-
);
|
|
8913
|
+
return allowlist.some((entry) => entry.endsWith("/**") ? p.startsWith(entry.slice(0, -2)) : p === entry);
|
|
8916
8914
|
}
|
|
8917
8915
|
function isForbiddenPath(p, deliveryPathAllowlist = []) {
|
|
8918
8916
|
if (FORBIDDEN_PATH_EXACT.has(p)) return true;
|
|
@@ -12714,18 +12712,10 @@ var init_commitAndPush = __esm({
|
|
|
12714
12712
|
const deliveryPathAllowlist = Array.isArray(ctx.data.deliveryPathAllowlist) ? ctx.data.deliveryPathAllowlist : [];
|
|
12715
12713
|
const deliveryConfigAllowlist = ctx.data.deliveryConfigAllowlist && typeof ctx.data.deliveryConfigAllowlist === "object" ? ctx.data.deliveryConfigAllowlist : {};
|
|
12716
12714
|
try {
|
|
12717
|
-
const result2 = commitAndPush(
|
|
12718
|
-
branch,
|
|
12719
|
-
message,
|
|
12720
|
-
ctx.cwd,
|
|
12721
|
-
deliveryPathAllowlist,
|
|
12722
|
-
deliveryConfigAllowlist
|
|
12723
|
-
);
|
|
12715
|
+
const result2 = commitAndPush(branch, message, ctx.cwd, deliveryPathAllowlist, deliveryConfigAllowlist);
|
|
12724
12716
|
ctx.data.commitResult = result2;
|
|
12725
12717
|
const postCommitFiles = result2.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
|
|
12726
|
-
ctx.data.changedFiles = postCommitFiles.filter(
|
|
12727
|
-
(f) => !isForbiddenPath(f, deliveryPathAllowlist)
|
|
12728
|
-
);
|
|
12718
|
+
ctx.data.changedFiles = postCommitFiles.filter((f) => !isForbiddenPath(f, deliveryPathAllowlist));
|
|
12729
12719
|
if (result2.committed && !result2.pushed) {
|
|
12730
12720
|
const reason = result2.pushError ?? "push failed (no error detail)";
|
|
12731
12721
|
ctx.data.commitCrash = reason;
|
|
@@ -16553,8 +16543,8 @@ var loadLiveAgent;
|
|
|
16553
16543
|
var init_loadLiveAgent = __esm({
|
|
16554
16544
|
"src/scripts/loadLiveAgent.ts"() {
|
|
16555
16545
|
"use strict";
|
|
16556
|
-
init_definition_paths();
|
|
16557
16546
|
init_agents();
|
|
16547
|
+
init_definition_paths();
|
|
16558
16548
|
init_state_backend();
|
|
16559
16549
|
loadLiveAgent = async (ctx, profile) => {
|
|
16560
16550
|
const agent = String(ctx.args.agent ?? ctx.data.jobAgent ?? "").trim();
|
|
@@ -18594,10 +18584,7 @@ function parseSetCookie(value, hostname) {
|
|
|
18594
18584
|
const attributes = /* @__PURE__ */ new Map();
|
|
18595
18585
|
for (const part of parts) {
|
|
18596
18586
|
const index = part.indexOf("=");
|
|
18597
|
-
attributes.set(
|
|
18598
|
-
(index < 0 ? part : part.slice(0, index)).toLowerCase(),
|
|
18599
|
-
index < 0 ? "" : part.slice(index + 1)
|
|
18600
|
-
);
|
|
18587
|
+
attributes.set((index < 0 ? part : part.slice(0, index)).toLowerCase(), index < 0 ? "" : part.slice(index + 1));
|
|
18601
18588
|
}
|
|
18602
18589
|
const sameSite = attributes.get("samesite")?.toLowerCase();
|
|
18603
18590
|
return {
|
|
@@ -18722,11 +18709,9 @@ function mergeStorageStates(existingPath, nextPath) {
|
|
|
18722
18709
|
}
|
|
18723
18710
|
origins.set(entry.origin, { origin: entry.origin, localStorage: [...localStorage.values()] });
|
|
18724
18711
|
}
|
|
18725
|
-
fs49.writeFileSync(
|
|
18726
|
-
|
|
18727
|
-
|
|
18728
|
-
{ mode: 384 }
|
|
18729
|
-
);
|
|
18712
|
+
fs49.writeFileSync(nextPath, JSON.stringify({ cookies: [...cookies.values()], origins: [...origins.values()] }), {
|
|
18713
|
+
mode: 384
|
|
18714
|
+
});
|
|
18730
18715
|
}
|
|
18731
18716
|
function configurePlaywright(profile, storageStatePath) {
|
|
18732
18717
|
const playwright = profile.claudeCode.mcpServers.find((server) => server.name === "playwright");
|
|
@@ -20742,12 +20727,19 @@ function buildTickChildEnv(parent, force, loaded) {
|
|
|
20742
20727
|
"GH_PAT",
|
|
20743
20728
|
"GITHUB_TOKEN",
|
|
20744
20729
|
"GITHUB_REPOSITORY",
|
|
20730
|
+
"GITHUB_REPOSITORY_ID",
|
|
20731
|
+
"GITHUB_REPOSITORY_OWNER_ID",
|
|
20745
20732
|
"GITHUB_REF",
|
|
20746
20733
|
"GITHUB_SHA",
|
|
20747
20734
|
"GITHUB_RUN_ID",
|
|
20748
20735
|
"GITHUB_RUN_ATTEMPT",
|
|
20736
|
+
"GITHUB_SERVER_URL",
|
|
20737
|
+
"GITHUB_EVENT_NAME",
|
|
20749
20738
|
"GITHUB_WORKFLOW",
|
|
20739
|
+
"GITHUB_WORKFLOW_REF",
|
|
20750
20740
|
"GITHUB_ACTIONS",
|
|
20741
|
+
"ACTIONS_ID_TOKEN_REQUEST_URL",
|
|
20742
|
+
"ACTIONS_ID_TOKEN_REQUEST_TOKEN",
|
|
20751
20743
|
"CI",
|
|
20752
20744
|
"KODY_DRY_RUN",
|
|
20753
20745
|
"KODY_NO_COMMIT"
|
|
@@ -20981,25 +20973,6 @@ var init_runTickScript = __esm({
|
|
|
20981
20973
|
}
|
|
20982
20974
|
});
|
|
20983
20975
|
|
|
20984
|
-
// src/scripts/saveManagedGoalState.ts
|
|
20985
|
-
var saveManagedGoalState;
|
|
20986
|
-
var init_saveManagedGoalState = __esm({
|
|
20987
|
-
"src/scripts/saveManagedGoalState.ts"() {
|
|
20988
|
-
"use strict";
|
|
20989
|
-
init_state2();
|
|
20990
|
-
saveManagedGoalState = async (ctx) => {
|
|
20991
|
-
ctx.skipAgent = true;
|
|
20992
|
-
const goal = ctx.data.goal;
|
|
20993
|
-
if (!goal?.raw) return;
|
|
20994
|
-
const original = typeof ctx.data.goalOriginalStateText === "string" ? ctx.data.goalOriginalStateText : "";
|
|
20995
|
-
const changedBeforeTimestamp = original !== serializeGoalState(goal.raw);
|
|
20996
|
-
const updated = changedBeforeTimestamp ? { ...goal.raw, updatedAt: nowIso() } : goal.raw;
|
|
20997
|
-
ctx.data.goalPersistState = updated;
|
|
20998
|
-
ctx.data.goalPersistChanged = original !== serializeGoalState(updated);
|
|
20999
|
-
};
|
|
21000
|
-
}
|
|
21001
|
-
});
|
|
21002
|
-
|
|
21003
20976
|
// src/scripts/saveLiveAgentState.ts
|
|
21004
20977
|
var saveLiveAgentState;
|
|
21005
20978
|
var init_saveLiveAgentState = __esm({
|
|
@@ -21035,6 +21008,25 @@ var init_saveLiveAgentState = __esm({
|
|
|
21035
21008
|
}
|
|
21036
21009
|
});
|
|
21037
21010
|
|
|
21011
|
+
// src/scripts/saveManagedGoalState.ts
|
|
21012
|
+
var saveManagedGoalState;
|
|
21013
|
+
var init_saveManagedGoalState = __esm({
|
|
21014
|
+
"src/scripts/saveManagedGoalState.ts"() {
|
|
21015
|
+
"use strict";
|
|
21016
|
+
init_state2();
|
|
21017
|
+
saveManagedGoalState = async (ctx) => {
|
|
21018
|
+
ctx.skipAgent = true;
|
|
21019
|
+
const goal = ctx.data.goal;
|
|
21020
|
+
if (!goal?.raw) return;
|
|
21021
|
+
const original = typeof ctx.data.goalOriginalStateText === "string" ? ctx.data.goalOriginalStateText : "";
|
|
21022
|
+
const changedBeforeTimestamp = original !== serializeGoalState(goal.raw);
|
|
21023
|
+
const updated = changedBeforeTimestamp ? { ...goal.raw, updatedAt: nowIso() } : goal.raw;
|
|
21024
|
+
ctx.data.goalPersistState = updated;
|
|
21025
|
+
ctx.data.goalPersistChanged = original !== serializeGoalState(updated);
|
|
21026
|
+
};
|
|
21027
|
+
}
|
|
21028
|
+
});
|
|
21029
|
+
|
|
21038
21030
|
// src/scripts/setCommentTarget.ts
|
|
21039
21031
|
var setCommentTarget;
|
|
21040
21032
|
var init_setCommentTarget = __esm({
|
|
@@ -22383,8 +22375,8 @@ var init_scripts = __esm({
|
|
|
22383
22375
|
init_runScheduledImplementationTick();
|
|
22384
22376
|
init_runSimpleCapabilityScript();
|
|
22385
22377
|
init_runTickScript();
|
|
22386
|
-
init_saveManagedGoalState();
|
|
22387
22378
|
init_saveLiveAgentState();
|
|
22379
|
+
init_saveManagedGoalState();
|
|
22388
22380
|
init_saveTaskState();
|
|
22389
22381
|
init_setCommentTarget();
|
|
22390
22382
|
init_setLifecycleLabel();
|
|
@@ -25587,6 +25579,7 @@ function makeRunId(sessionId, suffix) {
|
|
|
25587
25579
|
}
|
|
25588
25580
|
|
|
25589
25581
|
// src/chat/session-store.ts
|
|
25582
|
+
init_kody_api_client();
|
|
25590
25583
|
init_convex_client();
|
|
25591
25584
|
import { anyApi as anyApi2 } from "convex/server";
|
|
25592
25585
|
function currentEpochTurns(result) {
|
|
@@ -25614,11 +25607,12 @@ function createSessionStore(opts) {
|
|
|
25614
25607
|
warn: (message) => process.stderr.write(`[kody:chat:store] ${message}
|
|
25615
25608
|
`)
|
|
25616
25609
|
};
|
|
25617
|
-
const
|
|
25618
|
-
const
|
|
25610
|
+
const env = opts.env ?? process.env;
|
|
25611
|
+
const client = opts.client !== void 0 ? opts.client : hasGitHubActionsIdentity(env) ? createKodyApiBackendClient(env) : createConvexClientFromEnv(env);
|
|
25612
|
+
const tenantId2 = opts.tenantId ?? env.GITHUB_REPOSITORY ?? "";
|
|
25619
25613
|
if (!client || !tenantId2) {
|
|
25620
25614
|
throw new Error(
|
|
25621
|
-
"Canonical
|
|
25615
|
+
"Canonical conversation storage requires GitHub Actions identity or direct Kody backend credentials"
|
|
25622
25616
|
);
|
|
25623
25617
|
}
|
|
25624
25618
|
logger.info(`conversation ${opts.sessionId}: using canonical Convex store (tenant ${tenantId2})`);
|
|
@@ -27196,6 +27190,10 @@ function ensurePackageManagerInstalled(pm, cwd) {
|
|
|
27196
27190
|
return shellOut("npm", ["install", "-g", spec], cwd);
|
|
27197
27191
|
}
|
|
27198
27192
|
function installDeps(pm, cwd) {
|
|
27193
|
+
if (!fs56.existsSync(path53.join(cwd, "package.json"))) {
|
|
27194
|
+
process.stdout.write("\u2192 kody: no package.json found \u2014 skipping consumer dependency install\n");
|
|
27195
|
+
return 0;
|
|
27196
|
+
}
|
|
27199
27197
|
const ensureCode = ensurePackageManagerInstalled(pm, cwd);
|
|
27200
27198
|
if (ensureCode !== 0) return ensureCode;
|
|
27201
27199
|
const args = {
|
|
@@ -29289,8 +29287,137 @@ init_repoWorkspace();
|
|
|
29289
29287
|
import * as path58 from "path";
|
|
29290
29288
|
import { createInterface as createInterface2 } from "readline";
|
|
29291
29289
|
|
|
29290
|
+
// src/terminal/brain-terminal-adapters.ts
|
|
29291
|
+
import { spawn as spawn9 } from "child_process";
|
|
29292
|
+
import { createHash as createHash9, randomBytes as randomBytes2 } from "crypto";
|
|
29293
|
+
import { mkdir, readFile, rename, writeFile as writeFile2 } from "fs/promises";
|
|
29294
|
+
import * as path57 from "path";
|
|
29295
|
+
function runTerminalCommand(command, args, input) {
|
|
29296
|
+
return new Promise((resolve24, reject) => {
|
|
29297
|
+
const child = spawn9(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
29298
|
+
const stdout = [];
|
|
29299
|
+
const stderr = [];
|
|
29300
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
29301
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
29302
|
+
child.on("error", reject);
|
|
29303
|
+
child.on("close", (code) => {
|
|
29304
|
+
resolve24({
|
|
29305
|
+
code: code ?? 1,
|
|
29306
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
29307
|
+
stderr: Buffer.concat(stderr).toString("utf8")
|
|
29308
|
+
});
|
|
29309
|
+
});
|
|
29310
|
+
if (input !== void 0) child.stdin.end(input);
|
|
29311
|
+
else child.stdin.end();
|
|
29312
|
+
});
|
|
29313
|
+
}
|
|
29314
|
+
function storeKey(id) {
|
|
29315
|
+
return createHash9("sha256").update(id).digest("hex");
|
|
29316
|
+
}
|
|
29317
|
+
function isStoredSession(value) {
|
|
29318
|
+
if (!value || typeof value !== "object") return false;
|
|
29319
|
+
const session = value;
|
|
29320
|
+
return session.version === 1 && typeof session.id === "string" && typeof session.sessionName === "string" && typeof session.cwd === "string" && Number.isInteger(session.generation) && Number.isInteger(session.revision) && typeof session.output === "string" && typeof session.scope?.owner === "string" && typeof session.scope?.repo === "string" && typeof session.scope?.conversationId === "string";
|
|
29321
|
+
}
|
|
29322
|
+
var FileBrainTerminalMetadataStore = class {
|
|
29323
|
+
constructor(root) {
|
|
29324
|
+
this.root = root;
|
|
29325
|
+
}
|
|
29326
|
+
root;
|
|
29327
|
+
file(id) {
|
|
29328
|
+
return path57.join(this.root, `${storeKey(id)}.json`);
|
|
29329
|
+
}
|
|
29330
|
+
async read(id) {
|
|
29331
|
+
try {
|
|
29332
|
+
const parsed = JSON.parse(await readFile(this.file(id), "utf8"));
|
|
29333
|
+
if (!isStoredSession(parsed) || parsed.id !== id) {
|
|
29334
|
+
throw new Error("stored terminal session is invalid");
|
|
29335
|
+
}
|
|
29336
|
+
return parsed;
|
|
29337
|
+
} catch (error) {
|
|
29338
|
+
if (error.code === "ENOENT") return null;
|
|
29339
|
+
throw error;
|
|
29340
|
+
}
|
|
29341
|
+
}
|
|
29342
|
+
async write(session) {
|
|
29343
|
+
await mkdir(this.root, { recursive: true, mode: 448 });
|
|
29344
|
+
const target = this.file(session.id);
|
|
29345
|
+
const temporary = `${target}.${process.pid}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
29346
|
+
await writeFile2(temporary, `${JSON.stringify(session)}
|
|
29347
|
+
`, { mode: 384 });
|
|
29348
|
+
await rename(temporary, target);
|
|
29349
|
+
}
|
|
29350
|
+
};
|
|
29351
|
+
function commandError(action, result) {
|
|
29352
|
+
const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`;
|
|
29353
|
+
return new Error(`tmux ${action} failed: ${detail.slice(0, 500)}`);
|
|
29354
|
+
}
|
|
29355
|
+
var TmuxBrainTerminalRuntime = class {
|
|
29356
|
+
constructor(run = runTerminalCommand) {
|
|
29357
|
+
this.run = run;
|
|
29358
|
+
}
|
|
29359
|
+
run;
|
|
29360
|
+
async tmux(action, args, input) {
|
|
29361
|
+
const result = await this.run("tmux", args, input);
|
|
29362
|
+
if (result.code !== 0) throw commandError(action, result);
|
|
29363
|
+
return result;
|
|
29364
|
+
}
|
|
29365
|
+
async start(sessionName2, cwd, cols, rows) {
|
|
29366
|
+
await this.tmux("start", [
|
|
29367
|
+
"new-session",
|
|
29368
|
+
"-d",
|
|
29369
|
+
"-s",
|
|
29370
|
+
sessionName2,
|
|
29371
|
+
"-x",
|
|
29372
|
+
String(cols),
|
|
29373
|
+
"-y",
|
|
29374
|
+
String(rows),
|
|
29375
|
+
"-c",
|
|
29376
|
+
cwd,
|
|
29377
|
+
"/bin/bash",
|
|
29378
|
+
"-l"
|
|
29379
|
+
]);
|
|
29380
|
+
await this.tmux("configure", ["set-option", "-t", sessionName2, "status", "off"]);
|
|
29381
|
+
await this.tmux("configure", ["set-option", "-t", sessionName2, "history-limit", "50000"]);
|
|
29382
|
+
await this.tmux("configure", ["set-option", "-w", "-t", sessionName2, "remain-on-exit", "on"]);
|
|
29383
|
+
const inspected = await this.inspect(sessionName2);
|
|
29384
|
+
if (!inspected.alive || inspected.processId === null) throw new Error("tmux terminal did not start");
|
|
29385
|
+
return { processId: inspected.processId };
|
|
29386
|
+
}
|
|
29387
|
+
async inspect(sessionName2) {
|
|
29388
|
+
const result = await this.run("tmux", ["list-panes", "-t", sessionName2, "-F", "#{pane_dead}:#{pane_pid}"]);
|
|
29389
|
+
if (result.code !== 0) return { alive: false, processId: null };
|
|
29390
|
+
const [dead, pid] = result.stdout.trim().split(":");
|
|
29391
|
+
const processId = Number(pid);
|
|
29392
|
+
return {
|
|
29393
|
+
alive: dead === "0" && Number.isInteger(processId) && processId > 0,
|
|
29394
|
+
processId: Number.isInteger(processId) && processId > 0 ? processId : null
|
|
29395
|
+
};
|
|
29396
|
+
}
|
|
29397
|
+
async capture(sessionName2) {
|
|
29398
|
+
const alternate = await this.run("tmux", ["display-message", "-p", "-t", sessionName2, "#{alternate_on}"]);
|
|
29399
|
+
if (alternate.code !== 0) throw commandError("inspect screen", alternate);
|
|
29400
|
+
const args = alternate.stdout.trim() === "1" ? ["capture-pane", "-p", "-e", "-t", sessionName2] : ["capture-pane", "-p", "-e", "-J", "-S", "-50000", "-t", sessionName2];
|
|
29401
|
+
return (await this.tmux("capture", args)).stdout;
|
|
29402
|
+
}
|
|
29403
|
+
async input(sessionName2, data) {
|
|
29404
|
+
const bufferName = `kody_${randomBytes2(8).toString("hex")}`;
|
|
29405
|
+
await this.tmux("load input", ["load-buffer", "-b", bufferName, "-"], data);
|
|
29406
|
+
await this.tmux("paste input", ["paste-buffer", "-d", "-b", bufferName, "-t", sessionName2]);
|
|
29407
|
+
}
|
|
29408
|
+
async resize(sessionName2, cols, rows) {
|
|
29409
|
+
await this.tmux("resize", ["resize-window", "-t", sessionName2, "-x", String(cols), "-y", String(rows)]);
|
|
29410
|
+
}
|
|
29411
|
+
async stop(sessionName2) {
|
|
29412
|
+
const result = await this.run("tmux", ["kill-session", "-t", sessionName2]);
|
|
29413
|
+
if (result.code !== 0 && !/can't find session|no server running/i.test(result.stderr)) {
|
|
29414
|
+
throw commandError("stop", result);
|
|
29415
|
+
}
|
|
29416
|
+
}
|
|
29417
|
+
};
|
|
29418
|
+
|
|
29292
29419
|
// src/terminal/brain-terminal-session.ts
|
|
29293
|
-
import { createHash as
|
|
29420
|
+
import { createHash as createHash10 } from "crypto";
|
|
29294
29421
|
var MAX_CAPTURE_CHARS = 2e5;
|
|
29295
29422
|
function requiredIdentifier(value, name, max = 240) {
|
|
29296
29423
|
if (typeof value !== "string" || !value.trim() || value.length > max) {
|
|
@@ -29371,7 +29498,7 @@ function parseBrainTerminalCommand(value) {
|
|
|
29371
29498
|
}
|
|
29372
29499
|
}
|
|
29373
29500
|
function sessionName(id) {
|
|
29374
|
-
return `kody_${
|
|
29501
|
+
return `kody_${createHash10("sha256").update(id).digest("hex").slice(0, 32)}`;
|
|
29375
29502
|
}
|
|
29376
29503
|
function stateEvent(session) {
|
|
29377
29504
|
return {
|
|
@@ -29433,12 +29560,7 @@ var BrainTerminalSessionAgent = class {
|
|
|
29433
29560
|
await this.persist(session);
|
|
29434
29561
|
let started;
|
|
29435
29562
|
try {
|
|
29436
|
-
started = await this.dependencies.runtime.start(
|
|
29437
|
-
session.sessionName,
|
|
29438
|
-
session.cwd,
|
|
29439
|
-
session.cols,
|
|
29440
|
-
session.rows
|
|
29441
|
-
);
|
|
29563
|
+
started = await this.dependencies.runtime.start(session.sessionName, session.cwd, session.cols, session.rows);
|
|
29442
29564
|
} catch (cause) {
|
|
29443
29565
|
session = { ...session, state: "failed", updatedAt: this.now() };
|
|
29444
29566
|
await this.persist(session);
|
|
@@ -29597,155 +29719,6 @@ var BrainTerminalSessionAgent = class {
|
|
|
29597
29719
|
}
|
|
29598
29720
|
};
|
|
29599
29721
|
|
|
29600
|
-
// src/terminal/brain-terminal-adapters.ts
|
|
29601
|
-
import { createHash as createHash10, randomBytes as randomBytes2 } from "crypto";
|
|
29602
|
-
import { mkdir, readFile, rename, writeFile as writeFile2 } from "fs/promises";
|
|
29603
|
-
import * as path57 from "path";
|
|
29604
|
-
import { spawn as spawn9 } from "child_process";
|
|
29605
|
-
function runTerminalCommand(command, args, input) {
|
|
29606
|
-
return new Promise((resolve24, reject) => {
|
|
29607
|
-
const child = spawn9(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
29608
|
-
const stdout = [];
|
|
29609
|
-
const stderr = [];
|
|
29610
|
-
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
29611
|
-
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
29612
|
-
child.on("error", reject);
|
|
29613
|
-
child.on("close", (code) => {
|
|
29614
|
-
resolve24({
|
|
29615
|
-
code: code ?? 1,
|
|
29616
|
-
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
29617
|
-
stderr: Buffer.concat(stderr).toString("utf8")
|
|
29618
|
-
});
|
|
29619
|
-
});
|
|
29620
|
-
if (input !== void 0) child.stdin.end(input);
|
|
29621
|
-
else child.stdin.end();
|
|
29622
|
-
});
|
|
29623
|
-
}
|
|
29624
|
-
function storeKey(id) {
|
|
29625
|
-
return createHash10("sha256").update(id).digest("hex");
|
|
29626
|
-
}
|
|
29627
|
-
function isStoredSession(value) {
|
|
29628
|
-
if (!value || typeof value !== "object") return false;
|
|
29629
|
-
const session = value;
|
|
29630
|
-
return session.version === 1 && typeof session.id === "string" && typeof session.sessionName === "string" && typeof session.cwd === "string" && Number.isInteger(session.generation) && Number.isInteger(session.revision) && typeof session.output === "string" && typeof session.scope?.owner === "string" && typeof session.scope?.repo === "string" && typeof session.scope?.conversationId === "string";
|
|
29631
|
-
}
|
|
29632
|
-
var FileBrainTerminalMetadataStore = class {
|
|
29633
|
-
constructor(root) {
|
|
29634
|
-
this.root = root;
|
|
29635
|
-
}
|
|
29636
|
-
root;
|
|
29637
|
-
file(id) {
|
|
29638
|
-
return path57.join(this.root, `${storeKey(id)}.json`);
|
|
29639
|
-
}
|
|
29640
|
-
async read(id) {
|
|
29641
|
-
try {
|
|
29642
|
-
const parsed = JSON.parse(await readFile(this.file(id), "utf8"));
|
|
29643
|
-
if (!isStoredSession(parsed) || parsed.id !== id) {
|
|
29644
|
-
throw new Error("stored terminal session is invalid");
|
|
29645
|
-
}
|
|
29646
|
-
return parsed;
|
|
29647
|
-
} catch (error) {
|
|
29648
|
-
if (error.code === "ENOENT") return null;
|
|
29649
|
-
throw error;
|
|
29650
|
-
}
|
|
29651
|
-
}
|
|
29652
|
-
async write(session) {
|
|
29653
|
-
await mkdir(this.root, { recursive: true, mode: 448 });
|
|
29654
|
-
const target = this.file(session.id);
|
|
29655
|
-
const temporary = `${target}.${process.pid}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
29656
|
-
await writeFile2(temporary, `${JSON.stringify(session)}
|
|
29657
|
-
`, { mode: 384 });
|
|
29658
|
-
await rename(temporary, target);
|
|
29659
|
-
}
|
|
29660
|
-
};
|
|
29661
|
-
function commandError(action, result) {
|
|
29662
|
-
const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`;
|
|
29663
|
-
return new Error(`tmux ${action} failed: ${detail.slice(0, 500)}`);
|
|
29664
|
-
}
|
|
29665
|
-
var TmuxBrainTerminalRuntime = class {
|
|
29666
|
-
constructor(run = runTerminalCommand) {
|
|
29667
|
-
this.run = run;
|
|
29668
|
-
}
|
|
29669
|
-
run;
|
|
29670
|
-
async tmux(action, args, input) {
|
|
29671
|
-
const result = await this.run("tmux", args, input);
|
|
29672
|
-
if (result.code !== 0) throw commandError(action, result);
|
|
29673
|
-
return result;
|
|
29674
|
-
}
|
|
29675
|
-
async start(sessionName2, cwd, cols, rows) {
|
|
29676
|
-
await this.tmux("start", [
|
|
29677
|
-
"new-session",
|
|
29678
|
-
"-d",
|
|
29679
|
-
"-s",
|
|
29680
|
-
sessionName2,
|
|
29681
|
-
"-x",
|
|
29682
|
-
String(cols),
|
|
29683
|
-
"-y",
|
|
29684
|
-
String(rows),
|
|
29685
|
-
"-c",
|
|
29686
|
-
cwd,
|
|
29687
|
-
"/bin/bash",
|
|
29688
|
-
"-l"
|
|
29689
|
-
]);
|
|
29690
|
-
await this.tmux("configure", ["set-option", "-t", sessionName2, "status", "off"]);
|
|
29691
|
-
await this.tmux("configure", ["set-option", "-t", sessionName2, "history-limit", "50000"]);
|
|
29692
|
-
await this.tmux("configure", ["set-option", "-w", "-t", sessionName2, "remain-on-exit", "on"]);
|
|
29693
|
-
const inspected = await this.inspect(sessionName2);
|
|
29694
|
-
if (!inspected.alive || inspected.processId === null) throw new Error("tmux terminal did not start");
|
|
29695
|
-
return { processId: inspected.processId };
|
|
29696
|
-
}
|
|
29697
|
-
async inspect(sessionName2) {
|
|
29698
|
-
const result = await this.run("tmux", [
|
|
29699
|
-
"list-panes",
|
|
29700
|
-
"-t",
|
|
29701
|
-
sessionName2,
|
|
29702
|
-
"-F",
|
|
29703
|
-
"#{pane_dead}:#{pane_pid}"
|
|
29704
|
-
]);
|
|
29705
|
-
if (result.code !== 0) return { alive: false, processId: null };
|
|
29706
|
-
const [dead, pid] = result.stdout.trim().split(":");
|
|
29707
|
-
const processId = Number(pid);
|
|
29708
|
-
return {
|
|
29709
|
-
alive: dead === "0" && Number.isInteger(processId) && processId > 0,
|
|
29710
|
-
processId: Number.isInteger(processId) && processId > 0 ? processId : null
|
|
29711
|
-
};
|
|
29712
|
-
}
|
|
29713
|
-
async capture(sessionName2) {
|
|
29714
|
-
const alternate = await this.run("tmux", [
|
|
29715
|
-
"display-message",
|
|
29716
|
-
"-p",
|
|
29717
|
-
"-t",
|
|
29718
|
-
sessionName2,
|
|
29719
|
-
"#{alternate_on}"
|
|
29720
|
-
]);
|
|
29721
|
-
if (alternate.code !== 0) throw commandError("inspect screen", alternate);
|
|
29722
|
-
const args = alternate.stdout.trim() === "1" ? ["capture-pane", "-p", "-e", "-t", sessionName2] : ["capture-pane", "-p", "-e", "-J", "-S", "-50000", "-t", sessionName2];
|
|
29723
|
-
return (await this.tmux("capture", args)).stdout;
|
|
29724
|
-
}
|
|
29725
|
-
async input(sessionName2, data) {
|
|
29726
|
-
const bufferName = `kody_${randomBytes2(8).toString("hex")}`;
|
|
29727
|
-
await this.tmux("load input", ["load-buffer", "-b", bufferName, "-"], data);
|
|
29728
|
-
await this.tmux("paste input", ["paste-buffer", "-d", "-b", bufferName, "-t", sessionName2]);
|
|
29729
|
-
}
|
|
29730
|
-
async resize(sessionName2, cols, rows) {
|
|
29731
|
-
await this.tmux("resize", [
|
|
29732
|
-
"resize-window",
|
|
29733
|
-
"-t",
|
|
29734
|
-
sessionName2,
|
|
29735
|
-
"-x",
|
|
29736
|
-
String(cols),
|
|
29737
|
-
"-y",
|
|
29738
|
-
String(rows)
|
|
29739
|
-
]);
|
|
29740
|
-
}
|
|
29741
|
-
async stop(sessionName2) {
|
|
29742
|
-
const result = await this.run("tmux", ["kill-session", "-t", sessionName2]);
|
|
29743
|
-
if (result.code !== 0 && !/can't find session|no server running/i.test(result.stderr)) {
|
|
29744
|
-
throw commandError("stop", result);
|
|
29745
|
-
}
|
|
29746
|
-
}
|
|
29747
|
-
};
|
|
29748
|
-
|
|
29749
29722
|
// src/servers/brain-terminal-agent.ts
|
|
29750
29723
|
var DEFAULT_POLL_INTERVAL_MS = 150;
|
|
29751
29724
|
function writeEvent(output, event) {
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "kody
|
|
3
|
+
"version": "0.4.617",
|
|
4
|
+
"description": "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"bin": {
|
|
8
12
|
"kody-engine": "dist/bin/kody.js"
|
|
@@ -12,6 +16,30 @@
|
|
|
12
16
|
"templates",
|
|
13
17
|
"kody.config.schema.json"
|
|
14
18
|
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"kody:run": "tsx bin/kody.ts",
|
|
21
|
+
"serve": "tsx bin/kody.ts serve",
|
|
22
|
+
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
23
|
+
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
24
|
+
"clean:dist": "node scripts/clean-dist.cjs",
|
|
25
|
+
"build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
|
|
26
|
+
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
27
|
+
"pretest": "pnpm check:modularity",
|
|
28
|
+
"test": "vitest run tests/unit tests/int --coverage",
|
|
29
|
+
"posttest": "tsx scripts/check-coverage-floor.ts",
|
|
30
|
+
"test:smoke": "vitest run tests/smoke --no-coverage",
|
|
31
|
+
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
32
|
+
"verify:live-release": "tsx scripts/live-release-gate.ts",
|
|
33
|
+
"test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
|
|
34
|
+
"test:all": "vitest run tests --no-coverage",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"lint": "biome check",
|
|
37
|
+
"lint:fix": "biome check --write",
|
|
38
|
+
"format": "biome format --write",
|
|
39
|
+
"verify:package": "node scripts/verify-package-tarball.cjs",
|
|
40
|
+
"brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
|
|
41
|
+
"prepublishOnly": "pnpm typecheck && pnpm test:runtime-services && pnpm build && pnpm verify:package"
|
|
42
|
+
},
|
|
15
43
|
"dependencies": {
|
|
16
44
|
"@actions/cache": "^6.0.0",
|
|
17
45
|
"@anthropic-ai/claude-agent-sdk": "0.2.119",
|
|
@@ -33,33 +61,6 @@
|
|
|
33
61
|
"engines": {
|
|
34
62
|
"node": ">=22"
|
|
35
63
|
},
|
|
36
|
-
"repository": {
|
|
37
|
-
"type": "git",
|
|
38
|
-
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
39
|
-
},
|
|
40
64
|
"homepage": "https://github.com/aharonyaircohen/kody-engine",
|
|
41
|
-
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
42
|
-
|
|
43
|
-
"kody:run": "tsx bin/kody.ts",
|
|
44
|
-
"serve": "tsx bin/kody.ts serve",
|
|
45
|
-
"serve:vscode": "tsx bin/kody.ts serve vscode",
|
|
46
|
-
"serve:claude": "tsx bin/kody.ts serve claude",
|
|
47
|
-
"clean:dist": "node scripts/clean-dist.cjs",
|
|
48
|
-
"build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
|
|
49
|
-
"check:modularity": "tsx scripts/check-script-modularity.ts",
|
|
50
|
-
"pretest": "pnpm check:modularity",
|
|
51
|
-
"test": "vitest run tests/unit tests/int --coverage",
|
|
52
|
-
"posttest": "tsx scripts/check-coverage-floor.ts",
|
|
53
|
-
"test:smoke": "vitest run tests/smoke --no-coverage",
|
|
54
|
-
"test:e2e": "vitest run tests/e2e --no-coverage",
|
|
55
|
-
"verify:live-release": "tsx scripts/live-release-gate.ts",
|
|
56
|
-
"test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
|
|
57
|
-
"test:all": "vitest run tests --no-coverage",
|
|
58
|
-
"typecheck": "tsc --noEmit",
|
|
59
|
-
"lint": "biome check",
|
|
60
|
-
"lint:fix": "biome check --write",
|
|
61
|
-
"format": "biome format --write",
|
|
62
|
-
"verify:package": "node scripts/verify-package-tarball.cjs",
|
|
63
|
-
"brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
|
|
64
|
-
}
|
|
65
|
-
}
|
|
65
|
+
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
66
|
+
}
|