@kody-ade/kody-engine 0.4.509 → 0.4.511

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.
Files changed (2) hide show
  1. package/dist/bin/kody.js +96 -24
  2. package/package.json +1 -1
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.509",
18
+ version: "0.4.511",
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",
@@ -809,6 +809,16 @@ async function enforceSubagentModelInheritance(input) {
809
809
  }
810
810
  };
811
811
  }
812
+ function createSubagentInvocationHook(invokedSubagents) {
813
+ return async (input) => {
814
+ const toolInput = input.tool_input;
815
+ if (toolInput && typeof toolInput === "object" && !Array.isArray(toolInput)) {
816
+ const subagentType = toolInput.subagent_type;
817
+ if (typeof subagentType === "string" && subagentType.length > 0) invokedSubagents.add(subagentType);
818
+ }
819
+ return {};
820
+ };
821
+ }
812
822
  function splitFrontmatter(raw) {
813
823
  const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
814
824
  if (!match) return { fm: {}, body: raw.trim() };
@@ -1959,8 +1969,15 @@ function parseCapabilityContract(raw) {
1959
1969
  if (timeoutMs !== void 0 && parsed.execution !== "script") {
1960
1970
  throw new Error('contract.json timeoutMs is supported only when execution is "script"');
1961
1971
  }
1972
+ const requiredSubagents = parsed.requiredSubagents === void 0 ? void 0 : Array.isArray(parsed.requiredSubagents) && parsed.requiredSubagents.length > 0 && parsed.requiredSubagents.every((name) => typeof name === "string" && /^[a-z][a-z0-9-]{0,63}$/.test(name)) ? [...new Set(parsed.requiredSubagents)] : null;
1973
+ if (requiredSubagents === null) {
1974
+ throw new Error("contract.json requiredSubagents must contain valid specialist names");
1975
+ }
1976
+ if (requiredSubagents && parsed.execution !== "agent") {
1977
+ throw new Error('contract.json requiredSubagents are supported only when execution is "agent"');
1978
+ }
1962
1979
  const unsupported = Object.keys(parsed).filter(
1963
- (key) => key !== "execution" && key !== "secrets" && key !== "timeoutMs" && key !== "input" && key !== "output"
1980
+ (key) => key !== "execution" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
1964
1981
  );
1965
1982
  if (unsupported.length > 0) {
1966
1983
  throw new Error(`contract.json contains unsupported fields: ${unsupported.join(", ")}`);
@@ -1969,6 +1986,7 @@ function parseCapabilityContract(raw) {
1969
1986
  ...parsed.execution ? { execution: parsed.execution } : {},
1970
1987
  ...secrets ? { secrets } : {},
1971
1988
  ...timeoutMs !== void 0 ? { timeoutMs } : {},
1989
+ ...requiredSubagents ? { requiredSubagents } : {},
1972
1990
  input: parsed.input,
1973
1991
  output: parsed.output
1974
1992
  };
@@ -3717,6 +3735,8 @@ async function runAgent(opts) {
3717
3735
  let messageCount = 0;
3718
3736
  let finalText = "";
3719
3737
  let getSubmitted;
3738
+ const invokedSubagents = /* @__PURE__ */ new Set();
3739
+ const subagentInvocationHook = createSubagentInvocationHook(invokedSubagents);
3720
3740
  for (let attempt = 0; ; attempt++) {
3721
3741
  let ndjsonWriteFailed = false;
3722
3742
  let ndjsonWriteError;
@@ -3751,6 +3771,12 @@ async function runAgent(opts) {
3751
3771
  matcher: "Agent",
3752
3772
  hooks: [enforceSubagentModelInheritance]
3753
3773
  }
3774
+ ],
3775
+ PostToolUse: [
3776
+ {
3777
+ matcher: "Agent",
3778
+ hooks: [subagentInvocationHook]
3779
+ }
3754
3780
  ]
3755
3781
  }
3756
3782
  };
@@ -4072,7 +4098,8 @@ async function runAgent(opts) {
4072
4098
  durationMs: Date.now() - startedAt,
4073
4099
  tokens,
4074
4100
  costUsd,
4075
- messageCount
4101
+ messageCount,
4102
+ invokedSubagents: [...invokedSubagents]
4076
4103
  };
4077
4104
  }
4078
4105
  var DEFAULT_ALLOWED_TOOLS, DEFAULT_TURN_TIMEOUT_MS, MAX_CONNECTION_RETRIES, CONNECTION_RETRY_BASE_MS, MUTATING_FILE_TOOLS, BASH_WRITE_VERB, AGENT_KEEP_SECRETS;
@@ -4884,9 +4911,7 @@ var init_capability_contract_validation = __esm({
4884
4911
  const property = error.keyword === "additionalProperties" && typeof error.params.additionalProperty === "string" ? ` (${error.params.additionalProperty})` : "";
4885
4912
  return `${location}: ${error.message ?? error.keyword}${property}`;
4886
4913
  }).join("; ");
4887
- super(
4888
- `Capability ${boundary} does not match its declared contract: ${details}`
4889
- );
4914
+ super(`Capability ${boundary} does not match its declared contract: ${details}`);
4890
4915
  this.boundary = boundary;
4891
4916
  this.errors = errors;
4892
4917
  this.name = "CapabilityContractValidationError";
@@ -7921,10 +7946,7 @@ function capabilityInputEnvironment(input) {
7921
7946
  function capabilityConfigEnvironment(config) {
7922
7947
  if (!config || typeof config !== "object" || Array.isArray(config)) return {};
7923
7948
  return Object.fromEntries(
7924
- flattenConfig(config).map(([key, value]) => [
7925
- `KODY_CFG_${key}`,
7926
- value
7927
- ])
7949
+ flattenConfig(config).map(([key, value]) => [`KODY_CFG_${key}`, value])
7928
7950
  );
7929
7951
  }
7930
7952
  function flattenConfig(config, prefix = "") {
@@ -7937,9 +7959,7 @@ function flattenConfig(config, prefix = "") {
7937
7959
  } else if (Array.isArray(value)) {
7938
7960
  entries.push([key, JSON.stringify(value)]);
7939
7961
  } else if (typeof value === "object") {
7940
- entries.push(
7941
- ...flattenConfig(value, key)
7942
- );
7962
+ entries.push(...flattenConfig(value, key));
7943
7963
  }
7944
7964
  }
7945
7965
  return entries;
@@ -13619,9 +13639,7 @@ import { randomUUID } from "crypto";
13619
13639
  function assertLoopDispatchesSucceeded(results) {
13620
13640
  const failed = results.filter((result) => result.status === "failed");
13621
13641
  if (failed.length === 0) return;
13622
- throw new Error(
13623
- `Loop dispatch failed: ${failed.map((result) => `${result.loopId}: ${result.reason}`).join("; ")}`
13624
- );
13642
+ throw new Error(`Loop dispatch failed: ${failed.map((result) => `${result.loopId}: ${result.reason}`).join("; ")}`);
13625
13643
  }
13626
13644
  async function dispatchLoopsWith(input) {
13627
13645
  const results = [];
@@ -15668,10 +15686,10 @@ var init_loadQaContext = __esm({
15668
15686
  });
15669
15687
 
15670
15688
  // src/scripts/loadSimpleCapability.ts
15689
+ import { randomUUID as randomUUID2 } from "crypto";
15671
15690
  import * as fs43 from "fs";
15672
- import * as path40 from "path";
15673
15691
  import * as os6 from "os";
15674
- import { randomUUID as randomUUID2 } from "crypto";
15692
+ import * as path40 from "path";
15675
15693
  function registerCapabilitySubagents(profile, toolRoot, toolFiles) {
15676
15694
  const subagentFiles = toolFiles.flatMap((file) => {
15677
15695
  const match = /^agents\/([a-z][a-z0-9-]{0,63})\.md$/.exec(file);
@@ -15770,6 +15788,13 @@ var init_loadSimpleCapability = __esm({
15770
15788
  if (ctx.data.capabilityExecution === "agent") {
15771
15789
  registerCapabilitySubagents(profile, toolRoot, toolFiles);
15772
15790
  }
15791
+ const requiredSubagents = capability.contract?.requiredSubagents ?? [];
15792
+ for (const required2 of requiredSubagents) {
15793
+ if (!profile.claudeCode.subagents.includes(required2)) {
15794
+ throw new Error(`Capability "${slug}" required specialist ${required2} is not available`);
15795
+ }
15796
+ }
15797
+ if (requiredSubagents.length > 0) ctx.data.requiredSubagents = requiredSubagents;
15773
15798
  if (capability.contract?.execution === "script") {
15774
15799
  ctx.data.capabilityScriptPath = path40.join(capability.dir, "tools", "run.sh");
15775
15800
  ctx.data.capabilitySecretNames = capability.contract.secrets ?? [];
@@ -15827,7 +15852,14 @@ var init_loadSimpleCapability = __esm({
15827
15852
  outputPath,
15828
15853
  "The Engine reads this file as the authoritative result. Do not write markdown to it."
15829
15854
  ] : []
15830
- ] : ["Return one JSON value."]
15855
+ ] : ["Return one JSON value."],
15856
+ ...requiredSubagents.length > 0 ? [
15857
+ "",
15858
+ "## Required specialists",
15859
+ "",
15860
+ `You must invoke each of these private specialists before returning: ${requiredSubagents.join(", ")}.`,
15861
+ "The Engine verifies the actual Agent calls and blocks the capability if any are skipped."
15862
+ ] : []
15831
15863
  ].join("\n");
15832
15864
  };
15833
15865
  }
@@ -16775,6 +16807,9 @@ var init_parseReproOutput = __esm({
16775
16807
 
16776
16808
  // src/scripts/parseSimpleCapabilityOutput.ts
16777
16809
  import * as fs45 from "fs";
16810
+ function stringList2(value) {
16811
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
16812
+ }
16778
16813
  function readOutputFile(outputPath) {
16779
16814
  if (!outputPath || !fs45.existsSync(outputPath)) return { found: false };
16780
16815
  try {
@@ -16822,6 +16857,28 @@ var init_parseSimpleCapabilityOutput = __esm({
16822
16857
  init_capability_contract_validation();
16823
16858
  init_capabilityResult();
16824
16859
  parseSimpleCapabilityOutput = async (ctx, _profile, agentResult) => {
16860
+ const requiredSubagents = stringList2(ctx.data.requiredSubagents);
16861
+ const invokedSubagents = new Set(agentResult?.invokedSubagents ?? []);
16862
+ const missingSubagents = requiredSubagents.filter((name) => !invokedSubagents.has(name));
16863
+ if (missingSubagents.length > 0) {
16864
+ const label = missingSubagents.length === 1 ? "specialist was" : "specialists were";
16865
+ const reason2 = `Required ${label} not invoked: ${missingSubagents.join(", ")}`;
16866
+ ctx.output.exitCode = 64;
16867
+ ctx.output.reason = reason2;
16868
+ ctx.data.capabilityOutput = { status: "blocked", reason: reason2, summary: reason2 };
16869
+ ctx.data.capabilityResults = [
16870
+ {
16871
+ version: 1,
16872
+ status: "blocked",
16873
+ summary: reason2,
16874
+ facts: {},
16875
+ artifacts: [],
16876
+ missingEvidence: [],
16877
+ blockers: [reason2]
16878
+ }
16879
+ ];
16880
+ return;
16881
+ }
16825
16882
  const outputPath = typeof ctx.data.capabilityOutputPath === "string" ? ctx.data.capabilityOutputPath : void 0;
16826
16883
  const fileOutput = readOutputFile(outputPath);
16827
16884
  const output = fileOutput.found ? fileOutput.value : Object.hasOwn(ctx.data, "capabilityScriptOutput") ? ctx.data.capabilityScriptOutput : parseOutput(agentResult?.finalText);
@@ -21076,9 +21133,7 @@ async function runImplementation(profileName, input) {
21076
21133
  profile.inputs,
21077
21134
  args,
21078
21135
  profile.canonicalContract.capabilityId,
21079
- Object.keys(
21080
- profile.canonicalContract.inputSchema.properties ?? {}
21081
- )
21136
+ Object.keys(profile.canonicalContract.inputSchema.properties ?? {})
21082
21137
  )
21083
21138
  );
21084
21139
  }
@@ -22560,7 +22615,8 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
22560
22615
  }
22561
22616
  const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
22562
22617
  if (!transition) {
22563
- const reason = `workflow step ${step.id} has no available connection`;
22618
+ const exhausted = exhaustedWorkflowTransitions(step, chainData, state.transitionCounts);
22619
+ const reason = exhausted.length > 0 ? `workflow step ${step.id} reached iteration limit: ${exhausted.join(", ")}` : `workflow step ${step.id} has no available connection`;
22564
22620
  state.status = "blocked";
22565
22621
  state.blocker = reason;
22566
22622
  await checkpoint?.(state);
@@ -22636,6 +22692,15 @@ function selectWorkflowTransition(step, data, counts) {
22636
22692
  }
22637
22693
  return fallback;
22638
22694
  }
22695
+ function exhaustedWorkflowTransitions(step, data, counts) {
22696
+ return (step.next ?? []).flatMap((transition) => {
22697
+ if (transition.maxIterations === void 0) return [];
22698
+ const key = `${step.id}->${transition.to}`;
22699
+ if ((counts[key] ?? 0) < transition.maxIterations) return [];
22700
+ const matches = transition.default === true || !transition.when || conditionMatches(transition.when, workflowConditionContext(data));
22701
+ return matches ? [`${key} (${transition.maxIterations})`] : [];
22702
+ });
22703
+ }
22639
22704
  function workflowResultConditionPaths(transitions) {
22640
22705
  return transitions.flatMap(
22641
22706
  (transition) => Object.keys(transition.when ?? {}).filter((path53) => path53.startsWith("result."))
@@ -24840,8 +24905,13 @@ Exit codes (inherited from kody run):
24840
24905
  2 verify failed (no PR opened \u2014 branch pushed for inspection)
24841
24906
  3 no commits to ship
24842
24907
  4 PR creation failed
24908
+ 64 capability or workflow blocked by an invalid or exhausted route
24909
+ 124 execution timed out
24843
24910
  99 wrapper crashed
24844
24911
  `;
24912
+ function normalizeJobExitCode(exitCode) {
24913
+ return Number.isInteger(exitCode) && exitCode >= 0 && exitCode <= 255 ? exitCode : 99;
24914
+ }
24845
24915
  function parseCiArgs(argv) {
24846
24916
  const result = { errors: [] };
24847
24917
  for (let i = 0; i < argv.length; i++) {
@@ -25332,7 +25402,9 @@ async function runCi(argv) {
25332
25402
  }
25333
25403
  );
25334
25404
  const ec = result.exitCode;
25335
- return ec === 0 || ec === 1 || ec === 2 ? ec : 99;
25405
+ if (ec !== 0 && result.reason) process.stderr.write(`[kody] ${result.reason}
25406
+ `);
25407
+ return normalizeJobExitCode(ec);
25336
25408
  }
25337
25409
  if (!args.issueNumber && !autoFallback && runRequestFanOut) {
25338
25410
  return runScheduledFanOut(cwd, args, { force: runRequestFanOutForce });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.509",
3
+ "version": "0.4.511",
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",