@kody-ade/kody-engine 0.4.464 → 0.4.465

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.464",
18
+ version: "0.4.465",
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",
@@ -1688,6 +1688,9 @@ function readCapabilityFolder(root, slug) {
1688
1688
  try {
1689
1689
  const rawBody = fs4.readFileSync(bodyPath, "utf-8");
1690
1690
  const contract = fs4.existsSync(contractPath) ? parseCapabilityContract(fs4.readFileSync(contractPath, "utf-8")) : void 0;
1691
+ if (contract?.execution === "script" && !isRegularFile(path5.join(dir, "tools", "run.sh"))) {
1692
+ throw new Error('script-backed Capability requires a regular "tools/run.sh" file');
1693
+ }
1691
1694
  const { title, body } = parseCapabilityBody(rawBody, slug);
1692
1695
  return {
1693
1696
  slug,
@@ -1701,12 +1704,17 @@ function readCapabilityFolder(root, slug) {
1701
1704
  config: {
1702
1705
  action: slug,
1703
1706
  describe: title,
1707
+ ...contract?.execution ? { execution: contract.execution } : {},
1704
1708
  ...contract ? {
1705
1709
  inputSchema: contract.input,
1706
1710
  outputSchema: contract.output
1707
1711
  } : {}
1708
1712
  },
1709
- rawProfile: contract ? { input: contract.input, output: contract.output } : {},
1713
+ rawProfile: contract ? {
1714
+ ...contract.execution ? { execution: contract.execution } : {},
1715
+ input: contract.input,
1716
+ output: contract.output
1717
+ } : {},
1710
1718
  ...contract ? { contract } : {}
1711
1719
  };
1712
1720
  } catch {
@@ -1718,11 +1726,28 @@ function parseCapabilityContract(raw) {
1718
1726
  if (!isPlainObject(parsed) || !isPlainObject(parsed.input) || !isPlainObject(parsed.output)) {
1719
1727
  throw new Error("contract.json must contain input and output JSON schemas");
1720
1728
  }
1721
- const unsupported = Object.keys(parsed).filter((key) => key !== "input" && key !== "output");
1729
+ if (parsed.execution !== void 0 && parsed.execution !== "agent" && parsed.execution !== "script") {
1730
+ throw new Error('contract.json execution must be "agent" or "script"');
1731
+ }
1732
+ const unsupported = Object.keys(parsed).filter(
1733
+ (key) => key !== "execution" && key !== "input" && key !== "output"
1734
+ );
1722
1735
  if (unsupported.length > 0) {
1723
1736
  throw new Error(`contract.json contains unsupported fields: ${unsupported.join(", ")}`);
1724
1737
  }
1725
- return { input: parsed.input, output: parsed.output };
1738
+ return {
1739
+ ...parsed.execution ? { execution: parsed.execution } : {},
1740
+ input: parsed.input,
1741
+ output: parsed.output
1742
+ };
1743
+ }
1744
+ function isRegularFile(filePath) {
1745
+ try {
1746
+ const stat = fs4.lstatSync(filePath);
1747
+ return stat.isFile() && !stat.isSymbolicLink();
1748
+ } catch {
1749
+ return false;
1750
+ }
1726
1751
  }
1727
1752
  function schemaPropertyPaths(schema, prefix) {
1728
1753
  const properties = isPlainObject(schema.properties) ? schema.properties : {};
@@ -16249,6 +16274,10 @@ var init_loadSimpleCapability = __esm({
16249
16274
  }
16250
16275
  ctx.data.jobCapability = slug;
16251
16276
  ctx.data.capabilityInput = input;
16277
+ ctx.data.capabilityExecution = capability.contract?.execution ?? "agent";
16278
+ if (capability.contract?.execution === "script") {
16279
+ ctx.data.capabilityScriptPath = path40.join(capability.dir, "tools", "run.sh");
16280
+ }
16252
16281
  if (capability.config.outputSchema) {
16253
16282
  ctx.data.capabilityOutputSchema = capability.config.outputSchema;
16254
16283
  }
@@ -17274,7 +17303,7 @@ var init_parseSimpleCapabilityOutput = __esm({
17274
17303
  "use strict";
17275
17304
  init_capability_contract_validation();
17276
17305
  parseSimpleCapabilityOutput = async (ctx, _profile, agentResult) => {
17277
- const output = parseOutput(agentResult?.finalText);
17306
+ const output = Object.hasOwn(ctx.data, "capabilityScriptOutput") ? ctx.data.capabilityScriptOutput : parseOutput(agentResult?.finalText);
17278
17307
  if (output === void 0) {
17279
17308
  const reason2 = agentResult?.outcomeKind === "out_of_turns" ? "Capability execution limit reached" : "Capability execution ended before returning a result";
17280
17309
  const blocked = {
@@ -19768,6 +19797,74 @@ var init_runTickScript = __esm({
19768
19797
  }
19769
19798
  });
19770
19799
 
19800
+ // src/scripts/runSimpleCapabilityScript.ts
19801
+ import { spawnSync as spawnSync3 } from "child_process";
19802
+ import * as fs48 from "fs";
19803
+ function isRegularFile2(filePath) {
19804
+ try {
19805
+ const stat = fs48.lstatSync(filePath);
19806
+ return stat.isFile() && !stat.isSymbolicLink();
19807
+ } catch {
19808
+ return false;
19809
+ }
19810
+ }
19811
+ function isStringRecord(value) {
19812
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
19813
+ }
19814
+ var SCRIPT_TIMEOUT_MS, SCRIPT_MAX_OUTPUT_BYTES, runSimpleCapabilityScript;
19815
+ var init_runSimpleCapabilityScript = __esm({
19816
+ "src/scripts/runSimpleCapabilityScript.ts"() {
19817
+ "use strict";
19818
+ init_tickShellRunner();
19819
+ SCRIPT_TIMEOUT_MS = 5 * 60 * 1e3;
19820
+ SCRIPT_MAX_OUTPUT_BYTES = 1024 * 1024;
19821
+ runSimpleCapabilityScript = async (ctx) => {
19822
+ ctx.skipAgent = true;
19823
+ const scriptPath = typeof ctx.data.capabilityScriptPath === "string" ? ctx.data.capabilityScriptPath : "";
19824
+ if (!scriptPath || !isRegularFile2(scriptPath)) {
19825
+ ctx.output.exitCode = 99;
19826
+ ctx.output.reason = 'Script-backed Capability requires a regular "tools/run.sh" entrypoint';
19827
+ return;
19828
+ }
19829
+ const capabilityEnvironment2 = isStringRecord(ctx.data.capabilityEnvironment) ? ctx.data.capabilityEnvironment : {};
19830
+ const result = spawnSync3("bash", [scriptPath], {
19831
+ cwd: ctx.cwd,
19832
+ env: {
19833
+ ...buildTickChildEnv(process.env, false),
19834
+ ...capabilityEnvironment2
19835
+ },
19836
+ stdio: ["ignore", "pipe", "pipe"],
19837
+ encoding: "utf-8",
19838
+ timeout: SCRIPT_TIMEOUT_MS,
19839
+ maxBuffer: SCRIPT_MAX_OUTPUT_BYTES
19840
+ });
19841
+ if (result.stderr) process.stderr.write(result.stderr);
19842
+ if (result.error) {
19843
+ const timedOut = result.error.code === "ETIMEDOUT";
19844
+ ctx.output.exitCode = timedOut ? 124 : 99;
19845
+ ctx.output.reason = timedOut ? "Capability script timed out after 5 minutes" : `Capability script failed to start: ${result.error.message}`;
19846
+ return;
19847
+ }
19848
+ if (result.signal) {
19849
+ ctx.output.exitCode = 124;
19850
+ ctx.output.reason = `Capability script was killed by ${result.signal}`;
19851
+ return;
19852
+ }
19853
+ if (result.status !== 0) {
19854
+ ctx.output.exitCode = result.status ?? 99;
19855
+ ctx.output.reason = `Capability script exited ${result.status ?? 99}`;
19856
+ return;
19857
+ }
19858
+ try {
19859
+ ctx.data.capabilityScriptOutput = JSON.parse(result.stdout ?? "");
19860
+ } catch {
19861
+ ctx.output.exitCode = 64;
19862
+ ctx.output.reason = "Capability script must return exactly one valid JSON value on stdout";
19863
+ }
19864
+ };
19865
+ }
19866
+ });
19867
+
19771
19868
  // src/scripts/saveManagedGoalState.ts
19772
19869
  var saveManagedGoalState;
19773
19870
  var init_saveManagedGoalState = __esm({
@@ -20783,7 +20880,7 @@ var init_warmupMcp = __esm({
20783
20880
  });
20784
20881
 
20785
20882
  // src/scripts/writeAgentRunSummary.ts
20786
- import * as fs48 from "fs";
20883
+ import * as fs49 from "fs";
20787
20884
  var writeAgentRunSummary;
20788
20885
  var init_writeAgentRunSummary = __esm({
20789
20886
  "src/scripts/writeAgentRunSummary.ts"() {
@@ -20809,7 +20906,7 @@ var init_writeAgentRunSummary = __esm({
20809
20906
  if (reason) lines.push(`- **Reason:** ${reason}`);
20810
20907
  lines.push("");
20811
20908
  try {
20812
- fs48.appendFileSync(summaryPath, `${lines.join("\n")}
20909
+ fs49.appendFileSync(summaryPath, `${lines.join("\n")}
20813
20910
  `);
20814
20911
  } catch {
20815
20912
  }
@@ -21012,6 +21109,7 @@ var init_scripts = __esm({
21012
21109
  init_runPreviewBuild();
21013
21110
  init_runScheduledImplementationTick();
21014
21111
  init_runTickScript();
21112
+ init_runSimpleCapabilityScript();
21015
21113
  init_saveManagedGoalState();
21016
21114
  init_saveTaskState();
21017
21115
  init_setCommentTarget();
@@ -21080,6 +21178,7 @@ var init_scripts = __esm({
21080
21178
  runScheduledImplementationTick,
21081
21179
  runScheduledExecutableTick: runScheduledImplementationTick,
21082
21180
  runTickScript,
21181
+ runSimpleCapabilityScript,
21083
21182
  runPreviewBuild,
21084
21183
  advanceManagedGoal,
21085
21184
  loadGoalState,
@@ -21145,7 +21244,7 @@ var init_scripts = __esm({
21145
21244
  });
21146
21245
 
21147
21246
  // src/stateWorkspace.ts
21148
- import * as fs49 from "fs";
21247
+ import * as fs50 from "fs";
21149
21248
  import * as path47 from "path";
21150
21249
  function tenantId(config) {
21151
21250
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
@@ -21154,8 +21253,8 @@ function tenantId(config) {
21154
21253
  }
21155
21254
  function writeRuntimeFile(cwd, relativePath, content) {
21156
21255
  const target = path47.join(cwd, RUNTIME_ROOT, relativePath);
21157
- fs49.mkdirSync(path47.dirname(target), { recursive: true });
21158
- fs49.writeFileSync(target, content, "utf8");
21256
+ fs50.mkdirSync(path47.dirname(target), { recursive: true });
21257
+ fs50.writeFileSync(target, content, "utf8");
21159
21258
  }
21160
21259
  function record(value) {
21161
21260
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -21224,7 +21323,7 @@ async function hydrateStateWorkspace(config, cwd, backendOverride) {
21224
21323
  if (hydratedWorkspaces.has(key)) return;
21225
21324
  const backend = backendOverride ?? createStateBackendFromEnv();
21226
21325
  const root = path47.join(cwd, RUNTIME_ROOT);
21227
- fs49.rmSync(root, { recursive: true, force: true });
21326
+ fs50.rmSync(root, { recursive: true, force: true });
21228
21327
  await Promise.all([
21229
21328
  hydratePrefix(backend, tenant, cwd, "context:"),
21230
21329
  hydratePrefix(backend, tenant, cwd, "memory:"),
@@ -21311,7 +21410,7 @@ var init_tools = __esm({
21311
21410
 
21312
21411
  // src/executor.ts
21313
21412
  import { spawn as spawn8 } from "child_process";
21314
- import * as fs50 from "fs";
21413
+ import * as fs51 from "fs";
21315
21414
  import * as os7 from "os";
21316
21415
  import * as path48 from "path";
21317
21416
  function isMutatingPostflight(scriptName) {
@@ -22029,7 +22128,7 @@ function resolveProfilePath(profileName, cwd = process.cwd()) {
22029
22128
  // fallback
22030
22129
  ];
22031
22130
  for (const c of candidates) {
22032
- if (fs50.existsSync(c)) return c;
22131
+ if (fs51.existsSync(c)) return c;
22033
22132
  }
22034
22133
  return candidates[0];
22035
22134
  }
@@ -22145,7 +22244,7 @@ function resolveShellTimeoutMs(entry) {
22145
22244
  async function runShellEntry(entry, ctx, profile) {
22146
22245
  const shellName = entry.shell;
22147
22246
  const shellPath = path48.join(profile.dir, shellName);
22148
- if (!fs50.existsSync(shellPath)) {
22247
+ if (!fs51.existsSync(shellPath)) {
22149
22248
  ctx.skipAgent = true;
22150
22249
  ctx.output.exitCode = 99;
22151
22250
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -22224,9 +22323,9 @@ async function runShellEntry(entry, ctx, profile) {
22224
22323
  }
22225
22324
  let sideChannelText = "";
22226
22325
  try {
22227
- if (fs50.existsSync(outputFile)) {
22228
- sideChannelText = fs50.readFileSync(outputFile, "utf-8");
22229
- fs50.rmSync(outputFile, { force: true });
22326
+ if (fs51.existsSync(outputFile)) {
22327
+ sideChannelText = fs51.readFileSync(outputFile, "utf-8");
22328
+ fs51.rmSync(outputFile, { force: true });
22230
22329
  }
22231
22330
  } catch {
22232
22331
  }
@@ -24496,7 +24595,7 @@ async function hydrateDefinitionsFromEnv(cwd = process.cwd(), env = process.env)
24496
24595
 
24497
24596
  // src/kody-cli.ts
24498
24597
  import { execFileSync as execFileSync24 } from "child_process";
24499
- import * as fs51 from "fs";
24598
+ import * as fs52 from "fs";
24500
24599
  import * as path49 from "path";
24501
24600
 
24502
24601
  // src/app-auth.ts
@@ -25316,9 +25415,9 @@ async function resolveAuthToken(env = process.env) {
25316
25415
  return void 0;
25317
25416
  }
25318
25417
  function detectPackageManager2(cwd) {
25319
- if (fs51.existsSync(path49.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
25320
- if (fs51.existsSync(path49.join(cwd, "yarn.lock"))) return "yarn";
25321
- if (fs51.existsSync(path49.join(cwd, "bun.lockb"))) return "bun";
25418
+ if (fs52.existsSync(path49.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
25419
+ if (fs52.existsSync(path49.join(cwd, "yarn.lock"))) return "yarn";
25420
+ if (fs52.existsSync(path49.join(cwd, "bun.lockb"))) return "bun";
25322
25421
  return "npm";
25323
25422
  }
25324
25423
  function shouldChainScheduledWatch(match) {
@@ -25422,8 +25521,8 @@ function postFailureTail(issueNumber, cwd, reason) {
25422
25521
  const logPath = lastRunLogPath(cwd);
25423
25522
  let tail = "";
25424
25523
  try {
25425
- if (fs51.existsSync(logPath)) {
25426
- const content = fs51.readFileSync(logPath, "utf-8");
25524
+ if (fs52.existsSync(logPath)) {
25525
+ const content = fs52.readFileSync(logPath, "utf-8");
25427
25526
  tail = content.slice(-3e3);
25428
25527
  }
25429
25528
  } catch {
@@ -25511,9 +25610,9 @@ async function runCi(argv) {
25511
25610
  forceRunCliArgs = { goal: envForceMessage };
25512
25611
  }
25513
25612
  }
25514
- if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs51.existsSync(dispatchEventPath)) {
25613
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs52.existsSync(dispatchEventPath)) {
25515
25614
  try {
25516
- const evt = JSON.parse(fs51.readFileSync(dispatchEventPath, "utf-8"));
25615
+ const evt = JSON.parse(fs52.readFileSync(dispatchEventPath, "utf-8"));
25517
25616
  const inputs = objectValue2(evt.inputs);
25518
25617
  const issueInput = parseInt(String(inputs?.issue_number ?? ""), 10);
25519
25618
  const sessionInput = String(inputs?.sessionId ?? "");
@@ -25921,7 +26020,7 @@ init_repoWorkspace();
25921
26020
 
25922
26021
  // src/scripts/brainTurnLog.ts
25923
26022
  init_runtimePaths();
25924
- import * as fs52 from "fs";
26023
+ import * as fs53 from "fs";
25925
26024
  import * as path50 from "path";
25926
26025
  import posixPath4 from "path/posix";
25927
26026
  var live = /* @__PURE__ */ new Map();
@@ -25930,8 +26029,8 @@ function brainEventsFilePath(dir, chatId) {
25930
26029
  }
25931
26030
  function lastPersistedSeq(dir, chatId) {
25932
26031
  const p = brainEventsFilePath(dir, chatId);
25933
- if (!fs52.existsSync(p)) return 0;
25934
- const lines = fs52.readFileSync(p, "utf-8").split("\n").filter(Boolean);
26032
+ if (!fs53.existsSync(p)) return 0;
26033
+ const lines = fs53.readFileSync(p, "utf-8").split("\n").filter(Boolean);
25935
26034
  if (lines.length === 0) return 0;
25936
26035
  try {
25937
26036
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -25941,9 +26040,9 @@ function lastPersistedSeq(dir, chatId) {
25941
26040
  }
25942
26041
  function readSince(dir, chatId, since) {
25943
26042
  const p = brainEventsFilePath(dir, chatId);
25944
- if (!fs52.existsSync(p)) return [];
26043
+ if (!fs53.existsSync(p)) return [];
25945
26044
  const out = [];
25946
- for (const line of fs52.readFileSync(p, "utf-8").split("\n")) {
26045
+ for (const line of fs53.readFileSync(p, "utf-8").split("\n")) {
25947
26046
  if (!line) continue;
25948
26047
  try {
25949
26048
  const rec = JSON.parse(line);
@@ -25969,12 +26068,12 @@ function beginTurn(dir, chatId) {
25969
26068
  };
25970
26069
  live.set(chatId, state);
25971
26070
  const p = brainEventsFilePath(dir, chatId);
25972
- fs52.mkdirSync(path50.dirname(p), { recursive: true });
26071
+ fs53.mkdirSync(path50.dirname(p), { recursive: true });
25973
26072
  return (event) => {
25974
26073
  state.seq += 1;
25975
26074
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
25976
26075
  try {
25977
- fs52.appendFileSync(p, `${JSON.stringify(rec)}
26076
+ fs53.appendFileSync(p, `${JSON.stringify(rec)}
25978
26077
  `);
25979
26078
  } catch (err) {
25980
26079
  process.stderr.write(
@@ -26013,7 +26112,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
26013
26112
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
26014
26113
  };
26015
26114
  try {
26016
- fs52.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
26115
+ fs53.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
26017
26116
  `);
26018
26117
  } catch {
26019
26118
  }
@@ -28246,7 +28345,7 @@ async function poolServe() {
28246
28345
 
28247
28346
  // src/servers/runner-serve.ts
28248
28347
  import { spawn as spawn9 } from "child_process";
28249
- import * as fs53 from "fs";
28348
+ import * as fs54 from "fs";
28250
28349
  import { createServer as createServer6 } from "http";
28251
28350
  var DEFAULT_PORT2 = 8080;
28252
28351
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -28381,8 +28480,8 @@ async function defaultRunJob(job) {
28381
28480
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
28382
28481
  const branch = job.ref ?? "main";
28383
28482
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
28384
- fs53.rmSync(workdir, { recursive: true, force: true });
28385
- fs53.mkdirSync(workdir, { recursive: true });
28483
+ fs54.rmSync(workdir, { recursive: true, force: true });
28484
+ fs54.mkdirSync(workdir, { recursive: true });
28386
28485
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
28387
28486
  const target = job.runRequest.target;
28388
28487
  const interactive = target.type === "chat";
@@ -74,7 +74,11 @@
74
74
  "scripts": {
75
75
  "preflight": [
76
76
  { "script": "loadSimpleCapability" },
77
- { "script": "prepareCapabilityDelivery" }
77
+ { "script": "prepareCapabilityDelivery" },
78
+ {
79
+ "script": "runSimpleCapabilityScript",
80
+ "runWhen": { "data.capabilityExecution": "script" }
81
+ }
78
82
  ],
79
83
  "postflight": [
80
84
  { "script": "parseSimpleCapabilityOutput" }
@@ -36,7 +36,13 @@
36
36
  },
37
37
  "cliTools": [],
38
38
  "scripts": {
39
- "preflight": [{ "script": "loadSimpleCapability" }],
39
+ "preflight": [
40
+ { "script": "loadSimpleCapability" },
41
+ {
42
+ "script": "runSimpleCapabilityScript",
43
+ "runWhen": { "data.capabilityExecution": "script" }
44
+ }
45
+ ],
40
46
  "postflight": [{ "script": "parseSimpleCapabilityOutput" }]
41
47
  },
42
48
  "inputArtifacts": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.464",
3
+ "version": "0.4.465",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",