@kody-ade/kody-engine 0.4.564 → 0.4.566

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.564",
18
+ version: "0.4.566",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -669,6 +669,29 @@ var init_format = __esm({
669
669
  }
670
670
  });
671
671
 
672
+ // src/fileEditGuards.ts
673
+ import * as fs3 from "fs";
674
+ import * as path3 from "path";
675
+ function createMissingParentWriteGuard(cwd) {
676
+ return async (input) => {
677
+ const toolInput = input.tool_input;
678
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) return {};
679
+ const filePath = toolInput.file_path;
680
+ if (typeof filePath !== "string" || filePath.length === 0) return {};
681
+ const resolvedPath = path3.resolve(cwd, filePath);
682
+ if (fs3.existsSync(path3.dirname(resolvedPath))) return {};
683
+ return {
684
+ decision: "block",
685
+ reason: `Cannot write ${resolvedPath}: its parent directory does not exist. Locate and edit the real repository source path first. If this task genuinely requires a new directory, create that directory explicitly before writing the file.`
686
+ };
687
+ };
688
+ }
689
+ var init_fileEditGuards = __esm({
690
+ "src/fileEditGuards.ts"() {
691
+ "use strict";
692
+ }
693
+ });
694
+
672
695
  // src/agency/capability-contract-validation.ts
673
696
  import Ajv from "ajv";
674
697
  function createCapabilityContractValueValidator(compile) {
@@ -741,12 +764,12 @@ var init_capability_contract_validation = __esm({
741
764
  });
742
765
 
743
766
  // src/outputContractHooks.ts
744
- import * as fs3 from "fs";
745
- import * as path3 from "path";
767
+ import * as fs4 from "fs";
768
+ import * as path4 from "path";
746
769
  function outputContractError(contract) {
747
770
  let value;
748
771
  try {
749
- value = JSON.parse(fs3.readFileSync(contract.path, "utf8"));
772
+ value = JSON.parse(fs4.readFileSync(contract.path, "utf8"));
750
773
  } catch (error) {
751
774
  return `The required output file is missing or is not valid JSON: ${error instanceof Error ? error.message : String(error)}`;
752
775
  }
@@ -761,12 +784,12 @@ function correctionMessage(contract, error) {
761
784
  return `The authoritative output does not match its required contract: ${error}. Please overwrite ${contract.path} with only the required JSON shape before finishing.`;
762
785
  }
763
786
  function createOutputContractPostWriteHook(contract) {
764
- const expectedPath = path3.resolve(contract.path);
787
+ const expectedPath = path4.resolve(contract.path);
765
788
  return async (input) => {
766
789
  const toolInput = input.tool_input;
767
790
  if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) return {};
768
791
  const filePath = toolInput.file_path;
769
- if (typeof filePath !== "string" || path3.resolve(filePath) !== expectedPath) return {};
792
+ if (typeof filePath !== "string" || path4.resolve(filePath) !== expectedPath) return {};
770
793
  const error = outputContractError(contract);
771
794
  if (!error) return {};
772
795
  return {
@@ -779,7 +802,7 @@ function createOutputContractPostWriteHook(contract) {
779
802
  }
780
803
  function createOutputContractStopHook(contract) {
781
804
  return async () => {
782
- if (!fs3.existsSync(contract.path)) {
805
+ if (!fs4.existsSync(contract.path)) {
783
806
  return {
784
807
  decision: "block",
785
808
  reason: "Continue the Journey from the current page and complete the next unresolved user outcome. Do not write the result merely because you paused; write it only after the Journey passes, fails, or cannot safely continue."
@@ -810,21 +833,21 @@ __export(runtimePaths_exports, {
810
833
  });
811
834
  import { createHash } from "crypto";
812
835
  import * as os2 from "os";
813
- import * as path4 from "path";
836
+ import * as path5 from "path";
814
837
  function runtimeDirForCwd(cwd, ...parts) {
815
- const key = createHash("sha256").update(path4.resolve(cwd)).digest("hex").slice(0, 16);
816
- return path4.join(os2.tmpdir(), "kody-engine", key, ...parts);
838
+ const key = createHash("sha256").update(path5.resolve(cwd)).digest("hex").slice(0, 16);
839
+ return path5.join(os2.tmpdir(), "kody-engine", key, ...parts);
817
840
  }
818
841
  function runtimeStatePath(cwd, ...parts) {
819
842
  const configuredRoot = process.env.KODY_RUNTIME_DIR?.trim();
820
- const base = configuredRoot ? path4.resolve(configuredRoot) : runtimeDirForCwd(cwd);
821
- return path4.join(base, ...parts);
843
+ const base = configuredRoot ? path5.resolve(configuredRoot) : runtimeDirForCwd(cwd);
844
+ return path5.join(base, ...parts);
822
845
  }
823
846
  function agentRunDir(cwd) {
824
847
  return runtimeStatePath(cwd, "agent-runs");
825
848
  }
826
849
  function lastRunLogPath(cwd) {
827
- return path4.join(agentRunDir(cwd), "last-run.jsonl");
850
+ return path5.join(agentRunDir(cwd), "last-run.jsonl");
828
851
  }
829
852
  var init_runtimePaths = __esm({
830
853
  "src/runtimePaths.ts"() {
@@ -833,31 +856,31 @@ var init_runtimePaths = __esm({
833
856
  });
834
857
 
835
858
  // src/scripts/buildSyntheticPlugin.ts
836
- import * as fs4 from "fs";
859
+ import * as fs5 from "fs";
837
860
  import * as os3 from "os";
838
- import * as path5 from "path";
861
+ import * as path6 from "path";
839
862
  function getPluginsCatalogRoot() {
840
- const here = path5.dirname(new URL(import.meta.url).pathname);
863
+ const here = path6.dirname(new URL(import.meta.url).pathname);
841
864
  const candidates = [
842
- path5.join(here, "..", "plugins"),
865
+ path6.join(here, "..", "plugins"),
843
866
  // dev: src/scripts → src/plugins
844
- path5.join(here, "..", "..", "plugins"),
867
+ path6.join(here, "..", "..", "plugins"),
845
868
  // built: dist/scripts → dist/plugins
846
- path5.join(here, "..", "..", "src", "plugins")
869
+ path6.join(here, "..", "..", "src", "plugins")
847
870
  // fallback
848
871
  ];
849
872
  for (const c of candidates) {
850
- if (fs4.existsSync(c) && fs4.statSync(c).isDirectory()) return c;
873
+ if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) return c;
851
874
  }
852
875
  return candidates[0];
853
876
  }
854
877
  function copyDir(src, dst) {
855
- fs4.mkdirSync(dst, { recursive: true });
856
- for (const ent of fs4.readdirSync(src, { withFileTypes: true })) {
857
- const s = path5.join(src, ent.name);
858
- const d = path5.join(dst, ent.name);
878
+ fs5.mkdirSync(dst, { recursive: true });
879
+ for (const ent of fs5.readdirSync(src, { withFileTypes: true })) {
880
+ const s = path6.join(src, ent.name);
881
+ const d = path6.join(dst, ent.name);
859
882
  if (ent.isDirectory()) copyDir(s, d);
860
- else if (ent.isFile()) fs4.copyFileSync(s, d);
883
+ else if (ent.isFile()) fs5.copyFileSync(s, d);
861
884
  }
862
885
  }
863
886
  var buildSyntheticPlugin;
@@ -870,47 +893,47 @@ var init_buildSyntheticPlugin = __esm({
870
893
  if (!needsSynthetic) return;
871
894
  const catalog = getPluginsCatalogRoot();
872
895
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
873
- const root = path5.join(os3.tmpdir(), `kody-synth-${runId}`);
874
- fs4.mkdirSync(path5.join(root, ".claude-plugin"), { recursive: true });
896
+ const root = path6.join(os3.tmpdir(), `kody-synth-${runId}`);
897
+ fs5.mkdirSync(path6.join(root, ".claude-plugin"), { recursive: true });
875
898
  const resolvePart = (bucket, entry) => {
876
- const local = path5.join(profile.dir, bucket, entry);
877
- if (fs4.existsSync(local)) return local;
878
- const shared = path5.resolve(profile.dir, "..", "..", "shared", bucket, entry);
879
- if (fs4.existsSync(shared)) return shared;
880
- const central = path5.join(catalog, bucket, entry);
881
- if (fs4.existsSync(central)) return central;
899
+ const local = path6.join(profile.dir, bucket, entry);
900
+ if (fs5.existsSync(local)) return local;
901
+ const shared = path6.resolve(profile.dir, "..", "..", "shared", bucket, entry);
902
+ if (fs5.existsSync(shared)) return shared;
903
+ const central = path6.join(catalog, bucket, entry);
904
+ if (fs5.existsSync(central)) return central;
882
905
  throw new Error(
883
- `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in implementation dir (${profile.dir}/${bucket}/), Store shared assets (${path5.dirname(shared)}/), or catalog (${catalog}/${bucket}/)`
906
+ `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in implementation dir (${profile.dir}/${bucket}/), Store shared assets (${path6.dirname(shared)}/), or catalog (${catalog}/${bucket}/)`
884
907
  );
885
908
  };
886
909
  if (cc.skills.length > 0) {
887
- const dst = path5.join(root, "skills");
888
- fs4.mkdirSync(dst, { recursive: true });
910
+ const dst = path6.join(root, "skills");
911
+ fs5.mkdirSync(dst, { recursive: true });
889
912
  for (const name of cc.skills) {
890
- copyDir(resolvePart("skills", name), path5.join(dst, name));
913
+ copyDir(resolvePart("skills", name), path6.join(dst, name));
891
914
  }
892
915
  }
893
916
  if (cc.commands.length > 0) {
894
- const dst = path5.join(root, "commands");
895
- fs4.mkdirSync(dst, { recursive: true });
917
+ const dst = path6.join(root, "commands");
918
+ fs5.mkdirSync(dst, { recursive: true });
896
919
  for (const name of cc.commands) {
897
- fs4.copyFileSync(resolvePart("commands", `${name}.md`), path5.join(dst, `${name}.md`));
920
+ fs5.copyFileSync(resolvePart("commands", `${name}.md`), path6.join(dst, `${name}.md`));
898
921
  }
899
922
  }
900
923
  if (cc.hooks.length > 0) {
901
- const dst = path5.join(root, "hooks");
902
- fs4.mkdirSync(dst, { recursive: true });
924
+ const dst = path6.join(root, "hooks");
925
+ fs5.mkdirSync(dst, { recursive: true });
903
926
  const merged = { hooks: {} };
904
927
  for (const name of cc.hooks) {
905
928
  const src = resolvePart("hooks", `${name}.json`);
906
- const parsed = JSON.parse(fs4.readFileSync(src, "utf-8"));
929
+ const parsed = JSON.parse(fs5.readFileSync(src, "utf-8"));
907
930
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
908
931
  if (!Array.isArray(entries)) continue;
909
932
  if (!merged.hooks[event]) merged.hooks[event] = [];
910
933
  merged.hooks[event].push(...entries);
911
934
  }
912
935
  }
913
- fs4.writeFileSync(path5.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
936
+ fs5.writeFileSync(path6.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
914
937
  `);
915
938
  }
916
939
  const manifest = {
@@ -920,7 +943,7 @@ var init_buildSyntheticPlugin = __esm({
920
943
  };
921
944
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
922
945
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
923
- fs4.writeFileSync(path5.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
946
+ fs5.writeFileSync(path6.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
924
947
  `);
925
948
  ctx.data.syntheticPluginPath = root;
926
949
  };
@@ -928,8 +951,8 @@ var init_buildSyntheticPlugin = __esm({
928
951
  });
929
952
 
930
953
  // src/subagents.ts
931
- import * as fs5 from "fs";
932
- import * as path6 from "path";
954
+ import * as fs6 from "fs";
955
+ import * as path7 from "path";
933
956
  async function enforceSubagentModelInheritance(input) {
934
957
  const toolInput = input.tool_input;
935
958
  if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) return {};
@@ -964,12 +987,12 @@ function splitFrontmatter(raw) {
964
987
  return { fm, body: (match[2] ?? "").trim() };
965
988
  }
966
989
  function resolveAgentFile(profileDir, name) {
967
- const local = path6.join(profileDir, "agents", `${name}.md`);
968
- if (fs5.existsSync(local)) return local;
969
- const shared = path6.resolve(profileDir, "..", "..", "shared", "agents", `${name}.md`);
970
- if (fs5.existsSync(shared)) return shared;
971
- const central = path6.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
972
- if (fs5.existsSync(central)) return central;
990
+ const local = path7.join(profileDir, "agents", `${name}.md`);
991
+ if (fs6.existsSync(local)) return local;
992
+ const shared = path7.resolve(profileDir, "..", "..", "shared", "agents", `${name}.md`);
993
+ if (fs6.existsSync(shared)) return shared;
994
+ const central = path7.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
995
+ if (fs6.existsSync(central)) return central;
973
996
  throw new Error(
974
997
  `loadSubagents: agent '${name}' not found in ${profileDir}/agents/, hydrated shared assets, or engine catalog`
975
998
  );
@@ -980,7 +1003,7 @@ function captureSubagentTemplates(profile) {
980
1003
  const out = {};
981
1004
  for (const name of names) {
982
1005
  try {
983
- out[name] = fs5.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
1006
+ out[name] = fs6.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
984
1007
  } catch {
985
1008
  }
986
1009
  }
@@ -991,7 +1014,7 @@ function loadSubagents(profile) {
991
1014
  if (!names || names.length === 0) return void 0;
992
1015
  const agents = {};
993
1016
  for (const name of names) {
994
- const raw = profile.subagentTemplates?.[name] ?? fs5.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
1017
+ const raw = profile.subagentTemplates?.[name] ?? fs6.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
995
1018
  const { fm, body } = splitFrontmatter(raw);
996
1019
  if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
997
1020
  const def = {
@@ -1024,8 +1047,8 @@ __export(events_exports, {
1024
1047
  resolveRunId: () => resolveRunId
1025
1048
  });
1026
1049
  import * as crypto from "crypto";
1027
- import * as fs6 from "fs";
1028
- import * as path7 from "path";
1050
+ import * as fs7 from "fs";
1051
+ import * as path8 from "path";
1029
1052
  function resolveRunId() {
1030
1053
  if (process.env.KODY_RUN_ID) {
1031
1054
  cachedRunId = process.env.KODY_RUN_ID;
@@ -1058,16 +1081,16 @@ function emitEvent(cwd, ev) {
1058
1081
  ...ev
1059
1082
  };
1060
1083
  const file = eventsPath(cwd, runId);
1061
- fs6.mkdirSync(path7.dirname(file), { recursive: true });
1062
- fs6.appendFileSync(file, `${JSON.stringify(fullEvent)}
1084
+ fs7.mkdirSync(path8.dirname(file), { recursive: true });
1085
+ fs7.appendFileSync(file, `${JSON.stringify(fullEvent)}
1063
1086
  `);
1064
1087
  } catch {
1065
1088
  }
1066
1089
  }
1067
1090
  function readEvents(cwd, runId) {
1068
1091
  const file = eventsPath(cwd, runId);
1069
- if (!fs6.existsSync(file)) return [];
1070
- const lines = fs6.readFileSync(file, "utf-8").split("\n");
1092
+ if (!fs7.existsSync(file)) return [];
1093
+ const lines = fs7.readFileSync(file, "utf-8").split("\n");
1071
1094
  const out = [];
1072
1095
  for (const line of lines) {
1073
1096
  const trimmed = line.trim();
@@ -1081,10 +1104,10 @@ function readEvents(cwd, runId) {
1081
1104
  }
1082
1105
  function listRuns(cwd) {
1083
1106
  const runsDir = runtimeStatePath(cwd, "agent-runs");
1084
- if (!fs6.existsSync(runsDir)) return [];
1085
- return fs6.readdirSync(runsDir).filter((name) => {
1107
+ if (!fs7.existsSync(runsDir)) return [];
1108
+ return fs7.readdirSync(runsDir).filter((name) => {
1086
1109
  try {
1087
- return fs6.statSync(path7.join(runsDir, name)).isDirectory();
1110
+ return fs7.statSync(path8.join(runsDir, name)).isDirectory();
1088
1111
  } catch {
1089
1112
  return false;
1090
1113
  }
@@ -1121,7 +1144,7 @@ function buildVerifyEnv(source = process.env) {
1121
1144
  return env;
1122
1145
  }
1123
1146
  function runCommand(command, cwd) {
1124
- return new Promise((resolve21) => {
1147
+ return new Promise((resolve23) => {
1125
1148
  const start = Date.now();
1126
1149
  const child = spawn(command, {
1127
1150
  cwd,
@@ -1150,11 +1173,11 @@ function runCommand(command, cwd) {
1150
1173
  child.on("exit", (code) => {
1151
1174
  clearTimeout(timer);
1152
1175
  const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
1153
- resolve21({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
1176
+ resolve23({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
1154
1177
  });
1155
1178
  child.on("error", (err) => {
1156
1179
  clearTimeout(timer);
1157
- resolve21({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
1180
+ resolve23({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
1158
1181
  });
1159
1182
  });
1160
1183
  }
@@ -1463,7 +1486,7 @@ function cmsHeaders(opts) {
1463
1486
  }
1464
1487
  };
1465
1488
  }
1466
- async function callDashboardCms(opts, path55, init = {}) {
1489
+ async function callDashboardCms(opts, path58, init = {}) {
1467
1490
  const baseUrl = dashboardBaseUrl(opts);
1468
1491
  if (!baseUrl) {
1469
1492
  return {
@@ -1475,7 +1498,7 @@ async function callDashboardCms(opts, path55, init = {}) {
1475
1498
  const headerResult = cmsHeaders(opts);
1476
1499
  if (!headerResult.ok) return headerResult;
1477
1500
  try {
1478
- const res = await fetch(`${baseUrl}${path55}`, {
1501
+ const res = await fetch(`${baseUrl}${path58}`, {
1479
1502
  ...init,
1480
1503
  headers: {
1481
1504
  ...headerResult.headers,
@@ -1547,8 +1570,8 @@ function documentArg(value) {
1547
1570
  function normalizeCmsDocumentIdInput(input) {
1548
1571
  const trimmed = stripWrappingQuotes(input.trim());
1549
1572
  const withoutQuery = trimmed.split(/[?#]/, 1)[0] ?? trimmed;
1550
- const path55 = parseDocumentPath(withoutQuery);
1551
- return path55 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1573
+ const path58 = parseDocumentPath(withoutQuery);
1574
+ return path58 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1552
1575
  }
1553
1576
  function stripWrappingQuotes(value) {
1554
1577
  let current = value;
@@ -1559,9 +1582,9 @@ function stripWrappingQuotes(value) {
1559
1582
  }
1560
1583
  }
1561
1584
  function parseDocumentPath(value) {
1562
- const path55 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1563
- if (!path55?.includes("/content/entries/")) return null;
1564
- const parts = path55.split("/").filter(Boolean).map(decodePathPart);
1585
+ const path58 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1586
+ if (!path58?.includes("/content/entries/")) return null;
1587
+ const parts = path58.split("/").filter(Boolean).map(decodePathPart);
1565
1588
  const entriesIndex = parts.findIndex((part, index) => part === "content" && parts[index + 1] === "entries");
1566
1589
  const idPart = parts[entriesIndex + 3];
1567
1590
  if (!idPart || idPart === "new") return null;
@@ -2006,8 +2029,8 @@ var init_issue = __esm({
2006
2029
  });
2007
2030
 
2008
2031
  // src/capabilityFolders.ts
2009
- import * as fs7 from "fs";
2010
- import * as path8 from "path";
2032
+ import * as fs8 from "fs";
2033
+ import * as path9 from "path";
2011
2034
  function capabilityOutputConditionPaths(config) {
2012
2035
  if (config.outputSchema) {
2013
2036
  return new Set(schemaPropertyPaths(config.outputSchema, "result"));
@@ -2022,43 +2045,43 @@ function capabilityOutputConditionPaths(config) {
2022
2045
  ]);
2023
2046
  }
2024
2047
  function listCapabilityFolderSlugs(absDir) {
2025
- if (!fs7.existsSync(absDir)) return [];
2048
+ if (!fs8.existsSync(absDir)) return [];
2026
2049
  let entries;
2027
2050
  try {
2028
- entries = fs7.readdirSync(absDir, { withFileTypes: true });
2051
+ entries = fs8.readdirSync(absDir, { withFileTypes: true });
2029
2052
  } catch {
2030
2053
  return [];
2031
2054
  }
2032
- return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isCapabilityFolder(path8.join(absDir, e.name))).map((e) => e.name).sort();
2055
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isCapabilityFolder(path9.join(absDir, e.name))).map((e) => e.name).sort();
2033
2056
  }
2034
2057
  function isCapabilityFolder(dir) {
2035
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
2036
- const legacyBody = path8.join(dir, CAPABILITY_BODY_FILE);
2037
- if (fs7.existsSync(legacyBody)) {
2058
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
2059
+ const legacyBody = path9.join(dir, CAPABILITY_BODY_FILE);
2060
+ if (fs8.existsSync(legacyBody)) {
2038
2061
  return entries.every(
2039
2062
  (entry) => entry.name === CAPABILITY_BODY_FILE || entry.name === CAPABILITY_CONTRACT_FILE || entry.isDirectory() && (entry.name === "skills" || entry.name === "tools")
2040
2063
  );
2041
2064
  }
2042
- const canonicalBody = path8.join(dir, CANONICAL_CAPABILITY_BODY_FILE);
2043
- const canonicalDefinition = path8.join(dir, CANONICAL_CAPABILITY_DEFINITION_FILE);
2044
- if (!fs7.existsSync(canonicalBody) || !fs7.existsSync(canonicalDefinition)) return false;
2065
+ const canonicalBody = path9.join(dir, CANONICAL_CAPABILITY_BODY_FILE);
2066
+ const canonicalDefinition = path9.join(dir, CANONICAL_CAPABILITY_DEFINITION_FILE);
2067
+ if (!fs8.existsSync(canonicalBody) || !fs8.existsSync(canonicalDefinition)) return false;
2045
2068
  return entries.every(
2046
2069
  (entry) => entry.name === CANONICAL_CAPABILITY_BODY_FILE || entry.name === CANONICAL_CAPABILITY_DEFINITION_FILE
2047
2070
  );
2048
2071
  }
2049
2072
  function readCapabilityFolder(root, slug) {
2050
- const dir = path8.join(root, slug);
2051
- const legacyBodyPath = path8.join(dir, CAPABILITY_BODY_FILE);
2052
- const canonicalBodyPath = path8.join(dir, CANONICAL_CAPABILITY_BODY_FILE);
2053
- const bodyPath = fs7.existsSync(legacyBodyPath) ? legacyBodyPath : canonicalBodyPath;
2054
- const contractPath = path8.join(dir, CAPABILITY_CONTRACT_FILE);
2055
- if (!fs7.existsSync(bodyPath) || !fs7.statSync(bodyPath).isFile()) return null;
2073
+ const dir = path9.join(root, slug);
2074
+ const legacyBodyPath = path9.join(dir, CAPABILITY_BODY_FILE);
2075
+ const canonicalBodyPath = path9.join(dir, CANONICAL_CAPABILITY_BODY_FILE);
2076
+ const bodyPath = fs8.existsSync(legacyBodyPath) ? legacyBodyPath : canonicalBodyPath;
2077
+ const contractPath = path9.join(dir, CAPABILITY_CONTRACT_FILE);
2078
+ if (!fs8.existsSync(bodyPath) || !fs8.statSync(bodyPath).isFile()) return null;
2056
2079
  if (!isCapabilityFolder(dir)) return null;
2057
2080
  try {
2058
- const rawBody = fs7.readFileSync(bodyPath, "utf-8");
2081
+ const rawBody = fs8.readFileSync(bodyPath, "utf-8");
2059
2082
  if (bodyPath === canonicalBodyPath) {
2060
- const definitionPath = path8.join(dir, CANONICAL_CAPABILITY_DEFINITION_FILE);
2061
- const definition = JSON.parse(fs7.readFileSync(definitionPath, "utf-8"));
2083
+ const definitionPath = path9.join(dir, CANONICAL_CAPABILITY_DEFINITION_FILE);
2084
+ const definition = JSON.parse(fs8.readFileSync(definitionPath, "utf-8"));
2062
2085
  if (definition.id !== slug || typeof definition.action !== "string") return null;
2063
2086
  const { title: title2, body: body2 } = parseCapabilityBody(rawBody, slug);
2064
2087
  return {
@@ -2078,8 +2101,8 @@ function readCapabilityFolder(root, slug) {
2078
2101
  rawProfile: definition
2079
2102
  };
2080
2103
  }
2081
- const contract = fs7.existsSync(contractPath) ? parseCapabilityContract(fs7.readFileSync(contractPath, "utf-8")) : void 0;
2082
- if (contract?.execution === "script" && !isRegularFile(path8.join(dir, "tools", "run.sh"))) {
2104
+ const contract = fs8.existsSync(contractPath) ? parseCapabilityContract(fs8.readFileSync(contractPath, "utf-8")) : void 0;
2105
+ if (contract?.execution === "script" && !isRegularFile(path9.join(dir, "tools", "run.sh"))) {
2083
2106
  throw new Error('script-backed Capability requires a regular "tools/run.sh" file');
2084
2107
  }
2085
2108
  const { title, body } = parseCapabilityBody(rawBody, slug);
@@ -2192,7 +2215,7 @@ function parseCapabilityRequirements(raw) {
2192
2215
  }
2193
2216
  function isRegularFile(filePath) {
2194
2217
  try {
2195
- const stat = fs7.lstatSync(filePath);
2218
+ const stat = fs8.lstatSync(filePath);
2196
2219
  return stat.isFile() && !stat.isSymbolicLink();
2197
2220
  } catch {
2198
2221
  return false;
@@ -2201,8 +2224,8 @@ function isRegularFile(filePath) {
2201
2224
  function schemaPropertyPaths(schema, prefix) {
2202
2225
  const properties = isPlainObject(schema.properties) ? schema.properties : {};
2203
2226
  return Object.entries(properties).flatMap(([name, property]) => {
2204
- const path55 = `${prefix}.${name}`;
2205
- return isPlainObject(property) ? [path55, ...schemaPropertyPaths(property, path55)] : [path55];
2227
+ const path58 = `${prefix}.${name}`;
2228
+ return isPlainObject(property) ? [path58, ...schemaPropertyPaths(property, path58)] : [path58];
2206
2229
  });
2207
2230
  }
2208
2231
  function parseCapabilityBody(raw, slug) {
@@ -2266,6 +2289,7 @@ function parseWorkflowStep(value) {
2266
2289
  const target = stringField(raw.target);
2267
2290
  const delivery = stringField(raw.delivery);
2268
2291
  const targetFact = stringField(raw.targetFact ?? raw.target_fact);
2292
+ const timeoutSeconds = typeof raw.timeoutSeconds === "number" && Number.isInteger(raw.timeoutSeconds) && raw.timeoutSeconds > 0 && raw.timeoutSeconds <= 3600 ? raw.timeoutSeconds : void 0;
2269
2293
  const hasInput = Object.hasOwn(raw, "input");
2270
2294
  const inputs = parseWorkflowInputBindings(raw.inputs);
2271
2295
  const next = parseWorkflowTransitions(raw.next);
@@ -2281,6 +2305,7 @@ function parseWorkflowStep(value) {
2281
2305
  ...delivery === "pull-request" ? { delivery } : {},
2282
2306
  ...targetFact ? { targetFact } : {},
2283
2307
  ...reason ? { reason } : {},
2308
+ ...timeoutSeconds ? { timeoutSeconds } : {},
2284
2309
  ...next ? { next } : {},
2285
2310
  ...isPlainObject(raw.runWhen) ? { runWhen: raw.runWhen } : {},
2286
2311
  ...stringList(raw.continueOn ?? raw.continue_on).length > 0 ? { continueOn: stringList(raw.continueOn ?? raw.continue_on) } : {},
@@ -2366,51 +2391,51 @@ var init_capabilityFolders = __esm({
2366
2391
  });
2367
2392
 
2368
2393
  // src/definition-paths.ts
2369
- import * as fs8 from "fs";
2370
- import * as path9 from "path";
2394
+ import * as fs9 from "fs";
2395
+ import * as path10 from "path";
2371
2396
  function definitionsRoot(cwd = process.cwd()) {
2372
2397
  const override = process.env.KODY_DEFINITIONS_ROOT?.trim();
2373
2398
  const overrideCwd = process.env.KODY_DEFINITIONS_ROOT_CWD?.trim();
2374
- if (override && overrideCwd && path9.resolve(cwd) === path9.resolve(overrideCwd)) {
2375
- return storeCatalogRoot(path9.resolve(override));
2399
+ if (override && overrideCwd && path10.resolve(cwd) === path10.resolve(overrideCwd)) {
2400
+ return storeCatalogRoot(path10.resolve(override));
2376
2401
  }
2377
- const hydrated = path9.join(cwd, ".kody-engine", "definitions");
2378
- if (fs8.existsSync(hydrated)) return hydrated;
2379
- return override ? storeCatalogRoot(path9.resolve(override)) : hydrated;
2402
+ const hydrated = path10.join(cwd, ".kody-engine", "definitions");
2403
+ if (fs9.existsSync(hydrated)) return hydrated;
2404
+ return override ? storeCatalogRoot(path10.resolve(override)) : hydrated;
2380
2405
  }
2381
2406
  function hasExplicitDefinitionsRoot(cwd = process.cwd(), env = process.env) {
2382
2407
  const root = env.KODY_DEFINITIONS_ROOT?.trim();
2383
2408
  const rootCwd = env.KODY_DEFINITIONS_ROOT_CWD?.trim();
2384
- return Boolean(root && rootCwd && path9.resolve(cwd) === path9.resolve(rootCwd));
2409
+ return Boolean(root && rootCwd && path10.resolve(cwd) === path10.resolve(rootCwd));
2385
2410
  }
2386
2411
  function capabilitiesRoot(cwd = process.cwd()) {
2387
- return storeAssetRoot(cwd, "capabilities") ?? path9.join(definitionsRoot(cwd), "capabilities");
2412
+ return storeAssetRoot(cwd, "capabilities") ?? path10.join(definitionsRoot(cwd), "capabilities");
2388
2413
  }
2389
2414
  function implementationsRoot(cwd = process.cwd()) {
2390
- return path9.join(definitionsRoot(cwd), "implementations");
2415
+ return path10.join(definitionsRoot(cwd), "implementations");
2391
2416
  }
2392
2417
  function agentsRoot(cwd = process.cwd()) {
2393
- return storeAssetRoot(cwd, "agent") ?? path9.join(definitionsRoot(cwd), "agents");
2418
+ return storeAssetRoot(cwd, "agent") ?? path10.join(definitionsRoot(cwd), "agents");
2394
2419
  }
2395
2420
  function storeCatalogRoot(root) {
2396
2421
  const manifest = readStoreManifest(root);
2397
- const roots = ["capabilities", "workflows", "loops"].map((kind) => manifest?.assetRoots?.[kind]).filter((value) => typeof value === "string" && Boolean(value.trim())).map((value) => path9.dirname(value));
2398
- return roots.length === 3 && new Set(roots).size === 1 ? path9.join(root, roots[0]) : root;
2422
+ const roots = ["capabilities", "workflows", "loops"].map((kind) => manifest?.assetRoots?.[kind]).filter((value) => typeof value === "string" && Boolean(value.trim())).map((value) => path10.dirname(value));
2423
+ return roots.length === 3 && new Set(roots).size === 1 ? path10.join(root, roots[0]) : root;
2399
2424
  }
2400
2425
  function storeAssetRoot(cwd, kind) {
2401
2426
  const override = process.env.KODY_DEFINITIONS_ROOT?.trim();
2402
2427
  if (!override) return null;
2403
2428
  const overrideCwd = process.env.KODY_DEFINITIONS_ROOT_CWD?.trim();
2404
- if (overrideCwd && path9.resolve(cwd) !== path9.resolve(overrideCwd)) return null;
2405
- const root = path9.resolve(override);
2429
+ if (overrideCwd && path10.resolve(cwd) !== path10.resolve(overrideCwd)) return null;
2430
+ const root = path10.resolve(override);
2406
2431
  const configured = readStoreManifest(root)?.assetRoots?.[kind];
2407
- return typeof configured === "string" && configured.trim() ? path9.join(root, configured) : null;
2432
+ return typeof configured === "string" && configured.trim() ? path10.join(root, configured) : null;
2408
2433
  }
2409
2434
  function readStoreManifest(root) {
2410
- const file = path9.join(root, "kody-store.json");
2411
- if (!fs8.existsSync(file)) return null;
2435
+ const file = path10.join(root, "kody-store.json");
2436
+ if (!fs9.existsSync(file)) return null;
2412
2437
  try {
2413
- return JSON.parse(fs8.readFileSync(file, "utf8"));
2438
+ return JSON.parse(fs9.readFileSync(file, "utf8"));
2414
2439
  } catch {
2415
2440
  return null;
2416
2441
  }
@@ -2422,32 +2447,32 @@ var init_definition_paths = __esm({
2422
2447
  });
2423
2448
 
2424
2449
  // src/registry.ts
2425
- import * as fs9 from "fs";
2426
- import * as path10 from "path";
2450
+ import * as fs10 from "fs";
2451
+ import * as path11 from "path";
2427
2452
  function getImplementationsRoot() {
2428
- const here = path10.dirname(new URL(import.meta.url).pathname);
2453
+ const here = path11.dirname(new URL(import.meta.url).pathname);
2429
2454
  const candidates = [
2430
- path10.join(here, "implementations"),
2455
+ path11.join(here, "implementations"),
2431
2456
  // dev: src/
2432
- path10.join(here, "..", "implementations"),
2457
+ path11.join(here, "..", "implementations"),
2433
2458
  // built: dist/bin → dist/implementations
2434
- path10.join(here, "..", "src", "implementations")
2459
+ path11.join(here, "..", "src", "implementations")
2435
2460
  // fallback
2436
2461
  ];
2437
2462
  for (const c of candidates) {
2438
- if (fs9.existsSync(c) && fs9.statSync(c).isDirectory()) return c;
2463
+ if (fs10.existsSync(c) && fs10.statSync(c).isDirectory()) return c;
2439
2464
  }
2440
2465
  return candidates[0];
2441
2466
  }
2442
2467
  function getRuntimeServicesRoot() {
2443
- const here = path10.dirname(new URL(import.meta.url).pathname);
2468
+ const here = path11.dirname(new URL(import.meta.url).pathname);
2444
2469
  const candidates = [
2445
- path10.join(here, "runtime-services"),
2446
- path10.join(here, "..", "runtime-services"),
2447
- path10.join(here, "..", "src", "runtime-services")
2470
+ path11.join(here, "runtime-services"),
2471
+ path11.join(here, "..", "runtime-services"),
2472
+ path11.join(here, "..", "src", "runtime-services")
2448
2473
  ];
2449
2474
  for (const candidate of candidates) {
2450
- if (fs9.existsSync(candidate) && fs9.statSync(candidate).isDirectory()) return candidate;
2475
+ if (fs10.existsSync(candidate) && fs10.statSync(candidate).isDirectory()) return candidate;
2451
2476
  }
2452
2477
  return candidates[0];
2453
2478
  }
@@ -2455,17 +2480,17 @@ function getProjectCapabilitiesRoot() {
2455
2480
  return capabilitiesRoot();
2456
2481
  }
2457
2482
  function getBuiltinCapabilitiesRoot() {
2458
- const here = path10.dirname(new URL(import.meta.url).pathname);
2483
+ const here = path11.dirname(new URL(import.meta.url).pathname);
2459
2484
  const candidates = [
2460
- path10.join(here, "capabilities"),
2485
+ path11.join(here, "capabilities"),
2461
2486
  // dev: src/
2462
- path10.join(here, "..", "capabilities"),
2487
+ path11.join(here, "..", "capabilities"),
2463
2488
  // built: dist/bin → dist/capabilities
2464
- path10.join(here, "..", "src", "capabilities")
2489
+ path11.join(here, "..", "src", "capabilities")
2465
2490
  // fallback
2466
2491
  ];
2467
2492
  for (const c of candidates) {
2468
- if (fs9.existsSync(c) && fs9.statSync(c).isDirectory()) return c;
2493
+ if (fs10.existsSync(c) && fs10.statSync(c).isDirectory()) return c;
2469
2494
  }
2470
2495
  return candidates[0];
2471
2496
  }
@@ -2488,14 +2513,14 @@ function listImplementations(roots = getImplementationRoots()) {
2488
2513
  const seen = /* @__PURE__ */ new Set();
2489
2514
  const out = [];
2490
2515
  for (const root of rootList) {
2491
- if (!fs9.existsSync(root)) continue;
2516
+ if (!fs10.existsSync(root)) continue;
2492
2517
  const requireImplementationProfile = isCapabilityRoot(root);
2493
- const entries = fs9.readdirSync(root, { withFileTypes: true });
2518
+ const entries = fs10.readdirSync(root, { withFileTypes: true });
2494
2519
  for (const ent of entries) {
2495
2520
  if (!ent.isDirectory()) continue;
2496
2521
  if (seen.has(ent.name)) continue;
2497
2522
  const profilePath = implementationRuntimePath(root, ent.name);
2498
- if (fs9.existsSync(profilePath) && fs9.statSync(profilePath).isFile() && isImplementationProfile(profilePath, requireImplementationProfile)) {
2523
+ if (fs10.existsSync(profilePath) && fs10.statSync(profilePath).isFile() && isImplementationProfile(profilePath, requireImplementationProfile)) {
2499
2524
  out.push({ name: ent.name, profilePath });
2500
2525
  seen.add(ent.name);
2501
2526
  }
@@ -2515,7 +2540,7 @@ function resolveImplementationCandidates(name, roots = getRuntimeProfileRootsFor
2515
2540
  const out = [];
2516
2541
  for (const root of rootList) {
2517
2542
  const profilePath = implementationRuntimePath(root, name);
2518
- if (fs9.existsSync(profilePath) && fs9.statSync(profilePath).isFile() && isImplementationProfile(profilePath, isCapabilityRoot(root))) {
2543
+ if (fs10.existsSync(profilePath) && fs10.statSync(profilePath).isFile() && isImplementationProfile(profilePath, isCapabilityRoot(root))) {
2519
2544
  out.push(profilePath);
2520
2545
  }
2521
2546
  }
@@ -2589,7 +2614,7 @@ function implementationDeclaresInput(implementation, inputName, cwd = process.cw
2589
2614
  const profilePath = resolveImplementation(implementation, getRuntimeProfileRootsForCwd(cwd));
2590
2615
  if (!profilePath) return false;
2591
2616
  try {
2592
- const document = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
2617
+ const document = JSON.parse(fs10.readFileSync(profilePath, "utf-8"));
2593
2618
  const raw = document.config ?? document;
2594
2619
  if (!Array.isArray(raw.inputs)) return false;
2595
2620
  return raw.inputs.some((entry) => {
@@ -2605,29 +2630,29 @@ function isSafeName(name) {
2605
2630
  return /^[a-z][a-z0-9-]*$/.test(name) && !name.includes("..");
2606
2631
  }
2607
2632
  function isCapabilityRoot(root) {
2608
- const normalized = path10.normalize(root);
2609
- if (path10.basename(normalized) === "capabilities") return true;
2633
+ const normalized = path11.normalize(root);
2634
+ if (path11.basename(normalized) === "capabilities") return true;
2610
2635
  const knownRoots = [getProjectCapabilitiesRoot(), getBuiltinCapabilitiesRoot()];
2611
- return knownRoots.some((candidate) => candidate && path10.normalize(candidate) === normalized);
2636
+ return knownRoots.some((candidate) => candidate && path11.normalize(candidate) === normalized);
2612
2637
  }
2613
2638
  function implementationRuntimePath(root, name) {
2614
- const runtimePath = path10.join(root, name, "runtime.json");
2615
- if (fs9.existsSync(runtimePath)) return runtimePath;
2616
- const internalProfilePath = path10.join(root, name, "profile.json");
2617
- if (fs9.existsSync(internalProfilePath)) return internalProfilePath;
2618
- return path10.join(root, name, CAPABILITY_PROFILE_FILE);
2639
+ const runtimePath = path11.join(root, name, "runtime.json");
2640
+ if (fs10.existsSync(runtimePath)) return runtimePath;
2641
+ const internalProfilePath = path11.join(root, name, "profile.json");
2642
+ if (fs10.existsSync(internalProfilePath)) return internalProfilePath;
2643
+ return path11.join(root, name, CAPABILITY_PROFILE_FILE);
2619
2644
  }
2620
2645
  function isImplementationProfile(profilePath, requireImplementationProfile) {
2621
2646
  if (!requireImplementationProfile) return true;
2622
2647
  try {
2623
- const raw = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
2648
+ const raw = JSON.parse(fs10.readFileSync(profilePath, "utf-8"));
2624
2649
  return typeof raw.role === "string" && PUBLIC_IMPLEMENTATION_ROLES.has(raw.role);
2625
2650
  } catch {
2626
2651
  return false;
2627
2652
  }
2628
2653
  }
2629
2654
  function listFolderCapabilityActions(root, source) {
2630
- if (!fs9.existsSync(root) || !fs9.statSync(root).isDirectory()) return [];
2655
+ if (!fs10.existsSync(root) || !fs10.statSync(root).isDirectory()) return [];
2631
2656
  const out = [];
2632
2657
  for (const slug of listCapabilityFolderSlugs(root)) {
2633
2658
  if (!isSafeName(slug)) continue;
@@ -2659,7 +2684,7 @@ function hasUnresolvedExplicitImplementation(capability, implementation) {
2659
2684
  return resolveImplementation(implementation) === null;
2660
2685
  }
2661
2686
  function listBuiltinCapabilityActions(root = getBuiltinCapabilitiesRoot()) {
2662
- if (!fs9.existsSync(root) || !fs9.statSync(root).isDirectory()) return [];
2687
+ if (!fs10.existsSync(root) || !fs10.statSync(root).isDirectory()) return [];
2663
2688
  const out = [];
2664
2689
  for (const slug of listCapabilityFolderSlugs(root)) {
2665
2690
  if (!isSafeName(slug)) continue;
@@ -2684,7 +2709,7 @@ function getProfileInputs(name, roots = getImplementationRoots()) {
2684
2709
  const profilePath = resolveImplementation(name, roots);
2685
2710
  if (!profilePath) return null;
2686
2711
  try {
2687
- const document = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
2712
+ const document = JSON.parse(fs10.readFileSync(profilePath, "utf-8"));
2688
2713
  if (!document || typeof document !== "object") return [];
2689
2714
  const raw = "config" in document && document.config && typeof document.config === "object" ? document.config : document;
2690
2715
  if (!Array.isArray(raw.inputs)) return [];
@@ -3781,8 +3806,8 @@ var init_capabilityMcp = __esm({
3781
3806
 
3782
3807
  // src/repoWorkspace.ts
3783
3808
  import { spawn as spawn2, spawnSync } from "child_process";
3784
- import * as fs10 from "fs";
3785
- import * as path11 from "path";
3809
+ import * as fs11 from "fs";
3810
+ import * as path12 from "path";
3786
3811
  function buildCloneProcess(repo, token, baseEnv = process.env) {
3787
3812
  const url = `https://github.com/${repo}.git`;
3788
3813
  const env = { ...baseEnv };
@@ -3797,10 +3822,10 @@ function buildCloneProcess(repo, token, baseEnv = process.env) {
3797
3822
  async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
3798
3823
  const name = repo?.trim();
3799
3824
  if (!name || !REPO_RE.test(name)) return null;
3800
- const root = path11.resolve(reposRoot);
3801
- const dir = path11.resolve(root, name);
3802
- if (dir !== root && !dir.startsWith(root + path11.sep)) return null;
3803
- if (fs10.existsSync(path11.join(dir, ".git"))) return dir;
3825
+ const root = path12.resolve(reposRoot);
3826
+ const dir = path12.resolve(root, name);
3827
+ if (dir !== root && !dir.startsWith(root + path12.sep)) return null;
3828
+ if (fs11.existsSync(path12.join(dir, ".git"))) return dir;
3804
3829
  const inflight = repoClones.get(dir);
3805
3830
  if (inflight) {
3806
3831
  await inflight;
@@ -3832,9 +3857,9 @@ var init_repoWorkspace = __esm({
3832
3857
  repoClones = /* @__PURE__ */ new Map();
3833
3858
  GIT_CREDENTIAL_HELPER = `!f() { if [ "$1" = get ] && [ -n "$GITHUB_TOKEN" ]; then printf '%s\\n' username=x-access-token "password=$GITHUB_TOKEN"; fi; }; f`;
3834
3859
  defaultCloneRepo = (repo, token, dir) => {
3835
- fs10.mkdirSync(path11.dirname(dir), { recursive: true });
3860
+ fs11.mkdirSync(path12.dirname(dir), { recursive: true });
3836
3861
  const clone = buildCloneProcess(repo, token);
3837
- return new Promise((resolve21, reject) => {
3862
+ return new Promise((resolve23, reject) => {
3838
3863
  const child = spawn2("git", ["clone", "--depth=1", clone.url, dir], {
3839
3864
  env: clone.env,
3840
3865
  stdio: "inherit"
@@ -3854,7 +3879,7 @@ var init_repoWorkspace = __esm({
3854
3879
  }
3855
3880
  } catch {
3856
3881
  }
3857
- resolve21();
3882
+ resolve23();
3858
3883
  });
3859
3884
  child.on("error", reject);
3860
3885
  });
@@ -3926,8 +3951,8 @@ var init_fetchRepoMcp = __esm({
3926
3951
  });
3927
3952
 
3928
3953
  // src/agent.ts
3929
- import * as fs11 from "fs";
3930
- import * as path12 from "path";
3954
+ import * as fs12 from "fs";
3955
+ import * as path13 from "path";
3931
3956
  import { query } from "@anthropic-ai/claude-agent-sdk";
3932
3957
  function classifySubtype(subtype) {
3933
3958
  if (!subtype) return "generic_failed";
@@ -3996,8 +4021,8 @@ function buildAgentEnvironment(baseEnv, repoToken, requestEnvironment) {
3996
4021
  }
3997
4022
  async function runAgent(opts) {
3998
4023
  const ndjsonDir = opts.ndjsonDir ?? agentRunDir(opts.cwd);
3999
- fs11.mkdirSync(ndjsonDir, { recursive: true });
4000
- const ndjsonPath = path12.join(ndjsonDir, "last-run.jsonl");
4024
+ fs12.mkdirSync(ndjsonDir, { recursive: true });
4025
+ const ndjsonPath = path13.join(ndjsonDir, "last-run.jsonl");
4001
4026
  const env = buildAgentEnvironment(process.env, opts.repoToken, opts.environment);
4002
4027
  if (opts.litellmUrl) {
4003
4028
  env.ANTHROPIC_BASE_URL = opts.litellmUrl;
@@ -4015,12 +4040,13 @@ async function runAgent(opts) {
4015
4040
  let getSubmitted;
4016
4041
  const invokedSubagents = /* @__PURE__ */ new Set();
4017
4042
  const subagentInvocationHook = createSubagentInvocationHook(invokedSubagents);
4043
+ const missingParentWriteGuard = createMissingParentWriteGuard(opts.cwd);
4018
4044
  const outputContractPostWriteHook = opts.outputContract ? createOutputContractPostWriteHook(opts.outputContract) : null;
4019
4045
  const outputContractStopHook = opts.outputContract ? createOutputContractStopHook(opts.outputContract) : null;
4020
4046
  for (let attempt = 0; ; attempt++) {
4021
4047
  let ndjsonWriteFailed = false;
4022
4048
  let ndjsonWriteError;
4023
- const fullLog = fs11.createWriteStream(ndjsonPath, { flags: "w" });
4049
+ const fullLog = fs12.createWriteStream(ndjsonPath, { flags: "w" });
4024
4050
  fullLog.on("error", (err) => {
4025
4051
  ndjsonWriteFailed = true;
4026
4052
  ndjsonWriteError = err instanceof Error ? err.message : String(err);
@@ -4051,6 +4077,10 @@ async function runAgent(opts) {
4051
4077
  {
4052
4078
  matcher: "Agent",
4053
4079
  hooks: [enforceSubagentModelInheritance]
4080
+ },
4081
+ {
4082
+ matcher: "Write",
4083
+ hooks: [missingParentWriteGuard]
4054
4084
  }
4055
4085
  ],
4056
4086
  PostToolUse: [
@@ -4201,10 +4231,10 @@ async function runAgent(opts) {
4201
4231
  let timer;
4202
4232
  let next;
4203
4233
  if (turnTimeoutMs > 0) {
4204
- const timeoutPromise = new Promise((resolve21) => {
4234
+ const timeoutPromise = new Promise((resolve23) => {
4205
4235
  timer = setTimeout(() => {
4206
4236
  timedOut = true;
4207
- resolve21({ done: true, value: void 0 });
4237
+ resolve23({ done: true, value: void 0 });
4208
4238
  }, turnTimeoutMs);
4209
4239
  });
4210
4240
  next = await Promise.race([nextPromise, timeoutPromise]);
@@ -4220,7 +4250,7 @@ async function runAgent(opts) {
4220
4250
  try {
4221
4251
  await Promise.race([
4222
4252
  iterator.return(void 0).catch(() => void 0),
4223
- new Promise((resolve21) => setTimeout(resolve21, 1e4).unref())
4253
+ new Promise((resolve23) => setTimeout(resolve23, 1e4).unref())
4224
4254
  ]);
4225
4255
  } catch {
4226
4256
  }
@@ -4403,6 +4433,7 @@ var init_agent = __esm({
4403
4433
  init_claudeBinary();
4404
4434
  init_config();
4405
4435
  init_format();
4436
+ init_fileEditGuards();
4406
4437
  init_outputContractHooks();
4407
4438
  init_runtimePaths();
4408
4439
  init_subagents();
@@ -4424,8 +4455,8 @@ var init_agent = __esm({
4424
4455
  });
4425
4456
 
4426
4457
  // src/agents.ts
4427
- import * as fs12 from "fs";
4428
- import * as path13 from "path";
4458
+ import * as fs13 from "fs";
4459
+ import * as path14 from "path";
4429
4460
  function stripFrontmatter(raw) {
4430
4461
  const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
4431
4462
  return (match ? match[1] : raw).trim();
@@ -4434,8 +4465,8 @@ function loadAgentIdentity(cwd, slug, agentsDir = agentsRoot(cwd)) {
4434
4465
  const trimmed = slug.trim();
4435
4466
  if (!trimmed) throw new Error("loadAgentIdentity: empty agent slug");
4436
4467
  const agentPath = resolveAgentFile2(cwd, trimmed, agentsDir);
4437
- if (fs12.existsSync(agentPath)) {
4438
- const body = stripFrontmatter(fs12.readFileSync(agentPath, "utf-8"));
4468
+ if (fs13.existsSync(agentPath)) {
4469
+ const body = stripFrontmatter(fs13.readFileSync(agentPath, "utf-8"));
4439
4470
  if (body) return body;
4440
4471
  const builtinForEmpty = BUILTIN_AGENTS[trimmed];
4441
4472
  if (builtinForEmpty) return builtinForEmpty;
@@ -4446,8 +4477,8 @@ function loadAgentIdentity(cwd, slug, agentsDir = agentsRoot(cwd)) {
4446
4477
  throw new Error(`loadAgentIdentity: agent '${trimmed}' declared but ${agentPath} does not exist`);
4447
4478
  }
4448
4479
  function resolveAgentFile2(cwd, slug, agentsDir = agentsRoot(cwd)) {
4449
- const localPath = path13.resolve(cwd, agentsDir, `${slug}.md`);
4450
- if (fs12.existsSync(localPath)) return localPath;
4480
+ const localPath = path14.resolve(cwd, agentsDir, `${slug}.md`);
4481
+ if (fs13.existsSync(localPath)) return localPath;
4451
4482
  return localPath;
4452
4483
  }
4453
4484
  function frameAgentIdentity(slug, agent) {
@@ -4479,14 +4510,14 @@ var init_agents = __esm({
4479
4510
  });
4480
4511
 
4481
4512
  // src/task-artifacts.ts
4482
- import fs13 from "fs";
4483
- import path14 from "path";
4513
+ import fs14 from "fs";
4514
+ import path15 from "path";
4484
4515
  import posixPath from "path/posix";
4485
4516
  function prepareTaskArtifactsDir(cwd, taskId) {
4486
4517
  const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
4487
4518
  const absDir = runtimeStatePath(cwd, "task-artifacts", safeId);
4488
4519
  const relDir = absDir;
4489
- fs13.mkdirSync(absDir, { recursive: true });
4520
+ fs14.mkdirSync(absDir, { recursive: true });
4490
4521
  return { taskId: safeId, absDir, relDir };
4491
4522
  }
4492
4523
  function initializeTaskArtifacts(artifacts, metadata = { taskType: "job" }) {
@@ -4516,16 +4547,16 @@ function initializeTaskArtifacts(artifacts, metadata = { taskType: "job" }) {
4516
4547
  "handoff-notes.md": "Baseline handoff: the agent did not provide additional notes.\n"
4517
4548
  };
4518
4549
  for (const file of TASK_ARTIFACT_FILES) {
4519
- const full = path14.join(artifacts.absDir, file);
4520
- if (!fs13.existsSync(full)) fs13.writeFileSync(full, defaults[file], "utf8");
4550
+ const full = path15.join(artifacts.absDir, file);
4551
+ if (!fs14.existsSync(full)) fs14.writeFileSync(full, defaults[file], "utf8");
4521
4552
  }
4522
4553
  }
4523
4554
  function verifyTaskArtifacts(absDir) {
4524
4555
  const missing = [];
4525
4556
  for (const name of TASK_ARTIFACT_FILES) {
4526
- const full = path14.join(absDir, name);
4557
+ const full = path15.join(absDir, name);
4527
4558
  try {
4528
- const stat = fs13.statSync(full);
4559
+ const stat = fs14.statSync(full);
4529
4560
  if (!stat.isFile() || stat.size === 0) missing.push(name);
4530
4561
  } catch {
4531
4562
  missing.push(name);
@@ -4541,11 +4572,11 @@ async function persistTaskArtifactsToState(config, _cwd, artifacts) {
4541
4572
  if (hasStateBackendConfig() && tenantId2) {
4542
4573
  const backend = createStateBackendFromEnv();
4543
4574
  for (const file of TASK_ARTIFACT_FILES) {
4544
- const full = path14.join(artifacts.absDir, file);
4545
- if (!fs13.existsSync(full)) continue;
4546
- const stat = fs13.statSync(full);
4575
+ const full = path15.join(artifacts.absDir, file);
4576
+ if (!fs14.existsSync(full)) continue;
4577
+ const stat = fs14.statSync(full);
4547
4578
  if (!stat.isFile() || stat.size === 0) continue;
4548
- const content = fs13.readFileSync(full, "utf-8");
4579
+ const content = fs14.readFileSync(full, "utf-8");
4549
4580
  const kind = file.replace(/\.(json|md)$/, "");
4550
4581
  let doc = content;
4551
4582
  if (file.endsWith(".json")) {
@@ -4693,6 +4724,15 @@ function validateWorkflow(value, options = {}) {
4693
4724
  if (step.input !== void 0 && step.inputs !== void 0) {
4694
4725
  issue(issues, "conflicting_inputs", base, "workflow step cannot declare both input and inputs");
4695
4726
  }
4727
+ const timeoutSeconds = step.timeoutSeconds;
4728
+ if (timeoutSeconds !== void 0 && (typeof timeoutSeconds !== "number" || !Number.isInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > 3600)) {
4729
+ issue(
4730
+ issues,
4731
+ "invalid_step_timeout",
4732
+ `${base}.timeoutSeconds`,
4733
+ "workflow step timeoutSeconds must be an integer from 1 to 3600"
4734
+ );
4735
+ }
4696
4736
  validateInputBindings(
4697
4737
  step.inputs,
4698
4738
  `${base}.inputs`,
@@ -4845,15 +4885,15 @@ function validateWorkflow(value, options = {}) {
4845
4885
  }
4846
4886
  return issues;
4847
4887
  }
4848
- function validateInputBindings(value, path55, issues, declaredInputs) {
4888
+ function validateInputBindings(value, path58, issues, declaredInputs) {
4849
4889
  if (value === void 0) return;
4850
4890
  const bindings = asRecord(value);
4851
4891
  if (!bindings || Object.keys(bindings).length === 0) {
4852
- issue(issues, "invalid_inputs", path55, "workflow step inputs must contain at least one named mapping");
4892
+ issue(issues, "invalid_inputs", path58, "workflow step inputs must contain at least one named mapping");
4853
4893
  return;
4854
4894
  }
4855
4895
  for (const [name, value2] of Object.entries(bindings)) {
4856
- const bindingPath = `${path55}.${name}`;
4896
+ const bindingPath = `${path58}.${name}`;
4857
4897
  if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(name)) {
4858
4898
  issue(issues, "invalid_input_name", bindingPath, `workflow input name ${name} is invalid`);
4859
4899
  }
@@ -4872,7 +4912,7 @@ function validateInputBindings(value, path55, issues, declaredInputs) {
4872
4912
  }
4873
4913
  }
4874
4914
  }
4875
- function validateInputBindingSources(value, path55, issues, capabilitiesByStep, capabilityOutputs) {
4915
+ function validateInputBindingSources(value, path58, issues, capabilitiesByStep, capabilityOutputs) {
4876
4916
  const bindings = asRecord(value);
4877
4917
  if (!bindings) return;
4878
4918
  for (const [name, rawBinding] of Object.entries(bindings)) {
@@ -4885,7 +4925,7 @@ function validateInputBindingSources(value, path55, issues, capabilitiesByStep,
4885
4925
  issue(
4886
4926
  issues,
4887
4927
  "missing_input_step",
4888
- `${path55}.${name}.from`,
4928
+ `${path58}.${name}.from`,
4889
4929
  `workflow input mapping references missing step ${sourceStep ?? "<none>"}`
4890
4930
  );
4891
4931
  continue;
@@ -4896,7 +4936,7 @@ function validateInputBindingSources(value, path55, issues, capabilitiesByStep,
4896
4936
  issue(
4897
4937
  issues,
4898
4938
  "undeclared_step_output",
4899
- `${path55}.${name}.from`,
4939
+ `${path58}.${name}.from`,
4900
4940
  `workflow input mapping reads ${outputPath}, but step ${sourceStep} does not declare it`
4901
4941
  );
4902
4942
  }
@@ -4905,11 +4945,11 @@ function validateInputBindingSources(value, path55, issues, capabilitiesByStep,
4905
4945
  function formatWorkflowValidationIssues(issues) {
4906
4946
  return issues.map((entry) => `${entry.path}: ${entry.message}`);
4907
4947
  }
4908
- function validateDataMatch(value, path55, issues, capabilityOutputs) {
4948
+ function validateDataMatch(value, path58, issues, capabilityOutputs) {
4909
4949
  if (value === void 0) return;
4910
4950
  const match = asRecord(value);
4911
4951
  if (!match || Object.keys(match).length === 0) {
4912
- issue(issues, "invalid_condition", path55, "workflow condition must contain at least one match");
4952
+ issue(issues, "invalid_condition", path58, "workflow condition must contain at least one match");
4913
4953
  return;
4914
4954
  }
4915
4955
  for (const [field, expected] of Object.entries(match)) {
@@ -4917,7 +4957,7 @@ function validateDataMatch(value, path55, issues, capabilityOutputs) {
4917
4957
  issue(
4918
4958
  issues,
4919
4959
  "invalid_data_path",
4920
- `${path55}.${field}`,
4960
+ `${path58}.${field}`,
4921
4961
  `workflow condition must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
4922
4962
  );
4923
4963
  }
@@ -4925,12 +4965,12 @@ function validateDataMatch(value, path55, issues, capabilityOutputs) {
4925
4965
  issue(
4926
4966
  issues,
4927
4967
  "undeclared_result_path",
4928
- `${path55}.${field}`,
4968
+ `${path58}.${field}`,
4929
4969
  `workflow condition reads ${field}, but the source capability does not declare it`
4930
4970
  );
4931
4971
  }
4932
4972
  if (!isComparable(expected)) {
4933
- issue(issues, "invalid_condition_value", `${path55}.${field}`, "workflow condition value must be a JSON scalar");
4973
+ issue(issues, "invalid_condition_value", `${path58}.${field}`, "workflow condition value must be a JSON scalar");
4934
4974
  }
4935
4975
  }
4936
4976
  }
@@ -4954,8 +4994,8 @@ function isJsonValue(value) {
4954
4994
  if (!value || typeof value !== "object") return false;
4955
4995
  return Object.values(value).every(isJsonValue);
4956
4996
  }
4957
- function issue(issues, code, path55, message) {
4958
- issues.push({ code, path: path55, message });
4997
+ function issue(issues, code, path58, message) {
4998
+ issues.push({ code, path: path58, message });
4959
4999
  }
4960
5000
  var SAFE_NAME, SAFE_STEP_ID, SAFE_DATA_PATH, SAFE_INPUT_SOURCE, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
4961
5001
  var init_workflowValidation = __esm({
@@ -4976,6 +5016,7 @@ var init_workflowValidation = __esm({
4976
5016
  "delivery",
4977
5017
  "targetFact",
4978
5018
  "reason",
5019
+ "timeoutSeconds",
4979
5020
  "next",
4980
5021
  "runWhen",
4981
5022
  "continueOn",
@@ -4987,8 +5028,8 @@ var init_workflowValidation = __esm({
4987
5028
  });
4988
5029
 
4989
5030
  // src/workflowDefinitions.ts
4990
- import * as fs19 from "fs";
4991
- import * as path20 from "path";
5031
+ import * as fs20 from "fs";
5032
+ import * as path21 from "path";
4992
5033
  function isWorkflowDefinitionId(value) {
4993
5034
  return WORKFLOW_ID_PATTERN.test(value);
4994
5035
  }
@@ -5033,12 +5074,12 @@ function readWorkflowDefinition(_config, cwd, id) {
5033
5074
  const root = cwd ?? process.cwd();
5034
5075
  const relativePath = workflowDefinitionPath(id);
5035
5076
  const candidates = [
5036
- path20.join(root, ".kody-engine", "runtime", relativePath),
5037
- path20.join(definitionsRoot(root), relativePath)
5077
+ path21.join(root, ".kody-engine", "runtime", relativePath),
5078
+ path21.join(definitionsRoot(root), relativePath)
5038
5079
  ];
5039
5080
  for (const filePath of candidates) {
5040
- if (!fs19.existsSync(filePath)) continue;
5041
- const workflow = parseWorkflowDefinition(fs19.readFileSync(filePath, "utf8"));
5081
+ if (!fs20.existsSync(filePath)) continue;
5082
+ const workflow = parseWorkflowDefinition(fs20.readFileSync(filePath, "utf8"));
5042
5083
  if (workflow) return workflow;
5043
5084
  }
5044
5085
  return null;
@@ -5046,7 +5087,7 @@ function readWorkflowDefinition(_config, cwd, id) {
5046
5087
  function workflowDefinitionToCapabilityFolder(id, workflow, source = workflowDefinitionPath(id)) {
5047
5088
  return {
5048
5089
  slug: id,
5049
- dir: path20.dirname(source),
5090
+ dir: path21.dirname(source),
5050
5091
  profilePath: source,
5051
5092
  bodyPath: source,
5052
5093
  title: workflow.name,
@@ -5102,7 +5143,7 @@ var init_workflowDefinitions = __esm({
5102
5143
 
5103
5144
  // src/gha.ts
5104
5145
  import { execFileSync as execFileSync2 } from "child_process";
5105
- import * as fs22 from "fs";
5146
+ import * as fs23 from "fs";
5106
5147
  function getRunUrl() {
5107
5148
  const server = process.env.GITHUB_SERVER_URL;
5108
5149
  const repo = process.env.GITHUB_REPOSITORY;
@@ -5113,10 +5154,10 @@ function getRunUrl() {
5113
5154
  function reactToTriggerComment(cwd) {
5114
5155
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
5115
5156
  const eventPath = process.env.GITHUB_EVENT_PATH;
5116
- if (!eventPath || !fs22.existsSync(eventPath)) return;
5157
+ if (!eventPath || !fs23.existsSync(eventPath)) return;
5117
5158
  let event = null;
5118
5159
  try {
5119
- event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
5160
+ event = JSON.parse(fs23.readFileSync(eventPath, "utf-8"));
5120
5161
  } catch {
5121
5162
  return;
5122
5163
  }
@@ -5646,15 +5687,15 @@ var init_lifecycles = __esm({
5646
5687
 
5647
5688
  // src/profile.ts
5648
5689
  import { createHash as createHash3 } from "crypto";
5649
- import * as fs23 from "fs";
5650
- import * as path22 from "path";
5690
+ import * as fs24 from "fs";
5691
+ import * as path23 from "path";
5651
5692
  function loadProfile(profilePath) {
5652
- if (!fs23.existsSync(profilePath)) {
5693
+ if (!fs24.existsSync(profilePath)) {
5653
5694
  throw new ProfileError(profilePath, "file not found");
5654
5695
  }
5655
5696
  let raw;
5656
5697
  try {
5657
- raw = JSON.parse(fs23.readFileSync(profilePath, "utf-8"));
5698
+ raw = JSON.parse(fs24.readFileSync(profilePath, "utf-8"));
5658
5699
  } catch (err) {
5659
5700
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
5660
5701
  }
@@ -5666,7 +5707,7 @@ function loadProfile(profilePath) {
5666
5707
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
5667
5708
  if (unknownKeys.length > 0) {
5668
5709
  process.stderr.write(
5669
- `[kody profile] ${path22.basename(path22.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
5710
+ `[kody profile] ${path23.basename(path23.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
5670
5711
  `
5671
5712
  );
5672
5713
  }
@@ -5676,7 +5717,7 @@ function loadProfile(profilePath) {
5676
5717
  if (!refPath) {
5677
5718
  throw new ProfileError(profilePath, `capability references unknown implementation '${execRef}'`);
5678
5719
  }
5679
- if (path22.resolve(refPath) === path22.resolve(profilePath)) {
5720
+ if (path23.resolve(refPath) === path23.resolve(profilePath)) {
5680
5721
  } else {
5681
5722
  const base = loadProfile(refPath);
5682
5723
  return {
@@ -5774,8 +5815,8 @@ function loadProfile(profilePath) {
5774
5815
  // Phase 5 in-process handoff opt-in. Default false; containers
5775
5816
  // flip to true after end-to-end verification.
5776
5817
  preloadContext: r.preloadContext === true,
5777
- dir: path22.dirname(profilePath),
5778
- promptTemplates: readPromptTemplates(path22.dirname(profilePath))
5818
+ dir: path23.dirname(profilePath),
5819
+ promptTemplates: readPromptTemplates(path23.dirname(profilePath))
5779
5820
  };
5780
5821
  if (lifecycle) {
5781
5822
  applyLifecycle(profile, profilePath);
@@ -5810,19 +5851,19 @@ function loadProfile(profilePath) {
5810
5851
  return profile;
5811
5852
  }
5812
5853
  function compileRuntimeDocument(runtimePath, document) {
5813
- if (path22.basename(runtimePath) !== "runtime.json") return document;
5854
+ if (path23.basename(runtimePath) !== "runtime.json") return document;
5814
5855
  if (document.adapter !== "kody-engine-profile") {
5815
5856
  throw new ProfileError(runtimePath, "unsupported runtime adapter document");
5816
5857
  }
5817
- const implementationDir = path22.dirname(runtimePath);
5818
- const implementation = readJsonObject(path22.join(implementationDir, "definition.json"), "Implementation definition");
5819
- const definitionsRoot2 = path22.dirname(path22.dirname(implementationDir));
5858
+ const implementationDir = path23.dirname(runtimePath);
5859
+ const implementation = readJsonObject(path23.join(implementationDir, "definition.json"), "Implementation definition");
5860
+ const definitionsRoot2 = path23.dirname(path23.dirname(implementationDir));
5820
5861
  const capabilityId = implementation.capabilityRef && typeof implementation.capabilityRef === "object" && !Array.isArray(implementation.capabilityRef) ? implementation.capabilityRef.id : void 0;
5821
5862
  if (typeof capabilityId !== "string" || !capabilityId) {
5822
5863
  throw new ProfileError(runtimePath, "Implementation capabilityRef is invalid");
5823
5864
  }
5824
5865
  const capability = readJsonObject(
5825
- path22.join(definitionsRoot2, "capabilities", capabilityId, "definition.json"),
5866
+ path23.join(definitionsRoot2, "capabilities", capabilityId, "definition.json"),
5826
5867
  "Capability definition"
5827
5868
  );
5828
5869
  const {
@@ -5861,7 +5902,7 @@ function canonical(value) {
5861
5902
  }
5862
5903
  function readJsonObject(filePath, label) {
5863
5904
  try {
5864
- const value = JSON.parse(fs23.readFileSync(filePath, "utf-8"));
5905
+ const value = JSON.parse(fs24.readFileSync(filePath, "utf-8"));
5865
5906
  if (!value || typeof value !== "object" || Array.isArray(value)) {
5866
5907
  throw new Error("must be an object");
5867
5908
  }
@@ -5879,17 +5920,17 @@ function readPromptTemplates(dir) {
5879
5920
  const out = {};
5880
5921
  const read = (p) => {
5881
5922
  try {
5882
- out[p] = fs23.readFileSync(p, "utf-8");
5923
+ out[p] = fs24.readFileSync(p, "utf-8");
5883
5924
  } catch {
5884
5925
  }
5885
5926
  };
5886
- read(path22.join(dir, "prompt.md"));
5887
- read(path22.join(dir, "capability.md"));
5888
- read(path22.join(dir, "capability.md"));
5927
+ read(path23.join(dir, "prompt.md"));
5928
+ read(path23.join(dir, "capability.md"));
5929
+ read(path23.join(dir, "capability.md"));
5889
5930
  try {
5890
- const promptsDir = path22.join(dir, "prompts");
5891
- for (const ent of fs23.readdirSync(promptsDir)) {
5892
- if (ent.endsWith(".md")) read(path22.join(promptsDir, ent));
5931
+ const promptsDir = path23.join(dir, "prompts");
5932
+ for (const ent of fs24.readdirSync(promptsDir)) {
5933
+ if (ent.endsWith(".md")) read(path23.join(promptsDir, ent));
5893
5934
  }
5894
5935
  } catch {
5895
5936
  }
@@ -6666,16 +6707,16 @@ var init_state = __esm({
6666
6707
  });
6667
6708
 
6668
6709
  // src/prompt.ts
6669
- import * as fs24 from "fs";
6670
- import * as path23 from "path";
6710
+ import * as fs25 from "fs";
6711
+ import * as path24 from "path";
6671
6712
  function loadProjectConventions(projectDir) {
6672
6713
  const out = [];
6673
6714
  for (const rel of CONVENTION_FILES) {
6674
- const abs = path23.join(projectDir, rel);
6675
- if (!fs24.existsSync(abs)) continue;
6715
+ const abs = path24.join(projectDir, rel);
6716
+ if (!fs25.existsSync(abs)) continue;
6676
6717
  let content;
6677
6718
  try {
6678
- content = fs24.readFileSync(abs, "utf-8");
6719
+ content = fs25.readFileSync(abs, "utf-8");
6679
6720
  } catch {
6680
6721
  continue;
6681
6722
  }
@@ -6910,8 +6951,8 @@ var loadMemoryContext_exports = {};
6910
6951
  __export(loadMemoryContext_exports, {
6911
6952
  loadMemoryContext: () => loadMemoryContext
6912
6953
  });
6913
- import * as fs25 from "fs";
6914
- import * as path24 from "path";
6954
+ import * as fs26 from "fs";
6955
+ import * as path25 from "path";
6915
6956
  function formatBlockFromBackend(docs) {
6916
6957
  const pages = docs.flatMap((record2) => {
6917
6958
  if (!record2.doc || typeof record2.doc !== "object") return [];
@@ -6934,21 +6975,21 @@ function collectPages(memoryAbs) {
6934
6975
  walkMd(memoryAbs, (file) => {
6935
6976
  let stat;
6936
6977
  try {
6937
- stat = fs25.statSync(file);
6978
+ stat = fs26.statSync(file);
6938
6979
  } catch {
6939
6980
  return;
6940
6981
  }
6941
6982
  let raw;
6942
6983
  try {
6943
- raw = fs25.readFileSync(file, "utf-8");
6984
+ raw = fs26.readFileSync(file, "utf-8");
6944
6985
  } catch {
6945
6986
  return;
6946
6987
  }
6947
6988
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
6948
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path24.basename(file, ".md");
6989
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path25.basename(file, ".md");
6949
6990
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
6950
6991
  out.push({
6951
- relPath: path24.relative(memoryAbs, file),
6992
+ relPath: path25.relative(memoryAbs, file),
6952
6993
  title,
6953
6994
  updated,
6954
6995
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
@@ -7016,16 +7057,16 @@ function walkMd(root, visit) {
7016
7057
  const dir = stack.pop();
7017
7058
  let names;
7018
7059
  try {
7019
- names = fs25.readdirSync(dir);
7060
+ names = fs26.readdirSync(dir);
7020
7061
  } catch {
7021
7062
  continue;
7022
7063
  }
7023
7064
  for (const name of names) {
7024
7065
  if (name.startsWith(".")) continue;
7025
- const full = path24.join(dir, name);
7066
+ const full = path25.join(dir, name);
7026
7067
  let stat;
7027
7068
  try {
7028
- stat = fs25.statSync(full);
7069
+ stat = fs26.statSync(full);
7029
7070
  } catch {
7030
7071
  continue;
7031
7072
  }
@@ -7060,8 +7101,8 @@ var init_loadMemoryContext = __esm({
7060
7101
  }
7061
7102
  return;
7062
7103
  }
7063
- const memoryAbs = path24.join(ctx.cwd, MEMORY_DIR_RELATIVE);
7064
- if (!fs25.existsSync(memoryAbs)) {
7104
+ const memoryAbs = path25.join(ctx.cwd, MEMORY_DIR_RELATIVE);
7105
+ if (!fs26.existsSync(memoryAbs)) {
7065
7106
  ctx.data.memoryContext = "";
7066
7107
  return;
7067
7108
  }
@@ -7105,11 +7146,11 @@ var init_loadCoverageRules = __esm({
7105
7146
 
7106
7147
  // src/container.ts
7107
7148
  import { execFileSync as execFileSync3 } from "child_process";
7108
- import * as fs26 from "fs";
7149
+ import * as fs27 from "fs";
7109
7150
  function getProfileInputsForChild(profileName, _cwd) {
7110
7151
  try {
7111
7152
  const profilePath = resolveProfilePath(profileName);
7112
- if (!fs26.existsSync(profilePath)) return null;
7153
+ if (!fs27.existsSync(profilePath)) return null;
7113
7154
  return loadProfile(profilePath).inputs;
7114
7155
  } catch {
7115
7156
  return null;
@@ -7573,10 +7614,10 @@ var init_lifecycleLabels = __esm({
7573
7614
 
7574
7615
  // src/litellm.ts
7575
7616
  import { execFileSync as execFileSync4, spawn as spawn4 } from "child_process";
7576
- import * as fs27 from "fs";
7617
+ import * as fs28 from "fs";
7577
7618
  import * as net from "net";
7578
7619
  import * as os4 from "os";
7579
- import * as path25 from "path";
7620
+ import * as path26 from "path";
7580
7621
  async function checkLitellmHealth(url) {
7581
7622
  try {
7582
7623
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -7646,7 +7687,7 @@ function locateLitellmScript() {
7646
7687
  }
7647
7688
  function resolveLitellmCommand() {
7648
7689
  const imageScript = "/opt/venv/bin/litellm";
7649
- if (fs27.existsSync(imageScript)) return imageScript;
7690
+ if (fs28.existsSync(imageScript)) return imageScript;
7650
7691
  try {
7651
7692
  execFileSync4("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
7652
7693
  return "litellm";
@@ -7689,13 +7730,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
7689
7730
  const spawnProxy = () => {
7690
7731
  const portMatch = activeUrl.match(/:(\d+)/);
7691
7732
  const port = portMatch ? portMatch[1] : "4000";
7692
- const configPath = path25.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
7693
- fs27.writeFileSync(configPath, generateLitellmConfigYaml(model));
7733
+ const configPath = path26.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
7734
+ fs28.writeFileSync(configPath, generateLitellmConfigYaml(model));
7694
7735
  const args = ["--config", configPath, "--port", port];
7695
- const nextLogPath = path25.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
7696
- const outFd = fs27.openSync(nextLogPath, "w");
7736
+ const nextLogPath = path26.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
7737
+ const outFd = fs28.openSync(nextLogPath, "w");
7697
7738
  child = spawn4(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
7698
- fs27.closeSync(outFd);
7739
+ fs28.closeSync(outFd);
7699
7740
  logPath = nextLogPath;
7700
7741
  };
7701
7742
  const waitForHealth = async () => {
@@ -7709,7 +7750,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
7709
7750
  const readLogTail = () => {
7710
7751
  if (!logPath) return "";
7711
7752
  try {
7712
- return fs27.readFileSync(logPath, "utf-8").slice(-2e3);
7753
+ return fs28.readFileSync(logPath, "utf-8").slice(-2e3);
7713
7754
  } catch {
7714
7755
  return "";
7715
7756
  }
@@ -7782,20 +7823,20 @@ async function nextAvailableLitellmUrl(url) {
7782
7823
  throw new Error(`no free LiteLLM port found after ${startPort}`);
7783
7824
  }
7784
7825
  function canListen(port, host) {
7785
- return new Promise((resolve21) => {
7826
+ return new Promise((resolve23) => {
7786
7827
  const server = net.createServer();
7787
- server.once("error", () => resolve21(false));
7828
+ server.once("error", () => resolve23(false));
7788
7829
  server.once("listening", () => {
7789
- server.close(() => resolve21(true));
7830
+ server.close(() => resolve23(true));
7790
7831
  });
7791
7832
  server.listen(port, host);
7792
7833
  });
7793
7834
  }
7794
7835
  function readDotenvApiKeys(projectDir) {
7795
- const dotenvPath = path25.join(projectDir, ".env");
7796
- if (!fs27.existsSync(dotenvPath)) return {};
7836
+ const dotenvPath = path26.join(projectDir, ".env");
7837
+ if (!fs28.existsSync(dotenvPath)) return {};
7797
7838
  const result = {};
7798
- for (const rawLine of fs27.readFileSync(dotenvPath, "utf-8").split("\n")) {
7839
+ for (const rawLine of fs28.readFileSync(dotenvPath, "utf-8").split("\n")) {
7799
7840
  const line = rawLine.trim();
7800
7841
  if (!line || line.startsWith("#")) continue;
7801
7842
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -8454,8 +8495,8 @@ var init_pushWithRetry = __esm({
8454
8495
 
8455
8496
  // src/commit.ts
8456
8497
  import { execFileSync as execFileSync6 } from "child_process";
8457
- import * as fs28 from "fs";
8458
- import * as path26 from "path";
8498
+ import * as fs29 from "fs";
8499
+ import * as path27 from "path";
8459
8500
  function isGitHubYamlPath(filePath) {
8460
8501
  const normalized = filePath.replace(/^\.\/+/, "");
8461
8502
  return normalized.startsWith(".github/") && /\.ya?ml$/i.test(normalized);
@@ -8497,18 +8538,18 @@ function ensureGitIdentity(cwd) {
8497
8538
  }
8498
8539
  function abortUnfinishedGitOps(cwd) {
8499
8540
  const aborted = [];
8500
- const gitDir = path26.join(cwd ?? process.cwd(), ".git");
8501
- if (!fs28.existsSync(gitDir)) return aborted;
8502
- if (fs28.existsSync(path26.join(gitDir, "MERGE_HEAD"))) {
8541
+ const gitDir = path27.join(cwd ?? process.cwd(), ".git");
8542
+ if (!fs29.existsSync(gitDir)) return aborted;
8543
+ if (fs29.existsSync(path27.join(gitDir, "MERGE_HEAD"))) {
8503
8544
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
8504
8545
  }
8505
- if (fs28.existsSync(path26.join(gitDir, "CHERRY_PICK_HEAD"))) {
8546
+ if (fs29.existsSync(path27.join(gitDir, "CHERRY_PICK_HEAD"))) {
8506
8547
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
8507
8548
  }
8508
- if (fs28.existsSync(path26.join(gitDir, "REVERT_HEAD"))) {
8549
+ if (fs29.existsSync(path27.join(gitDir, "REVERT_HEAD"))) {
8509
8550
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
8510
8551
  }
8511
- if (fs28.existsSync(path26.join(gitDir, "rebase-merge")) || fs28.existsSync(path26.join(gitDir, "rebase-apply"))) {
8552
+ if (fs29.existsSync(path27.join(gitDir, "rebase-merge")) || fs29.existsSync(path27.join(gitDir, "rebase-apply"))) {
8512
8553
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
8513
8554
  }
8514
8555
  try {
@@ -8565,7 +8606,7 @@ function normalizeCommitMessage(raw) {
8565
8606
  function commitAndPush(branch, agentMessage, cwd) {
8566
8607
  const allChanged = listChangedFiles(cwd);
8567
8608
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
8568
- const mergeHeadExists = fs28.existsSync(path26.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
8609
+ const mergeHeadExists = fs29.existsSync(path27.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
8569
8610
  if (allowedFiles.length === 0 && !mergeHeadExists) {
8570
8611
  return { committed: false, pushed: false, sha: "", message: "" };
8571
8612
  }
@@ -9205,13 +9246,13 @@ var init_state2 = __esm({
9205
9246
  });
9206
9247
 
9207
9248
  // src/goal/runLog.ts
9208
- import * as fs29 from "fs";
9249
+ import * as fs30 from "fs";
9209
9250
  function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
9210
9251
  const logs = goalRunLogs(data);
9211
9252
  const existing = logs[goalId];
9212
- const path55 = existing?.path ?? goalRunLogPath(goalId, data);
9253
+ const path58 = existing?.path ?? goalRunLogPath(goalId, data);
9213
9254
  logs[goalId] = {
9214
- path: path55,
9255
+ path: path58,
9215
9256
  events: [...existing?.events ?? [], buildGoalRunLogEvent(data, goalId, event, at)]
9216
9257
  };
9217
9258
  }
@@ -9547,8 +9588,8 @@ function readGithubEvent() {
9547
9588
  const eventPath = process.env.GITHUB_EVENT_PATH;
9548
9589
  if (!eventPath) return null;
9549
9590
  try {
9550
- if (!fs29.existsSync(eventPath)) return null;
9551
- const parsed = JSON.parse(fs29.readFileSync(eventPath, "utf-8"));
9591
+ if (!fs30.existsSync(eventPath)) return null;
9592
+ const parsed = JSON.parse(fs30.readFileSync(eventPath, "utf-8"));
9552
9593
  return recordValue3(parsed);
9553
9594
  } catch {
9554
9595
  return null;
@@ -9658,8 +9699,8 @@ var init_stateStore = __esm({
9658
9699
  });
9659
9700
 
9660
9701
  // src/goal/targetLoopResolution.ts
9661
- import * as fs30 from "fs";
9662
- import * as path27 from "path";
9702
+ import * as fs31 from "fs";
9703
+ import * as path28 from "path";
9663
9704
  async function resolveActiveGoalLoopTarget(config, cwd, loopGoalId, loopGoal) {
9664
9705
  const targetId = loopGoal.loopTarget?.id.trim() ?? "";
9665
9706
  assertSafeGoalId(targetId, "loop target");
@@ -9737,11 +9778,11 @@ function goalInstanceTime(state) {
9737
9778
  return Number.isNaN(parsed) ? 0 : parsed;
9738
9779
  }
9739
9780
  function loadGoalTemplate(cwd, targetId) {
9740
- return readJsonObject2(path27.join(cwd, ".kody-engine", "definitions", "goals", targetId, "state.json"));
9781
+ return readJsonObject2(path28.join(cwd, ".kody-engine", "definitions", "goals", targetId, "state.json"));
9741
9782
  }
9742
9783
  function readJsonObject2(filePath) {
9743
- if (!fs30.existsSync(filePath)) return null;
9744
- const parsed = JSON.parse(fs30.readFileSync(filePath, "utf8"));
9784
+ if (!fs31.existsSync(filePath)) return null;
9785
+ const parsed = JSON.parse(fs31.readFileSync(filePath, "utf8"));
9745
9786
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
9746
9787
  throw new Error(`goal template ${filePath} must be a JSON object`);
9747
9788
  }
@@ -10088,15 +10129,15 @@ var init_backendStateBackend = __esm({
10088
10129
  this.jobsDir = opts.jobsDir.replace(/\/+$/, "");
10089
10130
  }
10090
10131
  async load(slug) {
10091
- const path55 = stateFilePath(this.jobsDir, slug);
10132
+ const path58 = stateFilePath(this.jobsDir, slug);
10092
10133
  const loaded = await createStateBackendFromEnv().get(this.tenantId, `capabilities/${slug}`, "job-state");
10093
10134
  if (!loaded) {
10094
- return { path: path55, handle: null, state: initialStateEnvelope("seed"), created: true };
10135
+ return { path: path58, handle: null, state: initialStateEnvelope("seed"), created: true };
10095
10136
  }
10096
10137
  if (!isStateEnvelope(loaded.doc)) {
10097
10138
  throw new Error(`BackendStateBackend: capabilities/${slug} is not a StateEnvelope`);
10098
10139
  }
10099
- return { path: path55, handle: loaded.updatedAt, state: loaded.doc, created: false };
10140
+ return { path: path58, handle: loaded.updatedAt, state: loaded.doc, created: false };
10100
10141
  }
10101
10142
  async save(loaded, next) {
10102
10143
  if (!loaded.created && isStateUnchanged(loaded.state, next)) return false;
@@ -10116,8 +10157,8 @@ var init_backendStateBackend = __esm({
10116
10157
  });
10117
10158
 
10118
10159
  // src/scripts/jobState/localFileBackend.ts
10119
- import * as fs31 from "fs";
10120
- import * as path28 from "path";
10160
+ import * as fs32 from "fs";
10161
+ import * as path29 from "path";
10121
10162
  function sanitizeKey(s) {
10122
10163
  return s.replace(/[^A-Za-z0-9._-]/g, "-");
10123
10164
  }
@@ -10173,7 +10214,7 @@ var init_localFileBackend = __esm({
10173
10214
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
10174
10215
  this.cwd = opts.cwd;
10175
10216
  this.jobsDir = opts.jobsDir;
10176
- this.absDir = path28.resolve(opts.cwd, opts.jobsDir);
10217
+ this.absDir = path29.resolve(opts.cwd, opts.jobsDir);
10177
10218
  this.owner = opts.owner;
10178
10219
  this.repo = opts.repo;
10179
10220
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -10188,7 +10229,7 @@ var init_localFileBackend = __esm({
10188
10229
  `);
10189
10230
  return;
10190
10231
  }
10191
- fs31.mkdirSync(this.absDir, { recursive: true });
10232
+ fs32.mkdirSync(this.absDir, { recursive: true });
10192
10233
  const prefix = this.cacheKeyPrefix();
10193
10234
  const probeKey = `${prefix}probe-${Date.now()}`;
10194
10235
  try {
@@ -10217,7 +10258,7 @@ var init_localFileBackend = __esm({
10217
10258
  `);
10218
10259
  return;
10219
10260
  }
10220
- if (!fs31.existsSync(this.absDir)) {
10261
+ if (!fs32.existsSync(this.absDir)) {
10221
10262
  return;
10222
10263
  }
10223
10264
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -10233,11 +10274,11 @@ var init_localFileBackend = __esm({
10233
10274
  }
10234
10275
  load(slug) {
10235
10276
  const relPath = stateFilePath(this.jobsDir, slug);
10236
- const absPath = path28.resolve(this.cwd, relPath);
10237
- if (!fs31.existsSync(absPath)) {
10277
+ const absPath = path29.resolve(this.cwd, relPath);
10278
+ if (!fs32.existsSync(absPath)) {
10238
10279
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
10239
10280
  }
10240
- const raw = fs31.readFileSync(absPath, "utf-8");
10281
+ const raw = fs32.readFileSync(absPath, "utf-8");
10241
10282
  let parsed;
10242
10283
  try {
10243
10284
  parsed = JSON.parse(raw);
@@ -10254,13 +10295,13 @@ var init_localFileBackend = __esm({
10254
10295
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
10255
10296
  return false;
10256
10297
  }
10257
- const absPath = path28.resolve(this.cwd, loaded.path);
10258
- fs31.mkdirSync(path28.dirname(absPath), { recursive: true });
10298
+ const absPath = path29.resolve(this.cwd, loaded.path);
10299
+ fs32.mkdirSync(path29.dirname(absPath), { recursive: true });
10259
10300
  const body = `${JSON.stringify(next, null, 2)}
10260
10301
  `;
10261
10302
  const tmpPath = `${absPath}.${process.pid}.tmp`;
10262
- fs31.writeFileSync(tmpPath, body, "utf-8");
10263
- fs31.renameSync(tmpPath, absPath);
10303
+ fs32.writeFileSync(tmpPath, body, "utf-8");
10304
+ fs32.renameSync(tmpPath, absPath);
10264
10305
  return true;
10265
10306
  }
10266
10307
  cacheKeyPrefix() {
@@ -10292,7 +10333,7 @@ var init_jobState = __esm({
10292
10333
  });
10293
10334
 
10294
10335
  // src/scripts/goalCapabilityScheduling.ts
10295
- import * as path29 from "path";
10336
+ import * as path30 from "path";
10296
10337
  function isCapabilityCadenceGoal(goal, extra) {
10297
10338
  return extra.scheduleMode === "agentLoop" || extra.scheduler === "agentLoop" || goal.type === "standing" && goal.capabilities.length > 0;
10298
10339
  }
@@ -10348,7 +10389,7 @@ function planTargetLoopSchedule(opts) {
10348
10389
  }
10349
10390
  async function planGoalCapabilitySchedule(opts) {
10350
10391
  const jobsDir = opts.jobsDir ?? capabilitiesRoot(opts.cwd);
10351
- const jobsRoot = path29.resolve(opts.cwd, jobsDir);
10392
+ const jobsRoot = path30.resolve(opts.cwd, jobsDir);
10352
10393
  const now = opts.now ?? /* @__PURE__ */ new Date();
10353
10394
  const at = now.toISOString();
10354
10395
  const backend = resolveBackend({ config: opts.config, cwd: opts.cwd, jobsDir });
@@ -12164,8 +12205,8 @@ var init_classifyByLabel = __esm({
12164
12205
 
12165
12206
  // src/scripts/commitAndPush.ts
12166
12207
  import { createHash as createHash5 } from "crypto";
12167
- import * as fs32 from "fs";
12168
- import * as path30 from "path";
12208
+ import * as fs33 from "fs";
12209
+ import * as path31 from "path";
12169
12210
  function sentinelPathForStage(cwd, profileName, workflowExecutionKey) {
12170
12211
  const runId = resolveRunId();
12171
12212
  const executionSuffix = typeof workflowExecutionKey === "string" && workflowExecutionKey.length > 0 ? `-${createHash5("sha256").update(workflowExecutionKey).digest("hex").slice(0, 16)}` : "";
@@ -12187,9 +12228,9 @@ var init_commitAndPush = __esm({
12187
12228
  }
12188
12229
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
12189
12230
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name, ctx.data.workflowExecutionKey) : null;
12190
- if (sentinel && fs32.existsSync(sentinel)) {
12231
+ if (sentinel && fs33.existsSync(sentinel)) {
12191
12232
  try {
12192
- const replay = JSON.parse(fs32.readFileSync(sentinel, "utf-8"));
12233
+ const replay = JSON.parse(fs33.readFileSync(sentinel, "utf-8"));
12193
12234
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
12194
12235
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
12195
12236
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -12249,8 +12290,8 @@ var init_commitAndPush = __esm({
12249
12290
  const result = ctx.data.commitResult;
12250
12291
  if (sentinel && result?.committed) {
12251
12292
  try {
12252
- fs32.mkdirSync(path30.dirname(sentinel), { recursive: true });
12253
- fs32.writeFileSync(
12293
+ fs33.mkdirSync(path31.dirname(sentinel), { recursive: true });
12294
+ fs33.writeFileSync(
12254
12295
  sentinel,
12255
12296
  JSON.stringify(
12256
12297
  {
@@ -12344,8 +12385,8 @@ var init_commitGoalState = __esm({
12344
12385
  });
12345
12386
 
12346
12387
  // src/scripts/composePrompt.ts
12347
- import * as fs33 from "fs";
12348
- import * as path31 from "path";
12388
+ import * as fs34 from "fs";
12389
+ import * as path32 from "path";
12349
12390
  function fenceUntrusted(value) {
12350
12391
  if (value.trim().length === 0) return value;
12351
12392
  const safe = value.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
@@ -12469,10 +12510,10 @@ var init_composePrompt = __esm({
12469
12510
  const explicit = ctx.data.promptTemplate;
12470
12511
  const mode = ctx.args.mode;
12471
12512
  const candidates = [
12472
- explicit ? path31.join(profile.dir, explicit) : null,
12473
- mode ? path31.join(profile.dir, "prompts", `${mode}.md`) : null,
12474
- path31.join(profile.dir, "prompt.md"),
12475
- path31.join(profile.dir, "capability.md")
12513
+ explicit ? path32.join(profile.dir, explicit) : null,
12514
+ mode ? path32.join(profile.dir, "prompts", `${mode}.md`) : null,
12515
+ path32.join(profile.dir, "prompt.md"),
12516
+ path32.join(profile.dir, "capability.md")
12476
12517
  ].filter(Boolean);
12477
12518
  let templatePath = "";
12478
12519
  let template = "";
@@ -12485,7 +12526,7 @@ var init_composePrompt = __esm({
12485
12526
  break;
12486
12527
  }
12487
12528
  try {
12488
- template = fs33.readFileSync(c, "utf-8");
12529
+ template = fs34.readFileSync(c, "utf-8");
12489
12530
  templatePath = c;
12490
12531
  break;
12491
12532
  } catch (err) {
@@ -12496,7 +12537,7 @@ var init_composePrompt = __esm({
12496
12537
  if (!templatePath) {
12497
12538
  let dirState;
12498
12539
  try {
12499
- dirState = `dir contents: [${fs33.readdirSync(profile.dir).join(", ")}]`;
12540
+ dirState = `dir contents: [${fs34.readdirSync(profile.dir).join(", ")}]`;
12500
12541
  } catch (err) {
12501
12542
  dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
12502
12543
  }
@@ -13230,19 +13271,19 @@ var init_deriveQaScopeFromIssue = __esm({
13230
13271
 
13231
13272
  // src/scripts/diagMcp.ts
13232
13273
  import { execFileSync as execFileSync9 } from "child_process";
13233
- import * as fs34 from "fs";
13274
+ import * as fs35 from "fs";
13234
13275
  import * as os5 from "os";
13235
- import * as path32 from "path";
13276
+ import * as path33 from "path";
13236
13277
  var diagMcp;
13237
13278
  var init_diagMcp = __esm({
13238
13279
  "src/scripts/diagMcp.ts"() {
13239
13280
  "use strict";
13240
13281
  diagMcp = async (_ctx) => {
13241
13282
  const home = os5.homedir();
13242
- const cacheDir = path32.join(home, ".cache", "ms-playwright");
13283
+ const cacheDir = path33.join(home, ".cache", "ms-playwright");
13243
13284
  let entries = [];
13244
13285
  try {
13245
- entries = fs34.readdirSync(cacheDir);
13286
+ entries = fs35.readdirSync(cacheDir);
13246
13287
  } catch {
13247
13288
  }
13248
13289
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -13270,13 +13311,13 @@ var init_diagMcp = __esm({
13270
13311
  });
13271
13312
 
13272
13313
  // src/scripts/frameworkDetectors.ts
13273
- import * as fs35 from "fs";
13274
- import * as path33 from "path";
13314
+ import * as fs36 from "fs";
13315
+ import * as path34 from "path";
13275
13316
  function detectFrameworks(cwd) {
13276
13317
  const out = [];
13277
13318
  let deps = {};
13278
13319
  try {
13279
- const pkg = JSON.parse(fs35.readFileSync(path33.join(cwd, "package.json"), "utf-8"));
13320
+ const pkg = JSON.parse(fs36.readFileSync(path34.join(cwd, "package.json"), "utf-8"));
13280
13321
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
13281
13322
  } catch {
13282
13323
  return out;
@@ -13313,25 +13354,25 @@ function detectFrameworks(cwd) {
13313
13354
  }
13314
13355
  function findFile(cwd, candidates) {
13315
13356
  for (const c of candidates) {
13316
- if (fs35.existsSync(path33.join(cwd, c))) return c;
13357
+ if (fs36.existsSync(path34.join(cwd, c))) return c;
13317
13358
  }
13318
13359
  return null;
13319
13360
  }
13320
13361
  function discoverPayloadCollections(cwd) {
13321
13362
  const out = [];
13322
13363
  for (const dir of COLLECTION_DIRS) {
13323
- const full = path33.join(cwd, dir);
13324
- if (!fs35.existsSync(full)) continue;
13364
+ const full = path34.join(cwd, dir);
13365
+ if (!fs36.existsSync(full)) continue;
13325
13366
  let files;
13326
13367
  try {
13327
- files = fs35.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
13368
+ files = fs36.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
13328
13369
  } catch {
13329
13370
  continue;
13330
13371
  }
13331
13372
  for (const file of files) {
13332
13373
  try {
13333
- const filePath = path33.join(full, file);
13334
- const content = fs35.readFileSync(filePath, "utf-8").slice(0, 1e4);
13374
+ const filePath = path34.join(full, file);
13375
+ const content = fs36.readFileSync(filePath, "utf-8").slice(0, 1e4);
13335
13376
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
13336
13377
  if (!slugMatch) continue;
13337
13378
  const slug = slugMatch[1];
@@ -13345,7 +13386,7 @@ function discoverPayloadCollections(cwd) {
13345
13386
  out.push({
13346
13387
  name,
13347
13388
  slug,
13348
- filePath: path33.relative(cwd, filePath),
13389
+ filePath: path34.relative(cwd, filePath),
13349
13390
  fields: fields.slice(0, 20),
13350
13391
  hasAdmin
13351
13392
  });
@@ -13358,28 +13399,28 @@ function discoverPayloadCollections(cwd) {
13358
13399
  function discoverAdminComponents(cwd, collections) {
13359
13400
  const out = [];
13360
13401
  for (const dir of ADMIN_COMPONENT_DIRS) {
13361
- const full = path33.join(cwd, dir);
13362
- if (!fs35.existsSync(full)) continue;
13402
+ const full = path34.join(cwd, dir);
13403
+ if (!fs36.existsSync(full)) continue;
13363
13404
  let entries;
13364
13405
  try {
13365
- entries = fs35.readdirSync(full, { withFileTypes: true });
13406
+ entries = fs36.readdirSync(full, { withFileTypes: true });
13366
13407
  } catch {
13367
13408
  continue;
13368
13409
  }
13369
13410
  for (const entry of entries) {
13370
- const entryPath = path33.join(full, entry.name);
13411
+ const entryPath = path34.join(full, entry.name);
13371
13412
  let name;
13372
13413
  let filePath;
13373
13414
  if (entry.isDirectory()) {
13374
13415
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
13375
- (f) => fs35.existsSync(path33.join(entryPath, f))
13416
+ (f) => fs36.existsSync(path34.join(entryPath, f))
13376
13417
  );
13377
13418
  if (!indexFile) continue;
13378
13419
  name = entry.name;
13379
- filePath = path33.relative(cwd, path33.join(entryPath, indexFile));
13420
+ filePath = path34.relative(cwd, path34.join(entryPath, indexFile));
13380
13421
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
13381
13422
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
13382
- filePath = path33.relative(cwd, entryPath);
13423
+ filePath = path34.relative(cwd, entryPath);
13383
13424
  } else {
13384
13425
  continue;
13385
13426
  }
@@ -13387,7 +13428,7 @@ function discoverAdminComponents(cwd, collections) {
13387
13428
  if (collections) {
13388
13429
  for (const col of collections) {
13389
13430
  try {
13390
- const colContent = fs35.readFileSync(path33.join(cwd, col.filePath), "utf-8");
13431
+ const colContent = fs36.readFileSync(path34.join(cwd, col.filePath), "utf-8");
13391
13432
  if (colContent.includes(name)) {
13392
13433
  usedInCollection = col.slug;
13393
13434
  break;
@@ -13405,8 +13446,8 @@ function scanApiRoutes(cwd) {
13405
13446
  const out = [];
13406
13447
  const appDirs = ["src/app", "app"];
13407
13448
  for (const appDir of appDirs) {
13408
- const apiDir = path33.join(cwd, appDir, "api");
13409
- if (!fs35.existsSync(apiDir)) continue;
13449
+ const apiDir = path34.join(cwd, appDir, "api");
13450
+ if (!fs36.existsSync(apiDir)) continue;
13410
13451
  walkApiRoutes(apiDir, "/api", cwd, out);
13411
13452
  break;
13412
13453
  }
@@ -13415,14 +13456,14 @@ function scanApiRoutes(cwd) {
13415
13456
  function walkApiRoutes(dir, prefix, cwd, out) {
13416
13457
  let entries;
13417
13458
  try {
13418
- entries = fs35.readdirSync(dir, { withFileTypes: true });
13459
+ entries = fs36.readdirSync(dir, { withFileTypes: true });
13419
13460
  } catch {
13420
13461
  return;
13421
13462
  }
13422
13463
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
13423
13464
  if (routeFile) {
13424
13465
  try {
13425
- const content = fs35.readFileSync(path33.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
13466
+ const content = fs36.readFileSync(path34.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
13426
13467
  const methods = HTTP_METHODS.filter(
13427
13468
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
13428
13469
  );
@@ -13430,7 +13471,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
13430
13471
  out.push({
13431
13472
  path: prefix,
13432
13473
  methods,
13433
- filePath: path33.relative(cwd, path33.join(dir, routeFile.name))
13474
+ filePath: path34.relative(cwd, path34.join(dir, routeFile.name))
13434
13475
  });
13435
13476
  }
13436
13477
  } catch {
@@ -13441,7 +13482,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
13441
13482
  if (entry.name === "node_modules" || entry.name === ".next") continue;
13442
13483
  let segment = entry.name;
13443
13484
  if (segment.startsWith("(") && segment.endsWith(")")) {
13444
- walkApiRoutes(path33.join(dir, entry.name), prefix, cwd, out);
13485
+ walkApiRoutes(path34.join(dir, entry.name), prefix, cwd, out);
13445
13486
  continue;
13446
13487
  }
13447
13488
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -13449,16 +13490,16 @@ function walkApiRoutes(dir, prefix, cwd, out) {
13449
13490
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
13450
13491
  segment = `:${segment.slice(1, -1)}`;
13451
13492
  }
13452
- walkApiRoutes(path33.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
13493
+ walkApiRoutes(path34.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
13453
13494
  }
13454
13495
  }
13455
13496
  function scanEnvVars(cwd) {
13456
13497
  const candidates = [".env.example", ".env.local.example", ".env.template"];
13457
13498
  for (const envFile of candidates) {
13458
- const envPath = path33.join(cwd, envFile);
13459
- if (!fs35.existsSync(envPath)) continue;
13499
+ const envPath = path34.join(cwd, envFile);
13500
+ if (!fs36.existsSync(envPath)) continue;
13460
13501
  try {
13461
- const content = fs35.readFileSync(envPath, "utf-8");
13502
+ const content = fs36.readFileSync(envPath, "utf-8");
13462
13503
  const vars = [];
13463
13504
  for (const line of content.split("\n")) {
13464
13505
  const trimmed = line.trim();
@@ -13503,8 +13544,8 @@ var init_frameworkDetectors = __esm({
13503
13544
  });
13504
13545
 
13505
13546
  // src/scripts/discoverQaContext.ts
13506
- import * as fs36 from "fs";
13507
- import * as path34 from "path";
13547
+ import * as fs37 from "fs";
13548
+ import * as path35 from "path";
13508
13549
  function runQaDiscovery(cwd) {
13509
13550
  const out = {
13510
13551
  routes: [],
@@ -13535,9 +13576,9 @@ function runQaDiscovery(cwd) {
13535
13576
  }
13536
13577
  function detectDevServer(cwd, out) {
13537
13578
  try {
13538
- const pkg = JSON.parse(fs36.readFileSync(path34.join(cwd, "package.json"), "utf-8"));
13579
+ const pkg = JSON.parse(fs37.readFileSync(path35.join(cwd, "package.json"), "utf-8"));
13539
13580
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
13540
- const pm = fs36.existsSync(path34.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs36.existsSync(path34.join(cwd, "yarn.lock")) ? "yarn" : fs36.existsSync(path34.join(cwd, "bun.lockb")) ? "bun" : "npm";
13581
+ const pm = fs37.existsSync(path35.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs37.existsSync(path35.join(cwd, "yarn.lock")) ? "yarn" : fs37.existsSync(path35.join(cwd, "bun.lockb")) ? "bun" : "npm";
13541
13582
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
13542
13583
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
13543
13584
  else if (allDeps.vite) out.devPort = 5173;
@@ -13547,8 +13588,8 @@ function detectDevServer(cwd, out) {
13547
13588
  function scanFrontendRoutes(cwd, out) {
13548
13589
  const appDirs = ["src/app", "app"];
13549
13590
  for (const appDir of appDirs) {
13550
- const full = path34.join(cwd, appDir);
13551
- if (!fs36.existsSync(full)) continue;
13591
+ const full = path35.join(cwd, appDir);
13592
+ if (!fs37.existsSync(full)) continue;
13552
13593
  walkFrontendRoutes(full, "", out);
13553
13594
  break;
13554
13595
  }
@@ -13556,7 +13597,7 @@ function scanFrontendRoutes(cwd, out) {
13556
13597
  function walkFrontendRoutes(dir, prefix, out) {
13557
13598
  let entries;
13558
13599
  try {
13559
- entries = fs36.readdirSync(dir, { withFileTypes: true });
13600
+ entries = fs37.readdirSync(dir, { withFileTypes: true });
13560
13601
  } catch {
13561
13602
  return;
13562
13603
  }
@@ -13573,7 +13614,7 @@ function walkFrontendRoutes(dir, prefix, out) {
13573
13614
  if (entry.name === "node_modules" || entry.name === ".next") continue;
13574
13615
  let segment = entry.name;
13575
13616
  if (segment.startsWith("(") && segment.endsWith(")")) {
13576
- walkFrontendRoutes(path34.join(dir, entry.name), prefix, out);
13617
+ walkFrontendRoutes(path35.join(dir, entry.name), prefix, out);
13577
13618
  continue;
13578
13619
  }
13579
13620
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -13581,7 +13622,7 @@ function walkFrontendRoutes(dir, prefix, out) {
13581
13622
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
13582
13623
  segment = `:${segment.slice(1, -1)}`;
13583
13624
  }
13584
- walkFrontendRoutes(path34.join(dir, entry.name), `${prefix}/${segment}`, out);
13625
+ walkFrontendRoutes(path35.join(dir, entry.name), `${prefix}/${segment}`, out);
13585
13626
  }
13586
13627
  }
13587
13628
  function detectAuthFiles(cwd, out) {
@@ -13598,23 +13639,23 @@ function detectAuthFiles(cwd, out) {
13598
13639
  "src/app/api/oauth"
13599
13640
  ];
13600
13641
  for (const c of candidates) {
13601
- if (fs36.existsSync(path34.join(cwd, c))) out.authFiles.push(c);
13642
+ if (fs37.existsSync(path35.join(cwd, c))) out.authFiles.push(c);
13602
13643
  }
13603
13644
  }
13604
13645
  function detectRoles(cwd, out) {
13605
13646
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
13606
13647
  for (const rp of rolePaths) {
13607
- const dir = path34.join(cwd, rp);
13608
- if (!fs36.existsSync(dir)) continue;
13648
+ const dir = path35.join(cwd, rp);
13649
+ if (!fs37.existsSync(dir)) continue;
13609
13650
  let files;
13610
13651
  try {
13611
- files = fs36.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
13652
+ files = fs37.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
13612
13653
  } catch {
13613
13654
  continue;
13614
13655
  }
13615
13656
  for (const f of files) {
13616
13657
  try {
13617
- const content = fs36.readFileSync(path34.join(dir, f), "utf-8").slice(0, 5e3);
13658
+ const content = fs37.readFileSync(path35.join(dir, f), "utf-8").slice(0, 5e3);
13618
13659
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
13619
13660
  if (roleMatches) {
13620
13661
  for (const m of roleMatches) {
@@ -13875,8 +13916,8 @@ var init_dispatchClassified = __esm({
13875
13916
  });
13876
13917
 
13877
13918
  // src/loopDefinitions.ts
13878
- import * as fs37 from "fs";
13879
- import * as path35 from "path";
13919
+ import * as fs38 from "fs";
13920
+ import * as path36 from "path";
13880
13921
  function normalizeLoopDefinition(value) {
13881
13922
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
13882
13923
  const raw = value;
@@ -13903,10 +13944,10 @@ function readLoopDefinition(cwd, id) {
13903
13944
  if (!ID.test(id)) return null;
13904
13945
  const roots = loopRoots(cwd);
13905
13946
  for (const root of roots) {
13906
- const filePath = path35.join(root, "loops", id, "loop.json");
13907
- if (!fs37.existsSync(filePath)) continue;
13947
+ const filePath = path36.join(root, "loops", id, "loop.json");
13948
+ if (!fs38.existsSync(filePath)) continue;
13908
13949
  try {
13909
- const loop = normalizeLoopDefinition(JSON.parse(fs37.readFileSync(filePath, "utf8")));
13950
+ const loop = normalizeLoopDefinition(JSON.parse(fs38.readFileSync(filePath, "utf8")));
13910
13951
  if (loop?.id === id) return loop;
13911
13952
  process.stderr.write(`[kody] invalid Loop definition: ${filePath}
13912
13953
  `);
@@ -13916,7 +13957,7 @@ function readLoopDefinition(cwd, id) {
13916
13957
  }
13917
13958
  }
13918
13959
  process.stderr.write(
13919
- `[kody] Loop not found: ${id} (${roots.map((root) => path35.join(root, "loops", id, "loop.json")).join(", ")})
13960
+ `[kody] Loop not found: ${id} (${roots.map((root) => path36.join(root, "loops", id, "loop.json")).join(", ")})
13920
13961
  `
13921
13962
  );
13922
13963
  return null;
@@ -13925,14 +13966,14 @@ function listLoopDefinitions(cwd) {
13925
13966
  const roots = loopRoots(cwd);
13926
13967
  const byId = /* @__PURE__ */ new Map();
13927
13968
  for (const root of roots.reverse()) {
13928
- const loopsDir = path35.join(root, "loops");
13929
- if (!fs37.existsSync(loopsDir)) continue;
13930
- for (const id of fs37.readdirSync(loopsDir).sort()) {
13969
+ const loopsDir = path36.join(root, "loops");
13970
+ if (!fs38.existsSync(loopsDir)) continue;
13971
+ for (const id of fs38.readdirSync(loopsDir).sort()) {
13931
13972
  if (!ID.test(id)) continue;
13932
- const filePath = path35.join(loopsDir, id, "loop.json");
13933
- if (!fs37.existsSync(filePath)) continue;
13973
+ const filePath = path36.join(loopsDir, id, "loop.json");
13974
+ if (!fs38.existsSync(filePath)) continue;
13934
13975
  try {
13935
- const loop = normalizeLoopDefinition(JSON.parse(fs37.readFileSync(filePath, "utf8")));
13976
+ const loop = normalizeLoopDefinition(JSON.parse(fs38.readFileSync(filePath, "utf8")));
13936
13977
  if (loop?.id === id) byId.set(id, loop);
13937
13978
  } catch {
13938
13979
  process.stderr.write(`[kody] unreadable Loop definition: ${filePath}
@@ -13944,8 +13985,8 @@ function listLoopDefinitions(cwd) {
13944
13985
  }
13945
13986
  function loopRoots(cwd) {
13946
13987
  return [
13947
- path35.join(cwd, ".kody-engine", "runtime"),
13948
- path35.join(cwd, ".kody-engine", "definitions"),
13988
+ path36.join(cwd, ".kody-engine", "runtime"),
13989
+ path36.join(cwd, ".kody-engine", "definitions"),
13949
13990
  definitionsRoot(cwd)
13950
13991
  ].filter((root, index, roots) => roots.indexOf(root) === index);
13951
13992
  }
@@ -15223,15 +15264,15 @@ var init_fixFlow = __esm({
15223
15264
  });
15224
15265
 
15225
15266
  // src/workflow-template.ts
15226
- import * as fs38 from "fs";
15227
- import * as path36 from "path";
15267
+ import * as fs39 from "fs";
15268
+ import * as path37 from "path";
15228
15269
  import { fileURLToPath } from "url";
15229
15270
  function loadKodyWorkflowTemplate() {
15230
- const here = path36.dirname(fileURLToPath(import.meta.url));
15231
- const candidates = [path36.resolve(here, "../templates/kody.yml"), path36.resolve(here, "../../templates/kody.yml")];
15232
- const source = candidates.find((candidate) => fs38.existsSync(candidate));
15271
+ const here = path37.dirname(fileURLToPath(import.meta.url));
15272
+ const candidates = [path37.resolve(here, "../templates/kody.yml"), path37.resolve(here, "../../templates/kody.yml")];
15273
+ const source = candidates.find((candidate) => fs39.existsSync(candidate));
15233
15274
  if (!source) throw new Error(`Kody workflow template is missing: ${KODY_WORKFLOW_TEMPLATE_PATH}`);
15234
- return fs38.readFileSync(source, "utf8");
15275
+ return fs39.readFileSync(source, "utf8");
15235
15276
  }
15236
15277
  var KODY_WORKFLOW_TEMPLATE_PATH;
15237
15278
  var init_workflow_template = __esm({
@@ -15243,12 +15284,12 @@ var init_workflow_template = __esm({
15243
15284
 
15244
15285
  // src/scripts/initFlow.ts
15245
15286
  import { execFileSync as execFileSync14 } from "child_process";
15246
- import * as fs39 from "fs";
15247
- import * as path37 from "path";
15287
+ import * as fs40 from "fs";
15288
+ import * as path38 from "path";
15248
15289
  function detectPackageManager(cwd) {
15249
- if (fs39.existsSync(path37.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
15250
- if (fs39.existsSync(path37.join(cwd, "yarn.lock"))) return "yarn";
15251
- if (fs39.existsSync(path37.join(cwd, "bun.lockb"))) return "bun";
15290
+ if (fs40.existsSync(path38.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
15291
+ if (fs40.existsSync(path38.join(cwd, "yarn.lock"))) return "yarn";
15292
+ if (fs40.existsSync(path38.join(cwd, "bun.lockb"))) return "bun";
15252
15293
  return "npm";
15253
15294
  }
15254
15295
  function qualityCommandsFor(pm) {
@@ -15320,22 +15361,22 @@ function performInit(cwd, force) {
15320
15361
  const pm = detectPackageManager(cwd);
15321
15362
  const ownerRepo = detectOwnerRepo(cwd);
15322
15363
  const defaultBranch = defaultBranchFromGit(cwd);
15323
- const configPath = path37.join(cwd, "kody.config.json");
15324
- if (fs39.existsSync(configPath) && !force) {
15364
+ const configPath = path38.join(cwd, "kody.config.json");
15365
+ if (fs40.existsSync(configPath) && !force) {
15325
15366
  skipped.push("kody.config.json");
15326
15367
  } else {
15327
15368
  const cfg = makeConfig(pm, ownerRepo, defaultBranch);
15328
- fs39.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
15369
+ fs40.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
15329
15370
  `);
15330
15371
  wrote.push("kody.config.json");
15331
15372
  }
15332
- const workflowDir = path37.join(cwd, ".github", "workflows");
15333
- const workflowPath = path37.join(workflowDir, "kody.yml");
15334
- if (fs39.existsSync(workflowPath) && !force) {
15373
+ const workflowDir = path38.join(cwd, ".github", "workflows");
15374
+ const workflowPath = path38.join(workflowDir, "kody.yml");
15375
+ if (fs40.existsSync(workflowPath) && !force) {
15335
15376
  skipped.push(".github/workflows/kody.yml");
15336
15377
  } else {
15337
- fs39.mkdirSync(workflowDir, { recursive: true });
15338
- fs39.writeFileSync(workflowPath, loadKodyWorkflowTemplate());
15378
+ fs40.mkdirSync(workflowDir, { recursive: true });
15379
+ fs40.writeFileSync(workflowPath, loadKodyWorkflowTemplate());
15339
15380
  wrote.push(".github/workflows/kody.yml");
15340
15381
  }
15341
15382
  let labels;
@@ -15386,7 +15427,7 @@ Nothing to do. All files already present. (Use --force to overwrite.)
15386
15427
  });
15387
15428
 
15388
15429
  // src/scripts/loadAgentAdhoc.ts
15389
- import * as fs40 from "fs";
15430
+ import * as fs41 from "fs";
15390
15431
  function resolveMessage(messageArg) {
15391
15432
  const fromComment = readCommentBody();
15392
15433
  if (fromComment) return stripDirective(fromComment);
@@ -15394,9 +15435,9 @@ function resolveMessage(messageArg) {
15394
15435
  }
15395
15436
  function readCommentBody() {
15396
15437
  const eventPath = process.env.GITHUB_EVENT_PATH;
15397
- if (!eventPath || !fs40.existsSync(eventPath)) return "";
15438
+ if (!eventPath || !fs41.existsSync(eventPath)) return "";
15398
15439
  try {
15399
- const event = JSON.parse(fs40.readFileSync(eventPath, "utf-8"));
15440
+ const event = JSON.parse(fs41.readFileSync(eventPath, "utf-8"));
15400
15441
  return String(event.comment?.body ?? "");
15401
15442
  } catch {
15402
15443
  return "";
@@ -15450,10 +15491,10 @@ var init_loadAgentAdhoc = __esm({
15450
15491
  throw new Error("loadAgentAdhoc: ctx.args.agent must be a non-empty slug");
15451
15492
  }
15452
15493
  const agentPath = resolveAgentFile2(ctx.cwd, agentSlug, agentsDir);
15453
- if (!fs40.existsSync(agentPath)) {
15494
+ if (!fs41.existsSync(agentPath)) {
15454
15495
  throw new Error(`loadAgentAdhoc: agent identity not found: ${agentPath}`);
15455
15496
  }
15456
- const { title, body } = parseAgentFile(fs40.readFileSync(agentPath, "utf-8"), agentSlug);
15497
+ const { title, body } = parseAgentFile(fs41.readFileSync(agentPath, "utf-8"), agentSlug);
15457
15498
  const message = resolveMessage(ctx.args.message);
15458
15499
  if (!message) {
15459
15500
  throw new Error(
@@ -15525,13 +15566,13 @@ var init_loadCapabilityState = __esm({
15525
15566
  function isCompanyIntentId(value) {
15526
15567
  return SLUG_RE2.test(value);
15527
15568
  }
15528
- function normalizeCompanyIntent(path55, raw) {
15569
+ function normalizeCompanyIntent(path58, raw) {
15529
15570
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
15530
- throw new Error(`${path55}: intent must be JSON object`);
15571
+ throw new Error(`${path58}: intent must be JSON object`);
15531
15572
  }
15532
15573
  const input = raw;
15533
15574
  const id = stringField4(input.id);
15534
- if (!id || !isCompanyIntentId(id)) throw new Error(`${path55}: invalid intent id`);
15575
+ if (!id || !isCompanyIntentId(id)) throw new Error(`${path58}: invalid intent id`);
15535
15576
  const createdAt = stringField4(input.createdAt) || nowIso();
15536
15577
  const updatedAt = stringField4(input.updatedAt) || createdAt;
15537
15578
  const description = stringField4(input.description);
@@ -15693,7 +15734,7 @@ function retryDelaysMs() {
15693
15734
  }
15694
15735
  function sleep(ms) {
15695
15736
  if (ms <= 0) return Promise.resolve();
15696
- return new Promise((resolve21) => setTimeout(resolve21, ms));
15737
+ return new Promise((resolve23) => setTimeout(resolve23, ms));
15697
15738
  }
15698
15739
  async function fetchGoalStateWithRetry(config, goalId, cwd) {
15699
15740
  let state = await fetchGoalStateAsync(config, goalId, cwd);
@@ -15822,8 +15863,8 @@ var init_loadIssueStateComment = __esm({
15822
15863
  });
15823
15864
 
15824
15865
  // src/scripts/loadJobFromFile.ts
15825
- import * as fs41 from "fs";
15826
- import * as path38 from "path";
15866
+ import * as fs42 from "fs";
15867
+ import * as path39 from "path";
15827
15868
  function parseJobFile(raw, slug) {
15828
15869
  let stripped = raw;
15829
15870
  if (stripped.startsWith("---\n")) {
@@ -15862,10 +15903,10 @@ var init_loadJobFromFile = __esm({
15862
15903
  if (!slug) {
15863
15904
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
15864
15905
  }
15865
- const capability = resolveCapabilityFolder(slug, path38.resolve(ctx.cwd, jobsDir));
15906
+ const capability = resolveCapabilityFolder(slug, path39.resolve(ctx.cwd, jobsDir));
15866
15907
  if (!capability) {
15867
15908
  throw new Error(
15868
- `loadJobFromFile: capability folder not found or incomplete: ${path38.resolve(ctx.cwd, jobsDir, slug)}`
15909
+ `loadJobFromFile: capability folder not found or incomplete: ${path39.resolve(ctx.cwd, jobsDir, slug)}`
15869
15910
  );
15870
15911
  }
15871
15912
  const { title, body, config } = capability;
@@ -15875,12 +15916,12 @@ var init_loadJobFromFile = __esm({
15875
15916
  let agentIdentity = "";
15876
15917
  if (agentSlug) {
15877
15918
  const agentPath = resolveAgentFile2(ctx.cwd, agentSlug, agentsDir);
15878
- if (!fs41.existsSync(agentPath)) {
15919
+ if (!fs42.existsSync(agentPath)) {
15879
15920
  throw new Error(
15880
15921
  `loadJobFromFile: capability '${slug}' declares agent '${agentSlug}' but ${agentPath} does not exist`
15881
15922
  );
15882
15923
  }
15883
- const agentRaw = fs41.readFileSync(agentPath, "utf-8");
15924
+ const agentRaw = fs42.readFileSync(agentPath, "utf-8");
15884
15925
  const parsed = parseJobFile(agentRaw, agentSlug);
15885
15926
  agentTitle = parsed.title;
15886
15927
  agentIdentity = parsed.body;
@@ -15960,13 +16001,13 @@ ${truncate2(issue2.body, FINDING_BODY_MAX_BYTES)}`;
15960
16001
  });
15961
16002
 
15962
16003
  // src/scripts/kodyVariables.ts
15963
- import * as fs42 from "fs";
15964
- import * as path39 from "path";
16004
+ import * as fs43 from "fs";
16005
+ import * as path40 from "path";
15965
16006
  function readKodyVariables(cwd) {
15966
- const full = path39.join(cwd, KODY_VARIABLES_REL_PATH);
16007
+ const full = path40.join(cwd, KODY_VARIABLES_REL_PATH);
15967
16008
  let raw;
15968
16009
  try {
15969
- raw = fs42.readFileSync(full, "utf-8");
16010
+ raw = fs43.readFileSync(full, "utf-8");
15970
16011
  } catch {
15971
16012
  return {};
15972
16013
  }
@@ -15991,8 +16032,8 @@ var init_kodyVariables = __esm({
15991
16032
  });
15992
16033
 
15993
16034
  // src/scripts/loadQaContext.ts
15994
- import * as fs43 from "fs";
15995
- import * as path40 from "path";
16035
+ import * as fs44 from "fs";
16036
+ import * as path41 from "path";
15996
16037
  function parseSlugList(value) {
15997
16038
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
15998
16039
  return inner.split(",").map(
@@ -16021,18 +16062,18 @@ function readProfileAgents(raw) {
16021
16062
  return { agent: agent ?? legacy ?? ["kody"], body };
16022
16063
  }
16023
16064
  function readProfile(cwd) {
16024
- const dir = path40.join(cwd, CONTEXT_DIR_REL_PATH);
16025
- if (!fs43.existsSync(dir)) return "";
16065
+ const dir = path41.join(cwd, CONTEXT_DIR_REL_PATH);
16066
+ if (!fs44.existsSync(dir)) return "";
16026
16067
  let entries;
16027
16068
  try {
16028
- entries = fs43.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
16069
+ entries = fs44.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
16029
16070
  } catch {
16030
16071
  return "";
16031
16072
  }
16032
16073
  const blocks = [];
16033
16074
  for (const file of entries) {
16034
16075
  try {
16035
- const raw = fs43.readFileSync(path40.join(dir, file), "utf-8");
16076
+ const raw = fs44.readFileSync(path41.join(dir, file), "utf-8");
16036
16077
  const { agent, body } = readProfileAgents(raw);
16037
16078
  if (!agent.includes(QA_AGENT) && !agent.includes(ALL_AGENTS)) continue;
16038
16079
  blocks.push(`## ${file}
@@ -16082,9 +16123,9 @@ var init_loadQaContext = __esm({
16082
16123
 
16083
16124
  // src/scripts/loadSimpleCapability.ts
16084
16125
  import { randomUUID as randomUUID2 } from "crypto";
16085
- import * as fs44 from "fs";
16126
+ import * as fs45 from "fs";
16086
16127
  import * as os6 from "os";
16087
- import * as path41 from "path";
16128
+ import * as path42 from "path";
16088
16129
  function registerCapabilitySubagents(profile, toolRoot, toolFiles) {
16089
16130
  const subagentFiles = toolFiles.flatMap((file) => {
16090
16131
  const match = /^agents\/([a-z][a-z0-9-]{0,63})\.md$/.exec(file);
@@ -16097,7 +16138,7 @@ function registerCapabilitySubagents(profile, toolRoot, toolFiles) {
16097
16138
  profile.subagentTemplates = {
16098
16139
  ...profile.subagentTemplates ?? {},
16099
16140
  ...Object.fromEntries(
16100
- subagentFiles.map(({ name, file }) => [name, fs44.readFileSync(path41.join(toolRoot, file), "utf-8")])
16141
+ subagentFiles.map(({ name, file }) => [name, fs45.readFileSync(path42.join(toolRoot, file), "utf-8")])
16101
16142
  )
16102
16143
  };
16103
16144
  if (!profile.claudeCode.tools.includes("Agent")) {
@@ -16138,14 +16179,14 @@ function scalar(value) {
16138
16179
  return value;
16139
16180
  }
16140
16181
  function listFiles(root) {
16141
- if (!fs44.existsSync(root)) return [];
16182
+ if (!fs45.existsSync(root)) return [];
16142
16183
  const files = [];
16143
16184
  const visit = (dir) => {
16144
- for (const entry of fs44.readdirSync(dir, { withFileTypes: true })) {
16145
- const absolute = path41.join(dir, entry.name);
16185
+ for (const entry of fs45.readdirSync(dir, { withFileTypes: true })) {
16186
+ const absolute = path42.join(dir, entry.name);
16146
16187
  if (entry.isSymbolicLink()) continue;
16147
16188
  if (entry.isDirectory()) visit(absolute);
16148
- else if (entry.isFile()) files.push(path41.relative(root, absolute));
16189
+ else if (entry.isFile()) files.push(path42.relative(root, absolute));
16149
16190
  }
16150
16191
  };
16151
16192
  visit(root);
@@ -16168,8 +16209,8 @@ var init_loadSimpleCapability = __esm({
16168
16209
  if (!capability) {
16169
16210
  throw new Error(`Capability "${slug}" is not a valid simple capability folder`);
16170
16211
  }
16171
- const toolRoot = path41.join(capability.dir, "tools");
16172
- const skillRoot = path41.join(capability.dir, "skills");
16212
+ const toolRoot = path42.join(capability.dir, "tools");
16213
+ const skillRoot = path42.join(capability.dir, "skills");
16173
16214
  const toolFiles = listFiles(toolRoot);
16174
16215
  const skillFiles = listFiles(skillRoot);
16175
16216
  const parsedInput = parseInput(ctx.args.input);
@@ -16194,14 +16235,14 @@ var init_loadSimpleCapability = __esm({
16194
16235
  }
16195
16236
  if (requiredSubagents.length > 0) ctx.data.requiredSubagents = requiredSubagents;
16196
16237
  if (capability.contract?.execution === "script") {
16197
- ctx.data.capabilityScriptPath = path41.join(capability.dir, "tools", "run.sh");
16238
+ ctx.data.capabilityScriptPath = path42.join(capability.dir, "tools", "run.sh");
16198
16239
  ctx.data.capabilitySecretNames = capability.contract.secrets ?? [];
16199
16240
  ctx.data.capabilityScriptTimeoutMs = capability.contract.timeoutMs;
16200
16241
  }
16201
16242
  if (capability.config.outputSchema) {
16202
16243
  ctx.data.capabilityOutputSchema = capability.config.outputSchema;
16203
16244
  }
16204
- const outputPath = ctx.data.capabilityExecution === "agent" && capability.config.outputSchema ? path41.join(os6.tmpdir(), `kody-capability-output-${randomUUID2()}.json`) : void 0;
16245
+ const outputPath = ctx.data.capabilityExecution === "agent" && capability.config.outputSchema ? path42.join(os6.tmpdir(), `kody-capability-output-${randomUUID2()}.json`) : void 0;
16205
16246
  if (outputPath) ctx.data.capabilityOutputPath = outputPath;
16206
16247
  ctx.data.capabilityEnvironment = {
16207
16248
  ...capabilityInputEnvironment(input),
@@ -16224,7 +16265,7 @@ var init_loadSimpleCapability = __esm({
16224
16265
  ...skillFiles.flatMap((file) => [
16225
16266
  `### ${file}`,
16226
16267
  "",
16227
- fs44.readFileSync(path41.join(skillRoot, file), "utf-8"),
16268
+ fs45.readFileSync(path42.join(skillRoot, file), "utf-8"),
16228
16269
  ""
16229
16270
  ])
16230
16271
  ] : [],
@@ -16233,7 +16274,7 @@ var init_loadSimpleCapability = __esm({
16233
16274
  "## Tools",
16234
16275
  "",
16235
16276
  "Inspect or run these capability-owned files when needed:",
16236
- ...toolFiles.map((file) => `- ${path41.join(toolRoot, file)}`)
16277
+ ...toolFiles.map((file) => `- ${path42.join(toolRoot, file)}`)
16237
16278
  ] : [],
16238
16279
  "",
16239
16280
  ...capability.config.outputSchema ? [
@@ -16264,8 +16305,8 @@ var init_loadSimpleCapability = __esm({
16264
16305
  });
16265
16306
 
16266
16307
  // src/taskContext.ts
16267
- import * as fs45 from "fs";
16268
- import * as path42 from "path";
16308
+ import * as fs46 from "fs";
16309
+ import * as path43 from "path";
16269
16310
  function buildTaskContext(args) {
16270
16311
  return {
16271
16312
  schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
@@ -16281,9 +16322,9 @@ function buildTaskContext(args) {
16281
16322
  function persistTaskContext(cwd, ctx) {
16282
16323
  try {
16283
16324
  const dir = runtimeStatePath(cwd, "agent-runs", ctx.runId);
16284
- fs45.mkdirSync(dir, { recursive: true });
16285
- const file = path42.join(dir, "task-context.json");
16286
- fs45.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16325
+ fs46.mkdirSync(dir, { recursive: true });
16326
+ const file = path43.join(dir, "task-context.json");
16327
+ fs46.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16287
16328
  `);
16288
16329
  return file;
16289
16330
  } catch (err) {
@@ -16710,19 +16751,19 @@ function parseAgencyModelProposal(raw) {
16710
16751
  function normalizeBundleFiles(bundle) {
16711
16752
  const seen = /* @__PURE__ */ new Set();
16712
16753
  return bundle.files.map((file, index) => {
16713
- const path55 = file.path.replace(/^\/+/, "");
16714
- const parts = path55.split("/");
16715
- if (!path55 || file.path.startsWith("/") || file.path.includes("\\") || parts.some((part) => !part || part === "." || part === "..")) {
16754
+ const path58 = file.path.replace(/^\/+/, "");
16755
+ const parts = path58.split("/");
16756
+ if (!path58 || file.path.startsWith("/") || file.path.includes("\\") || parts.some((part) => !part || part === "." || part === "..")) {
16716
16757
  throw new Error(`openAgencyModelReviewPr: files[${index}].path is unsafe`);
16717
16758
  }
16718
16759
  if (!/^(agents\/[^/]+\.md|capabilities\/[^/]+\/.+|goals\/(?:templates\/)?[^/]+\/.+|workflows\/[^/]+\.json)$/.test(
16719
- path55
16760
+ path58
16720
16761
  )) {
16721
16762
  throw new Error(`openAgencyModelReviewPr: files[${index}].path is not a supported definition path`);
16722
16763
  }
16723
- if (seen.has(path55)) throw new Error(`openAgencyModelReviewPr: duplicate generated file path: ${path55}`);
16724
- seen.add(path55);
16725
- return { path: path55, content: file.content.replace(/\r\n?/g, "\n") };
16764
+ if (seen.has(path58)) throw new Error(`openAgencyModelReviewPr: duplicate generated file path: ${path58}`);
16765
+ seen.add(path58);
16766
+ return { path: path58, content: file.content.replace(/\r\n?/g, "\n") };
16726
16767
  });
16727
16768
  }
16728
16769
  function buildProposalId(issueNumber, bundle, sourceLabel) {
@@ -17204,16 +17245,16 @@ var init_parseReproOutput = __esm({
17204
17245
  });
17205
17246
 
17206
17247
  // src/scripts/parseSimpleCapabilityOutput.ts
17207
- import * as fs46 from "fs";
17248
+ import * as fs47 from "fs";
17208
17249
  function stringList2(value) {
17209
17250
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
17210
17251
  }
17211
17252
  function readOutputFile(outputPath) {
17212
- if (!outputPath || !fs46.existsSync(outputPath)) return { found: false };
17253
+ if (!outputPath || !fs47.existsSync(outputPath)) return { found: false };
17213
17254
  try {
17214
- return { found: true, value: JSON.parse(fs46.readFileSync(outputPath, "utf-8")) };
17255
+ return { found: true, value: JSON.parse(fs47.readFileSync(outputPath, "utf-8")) };
17215
17256
  } finally {
17216
- fs46.rmSync(outputPath, { force: true });
17257
+ fs47.rmSync(outputPath, { force: true });
17217
17258
  }
17218
17259
  }
17219
17260
  function parseOutput(text2) {
@@ -17832,9 +17873,9 @@ var init_postResearchComment = __esm({
17832
17873
  });
17833
17874
 
17834
17875
  // src/scripts/prepareBrowserAuth.ts
17835
- import * as fs47 from "fs";
17876
+ import * as fs48 from "fs";
17836
17877
  import * as os7 from "os";
17837
- import * as path43 from "path";
17878
+ import * as path44 from "path";
17838
17879
  function appendAuthMessage(ctx, message) {
17839
17880
  const current = typeof ctx.data.qaAuthBlock === "string" ? ctx.data.qaAuthBlock.trim() : "";
17840
17881
  ctx.data.qaAuthBlock = current ? `${current}
@@ -17873,9 +17914,9 @@ async function githubJson(url, token) {
17873
17914
  return await response.json();
17874
17915
  }
17875
17916
  function writeKodyStorageState(input) {
17876
- const directory = fs47.mkdtempSync(path43.join(os7.tmpdir(), "kody-browser-auth-"));
17877
- fs47.chmodSync(directory, 448);
17878
- const file = path43.join(directory, "storage-state.json");
17917
+ const directory = fs48.mkdtempSync(path44.join(os7.tmpdir(), "kody-browser-auth-"));
17918
+ fs48.chmodSync(directory, 448);
17919
+ const file = path44.join(directory, "storage-state.json");
17879
17920
  const now = Date.now();
17880
17921
  const repoEntry = {
17881
17922
  repoUrl: input.repoUrl,
@@ -17905,7 +17946,7 @@ function writeKodyStorageState(input) {
17905
17946
  }
17906
17947
  ]
17907
17948
  };
17908
- fs47.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17949
+ fs48.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17909
17950
  return { directory, file };
17910
17951
  }
17911
17952
  function configurePlaywright(profile, storageStatePath) {
@@ -17987,7 +18028,7 @@ async function prepareMethod(ctx, profile, method) {
17987
18028
  configurePlaywright(profile, state.file);
17988
18029
  const authDirectory = state.directory;
17989
18030
  registerRuntimeCleanup(ctx, () => {
17990
- fs47.rmSync(authDirectory, { recursive: true, force: true });
18031
+ fs48.rmSync(authDirectory, { recursive: true, force: true });
17991
18032
  });
17992
18033
  appendAuthMessage(
17993
18034
  ctx,
@@ -17995,7 +18036,7 @@ async function prepareMethod(ctx, profile, method) {
17995
18036
  );
17996
18037
  return true;
17997
18038
  } catch (error) {
17998
- if (state) fs47.rmSync(state.directory, { recursive: true, force: true });
18039
+ if (state) fs48.rmSync(state.directory, { recursive: true, force: true });
17999
18040
  const reason = error instanceof Error ? error.message : String(error);
18000
18041
  appendAuthMessage(
18001
18042
  ctx,
@@ -18134,7 +18175,7 @@ var init_prepareCapabilityDelivery = __esm({
18134
18175
 
18135
18176
  // src/scripts/prepareSimpleCapabilityRuntime.ts
18136
18177
  import { isIP } from "net";
18137
- import * as path44 from "path";
18178
+ import * as path45 from "path";
18138
18179
  function requirementsFrom(ctx) {
18139
18180
  const raw = ctx.data.capabilityRequirements;
18140
18181
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
@@ -18178,7 +18219,7 @@ function browserRuntime(ctx, requirements) {
18178
18219
  "--allowed-origins",
18179
18220
  origin,
18180
18221
  "--output-dir",
18181
- path44.resolve(ctx.cwd, "test-results", "quality-runs", qualityRunId)
18222
+ path45.resolve(ctx.cwd, "test-results", "quality-runs", qualityRunId)
18182
18223
  ]
18183
18224
  };
18184
18225
  }
@@ -18543,9 +18584,9 @@ function latestResult(raw, agentResult) {
18543
18584
  function recordField4(value) {
18544
18585
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
18545
18586
  }
18546
- function resolveDotted(root, path55) {
18547
- if (!path55) return void 0;
18548
- return path55.split(".").reduce((value, key) => recordField4(value)?.[key], root);
18587
+ function resolveDotted(root, path58) {
18588
+ if (!path58) return void 0;
18589
+ return path58.split(".").reduce((value, key) => recordField4(value)?.[key], root);
18549
18590
  }
18550
18591
  function stringValue5(value) {
18551
18592
  return typeof value === "string" && value.trim() ? value.trim() : null;
@@ -19387,7 +19428,7 @@ var init_previewBuildHelpers = __esm({
19387
19428
  // src/scripts/previewBuildRun.ts
19388
19429
  import { spawn as spawn5 } from "child_process";
19389
19430
  async function runCmd(cmd, args, opts = {}) {
19390
- await new Promise((resolve21, reject) => {
19431
+ await new Promise((resolve23, reject) => {
19391
19432
  const child = spawn5(cmd, args, {
19392
19433
  cwd: opts.cwd,
19393
19434
  env: { ...process.env, ...opts.env ?? {} },
@@ -19399,7 +19440,7 @@ async function runCmd(cmd, args, opts = {}) {
19399
19440
  }
19400
19441
  child.on("error", reject);
19401
19442
  child.on("close", (code) => {
19402
- if (code === 0) resolve21();
19443
+ if (code === 0) resolve23();
19403
19444
  else reject(new Error(`${cmd} ${args.join(" ")} exited ${code}`));
19404
19445
  });
19405
19446
  });
@@ -19471,12 +19512,12 @@ fi
19471
19512
 
19472
19513
  // src/scripts/runPreviewBuild.ts
19473
19514
  import { copyFile, writeFile } from "fs/promises";
19474
- import * as path45 from "path";
19515
+ import * as path46 from "path";
19475
19516
  import { fileURLToPath as fileURLToPath2 } from "url";
19476
19517
  function bundledDockerfilePath(mode) {
19477
- const here = path45.dirname(fileURLToPath2(import.meta.url));
19518
+ const here = path46.dirname(fileURLToPath2(import.meta.url));
19478
19519
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
19479
- return path45.join(here, "preview-build-templates", file);
19520
+ return path46.join(here, "preview-build-templates", file);
19480
19521
  }
19481
19522
  function required(name) {
19482
19523
  const v = (process.env[name] ?? "").trim();
@@ -19711,10 +19752,10 @@ var init_runPreviewBuild = __esm({
19711
19752
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
19712
19753
  if (Object.keys(buildEnv).length > 0) {
19713
19754
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
19714
- await writeFile(path45.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
19755
+ await writeFile(path46.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
19715
19756
  `, "utf8");
19716
19757
  }
19717
- const consumerDockerfile = path45.join(ctx.cwd, "Dockerfile.preview");
19758
+ const consumerDockerfile = path46.join(ctx.cwd, "Dockerfile.preview");
19718
19759
  const { stat } = await import("fs/promises");
19719
19760
  let hasConsumerDockerfile = false;
19720
19761
  try {
@@ -19898,8 +19939,8 @@ var init_tickShellRunner = __esm({
19898
19939
  });
19899
19940
 
19900
19941
  // src/scripts/runScheduledImplementationTick.ts
19901
- import * as fs48 from "fs";
19902
- import * as path46 from "path";
19942
+ import * as fs49 from "fs";
19943
+ import * as path47 from "path";
19903
19944
  var runScheduledImplementationTick;
19904
19945
  var init_runScheduledImplementationTick = __esm({
19905
19946
  "src/scripts/runScheduledImplementationTick.ts"() {
@@ -19920,14 +19961,14 @@ var init_runScheduledImplementationTick = __esm({
19920
19961
  ctx.output.reason = `runScheduledImplementationTick: args.slug or ctx.args.${slugArg} must be non-empty capability slug`;
19921
19962
  return;
19922
19963
  }
19923
- const capability = resolveCapabilityFolder(slug, path46.resolve(ctx.cwd, jobsDir));
19964
+ const capability = resolveCapabilityFolder(slug, path47.resolve(ctx.cwd, jobsDir));
19924
19965
  if (!capability) {
19925
19966
  ctx.output.exitCode = 99;
19926
19967
  ctx.output.reason = `runScheduledImplementationTick: capability folder not found or incomplete: ${slug} (searched ${jobsDir} and company store)`;
19927
19968
  return;
19928
19969
  }
19929
- const shellPath = path46.join(profile.dir, shell);
19930
- if (!fs48.existsSync(shellPath)) {
19970
+ const shellPath = path47.join(profile.dir, shell);
19971
+ if (!fs49.existsSync(shellPath)) {
19931
19972
  ctx.output.exitCode = 99;
19932
19973
  ctx.output.reason = `runScheduledImplementationTick: shell not found: ${shell} (looked in ${profile.dir})`;
19933
19974
  return;
@@ -19959,13 +20000,13 @@ var init_runScheduledImplementationTick = __esm({
19959
20000
 
19960
20001
  // src/scripts/runSimpleCapabilityScript.ts
19961
20002
  import { spawnSync as spawnSync3 } from "child_process";
19962
- import * as fs49 from "fs";
20003
+ import * as fs50 from "fs";
19963
20004
  function formatDuration2(timeoutMs) {
19964
20005
  return timeoutMs % 6e4 === 0 ? `${timeoutMs / 6e4} minutes` : `${timeoutMs}ms`;
19965
20006
  }
19966
20007
  function isRegularFile2(filePath) {
19967
20008
  try {
19968
- const stat = fs49.lstatSync(filePath);
20009
+ const stat = fs50.lstatSync(filePath);
19969
20010
  return stat.isFile() && !stat.isSymbolicLink();
19970
20011
  } catch {
19971
20012
  return false;
@@ -20044,8 +20085,8 @@ var init_runSimpleCapabilityScript = __esm({
20044
20085
  });
20045
20086
 
20046
20087
  // src/scripts/runTickScript.ts
20047
- import * as fs50 from "fs";
20048
- import * as path47 from "path";
20088
+ import * as fs51 from "fs";
20089
+ import * as path48 from "path";
20049
20090
  var runTickScript;
20050
20091
  var init_runTickScript = __esm({
20051
20092
  "src/scripts/runTickScript.ts"() {
@@ -20065,10 +20106,10 @@ var init_runTickScript = __esm({
20065
20106
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
20066
20107
  return;
20067
20108
  }
20068
- const capability = readCapabilityFolder(path47.resolve(ctx.cwd, jobsDir), slug);
20109
+ const capability = readCapabilityFolder(path48.resolve(ctx.cwd, jobsDir), slug);
20069
20110
  if (!capability) {
20070
20111
  ctx.output.exitCode = 99;
20071
- ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path47.resolve(ctx.cwd, jobsDir, slug)}`;
20112
+ ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path48.resolve(ctx.cwd, jobsDir, slug)}`;
20072
20113
  return;
20073
20114
  }
20074
20115
  const tickScript = capability.config.tickScript;
@@ -20077,8 +20118,8 @@ var init_runTickScript = __esm({
20077
20118
  ctx.output.reason = `runTickScript: capability ${slug} has no \`tickScript\` in profile.json \u2014 route via capability-tick instead`;
20078
20119
  return;
20079
20120
  }
20080
- const scriptPath = path47.isAbsolute(tickScript) ? tickScript : path47.join(ctx.cwd, tickScript);
20081
- if (!fs50.existsSync(scriptPath)) {
20121
+ const scriptPath = path48.isAbsolute(tickScript) ? tickScript : path48.join(ctx.cwd, tickScript);
20122
+ if (!fs51.existsSync(scriptPath)) {
20082
20123
  ctx.output.exitCode = 99;
20083
20124
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
20084
20125
  return;
@@ -20360,7 +20401,7 @@ var init_syncFlow = __esm({
20360
20401
  });
20361
20402
 
20362
20403
  // src/scripts/validateAgencyModelProposal.ts
20363
- import * as path48 from "path";
20404
+ import * as path49 from "path";
20364
20405
  function validateModelBundle(bundle, expectedKind, options = {}) {
20365
20406
  const failures = [];
20366
20407
  validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind, options);
@@ -20678,7 +20719,7 @@ var init_validateAgencyModelProposal = __esm({
20678
20719
  const bundle = parseAgencyModelProposal(raw);
20679
20720
  const expectedKind = readExpectedModelKind(args);
20680
20721
  const failures = validateModelBundle(bundle, expectedKind, {
20681
- capabilityRoot: path48.join(ctx.cwd, ".kody", "capabilities")
20722
+ capabilityRoot: path49.join(ctx.cwd, ".kody", "capabilities")
20682
20723
  });
20683
20724
  if (failures.length > 0) {
20684
20725
  throw new Error(`validateAgencyModelProposal: ${failures.join("; ")}`);
@@ -20741,7 +20782,7 @@ function stripAnsi2(s) {
20741
20782
  return s.replace(ANSI_RE2, "");
20742
20783
  }
20743
20784
  function runCommand2(command, cwd) {
20744
- return new Promise((resolve21) => {
20785
+ return new Promise((resolve23) => {
20745
20786
  const child = spawn6(command, {
20746
20787
  cwd,
20747
20788
  shell: true,
@@ -20768,11 +20809,11 @@ function runCommand2(command, cwd) {
20768
20809
  }, TEST_TIMEOUT_MS);
20769
20810
  child.on("exit", (code) => {
20770
20811
  clearTimeout(timer);
20771
- resolve21({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
20812
+ resolve23({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
20772
20813
  });
20773
20814
  child.on("error", (err) => {
20774
20815
  clearTimeout(timer);
20775
- resolve21({ exitCode: -1, output: err.message });
20816
+ resolve23({ exitCode: -1, output: err.message });
20776
20817
  });
20777
20818
  });
20778
20819
  }
@@ -21178,21 +21219,21 @@ function lineStream(stream) {
21178
21219
  tryDeliver();
21179
21220
  });
21180
21221
  return {
21181
- next: (timeoutMs) => new Promise((resolve21) => {
21222
+ next: (timeoutMs) => new Promise((resolve23) => {
21182
21223
  if (queue.length > 0) {
21183
- resolve21(queue.shift());
21224
+ resolve23(queue.shift());
21184
21225
  return;
21185
21226
  }
21186
21227
  if (ended) {
21187
- resolve21(null);
21228
+ resolve23(null);
21188
21229
  return;
21189
21230
  }
21190
- waiter = resolve21;
21231
+ waiter = resolve23;
21191
21232
  const t = setTimeout(
21192
21233
  () => {
21193
- if (waiter === resolve21) {
21234
+ if (waiter === resolve23) {
21194
21235
  waiter = null;
21195
- resolve21(null);
21236
+ resolve23(null);
21196
21237
  }
21197
21238
  },
21198
21239
  Math.max(0, timeoutMs)
@@ -21229,7 +21270,7 @@ var init_warmupMcp = __esm({
21229
21270
  });
21230
21271
 
21231
21272
  // src/scripts/writeAgentRunSummary.ts
21232
- import * as fs51 from "fs";
21273
+ import * as fs52 from "fs";
21233
21274
  var writeAgentRunSummary;
21234
21275
  var init_writeAgentRunSummary = __esm({
21235
21276
  "src/scripts/writeAgentRunSummary.ts"() {
@@ -21255,7 +21296,7 @@ var init_writeAgentRunSummary = __esm({
21255
21296
  if (reason) lines.push(`- **Reason:** ${reason}`);
21256
21297
  lines.push("");
21257
21298
  try {
21258
- fs51.appendFileSync(summaryPath, `${lines.join("\n")}
21299
+ fs52.appendFileSync(summaryPath, `${lines.join("\n")}
21259
21300
  `);
21260
21301
  } catch {
21261
21302
  }
@@ -21593,17 +21634,17 @@ var init_scripts = __esm({
21593
21634
  });
21594
21635
 
21595
21636
  // src/stateWorkspace.ts
21596
- import * as fs52 from "fs";
21597
- import * as path49 from "path";
21637
+ import * as fs53 from "fs";
21638
+ import * as path50 from "path";
21598
21639
  function tenantId(config) {
21599
21640
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
21600
21641
  const repo = config.github?.repo?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
21601
21642
  return owner && repo ? `${owner}/${repo}` : null;
21602
21643
  }
21603
21644
  function writeRuntimeFile(cwd, relativePath, content) {
21604
- const target = path49.join(cwd, RUNTIME_ROOT, relativePath);
21605
- fs52.mkdirSync(path49.dirname(target), { recursive: true });
21606
- fs52.writeFileSync(target, content, "utf8");
21645
+ const target = path50.join(cwd, RUNTIME_ROOT, relativePath);
21646
+ fs53.mkdirSync(path50.dirname(target), { recursive: true });
21647
+ fs53.writeFileSync(target, content, "utf8");
21607
21648
  }
21608
21649
  function record(value) {
21609
21650
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -21668,11 +21709,11 @@ async function hydrateStateWorkspace(config, cwd, backendOverride) {
21668
21709
  throw new Error("Kody backend access is required for runtime workspace documents");
21669
21710
  return;
21670
21711
  }
21671
- const key = `${path49.resolve(cwd)}|${tenant}`;
21712
+ const key = `${path50.resolve(cwd)}|${tenant}`;
21672
21713
  if (hydratedWorkspaces.has(key)) return;
21673
21714
  const backend = backendOverride ?? createStateBackendFromEnv();
21674
- const root = path49.join(cwd, RUNTIME_ROOT);
21675
- fs52.rmSync(root, { recursive: true, force: true });
21715
+ const root = path50.join(cwd, RUNTIME_ROOT);
21716
+ fs53.rmSync(root, { recursive: true, force: true });
21676
21717
  await Promise.all([
21677
21718
  hydratePrefix(backend, tenant, cwd, "context:"),
21678
21719
  hydratePrefix(backend, tenant, cwd, "memory:"),
@@ -21688,7 +21729,7 @@ var init_stateWorkspace = __esm({
21688
21729
  "src/stateWorkspace.ts"() {
21689
21730
  "use strict";
21690
21731
  init_state_backend();
21691
- RUNTIME_ROOT = path49.join(".kody-engine", "runtime");
21732
+ RUNTIME_ROOT = path50.join(".kody-engine", "runtime");
21692
21733
  hydratedWorkspaces = /* @__PURE__ */ new Set();
21693
21734
  }
21694
21735
  });
@@ -21759,9 +21800,9 @@ var init_tools = __esm({
21759
21800
 
21760
21801
  // src/executor.ts
21761
21802
  import { spawn as spawn8 } from "child_process";
21762
- import * as fs53 from "fs";
21803
+ import * as fs54 from "fs";
21763
21804
  import * as os8 from "os";
21764
- import * as path50 from "path";
21805
+ import * as path51 from "path";
21765
21806
  function isMutatingPostflight(scriptName) {
21766
21807
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
21767
21808
  }
@@ -22013,7 +22054,7 @@ async function runImplementation(profileName, input) {
22013
22054
  const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
22014
22055
  const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
22015
22056
  const invokeAgent = async (prompt) => {
22016
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path50.isAbsolute(p) ? p : path50.resolve(profile.dir, p)).filter((p) => p.length > 0);
22057
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path51.isAbsolute(p) ? p : path51.resolve(profile.dir, p)).filter((p) => p.length > 0);
22017
22058
  const syntheticPath = ctx.data.syntheticPluginPath;
22018
22059
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
22019
22060
  const agents = loadSubagents(profile);
@@ -22493,17 +22534,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
22493
22534
  function resolveProfilePath(profileName, cwd = process.cwd()) {
22494
22535
  const found = resolveImplementation(profileName, getRuntimeProfileRootsForCwd(cwd));
22495
22536
  if (found) return found;
22496
- const here = path50.dirname(new URL(import.meta.url).pathname);
22537
+ const here = path51.dirname(new URL(import.meta.url).pathname);
22497
22538
  const candidates = [
22498
- path50.join(here, "implementations", profileName, "profile.json"),
22539
+ path51.join(here, "implementations", profileName, "profile.json"),
22499
22540
  // same-dir sibling (dev)
22500
- path50.join(here, "..", "implementations", profileName, "profile.json"),
22541
+ path51.join(here, "..", "implementations", profileName, "profile.json"),
22501
22542
  // up one (prod: dist/bin → dist/implementations)
22502
- path50.join(here, "..", "src", "implementations", profileName, "profile.json")
22543
+ path51.join(here, "..", "src", "implementations", profileName, "profile.json")
22503
22544
  // fallback
22504
22545
  ];
22505
22546
  for (const c of candidates) {
22506
- if (fs53.existsSync(c)) return c;
22547
+ if (fs54.existsSync(c)) return c;
22507
22548
  }
22508
22549
  return candidates[0];
22509
22550
  }
@@ -22618,15 +22659,15 @@ function resolveShellTimeoutMs(entry) {
22618
22659
  }
22619
22660
  async function runShellEntry(entry, ctx, profile) {
22620
22661
  const shellName = entry.shell;
22621
- const shellPath = path50.join(profile.dir, shellName);
22622
- if (!fs53.existsSync(shellPath)) {
22662
+ const shellPath = path51.join(profile.dir, shellName);
22663
+ if (!fs54.existsSync(shellPath)) {
22623
22664
  ctx.skipAgent = true;
22624
22665
  ctx.output.exitCode = 99;
22625
22666
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
22626
22667
  return;
22627
22668
  }
22628
22669
  const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
22629
- const outputFile = path50.join(
22670
+ const outputFile = path51.join(
22630
22671
  os8.tmpdir(),
22631
22672
  `kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
22632
22673
  );
@@ -22656,14 +22697,14 @@ async function runShellEntry(entry, ctx, profile) {
22656
22697
  let killTimer;
22657
22698
  let escalateTimer;
22658
22699
  const result = await new Promise(
22659
- (resolve21) => {
22700
+ (resolve23) => {
22660
22701
  let settled = false;
22661
22702
  const settle = (code, signal, spawnErr) => {
22662
22703
  if (settled) return;
22663
22704
  settled = true;
22664
22705
  if (killTimer) clearTimeout(killTimer);
22665
22706
  if (escalateTimer) clearTimeout(escalateTimer);
22666
- resolve21({ code, signal, spawnErr });
22707
+ resolve23({ code, signal, spawnErr });
22667
22708
  };
22668
22709
  child.on("error", (err) => settle(null, null, err));
22669
22710
  child.on("close", (code, signal) => settle(code, signal));
@@ -22693,9 +22734,9 @@ async function runShellEntry(entry, ctx, profile) {
22693
22734
  }
22694
22735
  let sideChannelText = "";
22695
22736
  try {
22696
- if (fs53.existsSync(outputFile)) {
22697
- sideChannelText = fs53.readFileSync(outputFile, "utf-8");
22698
- fs53.rmSync(outputFile, { force: true });
22737
+ if (fs54.existsSync(outputFile)) {
22738
+ sideChannelText = fs54.readFileSync(outputFile, "utf-8");
22739
+ fs54.rmSync(outputFile, { force: true });
22699
22740
  }
22700
22741
  } catch {
22701
22742
  }
@@ -23545,25 +23586,31 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
23545
23586
 
23546
23587
  `
23547
23588
  );
23548
- result = await runJob(child, {
23549
- ...base,
23550
- preloadedData: {
23551
- ...chainData,
23552
- runSubjectType: "capability",
23553
- runSubjectId: step.capability,
23554
- runSubjectLabel: step.id,
23555
- workflowStep: step.id,
23556
- workflowStepIndex: index + 1,
23557
- workflowExecutionKey: graphWorkflowExecutionKey(
23558
- base.preloadedData?.workflowExecutionKey,
23559
- capability.slug,
23560
- step.id,
23561
- state.transitionCounts
23562
- ),
23563
- workflowStepReason: step.reason,
23564
- workflowContinueOn: step.continueOn ?? []
23565
- }
23566
- });
23589
+ const stepAbort = workflowStepAbortController(base.abortController, step.timeoutSeconds);
23590
+ try {
23591
+ result = await runJob(child, {
23592
+ ...base,
23593
+ abortController: stepAbort.controller,
23594
+ preloadedData: {
23595
+ ...chainData,
23596
+ runSubjectType: "capability",
23597
+ runSubjectId: step.capability,
23598
+ runSubjectLabel: step.id,
23599
+ workflowStep: step.id,
23600
+ workflowStepIndex: index + 1,
23601
+ workflowExecutionKey: graphWorkflowExecutionKey(
23602
+ base.preloadedData?.workflowExecutionKey,
23603
+ capability.slug,
23604
+ step.id,
23605
+ state.transitionCounts
23606
+ ),
23607
+ workflowStepReason: step.reason,
23608
+ workflowContinueOn: step.continueOn ?? []
23609
+ }
23610
+ });
23611
+ } finally {
23612
+ stepAbort.cleanup();
23613
+ }
23567
23614
  finishWorkflowStep(state, step, result);
23568
23615
  mergeWorkflowResults(state, result.capabilityResults);
23569
23616
  if (result.capabilityOutput && typeof result.capabilityOutput === "object" && !Array.isArray(result.capabilityOutput)) {
@@ -23699,11 +23746,11 @@ function exhaustedWorkflowTransitions(step, data, counts) {
23699
23746
  }
23700
23747
  function workflowResultConditionPaths(transitions) {
23701
23748
  return transitions.flatMap(
23702
- (transition) => Object.keys(transition.when ?? {}).filter((path55) => path55.startsWith("result."))
23749
+ (transition) => Object.keys(transition.when ?? {}).filter((path58) => path58.startsWith("result."))
23703
23750
  );
23704
23751
  }
23705
23752
  function conditionMatches(condition, context) {
23706
- return Object.entries(condition).every(([path55, expected]) => valueMatches(resolveDottedPath2(context, path55), expected));
23753
+ return Object.entries(condition).every(([path58, expected]) => valueMatches(resolveDottedPath2(context, path58), expected));
23707
23754
  }
23708
23755
  function withWorkflowBoundaryEval(capability, result) {
23709
23756
  const capabilityKind = capability.config.capabilityKind;
@@ -23871,6 +23918,24 @@ function canContinueWorkflow(step, outcome) {
23871
23918
  if (!outcome || !step.continueOn || step.continueOn.length === 0) return false;
23872
23919
  return step.continueOn.includes(outcome.type);
23873
23920
  }
23921
+ function workflowStepAbortController(parent, timeoutSeconds) {
23922
+ if (!timeoutSeconds) return { controller: parent, cleanup: () => void 0 };
23923
+ const controller = new AbortController();
23924
+ const forwardParentAbort = () => controller.abort(parent?.signal.reason);
23925
+ if (parent?.signal.aborted) forwardParentAbort();
23926
+ else parent?.signal.addEventListener("abort", forwardParentAbort, { once: true });
23927
+ const timer = setTimeout(() => {
23928
+ controller.abort(new Error(`workflow step timed out after ${timeoutSeconds}s`));
23929
+ }, timeoutSeconds * 1e3);
23930
+ timer.unref?.();
23931
+ return {
23932
+ controller,
23933
+ cleanup: () => {
23934
+ clearTimeout(timer);
23935
+ parent?.signal.removeEventListener("abort", forwardParentAbort);
23936
+ }
23937
+ };
23938
+ }
23874
23939
  function workflowOutcome(result) {
23875
23940
  return result.taskState?.core.lastOutcome ?? null;
23876
23941
  }
@@ -24133,7 +24198,7 @@ function translateOpenAISseToBrain(opts) {
24133
24198
 
24134
24199
  // src/servers/brain-serve.ts
24135
24200
  import { createServer as createServer2 } from "http";
24136
- import * as path53 from "path";
24201
+ import * as path54 from "path";
24137
24202
 
24138
24203
  // src/chat/loop.ts
24139
24204
  init_agent();
@@ -24141,13 +24206,13 @@ init_agents();
24141
24206
  init_config();
24142
24207
  init_registry();
24143
24208
  init_task_artifacts();
24144
- import * as fs17 from "fs";
24145
- import * as path18 from "path";
24209
+ import * as fs18 from "fs";
24210
+ import * as path19 from "path";
24146
24211
 
24147
24212
  // src/chat/attachments.ts
24148
24213
  init_runtimePaths();
24149
- import * as fs14 from "fs";
24150
- import * as path15 from "path";
24214
+ import * as fs15 from "fs";
24215
+ import * as path16 from "path";
24151
24216
  var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
24152
24217
  var EXT_BY_MIME = {
24153
24218
  "image/png": "png",
@@ -24180,11 +24245,11 @@ function prepareAttachments(turns, cwd, sessionId) {
24180
24245
  if (!isImage) return `[File: ${name}]`;
24181
24246
  try {
24182
24247
  if (!dirEnsured) {
24183
- fs14.mkdirSync(dir, { recursive: true });
24248
+ fs15.mkdirSync(dir, { recursive: true });
24184
24249
  dirEnsured = true;
24185
24250
  }
24186
- const filePath = path15.join(dir, `${imageCounter}.${extFor(mime)}`);
24187
- fs14.writeFileSync(filePath, Buffer.from(data, "base64"));
24251
+ const filePath = path16.join(dir, `${imageCounter}.${extFor(mime)}`);
24252
+ fs15.writeFileSync(filePath, Buffer.from(data, "base64"));
24188
24253
  imageCounter += 1;
24189
24254
  imagePaths.push(filePath);
24190
24255
  return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it.]`;
@@ -24201,8 +24266,8 @@ function prepareAttachments(turns, cwd, sessionId) {
24201
24266
 
24202
24267
  // src/chat/codex-app-server.ts
24203
24268
  import { spawn as spawn3 } from "child_process";
24204
- import * as fs15 from "fs";
24205
- import * as path16 from "path";
24269
+ import * as fs16 from "fs";
24270
+ import * as path17 from "path";
24206
24271
  import { createInterface } from "readline";
24207
24272
  function codexThreadStartParams(args) {
24208
24273
  return {
@@ -24287,9 +24352,9 @@ var CodexAppServerClient = class {
24287
24352
  await this.request("thread/resume", { threadId });
24288
24353
  }
24289
24354
  async runTurn(args) {
24290
- await new Promise((resolve21, reject) => {
24355
+ await new Promise((resolve23, reject) => {
24291
24356
  this.process.turnWaiters.set(args.threadId, {
24292
- resolve: resolve21,
24357
+ resolve: resolve23,
24293
24358
  reject,
24294
24359
  onNotification: args.onNotification,
24295
24360
  queue: Promise.resolve()
@@ -24306,8 +24371,8 @@ var CodexAppServerClient = class {
24306
24371
  }
24307
24372
  request(method, params) {
24308
24373
  const id = this.process.nextId++;
24309
- return new Promise((resolve21, reject) => {
24310
- this.process.pending.set(id, { resolve: resolve21, reject });
24374
+ return new Promise((resolve23, reject) => {
24375
+ this.process.pending.set(id, { resolve: resolve23, reject });
24311
24376
  this.process.child.stdin.write(`${JSON.stringify({ method, id, params })}
24312
24377
  `);
24313
24378
  });
@@ -24373,11 +24438,11 @@ var CodexAppServerClient = class {
24373
24438
  };
24374
24439
  var clients = /* @__PURE__ */ new Map();
24375
24440
  function threadMapPath(cwd) {
24376
- return path16.join(cwd, ".kody-engine", "runtime", "codex-threads.json");
24441
+ return path17.join(cwd, ".kody-engine", "runtime", "codex-threads.json");
24377
24442
  }
24378
24443
  function readThreadMap(cwd) {
24379
24444
  try {
24380
- const value = JSON.parse(fs15.readFileSync(threadMapPath(cwd), "utf8"));
24445
+ const value = JSON.parse(fs16.readFileSync(threadMapPath(cwd), "utf8"));
24381
24446
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
24382
24447
  return Object.fromEntries(
24383
24448
  Object.entries(value).filter(
@@ -24390,8 +24455,8 @@ function readThreadMap(cwd) {
24390
24455
  }
24391
24456
  function writeThreadMap(cwd, map) {
24392
24457
  const file = threadMapPath(cwd);
24393
- fs15.mkdirSync(path16.dirname(file), { recursive: true });
24394
- fs15.writeFileSync(file, `${JSON.stringify(map, null, 2)}
24458
+ fs16.mkdirSync(path17.dirname(file), { recursive: true });
24459
+ fs16.writeFileSync(file, `${JSON.stringify(map, null, 2)}
24395
24460
  `);
24396
24461
  }
24397
24462
  async function runCodexChatTurn(args) {
@@ -24481,8 +24546,8 @@ async function runCodexChatTurn(args) {
24481
24546
  }
24482
24547
 
24483
24548
  // src/chat/events.ts
24484
- import * as fs16 from "fs";
24485
- import * as path17 from "path";
24549
+ import * as fs17 from "fs";
24550
+ import * as path18 from "path";
24486
24551
  import posixPath2 from "path/posix";
24487
24552
  var BackendEventSink = class {
24488
24553
  constructor(append, tenantId2, sessionId) {
@@ -24498,7 +24563,7 @@ var BackendEventSink = class {
24498
24563
  }
24499
24564
  };
24500
24565
  function eventsFilePath(cwd, sessionId) {
24501
- return path17.join(cwd, ".kody-engine", "runtime", "events", `${sessionId}.jsonl`);
24566
+ return path18.join(cwd, ".kody-engine", "runtime", "events", `${sessionId}.jsonl`);
24502
24567
  }
24503
24568
  var FileSink = class {
24504
24569
  constructor(file) {
@@ -24506,8 +24571,8 @@ var FileSink = class {
24506
24571
  }
24507
24572
  file;
24508
24573
  async emit(event) {
24509
- fs16.mkdirSync(path17.dirname(this.file), { recursive: true });
24510
- fs16.appendFileSync(this.file, `${JSON.stringify(event)}
24574
+ fs17.mkdirSync(path18.dirname(this.file), { recursive: true });
24575
+ fs17.appendFileSync(this.file, `${JSON.stringify(event)}
24511
24576
  `);
24512
24577
  }
24513
24578
  };
@@ -24772,7 +24837,7 @@ function buildImplementationCatalog() {
24772
24837
  const entries = [];
24773
24838
  for (const { name, profilePath } of discovered) {
24774
24839
  try {
24775
- const raw = JSON.parse(fs17.readFileSync(profilePath, "utf-8"));
24840
+ const raw = JSON.parse(fs18.readFileSync(profilePath, "utf-8"));
24776
24841
  const describe = typeof raw.describe === "string" ? raw.describe : "";
24777
24842
  const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
24778
24843
  entries.push({ name, describe: firstSentence.trim() });
@@ -24894,7 +24959,7 @@ async function runChatTurn(opts) {
24894
24959
  quiet: opts.quiet,
24895
24960
  additionalDirectories: [
24896
24961
  taskArtifactsPaths.absDir,
24897
- ...Array.from(new Set(imagePaths.map((p2) => path18.dirname(p2))))
24962
+ ...Array.from(new Set(imagePaths.map((p2) => path19.dirname(p2))))
24898
24963
  ],
24899
24964
  systemPromptAppend: systemPrompt,
24900
24965
  ...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {},
@@ -25082,10 +25147,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
25082
25147
  var MEMORY_INDEX_REL = ".kody-engine/runtime/memory/INDEX.md";
25083
25148
  var MAX_INDEX_BYTES = 8e3;
25084
25149
  function readMemoryIndexBlock(cwd) {
25085
- const indexPath = path18.join(cwd, MEMORY_INDEX_REL);
25150
+ const indexPath = path19.join(cwd, MEMORY_INDEX_REL);
25086
25151
  let raw;
25087
25152
  try {
25088
- raw = fs17.readFileSync(indexPath, "utf-8");
25153
+ raw = fs18.readFileSync(indexPath, "utf-8");
25089
25154
  } catch {
25090
25155
  return "";
25091
25156
  }
@@ -25105,17 +25170,17 @@ _\u2026 (memory index truncated; use recall_search to read more)_` : trimmed;
25105
25170
  var CONTEXT_DIR_REL = ".kody-engine/runtime/context";
25106
25171
  var MAX_CONTEXT_BYTES = 12e3;
25107
25172
  function readContextBlock(cwd) {
25108
- const dir = path18.join(cwd, CONTEXT_DIR_REL);
25173
+ const dir = path19.join(cwd, CONTEXT_DIR_REL);
25109
25174
  let files;
25110
25175
  try {
25111
- files = fs17.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
25176
+ files = fs18.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
25112
25177
  } catch {
25113
25178
  return "";
25114
25179
  }
25115
25180
  const sections = [];
25116
25181
  for (const file of files) {
25117
25182
  try {
25118
- const content = fs17.readFileSync(path18.join(dir, file), "utf-8").trim();
25183
+ const content = fs18.readFileSync(path19.join(dir, file), "utf-8").trim();
25119
25184
  if (content) sections.push(`### ${file.replace(/\.md$/, "")}
25120
25185
 
25121
25186
  ${content}`);
@@ -25141,7 +25206,7 @@ var SYSTEM_PROMPT_OVERRIDE_REL = ".kody-engine/runtime/system-prompt.md";
25141
25206
  function readSystemPromptOverride(cwd) {
25142
25207
  let raw;
25143
25208
  try {
25144
- raw = fs17.readFileSync(path18.join(cwd, SYSTEM_PROMPT_OVERRIDE_REL), "utf-8");
25209
+ raw = fs18.readFileSync(path19.join(cwd, SYSTEM_PROMPT_OVERRIDE_REL), "utf-8");
25145
25210
  } catch {
25146
25211
  return null;
25147
25212
  }
@@ -25149,10 +25214,10 @@ function readSystemPromptOverride(cwd) {
25149
25214
  return trimmed.length > 0 ? trimmed : null;
25150
25215
  }
25151
25216
  function readInstructionsBlock(cwd) {
25152
- const instructionsPath = path18.join(cwd, INSTRUCTIONS_REL);
25217
+ const instructionsPath = path19.join(cwd, INSTRUCTIONS_REL);
25153
25218
  let raw;
25154
25219
  try {
25155
- raw = fs17.readFileSync(instructionsPath, "utf-8");
25220
+ raw = fs18.readFileSync(instructionsPath, "utf-8");
25156
25221
  } catch {
25157
25222
  return "";
25158
25223
  }
@@ -25186,15 +25251,15 @@ function resolveBrainDriver(runtime) {
25186
25251
  }
25187
25252
 
25188
25253
  // src/chat/session.ts
25189
- import * as fs18 from "fs";
25190
- import * as path19 from "path";
25254
+ import * as fs19 from "fs";
25255
+ import * as path20 from "path";
25191
25256
  import posixPath3 from "path/posix";
25192
25257
  function sessionFilePath(cwd, sessionId) {
25193
- return path19.join(cwd, ".kody-engine", "runtime", "sessions", `${sessionId}.jsonl`);
25258
+ return path20.join(cwd, ".kody-engine", "runtime", "sessions", `${sessionId}.jsonl`);
25194
25259
  }
25195
25260
  function readSession(file) {
25196
- if (!fs18.existsSync(file)) return [];
25197
- const raw = fs18.readFileSync(file, "utf-8").trim();
25261
+ if (!fs19.existsSync(file)) return [];
25262
+ const raw = fs19.readFileSync(file, "utf-8").trim();
25198
25263
  if (!raw) return [];
25199
25264
  const turns = [];
25200
25265
  for (const line of raw.split("\n")) {
@@ -25217,8 +25282,8 @@ init_config();
25217
25282
  init_state_backend();
25218
25283
  init_workflowDefinitions();
25219
25284
  import { createHash as createHash2 } from "crypto";
25220
- import * as fs20 from "fs";
25221
- import * as path21 from "path";
25285
+ import * as fs21 from "fs";
25286
+ import * as path22 from "path";
25222
25287
  var SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,127}$/;
25223
25288
  var REPOSITORY_OWNED_NAMESPACES = ["loops"];
25224
25289
  function assertSafeDefinitionPath(filePath) {
@@ -25250,9 +25315,9 @@ function verifyDefinition(definition) {
25250
25315
  }
25251
25316
  function writeBundle(root, bundle) {
25252
25317
  for (const [filePath, contents] of Object.entries(bundle.files)) {
25253
- const target = path21.join(root, filePath);
25254
- fs20.mkdirSync(path21.dirname(target), { recursive: true });
25255
- fs20.writeFileSync(target, contents, "utf8");
25318
+ const target = path22.join(root, filePath);
25319
+ fs21.mkdirSync(path22.dirname(target), { recursive: true });
25320
+ fs21.writeFileSync(target, contents, "utf8");
25256
25321
  }
25257
25322
  }
25258
25323
  function writeDefinition(root, kind, definition) {
@@ -25260,22 +25325,22 @@ function writeDefinition(root, kind, definition) {
25260
25325
  if (kind === "agent") {
25261
25326
  const raw = bundle.files["agent.md"];
25262
25327
  if (typeof raw !== "string") throw new Error(`agent definition ${definition.slug} is missing agent.md`);
25263
- fs20.writeFileSync(path21.join(root, "agents", `${definition.slug}.md`), raw, "utf8");
25328
+ fs21.writeFileSync(path22.join(root, "agents", `${definition.slug}.md`), raw, "utf8");
25264
25329
  return;
25265
25330
  }
25266
25331
  if (kind === "goal") {
25267
- writeBundle(path21.join(root, "goals", definition.slug), bundle);
25332
+ writeBundle(path22.join(root, "goals", definition.slug), bundle);
25268
25333
  return;
25269
25334
  }
25270
25335
  if (kind === "implementation") {
25271
- writeBundle(path21.join(root, "implementations", definition.slug), bundle);
25336
+ writeBundle(path22.join(root, "implementations", definition.slug), bundle);
25272
25337
  return;
25273
25338
  }
25274
25339
  if (kind === "asset") {
25275
- writeBundle(path21.join(root, "shared"), bundle);
25340
+ writeBundle(path22.join(root, "shared"), bundle);
25276
25341
  return;
25277
25342
  }
25278
- writeBundle(path21.join(root, "capabilities", definition.slug), bundle);
25343
+ writeBundle(path22.join(root, "capabilities", definition.slug), bundle);
25279
25344
  }
25280
25345
  function writeWorkflow(root, document) {
25281
25346
  const workflow = normalizeWorkflowDefinition(document.definition);
@@ -25283,28 +25348,28 @@ function writeWorkflow(root, document) {
25283
25348
  const contents = `${JSON.stringify(workflow, null, 2)}
25284
25349
  `;
25285
25350
  const bundle = { schemaVersion: 1, files: { "workflow.json": contents } };
25286
- const target = path21.join(root, workflowDefinitionPath(document.workflowId));
25287
- fs20.mkdirSync(path21.dirname(target), { recursive: true });
25288
- fs20.writeFileSync(target, contents, "utf8");
25351
+ const target = path22.join(root, workflowDefinitionPath(document.workflowId));
25352
+ fs21.mkdirSync(path22.dirname(target), { recursive: true });
25353
+ fs21.writeFileSync(target, contents, "utf8");
25289
25354
  return definitionVersion(bundle);
25290
25355
  }
25291
25356
  function preserveRepositoryDefinitions(root, staging) {
25292
25357
  for (const namespace of REPOSITORY_OWNED_NAMESPACES) {
25293
- const source = path21.join(root, namespace);
25294
- if (!fs20.existsSync(source)) continue;
25295
- fs20.cpSync(source, path21.join(staging, namespace), { recursive: true });
25358
+ const source = path22.join(root, namespace);
25359
+ if (!fs21.existsSync(source)) continue;
25360
+ fs21.cpSync(source, path22.join(staging, namespace), { recursive: true });
25296
25361
  }
25297
25362
  }
25298
25363
  async function hydrateDefinitions(options) {
25299
- const root = path21.join(options.cwd, ".kody-engine", "definitions");
25364
+ const root = path22.join(options.cwd, ".kody-engine", "definitions");
25300
25365
  const staging = `${root}.tmp-${process.pid}-${Date.now()}`;
25301
- fs20.rmSync(staging, { recursive: true, force: true });
25302
- fs20.mkdirSync(path21.join(staging, "agents"), { recursive: true });
25303
- fs20.mkdirSync(path21.join(staging, "capabilities"), { recursive: true });
25304
- fs20.mkdirSync(path21.join(staging, "goals"), { recursive: true });
25305
- fs20.mkdirSync(path21.join(staging, "implementations"), { recursive: true });
25306
- fs20.mkdirSync(path21.join(staging, "shared"), { recursive: true });
25307
- fs20.mkdirSync(path21.join(staging, "workflows"), { recursive: true });
25366
+ fs21.rmSync(staging, { recursive: true, force: true });
25367
+ fs21.mkdirSync(path22.join(staging, "agents"), { recursive: true });
25368
+ fs21.mkdirSync(path22.join(staging, "capabilities"), { recursive: true });
25369
+ fs21.mkdirSync(path22.join(staging, "goals"), { recursive: true });
25370
+ fs21.mkdirSync(path22.join(staging, "implementations"), { recursive: true });
25371
+ fs21.mkdirSync(path22.join(staging, "shared"), { recursive: true });
25372
+ fs21.mkdirSync(path22.join(staging, "workflows"), { recursive: true });
25308
25373
  try {
25309
25374
  const [capabilities, agents, goals, implementations, assets, workflows] = await Promise.all([
25310
25375
  options.backend.listDefinitions(options.tenantId, "capability"),
@@ -25345,13 +25410,13 @@ async function hydrateDefinitions(options) {
25345
25410
  hydratedAt: (/* @__PURE__ */ new Date()).toISOString(),
25346
25411
  versions: Object.fromEntries(Object.entries(versions).sort(([left], [right]) => left.localeCompare(right)))
25347
25412
  };
25348
- fs20.writeFileSync(path21.join(staging, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
25413
+ fs21.writeFileSync(path22.join(staging, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
25349
25414
  `, "utf8");
25350
- fs20.rmSync(root, { recursive: true, force: true });
25351
- fs20.renameSync(staging, root);
25415
+ fs21.rmSync(root, { recursive: true, force: true });
25416
+ fs21.renameSync(staging, root);
25352
25417
  return { root, tenantId: options.tenantId, versions: manifest.versions };
25353
25418
  } catch (error) {
25354
- fs20.rmSync(staging, { recursive: true, force: true });
25419
+ fs21.rmSync(staging, { recursive: true, force: true });
25355
25420
  throw error;
25356
25421
  }
25357
25422
  }
@@ -25373,8 +25438,8 @@ async function hydrateDefinitionsFromEnv(cwd = process.cwd(), env = process.env)
25373
25438
 
25374
25439
  // src/kody-cli.ts
25375
25440
  import { execFileSync as execFileSync24 } from "child_process";
25376
- import * as fs54 from "fs";
25377
- import * as path51 from "path";
25441
+ import * as fs55 from "fs";
25442
+ import * as path52 from "path";
25378
25443
 
25379
25444
  // src/app-auth.ts
25380
25445
  import { createSign } from "crypto";
@@ -25503,7 +25568,7 @@ init_definition_paths();
25503
25568
 
25504
25569
  // src/dispatch.ts
25505
25570
  init_config();
25506
- import * as fs21 from "fs";
25571
+ import * as fs22 from "fs";
25507
25572
 
25508
25573
  // src/cron-match.ts
25509
25574
  var FIELD_BOUNDS = [
@@ -25610,10 +25675,10 @@ function autoDispatch(opts) {
25610
25675
  }
25611
25676
  const eventName = process.env.GITHUB_EVENT_NAME;
25612
25677
  const eventPath = process.env.GITHUB_EVENT_PATH;
25613
- if (!eventName || !eventPath || !fs21.existsSync(eventPath)) return null;
25678
+ if (!eventName || !eventPath || !fs22.existsSync(eventPath)) return null;
25614
25679
  let event = {};
25615
25680
  try {
25616
- event = JSON.parse(fs21.readFileSync(eventPath, "utf-8"));
25681
+ event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
25617
25682
  } catch {
25618
25683
  return null;
25619
25684
  }
@@ -25737,7 +25802,7 @@ function autoDispatchTyped(opts) {
25737
25802
  if (legacy) return { kind: "route", ...legacy };
25738
25803
  const eventName = process.env.GITHUB_EVENT_NAME;
25739
25804
  const eventPath = process.env.GITHUB_EVENT_PATH;
25740
- if (!eventName || !eventPath || !fs21.existsSync(eventPath)) {
25805
+ if (!eventName || !eventPath || !fs22.existsSync(eventPath)) {
25741
25806
  return { kind: "silent", reason: "no GHA event context" };
25742
25807
  }
25743
25808
  if (eventName !== "issue_comment") {
@@ -25745,7 +25810,7 @@ function autoDispatchTyped(opts) {
25745
25810
  }
25746
25811
  let event = {};
25747
25812
  try {
25748
- event = JSON.parse(fs21.readFileSync(eventPath, "utf-8"));
25813
+ event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
25749
25814
  } catch {
25750
25815
  return { kind: "silent", reason: "GHA event payload unreadable" };
25751
25816
  }
@@ -25799,7 +25864,7 @@ function dispatchScheduledWatches(opts) {
25799
25864
  for (const exe of listRuntimeProfilesForCwd(opts?.cwd ?? process.cwd())) {
25800
25865
  let raw;
25801
25866
  try {
25802
- raw = fs21.readFileSync(exe.profilePath, "utf-8");
25867
+ raw = fs22.readFileSync(exe.profilePath, "utf-8");
25803
25868
  } catch {
25804
25869
  continue;
25805
25870
  }
@@ -26176,9 +26241,9 @@ async function resolveAuthToken(env = process.env) {
26176
26241
  return void 0;
26177
26242
  }
26178
26243
  function detectPackageManager2(cwd) {
26179
- if (fs54.existsSync(path51.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
26180
- if (fs54.existsSync(path51.join(cwd, "yarn.lock"))) return "yarn";
26181
- if (fs54.existsSync(path51.join(cwd, "bun.lockb"))) return "bun";
26244
+ if (fs55.existsSync(path52.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
26245
+ if (fs55.existsSync(path52.join(cwd, "yarn.lock"))) return "yarn";
26246
+ if (fs55.existsSync(path52.join(cwd, "bun.lockb"))) return "bun";
26182
26247
  return "npm";
26183
26248
  }
26184
26249
  function shouldChainScheduledWatch(match) {
@@ -26281,8 +26346,8 @@ function postFailureTail(issueNumber, cwd, reason) {
26281
26346
  const logPath = lastRunLogPath(cwd);
26282
26347
  let tail = "";
26283
26348
  try {
26284
- if (fs54.existsSync(logPath)) {
26285
- const content = fs54.readFileSync(logPath, "utf-8");
26349
+ if (fs55.existsSync(logPath)) {
26350
+ const content = fs55.readFileSync(logPath, "utf-8");
26286
26351
  tail = content.slice(-3e3);
26287
26352
  }
26288
26353
  } catch {
@@ -26311,7 +26376,7 @@ async function runCi(argv) {
26311
26376
  return 0;
26312
26377
  }
26313
26378
  const args = parseCiArgs(argv);
26314
- const cwd = args.cwd ? path51.resolve(args.cwd) : process.cwd();
26379
+ const cwd = args.cwd ? path52.resolve(args.cwd) : process.cwd();
26315
26380
  try {
26316
26381
  const n = unpackAllSecrets();
26317
26382
  if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
@@ -26377,9 +26442,9 @@ async function runCi(argv) {
26377
26442
  forceRunCliArgs = { goal: envForceMessage };
26378
26443
  }
26379
26444
  }
26380
- if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs54.existsSync(dispatchEventPath)) {
26445
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs55.existsSync(dispatchEventPath)) {
26381
26446
  try {
26382
- const evt = JSON.parse(fs54.readFileSync(dispatchEventPath, "utf-8"));
26447
+ const evt = JSON.parse(fs55.readFileSync(dispatchEventPath, "utf-8"));
26383
26448
  const inputs = objectValue2(evt.inputs);
26384
26449
  const issueInput = parseInt(String(inputs?.issue_number ?? ""), 10);
26385
26450
  const sessionInput = String(inputs?.sessionId ?? "");
@@ -26794,8 +26859,8 @@ init_repoWorkspace();
26794
26859
 
26795
26860
  // src/scripts/brainTurnLog.ts
26796
26861
  init_runtimePaths();
26797
- import * as fs55 from "fs";
26798
- import * as path52 from "path";
26862
+ import * as fs56 from "fs";
26863
+ import * as path53 from "path";
26799
26864
  import posixPath4 from "path/posix";
26800
26865
  var live = /* @__PURE__ */ new Map();
26801
26866
  function brainEventsFilePath(dir, chatId) {
@@ -26803,8 +26868,8 @@ function brainEventsFilePath(dir, chatId) {
26803
26868
  }
26804
26869
  function lastPersistedSeq(dir, chatId) {
26805
26870
  const p = brainEventsFilePath(dir, chatId);
26806
- if (!fs55.existsSync(p)) return 0;
26807
- const lines = fs55.readFileSync(p, "utf-8").split("\n").filter(Boolean);
26871
+ if (!fs56.existsSync(p)) return 0;
26872
+ const lines = fs56.readFileSync(p, "utf-8").split("\n").filter(Boolean);
26808
26873
  if (lines.length === 0) return 0;
26809
26874
  try {
26810
26875
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -26814,9 +26879,9 @@ function lastPersistedSeq(dir, chatId) {
26814
26879
  }
26815
26880
  function readSince(dir, chatId, since) {
26816
26881
  const p = brainEventsFilePath(dir, chatId);
26817
- if (!fs55.existsSync(p)) return [];
26882
+ if (!fs56.existsSync(p)) return [];
26818
26883
  const out = [];
26819
- for (const line of fs55.readFileSync(p, "utf-8").split("\n")) {
26884
+ for (const line of fs56.readFileSync(p, "utf-8").split("\n")) {
26820
26885
  if (!line) continue;
26821
26886
  try {
26822
26887
  const rec = JSON.parse(line);
@@ -26842,12 +26907,12 @@ function beginTurn(dir, chatId) {
26842
26907
  };
26843
26908
  live.set(chatId, state);
26844
26909
  const p = brainEventsFilePath(dir, chatId);
26845
- fs55.mkdirSync(path52.dirname(p), { recursive: true });
26910
+ fs56.mkdirSync(path53.dirname(p), { recursive: true });
26846
26911
  return (event) => {
26847
26912
  state.seq += 1;
26848
26913
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
26849
26914
  try {
26850
- fs55.appendFileSync(p, `${JSON.stringify(rec)}
26915
+ fs56.appendFileSync(p, `${JSON.stringify(rec)}
26851
26916
  `);
26852
26917
  } catch (err) {
26853
26918
  process.stderr.write(
@@ -26886,7 +26951,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
26886
26951
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
26887
26952
  };
26888
26953
  try {
26889
- fs55.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
26954
+ fs56.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
26890
26955
  `);
26891
26956
  } catch {
26892
26957
  }
@@ -26980,17 +27045,17 @@ function authOk(req, expected) {
26980
27045
  return false;
26981
27046
  }
26982
27047
  function readJsonBody(req) {
26983
- return new Promise((resolve21, reject) => {
27048
+ return new Promise((resolve23, reject) => {
26984
27049
  const chunks = [];
26985
27050
  req.on("data", (c) => chunks.push(c));
26986
27051
  req.on("end", () => {
26987
27052
  const raw = Buffer.concat(chunks).toString("utf-8");
26988
27053
  if (!raw.trim()) {
26989
- resolve21({});
27054
+ resolve23({});
26990
27055
  return;
26991
27056
  }
26992
27057
  try {
26993
- resolve21(JSON.parse(raw));
27058
+ resolve23(JSON.parse(raw));
26994
27059
  } catch (err) {
26995
27060
  reject(err instanceof Error ? err : new Error(String(err)));
26996
27061
  }
@@ -27282,7 +27347,7 @@ function buildServer(opts) {
27282
27347
  const runTurn = opts.runTurn ?? runChatTurn;
27283
27348
  const createStore = opts.createStore ?? createSessionStore;
27284
27349
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
27285
- const reposRoot = opts.reposRoot ?? path53.join(path53.dirname(path53.resolve(opts.cwd)), "repos");
27350
+ const reposRoot = opts.reposRoot ?? path54.join(path54.dirname(path54.resolve(opts.cwd)), "repos");
27286
27351
  return createServer2(async (req, res) => {
27287
27352
  if (!req.method || !req.url) {
27288
27353
  sendJson(res, 400, { error: "bad request" });
@@ -27363,11 +27428,11 @@ async function brainServe(opts) {
27363
27428
  litellmUrl,
27364
27429
  driver
27365
27430
  });
27366
- await new Promise((resolve21) => {
27431
+ await new Promise((resolve23) => {
27367
27432
  server.listen(port, "0.0.0.0", () => {
27368
27433
  process.stdout.write(`[brain-serve] listening on 0.0.0.0:${port} (cwd=${opts.cwd})
27369
27434
  `);
27370
- resolve21();
27435
+ resolve23();
27371
27436
  });
27372
27437
  });
27373
27438
  const shutdown = (signal) => {
@@ -27622,14 +27687,14 @@ async function startBrainProxy(opts) {
27622
27687
  const { httpServer, handler } = buildBrainProxy(opts);
27623
27688
  const port = opts.port ?? 0;
27624
27689
  const host = opts.host ?? "127.0.0.1";
27625
- await new Promise((resolve21) => httpServer.listen(port, host, () => resolve21()));
27690
+ await new Promise((resolve23) => httpServer.listen(port, host, () => resolve23()));
27626
27691
  const addr = httpServer.address();
27627
27692
  return {
27628
27693
  httpServer,
27629
27694
  port: addr.port,
27630
27695
  url: `http://${host}:${addr.port}`,
27631
- stop: () => new Promise((resolve21) => {
27632
- httpServer.close(() => resolve21());
27696
+ stop: () => new Promise((resolve23) => {
27697
+ httpServer.close(() => resolve23());
27633
27698
  }),
27634
27699
  handler
27635
27700
  };
@@ -27779,23 +27844,23 @@ function buildMcpHttpServer(opts) {
27779
27844
  httpServer,
27780
27845
  routes,
27781
27846
  port,
27782
- stop: () => new Promise((resolve21) => {
27847
+ stop: () => new Promise((resolve23) => {
27783
27848
  let pending = transports.size;
27784
27849
  if (pending === 0) {
27785
- httpServer.close(() => resolve21());
27850
+ httpServer.close(() => resolve23());
27786
27851
  return;
27787
27852
  }
27788
27853
  for (const transport of transports.values()) {
27789
27854
  void transport.close().finally(() => {
27790
27855
  pending--;
27791
- if (pending === 0) httpServer.close(() => resolve21());
27856
+ if (pending === 0) httpServer.close(() => resolve23());
27792
27857
  });
27793
27858
  }
27794
27859
  })
27795
27860
  };
27796
27861
  }
27797
27862
  function listenMcpHttpServer(server, host = "127.0.0.1") {
27798
- return new Promise((resolve21, reject) => {
27863
+ return new Promise((resolve23, reject) => {
27799
27864
  server.httpServer.once("error", reject);
27800
27865
  server.httpServer.listen(server.port, host, () => {
27801
27866
  server.httpServer.off("error", reject);
@@ -27803,7 +27868,7 @@ function listenMcpHttpServer(server, host = "127.0.0.1") {
27803
27868
  if (addr && typeof addr === "object") {
27804
27869
  server.port = addr.port;
27805
27870
  }
27806
- resolve21();
27871
+ resolve23();
27807
27872
  });
27808
27873
  });
27809
27874
  }
@@ -27886,7 +27951,7 @@ async function loadConfigSafe() {
27886
27951
  }
27887
27952
 
27888
27953
  // src/chat-cli.ts
27889
- import * as path54 from "path";
27954
+ import * as path55 from "path";
27890
27955
 
27891
27956
  // src/chat/inbox.ts
27892
27957
  import { execFileSync as execFileSync25 } from "child_process";
@@ -27953,7 +28018,7 @@ async function waitForNextUserMessage(opts) {
27953
28018
  }
27954
28019
  }
27955
28020
  function sleep3(ms) {
27956
- return new Promise((resolve21) => setTimeout(resolve21, ms));
28021
+ return new Promise((resolve23) => setTimeout(resolve23, ms));
27957
28022
  }
27958
28023
  function currentBranch(cwd) {
27959
28024
  try {
@@ -28177,7 +28242,7 @@ async function runChat(argv) {
28177
28242
  ${CHAT_HELP}`);
28178
28243
  return 64;
28179
28244
  }
28180
- const cwd = args.cwd ? path54.resolve(args.cwd) : process.cwd();
28245
+ const cwd = args.cwd ? path55.resolve(args.cwd) : process.cwd();
28181
28246
  const sessionId = args.sessionId;
28182
28247
  const runRequest = readRunRequestFromEnv();
28183
28248
  if (runRequest && "request" in runRequest) {
@@ -28302,6 +28367,575 @@ init_definition_paths();
28302
28367
  init_job();
28303
28368
  init_registry();
28304
28369
 
28370
+ // src/servers/brain-terminal-agent.ts
28371
+ init_repoWorkspace();
28372
+ import * as path57 from "path";
28373
+ import { createInterface as createInterface2 } from "readline";
28374
+
28375
+ // src/terminal/brain-terminal-session.ts
28376
+ import { createHash as createHash9 } from "crypto";
28377
+ var MAX_CAPTURE_CHARS = 2e5;
28378
+ function requiredIdentifier(value, name, max = 240) {
28379
+ if (typeof value !== "string" || !value.trim() || value.length > max) {
28380
+ throw new Error(`${name} must be a non-empty string of at most ${max} characters`);
28381
+ }
28382
+ return value.trim();
28383
+ }
28384
+ function terminalSize(value, name) {
28385
+ if (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 1e3) {
28386
+ throw new Error(`${name} must be an integer between 1 and 1000`);
28387
+ }
28388
+ return Number(value);
28389
+ }
28390
+ function revision(value) {
28391
+ if (value === void 0) return void 0;
28392
+ if (!Number.isInteger(value) || Number(value) < 0) {
28393
+ throw new Error("afterRevision must be a non-negative integer");
28394
+ }
28395
+ return Number(value);
28396
+ }
28397
+ function parseBrainTerminalOpenRequest(value) {
28398
+ if (!value || typeof value !== "object") throw new Error("open request must be an object");
28399
+ const request = value;
28400
+ if (request.type !== "open") throw new Error("first terminal message must be open");
28401
+ if (!request.session || typeof request.session !== "object") throw new Error("session is required");
28402
+ const session = request.session;
28403
+ if (!session.scope || typeof session.scope !== "object") throw new Error("session scope is required");
28404
+ const scope = session.scope;
28405
+ return {
28406
+ type: "open",
28407
+ session: {
28408
+ id: requiredIdentifier(session.id, "session.id"),
28409
+ scope: {
28410
+ owner: requiredIdentifier(scope.owner, "scope.owner", 100),
28411
+ repo: requiredIdentifier(scope.repo, "scope.repo", 100),
28412
+ conversationId: requiredIdentifier(scope.conversationId, "scope.conversationId")
28413
+ }
28414
+ },
28415
+ cwd: requiredIdentifier(request.cwd, "cwd", 1e3),
28416
+ afterRevision: revision(request.afterRevision),
28417
+ cols: terminalSize(request.cols, "cols"),
28418
+ rows: terminalSize(request.rows, "rows")
28419
+ };
28420
+ }
28421
+ function parseBrainTerminalStatusRequest(value) {
28422
+ if (!value || typeof value !== "object") throw new Error("status request must be an object");
28423
+ const request = value;
28424
+ if (request.type !== "status") throw new Error("terminal request must be status");
28425
+ return { type: "status", sessionId: requiredIdentifier(request.sessionId, "sessionId") };
28426
+ }
28427
+ function parseBrainTerminalCommand(value) {
28428
+ if (!value || typeof value !== "object") throw new Error("terminal command must be an object");
28429
+ const command = value;
28430
+ const sessionId = requiredIdentifier(command.sessionId, "sessionId");
28431
+ switch (command.type) {
28432
+ case "attach":
28433
+ return { type: "attach", sessionId, afterRevision: revision(command.afterRevision) };
28434
+ case "input": {
28435
+ const inputId = requiredIdentifier(command.inputId, "inputId");
28436
+ if (typeof command.data !== "string" || command.data.length === 0) {
28437
+ throw new Error("data must be a non-empty string");
28438
+ }
28439
+ return { type: "input", sessionId, inputId, data: command.data };
28440
+ }
28441
+ case "resize":
28442
+ return {
28443
+ type: "resize",
28444
+ sessionId,
28445
+ cols: terminalSize(command.cols, "cols"),
28446
+ rows: terminalSize(command.rows, "rows")
28447
+ };
28448
+ case "detach":
28449
+ return { type: "detach", sessionId };
28450
+ case "restart":
28451
+ return { type: "restart", sessionId };
28452
+ default:
28453
+ throw new Error("unknown terminal command");
28454
+ }
28455
+ }
28456
+ function sessionName(id) {
28457
+ return `kody_${createHash9("sha256").update(id).digest("hex").slice(0, 32)}`;
28458
+ }
28459
+ function stateEvent(session) {
28460
+ return {
28461
+ type: "state",
28462
+ sessionId: session.id,
28463
+ generation: session.generation,
28464
+ state: session.state,
28465
+ processId: session.processId
28466
+ };
28467
+ }
28468
+ function failedEvent(session, code, cause) {
28469
+ return {
28470
+ type: "failed",
28471
+ sessionId: session.id,
28472
+ generation: session.generation,
28473
+ code,
28474
+ message: cause instanceof Error ? cause.message : String(cause)
28475
+ };
28476
+ }
28477
+ var BrainTerminalSessionAgent = class {
28478
+ constructor(dependencies) {
28479
+ this.dependencies = dependencies;
28480
+ }
28481
+ dependencies;
28482
+ session = null;
28483
+ now() {
28484
+ return (this.dependencies.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
28485
+ }
28486
+ requireSession() {
28487
+ if (!this.session) throw new Error("terminal session is not open");
28488
+ return this.session;
28489
+ }
28490
+ async persist(session) {
28491
+ this.session = session;
28492
+ await this.dependencies.store.write(session);
28493
+ }
28494
+ async open(rawRequest) {
28495
+ const request = parseBrainTerminalOpenRequest(rawRequest);
28496
+ let session = await this.dependencies.store.read(request.session.id);
28497
+ if (session && (session.scope.owner !== request.session.scope.owner || session.scope.repo !== request.session.scope.repo || session.scope.conversationId !== request.session.scope.conversationId)) {
28498
+ throw new Error("terminal session scope does not match stored identity");
28499
+ }
28500
+ if (!session) {
28501
+ session = {
28502
+ version: 1,
28503
+ id: request.session.id,
28504
+ scope: request.session.scope,
28505
+ sessionName: sessionName(request.session.id),
28506
+ cwd: request.cwd,
28507
+ generation: 1,
28508
+ state: "starting",
28509
+ revision: 0,
28510
+ output: "",
28511
+ processId: null,
28512
+ cols: request.cols,
28513
+ rows: request.rows,
28514
+ updatedAt: this.now()
28515
+ };
28516
+ await this.persist(session);
28517
+ let started;
28518
+ try {
28519
+ started = await this.dependencies.runtime.start(
28520
+ session.sessionName,
28521
+ session.cwd,
28522
+ session.cols,
28523
+ session.rows
28524
+ );
28525
+ } catch (cause) {
28526
+ session = { ...session, state: "failed", updatedAt: this.now() };
28527
+ await this.persist(session);
28528
+ return [failedEvent(session, "runtime_start_failed", cause)];
28529
+ }
28530
+ session = { ...session, state: "ready", processId: started.processId, updatedAt: this.now() };
28531
+ await this.persist(session);
28532
+ } else {
28533
+ const runtime = await this.dependencies.runtime.inspect(session.sessionName);
28534
+ session = {
28535
+ ...session,
28536
+ state: runtime.alive ? "ready" : session.state === "failed" ? "failed" : "exited",
28537
+ processId: runtime.processId,
28538
+ cols: request.cols,
28539
+ rows: request.rows,
28540
+ updatedAt: this.now()
28541
+ };
28542
+ if (runtime.alive) {
28543
+ await this.dependencies.runtime.resize(session.sessionName, request.cols, request.rows);
28544
+ }
28545
+ await this.persist(session);
28546
+ }
28547
+ const events = [stateEvent(session)];
28548
+ if (session.output && (request.afterRevision === void 0 || request.afterRevision < session.revision)) {
28549
+ events.push({
28550
+ type: "output",
28551
+ sessionId: session.id,
28552
+ generation: session.generation,
28553
+ revision: session.revision,
28554
+ data: `\x1B[2J\x1B[H${session.output}`
28555
+ });
28556
+ }
28557
+ return events;
28558
+ }
28559
+ async inspectStored(sessionId) {
28560
+ const id = requiredIdentifier(sessionId, "sessionId");
28561
+ const session = await this.dependencies.store.read(id);
28562
+ if (!session) return null;
28563
+ const runtime = await this.dependencies.runtime.inspect(session.sessionName);
28564
+ const next = {
28565
+ ...session,
28566
+ state: runtime.alive ? session.state : "exited",
28567
+ processId: runtime.processId,
28568
+ updatedAt: this.now()
28569
+ };
28570
+ await this.persist(next);
28571
+ return {
28572
+ id: next.id,
28573
+ generation: next.generation,
28574
+ state: next.state,
28575
+ revision: next.revision,
28576
+ processId: next.processId
28577
+ };
28578
+ }
28579
+ async status() {
28580
+ const session = this.requireSession();
28581
+ const runtime = await this.dependencies.runtime.inspect(session.sessionName);
28582
+ const nextState = runtime.alive ? session.state : session.state === "failed" ? "failed" : "exited";
28583
+ if (nextState !== session.state || runtime.processId !== session.processId) {
28584
+ await this.persist({
28585
+ ...session,
28586
+ state: nextState,
28587
+ processId: runtime.processId,
28588
+ updatedAt: this.now()
28589
+ });
28590
+ }
28591
+ const current = this.requireSession();
28592
+ return {
28593
+ id: current.id,
28594
+ generation: current.generation,
28595
+ state: current.state,
28596
+ revision: current.revision,
28597
+ processId: current.processId
28598
+ };
28599
+ }
28600
+ async captureOutput() {
28601
+ const session = this.requireSession();
28602
+ if (session.state !== "ready" && session.state !== "detached") return null;
28603
+ const output = (await this.dependencies.runtime.capture(session.sessionName)).slice(-MAX_CAPTURE_CHARS);
28604
+ if (output === session.output) return null;
28605
+ const next = {
28606
+ ...session,
28607
+ output,
28608
+ revision: session.revision + 1,
28609
+ updatedAt: this.now()
28610
+ };
28611
+ await this.persist(next);
28612
+ return {
28613
+ type: "output",
28614
+ sessionId: next.id,
28615
+ generation: next.generation,
28616
+ revision: next.revision,
28617
+ data: `\x1B[2J\x1B[H${output}`
28618
+ };
28619
+ }
28620
+ async detach() {
28621
+ const session = this.requireSession();
28622
+ const next = { ...session, state: "detached", updatedAt: this.now() };
28623
+ await this.persist(next);
28624
+ return stateEvent(next);
28625
+ }
28626
+ async command(rawCommand) {
28627
+ const command = parseBrainTerminalCommand(rawCommand);
28628
+ const session = this.requireSession();
28629
+ if (command.sessionId !== session.id) throw new Error("terminal command session identity mismatch");
28630
+ switch (command.type) {
28631
+ case "attach":
28632
+ return stateEvent(session);
28633
+ case "input":
28634
+ if (session.state !== "ready") throw new Error(`input is not allowed while terminal is ${session.state}`);
28635
+ await this.dependencies.runtime.input(session.sessionName, command.data);
28636
+ return {
28637
+ type: "input-accepted",
28638
+ sessionId: session.id,
28639
+ generation: session.generation,
28640
+ inputId: command.inputId
28641
+ };
28642
+ case "resize":
28643
+ if (session.state !== "ready") throw new Error(`resize is not allowed while terminal is ${session.state}`);
28644
+ await this.dependencies.runtime.resize(session.sessionName, command.cols, command.rows);
28645
+ await this.persist({ ...session, cols: command.cols, rows: command.rows, updatedAt: this.now() });
28646
+ return null;
28647
+ case "detach":
28648
+ return this.detach();
28649
+ case "restart": {
28650
+ if (session.state === "starting") throw new Error("restart is not allowed while terminal is starting");
28651
+ await this.dependencies.runtime.stop(session.sessionName);
28652
+ const starting = {
28653
+ ...session,
28654
+ generation: session.generation + 1,
28655
+ state: "starting",
28656
+ revision: 0,
28657
+ output: "",
28658
+ processId: null,
28659
+ updatedAt: this.now()
28660
+ };
28661
+ await this.persist(starting);
28662
+ let started;
28663
+ try {
28664
+ started = await this.dependencies.runtime.start(
28665
+ starting.sessionName,
28666
+ starting.cwd,
28667
+ starting.cols,
28668
+ starting.rows
28669
+ );
28670
+ } catch (cause) {
28671
+ const failed = { ...starting, state: "failed", updatedAt: this.now() };
28672
+ await this.persist(failed);
28673
+ return failedEvent(failed, "runtime_start_failed", cause);
28674
+ }
28675
+ const ready = { ...starting, state: "ready", processId: started.processId, updatedAt: this.now() };
28676
+ await this.persist(ready);
28677
+ return stateEvent(ready);
28678
+ }
28679
+ }
28680
+ }
28681
+ };
28682
+
28683
+ // src/terminal/brain-terminal-adapters.ts
28684
+ import { createHash as createHash10, randomBytes as randomBytes2 } from "crypto";
28685
+ import { mkdir, readFile, rename, writeFile as writeFile2 } from "fs/promises";
28686
+ import * as path56 from "path";
28687
+ import { spawn as spawn9 } from "child_process";
28688
+ function runTerminalCommand(command, args, input) {
28689
+ return new Promise((resolve23, reject) => {
28690
+ const child = spawn9(command, args, { stdio: ["pipe", "pipe", "pipe"] });
28691
+ const stdout = [];
28692
+ const stderr = [];
28693
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
28694
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
28695
+ child.on("error", reject);
28696
+ child.on("close", (code) => {
28697
+ resolve23({
28698
+ code: code ?? 1,
28699
+ stdout: Buffer.concat(stdout).toString("utf8"),
28700
+ stderr: Buffer.concat(stderr).toString("utf8")
28701
+ });
28702
+ });
28703
+ if (input !== void 0) child.stdin.end(input);
28704
+ else child.stdin.end();
28705
+ });
28706
+ }
28707
+ function storeKey(id) {
28708
+ return createHash10("sha256").update(id).digest("hex");
28709
+ }
28710
+ function isStoredSession(value) {
28711
+ if (!value || typeof value !== "object") return false;
28712
+ const session = value;
28713
+ return session.version === 1 && typeof session.id === "string" && typeof session.sessionName === "string" && typeof session.cwd === "string" && Number.isInteger(session.generation) && Number.isInteger(session.revision) && typeof session.output === "string" && typeof session.scope?.owner === "string" && typeof session.scope?.repo === "string" && typeof session.scope?.conversationId === "string";
28714
+ }
28715
+ var FileBrainTerminalMetadataStore = class {
28716
+ constructor(root) {
28717
+ this.root = root;
28718
+ }
28719
+ root;
28720
+ file(id) {
28721
+ return path56.join(this.root, `${storeKey(id)}.json`);
28722
+ }
28723
+ async read(id) {
28724
+ try {
28725
+ const parsed = JSON.parse(await readFile(this.file(id), "utf8"));
28726
+ if (!isStoredSession(parsed) || parsed.id !== id) {
28727
+ throw new Error("stored terminal session is invalid");
28728
+ }
28729
+ return parsed;
28730
+ } catch (error) {
28731
+ if (error.code === "ENOENT") return null;
28732
+ throw error;
28733
+ }
28734
+ }
28735
+ async write(session) {
28736
+ await mkdir(this.root, { recursive: true, mode: 448 });
28737
+ const target = this.file(session.id);
28738
+ const temporary = `${target}.${process.pid}.${randomBytes2(6).toString("hex")}.tmp`;
28739
+ await writeFile2(temporary, `${JSON.stringify(session)}
28740
+ `, { mode: 384 });
28741
+ await rename(temporary, target);
28742
+ }
28743
+ };
28744
+ function commandError(action, result) {
28745
+ const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`;
28746
+ return new Error(`tmux ${action} failed: ${detail.slice(0, 500)}`);
28747
+ }
28748
+ var TmuxBrainTerminalRuntime = class {
28749
+ constructor(run = runTerminalCommand) {
28750
+ this.run = run;
28751
+ }
28752
+ run;
28753
+ async tmux(action, args, input) {
28754
+ const result = await this.run("tmux", args, input);
28755
+ if (result.code !== 0) throw commandError(action, result);
28756
+ return result;
28757
+ }
28758
+ async start(sessionName2, cwd, cols, rows) {
28759
+ await this.tmux("start", [
28760
+ "new-session",
28761
+ "-d",
28762
+ "-s",
28763
+ sessionName2,
28764
+ "-x",
28765
+ String(cols),
28766
+ "-y",
28767
+ String(rows),
28768
+ "-c",
28769
+ cwd,
28770
+ "/bin/bash",
28771
+ "-l"
28772
+ ]);
28773
+ await this.tmux("configure", ["set-option", "-t", sessionName2, "status", "off"]);
28774
+ await this.tmux("configure", ["set-option", "-t", sessionName2, "history-limit", "50000"]);
28775
+ await this.tmux("configure", ["set-option", "-w", "-t", sessionName2, "remain-on-exit", "on"]);
28776
+ const inspected = await this.inspect(sessionName2);
28777
+ if (!inspected.alive || inspected.processId === null) throw new Error("tmux terminal did not start");
28778
+ return { processId: inspected.processId };
28779
+ }
28780
+ async inspect(sessionName2) {
28781
+ const result = await this.run("tmux", [
28782
+ "list-panes",
28783
+ "-t",
28784
+ sessionName2,
28785
+ "-F",
28786
+ "#{pane_dead}:#{pane_pid}"
28787
+ ]);
28788
+ if (result.code !== 0) return { alive: false, processId: null };
28789
+ const [dead, pid] = result.stdout.trim().split(":");
28790
+ const processId = Number(pid);
28791
+ return {
28792
+ alive: dead === "0" && Number.isInteger(processId) && processId > 0,
28793
+ processId: Number.isInteger(processId) && processId > 0 ? processId : null
28794
+ };
28795
+ }
28796
+ async capture(sessionName2) {
28797
+ const alternate = await this.run("tmux", [
28798
+ "display-message",
28799
+ "-p",
28800
+ "-t",
28801
+ sessionName2,
28802
+ "#{alternate_on}"
28803
+ ]);
28804
+ if (alternate.code !== 0) throw commandError("inspect screen", alternate);
28805
+ const args = alternate.stdout.trim() === "1" ? ["capture-pane", "-p", "-e", "-t", sessionName2] : ["capture-pane", "-p", "-e", "-J", "-S", "-50000", "-t", sessionName2];
28806
+ return (await this.tmux("capture", args)).stdout;
28807
+ }
28808
+ async input(sessionName2, data) {
28809
+ const bufferName = `kody_${randomBytes2(8).toString("hex")}`;
28810
+ await this.tmux("load input", ["load-buffer", "-b", bufferName, "-"], data);
28811
+ await this.tmux("paste input", ["paste-buffer", "-d", "-b", bufferName, "-t", sessionName2]);
28812
+ }
28813
+ async resize(sessionName2, cols, rows) {
28814
+ await this.tmux("resize", [
28815
+ "resize-window",
28816
+ "-t",
28817
+ sessionName2,
28818
+ "-x",
28819
+ String(cols),
28820
+ "-y",
28821
+ String(rows)
28822
+ ]);
28823
+ }
28824
+ async stop(sessionName2) {
28825
+ const result = await this.run("tmux", ["kill-session", "-t", sessionName2]);
28826
+ if (result.code !== 0 && !/can't find session|no server running/i.test(result.stderr)) {
28827
+ throw commandError("stop", result);
28828
+ }
28829
+ }
28830
+ };
28831
+
28832
+ // src/servers/brain-terminal-agent.ts
28833
+ var DEFAULT_POLL_INTERVAL_MS = 150;
28834
+ function writeEvent(output, event) {
28835
+ output.write(`${JSON.stringify(event)}
28836
+ `);
28837
+ }
28838
+ async function brainTerminalAgent(options) {
28839
+ const input = options.input ?? process.stdin;
28840
+ const output = options.output ?? process.stdout;
28841
+ const error = options.error ?? process.stderr;
28842
+ const reposRoot = process.env.BRAIN_REPOS_ROOT?.trim() || path57.join(path57.dirname(path57.resolve(options.cwd)), "repos");
28843
+ const stateRoot = process.env.BRAIN_TERMINAL_STATE_ROOT?.trim() || path57.join(path57.dirname(reposRoot), ".kody", "terminal-sessions");
28844
+ const agent = new BrainTerminalSessionAgent({
28845
+ store: new FileBrainTerminalMetadataStore(stateRoot),
28846
+ runtime: new TmuxBrainTerminalRuntime()
28847
+ });
28848
+ const lines = createInterface2({ input, crlfDelay: Infinity });
28849
+ let opened = false;
28850
+ let poll = null;
28851
+ let pollRunning = false;
28852
+ const stopPoll = () => {
28853
+ if (poll) clearInterval(poll);
28854
+ poll = null;
28855
+ };
28856
+ const capture = async () => {
28857
+ if (pollRunning) return;
28858
+ pollRunning = true;
28859
+ try {
28860
+ const event = await agent.captureOutput();
28861
+ if (event) writeEvent(output, event);
28862
+ const status = await agent.status();
28863
+ if (status.state === "exited") {
28864
+ writeEvent(output, {
28865
+ type: "exited",
28866
+ sessionId: status.id,
28867
+ generation: status.generation
28868
+ });
28869
+ stopPoll();
28870
+ }
28871
+ } catch (cause) {
28872
+ error.write(`[brain-terminal-agent] capture failed: ${cause instanceof Error ? cause.message : String(cause)}
28873
+ `);
28874
+ } finally {
28875
+ pollRunning = false;
28876
+ }
28877
+ };
28878
+ try {
28879
+ for await (const line of lines) {
28880
+ if (!line.trim()) continue;
28881
+ const value = JSON.parse(line);
28882
+ if (!opened) {
28883
+ if (value && typeof value === "object" && value.type === "status") {
28884
+ const request = parseBrainTerminalStatusRequest(value);
28885
+ const status = await agent.inspectStored(request.sessionId);
28886
+ if (status) {
28887
+ writeEvent(output, {
28888
+ type: "state",
28889
+ sessionId: status.id,
28890
+ generation: status.generation,
28891
+ state: status.state,
28892
+ processId: status.processId
28893
+ });
28894
+ } else {
28895
+ writeEvent(output, {
28896
+ type: "failed",
28897
+ sessionId: request.sessionId,
28898
+ generation: 1,
28899
+ code: "session_not_found",
28900
+ message: "Terminal session not found"
28901
+ });
28902
+ }
28903
+ return 0;
28904
+ }
28905
+ const requested = parseBrainTerminalOpenRequest(value);
28906
+ const repo = `${requested.session.scope.owner}/${requested.session.scope.repo}`;
28907
+ const workspaceCwd = await ensureRepoCwd({
28908
+ baseCwd: options.cwd,
28909
+ reposRoot,
28910
+ repo,
28911
+ repoToken: process.env.KODY_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.GH_PAT,
28912
+ cloneRepo: defaultCloneRepo
28913
+ });
28914
+ const events = await agent.open({ ...requested, cwd: workspaceCwd });
28915
+ for (const event2 of events) writeEvent(output, event2);
28916
+ opened = true;
28917
+ poll = setInterval(() => void capture(), options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
28918
+ poll.unref?.();
28919
+ continue;
28920
+ }
28921
+ const command = parseBrainTerminalCommand(value);
28922
+ const event = await agent.command(command);
28923
+ if (event) writeEvent(output, event);
28924
+ if (command.type === "detach") stopPoll();
28925
+ }
28926
+ if (opened) await agent.detach();
28927
+ return 0;
28928
+ } catch (cause) {
28929
+ stopPoll();
28930
+ error.write(`[brain-terminal-agent] ${cause instanceof Error ? cause.message : String(cause)}
28931
+ `);
28932
+ return 1;
28933
+ } finally {
28934
+ stopPoll();
28935
+ lines.close();
28936
+ }
28937
+ }
28938
+
28305
28939
  // src/servers/pool-serve.ts
28306
28940
  import { createServer as createServer5 } from "http";
28307
28941
 
@@ -28373,8 +29007,8 @@ var FlyClient = class {
28373
29007
  get fetch() {
28374
29008
  return this.opts.fetchImpl ?? fetch;
28375
29009
  }
28376
- async call(path55, init = {}) {
28377
- const res = await this.fetch(`${FLY_API_BASE}${path55}`, {
29010
+ async call(path58, init = {}) {
29011
+ const res = await this.fetch(`${FLY_API_BASE}${path58}`, {
28378
29012
  method: init.method ?? "GET",
28379
29013
  headers: {
28380
29014
  Authorization: `Bearer ${this.opts.token}`,
@@ -28385,7 +29019,7 @@ var FlyClient = class {
28385
29019
  if (res.status === 404 && init.allow404) return null;
28386
29020
  if (!res.ok) {
28387
29021
  const text2 = await res.text().catch(() => "");
28388
- throw new Error(`Fly API ${res.status} on ${path55}: ${text2.slice(0, 200) || res.statusText}`);
29022
+ throw new Error(`Fly API ${res.status} on ${path58}: ${text2.slice(0, 200) || res.statusText}`);
28389
29023
  }
28390
29024
  if (res.status === 204) return null;
28391
29025
  const raw = await res.text();
@@ -28898,14 +29532,14 @@ function sendJson2(res, status, body) {
28898
29532
  res.end(JSON.stringify(body));
28899
29533
  }
28900
29534
  function readJsonBody2(req) {
28901
- return new Promise((resolve21, reject) => {
29535
+ return new Promise((resolve23, reject) => {
28902
29536
  const chunks = [];
28903
29537
  req.on("data", (c) => chunks.push(c));
28904
29538
  req.on("end", () => {
28905
29539
  const raw = Buffer.concat(chunks).toString("utf-8");
28906
- if (!raw.trim()) return resolve21({});
29540
+ if (!raw.trim()) return resolve23({});
28907
29541
  try {
28908
- resolve21(JSON.parse(raw));
29542
+ resolve23(JSON.parse(raw));
28909
29543
  } catch (err) {
28910
29544
  reject(err instanceof Error ? err : new Error(String(err)));
28911
29545
  }
@@ -29059,10 +29693,10 @@ async function poolServe() {
29059
29693
  }
29060
29694
  });
29061
29695
  const apiHost = process.env.POOL_API_HOST ?? "::";
29062
- await new Promise((resolve21) => {
29696
+ await new Promise((resolve23) => {
29063
29697
  server.listen(apiPort, apiHost, () => {
29064
29698
  log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
29065
- resolve21();
29699
+ resolve23();
29066
29700
  });
29067
29701
  });
29068
29702
  if (loopTickEnabled) void runLoopTick();
@@ -29080,8 +29714,8 @@ async function poolServe() {
29080
29714
  }
29081
29715
 
29082
29716
  // src/servers/runner-serve.ts
29083
- import { spawn as spawn9 } from "child_process";
29084
- import * as fs56 from "fs";
29717
+ import { spawn as spawn10 } from "child_process";
29718
+ import * as fs57 from "fs";
29085
29719
  import { createServer as createServer6 } from "http";
29086
29720
  var DEFAULT_PORT2 = 8080;
29087
29721
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -29102,17 +29736,17 @@ function authOk2(req, expected) {
29102
29736
  return false;
29103
29737
  }
29104
29738
  function readJsonBody3(req) {
29105
- return new Promise((resolve21, reject) => {
29739
+ return new Promise((resolve23, reject) => {
29106
29740
  const chunks = [];
29107
29741
  req.on("data", (c) => chunks.push(c));
29108
29742
  req.on("end", () => {
29109
29743
  const raw = Buffer.concat(chunks).toString("utf-8");
29110
29744
  if (!raw.trim()) {
29111
- resolve21({});
29745
+ resolve23({});
29112
29746
  return;
29113
29747
  }
29114
29748
  try {
29115
- resolve21(JSON.parse(raw));
29749
+ resolve23(JSON.parse(raw));
29116
29750
  } catch (err) {
29117
29751
  reject(err instanceof Error ? err : new Error(String(err)));
29118
29752
  }
@@ -29157,8 +29791,8 @@ async function defaultRunJob(job) {
29157
29791
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
29158
29792
  const branch = job.ref ?? "main";
29159
29793
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
29160
- fs56.rmSync(workdir, { recursive: true, force: true });
29161
- fs56.mkdirSync(workdir, { recursive: true });
29794
+ fs57.rmSync(workdir, { recursive: true, force: true });
29795
+ fs57.mkdirSync(workdir, { recursive: true });
29162
29796
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
29163
29797
  const target = job.runRequest.target;
29164
29798
  const interactive = target.type === "chat";
@@ -29187,13 +29821,13 @@ async function defaultRunJob(job) {
29187
29821
  ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
29188
29822
  ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
29189
29823
  };
29190
- const run = (cmd, args, cwd) => new Promise((resolve21) => {
29191
- const child = spawn9(cmd, args, { stdio: "inherit", env: childEnv, cwd });
29192
- child.on("exit", (code) => resolve21(code ?? 0));
29824
+ const run = (cmd, args, cwd) => new Promise((resolve23) => {
29825
+ const child = spawn10(cmd, args, { stdio: "inherit", env: childEnv, cwd });
29826
+ child.on("exit", (code) => resolve23(code ?? 0));
29193
29827
  child.on("error", (err) => {
29194
29828
  process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
29195
29829
  `);
29196
- resolve21(1);
29830
+ resolve23(1);
29197
29831
  });
29198
29832
  });
29199
29833
  process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
@@ -29269,11 +29903,11 @@ async function runnerServe() {
29269
29903
  const port = Number(process.env.PORT ?? DEFAULT_PORT2);
29270
29904
  const server = buildServer2({ apiKey });
29271
29905
  const host = process.env.RUNNER_HOST ?? "::";
29272
- await new Promise((resolve21) => {
29906
+ await new Promise((resolve23) => {
29273
29907
  server.listen(port, host, () => {
29274
29908
  process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
29275
29909
  `);
29276
- resolve21();
29910
+ resolve23();
29277
29911
  });
29278
29912
  });
29279
29913
  const shutdown = (signal) => {
@@ -29291,7 +29925,7 @@ async function runnerServe() {
29291
29925
  // src/servers/serve.ts
29292
29926
  init_config();
29293
29927
  init_litellm();
29294
- import { spawn as spawn10 } from "child_process";
29928
+ import { spawn as spawn11 } from "child_process";
29295
29929
  function parseTarget(positional) {
29296
29930
  if (!Array.isArray(positional) || positional.length === 0) return "none";
29297
29931
  const first = String(positional[0]).toLowerCase();
@@ -29341,15 +29975,15 @@ async function serve(opts) {
29341
29975
  if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
29342
29976
  `);
29343
29977
  const args = ["--dangerously-skip-permissions", "--model", model.model];
29344
- const child = spawn10("claude", args, { stdio: "inherit", env: editorEnv, cwd: opts.cwd });
29345
- const exitCode = await new Promise((resolve21) => {
29346
- child.on("exit", (code) => resolve21(code ?? 0));
29978
+ const child = spawn11("claude", args, { stdio: "inherit", env: editorEnv, cwd: opts.cwd });
29979
+ const exitCode = await new Promise((resolve23) => {
29980
+ child.on("exit", (code) => resolve23(code ?? 0));
29347
29981
  child.on("error", (err) => {
29348
29982
  process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
29349
29983
  `);
29350
29984
  process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
29351
29985
  `);
29352
- resolve21(1);
29986
+ resolve23(1);
29353
29987
  });
29354
29988
  });
29355
29989
  killProxy();
@@ -29361,7 +29995,7 @@ async function serve(opts) {
29361
29995
  if (usesProxy) process.stdout.write(` ANTHROPIC_BASE_URL=${url}
29362
29996
  `);
29363
29997
  try {
29364
- const code = spawn10("code", [opts.cwd], { stdio: "inherit", env: editorEnv, detached: true });
29998
+ const code = spawn11("code", [opts.cwd], { stdio: "inherit", env: editorEnv, detached: true });
29365
29999
  code.on("error", (err) => {
29366
30000
  process.stderr.write(`[kody serve] failed to launch VS Code: ${err.message}
29367
30001
  `);
@@ -29709,7 +30343,15 @@ function parseArgs(argv) {
29709
30343
  if (result.cliArgs.quiet === true) result.quiet = true;
29710
30344
  return result;
29711
30345
  }
29712
- const SERVER_VERBS = /* @__PURE__ */ new Set(["serve", "pool-serve", "runner-serve", "brain-serve", "brain-proxy", "mcp-http-server"]);
30346
+ const SERVER_VERBS = /* @__PURE__ */ new Set([
30347
+ "serve",
30348
+ "pool-serve",
30349
+ "runner-serve",
30350
+ "brain-serve",
30351
+ "brain-terminal-agent",
30352
+ "brain-proxy",
30353
+ "mcp-http-server"
30354
+ ]);
29713
30355
  if (SERVER_VERBS.has(cmd)) {
29714
30356
  result.command = "server";
29715
30357
  result.serverName = cmd;
@@ -29813,6 +30455,8 @@ ${HELP_TEXT}`);
29813
30455
  return await runnerServe();
29814
30456
  case "brain-serve":
29815
30457
  return await brainServe({ cwd: cwd2 });
30458
+ case "brain-terminal-agent":
30459
+ return await brainTerminalAgent({ cwd: cwd2 });
29816
30460
  case "brain-proxy":
29817
30461
  return await brainProxy();
29818
30462
  case "mcp-http-server":