@kody-ade/kody-engine 0.4.612 → 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 +189 -214
- package/dist/capabilities/run/definition.json +4 -0
- package/package.json +6 -6
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)) {
|
|
@@ -6110,6 +6110,7 @@ function loadProfile(profilePath) {
|
|
|
6110
6110
|
const STATE_LOADERS = [
|
|
6111
6111
|
"loadCapabilityState",
|
|
6112
6112
|
"loadJobFromFile",
|
|
6113
|
+
"loadLiveAgent",
|
|
6113
6114
|
"runTickScript",
|
|
6114
6115
|
"runScheduledImplementationTick",
|
|
6115
6116
|
"runScheduledExecutableTick"
|
|
@@ -8909,9 +8910,7 @@ function abortUnfinishedGitOps(cwd) {
|
|
|
8909
8910
|
return aborted;
|
|
8910
8911
|
}
|
|
8911
8912
|
function isExplicitlyAllowed(p, allowlist) {
|
|
8912
|
-
return allowlist.some(
|
|
8913
|
-
(entry) => entry.endsWith("/**") ? p.startsWith(entry.slice(0, -2)) : p === entry
|
|
8914
|
-
);
|
|
8913
|
+
return allowlist.some((entry) => entry.endsWith("/**") ? p.startsWith(entry.slice(0, -2)) : p === entry);
|
|
8915
8914
|
}
|
|
8916
8915
|
function isForbiddenPath(p, deliveryPathAllowlist = []) {
|
|
8917
8916
|
if (FORBIDDEN_PATH_EXACT.has(p)) return true;
|
|
@@ -12713,18 +12712,10 @@ var init_commitAndPush = __esm({
|
|
|
12713
12712
|
const deliveryPathAllowlist = Array.isArray(ctx.data.deliveryPathAllowlist) ? ctx.data.deliveryPathAllowlist : [];
|
|
12714
12713
|
const deliveryConfigAllowlist = ctx.data.deliveryConfigAllowlist && typeof ctx.data.deliveryConfigAllowlist === "object" ? ctx.data.deliveryConfigAllowlist : {};
|
|
12715
12714
|
try {
|
|
12716
|
-
const result2 = commitAndPush(
|
|
12717
|
-
branch,
|
|
12718
|
-
message,
|
|
12719
|
-
ctx.cwd,
|
|
12720
|
-
deliveryPathAllowlist,
|
|
12721
|
-
deliveryConfigAllowlist
|
|
12722
|
-
);
|
|
12715
|
+
const result2 = commitAndPush(branch, message, ctx.cwd, deliveryPathAllowlist, deliveryConfigAllowlist);
|
|
12723
12716
|
ctx.data.commitResult = result2;
|
|
12724
12717
|
const postCommitFiles = result2.committed ? listFilesInCommit("HEAD", ctx.cwd) : listChangedFiles(ctx.cwd);
|
|
12725
|
-
ctx.data.changedFiles = postCommitFiles.filter(
|
|
12726
|
-
(f) => !isForbiddenPath(f, deliveryPathAllowlist)
|
|
12727
|
-
);
|
|
12718
|
+
ctx.data.changedFiles = postCommitFiles.filter((f) => !isForbiddenPath(f, deliveryPathAllowlist));
|
|
12728
12719
|
if (result2.committed && !result2.pushed) {
|
|
12729
12720
|
const reason = result2.pushError ?? "push failed (no error detail)";
|
|
12730
12721
|
ctx.data.commitCrash = reason;
|
|
@@ -16552,8 +16543,8 @@ var loadLiveAgent;
|
|
|
16552
16543
|
var init_loadLiveAgent = __esm({
|
|
16553
16544
|
"src/scripts/loadLiveAgent.ts"() {
|
|
16554
16545
|
"use strict";
|
|
16555
|
-
init_definition_paths();
|
|
16556
16546
|
init_agents();
|
|
16547
|
+
init_definition_paths();
|
|
16557
16548
|
init_state_backend();
|
|
16558
16549
|
loadLiveAgent = async (ctx, profile) => {
|
|
16559
16550
|
const agent = String(ctx.args.agent ?? ctx.data.jobAgent ?? "").trim();
|
|
@@ -18593,10 +18584,7 @@ function parseSetCookie(value, hostname) {
|
|
|
18593
18584
|
const attributes = /* @__PURE__ */ new Map();
|
|
18594
18585
|
for (const part of parts) {
|
|
18595
18586
|
const index = part.indexOf("=");
|
|
18596
|
-
attributes.set(
|
|
18597
|
-
(index < 0 ? part : part.slice(0, index)).toLowerCase(),
|
|
18598
|
-
index < 0 ? "" : part.slice(index + 1)
|
|
18599
|
-
);
|
|
18587
|
+
attributes.set((index < 0 ? part : part.slice(0, index)).toLowerCase(), index < 0 ? "" : part.slice(index + 1));
|
|
18600
18588
|
}
|
|
18601
18589
|
const sameSite = attributes.get("samesite")?.toLowerCase();
|
|
18602
18590
|
return {
|
|
@@ -18721,11 +18709,9 @@ function mergeStorageStates(existingPath, nextPath) {
|
|
|
18721
18709
|
}
|
|
18722
18710
|
origins.set(entry.origin, { origin: entry.origin, localStorage: [...localStorage.values()] });
|
|
18723
18711
|
}
|
|
18724
|
-
fs49.writeFileSync(
|
|
18725
|
-
|
|
18726
|
-
|
|
18727
|
-
{ mode: 384 }
|
|
18728
|
-
);
|
|
18712
|
+
fs49.writeFileSync(nextPath, JSON.stringify({ cookies: [...cookies.values()], origins: [...origins.values()] }), {
|
|
18713
|
+
mode: 384
|
|
18714
|
+
});
|
|
18729
18715
|
}
|
|
18730
18716
|
function configurePlaywright(profile, storageStatePath) {
|
|
18731
18717
|
const playwright = profile.claudeCode.mcpServers.find((server) => server.name === "playwright");
|
|
@@ -20741,12 +20727,19 @@ function buildTickChildEnv(parent, force, loaded) {
|
|
|
20741
20727
|
"GH_PAT",
|
|
20742
20728
|
"GITHUB_TOKEN",
|
|
20743
20729
|
"GITHUB_REPOSITORY",
|
|
20730
|
+
"GITHUB_REPOSITORY_ID",
|
|
20731
|
+
"GITHUB_REPOSITORY_OWNER_ID",
|
|
20744
20732
|
"GITHUB_REF",
|
|
20745
20733
|
"GITHUB_SHA",
|
|
20746
20734
|
"GITHUB_RUN_ID",
|
|
20747
20735
|
"GITHUB_RUN_ATTEMPT",
|
|
20736
|
+
"GITHUB_SERVER_URL",
|
|
20737
|
+
"GITHUB_EVENT_NAME",
|
|
20748
20738
|
"GITHUB_WORKFLOW",
|
|
20739
|
+
"GITHUB_WORKFLOW_REF",
|
|
20749
20740
|
"GITHUB_ACTIONS",
|
|
20741
|
+
"ACTIONS_ID_TOKEN_REQUEST_URL",
|
|
20742
|
+
"ACTIONS_ID_TOKEN_REQUEST_TOKEN",
|
|
20750
20743
|
"CI",
|
|
20751
20744
|
"KODY_DRY_RUN",
|
|
20752
20745
|
"KODY_NO_COMMIT"
|
|
@@ -20980,25 +20973,6 @@ var init_runTickScript = __esm({
|
|
|
20980
20973
|
}
|
|
20981
20974
|
});
|
|
20982
20975
|
|
|
20983
|
-
// src/scripts/saveManagedGoalState.ts
|
|
20984
|
-
var saveManagedGoalState;
|
|
20985
|
-
var init_saveManagedGoalState = __esm({
|
|
20986
|
-
"src/scripts/saveManagedGoalState.ts"() {
|
|
20987
|
-
"use strict";
|
|
20988
|
-
init_state2();
|
|
20989
|
-
saveManagedGoalState = async (ctx) => {
|
|
20990
|
-
ctx.skipAgent = true;
|
|
20991
|
-
const goal = ctx.data.goal;
|
|
20992
|
-
if (!goal?.raw) return;
|
|
20993
|
-
const original = typeof ctx.data.goalOriginalStateText === "string" ? ctx.data.goalOriginalStateText : "";
|
|
20994
|
-
const changedBeforeTimestamp = original !== serializeGoalState(goal.raw);
|
|
20995
|
-
const updated = changedBeforeTimestamp ? { ...goal.raw, updatedAt: nowIso() } : goal.raw;
|
|
20996
|
-
ctx.data.goalPersistState = updated;
|
|
20997
|
-
ctx.data.goalPersistChanged = original !== serializeGoalState(updated);
|
|
20998
|
-
};
|
|
20999
|
-
}
|
|
21000
|
-
});
|
|
21001
|
-
|
|
21002
20976
|
// src/scripts/saveLiveAgentState.ts
|
|
21003
20977
|
var saveLiveAgentState;
|
|
21004
20978
|
var init_saveLiveAgentState = __esm({
|
|
@@ -21034,6 +21008,25 @@ var init_saveLiveAgentState = __esm({
|
|
|
21034
21008
|
}
|
|
21035
21009
|
});
|
|
21036
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
|
+
|
|
21037
21030
|
// src/scripts/setCommentTarget.ts
|
|
21038
21031
|
var setCommentTarget;
|
|
21039
21032
|
var init_setCommentTarget = __esm({
|
|
@@ -22382,8 +22375,8 @@ var init_scripts = __esm({
|
|
|
22382
22375
|
init_runScheduledImplementationTick();
|
|
22383
22376
|
init_runSimpleCapabilityScript();
|
|
22384
22377
|
init_runTickScript();
|
|
22385
|
-
init_saveManagedGoalState();
|
|
22386
22378
|
init_saveLiveAgentState();
|
|
22379
|
+
init_saveManagedGoalState();
|
|
22387
22380
|
init_saveTaskState();
|
|
22388
22381
|
init_setCommentTarget();
|
|
22389
22382
|
init_setLifecycleLabel();
|
|
@@ -24229,9 +24222,10 @@ function enforceCapabilityOutputContract(capability, result) {
|
|
|
24229
24222
|
if (result.exitCode !== 0 || !schema) {
|
|
24230
24223
|
return result;
|
|
24231
24224
|
}
|
|
24225
|
+
const contractOutput = result.capabilityOutput === void 0 && typeof result.prUrl === "string" && result.prUrl.trim().length > 0 ? { prUrl: result.prUrl } : result.capabilityOutput;
|
|
24232
24226
|
try {
|
|
24233
|
-
validateCapabilityContractOutput(schema,
|
|
24234
|
-
return result;
|
|
24227
|
+
validateCapabilityContractOutput(schema, contractOutput);
|
|
24228
|
+
return contractOutput === result.capabilityOutput ? result : { ...result, capabilityOutput: contractOutput };
|
|
24235
24229
|
} catch (error) {
|
|
24236
24230
|
const reason = error instanceof Error ? error.message : String(error);
|
|
24237
24231
|
const blocked = {
|
|
@@ -25585,6 +25579,7 @@ function makeRunId(sessionId, suffix) {
|
|
|
25585
25579
|
}
|
|
25586
25580
|
|
|
25587
25581
|
// src/chat/session-store.ts
|
|
25582
|
+
init_kody_api_client();
|
|
25588
25583
|
init_convex_client();
|
|
25589
25584
|
import { anyApi as anyApi2 } from "convex/server";
|
|
25590
25585
|
function currentEpochTurns(result) {
|
|
@@ -25612,11 +25607,12 @@ function createSessionStore(opts) {
|
|
|
25612
25607
|
warn: (message) => process.stderr.write(`[kody:chat:store] ${message}
|
|
25613
25608
|
`)
|
|
25614
25609
|
};
|
|
25615
|
-
const
|
|
25616
|
-
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 ?? "";
|
|
25617
25613
|
if (!client || !tenantId2) {
|
|
25618
25614
|
throw new Error(
|
|
25619
|
-
"Canonical
|
|
25615
|
+
"Canonical conversation storage requires GitHub Actions identity or direct Kody backend credentials"
|
|
25620
25616
|
);
|
|
25621
25617
|
}
|
|
25622
25618
|
logger.info(`conversation ${opts.sessionId}: using canonical Convex store (tenant ${tenantId2})`);
|
|
@@ -27194,6 +27190,10 @@ function ensurePackageManagerInstalled(pm, cwd) {
|
|
|
27194
27190
|
return shellOut("npm", ["install", "-g", spec], cwd);
|
|
27195
27191
|
}
|
|
27196
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
|
+
}
|
|
27197
27197
|
const ensureCode = ensurePackageManagerInstalled(pm, cwd);
|
|
27198
27198
|
if (ensureCode !== 0) return ensureCode;
|
|
27199
27199
|
const args = {
|
|
@@ -29287,8 +29287,137 @@ init_repoWorkspace();
|
|
|
29287
29287
|
import * as path58 from "path";
|
|
29288
29288
|
import { createInterface as createInterface2 } from "readline";
|
|
29289
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
|
+
|
|
29290
29419
|
// src/terminal/brain-terminal-session.ts
|
|
29291
|
-
import { createHash as
|
|
29420
|
+
import { createHash as createHash10 } from "crypto";
|
|
29292
29421
|
var MAX_CAPTURE_CHARS = 2e5;
|
|
29293
29422
|
function requiredIdentifier(value, name, max = 240) {
|
|
29294
29423
|
if (typeof value !== "string" || !value.trim() || value.length > max) {
|
|
@@ -29369,7 +29498,7 @@ function parseBrainTerminalCommand(value) {
|
|
|
29369
29498
|
}
|
|
29370
29499
|
}
|
|
29371
29500
|
function sessionName(id) {
|
|
29372
|
-
return `kody_${
|
|
29501
|
+
return `kody_${createHash10("sha256").update(id).digest("hex").slice(0, 32)}`;
|
|
29373
29502
|
}
|
|
29374
29503
|
function stateEvent(session) {
|
|
29375
29504
|
return {
|
|
@@ -29431,12 +29560,7 @@ var BrainTerminalSessionAgent = class {
|
|
|
29431
29560
|
await this.persist(session);
|
|
29432
29561
|
let started;
|
|
29433
29562
|
try {
|
|
29434
|
-
started = await this.dependencies.runtime.start(
|
|
29435
|
-
session.sessionName,
|
|
29436
|
-
session.cwd,
|
|
29437
|
-
session.cols,
|
|
29438
|
-
session.rows
|
|
29439
|
-
);
|
|
29563
|
+
started = await this.dependencies.runtime.start(session.sessionName, session.cwd, session.cols, session.rows);
|
|
29440
29564
|
} catch (cause) {
|
|
29441
29565
|
session = { ...session, state: "failed", updatedAt: this.now() };
|
|
29442
29566
|
await this.persist(session);
|
|
@@ -29595,155 +29719,6 @@ var BrainTerminalSessionAgent = class {
|
|
|
29595
29719
|
}
|
|
29596
29720
|
};
|
|
29597
29721
|
|
|
29598
|
-
// src/terminal/brain-terminal-adapters.ts
|
|
29599
|
-
import { createHash as createHash10, randomBytes as randomBytes2 } from "crypto";
|
|
29600
|
-
import { mkdir, readFile, rename, writeFile as writeFile2 } from "fs/promises";
|
|
29601
|
-
import * as path57 from "path";
|
|
29602
|
-
import { spawn as spawn9 } from "child_process";
|
|
29603
|
-
function runTerminalCommand(command, args, input) {
|
|
29604
|
-
return new Promise((resolve24, reject) => {
|
|
29605
|
-
const child = spawn9(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
29606
|
-
const stdout = [];
|
|
29607
|
-
const stderr = [];
|
|
29608
|
-
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
29609
|
-
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
29610
|
-
child.on("error", reject);
|
|
29611
|
-
child.on("close", (code) => {
|
|
29612
|
-
resolve24({
|
|
29613
|
-
code: code ?? 1,
|
|
29614
|
-
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
29615
|
-
stderr: Buffer.concat(stderr).toString("utf8")
|
|
29616
|
-
});
|
|
29617
|
-
});
|
|
29618
|
-
if (input !== void 0) child.stdin.end(input);
|
|
29619
|
-
else child.stdin.end();
|
|
29620
|
-
});
|
|
29621
|
-
}
|
|
29622
|
-
function storeKey(id) {
|
|
29623
|
-
return createHash10("sha256").update(id).digest("hex");
|
|
29624
|
-
}
|
|
29625
|
-
function isStoredSession(value) {
|
|
29626
|
-
if (!value || typeof value !== "object") return false;
|
|
29627
|
-
const session = value;
|
|
29628
|
-
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";
|
|
29629
|
-
}
|
|
29630
|
-
var FileBrainTerminalMetadataStore = class {
|
|
29631
|
-
constructor(root) {
|
|
29632
|
-
this.root = root;
|
|
29633
|
-
}
|
|
29634
|
-
root;
|
|
29635
|
-
file(id) {
|
|
29636
|
-
return path57.join(this.root, `${storeKey(id)}.json`);
|
|
29637
|
-
}
|
|
29638
|
-
async read(id) {
|
|
29639
|
-
try {
|
|
29640
|
-
const parsed = JSON.parse(await readFile(this.file(id), "utf8"));
|
|
29641
|
-
if (!isStoredSession(parsed) || parsed.id !== id) {
|
|
29642
|
-
throw new Error("stored terminal session is invalid");
|
|
29643
|
-
}
|
|
29644
|
-
return parsed;
|
|
29645
|
-
} catch (error) {
|
|
29646
|
-
if (error.code === "ENOENT") return null;
|
|
29647
|
-
throw error;
|
|
29648
|
-
}
|
|
29649
|
-
}
|
|
29650
|
-
async write(session) {
|
|
29651
|
-
await mkdir(this.root, { recursive: true, mode: 448 });
|
|
29652
|
-
const target = this.file(session.id);
|
|
29653
|
-
const temporary = `${target}.${process.pid}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
29654
|
-
await writeFile2(temporary, `${JSON.stringify(session)}
|
|
29655
|
-
`, { mode: 384 });
|
|
29656
|
-
await rename(temporary, target);
|
|
29657
|
-
}
|
|
29658
|
-
};
|
|
29659
|
-
function commandError(action, result) {
|
|
29660
|
-
const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`;
|
|
29661
|
-
return new Error(`tmux ${action} failed: ${detail.slice(0, 500)}`);
|
|
29662
|
-
}
|
|
29663
|
-
var TmuxBrainTerminalRuntime = class {
|
|
29664
|
-
constructor(run = runTerminalCommand) {
|
|
29665
|
-
this.run = run;
|
|
29666
|
-
}
|
|
29667
|
-
run;
|
|
29668
|
-
async tmux(action, args, input) {
|
|
29669
|
-
const result = await this.run("tmux", args, input);
|
|
29670
|
-
if (result.code !== 0) throw commandError(action, result);
|
|
29671
|
-
return result;
|
|
29672
|
-
}
|
|
29673
|
-
async start(sessionName2, cwd, cols, rows) {
|
|
29674
|
-
await this.tmux("start", [
|
|
29675
|
-
"new-session",
|
|
29676
|
-
"-d",
|
|
29677
|
-
"-s",
|
|
29678
|
-
sessionName2,
|
|
29679
|
-
"-x",
|
|
29680
|
-
String(cols),
|
|
29681
|
-
"-y",
|
|
29682
|
-
String(rows),
|
|
29683
|
-
"-c",
|
|
29684
|
-
cwd,
|
|
29685
|
-
"/bin/bash",
|
|
29686
|
-
"-l"
|
|
29687
|
-
]);
|
|
29688
|
-
await this.tmux("configure", ["set-option", "-t", sessionName2, "status", "off"]);
|
|
29689
|
-
await this.tmux("configure", ["set-option", "-t", sessionName2, "history-limit", "50000"]);
|
|
29690
|
-
await this.tmux("configure", ["set-option", "-w", "-t", sessionName2, "remain-on-exit", "on"]);
|
|
29691
|
-
const inspected = await this.inspect(sessionName2);
|
|
29692
|
-
if (!inspected.alive || inspected.processId === null) throw new Error("tmux terminal did not start");
|
|
29693
|
-
return { processId: inspected.processId };
|
|
29694
|
-
}
|
|
29695
|
-
async inspect(sessionName2) {
|
|
29696
|
-
const result = await this.run("tmux", [
|
|
29697
|
-
"list-panes",
|
|
29698
|
-
"-t",
|
|
29699
|
-
sessionName2,
|
|
29700
|
-
"-F",
|
|
29701
|
-
"#{pane_dead}:#{pane_pid}"
|
|
29702
|
-
]);
|
|
29703
|
-
if (result.code !== 0) return { alive: false, processId: null };
|
|
29704
|
-
const [dead, pid] = result.stdout.trim().split(":");
|
|
29705
|
-
const processId = Number(pid);
|
|
29706
|
-
return {
|
|
29707
|
-
alive: dead === "0" && Number.isInteger(processId) && processId > 0,
|
|
29708
|
-
processId: Number.isInteger(processId) && processId > 0 ? processId : null
|
|
29709
|
-
};
|
|
29710
|
-
}
|
|
29711
|
-
async capture(sessionName2) {
|
|
29712
|
-
const alternate = await this.run("tmux", [
|
|
29713
|
-
"display-message",
|
|
29714
|
-
"-p",
|
|
29715
|
-
"-t",
|
|
29716
|
-
sessionName2,
|
|
29717
|
-
"#{alternate_on}"
|
|
29718
|
-
]);
|
|
29719
|
-
if (alternate.code !== 0) throw commandError("inspect screen", alternate);
|
|
29720
|
-
const args = alternate.stdout.trim() === "1" ? ["capture-pane", "-p", "-e", "-t", sessionName2] : ["capture-pane", "-p", "-e", "-J", "-S", "-50000", "-t", sessionName2];
|
|
29721
|
-
return (await this.tmux("capture", args)).stdout;
|
|
29722
|
-
}
|
|
29723
|
-
async input(sessionName2, data) {
|
|
29724
|
-
const bufferName = `kody_${randomBytes2(8).toString("hex")}`;
|
|
29725
|
-
await this.tmux("load input", ["load-buffer", "-b", bufferName, "-"], data);
|
|
29726
|
-
await this.tmux("paste input", ["paste-buffer", "-d", "-b", bufferName, "-t", sessionName2]);
|
|
29727
|
-
}
|
|
29728
|
-
async resize(sessionName2, cols, rows) {
|
|
29729
|
-
await this.tmux("resize", [
|
|
29730
|
-
"resize-window",
|
|
29731
|
-
"-t",
|
|
29732
|
-
sessionName2,
|
|
29733
|
-
"-x",
|
|
29734
|
-
String(cols),
|
|
29735
|
-
"-y",
|
|
29736
|
-
String(rows)
|
|
29737
|
-
]);
|
|
29738
|
-
}
|
|
29739
|
-
async stop(sessionName2) {
|
|
29740
|
-
const result = await this.run("tmux", ["kill-session", "-t", sessionName2]);
|
|
29741
|
-
if (result.code !== 0 && !/can't find session|no server running/i.test(result.stderr)) {
|
|
29742
|
-
throw commandError("stop", result);
|
|
29743
|
-
}
|
|
29744
|
-
}
|
|
29745
|
-
};
|
|
29746
|
-
|
|
29747
29722
|
// src/servers/brain-terminal-agent.ts
|
|
29748
29723
|
var DEFAULT_POLL_INTERVAL_MS = 150;
|
|
29749
29724
|
function writeEvent(output, event) {
|
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"
|
|
@@ -57,10 +61,6 @@
|
|
|
57
61
|
"engines": {
|
|
58
62
|
"node": ">=22"
|
|
59
63
|
},
|
|
60
|
-
"repository": {
|
|
61
|
-
"type": "git",
|
|
62
|
-
"url": "git+https://github.com/aharonyaircohen/kody-engine.git"
|
|
63
|
-
},
|
|
64
64
|
"homepage": "https://github.com/aharonyaircohen/kody-engine",
|
|
65
65
|
"bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
|
|
66
66
|
}
|