@kody-ade/kody-engine 0.4.442 → 0.4.444

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.442",
18
+ version: "0.4.444",
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",
@@ -1681,62 +1681,38 @@ function listCapabilityFolderSlugs(absDir) {
1681
1681
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isCapabilityFolder(path5.join(absDir, e.name))).map((e) => e.name).sort();
1682
1682
  }
1683
1683
  function isCapabilityFolder(dir) {
1684
- if (!fs4.existsSync(path5.join(dir, CAPABILITY_DEFINITION_FILE))) return false;
1685
1684
  if (!fs4.existsSync(path5.join(dir, CAPABILITY_BODY_FILE))) return false;
1686
1685
  const entries = fs4.readdirSync(dir, { withFileTypes: true });
1687
1686
  return entries.every(
1688
- (entry) => entry.name === CAPABILITY_DEFINITION_FILE || entry.name === CAPABILITY_BODY_FILE || entry.isDirectory() && (entry.name === "skills" || entry.name === "tools")
1687
+ (entry) => entry.name === CAPABILITY_BODY_FILE || entry.isDirectory() && (entry.name === "skills" || entry.name === "tools")
1689
1688
  );
1690
1689
  }
1691
1690
  function readCapabilityFolder(root, slug) {
1692
1691
  const dir = path5.join(root, slug);
1693
- const definitionPath = path5.join(dir, CAPABILITY_DEFINITION_FILE);
1694
- const profilePath = definitionPath;
1695
1692
  const bodyPath = path5.join(dir, CAPABILITY_BODY_FILE);
1696
- if (!fs4.existsSync(profilePath) || !fs4.statSync(profilePath).isFile()) return null;
1697
1693
  if (!fs4.existsSync(bodyPath) || !fs4.statSync(bodyPath).isFile()) return null;
1694
+ if (!isCapabilityFolder(dir)) return null;
1698
1695
  try {
1699
- const rawDefinition = JSON.parse(fs4.readFileSync(profilePath, "utf-8"));
1700
- const contract = parseCapabilityContract(rawDefinition);
1701
- if (!contract) return null;
1702
- const rawProfile = {
1703
- inputSchema: contract.input.schema,
1704
- outputSchema: contract.output.schema,
1705
- contract
1706
- };
1707
1696
  const rawBody = fs4.readFileSync(bodyPath, "utf-8");
1708
1697
  const { title, body } = parseCapabilityBody(rawBody, slug);
1709
1698
  return {
1710
1699
  slug,
1711
1700
  dir,
1712
- profilePath,
1701
+ profilePath: bodyPath,
1713
1702
  bodyPath,
1714
1703
  title,
1715
1704
  body,
1716
1705
  rawBody,
1717
1706
  config: {
1718
1707
  action: slug,
1719
- describe: title,
1720
- outputSchema: contract.output.schema
1708
+ describe: title
1721
1709
  },
1722
- rawProfile
1710
+ rawProfile: {}
1723
1711
  };
1724
1712
  } catch {
1725
1713
  return null;
1726
1714
  }
1727
1715
  }
1728
- function parseCapabilityContract(raw) {
1729
- if (Object.keys(raw).length !== 2 || !isPlainObject(raw.input) || !isPlainObject(raw.output)) return null;
1730
- const parseValue = (value) => {
1731
- if (Object.keys(value).some((key) => key !== "name" && key !== "schema") || typeof value.name !== "string" || !/^[a-z][a-z0-9-]*$/.test(value.name) || !isPlainObject(value.schema)) {
1732
- return null;
1733
- }
1734
- return { name: value.name, schema: value.schema };
1735
- };
1736
- const input = parseValue(raw.input);
1737
- const output = parseValue(raw.output);
1738
- return input && output ? { input, output } : null;
1739
- }
1740
1716
  function parseCapabilityBody(raw, slug) {
1741
1717
  const trimmed = raw.trim();
1742
1718
  const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
@@ -1789,28 +1765,24 @@ function parseWorkflowStep(value) {
1789
1765
  const raw = value;
1790
1766
  const capability = stringField(raw.capability ?? raw.action);
1791
1767
  if (!capability || !isSafeSlug(capability)) return null;
1792
- const implementation = stringField(raw.implementation);
1793
1768
  const id = stringField(raw.id);
1794
1769
  const action = stringField(raw.action);
1795
1770
  const evidence = stringField(raw.evidence);
1796
1771
  const reason = stringField(raw.reason);
1797
1772
  const target = stringField(raw.target);
1798
1773
  const targetFact = stringField(raw.targetFact ?? raw.target_fact);
1799
- const cliArgs = raw.cliArgs;
1800
- const inputs = parseWorkflowInputs(raw.inputs);
1774
+ const hasInput = Object.hasOwn(raw, "input");
1801
1775
  const next = parseWorkflowTransitions(raw.next);
1802
1776
  const report = parseReportPublication(raw.report);
1803
1777
  return {
1804
1778
  capability,
1779
+ ...hasInput ? { input: raw.input } : {},
1805
1780
  ...id && isSafeStepId(id) ? { id } : {},
1806
1781
  ...action && isSafeSlug(action) ? { action } : {},
1807
- ...implementation && isSafeSlug(implementation) ? { implementation } : {},
1808
1782
  ...evidence ? { evidence } : {},
1809
1783
  ...target === "issue" || target === "pr" ? { target } : {},
1810
1784
  ...targetFact ? { targetFact } : {},
1811
1785
  ...reason ? { reason } : {},
1812
- ...cliArgs && typeof cliArgs === "object" && !Array.isArray(cliArgs) ? { cliArgs } : {},
1813
- ...inputs ? { inputs } : {},
1814
1786
  ...next ? { next } : {},
1815
1787
  ...isPlainObject(raw.runWhen) ? { runWhen: raw.runWhen } : {},
1816
1788
  ...stringList(raw.continueOn ?? raw.continue_on).length > 0 ? { continueOn: stringList(raw.continueOn ?? raw.continue_on) } : {},
@@ -1818,17 +1790,6 @@ function parseWorkflowStep(value) {
1818
1790
  ...report ? { report } : {}
1819
1791
  };
1820
1792
  }
1821
- function parseWorkflowInputs(value) {
1822
- if (!isPlainObject(value)) return void 0;
1823
- const inputs = {};
1824
- for (const [name, raw] of Object.entries(value)) {
1825
- if (!isSafeSlug(name) || !isPlainObject(raw)) continue;
1826
- const from = stringField(raw.from);
1827
- if (!from) continue;
1828
- inputs[name] = { from };
1829
- }
1830
- return Object.keys(inputs).length > 0 ? inputs : void 0;
1831
- }
1832
1793
  function parseWorkflowTransitions(value) {
1833
1794
  const rawTransitions = Array.isArray(value) ? value : value === void 0 ? [] : [value];
1834
1795
  const transitions = rawTransitions.map((raw) => {
@@ -1885,13 +1846,12 @@ function isSafeSlug(value) {
1885
1846
  function isSafeStepId(value) {
1886
1847
  return /^[A-Za-z][A-Za-z0-9_-]*$/.test(value) && !value.includes("..");
1887
1848
  }
1888
- var CAPABILITY_PROFILE_FILE, CAPABILITY_DEFINITION_FILE, CAPABILITY_BODY_FILE;
1849
+ var CAPABILITY_BODY_FILE, CAPABILITY_PROFILE_FILE;
1889
1850
  var init_capabilityFolders = __esm({
1890
1851
  "src/capabilityFolders.ts"() {
1891
1852
  "use strict";
1892
- CAPABILITY_PROFILE_FILE = "contract.json";
1893
- CAPABILITY_DEFINITION_FILE = "contract.json";
1894
1853
  CAPABILITY_BODY_FILE = "instructions.md";
1854
+ CAPABILITY_PROFILE_FILE = CAPABILITY_BODY_FILE;
1895
1855
  }
1896
1856
  });
1897
1857
 
@@ -2060,36 +2020,27 @@ function getCapabilityActionInputs(action, projectCapabilitiesRoot = getProjectC
2060
2020
  const resolved = resolveCapabilityAction(action, projectCapabilitiesRoot);
2061
2021
  if (!resolved) return null;
2062
2022
  const capability = resolveCapabilityFolder(resolved.capability, projectCapabilitiesRoot);
2063
- if (capability && path7.basename(capability.profilePath) === "contract.json") {
2064
- const schema = capability.rawProfile.inputSchema;
2065
- if (!schema || typeof schema !== "object" || Array.isArray(schema)) return [];
2066
- const properties = schema.properties;
2067
- if (!properties || typeof properties !== "object" || Array.isArray(properties)) return [];
2068
- const required2 = new Set(
2069
- Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : []
2070
- );
2071
- return Object.entries(properties).map(([name, value]) => {
2072
- const property = value && typeof value === "object" && !Array.isArray(value) ? value : {};
2073
- const type = property.type === "integer" ? "int" : property.type === "boolean" ? "bool" : Array.isArray(property.enum) ? "enum" : "string";
2074
- return {
2075
- name,
2076
- flag: `--${name}`,
2077
- type,
2078
- required: required2.has(name),
2079
- ...type === "enum" && Array.isArray(property.enum) ? { values: property.enum.filter((item) => typeof item === "string") } : {},
2080
- describe: typeof property.description === "string" ? property.description : name
2081
- };
2082
- });
2023
+ if (capability && !capability.config.workflow) {
2024
+ return [
2025
+ {
2026
+ name: "input",
2027
+ flag: "--input",
2028
+ type: "string",
2029
+ required: false,
2030
+ bindsCommentRest: true,
2031
+ describe: "One JSON-compatible input value."
2032
+ }
2033
+ ];
2083
2034
  }
2084
2035
  return getProfileInputs(resolved.implementation);
2085
2036
  }
2086
2037
  function resolveCapabilityExecution(capability, cwd = process.cwd()) {
2087
- if (path7.basename(capability.profilePath) === "contract.json") {
2038
+ if (!capability.config.workflow) {
2088
2039
  return { implementation: "capability-run", cliArgs: { capability: capability.slug } };
2089
2040
  }
2090
2041
  const firstWorkflowStep = capability.config.workflow?.steps[0];
2091
2042
  if (firstWorkflowStep) {
2092
- const implementation2 = firstWorkflowStep.implementation ?? firstWorkflowStep.capability;
2043
+ const implementation2 = firstWorkflowStep.capability;
2093
2044
  return { implementation: implementation2, cliArgs: {} };
2094
2045
  }
2095
2046
  const implementation = capability.config.implementation ?? capability.config.implementations?.[0] ?? (capability.config.role ? capability.slug : void 0) ?? (capability.config.tickScript ? "capability-tick-scripted" : "capability-tick");
@@ -3471,7 +3422,7 @@ function stripAgentSecrets(env) {
3471
3422
  }
3472
3423
  return out;
3473
3424
  }
3474
- function buildAgentEnvironment(baseEnv, repoToken) {
3425
+ function buildAgentEnvironment(baseEnv, repoToken, requestEnvironment) {
3475
3426
  const env = stripAgentSecrets({
3476
3427
  ...baseEnv,
3477
3428
  SKIP_HOOKS: "1",
@@ -3484,13 +3435,14 @@ function buildAgentEnvironment(baseEnv, repoToken) {
3484
3435
  env.GITHUB_TOKEN = repoToken;
3485
3436
  env.GH_TOKEN = repoToken;
3486
3437
  }
3438
+ Object.assign(env, requestEnvironment);
3487
3439
  return env;
3488
3440
  }
3489
3441
  async function runAgent(opts) {
3490
3442
  const ndjsonDir = opts.ndjsonDir ?? agentRunDir(opts.cwd);
3491
3443
  fs8.mkdirSync(ndjsonDir, { recursive: true });
3492
3444
  const ndjsonPath = path9.join(ndjsonDir, "last-run.jsonl");
3493
- const env = buildAgentEnvironment(process.env, opts.repoToken);
3445
+ const env = buildAgentEnvironment(process.env, opts.repoToken, opts.environment);
3494
3446
  if (opts.litellmUrl) {
3495
3447
  env.ANTHROPIC_BASE_URL = opts.litellmUrl;
3496
3448
  env.ANTHROPIC_API_KEY = getAnthropicApiKeyOrDummy();
@@ -8892,7 +8844,7 @@ function validateWorkflow(value, options = {}) {
8892
8844
  }
8893
8845
  const graphMode = workflow?.startAt !== void 0 || rawSteps.some((entry) => {
8894
8846
  const step = asRecord2(entry);
8895
- return Boolean(step && (step.id !== void 0 || step.next !== void 0 || step.inputs !== void 0));
8847
+ return Boolean(step && (step.id !== void 0 || step.next !== void 0));
8896
8848
  });
8897
8849
  const steps = rawSteps.map(
8898
8850
  (entry) => typeof entry === "string" ? { capability: entry } : asRecord2(entry)
@@ -8929,33 +8881,8 @@ function validateWorkflow(value, options = {}) {
8929
8881
  }
8930
8882
  }
8931
8883
  validateDataMatch(step.runWhen, `${base}.runWhen`, issues);
8932
- const inputs = asRecord2(step.inputs);
8933
- if (step.inputs !== void 0 && !inputs) {
8934
- issue(issues, "invalid_inputs", `${base}.inputs`, "workflow step inputs must be an object");
8935
- }
8936
- if (inputs) {
8937
- for (const [name, mapping] of Object.entries(inputs)) {
8938
- const inputPath = `${base}.inputs.${name}`;
8939
- if (!SAFE_NAME.test(name)) issue(issues, "invalid_input_name", inputPath, `invalid input name ${name}`);
8940
- const from = text(asRecord2(mapping)?.from);
8941
- if (!from || !SAFE_DATA_PATH.test(from)) {
8942
- issue(
8943
- issues,
8944
- "invalid_data_path",
8945
- `${inputPath}.from`,
8946
- `workflow input ${name} must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
8947
- );
8948
- }
8949
- const declared = capability ? options.capabilityInputs?.get(capability) : void 0;
8950
- if (declared && !declared.has(name)) {
8951
- issue(
8952
- issues,
8953
- "unknown_capability_input",
8954
- inputPath,
8955
- `capability ${capability} does not declare input ${name}`
8956
- );
8957
- }
8958
- }
8884
+ if (step.input !== void 0 && !isJsonValue(step.input)) {
8885
+ issue(issues, "invalid_input", `${base}.input`, "workflow step input must be one JSON value");
8959
8886
  }
8960
8887
  });
8961
8888
  if (!graphMode) return issues;
@@ -9140,6 +9067,12 @@ function isComparable(value) {
9140
9067
  if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
9141
9068
  return Array.isArray(value) && value.length > 0 && value.every((item) => isComparable(item) && !Array.isArray(item));
9142
9069
  }
9070
+ function isJsonValue(value) {
9071
+ if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
9072
+ if (Array.isArray(value)) return value.every(isJsonValue);
9073
+ if (!value || typeof value !== "object") return false;
9074
+ return Object.values(value).every(isJsonValue);
9075
+ }
9143
9076
  function issue(issues, code, path54, message) {
9144
9077
  issues.push({ code, path: path54, message });
9145
9078
  }
@@ -9153,14 +9086,12 @@ var init_workflowValidation = __esm({
9153
9086
  SUPPORTED_STEP_FIELDS = /* @__PURE__ */ new Set([
9154
9087
  "id",
9155
9088
  "capability",
9089
+ "input",
9156
9090
  "action",
9157
- "implementation",
9158
9091
  "evidence",
9159
9092
  "target",
9160
9093
  "targetFact",
9161
9094
  "reason",
9162
- "cliArgs",
9163
- "inputs",
9164
9095
  "next",
9165
9096
  "runWhen",
9166
9097
  "continueOn",
@@ -9189,10 +9120,7 @@ function normalizeWorkflowDefinition(value) {
9189
9120
  const name = typeof raw.name === "string" ? raw.name.trim() : "";
9190
9121
  const requestedAgent = typeof raw.agent === "string" ? raw.agent.trim() : "";
9191
9122
  const agent = /^[a-z][a-z0-9-]*$/.test(requestedAgent) ? requestedAgent : "kody";
9192
- const hasGraphConnections = Array.isArray(raw.steps) && raw.steps.some(
9193
- (step) => step && typeof step === "object" && !Array.isArray(step) && (step.next !== void 0 || step.inputs !== void 0)
9194
- );
9195
- if (hasGraphConnections) {
9123
+ if (Array.isArray(raw.steps)) {
9196
9124
  if (validateWorkflow({ steps: raw.steps, ...raw.startAt !== void 0 ? { startAt: raw.startAt } : {} }).length > 0) {
9197
9125
  return null;
9198
9126
  }
@@ -15244,6 +15172,51 @@ var init_loadCapabilityState = __esm({
15244
15172
  // src/scripts/loadSimpleCapability.ts
15245
15173
  import * as fs39 from "fs";
15246
15174
  import * as path36 from "path";
15175
+ function parseInput(supplied) {
15176
+ if (typeof supplied !== "string") return supplied;
15177
+ try {
15178
+ return JSON.parse(supplied);
15179
+ } catch {
15180
+ return parseFlagInput(supplied) ?? supplied;
15181
+ }
15182
+ }
15183
+ function parseFlagInput(value) {
15184
+ const tokens = value.trim().split(/\s+/).filter(Boolean);
15185
+ if (!tokens.some((token) => token.startsWith("--"))) return null;
15186
+ const input = {};
15187
+ const text2 = [];
15188
+ for (let index = 0; index < tokens.length; index += 1) {
15189
+ const token = tokens[index];
15190
+ if (!token.startsWith("--") || token.length === 2) {
15191
+ text2.push(token);
15192
+ continue;
15193
+ }
15194
+ const equalAt = token.indexOf("=");
15195
+ const name = equalAt >= 0 ? token.slice(2, equalAt) : token.slice(2);
15196
+ const next = equalAt >= 0 ? token.slice(equalAt + 1) : tokens[index + 1];
15197
+ if (equalAt < 0 && next && !next.startsWith("--")) index += 1;
15198
+ input[name] = next && !next.startsWith("--") ? scalar(next) : true;
15199
+ }
15200
+ if (text2.length > 0) input.request = text2.join(" ");
15201
+ return input;
15202
+ }
15203
+ function scalar(value) {
15204
+ if (value === "true" || value === "false") return value === "true";
15205
+ if (/^-?\d+$/.test(value)) return Number(value);
15206
+ return value;
15207
+ }
15208
+ function capabilityEnvironment(input) {
15209
+ const environment = {
15210
+ KODY_CAPABILITY_INPUT: JSON.stringify(input ?? null)
15211
+ };
15212
+ if (!input || typeof input !== "object" || Array.isArray(input)) return environment;
15213
+ for (const [name, value] of Object.entries(input)) {
15214
+ if (value === void 0 || value === null) continue;
15215
+ const key = name.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
15216
+ environment[`KODY_ARG_${key}`] = typeof value === "string" ? value : JSON.stringify(value);
15217
+ }
15218
+ return environment;
15219
+ }
15247
15220
  function listFiles(root) {
15248
15221
  if (!fs39.existsSync(root)) return [];
15249
15222
  const files = [];
@@ -15262,8 +15235,8 @@ var loadSimpleCapability;
15262
15235
  var init_loadSimpleCapability = __esm({
15263
15236
  "src/scripts/loadSimpleCapability.ts"() {
15264
15237
  "use strict";
15265
- init_definition_paths();
15266
15238
  init_capabilityFolders();
15239
+ init_definition_paths();
15267
15240
  loadSimpleCapability = async (ctx) => {
15268
15241
  const slug = typeof ctx.args.capability === "string" ? ctx.args.capability.trim() : "";
15269
15242
  if (!/^[a-z][a-z0-9-]*$/.test(slug)) {
@@ -15273,37 +15246,24 @@ var init_loadSimpleCapability = __esm({
15273
15246
  if (!capability) {
15274
15247
  throw new Error(`Capability "${slug}" is not a valid simple capability folder`);
15275
15248
  }
15276
- const contract = capability.rawProfile.contract;
15277
15249
  const toolRoot = path36.join(capability.dir, "tools");
15278
15250
  const skillRoot = path36.join(capability.dir, "skills");
15279
15251
  const toolFiles = listFiles(toolRoot);
15280
15252
  const skillFiles = listFiles(skillRoot);
15281
- const supplied = ctx.args.input;
15282
- let input = supplied;
15283
- if (typeof supplied === "string") {
15284
- try {
15285
- input = JSON.parse(supplied);
15286
- } catch {
15287
- input = supplied;
15288
- }
15289
- }
15253
+ const input = parseInput(ctx.args.input);
15290
15254
  ctx.data.jobCapability = slug;
15291
15255
  ctx.data.capabilityInput = input;
15292
- ctx.data.capabilityContract = contract;
15256
+ ctx.data.capabilityEnvironment = capabilityEnvironment(input);
15293
15257
  ctx.data.prompt = [
15294
15258
  capability.rawBody.trim(),
15295
15259
  "",
15296
15260
  "## Input",
15297
15261
  "",
15298
15262
  "```json",
15299
- JSON.stringify({ [contract.input.name]: input }, null, 2),
15263
+ JSON.stringify(input ?? null, null, 2),
15300
15264
  "```",
15301
15265
  "",
15302
- "Return one JSON value matching the output contract:",
15303
- "",
15304
- "```json",
15305
- JSON.stringify({ [contract.output.name]: contract.output.schema }, null, 2),
15306
- "```",
15266
+ "Return one JSON value.",
15307
15267
  ...skillFiles.length ? [
15308
15268
  "",
15309
15269
  "## Skills",
@@ -16871,16 +16831,29 @@ var init_parseJobStateFromAgentResult = __esm({
16871
16831
  });
16872
16832
 
16873
16833
  // src/scripts/parseSimpleCapabilityOutput.ts
16874
- function parseEnvelope(text2) {
16875
- if (!text2) return null;
16876
- const candidate = text2.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)?.[1] ?? text2;
16834
+ function parseOutput(text2) {
16835
+ if (!text2) return void 0;
16877
16836
  try {
16878
- const parsed = JSON.parse(candidate);
16879
- if (!isObject(parsed) || !isObject(parsed.result)) return null;
16880
- return { result: parsed.result };
16837
+ return JSON.parse(text2);
16881
16838
  } catch {
16882
- return null;
16883
16839
  }
16840
+ const fences = [...text2.matchAll(/```([a-z0-9_-]+)?\s*([\s\S]*?)\s*```/gi)];
16841
+ const jsonFences = fences.filter((match) => match[1]?.toLowerCase() === "json");
16842
+ const labelledOutput = parseSingleJsonCandidate(jsonFences.map((match) => match[2]));
16843
+ if (labelledOutput.found) return labelledOutput.value;
16844
+ const plainOutput = parseSingleJsonCandidate(fences.filter((match) => !match[1]).map((match) => match[2]));
16845
+ return plainOutput.found ? plainOutput.value : void 0;
16846
+ }
16847
+ function parseSingleJsonCandidate(candidates) {
16848
+ const parsed = [];
16849
+ for (const candidate of candidates) {
16850
+ if (!candidate) continue;
16851
+ try {
16852
+ parsed.push(JSON.parse(candidate));
16853
+ } catch {
16854
+ }
16855
+ }
16856
+ return parsed.length === 1 ? { found: true, value: parsed[0] } : { found: false };
16884
16857
  }
16885
16858
  function isObject(value) {
16886
16859
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -16893,28 +16866,31 @@ var init_parseSimpleCapabilityOutput = __esm({
16893
16866
  "src/scripts/parseSimpleCapabilityOutput.ts"() {
16894
16867
  "use strict";
16895
16868
  parseSimpleCapabilityOutput = async (ctx, _profile, agentResult) => {
16896
- const envelope = parseEnvelope(agentResult?.finalText);
16897
- if (!envelope) {
16869
+ const output = parseOutput(agentResult?.finalText);
16870
+ if (output === void 0) {
16898
16871
  ctx.output.exitCode = 64;
16899
- ctx.output.reason = "simple capability did not return its JSON output contract";
16872
+ ctx.output.reason = "simple capability did not return one JSON value";
16900
16873
  return;
16901
16874
  }
16902
- const result = envelope.result;
16903
- const data = isObject(result.data) ? result.data : {};
16875
+ ctx.data.capabilityOutput = output;
16876
+ const result = isObject(output) ? output : {};
16877
+ const data = isObject(result.data) ? result.data : isObject(output) ? output : { output };
16904
16878
  const summary = typeof result.summary === "string" ? result.summary : "Capability completed";
16905
16879
  const reason = typeof result.reason === "string" ? result.reason : summary;
16906
16880
  const prUrl = stringValue4(data.pullRequestUrl) ?? stringValue4(data.prUrl) ?? stringValue4(result.pullRequestUrl) ?? stringValue4(result.prUrl);
16907
16881
  if (prUrl) ctx.output.prUrl = prUrl;
16908
16882
  ctx.output.reason = reason;
16909
- ctx.data.capabilityResults = [{
16910
- version: 1,
16911
- status: "changed",
16912
- summary,
16913
- facts: data,
16914
- artifacts: prUrl ? [{ label: "Pull request", url: prUrl }] : [],
16915
- missingEvidence: [],
16916
- blockers: []
16917
- }];
16883
+ ctx.data.capabilityResults = [
16884
+ {
16885
+ version: 1,
16886
+ status: "changed",
16887
+ summary,
16888
+ facts: data,
16889
+ artifacts: prUrl ? [{ label: "Pull request", url: prUrl }] : [],
16890
+ missingEvidence: [],
16891
+ blockers: []
16892
+ }
16893
+ ];
16918
16894
  };
16919
16895
  }
16920
16896
  });
@@ -19650,26 +19626,18 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures, op
19650
19626
  const workflow = hasWorkflowObject ? profile.workflow : { steps: profile.steps, ...profile.startAt !== void 0 ? { startAt: profile.startAt } : {} };
19651
19627
  const known = options.capabilityRoot ? getCapabilityRoots(options.capabilityRoot).flatMap((root) => listCapabilityFolderSlugs(root)) : [];
19652
19628
  const uniqueKnown = [...new Set(known)];
19653
- const capabilityInputs = /* @__PURE__ */ new Map();
19654
19629
  const capabilityOutputs = /* @__PURE__ */ new Map();
19655
19630
  if (options.capabilityRoot) {
19656
19631
  for (const capability of uniqueKnown) {
19657
19632
  const folder = getCapabilityRoots(options.capabilityRoot).map((root) => readCapabilityFolder(root, capability)).find((entry) => entry !== null);
19658
- capabilityOutputs.set(capability, folder ? capabilityOutputConditionPaths(folder.config) : /* @__PURE__ */ new Set());
19659
- const inputs = getCapabilityActionInputs(capability, options.capabilityRoot);
19660
- if (inputs) {
19661
- capabilityInputs.set(
19662
- capability,
19663
- new Set(inputs.flatMap((input) => [input.name, input.flag.replace(/^--/, "")]))
19664
- );
19665
- }
19633
+ const outputPaths = folder ? capabilityOutputConditionPaths(folder.config) : /* @__PURE__ */ new Set();
19634
+ if (outputPaths.size > 0) capabilityOutputs.set(capability, outputPaths);
19666
19635
  }
19667
19636
  }
19668
19637
  failures.push(
19669
19638
  ...formatWorkflowValidationIssues(
19670
19639
  validateWorkflow(workflow, {
19671
19640
  ...uniqueKnown.length > 0 ? { knownCapabilities: new Set(uniqueKnown) } : {},
19672
- ...capabilityInputs.size > 0 ? { capabilityInputs } : {},
19673
19641
  ...capabilityOutputs.size > 0 ? { capabilityOutputs } : {}
19674
19642
  })
19675
19643
  )
@@ -21214,6 +21182,7 @@ async function runImplementation(profileName, input) {
21214
21182
  prompt,
21215
21183
  model,
21216
21184
  cwd: input.cwd,
21185
+ environment: ctx.data.capabilityEnvironment && typeof ctx.data.capabilityEnvironment === "object" && !Array.isArray(ctx.data.capabilityEnvironment) ? ctx.data.capabilityEnvironment : void 0,
21217
21186
  litellmUrl: lm?.url ?? null,
21218
21187
  // On a connection drop mid-run, restart the (possibly crashed) proxy
21219
21188
  // before the agent retries. No-op for direct-Anthropic runs (lm null).
@@ -21471,7 +21440,8 @@ async function runImplementation(profileName, input) {
21471
21440
  nextJob: ctx.output.nextJob,
21472
21441
  afterNextJob: ctx.output.afterNextJob,
21473
21442
  taskState: ctx.data.taskState,
21474
- capabilityResults
21443
+ capabilityResults,
21444
+ ...Object.hasOwn(ctx.data, "capabilityOutput") ? { capabilityOutput: ctx.data.capabilityOutput } : {}
21475
21445
  });
21476
21446
  } catch (err) {
21477
21447
  const msg = err instanceof Error ? err.message : String(err);
@@ -22251,12 +22221,10 @@ async function runCapabilityImplementationStep(valid, profileName, capabilityIde
22251
22221
  const shouldApplyResolvedCapabilityArgs = valid.implementation === void 0 && resolvedCapability && profileName === resolvedCapability.implementation;
22252
22222
  input.cliArgs = shouldApplyResolvedCapabilityArgs ? { ...resolvedCapability.cliArgs, ...input.cliArgs } : input.cliArgs;
22253
22223
  if (profileName === "capability-run" && capabilityIdentity) {
22254
- const capabilityInput = { ...valid.cliArgs };
22224
+ const capabilityInput = Object.keys(valid.cliArgs).length > 0 ? genericInputFromArgs(valid.cliArgs) : void 0;
22255
22225
  input.cliArgs = {
22256
22226
  capability: capabilityIdentity,
22257
- ...Object.keys(capabilityInput).length > 0 ? {
22258
- input: Object.keys(capabilityInput).length === 1 && typeof capabilityInput.input === "string" ? capabilityInput.input : JSON.stringify(capabilityInput)
22259
- } : {}
22227
+ ...capabilityInput !== void 0 ? { input: JSON.stringify(capabilityInput) } : {}
22260
22228
  };
22261
22229
  }
22262
22230
  const run = base.chain === false ? runImplementation : runImplementationChain;
@@ -22316,7 +22284,7 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base) {
22316
22284
  );
22317
22285
  continue;
22318
22286
  }
22319
- const child = workflowStepToJob(step, parent, chainData);
22287
+ const child = workflowStepToJob(step, parent, chainData, base.cwd);
22320
22288
  process.stdout.write(
22321
22289
  `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
22322
22290
 
@@ -22341,6 +22309,7 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base) {
22341
22309
  ...chainData,
22342
22310
  ...result.taskState ? { taskState: result.taskState } : {},
22343
22311
  ...outcome ? { workflowLastOutcome: outcome } : {},
22312
+ ...result.capabilityOutput !== void 0 ? { workflowLastOutput: result.capabilityOutput } : {},
22344
22313
  ...prUrl ? { workflowPrUrl: prUrl } : {},
22345
22314
  ...parsePrNumber5(prUrl) ? { workflowPrNumber: parsePrNumber5(prUrl) } : {}
22346
22315
  };
@@ -22354,12 +22323,11 @@ async function runLinearCapabilityWorkflow(parent, workflow, capability, base) {
22354
22323
  return withWorkflowBoundaryEval(capability, result);
22355
22324
  }
22356
22325
  function isGraphWorkflow(workflow) {
22357
- return workflow.startAt !== void 0 || workflow.steps.some((step) => step.id !== void 0 || step.next !== void 0 || step.inputs !== void 0);
22326
+ return workflow.startAt !== void 0 || workflow.steps.some((step) => step.id !== void 0 || step.next !== void 0);
22358
22327
  }
22359
22328
  function workflowError(workflow, base) {
22360
22329
  const projectCapabilitiesRoot = hydratedCapabilitiesRoot(base.cwd);
22361
22330
  const knownCapabilities = /* @__PURE__ */ new Set();
22362
- const capabilityInputs = /* @__PURE__ */ new Map();
22363
22331
  const capabilityOutputs = /* @__PURE__ */ new Map();
22364
22332
  for (const step of workflow.steps) {
22365
22333
  const action = step.action ?? step.capability;
@@ -22367,21 +22335,10 @@ function workflowError(workflow, base) {
22367
22335
  const resolvedFolder = resolveCapabilityFolder(step.capability, projectCapabilitiesRoot);
22368
22336
  if (!resolvedAction && !resolvedFolder) continue;
22369
22337
  knownCapabilities.add(step.capability);
22370
- capabilityOutputs.set(
22371
- step.capability,
22372
- resolvedFolder ? capabilityOutputConditionPaths(resolvedFolder.config) : /* @__PURE__ */ new Set()
22373
- );
22374
- const inputs = getCapabilityActionInputs(action, projectCapabilitiesRoot);
22375
- if (inputs) {
22376
- capabilityInputs.set(
22377
- step.capability,
22378
- new Set(inputs.flatMap((input) => [input.name, input.flag.replace(/^--/, "")]))
22379
- );
22380
- }
22338
+ const outputPaths = resolvedFolder ? capabilityOutputConditionPaths(resolvedFolder.config) : /* @__PURE__ */ new Set();
22339
+ if (outputPaths.size > 0) capabilityOutputs.set(step.capability, outputPaths);
22381
22340
  }
22382
- return formatWorkflowValidationIssues(
22383
- validateWorkflow(workflow, { knownCapabilities, capabilityInputs, capabilityOutputs })
22384
- )[0] ?? null;
22341
+ return formatWorkflowValidationIssues(validateWorkflow(workflow, { knownCapabilities, capabilityOutputs }))[0] ?? null;
22385
22342
  }
22386
22343
  function initialWorkflowState(parent, workflow) {
22387
22344
  const prior = parent.workflowState;
@@ -22456,7 +22413,7 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
22456
22413
  await checkpoint?.(state);
22457
22414
  let child;
22458
22415
  try {
22459
- child = workflowStepToJob(step, parent, chainData);
22416
+ child = workflowStepToJob(step, parent, chainData, base.cwd);
22460
22417
  } catch (error) {
22461
22418
  const reason = error instanceof Error ? error.message : String(error);
22462
22419
  state.status = "blocked";
@@ -22490,6 +22447,7 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
22490
22447
  ...result.taskState ? { taskState: result.taskState } : {},
22491
22448
  ...outcome ? { workflowLastOutcome: outcome } : {},
22492
22449
  ...result.capabilityResults?.at(-1) ? { workflowLastResult: result.capabilityResults.at(-1) } : {},
22450
+ ...result.capabilityOutput !== void 0 ? { workflowLastOutput: result.capabilityOutput } : {},
22493
22451
  ...prUrl ? { workflowPrUrl: prUrl } : {},
22494
22452
  ...parsePrNumber5(prUrl) ? { workflowPrNumber: parsePrNumber5(prUrl) } : {}
22495
22453
  };
@@ -22588,23 +22546,12 @@ function withWorkflowBoundaryEval(capability, result) {
22588
22546
  reason: result.reason ? `${result.reason}; agency boundary eval failed: ${failed.join(", ")}` : `agency boundary eval failed: ${failed.join(", ")}`
22589
22547
  };
22590
22548
  }
22591
- function workflowStepToJob(step, parent, chainData) {
22549
+ function workflowStepToJob(step, parent, chainData, cwd) {
22592
22550
  const action = step.action ?? step.capability;
22593
- const mappedArgs = {};
22594
- const conditionContext = workflowConditionContext(chainData);
22595
- for (const [name, mapping] of Object.entries(step.inputs ?? {})) {
22596
- const value = resolveDottedPath2(conditionContext, mapping.from);
22597
- if (value === void 0) {
22598
- throw new InvalidJobError(`workflow step ${step.id ?? action} needs missing input ${mapping.from}`);
22599
- }
22600
- mappedArgs[name] = value;
22601
- }
22551
+ const targetNumber = workflowStepTargetNumber(step, parent, chainData);
22602
22552
  const rawArgs = {
22603
- ...parent.cliArgs,
22604
- ...mappedArgs,
22605
- ...step.cliArgs ?? {}
22553
+ ...parent.cliArgs
22606
22554
  };
22607
- const targetNumber = workflowStepTargetNumber(step, parent, chainData);
22608
22555
  if (step.target === "pr") {
22609
22556
  if (typeof targetNumber !== "number") {
22610
22557
  throw new InvalidJobError(`workflow step ${action} needs a PR target but no prior PR URL is available`);
@@ -22613,12 +22560,16 @@ function workflowStepToJob(step, parent, chainData) {
22613
22560
  } else if (step.target === "issue" && typeof targetNumber === "number") {
22614
22561
  rawArgs.issue = targetNumber;
22615
22562
  }
22616
- const cliArgs = filterCliArgsForStep(action, rawArgs);
22563
+ const genericInput = capabilityStepInput(
22564
+ step.input ?? chainData.workflowLastOutput ?? genericInputFromArgs(rawArgs),
22565
+ step.target,
22566
+ targetNumber
22567
+ );
22568
+ const cliArgs = usesGenericCapabilityInput(action, cwd) ? genericInput === void 0 ? {} : { input: JSON.stringify(genericInput) } : filterCliArgsForStep(action, rawArgs);
22617
22569
  const target = typeof targetNumber === "number" ? targetNumber : typeof parent.target === "number" ? parent.target : targetFromCliArgs(cliArgs);
22618
22570
  return {
22619
22571
  action,
22620
22572
  capability: step.capability,
22621
- ...step.implementation ? { implementation: step.implementation } : {},
22622
22573
  ...composeStepWhy(parent.why, step) ? { why: composeStepWhy(parent.why, step) } : {},
22623
22574
  ...parent.agent ? { agent: parent.agent } : {},
22624
22575
  ...parent.schedule ? { schedule: parent.schedule } : {},
@@ -22632,6 +22583,38 @@ function workflowStepToJob(step, parent, chainData) {
22632
22583
  ...parent.resultTarget ? { resultTarget: parent.resultTarget } : {}
22633
22584
  };
22634
22585
  }
22586
+ function usesGenericCapabilityInput(action, cwd) {
22587
+ const inputs = getCapabilityActionInputs(action, hydratedCapabilitiesRoot(cwd));
22588
+ return Boolean(inputs?.length === 1 && inputs[0]?.name === "input" && inputs[0]?.flag === "--input");
22589
+ }
22590
+ function genericInputFromArgs(args) {
22591
+ if (Object.hasOwn(args, "input")) {
22592
+ const { input, ...routing } = args;
22593
+ const parsed = parseGenericInput(input);
22594
+ if (Object.keys(routing).length === 0) return parsed;
22595
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
22596
+ return { ...parsed, ...routing };
22597
+ }
22598
+ return { request: parsed, ...routing };
22599
+ }
22600
+ return args;
22601
+ }
22602
+ function parseGenericInput(value) {
22603
+ if (typeof value !== "string") return value;
22604
+ try {
22605
+ return JSON.parse(value);
22606
+ } catch {
22607
+ return value;
22608
+ }
22609
+ }
22610
+ function capabilityStepInput(input, target, targetNumber) {
22611
+ if (!target || targetNumber === void 0) return input;
22612
+ const routing = { [target]: targetNumber };
22613
+ if (input && typeof input === "object" && !Array.isArray(input)) {
22614
+ return { ...input, ...routing };
22615
+ }
22616
+ return input === void 0 ? routing : { request: input, ...routing };
22617
+ }
22635
22618
  function shouldRunWorkflowStep(step, data) {
22636
22619
  if (!step.runWhen) return true;
22637
22620
  const context = workflowConditionContext(data);
@@ -22647,15 +22630,17 @@ function workflowOutcome(result) {
22647
22630
  function workflowConditionContext(data) {
22648
22631
  const lastOutcome = data.workflowLastOutcome;
22649
22632
  const lastResult = data.workflowLastResult;
22633
+ const lastOutput = data.workflowLastOutput;
22650
22634
  return {
22651
22635
  ...data,
22652
22636
  facts: data.workflowFacts ?? {},
22653
22637
  evidence: data.workflowEvidence ?? {},
22654
22638
  artifacts: data.workflowArtifacts ?? [],
22655
- result: lastResult,
22639
+ result: lastOutput === void 0 ? lastResult : lastOutput,
22656
22640
  workflow: {
22657
22641
  lastOutcome,
22658
22642
  lastResult,
22643
+ lastOutput,
22659
22644
  issueNumber: data.workflowIssueNumber,
22660
22645
  prNumber: data.workflowPrNumber,
22661
22646
  prUrl: data.workflowPrUrl
@@ -22674,10 +22659,9 @@ function valueMatches(actual, expected) {
22674
22659
  return actual === expected;
22675
22660
  }
22676
22661
  function workflowStepTargetNumber(step, parent, chainData) {
22677
- if (step.target === "pr")
22678
- return workflowPrNumber(chainData) ?? workflowTargetFactNumber(step, chainData) ?? targetFromCliArgs(step.cliArgs ?? {});
22662
+ if (step.target === "pr") return workflowPrNumber(chainData) ?? workflowTargetFactNumber(step, chainData);
22679
22663
  if (step.target === "issue") return workflowIssueNumber(parent);
22680
- return typeof parent.target === "number" ? parent.target : targetFromCliArgs({ ...parent.cliArgs, ...step.cliArgs ?? {} });
22664
+ return typeof parent.target === "number" ? parent.target : targetFromCliArgs(parent.cliArgs);
22681
22665
  }
22682
22666
  function workflowResumeStartIndex(steps, evidence) {
22683
22667
  if (!evidence) return 0;
@@ -16,6 +16,7 @@
16
16
  "flag": "--input",
17
17
  "type": "string",
18
18
  "required": false,
19
+ "bindsCommentRest": true,
19
20
  "describe": "The capability's single JSON input."
20
21
  }
21
22
  ],
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.442",
3
+ "version": "0.4.444",
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",
@@ -12,6 +12,29 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
+ "scripts": {
16
+ "kody:run": "tsx bin/kody.ts",
17
+ "serve": "tsx bin/kody.ts serve",
18
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
19
+ "serve:claude": "tsx bin/kody.ts serve claude",
20
+ "clean:dist": "node scripts/clean-dist.cjs",
21
+ "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
22
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
23
+ "pretest": "pnpm check:modularity",
24
+ "test": "vitest run tests/unit tests/int --coverage",
25
+ "posttest": "tsx scripts/check-coverage-floor.ts",
26
+ "test:smoke": "vitest run tests/smoke --no-coverage",
27
+ "test:e2e": "vitest run tests/e2e --no-coverage",
28
+ "test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
29
+ "test:all": "vitest run tests --no-coverage",
30
+ "typecheck": "tsc --noEmit",
31
+ "lint": "biome check",
32
+ "lint:fix": "biome check --write",
33
+ "format": "biome format --write",
34
+ "verify:package": "node scripts/verify-package-tarball.cjs",
35
+ "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
36
+ "prepublishOnly": "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm test:runtime-services && pnpm build && pnpm verify:package"
37
+ },
15
38
  "dependencies": {
16
39
  "@actions/cache": "^6.0.0",
17
40
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -38,27 +61,5 @@
38
61
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
39
62
  },
40
63
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
41
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
42
- "scripts": {
43
- "kody:run": "tsx bin/kody.ts",
44
- "serve": "tsx bin/kody.ts serve",
45
- "serve:vscode": "tsx bin/kody.ts serve vscode",
46
- "serve:claude": "tsx bin/kody.ts serve claude",
47
- "clean:dist": "node scripts/clean-dist.cjs",
48
- "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
49
- "check:modularity": "tsx scripts/check-script-modularity.ts",
50
- "pretest": "pnpm check:modularity",
51
- "test": "vitest run tests/unit tests/int --coverage",
52
- "posttest": "tsx scripts/check-coverage-floor.ts",
53
- "test:smoke": "vitest run tests/smoke --no-coverage",
54
- "test:e2e": "vitest run tests/e2e --no-coverage",
55
- "test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
56
- "test:all": "vitest run tests --no-coverage",
57
- "typecheck": "tsc --noEmit",
58
- "lint": "biome check",
59
- "lint:fix": "biome check --write",
60
- "format": "biome format --write",
61
- "verify:package": "node scripts/verify-package-tarball.cjs",
62
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
63
- }
64
- }
64
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
65
+ }