@kody-ade/kody-engine 0.4.251 → 0.4.253
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/agent-actions/agent-factory/prompt.md +2 -2
- package/dist/agent-actions/types.ts +1 -1
- package/dist/bin/kody.js +781 -405
- package/kody.config.schema.json +1 -1
- package/package.json +23 -22
package/dist/bin/kody.js
CHANGED
|
@@ -15,7 +15,7 @@ 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.253",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative agentAction profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -402,8 +402,8 @@ function parseStateRepo(config) {
|
|
|
402
402
|
}
|
|
403
403
|
function stateRepoPath(config, filePath) {
|
|
404
404
|
const state = resolveStateRepoConfig(config);
|
|
405
|
-
const
|
|
406
|
-
return path2.posix.join(state.path,
|
|
405
|
+
const relative3 = normalizeStatePath(filePath, "state file path");
|
|
406
|
+
return path2.posix.join(state.path, relative3);
|
|
407
407
|
}
|
|
408
408
|
function apiPath(config, targetPath) {
|
|
409
409
|
const parsed = parseStateRepo(config);
|
|
@@ -611,11 +611,11 @@ function parseJobsConfig(raw) {
|
|
|
611
611
|
if (!raw || typeof raw !== "object") return void 0;
|
|
612
612
|
const r = raw;
|
|
613
613
|
const out = {};
|
|
614
|
-
if (r.stateBackend === "contents-api"
|
|
614
|
+
if (r.stateBackend === "contents-api") {
|
|
615
615
|
out.stateBackend = r.stateBackend;
|
|
616
616
|
} else if (typeof r.stateBackend === "string") {
|
|
617
617
|
throw new Error(
|
|
618
|
-
`kody.config.json: jobs.stateBackend must be "contents-api"
|
|
618
|
+
`kody.config.json: jobs.stateBackend must be "contents-api"; local-file is not a supported durable storage mode`
|
|
619
619
|
);
|
|
620
620
|
}
|
|
621
621
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
@@ -936,17 +936,28 @@ var init_format = __esm({
|
|
|
936
936
|
// src/runtimePaths.ts
|
|
937
937
|
var runtimePaths_exports = {};
|
|
938
938
|
__export(runtimePaths_exports, {
|
|
939
|
+
agentRunDir: () => agentRunDir,
|
|
940
|
+
lastRunLogPath: () => lastRunLogPath,
|
|
941
|
+
runtimeDirForCwd: () => runtimeDirForCwd,
|
|
939
942
|
runtimeStatePath: () => runtimeStatePath
|
|
940
943
|
});
|
|
941
|
-
import
|
|
942
|
-
import os2 from "os";
|
|
943
|
-
import path4 from "path";
|
|
944
|
-
function
|
|
945
|
-
|
|
944
|
+
import { createHash } from "crypto";
|
|
945
|
+
import * as os2 from "os";
|
|
946
|
+
import * as path4 from "path";
|
|
947
|
+
function runtimeDirForCwd(cwd, ...parts) {
|
|
948
|
+
const key = createHash("sha256").update(path4.resolve(cwd)).digest("hex").slice(0, 16);
|
|
949
|
+
return path4.join(os2.tmpdir(), "kody-engine", key, ...parts);
|
|
946
950
|
}
|
|
947
|
-
function runtimeStatePath(cwd, ...
|
|
948
|
-
const
|
|
949
|
-
|
|
951
|
+
function runtimeStatePath(cwd, ...parts) {
|
|
952
|
+
const configuredRoot = process.env.KODY_RUNTIME_DIR?.trim();
|
|
953
|
+
const base = configuredRoot ? path4.resolve(configuredRoot) : runtimeDirForCwd(cwd);
|
|
954
|
+
return path4.join(base, ...parts);
|
|
955
|
+
}
|
|
956
|
+
function agentRunDir(cwd) {
|
|
957
|
+
return runtimeStatePath(cwd, "agent-runs");
|
|
958
|
+
}
|
|
959
|
+
function lastRunLogPath(cwd) {
|
|
960
|
+
return path4.join(agentRunDir(cwd), "last-run.jsonl");
|
|
950
961
|
}
|
|
951
962
|
var init_runtimePaths = __esm({
|
|
952
963
|
"src/runtimePaths.ts"() {
|
|
@@ -963,7 +974,7 @@ __export(events_exports, {
|
|
|
963
974
|
readEvents: () => readEvents,
|
|
964
975
|
resolveRunId: () => resolveRunId
|
|
965
976
|
});
|
|
966
|
-
import * as
|
|
977
|
+
import * as crypto from "crypto";
|
|
967
978
|
import * as fs3 from "fs";
|
|
968
979
|
import * as path5 from "path";
|
|
969
980
|
function resolveRunId() {
|
|
@@ -976,7 +987,7 @@ function resolveRunId() {
|
|
|
976
987
|
const attempt = process.env.GITHUB_RUN_ATTEMPT ?? "1";
|
|
977
988
|
cachedRunId = `gh-${process.env.GITHUB_RUN_ID}-${attempt}`;
|
|
978
989
|
} else {
|
|
979
|
-
cachedRunId = `${Date.now().toString(36)}-${
|
|
990
|
+
cachedRunId = `${Date.now().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
|
|
980
991
|
}
|
|
981
992
|
process.env.KODY_RUN_ID = cachedRunId;
|
|
982
993
|
return cachedRunId;
|
|
@@ -1042,7 +1053,7 @@ var init_events = __esm({
|
|
|
1042
1053
|
// src/verify.ts
|
|
1043
1054
|
import { spawn } from "child_process";
|
|
1044
1055
|
function runCommand(command, cwd) {
|
|
1045
|
-
return new Promise((
|
|
1056
|
+
return new Promise((resolve8) => {
|
|
1046
1057
|
const start = Date.now();
|
|
1047
1058
|
const child = spawn(command, {
|
|
1048
1059
|
cwd,
|
|
@@ -1071,11 +1082,11 @@ function runCommand(command, cwd) {
|
|
|
1071
1082
|
child.on("exit", (code) => {
|
|
1072
1083
|
clearTimeout(timer);
|
|
1073
1084
|
const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
|
|
1074
|
-
|
|
1085
|
+
resolve8({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
|
|
1075
1086
|
});
|
|
1076
1087
|
child.on("error", (err) => {
|
|
1077
1088
|
clearTimeout(timer);
|
|
1078
|
-
|
|
1089
|
+
resolve8({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
|
|
1079
1090
|
});
|
|
1080
1091
|
});
|
|
1081
1092
|
}
|
|
@@ -1446,7 +1457,7 @@ var init_agent_responsibilityFolders = __esm({
|
|
|
1446
1457
|
|
|
1447
1458
|
// src/companyStore.ts
|
|
1448
1459
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1449
|
-
import * as
|
|
1460
|
+
import * as crypto2 from "crypto";
|
|
1450
1461
|
import * as fs5 from "fs";
|
|
1451
1462
|
import * as os3 from "os";
|
|
1452
1463
|
import * as path7 from "path";
|
|
@@ -1522,7 +1533,7 @@ function cacheRoot() {
|
|
|
1522
1533
|
return process.env[CACHE_ENV]?.trim() || path7.join(os3.homedir(), ".cache", "kody", "company-store");
|
|
1523
1534
|
}
|
|
1524
1535
|
function cacheKey(repo, ref) {
|
|
1525
|
-
return
|
|
1536
|
+
return crypto2.createHash("sha256").update(`${repo}#${ref}`).digest("hex").slice(0, 24);
|
|
1526
1537
|
}
|
|
1527
1538
|
function runGit(args) {
|
|
1528
1539
|
execFileSync2("git", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
@@ -2344,7 +2355,7 @@ var init_repoWorkspace = __esm({
|
|
|
2344
2355
|
defaultCloneRepo = (repo, token, dir) => {
|
|
2345
2356
|
fs7.mkdirSync(path9.dirname(dir), { recursive: true });
|
|
2346
2357
|
const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
|
|
2347
|
-
return new Promise((
|
|
2358
|
+
return new Promise((resolve8, reject) => {
|
|
2348
2359
|
const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
|
|
2349
2360
|
stdio: "inherit"
|
|
2350
2361
|
});
|
|
@@ -2360,7 +2371,7 @@ var init_repoWorkspace = __esm({
|
|
|
2360
2371
|
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
2361
2372
|
} catch {
|
|
2362
2373
|
}
|
|
2363
|
-
|
|
2374
|
+
resolve8();
|
|
2364
2375
|
});
|
|
2365
2376
|
child.on("error", reject);
|
|
2366
2377
|
});
|
|
@@ -2485,7 +2496,7 @@ function stripAgentSecrets(env) {
|
|
|
2485
2496
|
return out;
|
|
2486
2497
|
}
|
|
2487
2498
|
async function runAgent(opts) {
|
|
2488
|
-
const ndjsonDir = opts.ndjsonDir ??
|
|
2499
|
+
const ndjsonDir = opts.ndjsonDir ?? agentRunDir(opts.cwd);
|
|
2489
2500
|
fs8.mkdirSync(ndjsonDir, { recursive: true });
|
|
2490
2501
|
const ndjsonPath = path10.join(ndjsonDir, "last-run.jsonl");
|
|
2491
2502
|
const env = stripAgentSecrets({
|
|
@@ -2543,6 +2554,7 @@ async function runAgent(opts) {
|
|
|
2543
2554
|
permissionMode: opts.permissionModeOverride ?? "acceptEdits",
|
|
2544
2555
|
env
|
|
2545
2556
|
};
|
|
2557
|
+
const additionalDirectories = new Set(opts.additionalDirectories ?? []);
|
|
2546
2558
|
const mcpEntries = [];
|
|
2547
2559
|
if (opts.mcpServers && opts.mcpServers.length > 0) {
|
|
2548
2560
|
for (const s of opts.mcpServers) {
|
|
@@ -2591,7 +2603,10 @@ async function runAgent(opts) {
|
|
|
2591
2603
|
});
|
|
2592
2604
|
mcpEntries.push(["kody-fetch-repo", fetchServer]);
|
|
2593
2605
|
queryOptions.allowedTools.push("mcp__kody-fetch-repo__fetch_repo");
|
|
2594
|
-
|
|
2606
|
+
additionalDirectories.add(opts.reposRoot);
|
|
2607
|
+
}
|
|
2608
|
+
if (additionalDirectories.size > 0) {
|
|
2609
|
+
queryOptions.additionalDirectories = [...additionalDirectories];
|
|
2595
2610
|
}
|
|
2596
2611
|
if (mcpEntries.length > 0) {
|
|
2597
2612
|
queryOptions.mcpServers = Object.fromEntries(mcpEntries);
|
|
@@ -2646,10 +2661,10 @@ async function runAgent(opts) {
|
|
|
2646
2661
|
let timer;
|
|
2647
2662
|
let next;
|
|
2648
2663
|
if (turnTimeoutMs > 0) {
|
|
2649
|
-
const timeoutPromise = new Promise((
|
|
2664
|
+
const timeoutPromise = new Promise((resolve8) => {
|
|
2650
2665
|
timer = setTimeout(() => {
|
|
2651
2666
|
timedOut = true;
|
|
2652
|
-
|
|
2667
|
+
resolve8({ done: true, value: void 0 });
|
|
2653
2668
|
}, turnTimeoutMs);
|
|
2654
2669
|
});
|
|
2655
2670
|
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
@@ -2841,6 +2856,7 @@ var init_agent = __esm({
|
|
|
2841
2856
|
init_claudeBinary();
|
|
2842
2857
|
init_config();
|
|
2843
2858
|
init_format();
|
|
2859
|
+
init_runtimePaths();
|
|
2844
2860
|
DEFAULT_ALLOWED_TOOLS = ["Bash", "Edit", "Read", "Write", "Glob", "Grep"];
|
|
2845
2861
|
DEFAULT_TURN_TIMEOUT_MS = 6e5;
|
|
2846
2862
|
MAX_CONNECTION_RETRIES = 2;
|
|
@@ -2851,6 +2867,7 @@ var init_agent = __esm({
|
|
|
2851
2867
|
"ANTHROPIC_API_KEY",
|
|
2852
2868
|
"ANTHROPIC_AUTH_TOKEN",
|
|
2853
2869
|
"ANTHROPIC_BASE_URL",
|
|
2870
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
2854
2871
|
"GH_TOKEN",
|
|
2855
2872
|
"GITHUB_TOKEN"
|
|
2856
2873
|
]);
|
|
@@ -2860,10 +2877,11 @@ var init_agent = __esm({
|
|
|
2860
2877
|
// src/task-artifacts.ts
|
|
2861
2878
|
import fs9 from "fs";
|
|
2862
2879
|
import path11 from "path";
|
|
2880
|
+
import posixPath from "path/posix";
|
|
2863
2881
|
function prepareTaskArtifactsDir(cwd, taskId) {
|
|
2864
2882
|
const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
2865
|
-
const
|
|
2866
|
-
const
|
|
2883
|
+
const absDir = runtimeStatePath(cwd, "task-artifacts", safeId);
|
|
2884
|
+
const relDir = absDir;
|
|
2867
2885
|
fs9.mkdirSync(absDir, { recursive: true });
|
|
2868
2886
|
return { taskId: safeId, absDir, relDir };
|
|
2869
2887
|
}
|
|
@@ -2880,11 +2898,30 @@ function verifyTaskArtifacts(absDir) {
|
|
|
2880
2898
|
}
|
|
2881
2899
|
return missing;
|
|
2882
2900
|
}
|
|
2901
|
+
function taskArtifactStatePath(taskId, file) {
|
|
2902
|
+
return posixPath.join("tasks", taskId, file);
|
|
2903
|
+
}
|
|
2904
|
+
function persistTaskArtifactsToState(config, cwd, artifacts) {
|
|
2905
|
+
for (const file of TASK_ARTIFACT_FILES) {
|
|
2906
|
+
const full = path11.join(artifacts.absDir, file);
|
|
2907
|
+
if (!fs9.existsSync(full)) continue;
|
|
2908
|
+
const stat = fs9.statSync(full);
|
|
2909
|
+
if (!stat.isFile() || stat.size === 0) continue;
|
|
2910
|
+
const content = fs9.readFileSync(full, "utf-8");
|
|
2911
|
+
upsertStateText(
|
|
2912
|
+
config,
|
|
2913
|
+
cwd,
|
|
2914
|
+
taskArtifactStatePath(artifacts.taskId, file),
|
|
2915
|
+
content,
|
|
2916
|
+
`task artifacts: ${artifacts.taskId}`
|
|
2917
|
+
);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2883
2920
|
function taskArtifactsPromptAddendum(opts) {
|
|
2884
2921
|
return [
|
|
2885
2922
|
"## Per-task artifacts (REQUIRED before your final response)",
|
|
2886
2923
|
"",
|
|
2887
|
-
`Before you finish, write these four files into \`${opts.relDir}/\`:`,
|
|
2924
|
+
`Before you finish, write these four local temp files into \`${opts.relDir}/\`:`,
|
|
2888
2925
|
"",
|
|
2889
2926
|
`1. **context.json** \u2014 task header. Shape:`,
|
|
2890
2927
|
" ```json",
|
|
@@ -2898,14 +2935,14 @@ function taskArtifactsPromptAddendum(opts) {
|
|
|
2898
2935
|
` "prUrl": "<url or null>",`,
|
|
2899
2936
|
` "runUrl": "<url or null>",`,
|
|
2900
2937
|
` "filesTouched": ["path/from/repo/root.ts", ...],`,
|
|
2901
|
-
` "sessionLog": "
|
|
2938
|
+
` "sessionLog": "sessions/<id>.jsonl",`,
|
|
2902
2939
|
` "startedAt": "<ISO>",`,
|
|
2903
2940
|
` "finishedAt": "<ISO>"`,
|
|
2904
2941
|
" }",
|
|
2905
2942
|
" ```",
|
|
2906
2943
|
"",
|
|
2907
2944
|
`2. **memory-recs.json** \u2014 array of sticky-note candidates worth promoting`,
|
|
2908
|
-
` to long-term
|
|
2945
|
+
` to long-term state-repo \`memory/\`. Each item:`,
|
|
2909
2946
|
" ```json",
|
|
2910
2947
|
" {",
|
|
2911
2948
|
` "type": "preference" | "decision" | "lesson",`,
|
|
@@ -2942,6 +2979,8 @@ var TASK_ARTIFACT_FILES;
|
|
|
2942
2979
|
var init_task_artifacts = __esm({
|
|
2943
2980
|
"src/task-artifacts.ts"() {
|
|
2944
2981
|
"use strict";
|
|
2982
|
+
init_runtimePaths();
|
|
2983
|
+
init_stateRepo();
|
|
2945
2984
|
TASK_ARTIFACT_FILES = ["context.json", "memory-recs.json", "followups.json", "handoff-notes.md"];
|
|
2946
2985
|
}
|
|
2947
2986
|
});
|
|
@@ -4708,7 +4747,7 @@ function sortByRecency(pages) {
|
|
|
4708
4747
|
function formatBlock(pages) {
|
|
4709
4748
|
if (pages.length === 0) return "";
|
|
4710
4749
|
const lines = [
|
|
4711
|
-
"# Project memory (
|
|
4750
|
+
"# Project memory (state repo `memory/`)",
|
|
4712
4751
|
"",
|
|
4713
4752
|
"Pages from prior memorize ticks. Treat as advisory context \u2014 confirm against the codebase before acting.",
|
|
4714
4753
|
""
|
|
@@ -5693,13 +5732,13 @@ function commitAndPush(branch, agentMessage, cwd) {
|
|
|
5693
5732
|
}
|
|
5694
5733
|
return { committed: true, pushed: false, sha, message, pushError: pushResult.reason };
|
|
5695
5734
|
}
|
|
5696
|
-
function hasCommitsAhead(branch,
|
|
5735
|
+
function hasCommitsAhead(branch, defaultBranch, cwd) {
|
|
5697
5736
|
try {
|
|
5698
|
-
const out = git(["rev-list", "--count", `origin/${
|
|
5737
|
+
const out = git(["rev-list", "--count", `origin/${defaultBranch}..${branch}`], cwd);
|
|
5699
5738
|
return parseInt(out, 10) > 0;
|
|
5700
5739
|
} catch {
|
|
5701
5740
|
try {
|
|
5702
|
-
const out = git(["rev-list", "--count", `${
|
|
5741
|
+
const out = git(["rev-list", "--count", `${defaultBranch}..${branch}`], cwd);
|
|
5703
5742
|
return parseInt(out, 10) > 0;
|
|
5704
5743
|
} catch {
|
|
5705
5744
|
return false;
|
|
@@ -5725,7 +5764,7 @@ var init_commit = __esm({
|
|
|
5725
5764
|
"dist/",
|
|
5726
5765
|
"build/"
|
|
5727
5766
|
];
|
|
5728
|
-
ALLOWED_PATH_PREFIXES = [
|
|
5767
|
+
ALLOWED_PATH_PREFIXES = [];
|
|
5729
5768
|
FORBIDDEN_PATH_EXACT = /* @__PURE__ */ new Set([".env", ".kody-pip-requirements.txt", "kody.config.json"]);
|
|
5730
5769
|
FORBIDDEN_PATH_SUFFIXES = [".log"];
|
|
5731
5770
|
CONVENTIONAL_PREFIXES = [
|
|
@@ -6129,10 +6168,10 @@ var init_state2 = __esm({
|
|
|
6129
6168
|
"use strict";
|
|
6130
6169
|
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
6131
6170
|
GoalStateError = class extends Error {
|
|
6132
|
-
constructor(
|
|
6133
|
-
super(`Invalid goal state at ${
|
|
6171
|
+
constructor(path48, message) {
|
|
6172
|
+
super(`Invalid goal state at ${path48}:
|
|
6134
6173
|
${message}`);
|
|
6135
|
-
this.path =
|
|
6174
|
+
this.path = path48;
|
|
6136
6175
|
this.name = "GoalStateError";
|
|
6137
6176
|
}
|
|
6138
6177
|
path;
|
|
@@ -7278,13 +7317,13 @@ function companyIntentPath(id) {
|
|
|
7278
7317
|
assertIntentId(id);
|
|
7279
7318
|
return `intents/${id}/intent.json`;
|
|
7280
7319
|
}
|
|
7281
|
-
function normalizeCompanyIntent(
|
|
7320
|
+
function normalizeCompanyIntent(path48, raw) {
|
|
7282
7321
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7283
|
-
throw new Error(`${
|
|
7322
|
+
throw new Error(`${path48}: intent must be JSON object`);
|
|
7284
7323
|
}
|
|
7285
7324
|
const input = raw;
|
|
7286
7325
|
const id = stringField2(input.id);
|
|
7287
|
-
if (!id || !isCompanyIntentId(id)) throw new Error(`${
|
|
7326
|
+
if (!id || !isCompanyIntentId(id)) throw new Error(`${path48}: invalid intent id`);
|
|
7288
7327
|
const createdAt = stringField2(input.createdAt) || nowIso();
|
|
7289
7328
|
const updatedAt = stringField2(input.updatedAt) || createdAt;
|
|
7290
7329
|
return {
|
|
@@ -7323,8 +7362,8 @@ function listCompanyIntents(config, cwd) {
|
|
|
7323
7362
|
const records = [];
|
|
7324
7363
|
for (const entry of entries) {
|
|
7325
7364
|
if (entry.type !== "dir" || !entry.name || !isCompanyIntentId(entry.name)) continue;
|
|
7326
|
-
const
|
|
7327
|
-
const file = readStateText(config, cwd,
|
|
7365
|
+
const path48 = companyIntentPath(entry.name);
|
|
7366
|
+
const file = readStateText(config, cwd, path48);
|
|
7328
7367
|
if (!file) continue;
|
|
7329
7368
|
records.push({
|
|
7330
7369
|
id: entry.name,
|
|
@@ -7335,8 +7374,8 @@ function listCompanyIntents(config, cwd) {
|
|
|
7335
7374
|
return records.sort((a, b) => a.intent.priority - b.intent.priority || a.id.localeCompare(b.id));
|
|
7336
7375
|
}
|
|
7337
7376
|
function readCompanyIntent(config, cwd, id) {
|
|
7338
|
-
const
|
|
7339
|
-
const file = readStateText(config, cwd,
|
|
7377
|
+
const path48 = companyIntentPath(id);
|
|
7378
|
+
const file = readStateText(config, cwd, path48);
|
|
7340
7379
|
if (!file) return null;
|
|
7341
7380
|
return { id, path: file.path, intent: normalizeCompanyIntent(file.path, JSON.parse(file.content)) };
|
|
7342
7381
|
}
|
|
@@ -9947,12 +9986,12 @@ function restoreKodyAssets(cwd) {
|
|
|
9947
9986
|
} catch {
|
|
9948
9987
|
}
|
|
9949
9988
|
}
|
|
9950
|
-
function ensureFeatureBranch(issueNumber, title,
|
|
9951
|
-
const result = ensureFeatureBranchInner(issueNumber, title,
|
|
9989
|
+
function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch) {
|
|
9990
|
+
const result = ensureFeatureBranchInner(issueNumber, title, defaultBranch, cwd, baseBranch);
|
|
9952
9991
|
restoreKodyAssets(cwd);
|
|
9953
9992
|
return result;
|
|
9954
9993
|
}
|
|
9955
|
-
function ensureFeatureBranchInner(issueNumber, title,
|
|
9994
|
+
function ensureFeatureBranchInner(issueNumber, title, defaultBranch, cwd, baseBranch) {
|
|
9956
9995
|
const branchName = deriveBranchName(issueNumber, title);
|
|
9957
9996
|
resetWorkingTree2(cwd);
|
|
9958
9997
|
const current = getCurrentBranch(cwd);
|
|
@@ -9969,7 +10008,7 @@ function ensureFeatureBranchInner(issueNumber, title, defaultBranch2, cwd, baseB
|
|
|
9969
10008
|
originBranchExists = true;
|
|
9970
10009
|
} catch {
|
|
9971
10010
|
}
|
|
9972
|
-
if (originBranchExists && baseBranch && baseBranch !==
|
|
10011
|
+
if (originBranchExists && baseBranch && baseBranch !== defaultBranch) {
|
|
9973
10012
|
let baseExists = false;
|
|
9974
10013
|
try {
|
|
9975
10014
|
git3(["rev-parse", "--verify", `origin/${baseBranch}`], cwd);
|
|
@@ -10010,16 +10049,16 @@ function ensureFeatureBranchInner(issueNumber, title, defaultBranch2, cwd, baseB
|
|
|
10010
10049
|
git3(["pull", "origin", branchName], cwd);
|
|
10011
10050
|
} catch {
|
|
10012
10051
|
}
|
|
10013
|
-
if (!baseBranch || baseBranch ===
|
|
10052
|
+
if (!baseBranch || baseBranch === defaultBranch) {
|
|
10014
10053
|
try {
|
|
10015
|
-
git3(["merge", "--no-edit", `origin/${
|
|
10054
|
+
git3(["merge", "--no-edit", `origin/${defaultBranch}`], cwd);
|
|
10016
10055
|
} catch {
|
|
10017
10056
|
try {
|
|
10018
10057
|
git3(["merge", "--abort"], cwd);
|
|
10019
10058
|
} catch {
|
|
10020
10059
|
}
|
|
10021
10060
|
throw new Error(
|
|
10022
|
-
`Branch '${branchName}' has merge conflicts with 'origin/${
|
|
10061
|
+
`Branch '${branchName}' has merge conflicts with 'origin/${defaultBranch}'. Resolve manually or delete the branch to start fresh.`
|
|
10023
10062
|
);
|
|
10024
10063
|
}
|
|
10025
10064
|
}
|
|
@@ -10031,8 +10070,8 @@ function ensureFeatureBranchInner(issueNumber, title, defaultBranch2, cwd, baseB
|
|
|
10031
10070
|
return { branch: branchName, created: false };
|
|
10032
10071
|
} catch {
|
|
10033
10072
|
}
|
|
10034
|
-
let forkPoint =
|
|
10035
|
-
if (baseBranch && baseBranch !==
|
|
10073
|
+
let forkPoint = defaultBranch;
|
|
10074
|
+
if (baseBranch && baseBranch !== defaultBranch) {
|
|
10036
10075
|
try {
|
|
10037
10076
|
git3(["rev-parse", "--verify", `origin/${baseBranch}`], cwd);
|
|
10038
10077
|
forkPoint = baseBranch;
|
|
@@ -10313,11 +10352,11 @@ function detectOwnerRepo(cwd) {
|
|
|
10313
10352
|
if (!m) return null;
|
|
10314
10353
|
return { owner: m[1], repo: m[2] };
|
|
10315
10354
|
}
|
|
10316
|
-
function makeConfig(pm, ownerRepo,
|
|
10355
|
+
function makeConfig(pm, ownerRepo, defaultBranch) {
|
|
10317
10356
|
return {
|
|
10318
10357
|
$schema: schemaUrlFromPkg(),
|
|
10319
10358
|
quality: qualityCommandsFor(pm),
|
|
10320
|
-
git: { defaultBranch
|
|
10359
|
+
git: { defaultBranch },
|
|
10321
10360
|
github: {
|
|
10322
10361
|
owner: ownerRepo?.owner ?? "OWNER",
|
|
10323
10362
|
repo: ownerRepo?.repo ?? "REPO"
|
|
@@ -10352,12 +10391,12 @@ function performInit(cwd, force) {
|
|
|
10352
10391
|
const skipped = [];
|
|
10353
10392
|
const pm = detectPackageManager(cwd);
|
|
10354
10393
|
const ownerRepo = detectOwnerRepo(cwd);
|
|
10355
|
-
const
|
|
10394
|
+
const defaultBranch = defaultBranchFromGit(cwd);
|
|
10356
10395
|
const configPath = path31.join(cwd, "kody.config.json");
|
|
10357
10396
|
if (fs31.existsSync(configPath) && !force) {
|
|
10358
10397
|
skipped.push("kody.config.json");
|
|
10359
10398
|
} else {
|
|
10360
|
-
const cfg = makeConfig(pm, ownerRepo,
|
|
10399
|
+
const cfg = makeConfig(pm, ownerRepo, defaultBranch);
|
|
10361
10400
|
fs31.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
10362
10401
|
`);
|
|
10363
10402
|
wrote.push("kody.config.json");
|
|
@@ -10371,15 +10410,6 @@ function performInit(cwd, force) {
|
|
|
10371
10410
|
fs31.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
|
|
10372
10411
|
wrote.push(".github/workflows/kody.yml");
|
|
10373
10412
|
}
|
|
10374
|
-
const agentsDir = path31.join(cwd, ".kody", "agents");
|
|
10375
|
-
const agentPath = path31.join(agentsDir, "kody.md");
|
|
10376
|
-
if (fs31.existsSync(agentPath) && !force) {
|
|
10377
|
-
skipped.push(".kody/agents/kody.md");
|
|
10378
|
-
} else {
|
|
10379
|
-
fs31.mkdirSync(agentsDir, { recursive: true });
|
|
10380
|
-
fs31.writeFileSync(agentPath, DEFAULT_AGENT_IDENTITY);
|
|
10381
|
-
wrote.push(".kody/agents/kody.md");
|
|
10382
|
-
}
|
|
10383
10413
|
for (const exe of listAgentActions()) {
|
|
10384
10414
|
let profile;
|
|
10385
10415
|
try {
|
|
@@ -10439,7 +10469,7 @@ jobs:
|
|
|
10439
10469
|
run: npx -y -p @kody-ade/kody-engine@latest kody-engine exec ${name}
|
|
10440
10470
|
`;
|
|
10441
10471
|
}
|
|
10442
|
-
var WORKFLOW_TEMPLATE,
|
|
10472
|
+
var WORKFLOW_TEMPLATE, initFlow;
|
|
10443
10473
|
var init_initFlow = __esm({
|
|
10444
10474
|
"src/scripts/initFlow.ts"() {
|
|
10445
10475
|
"use strict";
|
|
@@ -10512,13 +10542,6 @@ jobs:
|
|
|
10512
10542
|
- env:
|
|
10513
10543
|
ALL_SECRETS: \${{ toJSON(secrets) }}
|
|
10514
10544
|
run: npx -y -p @kody-ade/kody-engine@latest kody-engine ci
|
|
10515
|
-
`;
|
|
10516
|
-
DEFAULT_AGENT_IDENTITY = `# Kody
|
|
10517
|
-
|
|
10518
|
-
You are Kody, the default maintenance agent for scheduled agentResponsibilities.
|
|
10519
|
-
|
|
10520
|
-
Keep actions narrow, prefer read-only inspection, and only use the tools or commands named by the agentResponsibility.
|
|
10521
|
-
When a agentResponsibility writes a report or dispatches work, keep the output factual and concise.
|
|
10522
10545
|
`;
|
|
10523
10546
|
initFlow = async (ctx) => {
|
|
10524
10547
|
const force = ctx.args.force === true;
|
|
@@ -10724,7 +10747,7 @@ function retryDelaysMs() {
|
|
|
10724
10747
|
}
|
|
10725
10748
|
function sleep(ms) {
|
|
10726
10749
|
if (ms <= 0) return Promise.resolve();
|
|
10727
|
-
return new Promise((
|
|
10750
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
10728
10751
|
}
|
|
10729
10752
|
async function fetchGoalStateWithRetry(config, goalId, cwd) {
|
|
10730
10753
|
let state = fetchGoalState(config, goalId, cwd);
|
|
@@ -11551,7 +11574,11 @@ function normalizeBundleFiles(ctx, bundle) {
|
|
|
11551
11574
|
if (file.path.startsWith("/") || file.path.includes("\\")) {
|
|
11552
11575
|
throw new Error(`openAgentFactoryStatePr: files[${index}].path must be a relative POSIX path`);
|
|
11553
11576
|
}
|
|
11554
|
-
const
|
|
11577
|
+
const normalizedPath = normalizeStatePath(file.path, `files[${index}].path`);
|
|
11578
|
+
const relativePath = normalizedPath.replace(/^\.kody\/?/, "");
|
|
11579
|
+
if (!relativePath) {
|
|
11580
|
+
throw new Error(`openAgentFactoryStatePr: files[${index}].path must point to a state repo file`);
|
|
11581
|
+
}
|
|
11555
11582
|
if (seen.has(relativePath)) {
|
|
11556
11583
|
throw new Error(`openAgentFactoryStatePr: duplicate generated file path: ${relativePath}`);
|
|
11557
11584
|
}
|
|
@@ -13070,11 +13097,11 @@ var init_resolveQaUrl = __esm({
|
|
|
13070
13097
|
const fallback = readKodyVariables(ctx.cwd).QA_URL?.trim();
|
|
13071
13098
|
if (fallback && fallback.length > 0) {
|
|
13072
13099
|
ctx.data.previewUrl = fallback;
|
|
13073
|
-
ctx.data.previewUrlSource = "QA_URL variable (
|
|
13100
|
+
ctx.data.previewUrlSource = "QA_URL variable (state repo variables.json)";
|
|
13074
13101
|
return;
|
|
13075
13102
|
}
|
|
13076
13103
|
throw new Error(
|
|
13077
|
-
"qa-engineer: no URL resolved. Pass --url, set --goal <id> on a goal that has a Vercel preview, set $PREVIEW_URL, or set the QA_URL variable in
|
|
13104
|
+
"qa-engineer: no URL resolved. Pass --url, set --goal <id> on a goal that has a Vercel preview, set $PREVIEW_URL, or set the QA_URL variable in the state repo variables.json."
|
|
13078
13105
|
);
|
|
13079
13106
|
};
|
|
13080
13107
|
}
|
|
@@ -13307,9 +13334,9 @@ var init_runFlow = __esm({
|
|
|
13307
13334
|
});
|
|
13308
13335
|
|
|
13309
13336
|
// src/scripts/previewBuildHelpers.ts
|
|
13310
|
-
import { createDecipheriv, createHash as
|
|
13337
|
+
import { createDecipheriv, createHash as createHash3 } from "crypto";
|
|
13311
13338
|
function shortHash(s) {
|
|
13312
|
-
return
|
|
13339
|
+
return createHash3("sha256").update(s).digest("hex").slice(0, 6);
|
|
13313
13340
|
}
|
|
13314
13341
|
function previewAppName(repo, pr) {
|
|
13315
13342
|
const [owner, name] = repo.split("/");
|
|
@@ -13363,7 +13390,7 @@ function formatPreviewComment(args) {
|
|
|
13363
13390
|
].join("\n");
|
|
13364
13391
|
}
|
|
13365
13392
|
function defaultImageTag(repo, ref) {
|
|
13366
|
-
return
|
|
13393
|
+
return createHash3("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
|
|
13367
13394
|
}
|
|
13368
13395
|
var NEVER_PASS_TO_BUILD;
|
|
13369
13396
|
var init_previewBuildHelpers = __esm({
|
|
@@ -13383,7 +13410,7 @@ var init_previewBuildHelpers = __esm({
|
|
|
13383
13410
|
// src/scripts/previewBuildRun.ts
|
|
13384
13411
|
import { spawn as spawn4 } from "child_process";
|
|
13385
13412
|
async function runCmd(cmd, args, opts = {}) {
|
|
13386
|
-
await new Promise((
|
|
13413
|
+
await new Promise((resolve8, reject) => {
|
|
13387
13414
|
const child = spawn4(cmd, args, {
|
|
13388
13415
|
cwd: opts.cwd,
|
|
13389
13416
|
env: { ...process.env, ...opts.env ?? {} },
|
|
@@ -13395,7 +13422,7 @@ async function runCmd(cmd, args, opts = {}) {
|
|
|
13395
13422
|
}
|
|
13396
13423
|
child.on("error", reject);
|
|
13397
13424
|
child.on("close", (code) => {
|
|
13398
|
-
if (code === 0)
|
|
13425
|
+
if (code === 0) resolve8();
|
|
13399
13426
|
else reject(new Error(`${cmd} ${args.join(" ")} exited ${code}`));
|
|
13400
13427
|
});
|
|
13401
13428
|
});
|
|
@@ -13465,14 +13492,196 @@ fi
|
|
|
13465
13492
|
}
|
|
13466
13493
|
});
|
|
13467
13494
|
|
|
13495
|
+
// src/stateRepoGithub.ts
|
|
13496
|
+
import * as fs37 from "fs";
|
|
13497
|
+
import * as path36 from "path";
|
|
13498
|
+
function recordValue2(value) {
|
|
13499
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13500
|
+
}
|
|
13501
|
+
function contentsUrl(owner, repo, filePath) {
|
|
13502
|
+
const encodedPath = filePath.split("/").filter(Boolean).map((part) => encodeURIComponent(part)).join("/");
|
|
13503
|
+
return `${GITHUB_API}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/contents/${encodedPath}`;
|
|
13504
|
+
}
|
|
13505
|
+
async function githubContentsFile(opts) {
|
|
13506
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
13507
|
+
const res = await doFetch(contentsUrl(opts.owner, opts.repo, opts.path), {
|
|
13508
|
+
headers: {
|
|
13509
|
+
Authorization: `Bearer ${opts.githubToken}`,
|
|
13510
|
+
Accept: "application/vnd.github+json",
|
|
13511
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
13512
|
+
"User-Agent": "kody-engine"
|
|
13513
|
+
},
|
|
13514
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
|
|
13515
|
+
});
|
|
13516
|
+
if (res.status === 404) return null;
|
|
13517
|
+
if (!res.ok) {
|
|
13518
|
+
throw new Error(
|
|
13519
|
+
`GitHub ${opts.owner}/${opts.repo}:${opts.path}: ${res.status} ${(await res.text().catch(() => "")).slice(0, 160)}`
|
|
13520
|
+
);
|
|
13521
|
+
}
|
|
13522
|
+
return await res.json();
|
|
13523
|
+
}
|
|
13524
|
+
function decodeContentsFile(file, label) {
|
|
13525
|
+
if (file.type !== "file" || file.encoding !== "base64" || typeof file.content !== "string") {
|
|
13526
|
+
throw new Error(`${label} is not a base64 file`);
|
|
13527
|
+
}
|
|
13528
|
+
if (typeof file.sha !== "string") {
|
|
13529
|
+
throw new Error(`${label} response missing sha`);
|
|
13530
|
+
}
|
|
13531
|
+
return {
|
|
13532
|
+
path: label,
|
|
13533
|
+
content: Buffer.from(file.content, "base64").toString("utf-8"),
|
|
13534
|
+
sha: file.sha
|
|
13535
|
+
};
|
|
13536
|
+
}
|
|
13537
|
+
async function loadGithubStateConfig(opts) {
|
|
13538
|
+
const configFile = await githubContentsFile({
|
|
13539
|
+
owner: opts.owner,
|
|
13540
|
+
repo: opts.repo,
|
|
13541
|
+
path: "kody.config.json",
|
|
13542
|
+
githubToken: opts.githubToken,
|
|
13543
|
+
fetchImpl: opts.fetchImpl
|
|
13544
|
+
});
|
|
13545
|
+
let raw = {};
|
|
13546
|
+
if (configFile) {
|
|
13547
|
+
const decoded = decodeContentsFile(configFile, "kody.config.json");
|
|
13548
|
+
try {
|
|
13549
|
+
raw = JSON.parse(decoded.content);
|
|
13550
|
+
} catch (err) {
|
|
13551
|
+
throw new Error(`kody.config.json is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
13552
|
+
}
|
|
13553
|
+
}
|
|
13554
|
+
const githubRaw = recordValue2(raw.github) ?? {};
|
|
13555
|
+
const github = {
|
|
13556
|
+
owner: typeof githubRaw.owner === "string" && githubRaw.owner.trim() ? githubRaw.owner.trim() : opts.owner,
|
|
13557
|
+
repo: typeof githubRaw.repo === "string" && githubRaw.repo.trim() ? githubRaw.repo.trim() : opts.repo
|
|
13558
|
+
};
|
|
13559
|
+
const nestedState = recordValue2(raw.state) ?? {};
|
|
13560
|
+
const repoRaw = typeof raw.stateRepo === "string" ? raw.stateRepo : nestedState.repo;
|
|
13561
|
+
const pathRaw = typeof raw.statePath === "string" ? raw.statePath : nestedState.path;
|
|
13562
|
+
const stateRepo = typeof repoRaw === "string" && repoRaw.trim().length > 0 ? repoRaw.trim() : `https://github.com/${github.owner}/kody-state`;
|
|
13563
|
+
parseStateRepoSlug(stateRepo);
|
|
13564
|
+
const statePath2 = typeof pathRaw === "string" && pathRaw.trim().length > 0 ? pathRaw.trim() : github.repo;
|
|
13565
|
+
return {
|
|
13566
|
+
github,
|
|
13567
|
+
state: {
|
|
13568
|
+
repo: stateRepo,
|
|
13569
|
+
path: normalizeStatePath(statePath2)
|
|
13570
|
+
}
|
|
13571
|
+
};
|
|
13572
|
+
}
|
|
13573
|
+
async function readGithubStateText(opts) {
|
|
13574
|
+
const config = await loadGithubStateConfig(opts);
|
|
13575
|
+
return readGithubStateTextWithConfig({
|
|
13576
|
+
config,
|
|
13577
|
+
filePath: opts.filePath,
|
|
13578
|
+
githubToken: opts.githubToken,
|
|
13579
|
+
fetchImpl: opts.fetchImpl
|
|
13580
|
+
});
|
|
13581
|
+
}
|
|
13582
|
+
async function readGithubStateTextWithConfig(opts) {
|
|
13583
|
+
const target = stateRepoPath(opts.config, opts.filePath);
|
|
13584
|
+
const parsed = parseStateRepo(opts.config);
|
|
13585
|
+
const file = await githubContentsFile({
|
|
13586
|
+
owner: parsed.owner,
|
|
13587
|
+
repo: parsed.repo,
|
|
13588
|
+
path: target,
|
|
13589
|
+
githubToken: opts.githubToken,
|
|
13590
|
+
fetchImpl: opts.fetchImpl
|
|
13591
|
+
});
|
|
13592
|
+
return file ? decodeContentsFile(file, target) : null;
|
|
13593
|
+
}
|
|
13594
|
+
async function writeGithubStateTextWithConfig(opts) {
|
|
13595
|
+
const target = stateRepoPath(opts.config, opts.filePath);
|
|
13596
|
+
const parsed = parseStateRepo(opts.config);
|
|
13597
|
+
const payload = {
|
|
13598
|
+
message: opts.message,
|
|
13599
|
+
content: Buffer.from(opts.content, "utf-8").toString("base64")
|
|
13600
|
+
};
|
|
13601
|
+
if (opts.sha) payload.sha = opts.sha;
|
|
13602
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
13603
|
+
const res = await doFetch(contentsUrl(parsed.owner, parsed.repo, target), {
|
|
13604
|
+
method: "PUT",
|
|
13605
|
+
headers: {
|
|
13606
|
+
Authorization: `Bearer ${opts.githubToken}`,
|
|
13607
|
+
Accept: "application/vnd.github+json",
|
|
13608
|
+
"Content-Type": "application/json",
|
|
13609
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
13610
|
+
"User-Agent": "kody-engine"
|
|
13611
|
+
},
|
|
13612
|
+
body: JSON.stringify(payload),
|
|
13613
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
|
|
13614
|
+
});
|
|
13615
|
+
if (!res.ok) {
|
|
13616
|
+
throw new Error(
|
|
13617
|
+
`GitHub write ${parsed.owner}/${parsed.repo}:${target}: ${res.status} ${(await res.text().catch(() => "")).slice(0, 160)}`
|
|
13618
|
+
);
|
|
13619
|
+
}
|
|
13620
|
+
}
|
|
13621
|
+
function jsonlLines(text) {
|
|
13622
|
+
return text.split("\n").filter((line) => line.length > 0);
|
|
13623
|
+
}
|
|
13624
|
+
function renderJsonl(lines) {
|
|
13625
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
13626
|
+
` : "";
|
|
13627
|
+
}
|
|
13628
|
+
function mergeJsonl(localText, remoteText) {
|
|
13629
|
+
const remoteLines = jsonlLines(remoteText);
|
|
13630
|
+
const seen = new Set(remoteLines);
|
|
13631
|
+
const localOnly = jsonlLines(localText).filter((line) => !seen.has(line));
|
|
13632
|
+
return renderJsonl([...remoteLines, ...localOnly]);
|
|
13633
|
+
}
|
|
13634
|
+
async function syncJsonlFileFromGithubState(opts) {
|
|
13635
|
+
const remote = await readGithubStateTextWithConfig(opts);
|
|
13636
|
+
if (!remote) return;
|
|
13637
|
+
const local = fs37.existsSync(opts.localPath) ? fs37.readFileSync(opts.localPath, "utf-8") : "";
|
|
13638
|
+
const next = mergeJsonl(local, remote.content);
|
|
13639
|
+
if (next === local) return;
|
|
13640
|
+
fs37.mkdirSync(path36.dirname(opts.localPath), { recursive: true });
|
|
13641
|
+
fs37.writeFileSync(opts.localPath, next);
|
|
13642
|
+
}
|
|
13643
|
+
async function persistJsonlFileToGithubState(opts) {
|
|
13644
|
+
if (!fs37.existsSync(opts.localPath)) return;
|
|
13645
|
+
const localText = fs37.readFileSync(opts.localPath, "utf-8");
|
|
13646
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
13647
|
+
const remote = await readGithubStateTextWithConfig(opts);
|
|
13648
|
+
const body = mergeJsonl(localText, remote?.content ?? "");
|
|
13649
|
+
try {
|
|
13650
|
+
await writeGithubStateTextWithConfig({
|
|
13651
|
+
config: opts.config,
|
|
13652
|
+
filePath: opts.filePath,
|
|
13653
|
+
content: body,
|
|
13654
|
+
message: opts.message,
|
|
13655
|
+
githubToken: opts.githubToken,
|
|
13656
|
+
sha: remote?.sha,
|
|
13657
|
+
fetchImpl: opts.fetchImpl
|
|
13658
|
+
});
|
|
13659
|
+
return;
|
|
13660
|
+
} catch (err) {
|
|
13661
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13662
|
+
const conflict = /409|422|does not match|is at|but expected/i.test(msg);
|
|
13663
|
+
if (!conflict || attempt === 3) throw err;
|
|
13664
|
+
}
|
|
13665
|
+
}
|
|
13666
|
+
}
|
|
13667
|
+
var GITHUB_API, REQ_TIMEOUT_MS2;
|
|
13668
|
+
var init_stateRepoGithub = __esm({
|
|
13669
|
+
"src/stateRepoGithub.ts"() {
|
|
13670
|
+
"use strict";
|
|
13671
|
+
init_stateRepo();
|
|
13672
|
+
GITHUB_API = "https://api.github.com";
|
|
13673
|
+
REQ_TIMEOUT_MS2 = 3e4;
|
|
13674
|
+
}
|
|
13675
|
+
});
|
|
13676
|
+
|
|
13468
13677
|
// src/scripts/runPreviewBuild.ts
|
|
13469
13678
|
import { copyFile, writeFile } from "fs/promises";
|
|
13470
|
-
import * as
|
|
13679
|
+
import * as path37 from "path";
|
|
13471
13680
|
import { fileURLToPath } from "url";
|
|
13472
13681
|
function bundledDockerfilePath(mode) {
|
|
13473
|
-
const here =
|
|
13682
|
+
const here = path37.dirname(fileURLToPath(import.meta.url));
|
|
13474
13683
|
const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
|
|
13475
|
-
return
|
|
13684
|
+
return path37.join(here, "preview-build-templates", file);
|
|
13476
13685
|
}
|
|
13477
13686
|
function required(name) {
|
|
13478
13687
|
const v = (process.env[name] ?? "").trim();
|
|
@@ -13485,33 +13694,24 @@ function flyHeaders(token) {
|
|
|
13485
13694
|
"Content-Type": "application/json"
|
|
13486
13695
|
};
|
|
13487
13696
|
}
|
|
13488
|
-
async function ghJSON(url, token) {
|
|
13489
|
-
const res = await fetch(url, {
|
|
13490
|
-
headers: {
|
|
13491
|
-
Authorization: `Bearer ${token}`,
|
|
13492
|
-
Accept: "application/vnd.github+json",
|
|
13493
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
13494
|
-
},
|
|
13495
|
-
signal: AbortSignal.timeout(REQ_TIMEOUT_MS2)
|
|
13496
|
-
});
|
|
13497
|
-
if (!res.ok) {
|
|
13498
|
-
throw new Error(`GitHub ${url}: ${res.status} ${res.statusText}`);
|
|
13499
|
-
}
|
|
13500
|
-
return await res.json();
|
|
13501
|
-
}
|
|
13502
13697
|
async function fetchVaultDoc(repo, ghToken3, masterKey) {
|
|
13503
|
-
const
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13698
|
+
const [owner, name] = repo.split("/", 2);
|
|
13699
|
+
if (!owner || !name) throw new Error(`invalid GITHUB_REPOSITORY "${repo}"`);
|
|
13700
|
+
const meta = await readGithubStateText({
|
|
13701
|
+
owner,
|
|
13702
|
+
repo: name,
|
|
13703
|
+
filePath: "secrets.enc",
|
|
13704
|
+
githubToken: ghToken3
|
|
13705
|
+
});
|
|
13706
|
+
if (!meta) throw new Error("state repo has no secrets.enc \u2014 save secrets from the dashboard first");
|
|
13707
|
+
const payload = meta.content;
|
|
13508
13708
|
const plaintext = decryptVaultPayload(payload, masterKey);
|
|
13509
13709
|
return JSON.parse(plaintext);
|
|
13510
13710
|
}
|
|
13511
13711
|
async function flyAppExists(name, token) {
|
|
13512
13712
|
const res = await fetch(`${FLY_MACHINES}/apps/${encodeURIComponent(name)}`, {
|
|
13513
13713
|
headers: flyHeaders(token),
|
|
13514
|
-
signal: AbortSignal.timeout(
|
|
13714
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13515
13715
|
});
|
|
13516
13716
|
if (res.status === 404) return false;
|
|
13517
13717
|
if (!res.ok) {
|
|
@@ -13524,7 +13724,7 @@ async function flyCreateApp(name, orgSlug, token) {
|
|
|
13524
13724
|
method: "POST",
|
|
13525
13725
|
headers: flyHeaders(token),
|
|
13526
13726
|
body: JSON.stringify({ app_name: name, org_slug: orgSlug }),
|
|
13527
|
-
signal: AbortSignal.timeout(
|
|
13727
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13528
13728
|
});
|
|
13529
13729
|
if (res.status === 422) return;
|
|
13530
13730
|
if (!res.ok) {
|
|
@@ -13543,7 +13743,7 @@ async function flyAllocateSharedIps(appName, token) {
|
|
|
13543
13743
|
method: "POST",
|
|
13544
13744
|
headers: flyHeaders(token),
|
|
13545
13745
|
body: JSON.stringify({ query: mutation, variables: { appId: appName } }),
|
|
13546
|
-
signal: AbortSignal.timeout(
|
|
13746
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13547
13747
|
});
|
|
13548
13748
|
if (!res.ok) {
|
|
13549
13749
|
throw new Error(`allocateSharedIps ${appName}: ${res.status}`);
|
|
@@ -13559,7 +13759,7 @@ async function flyAllocateSharedIps(appName, token) {
|
|
|
13559
13759
|
async function flyListMachines(appName, token) {
|
|
13560
13760
|
const res = await fetch(`${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines`, {
|
|
13561
13761
|
headers: flyHeaders(token),
|
|
13562
|
-
signal: AbortSignal.timeout(
|
|
13762
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13563
13763
|
});
|
|
13564
13764
|
if (res.status === 404) return [];
|
|
13565
13765
|
if (!res.ok) {
|
|
@@ -13572,14 +13772,14 @@ async function flyDestroyMachine(appName, machineId, token) {
|
|
|
13572
13772
|
await fetch(`${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines/${encodeURIComponent(machineId)}/stop`, {
|
|
13573
13773
|
method: "POST",
|
|
13574
13774
|
headers: flyHeaders(token),
|
|
13575
|
-
signal: AbortSignal.timeout(
|
|
13775
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13576
13776
|
}).catch(() => void 0);
|
|
13577
13777
|
const res = await fetch(
|
|
13578
13778
|
`${FLY_MACHINES}/apps/${encodeURIComponent(appName)}/machines/${encodeURIComponent(machineId)}?force=true`,
|
|
13579
13779
|
{
|
|
13580
13780
|
method: "DELETE",
|
|
13581
13781
|
headers: flyHeaders(token),
|
|
13582
|
-
signal: AbortSignal.timeout(
|
|
13782
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13583
13783
|
}
|
|
13584
13784
|
);
|
|
13585
13785
|
if (res.status === 404) return;
|
|
@@ -13630,7 +13830,7 @@ async function flyCreatePreviewMachine(args, token) {
|
|
|
13630
13830
|
method: "POST",
|
|
13631
13831
|
headers: flyHeaders(token),
|
|
13632
13832
|
body: JSON.stringify(body),
|
|
13633
|
-
signal: AbortSignal.timeout(
|
|
13833
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13634
13834
|
});
|
|
13635
13835
|
if (res.ok) {
|
|
13636
13836
|
const { id } = await res.json();
|
|
@@ -13654,7 +13854,7 @@ async function postOrUpdatePreviewComment(args) {
|
|
|
13654
13854
|
};
|
|
13655
13855
|
const listRes = await fetch(`${base}?per_page=100`, {
|
|
13656
13856
|
headers,
|
|
13657
|
-
signal: AbortSignal.timeout(
|
|
13857
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13658
13858
|
}).catch(() => null);
|
|
13659
13859
|
let existingId = null;
|
|
13660
13860
|
if (listRes?.ok) {
|
|
@@ -13667,7 +13867,7 @@ async function postOrUpdatePreviewComment(args) {
|
|
|
13667
13867
|
method: "PATCH",
|
|
13668
13868
|
headers,
|
|
13669
13869
|
body: JSON.stringify({ body: args.body }),
|
|
13670
|
-
signal: AbortSignal.timeout(
|
|
13870
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13671
13871
|
});
|
|
13672
13872
|
return;
|
|
13673
13873
|
}
|
|
@@ -13675,19 +13875,20 @@ async function postOrUpdatePreviewComment(args) {
|
|
|
13675
13875
|
method: "POST",
|
|
13676
13876
|
headers,
|
|
13677
13877
|
body: JSON.stringify({ body: args.body }),
|
|
13678
|
-
signal: AbortSignal.timeout(
|
|
13878
|
+
signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
|
|
13679
13879
|
});
|
|
13680
13880
|
}
|
|
13681
|
-
var FLY_MACHINES, FLY_GRAPHQL,
|
|
13881
|
+
var FLY_MACHINES, FLY_GRAPHQL, REQ_TIMEOUT_MS3, runPreviewBuild;
|
|
13682
13882
|
var init_runPreviewBuild = __esm({
|
|
13683
13883
|
"src/scripts/runPreviewBuild.ts"() {
|
|
13684
13884
|
"use strict";
|
|
13685
13885
|
init_previewBuildHelpers();
|
|
13686
13886
|
init_previewBuildNamespace();
|
|
13687
13887
|
init_previewBuildRun();
|
|
13888
|
+
init_stateRepoGithub();
|
|
13688
13889
|
FLY_MACHINES = "https://api.machines.dev/v1";
|
|
13689
13890
|
FLY_GRAPHQL = "https://api.fly.io/graphql";
|
|
13690
|
-
|
|
13891
|
+
REQ_TIMEOUT_MS3 = 3e4;
|
|
13691
13892
|
runPreviewBuild = async (ctx, _profile, _args) => {
|
|
13692
13893
|
ctx.skipAgent = true;
|
|
13693
13894
|
const pr = Number(ctx.args.pr);
|
|
@@ -13731,10 +13932,10 @@ var init_runPreviewBuild = __esm({
|
|
|
13731
13932
|
console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
|
|
13732
13933
|
if (Object.keys(buildEnv).length > 0) {
|
|
13733
13934
|
const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
13734
|
-
await writeFile(
|
|
13935
|
+
await writeFile(path37.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
|
|
13735
13936
|
`, "utf8");
|
|
13736
13937
|
}
|
|
13737
|
-
const consumerDockerfile =
|
|
13938
|
+
const consumerDockerfile = path37.join(ctx.cwd, "Dockerfile.preview");
|
|
13738
13939
|
const { stat } = await import("fs/promises");
|
|
13739
13940
|
let hasConsumerDockerfile = false;
|
|
13740
13941
|
try {
|
|
@@ -13918,8 +14119,8 @@ var init_tickShellRunner = __esm({
|
|
|
13918
14119
|
});
|
|
13919
14120
|
|
|
13920
14121
|
// src/scripts/runScheduledAgentActionTick.ts
|
|
13921
|
-
import * as
|
|
13922
|
-
import * as
|
|
14122
|
+
import * as fs38 from "fs";
|
|
14123
|
+
import * as path38 from "path";
|
|
13923
14124
|
var runScheduledAgentActionTick;
|
|
13924
14125
|
var init_runScheduledAgentActionTick = __esm({
|
|
13925
14126
|
"src/scripts/runScheduledAgentActionTick.ts"() {
|
|
@@ -13939,14 +14140,14 @@ var init_runScheduledAgentActionTick = __esm({
|
|
|
13939
14140
|
ctx.output.reason = `runScheduledAgentActionTick: args.slug or ctx.args.${slugArg} must be non-empty agentResponsibility slug`;
|
|
13940
14141
|
return;
|
|
13941
14142
|
}
|
|
13942
|
-
const agentResponsibility = resolveAgentResponsibilityFolder(slug2,
|
|
14143
|
+
const agentResponsibility = resolveAgentResponsibilityFolder(slug2, path38.join(ctx.cwd, jobsDir));
|
|
13943
14144
|
if (!agentResponsibility) {
|
|
13944
14145
|
ctx.output.exitCode = 99;
|
|
13945
14146
|
ctx.output.reason = `runScheduledAgentActionTick: agentResponsibility folder not found or incomplete: ${slug2} (searched ${jobsDir} and company store)`;
|
|
13946
14147
|
return;
|
|
13947
14148
|
}
|
|
13948
|
-
const shellPath =
|
|
13949
|
-
if (!
|
|
14149
|
+
const shellPath = path38.join(profile.dir, shell);
|
|
14150
|
+
if (!fs38.existsSync(shellPath)) {
|
|
13950
14151
|
ctx.output.exitCode = 99;
|
|
13951
14152
|
ctx.output.reason = `runScheduledAgentActionTick: shell not found: ${shell} (looked in ${profile.dir})`;
|
|
13952
14153
|
return;
|
|
@@ -13977,8 +14178,8 @@ var init_runScheduledAgentActionTick = __esm({
|
|
|
13977
14178
|
});
|
|
13978
14179
|
|
|
13979
14180
|
// src/scripts/runTickScript.ts
|
|
13980
|
-
import * as
|
|
13981
|
-
import * as
|
|
14181
|
+
import * as fs39 from "fs";
|
|
14182
|
+
import * as path39 from "path";
|
|
13982
14183
|
var runTickScript;
|
|
13983
14184
|
var init_runTickScript = __esm({
|
|
13984
14185
|
"src/scripts/runTickScript.ts"() {
|
|
@@ -13997,10 +14198,10 @@ var init_runTickScript = __esm({
|
|
|
13997
14198
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
13998
14199
|
return;
|
|
13999
14200
|
}
|
|
14000
|
-
const agentResponsibility = readAgentResponsibilityFolder(
|
|
14201
|
+
const agentResponsibility = readAgentResponsibilityFolder(path39.join(ctx.cwd, jobsDir), slug2);
|
|
14001
14202
|
if (!agentResponsibility) {
|
|
14002
14203
|
ctx.output.exitCode = 99;
|
|
14003
|
-
ctx.output.reason = `runTickScript: agentResponsibility folder not found or incomplete: ${
|
|
14204
|
+
ctx.output.reason = `runTickScript: agentResponsibility folder not found or incomplete: ${path39.join(ctx.cwd, jobsDir, slug2)}`;
|
|
14004
14205
|
return;
|
|
14005
14206
|
}
|
|
14006
14207
|
const tickScript = agentResponsibility.config.tickScript;
|
|
@@ -14009,8 +14210,8 @@ var init_runTickScript = __esm({
|
|
|
14009
14210
|
ctx.output.reason = `runTickScript: agentResponsibility ${slug2} has no \`tickScript\` in profile.json \u2014 route via agent-responsibility-tick instead`;
|
|
14010
14211
|
return;
|
|
14011
14212
|
}
|
|
14012
|
-
const scriptPath =
|
|
14013
|
-
if (!
|
|
14213
|
+
const scriptPath = path39.isAbsolute(tickScript) ? tickScript : path39.join(ctx.cwd, tickScript);
|
|
14214
|
+
if (!fs39.existsSync(scriptPath)) {
|
|
14014
14215
|
ctx.output.exitCode = 99;
|
|
14015
14216
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
14016
14217
|
return;
|
|
@@ -14345,7 +14546,7 @@ function stripAnsi2(s) {
|
|
|
14345
14546
|
return s.replace(ANSI_RE2, "");
|
|
14346
14547
|
}
|
|
14347
14548
|
function runCommand2(command, cwd) {
|
|
14348
|
-
return new Promise((
|
|
14549
|
+
return new Promise((resolve8) => {
|
|
14349
14550
|
const child = spawn5(command, {
|
|
14350
14551
|
cwd,
|
|
14351
14552
|
shell: true,
|
|
@@ -14372,11 +14573,11 @@ function runCommand2(command, cwd) {
|
|
|
14372
14573
|
}, TEST_TIMEOUT_MS);
|
|
14373
14574
|
child.on("exit", (code) => {
|
|
14374
14575
|
clearTimeout(timer);
|
|
14375
|
-
|
|
14576
|
+
resolve8({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
|
|
14376
14577
|
});
|
|
14377
14578
|
child.on("error", (err) => {
|
|
14378
14579
|
clearTimeout(timer);
|
|
14379
|
-
|
|
14580
|
+
resolve8({ exitCode: -1, output: err.message });
|
|
14380
14581
|
});
|
|
14381
14582
|
});
|
|
14382
14583
|
}
|
|
@@ -14781,21 +14982,21 @@ function lineStream(stream) {
|
|
|
14781
14982
|
tryDeliver();
|
|
14782
14983
|
});
|
|
14783
14984
|
return {
|
|
14784
|
-
next: (timeoutMs) => new Promise((
|
|
14985
|
+
next: (timeoutMs) => new Promise((resolve8) => {
|
|
14785
14986
|
if (queue.length > 0) {
|
|
14786
|
-
|
|
14987
|
+
resolve8(queue.shift());
|
|
14787
14988
|
return;
|
|
14788
14989
|
}
|
|
14789
14990
|
if (ended) {
|
|
14790
|
-
|
|
14991
|
+
resolve8(null);
|
|
14791
14992
|
return;
|
|
14792
14993
|
}
|
|
14793
|
-
waiter =
|
|
14994
|
+
waiter = resolve8;
|
|
14794
14995
|
const t = setTimeout(
|
|
14795
14996
|
() => {
|
|
14796
|
-
if (waiter ===
|
|
14997
|
+
if (waiter === resolve8) {
|
|
14797
14998
|
waiter = null;
|
|
14798
|
-
|
|
14999
|
+
resolve8(null);
|
|
14799
15000
|
}
|
|
14800
15001
|
},
|
|
14801
15002
|
Math.max(0, timeoutMs)
|
|
@@ -14832,7 +15033,7 @@ var init_warmupMcp = __esm({
|
|
|
14832
15033
|
});
|
|
14833
15034
|
|
|
14834
15035
|
// src/scripts/writeAgentRunSummary.ts
|
|
14835
|
-
import * as
|
|
15036
|
+
import * as fs40 from "fs";
|
|
14836
15037
|
var writeAgentRunSummary;
|
|
14837
15038
|
var init_writeAgentRunSummary = __esm({
|
|
14838
15039
|
"src/scripts/writeAgentRunSummary.ts"() {
|
|
@@ -14858,7 +15059,7 @@ var init_writeAgentRunSummary = __esm({
|
|
|
14858
15059
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
14859
15060
|
lines.push("");
|
|
14860
15061
|
try {
|
|
14861
|
-
|
|
15062
|
+
fs40.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
14862
15063
|
`);
|
|
14863
15064
|
} catch {
|
|
14864
15065
|
}
|
|
@@ -15259,6 +15460,59 @@ var init_scripts = __esm({
|
|
|
15259
15460
|
}
|
|
15260
15461
|
});
|
|
15261
15462
|
|
|
15463
|
+
// src/stateWorkspace.ts
|
|
15464
|
+
import * as fs41 from "fs";
|
|
15465
|
+
import * as path40 from "path";
|
|
15466
|
+
function writeLocalFile(cwd, relativePath, content) {
|
|
15467
|
+
const fullPath = path40.join(cwd, relativePath);
|
|
15468
|
+
fs41.mkdirSync(path40.dirname(fullPath), { recursive: true });
|
|
15469
|
+
fs41.writeFileSync(fullPath, content);
|
|
15470
|
+
}
|
|
15471
|
+
function hydrateDirectory(config, cwd, stateDir, localDir) {
|
|
15472
|
+
const entries = listStateDirectory(config, cwd, stateDir);
|
|
15473
|
+
if (entries.length === 0) return;
|
|
15474
|
+
fs41.rmSync(path40.join(cwd, localDir), { recursive: true, force: true });
|
|
15475
|
+
for (const entry of entries) {
|
|
15476
|
+
if (!entry.name || !entry.type) continue;
|
|
15477
|
+
const childState = path40.posix.join(stateDir, entry.name);
|
|
15478
|
+
const childLocal = path40.join(localDir, entry.name);
|
|
15479
|
+
if (entry.type === "dir") {
|
|
15480
|
+
hydrateDirectory(config, cwd, childState, childLocal);
|
|
15481
|
+
} else if (entry.type === "file") {
|
|
15482
|
+
const file = readStateText(config, cwd, childState);
|
|
15483
|
+
if (file) writeLocalFile(cwd, childLocal, file.content);
|
|
15484
|
+
}
|
|
15485
|
+
}
|
|
15486
|
+
}
|
|
15487
|
+
function hydrateStateWorkspace(config, cwd) {
|
|
15488
|
+
for (const mapping of DIR_MAPPINGS) {
|
|
15489
|
+
hydrateDirectory(config, cwd, mapping.stateDir, mapping.localDir);
|
|
15490
|
+
}
|
|
15491
|
+
for (const mapping of FILE_MAPPINGS) {
|
|
15492
|
+
const file = readStateText(config, cwd, mapping.statePath);
|
|
15493
|
+
if (file) writeLocalFile(cwd, mapping.localPath, file.content);
|
|
15494
|
+
}
|
|
15495
|
+
}
|
|
15496
|
+
var DIR_MAPPINGS, FILE_MAPPINGS;
|
|
15497
|
+
var init_stateWorkspace = __esm({
|
|
15498
|
+
"src/stateWorkspace.ts"() {
|
|
15499
|
+
"use strict";
|
|
15500
|
+
init_stateRepo();
|
|
15501
|
+
DIR_MAPPINGS = [
|
|
15502
|
+
{ stateDir: "agent-actions", localDir: path40.join(".kody", "agent-actions") },
|
|
15503
|
+
{ stateDir: "agent-responsibilities", localDir: path40.join(".kody", "agent-responsibilities") },
|
|
15504
|
+
{ stateDir: "agents", localDir: path40.join(".kody", "agents") },
|
|
15505
|
+
{ stateDir: "context", localDir: path40.join(".kody", "context") },
|
|
15506
|
+
{ stateDir: "memory", localDir: path40.join(".kody", "memory") }
|
|
15507
|
+
];
|
|
15508
|
+
FILE_MAPPINGS = [
|
|
15509
|
+
{ statePath: "instructions.md", localPath: path40.join(".kody", "instructions.md") },
|
|
15510
|
+
{ statePath: "variables.json", localPath: path40.join(".kody", "variables.json") },
|
|
15511
|
+
{ statePath: "secrets.enc", localPath: path40.join(".kody", "secrets.enc") }
|
|
15512
|
+
];
|
|
15513
|
+
}
|
|
15514
|
+
});
|
|
15515
|
+
|
|
15262
15516
|
// src/tools.ts
|
|
15263
15517
|
import { execFileSync as execFileSync24 } from "child_process";
|
|
15264
15518
|
function verifyCliTools(tools, cwd) {
|
|
@@ -15325,8 +15579,8 @@ var init_tools = __esm({
|
|
|
15325
15579
|
|
|
15326
15580
|
// src/executor.ts
|
|
15327
15581
|
import { spawn as spawn7 } from "child_process";
|
|
15328
|
-
import * as
|
|
15329
|
-
import * as
|
|
15582
|
+
import * as fs42 from "fs";
|
|
15583
|
+
import * as path41 from "path";
|
|
15330
15584
|
function isMutatingPostflight(scriptName) {
|
|
15331
15585
|
return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
|
|
15332
15586
|
}
|
|
@@ -15450,6 +15704,9 @@ async function runAgentAction(profileName, input) {
|
|
|
15450
15704
|
return finishAndEnd({ exitCode: 99, reason: `config error: ${err instanceof Error ? err.message : String(err)}` });
|
|
15451
15705
|
}
|
|
15452
15706
|
}
|
|
15707
|
+
if (!input.skipConfig && config.github.owner && config.github.repo) {
|
|
15708
|
+
hydrateStateWorkspace(config, input.cwd);
|
|
15709
|
+
}
|
|
15453
15710
|
const perAgentActionModel = config.agent.perAgentAction?.[profileName];
|
|
15454
15711
|
const modelSpec = perAgentActionModel ? perAgentActionModel : profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
|
|
15455
15712
|
const profileHasThinkingTokens = typeof profile.claudeCode.maxThinkingTokens === "number" && profile.claudeCode.maxThinkingTokens > 0;
|
|
@@ -15491,13 +15748,13 @@ async function runAgentAction(profileName, input) {
|
|
|
15491
15748
|
})
|
|
15492
15749
|
};
|
|
15493
15750
|
})() : null;
|
|
15494
|
-
const ndjsonDir =
|
|
15751
|
+
const ndjsonDir = agentRunDir(input.cwd);
|
|
15495
15752
|
const agentSlug = typeof profile.agent === "string" && profile.agent.length > 0 ? profile.agent : typeof ctx.data.jobAgent === "string" && ctx.data.jobAgent.length > 0 ? ctx.data.jobAgent : null;
|
|
15496
15753
|
const agentIdentityBlock = agentSlug ? frameAgentIdentity(agentSlug, loadAgentIdentity(input.cwd, agentSlug)) : null;
|
|
15497
15754
|
const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
|
|
15498
15755
|
const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
|
|
15499
15756
|
const invokeAgent = async (prompt) => {
|
|
15500
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
15757
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path41.isAbsolute(p) ? p : path41.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
15501
15758
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
15502
15759
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
15503
15760
|
const agents = loadSubagents(profile);
|
|
@@ -15523,6 +15780,7 @@ async function runAgentAction(profileName, input) {
|
|
|
15523
15780
|
verbose: input.verbose,
|
|
15524
15781
|
quiet: input.quiet,
|
|
15525
15782
|
ndjsonDir,
|
|
15783
|
+
additionalDirectories: taskArtifacts ? [taskArtifacts.absDir] : void 0,
|
|
15526
15784
|
allowedToolsOverride: profile.claudeCode.tools,
|
|
15527
15785
|
permissionModeOverride: profile.claudeCode.permissionMode,
|
|
15528
15786
|
mcpServers: profile.claudeCode.mcpServers.length > 0 ? profile.claudeCode.mcpServers : void 0,
|
|
@@ -15751,7 +16009,14 @@ async function runAgentAction(profileName, input) {
|
|
|
15751
16009
|
process.stderr.write(`[task-artifacts] task ${taskArtifacts.taskId} missing: ${missing2.join(", ")}
|
|
15752
16010
|
`);
|
|
15753
16011
|
}
|
|
15754
|
-
|
|
16012
|
+
if (!input.skipConfig && (config.state || config.github.owner && config.github.repo)) {
|
|
16013
|
+
persistTaskArtifactsToState(config, input.cwd, taskArtifacts);
|
|
16014
|
+
}
|
|
16015
|
+
} catch (err) {
|
|
16016
|
+
process.stderr.write(
|
|
16017
|
+
`[task-artifacts] persist failed for task ${taskArtifacts.taskId}: ${err instanceof Error ? err.message : String(err)}
|
|
16018
|
+
`
|
|
16019
|
+
);
|
|
15755
16020
|
}
|
|
15756
16021
|
}
|
|
15757
16022
|
try {
|
|
@@ -15881,17 +16146,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
15881
16146
|
function resolveProfilePath(profileName) {
|
|
15882
16147
|
const found = resolveAgentAction(profileName);
|
|
15883
16148
|
if (found) return found;
|
|
15884
|
-
const here =
|
|
16149
|
+
const here = path41.dirname(new URL(import.meta.url).pathname);
|
|
15885
16150
|
const candidates = [
|
|
15886
|
-
|
|
16151
|
+
path41.join(here, "agent-actions", profileName, "profile.json"),
|
|
15887
16152
|
// same-dir sibling (dev)
|
|
15888
|
-
|
|
16153
|
+
path41.join(here, "..", "agent-actions", profileName, "profile.json"),
|
|
15889
16154
|
// up one (prod: dist/bin → dist/agent-actions)
|
|
15890
|
-
|
|
16155
|
+
path41.join(here, "..", "src", "agent-actions", profileName, "profile.json")
|
|
15891
16156
|
// fallback
|
|
15892
16157
|
];
|
|
15893
16158
|
for (const c of candidates) {
|
|
15894
|
-
if (
|
|
16159
|
+
if (fs42.existsSync(c)) return c;
|
|
15895
16160
|
}
|
|
15896
16161
|
return candidates[0];
|
|
15897
16162
|
}
|
|
@@ -15989,8 +16254,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
15989
16254
|
}
|
|
15990
16255
|
async function runShellEntry(entry, ctx, profile) {
|
|
15991
16256
|
const shellName = entry.shell;
|
|
15992
|
-
const shellPath =
|
|
15993
|
-
if (!
|
|
16257
|
+
const shellPath = path41.join(profile.dir, shellName);
|
|
16258
|
+
if (!fs42.existsSync(shellPath)) {
|
|
15994
16259
|
ctx.skipAgent = true;
|
|
15995
16260
|
ctx.output.exitCode = 99;
|
|
15996
16261
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -16028,14 +16293,14 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
16028
16293
|
let killTimer;
|
|
16029
16294
|
let escalateTimer;
|
|
16030
16295
|
const result = await new Promise(
|
|
16031
|
-
(
|
|
16296
|
+
(resolve8) => {
|
|
16032
16297
|
let settled = false;
|
|
16033
16298
|
const settle = (code, signal, spawnErr) => {
|
|
16034
16299
|
if (settled) return;
|
|
16035
16300
|
settled = true;
|
|
16036
16301
|
if (killTimer) clearTimeout(killTimer);
|
|
16037
16302
|
if (escalateTimer) clearTimeout(escalateTimer);
|
|
16038
|
-
|
|
16303
|
+
resolve8({ code, signal, spawnErr });
|
|
16039
16304
|
};
|
|
16040
16305
|
child.on("error", (err) => settle(null, null, err));
|
|
16041
16306
|
child.on("close", (code, signal) => settle(code, signal));
|
|
@@ -16121,9 +16386,11 @@ var init_executor = __esm({
|
|
|
16121
16386
|
init_litellm();
|
|
16122
16387
|
init_profile();
|
|
16123
16388
|
init_registry();
|
|
16389
|
+
init_runtimePaths();
|
|
16124
16390
|
init_scripts();
|
|
16125
16391
|
init_writeResponsibilityReport();
|
|
16126
16392
|
init_subagents();
|
|
16393
|
+
init_stateWorkspace();
|
|
16127
16394
|
init_task_artifacts();
|
|
16128
16395
|
init_tools();
|
|
16129
16396
|
MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set([
|
|
@@ -16150,7 +16417,7 @@ __export(job_exports, {
|
|
|
16150
16417
|
stableJobKey: () => stableJobKey,
|
|
16151
16418
|
validateJob: () => validateJob
|
|
16152
16419
|
});
|
|
16153
|
-
import * as
|
|
16420
|
+
import * as path42 from "path";
|
|
16154
16421
|
function newJobId(flavor) {
|
|
16155
16422
|
localJobSeq += 1;
|
|
16156
16423
|
const runId = process.env.GITHUB_RUN_ID;
|
|
@@ -16188,7 +16455,7 @@ function validateJob(input) {
|
|
|
16188
16455
|
async function runJob(job, base) {
|
|
16189
16456
|
const valid = validateJob(job);
|
|
16190
16457
|
const action = valid.action ?? valid.agentResponsibility;
|
|
16191
|
-
const projectAgentResponsibilitiesRoot =
|
|
16458
|
+
const projectAgentResponsibilitiesRoot = path42.join(base.cwd, ".kody", "agent-responsibilities");
|
|
16192
16459
|
const resolvedAgentResponsibility = action ? resolveAgentResponsibilityAction(action, projectAgentResponsibilitiesRoot) : null;
|
|
16193
16460
|
const agentResponsibilityIdentity = valid.agentResponsibility ?? resolvedAgentResponsibility?.agentResponsibility;
|
|
16194
16461
|
const agentResponsibilityContext = loadAgentResponsibilityContext(agentResponsibilityIdentity, base.cwd);
|
|
@@ -16248,7 +16515,7 @@ async function runJob(job, base) {
|
|
|
16248
16515
|
}
|
|
16249
16516
|
function loadAgentResponsibilityContext(slug2, cwd) {
|
|
16250
16517
|
if (!slug2) return null;
|
|
16251
|
-
return resolveAgentResponsibilityFolder(slug2,
|
|
16518
|
+
return resolveAgentResponsibilityFolder(slug2, path42.join(cwd, ".kody", "agent-responsibilities"));
|
|
16252
16519
|
}
|
|
16253
16520
|
function mintInstantJob(dispatch2, opts) {
|
|
16254
16521
|
return {
|
|
@@ -16400,9 +16667,9 @@ function translateOpenAISseToBrain(opts) {
|
|
|
16400
16667
|
}
|
|
16401
16668
|
|
|
16402
16669
|
// src/servers/brain-serve.ts
|
|
16403
|
-
import * as
|
|
16670
|
+
import * as fs45 from "fs";
|
|
16404
16671
|
import { createServer } from "http";
|
|
16405
|
-
import * as
|
|
16672
|
+
import * as path45 from "path";
|
|
16406
16673
|
|
|
16407
16674
|
// src/chat/loop.ts
|
|
16408
16675
|
init_agent();
|
|
@@ -16412,6 +16679,7 @@ import * as fs13 from "fs";
|
|
|
16412
16679
|
import * as path15 from "path";
|
|
16413
16680
|
|
|
16414
16681
|
// src/chat/attachments.ts
|
|
16682
|
+
init_runtimePaths();
|
|
16415
16683
|
import * as fs10 from "fs";
|
|
16416
16684
|
import * as path12 from "path";
|
|
16417
16685
|
var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
|
|
@@ -16429,7 +16697,7 @@ function extFor(mime) {
|
|
|
16429
16697
|
return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
|
|
16430
16698
|
}
|
|
16431
16699
|
function attachmentsDir(cwd, sessionId) {
|
|
16432
|
-
return
|
|
16700
|
+
return runtimeStatePath(cwd, "attachments", sessionId);
|
|
16433
16701
|
}
|
|
16434
16702
|
function prepareAttachments(turns, cwd, sessionId) {
|
|
16435
16703
|
const imagePaths = [];
|
|
@@ -16468,9 +16736,13 @@ function prepareAttachments(turns, cwd, sessionId) {
|
|
|
16468
16736
|
// src/chat/events.ts
|
|
16469
16737
|
import * as fs11 from "fs";
|
|
16470
16738
|
import * as path13 from "path";
|
|
16739
|
+
import posixPath2 from "path/posix";
|
|
16471
16740
|
function eventsFilePath(cwd, sessionId) {
|
|
16472
16741
|
return path13.join(cwd, ".kody", "events", `${sessionId}.jsonl`);
|
|
16473
16742
|
}
|
|
16743
|
+
function eventsStatePath(sessionId) {
|
|
16744
|
+
return posixPath2.join("events", `${sessionId}.jsonl`);
|
|
16745
|
+
}
|
|
16474
16746
|
var FileSink = class {
|
|
16475
16747
|
constructor(file) {
|
|
16476
16748
|
this.file = file;
|
|
@@ -16534,9 +16806,13 @@ function makeRunId(sessionId, suffix) {
|
|
|
16534
16806
|
// src/chat/session.ts
|
|
16535
16807
|
import * as fs12 from "fs";
|
|
16536
16808
|
import * as path14 from "path";
|
|
16809
|
+
import posixPath3 from "path/posix";
|
|
16537
16810
|
function sessionFilePath(cwd, sessionId) {
|
|
16538
16811
|
return path14.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
|
|
16539
16812
|
}
|
|
16813
|
+
function sessionStatePath(sessionId) {
|
|
16814
|
+
return posixPath3.join("sessions", `${sessionId}.jsonl`);
|
|
16815
|
+
}
|
|
16540
16816
|
function readMeta(file) {
|
|
16541
16817
|
if (!fs12.existsSync(file)) return null;
|
|
16542
16818
|
const raw = fs12.readFileSync(file, "utf-8");
|
|
@@ -16770,6 +17046,10 @@ async function runChatTurn(opts) {
|
|
|
16770
17046
|
litellmUrl: opts.litellmUrl,
|
|
16771
17047
|
verbose: opts.verbose,
|
|
16772
17048
|
quiet: opts.quiet,
|
|
17049
|
+
additionalDirectories: [
|
|
17050
|
+
taskArtifactsPaths.absDir,
|
|
17051
|
+
...Array.from(new Set(imagePaths.map((p2) => path15.dirname(p2))))
|
|
17052
|
+
],
|
|
16773
17053
|
systemPromptAppend: systemPrompt,
|
|
16774
17054
|
...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {},
|
|
16775
17055
|
// Let the agent clone + work on OTHER repos mid-conversation (a
|
|
@@ -16843,7 +17123,12 @@ async function runChatTurn(opts) {
|
|
|
16843
17123
|
`
|
|
16844
17124
|
);
|
|
16845
17125
|
}
|
|
16846
|
-
|
|
17126
|
+
if (opts.stateConfig) persistTaskArtifactsToState(opts.stateConfig, opts.cwd, taskArtifactsPaths);
|
|
17127
|
+
} catch (err) {
|
|
17128
|
+
process.stderr.write(
|
|
17129
|
+
`[task-artifacts] chat session ${taskArtifactsPaths.taskId} persist failed: ${err instanceof Error ? err.message : String(err)}
|
|
17130
|
+
`
|
|
17131
|
+
);
|
|
16847
17132
|
}
|
|
16848
17133
|
return { exitCode: 0, reply };
|
|
16849
17134
|
}
|
|
@@ -16873,9 +17158,9 @@ function readMemoryIndexBlock(cwd) {
|
|
|
16873
17158
|
}
|
|
16874
17159
|
const trimmed = raw.trim();
|
|
16875
17160
|
if (!trimmed) return "";
|
|
16876
|
-
const body = trimmed.length > MAX_INDEX_BYTES ? trimmed.slice(0, MAX_INDEX_BYTES) + "\n\n_\u2026 (memory index truncated;
|
|
17161
|
+
const body = trimmed.length > MAX_INDEX_BYTES ? trimmed.slice(0, MAX_INDEX_BYTES) + "\n\n_\u2026 (memory index truncated; use recall_search to read more)_" : trimmed;
|
|
16877
17162
|
return [
|
|
16878
|
-
"# Project memory index (
|
|
17163
|
+
"# Project memory index (state repo `memory/INDEX.md`)",
|
|
16879
17164
|
"",
|
|
16880
17165
|
"These are the lessons, decisions, and preferences already captured for this repo. Skim before acting; read individual files only if a line looks relevant to the current task.",
|
|
16881
17166
|
"",
|
|
@@ -16906,9 +17191,9 @@ ${content}`);
|
|
|
16906
17191
|
if (!joined) return "";
|
|
16907
17192
|
const body = joined.length > MAX_CONTEXT_BYTES ? `${joined.slice(0, MAX_CONTEXT_BYTES)}
|
|
16908
17193
|
|
|
16909
|
-
_\u2026 (context truncated;
|
|
17194
|
+
_\u2026 (context truncated; use the state repo context files for the full text)_` : joined;
|
|
16910
17195
|
return [
|
|
16911
|
-
"# Context (
|
|
17196
|
+
"# Context (state repo `context/`) \u2014 your default frame",
|
|
16912
17197
|
"",
|
|
16913
17198
|
"You are this company's in-house assistant, not a general-purpose chatbot. The text below describes who the company is, what it builds, its domain, customers, and vocabulary. This is your DEFAULT and PRIMARY frame: if a question matches or could refer to the company, its product, this repo, or its domain \u2014 even a single bare word or name, any casing or spacing \u2014 answer about THAT directly from this context. Such a question is NOT ambiguous: do NOT lead with or also mention the generic/dictionary meaning, and do NOT ask the user 'which one did you mean?'. Just answer about the company's thing. Give a general-knowledge answer only when the question is plainly unrelated to the company, and keep it brief.",
|
|
16914
17199
|
"",
|
|
@@ -16931,7 +17216,7 @@ function readInstructionsBlock(cwd) {
|
|
|
16931
17216
|
|
|
16932
17217
|
_\u2026 (instructions truncated)_` : trimmed;
|
|
16933
17218
|
return [
|
|
16934
|
-
"# User instructions for this repo (
|
|
17219
|
+
"# User instructions for this repo (state repo `instructions.md`)",
|
|
16935
17220
|
"",
|
|
16936
17221
|
"The user's explicit preferences for how you should behave \u2014 tone, length, formatting. Apply them automatically; they override the default style. If one conflicts with a hard rule above, the hard rule still wins.",
|
|
16937
17222
|
"",
|
|
@@ -16944,8 +17229,8 @@ init_config();
|
|
|
16944
17229
|
|
|
16945
17230
|
// src/kody-cli.ts
|
|
16946
17231
|
import { execFileSync as execFileSync25 } from "child_process";
|
|
16947
|
-
import * as
|
|
16948
|
-
import * as
|
|
17232
|
+
import * as fs43 from "fs";
|
|
17233
|
+
import * as path43 from "path";
|
|
16949
17234
|
|
|
16950
17235
|
// src/app-auth.ts
|
|
16951
17236
|
import { createSign } from "crypto";
|
|
@@ -17431,6 +17716,8 @@ init_gha();
|
|
|
17431
17716
|
init_issue();
|
|
17432
17717
|
init_job();
|
|
17433
17718
|
init_registry();
|
|
17719
|
+
init_runtimePaths();
|
|
17720
|
+
init_stateWorkspace();
|
|
17434
17721
|
var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
|
|
17435
17722
|
|
|
17436
17723
|
Usage:
|
|
@@ -17564,11 +17851,14 @@ async function resolveAuthToken(env = process.env) {
|
|
|
17564
17851
|
return void 0;
|
|
17565
17852
|
}
|
|
17566
17853
|
function detectPackageManager2(cwd) {
|
|
17567
|
-
if (
|
|
17568
|
-
if (
|
|
17569
|
-
if (
|
|
17854
|
+
if (fs43.existsSync(path43.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
17855
|
+
if (fs43.existsSync(path43.join(cwd, "yarn.lock"))) return "yarn";
|
|
17856
|
+
if (fs43.existsSync(path43.join(cwd, "bun.lockb"))) return "bun";
|
|
17570
17857
|
return "npm";
|
|
17571
17858
|
}
|
|
17859
|
+
function shouldChainScheduledWatch(match) {
|
|
17860
|
+
return match.action === "goal-scheduler" || match.agentResponsibility === "goal-scheduler" || match.agentAction === "goal-scheduler";
|
|
17861
|
+
}
|
|
17572
17862
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
17573
17863
|
try {
|
|
17574
17864
|
execFileSync25(cmd, args, {
|
|
@@ -17653,11 +17943,11 @@ function configureGitIdentity(cwd) {
|
|
|
17653
17943
|
}
|
|
17654
17944
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
17655
17945
|
if (!issueNumber) return;
|
|
17656
|
-
const logPath =
|
|
17946
|
+
const logPath = lastRunLogPath(cwd);
|
|
17657
17947
|
let tail = "";
|
|
17658
17948
|
try {
|
|
17659
|
-
if (
|
|
17660
|
-
const content =
|
|
17949
|
+
if (fs43.existsSync(logPath)) {
|
|
17950
|
+
const content = fs43.readFileSync(logPath, "utf-8");
|
|
17661
17951
|
tail = content.slice(-3e3);
|
|
17662
17952
|
}
|
|
17663
17953
|
} catch {
|
|
@@ -17682,11 +17972,12 @@ async function runCi(argv) {
|
|
|
17682
17972
|
return 0;
|
|
17683
17973
|
}
|
|
17684
17974
|
const args = parseCiArgs(argv);
|
|
17685
|
-
const cwd = args.cwd ?
|
|
17975
|
+
const cwd = args.cwd ? path43.resolve(args.cwd) : process.cwd();
|
|
17686
17976
|
let earlyConfig;
|
|
17687
17977
|
let earlyConfigError;
|
|
17688
17978
|
try {
|
|
17689
17979
|
earlyConfig = loadConfig(cwd);
|
|
17980
|
+
hydrateStateWorkspace(earlyConfig, cwd);
|
|
17690
17981
|
} catch (err) {
|
|
17691
17982
|
earlyConfigError = err instanceof Error ? err : new Error(String(err));
|
|
17692
17983
|
}
|
|
@@ -17696,9 +17987,9 @@ async function runCi(argv) {
|
|
|
17696
17987
|
let manualWorkflowDispatch = false;
|
|
17697
17988
|
let forceRunAction = null;
|
|
17698
17989
|
let forceRunCliArgs = {};
|
|
17699
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
17990
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs43.existsSync(dispatchEventPath)) {
|
|
17700
17991
|
try {
|
|
17701
|
-
const evt = JSON.parse(
|
|
17992
|
+
const evt = JSON.parse(fs43.readFileSync(dispatchEventPath, "utf-8"));
|
|
17702
17993
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
17703
17994
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
17704
17995
|
const dutyInput = String(evt?.inputs?.agentResponsibility ?? evt?.inputs?.agentAction ?? "").trim();
|
|
@@ -17829,9 +18120,9 @@ async function runCi(argv) {
|
|
|
17829
18120
|
`);
|
|
17830
18121
|
return 64;
|
|
17831
18122
|
}
|
|
17832
|
-
process.stdout.write(`\u2192 kody: no action for event ${process.env.GITHUB_EVENT_NAME} \u2014
|
|
18123
|
+
process.stdout.write(`\u2192 kody: no action for event ${process.env.GITHUB_EVENT_NAME} \u2014 checking scheduled watches
|
|
17833
18124
|
`);
|
|
17834
|
-
return
|
|
18125
|
+
return runScheduledFanOut(cwd, args, { force: false });
|
|
17835
18126
|
}
|
|
17836
18127
|
if (!args.issueNumber && !autoFallback) {
|
|
17837
18128
|
} else {
|
|
@@ -17985,7 +18276,13 @@ async function runScheduledFanOut(cwd, args, opts) {
|
|
|
17985
18276
|
agentAction: match.agentAction,
|
|
17986
18277
|
cliArgs: match.cliArgs
|
|
17987
18278
|
}),
|
|
17988
|
-
{
|
|
18279
|
+
{
|
|
18280
|
+
cwd,
|
|
18281
|
+
config,
|
|
18282
|
+
verbose: args.verbose,
|
|
18283
|
+
quiet: args.quiet,
|
|
18284
|
+
chain: shouldChainScheduledWatch(match)
|
|
18285
|
+
}
|
|
17989
18286
|
);
|
|
17990
18287
|
if (result.exitCode !== 0) {
|
|
17991
18288
|
process.stderr.write(
|
|
@@ -18023,16 +18320,21 @@ init_litellm();
|
|
|
18023
18320
|
init_repoWorkspace();
|
|
18024
18321
|
|
|
18025
18322
|
// src/scripts/brainTurnLog.ts
|
|
18026
|
-
|
|
18027
|
-
import * as
|
|
18323
|
+
init_runtimePaths();
|
|
18324
|
+
import * as fs44 from "fs";
|
|
18325
|
+
import * as path44 from "path";
|
|
18326
|
+
import posixPath4 from "path/posix";
|
|
18028
18327
|
var live = /* @__PURE__ */ new Map();
|
|
18029
|
-
function
|
|
18030
|
-
return
|
|
18328
|
+
function brainEventsFilePath(dir, chatId) {
|
|
18329
|
+
return runtimeStatePath(dir, "brain-events", `${chatId}.jsonl`);
|
|
18330
|
+
}
|
|
18331
|
+
function brainEventsStatePath(chatId) {
|
|
18332
|
+
return posixPath4.join("brain-events", `${chatId}.jsonl`);
|
|
18031
18333
|
}
|
|
18032
18334
|
function lastPersistedSeq(dir, chatId) {
|
|
18033
|
-
const p =
|
|
18034
|
-
if (!
|
|
18035
|
-
const lines =
|
|
18335
|
+
const p = brainEventsFilePath(dir, chatId);
|
|
18336
|
+
if (!fs44.existsSync(p)) return 0;
|
|
18337
|
+
const lines = fs44.readFileSync(p, "utf-8").split("\n").filter(Boolean);
|
|
18036
18338
|
if (lines.length === 0) return 0;
|
|
18037
18339
|
try {
|
|
18038
18340
|
return JSON.parse(lines[lines.length - 1]).seq || 0;
|
|
@@ -18041,10 +18343,10 @@ function lastPersistedSeq(dir, chatId) {
|
|
|
18041
18343
|
}
|
|
18042
18344
|
}
|
|
18043
18345
|
function readSince(dir, chatId, since) {
|
|
18044
|
-
const p =
|
|
18045
|
-
if (!
|
|
18346
|
+
const p = brainEventsFilePath(dir, chatId);
|
|
18347
|
+
if (!fs44.existsSync(p)) return [];
|
|
18046
18348
|
const out = [];
|
|
18047
|
-
for (const line of
|
|
18349
|
+
for (const line of fs44.readFileSync(p, "utf-8").split("\n")) {
|
|
18048
18350
|
if (!line) continue;
|
|
18049
18351
|
try {
|
|
18050
18352
|
const rec = JSON.parse(line);
|
|
@@ -18069,13 +18371,13 @@ function beginTurn(dir, chatId) {
|
|
|
18069
18371
|
subscribers: /* @__PURE__ */ new Set()
|
|
18070
18372
|
};
|
|
18071
18373
|
live.set(chatId, state);
|
|
18072
|
-
const p =
|
|
18073
|
-
|
|
18374
|
+
const p = brainEventsFilePath(dir, chatId);
|
|
18375
|
+
fs44.mkdirSync(path44.dirname(p), { recursive: true });
|
|
18074
18376
|
return (event) => {
|
|
18075
18377
|
state.seq += 1;
|
|
18076
18378
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
18077
18379
|
try {
|
|
18078
|
-
|
|
18380
|
+
fs44.appendFileSync(p, `${JSON.stringify(rec)}
|
|
18079
18381
|
`);
|
|
18080
18382
|
} catch (err) {
|
|
18081
18383
|
process.stderr.write(
|
|
@@ -18114,7 +18416,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
|
|
|
18114
18416
|
event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
|
|
18115
18417
|
};
|
|
18116
18418
|
try {
|
|
18117
|
-
|
|
18419
|
+
fs44.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
|
|
18118
18420
|
`);
|
|
18119
18421
|
} catch {
|
|
18120
18422
|
}
|
|
@@ -18183,6 +18485,7 @@ function getLastSeq(dir, chatId) {
|
|
|
18183
18485
|
}
|
|
18184
18486
|
|
|
18185
18487
|
// src/servers/brain-serve.ts
|
|
18488
|
+
init_stateRepoGithub();
|
|
18186
18489
|
var DEFAULT_PORT = 8080;
|
|
18187
18490
|
function getApiKey() {
|
|
18188
18491
|
const key = (process.env.BRAIN_API_KEY ?? "").trim();
|
|
@@ -18207,17 +18510,17 @@ function authOk(req, expected) {
|
|
|
18207
18510
|
return false;
|
|
18208
18511
|
}
|
|
18209
18512
|
function readJsonBody(req) {
|
|
18210
|
-
return new Promise((
|
|
18513
|
+
return new Promise((resolve8, reject) => {
|
|
18211
18514
|
const chunks = [];
|
|
18212
18515
|
req.on("data", (c) => chunks.push(c));
|
|
18213
18516
|
req.on("end", () => {
|
|
18214
18517
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
18215
18518
|
if (!raw.trim()) {
|
|
18216
|
-
|
|
18519
|
+
resolve8({});
|
|
18217
18520
|
return;
|
|
18218
18521
|
}
|
|
18219
18522
|
try {
|
|
18220
|
-
|
|
18523
|
+
resolve8(JSON.parse(raw));
|
|
18221
18524
|
} catch (err) {
|
|
18222
18525
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
18223
18526
|
}
|
|
@@ -18249,6 +18552,25 @@ function emitSse(res, event) {
|
|
|
18249
18552
|
|
|
18250
18553
|
`);
|
|
18251
18554
|
}
|
|
18555
|
+
function envGithubToken() {
|
|
18556
|
+
return (process.env.KODY_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.GH_PAT ?? "").trim();
|
|
18557
|
+
}
|
|
18558
|
+
function parseRepoSlug(repo) {
|
|
18559
|
+
if (!repo) return null;
|
|
18560
|
+
const [owner, name] = repo.split("/", 2);
|
|
18561
|
+
if (!owner || !name) return null;
|
|
18562
|
+
return { owner, repo: name };
|
|
18563
|
+
}
|
|
18564
|
+
function streamStartupError(res, dir, chatId, err) {
|
|
18565
|
+
const sinceFloor = getLastSeq(dir, chatId);
|
|
18566
|
+
const emitToLog = beginTurn(dir, chatId);
|
|
18567
|
+
emitToLog({
|
|
18568
|
+
type: "error",
|
|
18569
|
+
chatId,
|
|
18570
|
+
error: err instanceof Error ? err.message : String(err)
|
|
18571
|
+
});
|
|
18572
|
+
streamToRes(res, dir, chatId, sinceFloor);
|
|
18573
|
+
}
|
|
18252
18574
|
function translateChatEvent(event, chatId) {
|
|
18253
18575
|
switch (event.event) {
|
|
18254
18576
|
case "chat.message": {
|
|
@@ -18344,25 +18666,73 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
18344
18666
|
}
|
|
18345
18667
|
const repo = strField(body, "repo");
|
|
18346
18668
|
const repoToken = strField(body, "repoToken");
|
|
18347
|
-
|
|
18348
|
-
|
|
18669
|
+
let agentCwd;
|
|
18670
|
+
try {
|
|
18671
|
+
agentCwd = await ensureRepoCwd({
|
|
18672
|
+
baseCwd: opts.cwd,
|
|
18673
|
+
reposRoot: opts.reposRoot,
|
|
18674
|
+
repo,
|
|
18675
|
+
repoToken,
|
|
18676
|
+
cloneRepo: opts.cloneRepo
|
|
18677
|
+
});
|
|
18678
|
+
} catch (err) {
|
|
18679
|
+
streamStartupError(res, opts.cwd, chatId, err);
|
|
18680
|
+
return;
|
|
18681
|
+
}
|
|
18682
|
+
const stateToken = repoToken || envGithubToken();
|
|
18683
|
+
const parsedRepo = parseRepoSlug(repo);
|
|
18684
|
+
let stateConfig = null;
|
|
18685
|
+
if (parsedRepo && stateToken) {
|
|
18686
|
+
try {
|
|
18687
|
+
stateConfig = await loadGithubStateConfig({
|
|
18688
|
+
owner: parsedRepo.owner,
|
|
18689
|
+
repo: parsedRepo.repo,
|
|
18690
|
+
githubToken: stateToken,
|
|
18691
|
+
fetchImpl: opts.stateFetch
|
|
18692
|
+
});
|
|
18693
|
+
} catch (err) {
|
|
18694
|
+
process.stderr.write(
|
|
18695
|
+
`[brain-serve] state config unavailable for ${repo}: ${err instanceof Error ? err.message : String(err)}
|
|
18696
|
+
`
|
|
18697
|
+
);
|
|
18698
|
+
}
|
|
18699
|
+
}
|
|
18700
|
+
const sessionFile = sessionFilePath(agentCwd, chatId);
|
|
18701
|
+
const brainEventsFile = brainEventsFilePath(agentCwd, chatId);
|
|
18702
|
+
if (stateConfig && stateToken) {
|
|
18703
|
+
try {
|
|
18704
|
+
await syncJsonlFileFromGithubState({
|
|
18705
|
+
config: stateConfig,
|
|
18706
|
+
filePath: sessionStatePath(chatId),
|
|
18707
|
+
localPath: sessionFile,
|
|
18708
|
+
githubToken: stateToken,
|
|
18709
|
+
fetchImpl: opts.stateFetch
|
|
18710
|
+
});
|
|
18711
|
+
await syncJsonlFileFromGithubState({
|
|
18712
|
+
config: stateConfig,
|
|
18713
|
+
filePath: brainEventsStatePath(chatId),
|
|
18714
|
+
localPath: brainEventsFile,
|
|
18715
|
+
githubToken: stateToken,
|
|
18716
|
+
fetchImpl: opts.stateFetch
|
|
18717
|
+
});
|
|
18718
|
+
} catch (err) {
|
|
18719
|
+
process.stderr.write(
|
|
18720
|
+
`[brain-serve] state sync failed for ${repo ?? "local"}:${chatId}: ${err instanceof Error ? err.message : String(err)}
|
|
18721
|
+
`
|
|
18722
|
+
);
|
|
18723
|
+
}
|
|
18724
|
+
}
|
|
18725
|
+
fs45.mkdirSync(path45.dirname(sessionFile), { recursive: true });
|
|
18349
18726
|
appendTurn(sessionFile, {
|
|
18350
18727
|
role: "user",
|
|
18351
18728
|
content: message,
|
|
18352
18729
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
18353
18730
|
});
|
|
18354
|
-
const sinceFloor = getLastSeq(
|
|
18355
|
-
const emitToLog = beginTurn(
|
|
18731
|
+
const sinceFloor = getLastSeq(agentCwd, chatId);
|
|
18732
|
+
const emitToLog = beginTurn(agentCwd, chatId);
|
|
18356
18733
|
const sink = new BrokerSink(emitToLog, chatId);
|
|
18357
18734
|
void enqueue(chatId, async () => {
|
|
18358
18735
|
try {
|
|
18359
|
-
const agentCwd = await ensureRepoCwd({
|
|
18360
|
-
baseCwd: opts.cwd,
|
|
18361
|
-
reposRoot: opts.reposRoot,
|
|
18362
|
-
repo,
|
|
18363
|
-
repoToken,
|
|
18364
|
-
cloneRepo: opts.cloneRepo
|
|
18365
|
-
});
|
|
18366
18736
|
await opts.runTurn({
|
|
18367
18737
|
sessionId: chatId,
|
|
18368
18738
|
sessionFile,
|
|
@@ -18378,21 +18748,46 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
18378
18748
|
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
18379
18749
|
process.stderr.write(`[brain-serve] chat turn failed: ${errMsg3}
|
|
18380
18750
|
`);
|
|
18381
|
-
endTurnIfUnterminated(
|
|
18751
|
+
endTurnIfUnterminated(agentCwd, chatId, errMsg3);
|
|
18382
18752
|
} finally {
|
|
18383
18753
|
endTurnIfUnterminated(
|
|
18384
|
-
|
|
18754
|
+
agentCwd,
|
|
18385
18755
|
chatId,
|
|
18386
18756
|
"Brain turn ended without a reply (the machine may have restarted mid-turn) \u2014 please resend your message"
|
|
18387
18757
|
);
|
|
18758
|
+
if (stateConfig && stateToken) {
|
|
18759
|
+
try {
|
|
18760
|
+
await persistJsonlFileToGithubState({
|
|
18761
|
+
config: stateConfig,
|
|
18762
|
+
filePath: sessionStatePath(chatId),
|
|
18763
|
+
localPath: sessionFile,
|
|
18764
|
+
message: `brain: session ${chatId}`,
|
|
18765
|
+
githubToken: stateToken,
|
|
18766
|
+
fetchImpl: opts.stateFetch
|
|
18767
|
+
});
|
|
18768
|
+
await persistJsonlFileToGithubState({
|
|
18769
|
+
config: stateConfig,
|
|
18770
|
+
filePath: brainEventsStatePath(chatId),
|
|
18771
|
+
localPath: brainEventsFile,
|
|
18772
|
+
message: `brain: events ${chatId}`,
|
|
18773
|
+
githubToken: stateToken,
|
|
18774
|
+
fetchImpl: opts.stateFetch
|
|
18775
|
+
});
|
|
18776
|
+
} catch (err) {
|
|
18777
|
+
process.stderr.write(
|
|
18778
|
+
`[brain-serve] state persist failed for ${repo ?? "local"}:${chatId}: ${err instanceof Error ? err.message : String(err)}
|
|
18779
|
+
`
|
|
18780
|
+
);
|
|
18781
|
+
}
|
|
18782
|
+
}
|
|
18388
18783
|
}
|
|
18389
18784
|
});
|
|
18390
|
-
streamToRes(res,
|
|
18785
|
+
streamToRes(res, agentCwd, chatId, sinceFloor);
|
|
18391
18786
|
}
|
|
18392
18787
|
function buildServer(opts) {
|
|
18393
18788
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
18394
18789
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
18395
|
-
const reposRoot = opts.reposRoot ??
|
|
18790
|
+
const reposRoot = opts.reposRoot ?? path45.join(path45.dirname(path45.resolve(opts.cwd)), "repos");
|
|
18396
18791
|
return createServer(async (req, res) => {
|
|
18397
18792
|
if (!req.method || !req.url) {
|
|
18398
18793
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -18420,7 +18815,8 @@ function buildServer(opts) {
|
|
|
18420
18815
|
cloneRepo,
|
|
18421
18816
|
model: opts.model,
|
|
18422
18817
|
litellmUrl: opts.litellmUrl,
|
|
18423
|
-
runTurn
|
|
18818
|
+
runTurn,
|
|
18819
|
+
stateFetch: opts.stateFetch
|
|
18424
18820
|
});
|
|
18425
18821
|
return;
|
|
18426
18822
|
}
|
|
@@ -18466,11 +18862,11 @@ async function brainServe(opts) {
|
|
|
18466
18862
|
model,
|
|
18467
18863
|
litellmUrl
|
|
18468
18864
|
});
|
|
18469
|
-
await new Promise((
|
|
18865
|
+
await new Promise((resolve8) => {
|
|
18470
18866
|
server.listen(port, "0.0.0.0", () => {
|
|
18471
18867
|
process.stdout.write(`[brain-serve] listening on 0.0.0.0:${port} (cwd=${opts.cwd})
|
|
18472
18868
|
`);
|
|
18473
|
-
|
|
18869
|
+
resolve8();
|
|
18474
18870
|
});
|
|
18475
18871
|
});
|
|
18476
18872
|
const shutdown = (signal) => {
|
|
@@ -18720,14 +19116,14 @@ async function startBrainProxy(opts) {
|
|
|
18720
19116
|
const { httpServer, handler } = buildBrainProxy(opts);
|
|
18721
19117
|
const port = opts.port ?? 0;
|
|
18722
19118
|
const host = opts.host ?? "127.0.0.1";
|
|
18723
|
-
await new Promise((
|
|
19119
|
+
await new Promise((resolve8) => httpServer.listen(port, host, () => resolve8()));
|
|
18724
19120
|
const addr = httpServer.address();
|
|
18725
19121
|
return {
|
|
18726
19122
|
httpServer,
|
|
18727
19123
|
port: addr.port,
|
|
18728
19124
|
url: `http://${host}:${addr.port}`,
|
|
18729
|
-
stop: () => new Promise((
|
|
18730
|
-
httpServer.close(() =>
|
|
19125
|
+
stop: () => new Promise((resolve8) => {
|
|
19126
|
+
httpServer.close(() => resolve8());
|
|
18731
19127
|
}),
|
|
18732
19128
|
handler
|
|
18733
19129
|
};
|
|
@@ -18877,23 +19273,23 @@ function buildMcpHttpServer(opts) {
|
|
|
18877
19273
|
httpServer,
|
|
18878
19274
|
routes,
|
|
18879
19275
|
port,
|
|
18880
|
-
stop: () => new Promise((
|
|
19276
|
+
stop: () => new Promise((resolve8) => {
|
|
18881
19277
|
let pending = transports.size;
|
|
18882
19278
|
if (pending === 0) {
|
|
18883
|
-
httpServer.close(() =>
|
|
19279
|
+
httpServer.close(() => resolve8());
|
|
18884
19280
|
return;
|
|
18885
19281
|
}
|
|
18886
19282
|
for (const transport of transports.values()) {
|
|
18887
19283
|
void transport.close().finally(() => {
|
|
18888
19284
|
pending--;
|
|
18889
|
-
if (pending === 0) httpServer.close(() =>
|
|
19285
|
+
if (pending === 0) httpServer.close(() => resolve8());
|
|
18890
19286
|
});
|
|
18891
19287
|
}
|
|
18892
19288
|
})
|
|
18893
19289
|
};
|
|
18894
19290
|
}
|
|
18895
19291
|
function listenMcpHttpServer(server, host = "127.0.0.1") {
|
|
18896
|
-
return new Promise((
|
|
19292
|
+
return new Promise((resolve8, reject) => {
|
|
18897
19293
|
server.httpServer.once("error", reject);
|
|
18898
19294
|
server.httpServer.listen(server.port, host, () => {
|
|
18899
19295
|
server.httpServer.off("error", reject);
|
|
@@ -18901,7 +19297,7 @@ function listenMcpHttpServer(server, host = "127.0.0.1") {
|
|
|
18901
19297
|
if (addr && typeof addr === "object") {
|
|
18902
19298
|
server.port = addr.port;
|
|
18903
19299
|
}
|
|
18904
|
-
|
|
19300
|
+
resolve8();
|
|
18905
19301
|
});
|
|
18906
19302
|
});
|
|
18907
19303
|
}
|
|
@@ -18986,15 +19382,8 @@ async function loadConfigSafe() {
|
|
|
18986
19382
|
}
|
|
18987
19383
|
|
|
18988
19384
|
// src/chat-cli.ts
|
|
18989
|
-
import
|
|
18990
|
-
import * as
|
|
18991
|
-
import * as path45 from "path";
|
|
18992
|
-
|
|
18993
|
-
// src/chat/modes/interactive.ts
|
|
18994
|
-
init_issue();
|
|
18995
|
-
import { execFileSync as execFileSync27 } from "child_process";
|
|
18996
|
-
import * as fs44 from "fs";
|
|
18997
|
-
import * as path44 from "path";
|
|
19385
|
+
import * as fs47 from "fs";
|
|
19386
|
+
import * as path47 from "path";
|
|
18998
19387
|
|
|
18999
19388
|
// src/chat/inbox.ts
|
|
19000
19389
|
import { execFileSync as execFileSync26 } from "child_process";
|
|
@@ -19010,7 +19399,14 @@ async function waitForNextUserMessage(opts) {
|
|
|
19010
19399
|
const now = Date.now();
|
|
19011
19400
|
if (now >= opts.deadlineMs) return { kind: "deadline" };
|
|
19012
19401
|
if (now - idleStart >= opts.idleTimeoutMs) return { kind: "idle-timeout" };
|
|
19013
|
-
if (!opts.skipPull) {
|
|
19402
|
+
if (opts.sync && !opts.skipPull) {
|
|
19403
|
+
try {
|
|
19404
|
+
opts.sync();
|
|
19405
|
+
} catch (err) {
|
|
19406
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19407
|
+
logger.warn(`state sync failed (will retry): ${msg}`);
|
|
19408
|
+
}
|
|
19409
|
+
} else if (!opts.skipPull) {
|
|
19014
19410
|
try {
|
|
19015
19411
|
const branch = currentBranch(opts.cwd);
|
|
19016
19412
|
if (branch) {
|
|
@@ -19042,7 +19438,7 @@ async function waitForNextUserMessage(opts) {
|
|
|
19042
19438
|
}
|
|
19043
19439
|
}
|
|
19044
19440
|
function sleep3(ms) {
|
|
19045
|
-
return new Promise((
|
|
19441
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
19046
19442
|
}
|
|
19047
19443
|
function currentBranch(cwd) {
|
|
19048
19444
|
try {
|
|
@@ -19057,6 +19453,87 @@ function currentBranch(cwd) {
|
|
|
19057
19453
|
}
|
|
19058
19454
|
}
|
|
19059
19455
|
|
|
19456
|
+
// src/chat/state-sync.ts
|
|
19457
|
+
init_stateRepo();
|
|
19458
|
+
import * as fs46 from "fs";
|
|
19459
|
+
import * as path46 from "path";
|
|
19460
|
+
function jsonlLines2(text) {
|
|
19461
|
+
return text.split("\n").filter((line) => line.length > 0);
|
|
19462
|
+
}
|
|
19463
|
+
function renderJsonl2(lines) {
|
|
19464
|
+
return lines.length > 0 ? `${lines.join("\n")}
|
|
19465
|
+
` : "";
|
|
19466
|
+
}
|
|
19467
|
+
function mergeJsonl2(localText, remoteText) {
|
|
19468
|
+
const localLines = jsonlLines2(localText);
|
|
19469
|
+
const seen = new Set(localLines);
|
|
19470
|
+
const remoteOnly = jsonlLines2(remoteText).filter((line) => !seen.has(line));
|
|
19471
|
+
return renderJsonl2([...localLines, ...remoteOnly]);
|
|
19472
|
+
}
|
|
19473
|
+
function syncJsonlFileFromState(opts) {
|
|
19474
|
+
const remote = readStateText(opts.config, opts.cwd, opts.statePath);
|
|
19475
|
+
if (!remote) return;
|
|
19476
|
+
const local = fs46.existsSync(opts.localPath) ? fs46.readFileSync(opts.localPath, "utf-8") : "";
|
|
19477
|
+
const next = mergeJsonl2(local, remote.content);
|
|
19478
|
+
if (next === local) return;
|
|
19479
|
+
fs46.mkdirSync(path46.dirname(opts.localPath), { recursive: true });
|
|
19480
|
+
fs46.writeFileSync(opts.localPath, next);
|
|
19481
|
+
}
|
|
19482
|
+
function persistJsonlFileToState(opts) {
|
|
19483
|
+
if (!fs46.existsSync(opts.localPath)) return;
|
|
19484
|
+
const localText = fs46.readFileSync(opts.localPath, "utf-8");
|
|
19485
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
19486
|
+
const remote = readStateText(opts.config, opts.cwd, opts.statePath);
|
|
19487
|
+
const body = mergeJsonl2(localText, remote?.content ?? "");
|
|
19488
|
+
try {
|
|
19489
|
+
writeStateText(opts.config, opts.cwd, opts.statePath, body, opts.message, remote?.sha);
|
|
19490
|
+
return;
|
|
19491
|
+
} catch (err) {
|
|
19492
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19493
|
+
const conflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|is at|but expected/i.test(msg);
|
|
19494
|
+
if (!conflict || attempt === 3) throw err;
|
|
19495
|
+
}
|
|
19496
|
+
}
|
|
19497
|
+
}
|
|
19498
|
+
function syncChatFilesFromState(config, cwd, sessionId) {
|
|
19499
|
+
syncJsonlFileFromState({
|
|
19500
|
+
config,
|
|
19501
|
+
cwd,
|
|
19502
|
+
statePath: sessionStatePath(sessionId),
|
|
19503
|
+
localPath: sessionFilePath(cwd, sessionId)
|
|
19504
|
+
});
|
|
19505
|
+
syncJsonlFileFromState({
|
|
19506
|
+
config,
|
|
19507
|
+
cwd,
|
|
19508
|
+
statePath: eventsStatePath(sessionId),
|
|
19509
|
+
localPath: eventsFilePath(cwd, sessionId)
|
|
19510
|
+
});
|
|
19511
|
+
}
|
|
19512
|
+
function syncChatSessionFromState(config, cwd, sessionId) {
|
|
19513
|
+
syncJsonlFileFromState({
|
|
19514
|
+
config,
|
|
19515
|
+
cwd,
|
|
19516
|
+
statePath: sessionStatePath(sessionId),
|
|
19517
|
+
localPath: sessionFilePath(cwd, sessionId)
|
|
19518
|
+
});
|
|
19519
|
+
}
|
|
19520
|
+
function persistChatFilesToState(config, cwd, sessionId, message = `chat: reply for ${sessionId}`) {
|
|
19521
|
+
persistJsonlFileToState({
|
|
19522
|
+
config,
|
|
19523
|
+
cwd,
|
|
19524
|
+
statePath: sessionStatePath(sessionId),
|
|
19525
|
+
localPath: sessionFilePath(cwd, sessionId),
|
|
19526
|
+
message
|
|
19527
|
+
});
|
|
19528
|
+
persistJsonlFileToState({
|
|
19529
|
+
config,
|
|
19530
|
+
cwd,
|
|
19531
|
+
statePath: eventsStatePath(sessionId),
|
|
19532
|
+
localPath: eventsFilePath(cwd, sessionId),
|
|
19533
|
+
message
|
|
19534
|
+
});
|
|
19535
|
+
}
|
|
19536
|
+
|
|
19060
19537
|
// src/chat/modes/interactive.ts
|
|
19061
19538
|
var DEFAULT_IDLE_EXIT_MS = 5 * 6e4;
|
|
19062
19539
|
var DEFAULT_HARD_CAP_MS = 30 * 6e4;
|
|
@@ -19086,13 +19563,16 @@ async function runInteractiveMode(opts) {
|
|
|
19086
19563
|
if (!opts.skipGit) {
|
|
19087
19564
|
process.stdout.write(`\u2192 kody:chat:interactive: committing chat.ready event to git
|
|
19088
19565
|
`);
|
|
19089
|
-
commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false);
|
|
19566
|
+
commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false, opts.stateConfig ?? null);
|
|
19090
19567
|
process.stdout.write(`\u2192 kody:chat:interactive: chat.ready committed; entering poll loop
|
|
19091
19568
|
`);
|
|
19092
19569
|
}
|
|
19093
19570
|
let watermark = 0;
|
|
19094
19571
|
let turnsCompleted = 0;
|
|
19095
19572
|
while (true) {
|
|
19573
|
+
if (opts.stateConfig && !opts.skipGit) {
|
|
19574
|
+
syncChatSessionFromState(opts.stateConfig, opts.cwd, opts.sessionId);
|
|
19575
|
+
}
|
|
19096
19576
|
const turns = readSession(sessionFile);
|
|
19097
19577
|
const pendingIdx = findNextUserTurn(turns, watermark);
|
|
19098
19578
|
if (pendingIdx === -1) {
|
|
@@ -19103,16 +19583,19 @@ async function runInteractiveMode(opts) {
|
|
|
19103
19583
|
idleTimeoutMs: idleExitMs,
|
|
19104
19584
|
deadlineMs,
|
|
19105
19585
|
pollIntervalMs: opts.pollIntervalMs ?? DEFAULT_POLL_MS2,
|
|
19106
|
-
skipPull: opts.skipGit
|
|
19586
|
+
skipPull: opts.skipGit,
|
|
19587
|
+
...opts.stateConfig ? {
|
|
19588
|
+
sync: () => syncChatSessionFromState(opts.stateConfig, opts.cwd, opts.sessionId)
|
|
19589
|
+
} : {}
|
|
19107
19590
|
});
|
|
19108
19591
|
if (result.kind === "idle-timeout") {
|
|
19109
19592
|
await emitExit(opts, "idle-timeout", turnsCompleted);
|
|
19110
|
-
if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false);
|
|
19593
|
+
if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false, opts.stateConfig ?? null);
|
|
19111
19594
|
return { exitCode: 0, reason: "idle-timeout", turnsCompleted };
|
|
19112
19595
|
}
|
|
19113
19596
|
if (result.kind === "deadline") {
|
|
19114
19597
|
await emitExit(opts, "deadline", turnsCompleted);
|
|
19115
|
-
if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false);
|
|
19598
|
+
if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false, opts.stateConfig ?? null);
|
|
19116
19599
|
return { exitCode: 0, reason: "deadline", turnsCompleted };
|
|
19117
19600
|
}
|
|
19118
19601
|
}
|
|
@@ -19128,20 +19611,21 @@ async function runInteractiveMode(opts) {
|
|
|
19128
19611
|
verbose: opts.verbose,
|
|
19129
19612
|
quiet: opts.quiet,
|
|
19130
19613
|
invokeAgent: opts.invokeAgent,
|
|
19614
|
+
stateConfig: opts.stateConfig,
|
|
19131
19615
|
...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {}
|
|
19132
19616
|
});
|
|
19133
19617
|
} catch (err) {
|
|
19134
19618
|
const msg = err instanceof Error ? err.message : String(err);
|
|
19135
19619
|
await emit2(opts.sink, "chat.error", opts.sessionId, `loop-${turnsCompleted}`, { error: msg });
|
|
19136
19620
|
await emitExit(opts, "fatal", turnsCompleted);
|
|
19137
|
-
if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false);
|
|
19621
|
+
if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false, opts.stateConfig ?? null);
|
|
19138
19622
|
return { exitCode: 99, reason: "fatal", turnsCompleted };
|
|
19139
19623
|
}
|
|
19140
19624
|
if (turnResult.exitCode === 64) {
|
|
19141
19625
|
} else if (turnResult.exitCode !== 0) {
|
|
19142
19626
|
} else {
|
|
19143
19627
|
turnsCompleted += 1;
|
|
19144
|
-
if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false);
|
|
19628
|
+
if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false, opts.stateConfig ?? null);
|
|
19145
19629
|
}
|
|
19146
19630
|
watermark = readSession(sessionFile).length;
|
|
19147
19631
|
}
|
|
@@ -19153,93 +19637,12 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
19153
19637
|
if (turns.length > 0 && turns[turns.length - 1].role === "user") return turns.length - 1;
|
|
19154
19638
|
return -1;
|
|
19155
19639
|
}
|
|
19156
|
-
function commitTurn(cwd, sessionId, _verbose) {
|
|
19157
|
-
|
|
19158
|
-
|
|
19159
|
-
const rels = [sessionRel, eventsRel].filter((p) => fs44.existsSync(path44.join(cwd, p)));
|
|
19160
|
-
if (rels.length === 0) return;
|
|
19161
|
-
const repository = process.env.GITHUB_REPOSITORY;
|
|
19162
|
-
if (!repository) {
|
|
19163
|
-
process.stderr.write(
|
|
19164
|
-
`[kody:chat:interactive] GITHUB_REPOSITORY unset; cannot persist session/events via Contents API
|
|
19165
|
-
`
|
|
19166
|
-
);
|
|
19640
|
+
function commitTurn(cwd, sessionId, _verbose, stateConfig) {
|
|
19641
|
+
if (stateConfig) {
|
|
19642
|
+
persistChatFilesToState(stateConfig, cwd, sessionId, `chat: interactive turn for ${sessionId}`);
|
|
19167
19643
|
return;
|
|
19168
19644
|
}
|
|
19169
|
-
|
|
19170
|
-
for (const rel of rels) {
|
|
19171
|
-
const repoPath = rel.split(path44.sep).join("/");
|
|
19172
|
-
const localText = fs44.readFileSync(path44.join(cwd, rel), "utf-8");
|
|
19173
|
-
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
19174
|
-
}
|
|
19175
|
-
}
|
|
19176
|
-
function jsonlLines(text) {
|
|
19177
|
-
return text.split("\n").filter((l) => l.length > 0);
|
|
19178
|
-
}
|
|
19179
|
-
function getRemoteBlob(repository, branch, repoPath, cwd) {
|
|
19180
|
-
try {
|
|
19181
|
-
const raw = gh(["api", `/repos/${repository}/contents/${repoPath}?ref=${encodeURIComponent(branch)}`], { cwd });
|
|
19182
|
-
const o = JSON.parse(raw);
|
|
19183
|
-
if (o.type === "file" && o.encoding === "base64" && typeof o.content === "string" && typeof o.sha === "string") {
|
|
19184
|
-
return { sha: o.sha, lines: jsonlLines(Buffer.from(o.content, "base64").toString("utf-8")) };
|
|
19185
|
-
}
|
|
19186
|
-
return { sha: null, lines: [] };
|
|
19187
|
-
} catch (err) {
|
|
19188
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
19189
|
-
if (/HTTP 404/i.test(msg) || /Not Found/i.test(msg)) return { sha: null, lines: [] };
|
|
19190
|
-
throw err;
|
|
19191
|
-
}
|
|
19192
|
-
}
|
|
19193
|
-
function putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd) {
|
|
19194
|
-
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
19195
|
-
const remote = getRemoteBlob(repository, branch, repoPath, cwd);
|
|
19196
|
-
let body = localText;
|
|
19197
|
-
if (attempt > 1 && remote.lines.length > 0) {
|
|
19198
|
-
const localLines = jsonlLines(localText);
|
|
19199
|
-
const localSet = new Set(localLines);
|
|
19200
|
-
const extra = remote.lines.filter((l) => !localSet.has(l));
|
|
19201
|
-
if (extra.length > 0) body = `${[...localLines, ...extra].join("\n")}
|
|
19202
|
-
`;
|
|
19203
|
-
}
|
|
19204
|
-
const payload = {
|
|
19205
|
-
message: `chat: interactive turn for ${sessionId}`,
|
|
19206
|
-
content: Buffer.from(body, "utf-8").toString("base64"),
|
|
19207
|
-
branch
|
|
19208
|
-
};
|
|
19209
|
-
if (remote.sha) payload.sha = remote.sha;
|
|
19210
|
-
try {
|
|
19211
|
-
gh(["api", "--method", "PUT", `/repos/${repository}/contents/${repoPath}`, "--input", "-"], {
|
|
19212
|
-
cwd,
|
|
19213
|
-
input: JSON.stringify(payload)
|
|
19214
|
-
});
|
|
19215
|
-
return;
|
|
19216
|
-
} catch (err) {
|
|
19217
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
19218
|
-
const isConflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
19219
|
-
if (!isConflict || attempt === 3) {
|
|
19220
|
-
process.stderr.write(`[kody:chat:interactive] Contents PUT failed (${repoPath}, attempt ${attempt}): ${msg}
|
|
19221
|
-
`);
|
|
19222
|
-
return;
|
|
19223
|
-
}
|
|
19224
|
-
process.stderr.write(
|
|
19225
|
-
`[kody:chat:interactive] Contents PUT conflict (${repoPath}, attempt ${attempt}); refetch+union+retry
|
|
19226
|
-
`
|
|
19227
|
-
);
|
|
19228
|
-
}
|
|
19229
|
-
}
|
|
19230
|
-
}
|
|
19231
|
-
function defaultBranch(cwd) {
|
|
19232
|
-
try {
|
|
19233
|
-
const out = execFileSync27("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], {
|
|
19234
|
-
cwd,
|
|
19235
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
19236
|
-
});
|
|
19237
|
-
const symbolic = out.toString("utf-8").trim();
|
|
19238
|
-
if (symbolic.startsWith("origin/")) return symbolic.slice("origin/".length);
|
|
19239
|
-
return symbolic || null;
|
|
19240
|
-
} catch {
|
|
19241
|
-
return null;
|
|
19242
|
-
}
|
|
19645
|
+
throw new Error(`kody chat interactive requires state repo config for session ${sessionId}`);
|
|
19243
19646
|
}
|
|
19244
19647
|
async function emitExit(opts, reason, turnsCompleted) {
|
|
19245
19648
|
await emit2(opts.sink, "chat.exit", opts.sessionId, "exit", {
|
|
@@ -19261,7 +19664,7 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
19261
19664
|
// src/chat-cli.ts
|
|
19262
19665
|
init_config();
|
|
19263
19666
|
init_litellm();
|
|
19264
|
-
|
|
19667
|
+
init_stateWorkspace();
|
|
19265
19668
|
var DEFAULT_MODEL2 = "claude/claude-haiku-4-5-20251001";
|
|
19266
19669
|
var CHAT_HELP = `kody chat \u2014 dashboard-driven chat session
|
|
19267
19670
|
|
|
@@ -19313,31 +19716,6 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
19313
19716
|
}
|
|
19314
19717
|
return result;
|
|
19315
19718
|
}
|
|
19316
|
-
function commitChatFiles(cwd, sessionId, verbose) {
|
|
19317
|
-
const sessionFile = path45.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
19318
|
-
const eventsFile = path45.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
19319
|
-
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
19320
|
-
const tasksDir = path45.join(".kody", "tasks", safeSession);
|
|
19321
|
-
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
19322
|
-
const paths = candidatePaths.filter((p) => fs45.existsSync(path45.join(cwd, p)));
|
|
19323
|
-
if (paths.length === 0) return;
|
|
19324
|
-
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
19325
|
-
try {
|
|
19326
|
-
execFileSync28("git", ["add", "-f", ...paths], opts);
|
|
19327
|
-
execFileSync28("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
|
|
19328
|
-
} catch (err) {
|
|
19329
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
19330
|
-
process.stderr.write(`[kody:chat] commit skipped: ${msg}
|
|
19331
|
-
`);
|
|
19332
|
-
return;
|
|
19333
|
-
}
|
|
19334
|
-
const result = pushWithRetry({ cwd });
|
|
19335
|
-
if (!result.ok) {
|
|
19336
|
-
process.stderr.write(`[kody:chat] push FAILED after ${result.attempts} attempt(s): ${result.reason}
|
|
19337
|
-
`);
|
|
19338
|
-
throw new Error(`chat push failed: ${result.reason}`);
|
|
19339
|
-
}
|
|
19340
|
-
}
|
|
19341
19719
|
function tryLoadConfig(cwd) {
|
|
19342
19720
|
try {
|
|
19343
19721
|
return loadConfig(cwd);
|
|
@@ -19363,7 +19741,7 @@ async function runChat(argv) {
|
|
|
19363
19741
|
${CHAT_HELP}`);
|
|
19364
19742
|
return 64;
|
|
19365
19743
|
}
|
|
19366
|
-
const cwd = args.cwd ?
|
|
19744
|
+
const cwd = args.cwd ? path47.resolve(args.cwd) : process.cwd();
|
|
19367
19745
|
const sessionId = args.sessionId;
|
|
19368
19746
|
const unpackedSecrets = unpackAllSecrets();
|
|
19369
19747
|
if (unpackedSecrets > 0) {
|
|
@@ -19373,6 +19751,11 @@ ${CHAT_HELP}`);
|
|
|
19373
19751
|
await resolveAuthToken();
|
|
19374
19752
|
configureGitIdentity(cwd);
|
|
19375
19753
|
const config = tryLoadConfig(cwd);
|
|
19754
|
+
if (!config) {
|
|
19755
|
+
process.stderr.write("error: kody chat requires kody.config.json with configured state.repo/state.path\n");
|
|
19756
|
+
return 64;
|
|
19757
|
+
}
|
|
19758
|
+
hydrateStateWorkspace(config, cwd);
|
|
19376
19759
|
const modelSpec = args.model ?? config?.agent.model ?? DEFAULT_MODEL2;
|
|
19377
19760
|
const reasoningEffort = args.reasoningEffort ?? config?.agent.reasoningEffort ?? void 0;
|
|
19378
19761
|
let model;
|
|
@@ -19412,11 +19795,14 @@ ${CHAT_HELP}`);
|
|
|
19412
19795
|
process.stdout.write(`\u2192 kody:chat: litellm proxy ready (url=${litellm?.url ?? "skipped"})
|
|
19413
19796
|
`);
|
|
19414
19797
|
const sessionFile = sessionFilePath(cwd, sessionId);
|
|
19798
|
+
if (config) {
|
|
19799
|
+
syncChatFilesFromState(config, cwd, sessionId);
|
|
19800
|
+
}
|
|
19415
19801
|
if (args.initMessage) seedInitialMessage(sessionFile, args.initMessage);
|
|
19416
19802
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
19417
19803
|
const meta = readMeta(sessionFile);
|
|
19418
19804
|
process.stdout.write(
|
|
19419
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
19805
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs47.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
19420
19806
|
`
|
|
19421
19807
|
);
|
|
19422
19808
|
try {
|
|
@@ -19430,6 +19816,7 @@ ${CHAT_HELP}`);
|
|
|
19430
19816
|
meta,
|
|
19431
19817
|
verbose: args.verbose,
|
|
19432
19818
|
quiet: args.quiet,
|
|
19819
|
+
stateConfig: config,
|
|
19433
19820
|
...reasoningEffort ? { reasoningEffort } : {}
|
|
19434
19821
|
});
|
|
19435
19822
|
return result2.exitCode;
|
|
@@ -19443,9 +19830,10 @@ ${CHAT_HELP}`);
|
|
|
19443
19830
|
sink,
|
|
19444
19831
|
verbose: args.verbose,
|
|
19445
19832
|
quiet: args.quiet,
|
|
19833
|
+
stateConfig: config,
|
|
19446
19834
|
...reasoningEffort ? { reasoningEffort } : {}
|
|
19447
19835
|
});
|
|
19448
|
-
|
|
19836
|
+
persistChatFilesToState(config, cwd, sessionId);
|
|
19449
19837
|
return result.exitCode;
|
|
19450
19838
|
} finally {
|
|
19451
19839
|
try {
|
|
@@ -19574,8 +19962,8 @@ var FlyClient = class {
|
|
|
19574
19962
|
get fetch() {
|
|
19575
19963
|
return this.opts.fetchImpl ?? fetch;
|
|
19576
19964
|
}
|
|
19577
|
-
async call(
|
|
19578
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
19965
|
+
async call(path48, init = {}) {
|
|
19966
|
+
const res = await this.fetch(`${FLY_API_BASE}${path48}`, {
|
|
19579
19967
|
method: init.method ?? "GET",
|
|
19580
19968
|
headers: {
|
|
19581
19969
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -19586,7 +19974,7 @@ var FlyClient = class {
|
|
|
19586
19974
|
if (res.status === 404 && init.allow404) return null;
|
|
19587
19975
|
if (!res.ok) {
|
|
19588
19976
|
const text = await res.text().catch(() => "");
|
|
19589
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
19977
|
+
throw new Error(`Fly API ${res.status} on ${path48}: ${text.slice(0, 200) || res.statusText}`);
|
|
19590
19978
|
}
|
|
19591
19979
|
if (res.status === 204) return null;
|
|
19592
19980
|
const raw = await res.text();
|
|
@@ -19923,9 +20311,9 @@ function isSuspendedWithIp(m) {
|
|
|
19923
20311
|
}
|
|
19924
20312
|
|
|
19925
20313
|
// src/pool/vault.ts
|
|
20314
|
+
init_stateRepoGithub();
|
|
19926
20315
|
import { createDecipheriv as createDecipheriv2 } from "crypto";
|
|
19927
|
-
var
|
|
19928
|
-
var VAULT_PATH = ".kody/secrets.enc";
|
|
20316
|
+
var VAULT_PATH = "secrets.enc";
|
|
19929
20317
|
var CACHE_TTL_MS = 6e4;
|
|
19930
20318
|
var cache = /* @__PURE__ */ new Map();
|
|
19931
20319
|
function decryptVault(payload, masterKey) {
|
|
@@ -19945,30 +20333,18 @@ async function readVaultSecrets(opts) {
|
|
|
19945
20333
|
const key = `${opts.owner}/${opts.repo}`.toLowerCase();
|
|
19946
20334
|
const hit = cache.get(key);
|
|
19947
20335
|
if (hit && hit.expiresAt > Date.now()) return hit.secrets;
|
|
19948
|
-
const
|
|
19949
|
-
|
|
19950
|
-
|
|
19951
|
-
|
|
19952
|
-
|
|
19953
|
-
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
}
|
|
19957
|
-
}
|
|
19958
|
-
);
|
|
19959
|
-
if (res.status === 404) {
|
|
19960
|
-
cache.set(key, { secrets: {}, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
19961
|
-
return {};
|
|
19962
|
-
}
|
|
19963
|
-
if (!res.ok) {
|
|
19964
|
-
throw new Error(`vault read ${res.status} for ${key}: ${(await res.text().catch(() => "")).slice(0, 160)}`);
|
|
19965
|
-
}
|
|
19966
|
-
const body = await res.json();
|
|
19967
|
-
if (!body.content) {
|
|
20336
|
+
const file = await readGithubStateText({
|
|
20337
|
+
owner: opts.owner,
|
|
20338
|
+
repo: opts.repo,
|
|
20339
|
+
filePath: VAULT_PATH,
|
|
20340
|
+
githubToken: opts.githubToken,
|
|
20341
|
+
fetchImpl: opts.fetchImpl
|
|
20342
|
+
});
|
|
20343
|
+
if (!file) {
|
|
19968
20344
|
cache.set(key, { secrets: {}, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
19969
20345
|
return {};
|
|
19970
20346
|
}
|
|
19971
|
-
const ciphertext =
|
|
20347
|
+
const ciphertext = file.content.trim();
|
|
19972
20348
|
const doc = JSON.parse(decryptVault(ciphertext, opts.masterKey));
|
|
19973
20349
|
const flat = {};
|
|
19974
20350
|
for (const [name, entry] of Object.entries(doc.secrets ?? {})) {
|
|
@@ -20157,14 +20533,14 @@ function sendJson2(res, status, body) {
|
|
|
20157
20533
|
res.end(JSON.stringify(body));
|
|
20158
20534
|
}
|
|
20159
20535
|
function readJsonBody2(req) {
|
|
20160
|
-
return new Promise((
|
|
20536
|
+
return new Promise((resolve8, reject) => {
|
|
20161
20537
|
const chunks = [];
|
|
20162
20538
|
req.on("data", (c) => chunks.push(c));
|
|
20163
20539
|
req.on("end", () => {
|
|
20164
20540
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
20165
|
-
if (!raw.trim()) return
|
|
20541
|
+
if (!raw.trim()) return resolve8({});
|
|
20166
20542
|
try {
|
|
20167
|
-
|
|
20543
|
+
resolve8(JSON.parse(raw));
|
|
20168
20544
|
} catch (err) {
|
|
20169
20545
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
20170
20546
|
}
|
|
@@ -20292,10 +20668,10 @@ async function poolServe() {
|
|
|
20292
20668
|
}
|
|
20293
20669
|
});
|
|
20294
20670
|
const apiHost = process.env.POOL_API_HOST ?? "::";
|
|
20295
|
-
await new Promise((
|
|
20671
|
+
await new Promise((resolve8) => {
|
|
20296
20672
|
server.listen(apiPort, apiHost, () => {
|
|
20297
20673
|
log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
|
|
20298
|
-
|
|
20674
|
+
resolve8();
|
|
20299
20675
|
});
|
|
20300
20676
|
});
|
|
20301
20677
|
const shutdown = (signal) => {
|
|
@@ -20313,7 +20689,7 @@ async function poolServe() {
|
|
|
20313
20689
|
|
|
20314
20690
|
// src/servers/runner-serve.ts
|
|
20315
20691
|
import { spawn as spawn8 } from "child_process";
|
|
20316
|
-
import * as
|
|
20692
|
+
import * as fs48 from "fs";
|
|
20317
20693
|
import { createServer as createServer5 } from "http";
|
|
20318
20694
|
var DEFAULT_PORT2 = 8080;
|
|
20319
20695
|
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
@@ -20334,17 +20710,17 @@ function authOk2(req, expected) {
|
|
|
20334
20710
|
return false;
|
|
20335
20711
|
}
|
|
20336
20712
|
function readJsonBody3(req) {
|
|
20337
|
-
return new Promise((
|
|
20713
|
+
return new Promise((resolve8, reject) => {
|
|
20338
20714
|
const chunks = [];
|
|
20339
20715
|
req.on("data", (c) => chunks.push(c));
|
|
20340
20716
|
req.on("end", () => {
|
|
20341
20717
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
20342
20718
|
if (!raw.trim()) {
|
|
20343
|
-
|
|
20719
|
+
resolve8({});
|
|
20344
20720
|
return;
|
|
20345
20721
|
}
|
|
20346
20722
|
try {
|
|
20347
|
-
|
|
20723
|
+
resolve8(JSON.parse(raw));
|
|
20348
20724
|
} catch (err) {
|
|
20349
20725
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
20350
20726
|
}
|
|
@@ -20393,8 +20769,8 @@ async function defaultRunJob(job) {
|
|
|
20393
20769
|
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
20394
20770
|
const branch = job.ref ?? "main";
|
|
20395
20771
|
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
20396
|
-
|
|
20397
|
-
|
|
20772
|
+
fs48.rmSync(workdir, { recursive: true, force: true });
|
|
20773
|
+
fs48.mkdirSync(workdir, { recursive: true });
|
|
20398
20774
|
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
20399
20775
|
const interactive = job.mode === "interactive";
|
|
20400
20776
|
const scheduled = job.mode === "scheduled";
|
|
@@ -20407,9 +20783,9 @@ async function defaultRunJob(job) {
|
|
|
20407
20783
|
// takes (runScheduledFanOut → due agentResponsibilities/goals). Bare `kody` routes on this.
|
|
20408
20784
|
...scheduled ? { GITHUB_EVENT_NAME: "schedule" } : {},
|
|
20409
20785
|
// GITHUB_REPOSITORY + GH_TOKEN are normally injected by GitHub Actions.
|
|
20410
|
-
// The engine's interactive mode needs GITHUB_REPOSITORY to
|
|
20411
|
-
// chat.ready / events
|
|
20412
|
-
//
|
|
20786
|
+
// The engine's interactive mode needs GITHUB_REPOSITORY to resolve the
|
|
20787
|
+
// configured state repo and persist chat.ready / events (the durable signal
|
|
20788
|
+
// the dashboard polls for readiness) — without it commitTurn bails
|
|
20413
20789
|
// and the session never appears "ready". GH_TOKEN auths the `gh` CLI.
|
|
20414
20790
|
GITHUB_REPOSITORY: job.repo,
|
|
20415
20791
|
GH_TOKEN: job.githubToken,
|
|
@@ -20423,13 +20799,13 @@ async function defaultRunJob(job) {
|
|
|
20423
20799
|
...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
|
|
20424
20800
|
...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
|
|
20425
20801
|
};
|
|
20426
|
-
const run = (cmd, args, cwd) => new Promise((
|
|
20802
|
+
const run = (cmd, args, cwd) => new Promise((resolve8) => {
|
|
20427
20803
|
const child = spawn8(cmd, args, { stdio: "inherit", env: childEnv, cwd });
|
|
20428
|
-
child.on("exit", (code) =>
|
|
20804
|
+
child.on("exit", (code) => resolve8(code ?? 0));
|
|
20429
20805
|
child.on("error", (err) => {
|
|
20430
20806
|
process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
|
|
20431
20807
|
`);
|
|
20432
|
-
|
|
20808
|
+
resolve8(1);
|
|
20433
20809
|
});
|
|
20434
20810
|
});
|
|
20435
20811
|
process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
|
|
@@ -20506,11 +20882,11 @@ async function runnerServe() {
|
|
|
20506
20882
|
const port = Number(process.env.PORT ?? DEFAULT_PORT2);
|
|
20507
20883
|
const server = buildServer2({ apiKey });
|
|
20508
20884
|
const host = process.env.RUNNER_HOST ?? "::";
|
|
20509
|
-
await new Promise((
|
|
20885
|
+
await new Promise((resolve8) => {
|
|
20510
20886
|
server.listen(port, host, () => {
|
|
20511
20887
|
process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
|
|
20512
20888
|
`);
|
|
20513
|
-
|
|
20889
|
+
resolve8();
|
|
20514
20890
|
});
|
|
20515
20891
|
});
|
|
20516
20892
|
const shutdown = (signal) => {
|
|
@@ -20579,14 +20955,14 @@ async function serve(opts) {
|
|
|
20579
20955
|
`);
|
|
20580
20956
|
const args = ["--dangerously-skip-permissions", "--model", model.model];
|
|
20581
20957
|
const child = spawn9("claude", args, { stdio: "inherit", env: editorEnv, cwd: opts.cwd });
|
|
20582
|
-
const exitCode = await new Promise((
|
|
20583
|
-
child.on("exit", (code) =>
|
|
20958
|
+
const exitCode = await new Promise((resolve8) => {
|
|
20959
|
+
child.on("exit", (code) => resolve8(code ?? 0));
|
|
20584
20960
|
child.on("error", (err) => {
|
|
20585
20961
|
process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
|
|
20586
20962
|
`);
|
|
20587
20963
|
process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
|
|
20588
20964
|
`);
|
|
20589
|
-
|
|
20965
|
+
resolve8(1);
|
|
20590
20966
|
});
|
|
20591
20967
|
});
|
|
20592
20968
|
killProxy();
|