@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/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.251",
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 relative5 = normalizeStatePath(filePath, "state file path");
406
- return path2.posix.join(state.path, relative5);
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" || r.stateBackend === "local-file") {
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" or "local-file", got "${r.stateBackend}"`
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 crypto from "crypto";
942
- import os2 from "os";
943
- import path4 from "path";
944
- function safeKey(cwd) {
945
- return crypto.createHash("sha1").update(cwd).digest("hex").slice(0, 16);
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, ...segments) {
948
- const root = process.env.KODY_RUNTIME_DIR?.trim() || path4.join(os2.tmpdir(), "kody-runtime");
949
- return path4.join(root, safeKey(cwd), ...segments);
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 crypto2 from "crypto";
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)}-${crypto2.randomBytes(4).toString("hex")}`;
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((resolve7) => {
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
- resolve7({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
1085
+ resolve8({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
1075
1086
  });
1076
1087
  child.on("error", (err) => {
1077
1088
  clearTimeout(timer);
1078
- resolve7({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
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 crypto3 from "crypto";
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 crypto3.createHash("sha256").update(`${repo}#${ref}`).digest("hex").slice(0, 24);
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((resolve7, reject) => {
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
- resolve7();
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 ?? path10.join(opts.cwd, ".kody");
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
- queryOptions.additionalDirectories = [opts.reposRoot];
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((resolve7) => {
2664
+ const timeoutPromise = new Promise((resolve8) => {
2650
2665
  timer = setTimeout(() => {
2651
2666
  timedOut = true;
2652
- resolve7({ done: true, value: void 0 });
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 relDir = path11.join(".kody", "tasks", safeId);
2866
- const absDir = path11.join(cwd, relDir);
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": ".kody/sessions/<id>.jsonl",`,
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 \`.kody/memory/\`. Each item:`,
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 (`.kody/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, defaultBranch2, cwd) {
5735
+ function hasCommitsAhead(branch, defaultBranch, cwd) {
5697
5736
  try {
5698
- const out = git(["rev-list", "--count", `origin/${defaultBranch2}..${branch}`], cwd);
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", `${defaultBranch2}..${branch}`], cwd);
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 = [".kody/memory/", ".kody/tasks/"];
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(path46, message) {
6133
- super(`Invalid goal state at ${path46}:
6171
+ constructor(path48, message) {
6172
+ super(`Invalid goal state at ${path48}:
6134
6173
  ${message}`);
6135
- this.path = path46;
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(path46, raw) {
7320
+ function normalizeCompanyIntent(path48, raw) {
7282
7321
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7283
- throw new Error(`${path46}: intent must be JSON object`);
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(`${path46}: invalid intent id`);
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 path46 = companyIntentPath(entry.name);
7327
- const file = readStateText(config, cwd, path46);
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 path46 = companyIntentPath(id);
7339
- const file = readStateText(config, cwd, path46);
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, defaultBranch2, cwd, baseBranch) {
9951
- const result = ensureFeatureBranchInner(issueNumber, title, defaultBranch2, cwd, baseBranch);
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, defaultBranch2, cwd, baseBranch) {
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 !== defaultBranch2) {
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 === defaultBranch2) {
10052
+ if (!baseBranch || baseBranch === defaultBranch) {
10014
10053
  try {
10015
- git3(["merge", "--no-edit", `origin/${defaultBranch2}`], cwd);
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/${defaultBranch2}'. Resolve manually or delete the branch to start fresh.`
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 = defaultBranch2;
10035
- if (baseBranch && baseBranch !== defaultBranch2) {
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, defaultBranch2) {
10355
+ function makeConfig(pm, ownerRepo, defaultBranch) {
10317
10356
  return {
10318
10357
  $schema: schemaUrlFromPkg(),
10319
10358
  quality: qualityCommandsFor(pm),
10320
- git: { defaultBranch: defaultBranch2 },
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 defaultBranch2 = defaultBranchFromGit(cwd);
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, defaultBranch2);
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, DEFAULT_AGENT_IDENTITY, initFlow;
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((resolve7) => setTimeout(resolve7, ms));
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 relativePath = normalizeStatePath(file.path, `files[${index}].path`);
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 (.kody/variables.json)";
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 .kody/variables.json."
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 createHash2 } from "crypto";
13337
+ import { createDecipheriv, createHash as createHash3 } from "crypto";
13311
13338
  function shortHash(s) {
13312
- return createHash2("sha256").update(s).digest("hex").slice(0, 6);
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 createHash2("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
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((resolve7, reject) => {
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) resolve7();
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 path36 from "path";
13679
+ import * as path37 from "path";
13471
13680
  import { fileURLToPath } from "url";
13472
13681
  function bundledDockerfilePath(mode) {
13473
- const here = path36.dirname(fileURLToPath(import.meta.url));
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 path36.join(here, "preview-build-templates", file);
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 meta = await ghJSON(
13504
- `https://api.github.com/repos/${repo}/contents/.kody/secrets.enc`,
13505
- ghToken3
13506
- );
13507
- const payload = Buffer.from(meta.content, "base64").toString("utf8");
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
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(REQ_TIMEOUT_MS2)
13878
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS3)
13679
13879
  });
13680
13880
  }
13681
- var FLY_MACHINES, FLY_GRAPHQL, REQ_TIMEOUT_MS2, runPreviewBuild;
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
- REQ_TIMEOUT_MS2 = 3e4;
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(path36.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
13935
+ await writeFile(path37.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
13735
13936
  `, "utf8");
13736
13937
  }
13737
- const consumerDockerfile = path36.join(ctx.cwd, "Dockerfile.preview");
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 fs37 from "fs";
13922
- import * as path37 from "path";
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, path37.join(ctx.cwd, jobsDir));
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 = path37.join(profile.dir, shell);
13949
- if (!fs37.existsSync(shellPath)) {
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 fs38 from "fs";
13981
- import * as path38 from "path";
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(path38.join(ctx.cwd, jobsDir), slug2);
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: ${path38.join(ctx.cwd, jobsDir, slug2)}`;
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 = path38.isAbsolute(tickScript) ? tickScript : path38.join(ctx.cwd, tickScript);
14013
- if (!fs38.existsSync(scriptPath)) {
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((resolve7) => {
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
- resolve7({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
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
- resolve7({ exitCode: -1, output: err.message });
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((resolve7) => {
14985
+ next: (timeoutMs) => new Promise((resolve8) => {
14785
14986
  if (queue.length > 0) {
14786
- resolve7(queue.shift());
14987
+ resolve8(queue.shift());
14787
14988
  return;
14788
14989
  }
14789
14990
  if (ended) {
14790
- resolve7(null);
14991
+ resolve8(null);
14791
14992
  return;
14792
14993
  }
14793
- waiter = resolve7;
14994
+ waiter = resolve8;
14794
14995
  const t = setTimeout(
14795
14996
  () => {
14796
- if (waiter === resolve7) {
14997
+ if (waiter === resolve8) {
14797
14998
  waiter = null;
14798
- resolve7(null);
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 fs39 from "fs";
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
- fs39.appendFileSync(summaryPath, `${lines.join("\n")}
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 fs40 from "fs";
15329
- import * as path39 from "path";
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 = path39.join(input.cwd, ".kody");
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) => path39.isAbsolute(p) ? p : path39.resolve(profile.dir, p)).filter((p) => p.length > 0);
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
- } catch {
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 = path39.dirname(new URL(import.meta.url).pathname);
16149
+ const here = path41.dirname(new URL(import.meta.url).pathname);
15885
16150
  const candidates = [
15886
- path39.join(here, "agent-actions", profileName, "profile.json"),
16151
+ path41.join(here, "agent-actions", profileName, "profile.json"),
15887
16152
  // same-dir sibling (dev)
15888
- path39.join(here, "..", "agent-actions", profileName, "profile.json"),
16153
+ path41.join(here, "..", "agent-actions", profileName, "profile.json"),
15889
16154
  // up one (prod: dist/bin → dist/agent-actions)
15890
- path39.join(here, "..", "src", "agent-actions", profileName, "profile.json")
16155
+ path41.join(here, "..", "src", "agent-actions", profileName, "profile.json")
15891
16156
  // fallback
15892
16157
  ];
15893
16158
  for (const c of candidates) {
15894
- if (fs40.existsSync(c)) return c;
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 = path39.join(profile.dir, shellName);
15993
- if (!fs40.existsSync(shellPath)) {
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
- (resolve7) => {
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
- resolve7({ code, signal, spawnErr });
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 path40 from "path";
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 = path40.join(base.cwd, ".kody", "agent-responsibilities");
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, path40.join(cwd, ".kody", "agent-responsibilities"));
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 fs43 from "fs";
16670
+ import * as fs45 from "fs";
16404
16671
  import { createServer } from "http";
16405
- import * as path43 from "path";
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 path12.join(cwd, ".kody", "tmp", "attachments", sessionId);
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
- } catch {
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; open individual files under `.kody/memory/` to read more)_" : trimmed;
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 (`.kody/memory/INDEX.md`)",
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; see \`.kody/context/\` for the full text)_` : joined;
17194
+ _\u2026 (context truncated; use the state repo context files for the full text)_` : joined;
16910
17195
  return [
16911
- "# Context (`.kody/context/`) \u2014 your default frame",
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 (`.kody/instructions.md`)",
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 fs41 from "fs";
16948
- import * as path41 from "path";
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 (fs41.existsSync(path41.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
17568
- if (fs41.existsSync(path41.join(cwd, "yarn.lock"))) return "yarn";
17569
- if (fs41.existsSync(path41.join(cwd, "bun.lockb"))) return "bun";
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 = path41.join(cwd, ".kody", "last-run.jsonl");
17946
+ const logPath = lastRunLogPath(cwd);
17657
17947
  let tail = "";
17658
17948
  try {
17659
- if (fs41.existsSync(logPath)) {
17660
- const content = fs41.readFileSync(logPath, "utf-8");
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 ? path41.resolve(args.cwd) : process.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 && fs41.existsSync(dispatchEventPath)) {
17990
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs43.existsSync(dispatchEventPath)) {
17700
17991
  try {
17701
- const evt = JSON.parse(fs41.readFileSync(dispatchEventPath, "utf-8"));
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 exiting cleanly
18123
+ process.stdout.write(`\u2192 kody: no action for event ${process.env.GITHUB_EVENT_NAME} \u2014 checking scheduled watches
17833
18124
  `);
17834
- return 0;
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
- { cwd, config, verbose: args.verbose, quiet: args.quiet, chain: false }
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
- import * as fs42 from "fs";
18027
- import * as path42 from "path";
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 eventsPath2(dir, chatId) {
18030
- return path42.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
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 = eventsPath2(dir, chatId);
18034
- if (!fs42.existsSync(p)) return 0;
18035
- const lines = fs42.readFileSync(p, "utf-8").split("\n").filter(Boolean);
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 = eventsPath2(dir, chatId);
18045
- if (!fs42.existsSync(p)) return [];
18346
+ const p = brainEventsFilePath(dir, chatId);
18347
+ if (!fs44.existsSync(p)) return [];
18046
18348
  const out = [];
18047
- for (const line of fs42.readFileSync(p, "utf-8").split("\n")) {
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 = eventsPath2(dir, chatId);
18073
- fs42.mkdirSync(path42.dirname(p), { recursive: true });
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
- fs42.appendFileSync(p, `${JSON.stringify(rec)}
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
- fs42.appendFileSync(eventsPath2(dir, chatId), `${JSON.stringify(rec)}
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((resolve7, reject) => {
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
- resolve7({});
18519
+ resolve8({});
18217
18520
  return;
18218
18521
  }
18219
18522
  try {
18220
- resolve7(JSON.parse(raw));
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
- const sessionFile = sessionFilePath(opts.cwd, chatId);
18348
- fs43.mkdirSync(path43.dirname(sessionFile), { recursive: true });
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(opts.cwd, chatId);
18355
- const emitToLog = beginTurn(opts.cwd, chatId);
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(opts.cwd, chatId, errMsg3);
18751
+ endTurnIfUnterminated(agentCwd, chatId, errMsg3);
18382
18752
  } finally {
18383
18753
  endTurnIfUnterminated(
18384
- opts.cwd,
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, opts.cwd, chatId, sinceFloor);
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 ?? path43.join(path43.dirname(path43.resolve(opts.cwd)), "repos");
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((resolve7) => {
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
- resolve7();
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((resolve7) => httpServer.listen(port, host, () => resolve7()));
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((resolve7) => {
18730
- httpServer.close(() => resolve7());
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((resolve7) => {
19276
+ stop: () => new Promise((resolve8) => {
18881
19277
  let pending = transports.size;
18882
19278
  if (pending === 0) {
18883
- httpServer.close(() => resolve7());
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(() => resolve7());
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((resolve7, reject) => {
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
- resolve7();
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 { execFileSync as execFileSync28 } from "child_process";
18990
- import * as fs45 from "fs";
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((resolve7) => setTimeout(resolve7, ms));
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
- const sessionRel = path44.relative(cwd, sessionFilePath(cwd, sessionId));
19158
- const eventsRel = path44.relative(cwd, eventsFilePath(cwd, sessionId));
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
- const branch = defaultBranch(cwd) ?? "main";
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
- init_pushWithRetry();
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 ? path45.resolve(args.cwd) : process.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=${fs45.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
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
- commitChatFiles(cwd, sessionId, args.verbose ?? false);
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(path46, init = {}) {
19578
- const res = await this.fetch(`${FLY_API_BASE}${path46}`, {
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 ${path46}: ${text.slice(0, 200) || res.statusText}`);
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 GITHUB_API = "https://api.github.com";
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 doFetch = opts.fetchImpl ?? fetch;
19949
- const res = await doFetch(
19950
- `${GITHUB_API}/repos/${encodeURIComponent(opts.owner)}/${encodeURIComponent(opts.repo)}/contents/${VAULT_PATH}`,
19951
- {
19952
- headers: {
19953
- Authorization: `Bearer ${opts.githubToken}`,
19954
- Accept: "application/vnd.github+json",
19955
- "User-Agent": "kody-pool-serve"
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 = Buffer.from(body.content, body.encoding ?? "base64").toString("utf8").trim();
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((resolve7, reject) => {
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 resolve7({});
20541
+ if (!raw.trim()) return resolve8({});
20166
20542
  try {
20167
- resolve7(JSON.parse(raw));
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((resolve7) => {
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
- resolve7();
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 fs46 from "fs";
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((resolve7, reject) => {
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
- resolve7({});
20719
+ resolve8({});
20344
20720
  return;
20345
20721
  }
20346
20722
  try {
20347
- resolve7(JSON.parse(raw));
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
- fs46.rmSync(workdir, { recursive: true, force: true });
20397
- fs46.mkdirSync(workdir, { recursive: true });
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 persist
20411
- // chat.ready / events to .kody/events via the Contents API (the durable
20412
- // signal the dashboard polls for readiness) — without it commitTurn bails
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((resolve7) => {
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) => resolve7(code ?? 0));
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
- resolve7(1);
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((resolve7) => {
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
- resolve7();
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((resolve7) => {
20583
- child.on("exit", (code) => resolve7(code ?? 0));
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
- resolve7(1);
20965
+ resolve8(1);
20590
20966
  });
20591
20967
  });
20592
20968
  killProxy();