@kody-ade/kody-engine 0.4.508 → 0.4.510

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 +94 -7
  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.508",
18
+ version: "0.4.510",
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,17 @@ 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(
1973
+ (name) => typeof name === "string" && /^[a-z][a-z0-9-]{0,63}$/.test(name)
1974
+ ) ? [...new Set(parsed.requiredSubagents)] : null;
1975
+ if (requiredSubagents === null) {
1976
+ throw new Error("contract.json requiredSubagents must contain valid specialist names");
1977
+ }
1978
+ if (requiredSubagents && parsed.execution !== "agent") {
1979
+ throw new Error('contract.json requiredSubagents are supported only when execution is "agent"');
1980
+ }
1962
1981
  const unsupported = Object.keys(parsed).filter(
1963
- (key) => key !== "execution" && key !== "secrets" && key !== "timeoutMs" && key !== "input" && key !== "output"
1982
+ (key) => key !== "execution" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
1964
1983
  );
1965
1984
  if (unsupported.length > 0) {
1966
1985
  throw new Error(`contract.json contains unsupported fields: ${unsupported.join(", ")}`);
@@ -1969,6 +1988,7 @@ function parseCapabilityContract(raw) {
1969
1988
  ...parsed.execution ? { execution: parsed.execution } : {},
1970
1989
  ...secrets ? { secrets } : {},
1971
1990
  ...timeoutMs !== void 0 ? { timeoutMs } : {},
1991
+ ...requiredSubagents ? { requiredSubagents } : {},
1972
1992
  input: parsed.input,
1973
1993
  output: parsed.output
1974
1994
  };
@@ -3717,6 +3737,8 @@ async function runAgent(opts) {
3717
3737
  let messageCount = 0;
3718
3738
  let finalText = "";
3719
3739
  let getSubmitted;
3740
+ const invokedSubagents = /* @__PURE__ */ new Set();
3741
+ const subagentInvocationHook = createSubagentInvocationHook(invokedSubagents);
3720
3742
  for (let attempt = 0; ; attempt++) {
3721
3743
  let ndjsonWriteFailed = false;
3722
3744
  let ndjsonWriteError;
@@ -3751,6 +3773,12 @@ async function runAgent(opts) {
3751
3773
  matcher: "Agent",
3752
3774
  hooks: [enforceSubagentModelInheritance]
3753
3775
  }
3776
+ ],
3777
+ PostToolUse: [
3778
+ {
3779
+ matcher: "Agent",
3780
+ hooks: [subagentInvocationHook]
3781
+ }
3754
3782
  ]
3755
3783
  }
3756
3784
  };
@@ -4072,7 +4100,8 @@ async function runAgent(opts) {
4072
4100
  durationMs: Date.now() - startedAt,
4073
4101
  tokens,
4074
4102
  costUsd,
4075
- messageCount
4103
+ messageCount,
4104
+ invokedSubagents: [...invokedSubagents]
4076
4105
  };
4077
4106
  }
4078
4107
  var DEFAULT_ALLOWED_TOOLS, DEFAULT_TURN_TIMEOUT_MS, MAX_CONNECTION_RETRIES, CONNECTION_RETRY_BASE_MS, MUTATING_FILE_TOOLS, BASH_WRITE_VERB, AGENT_KEEP_SECRETS;
@@ -15770,6 +15799,13 @@ var init_loadSimpleCapability = __esm({
15770
15799
  if (ctx.data.capabilityExecution === "agent") {
15771
15800
  registerCapabilitySubagents(profile, toolRoot, toolFiles);
15772
15801
  }
15802
+ const requiredSubagents = capability.contract?.requiredSubagents ?? [];
15803
+ for (const required2 of requiredSubagents) {
15804
+ if (!profile.claudeCode.subagents.includes(required2)) {
15805
+ throw new Error(`Capability "${slug}" required specialist ${required2} is not available`);
15806
+ }
15807
+ }
15808
+ if (requiredSubagents.length > 0) ctx.data.requiredSubagents = requiredSubagents;
15773
15809
  if (capability.contract?.execution === "script") {
15774
15810
  ctx.data.capabilityScriptPath = path40.join(capability.dir, "tools", "run.sh");
15775
15811
  ctx.data.capabilitySecretNames = capability.contract.secrets ?? [];
@@ -15827,7 +15863,14 @@ var init_loadSimpleCapability = __esm({
15827
15863
  outputPath,
15828
15864
  "The Engine reads this file as the authoritative result. Do not write markdown to it."
15829
15865
  ] : []
15830
- ] : ["Return one JSON value."]
15866
+ ] : ["Return one JSON value."],
15867
+ ...requiredSubagents.length > 0 ? [
15868
+ "",
15869
+ "## Required specialists",
15870
+ "",
15871
+ `You must invoke each of these private specialists before returning: ${requiredSubagents.join(", ")}.`,
15872
+ "The Engine verifies the actual Agent calls and blocks the capability if any are skipped."
15873
+ ] : []
15831
15874
  ].join("\n");
15832
15875
  };
15833
15876
  }
@@ -16775,6 +16818,9 @@ var init_parseReproOutput = __esm({
16775
16818
 
16776
16819
  // src/scripts/parseSimpleCapabilityOutput.ts
16777
16820
  import * as fs45 from "fs";
16821
+ function stringList2(value) {
16822
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
16823
+ }
16778
16824
  function readOutputFile(outputPath) {
16779
16825
  if (!outputPath || !fs45.existsSync(outputPath)) return { found: false };
16780
16826
  try {
@@ -16822,6 +16868,28 @@ var init_parseSimpleCapabilityOutput = __esm({
16822
16868
  init_capability_contract_validation();
16823
16869
  init_capabilityResult();
16824
16870
  parseSimpleCapabilityOutput = async (ctx, _profile, agentResult) => {
16871
+ const requiredSubagents = stringList2(ctx.data.requiredSubagents);
16872
+ const invokedSubagents = new Set(agentResult?.invokedSubagents ?? []);
16873
+ const missingSubagents = requiredSubagents.filter((name) => !invokedSubagents.has(name));
16874
+ if (missingSubagents.length > 0) {
16875
+ const label = missingSubagents.length === 1 ? "specialist was" : "specialists were";
16876
+ const reason2 = `Required ${label} not invoked: ${missingSubagents.join(", ")}`;
16877
+ ctx.output.exitCode = 64;
16878
+ ctx.output.reason = reason2;
16879
+ ctx.data.capabilityOutput = { status: "blocked", reason: reason2, summary: reason2 };
16880
+ ctx.data.capabilityResults = [
16881
+ {
16882
+ version: 1,
16883
+ status: "blocked",
16884
+ summary: reason2,
16885
+ facts: {},
16886
+ artifacts: [],
16887
+ missingEvidence: [],
16888
+ blockers: [reason2]
16889
+ }
16890
+ ];
16891
+ return;
16892
+ }
16825
16893
  const outputPath = typeof ctx.data.capabilityOutputPath === "string" ? ctx.data.capabilityOutputPath : void 0;
16826
16894
  const fileOutput = readOutputFile(outputPath);
16827
16895
  const output = fileOutput.found ? fileOutput.value : Object.hasOwn(ctx.data, "capabilityScriptOutput") ? ctx.data.capabilityScriptOutput : parseOutput(agentResult?.finalText);
@@ -22560,7 +22628,8 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
22560
22628
  }
22561
22629
  const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
22562
22630
  if (!transition) {
22563
- const reason = `workflow step ${step.id} has no available connection`;
22631
+ const exhausted = exhaustedWorkflowTransitions(step, chainData, state.transitionCounts);
22632
+ const reason = exhausted.length > 0 ? `workflow step ${step.id} reached iteration limit: ${exhausted.join(", ")}` : `workflow step ${step.id} has no available connection`;
22564
22633
  state.status = "blocked";
22565
22634
  state.blocker = reason;
22566
22635
  await checkpoint?.(state);
@@ -22636,6 +22705,15 @@ function selectWorkflowTransition(step, data, counts) {
22636
22705
  }
22637
22706
  return fallback;
22638
22707
  }
22708
+ function exhaustedWorkflowTransitions(step, data, counts) {
22709
+ return (step.next ?? []).flatMap((transition) => {
22710
+ if (transition.maxIterations === void 0) return [];
22711
+ const key = `${step.id}->${transition.to}`;
22712
+ if ((counts[key] ?? 0) < transition.maxIterations) return [];
22713
+ const matches = transition.default === true || !transition.when || conditionMatches(transition.when, workflowConditionContext(data));
22714
+ return matches ? [`${key} (${transition.maxIterations})`] : [];
22715
+ });
22716
+ }
22639
22717
  function workflowResultConditionPaths(transitions) {
22640
22718
  return transitions.flatMap(
22641
22719
  (transition) => Object.keys(transition.when ?? {}).filter((path53) => path53.startsWith("result."))
@@ -24840,8 +24918,13 @@ Exit codes (inherited from kody run):
24840
24918
  2 verify failed (no PR opened \u2014 branch pushed for inspection)
24841
24919
  3 no commits to ship
24842
24920
  4 PR creation failed
24921
+ 64 capability or workflow blocked by an invalid or exhausted route
24922
+ 124 execution timed out
24843
24923
  99 wrapper crashed
24844
24924
  `;
24925
+ function normalizeJobExitCode(exitCode) {
24926
+ return Number.isInteger(exitCode) && exitCode >= 0 && exitCode <= 255 ? exitCode : 99;
24927
+ }
24845
24928
  function parseCiArgs(argv) {
24846
24929
  const result = { errors: [] };
24847
24930
  for (let i = 0; i < argv.length; i++) {
@@ -25332,7 +25415,9 @@ async function runCi(argv) {
25332
25415
  }
25333
25416
  );
25334
25417
  const ec = result.exitCode;
25335
- return ec === 0 || ec === 1 || ec === 2 ? ec : 99;
25418
+ if (ec !== 0 && result.reason) process.stderr.write(`[kody] ${result.reason}
25419
+ `);
25420
+ return normalizeJobExitCode(ec);
25336
25421
  }
25337
25422
  if (!args.issueNumber && !autoFallback && runRequestFanOut) {
25338
25423
  return runScheduledFanOut(cwd, args, { force: runRequestFanOutForce });
@@ -28433,7 +28518,9 @@ function envRunRequest(env = process.env) {
28433
28518
  const { target } = parsed.request;
28434
28519
  if (target.type === "chat") return { ...result, command: "chat", chatArgv: [] };
28435
28520
  if (target.type === "issue") return { ...result, command: "ci", ciArgv: ["--issue", String(target.id)] };
28436
- if (target.type === "goal" || target.type === "workflow") return { ...result, command: "ci", ciArgv: [] };
28521
+ if (target.type === "goal" || target.type === "loop" || target.type === "workflow") {
28522
+ return { ...result, command: "ci", ciArgv: [] };
28523
+ }
28437
28524
  return { ...result, errors: [`unsupported runRequest target: ${target.type ?? "unknown"}`] };
28438
28525
  }
28439
28526
  var HELP_TEXT = `kody-engine \u2014 single-session autonomous engineer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.508",
3
+ "version": "0.4.510",
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",