@kody-ade/kody-engine 0.4.158 → 0.4.160

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
@@ -1061,7 +1061,7 @@ var init_loadPriorArt = __esm({
1061
1061
  // package.json
1062
1062
  var package_default = {
1063
1063
  name: "@kody-ade/kody-engine",
1064
- version: "0.4.158",
1064
+ version: "0.4.160",
1065
1065
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
1066
1066
  license: "MIT",
1067
1067
  type: "module",
@@ -2177,33 +2177,28 @@ function attachmentsDir(cwd, sessionId) {
2177
2177
  return path10.join(cwd, ".kody", "tmp", "attachments", sessionId);
2178
2178
  }
2179
2179
  function prepareAttachments(turns, cwd, sessionId) {
2180
- const lastUserIdx = (() => {
2181
- for (let i = turns.length - 1; i >= 0; i--) {
2182
- if (turns[i].role === "user") return i;
2183
- }
2184
- return -1;
2185
- })();
2186
2180
  const imagePaths = [];
2187
2181
  let imageCounter = 0;
2188
- const rewritten = turns.map((turn, idx) => {
2189
- if (!turn.content.includes("base64,")) return turn;
2190
- const isLastUser = idx === lastUserIdx;
2182
+ let dirEnsured = false;
2183
+ const dir = attachmentsDir(cwd, sessionId);
2184
+ const rewritten = turns.map((turn) => {
2185
+ if (turn.role !== "user" || !turn.content.includes("base64,")) return turn;
2191
2186
  const newContent = turn.content.replace(
2192
2187
  INLINE_ATTACHMENT_RE,
2193
2188
  (_match, label, mime, data) => {
2194
2189
  const name = (label ?? "").trim() || "attachment";
2195
2190
  const isImage = mime.toLowerCase().startsWith("image/");
2196
- if (!isLastUser || !isImage) {
2197
- return `[${isImage ? "Image" : "File"}: ${name}${isLastUser ? "" : " \u2014 omitted from history"}]`;
2198
- }
2191
+ if (!isImage) return `[File: ${name}]`;
2199
2192
  try {
2200
- const dir = attachmentsDir(cwd, sessionId);
2201
- fs10.mkdirSync(dir, { recursive: true });
2193
+ if (!dirEnsured) {
2194
+ fs10.mkdirSync(dir, { recursive: true });
2195
+ dirEnsured = true;
2196
+ }
2202
2197
  const filePath = path10.join(dir, `${imageCounter}.${extFor(mime)}`);
2203
2198
  fs10.writeFileSync(filePath, Buffer.from(data, "base64"));
2204
2199
  imageCounter += 1;
2205
2200
  imagePaths.push(filePath);
2206
- return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it before answering.]`;
2201
+ return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it.]`;
2207
2202
  } catch {
2208
2203
  return `[Image: ${name} (could not be materialised)]`;
2209
2204
  }
@@ -6936,17 +6931,14 @@ var dispatchClassified = async (ctx) => {
6936
6931
  if (!classification || !VALID_CLASSES2.has(classification)) return;
6937
6932
  const action = ctx.data.action;
6938
6933
  if (!action) return;
6939
- const baseArg = typeof ctx.args.base === "string" && ctx.args.base.length > 0 ? ` --base ${ctx.args.base}` : "";
6940
- const dispatchLine = `@kody ${classification}${baseArg}`;
6934
+ const base = typeof ctx.args.base === "string" && ctx.args.base.length > 0 ? ctx.args.base : void 0;
6941
6935
  const auditLine = ctx.data.classificationAudit ?? `\u{1F50E} kody classified as \`${classification}\``;
6942
6936
  const state = ctx.data.taskState ?? emptyState();
6943
6937
  const nextState = reduce(state, "classify", action, void 0);
6944
6938
  const stateBody = renderStateComment(nextState);
6945
6939
  ctx.data.taskState = nextState;
6946
6940
  ctx.data.taskStateRendered = stateBody;
6947
- const body = `${dispatchLine}
6948
-
6949
- ${auditLine}
6941
+ const body = `${auditLine}
6950
6942
 
6951
6943
  ${stateBody}`;
6952
6944
  try {
@@ -6957,17 +6949,16 @@ ${stateBody}`;
6957
6949
  });
6958
6950
  } catch (err) {
6959
6951
  process.stderr.write(
6960
- `[kody dispatchClassified] failed to dispatch ${dispatchLine}: ${err instanceof Error ? err.message : String(err)}
6952
+ `[kody dispatchClassified] failed to post state comment for #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
6961
6953
  `
6962
6954
  );
6963
- ctx.data.action = failedAction3("dispatch post failed");
6964
- ctx.output.exitCode = 1;
6965
- ctx.output.reason = "classify: dispatch failed";
6966
6955
  }
6956
+ const cliArgs = { issue: issueNumber };
6957
+ if (base && getProfileInputs(classification)?.some((i) => i.name === "base")) {
6958
+ cliArgs.base = base;
6959
+ }
6960
+ ctx.output.nextDispatch = { executable: classification, cliArgs };
6967
6961
  };
6968
- function failedAction3(reason) {
6969
- return { type: "CLASSIFY_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
6970
- }
6971
6962
 
6972
6963
  // src/scripts/dispatchJobFileTicks.ts
6973
6964
  import * as fs29 from "fs";
@@ -9577,7 +9568,7 @@ function qaAction2(verdict, payload) {
9577
9568
  const type = verdict === "PASS" ? "QA_PASS" : verdict === "CONCERNS" ? "QA_CONCERNS" : verdict === "FAIL" ? "QA_FAIL" : "QA_COMPLETED";
9578
9569
  return { type, payload: { verdict, ...payload }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
9579
9570
  }
9580
- function failedAction4(reason) {
9571
+ function failedAction3(reason) {
9581
9572
  return { type: "QA_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
9582
9573
  }
9583
9574
  function slugifyScope(scope) {
@@ -9613,7 +9604,7 @@ var openQaIssue = async (ctx, _profile, agentResult) => {
9613
9604
  `);
9614
9605
  ctx.output.exitCode = 1;
9615
9606
  ctx.output.reason = reason;
9616
- ctx.data.action = failedAction4(reason);
9607
+ ctx.data.action = failedAction3(reason);
9617
9608
  return;
9618
9609
  }
9619
9610
  const reportBody = agentResult.finalText.trim();
@@ -9621,7 +9612,7 @@ var openQaIssue = async (ctx, _profile, agentResult) => {
9621
9612
  process.stderr.write("qa-engineer: agent produced no report body\n");
9622
9613
  ctx.output.exitCode = 1;
9623
9614
  ctx.output.reason = "empty report body";
9624
- ctx.data.action = failedAction4("empty report body");
9615
+ ctx.data.action = failedAction3("empty report body");
9625
9616
  return;
9626
9617
  }
9627
9618
  const verdict = detectVerdict(reportBody);
@@ -9635,7 +9626,7 @@ var openQaIssue = async (ctx, _profile, agentResult) => {
9635
9626
  const msg = err instanceof Error ? err.message : String(err);
9636
9627
  ctx.output.exitCode = 4;
9637
9628
  ctx.output.reason = `failed to comment on issue #${existingIssue}: ${msg}`;
9638
- ctx.data.action = failedAction4(ctx.output.reason);
9629
+ ctx.data.action = failedAction3(ctx.output.reason);
9639
9630
  return;
9640
9631
  }
9641
9632
  process.stdout.write(
@@ -9657,7 +9648,7 @@ QA_REPORT_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.gith
9657
9648
  const msg = err instanceof Error ? err.message : String(err);
9658
9649
  ctx.output.exitCode = 4;
9659
9650
  ctx.output.reason = `failed to open QA issue: ${truncate2(msg, 1e3)}`;
9660
- ctx.data.action = failedAction4(ctx.output.reason);
9651
+ ctx.data.action = failedAction3(ctx.output.reason);
9661
9652
  return;
9662
9653
  }
9663
9654
  process.stdout.write(`
@@ -10969,7 +10960,7 @@ var recordClassification = async (ctx) => {
10969
10960
  reason = parsed?.reason ?? null;
10970
10961
  }
10971
10962
  if (!classification) {
10972
- ctx.data.action = failedAction5("classification missing or invalid");
10963
+ ctx.data.action = failedAction4("classification missing or invalid");
10973
10964
  tryAuditComment(
10974
10965
  issueNumber,
10975
10966
  "\u26A0\uFE0F kody classifier could not decide \u2014 please re-run with an explicit `@kody <type>`.",
@@ -11010,7 +11001,7 @@ function tryAuditComment(issueNumber, body, cwd) {
11010
11001
  function makeAction3(type, payload) {
11011
11002
  return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
11012
11003
  }
11013
- function failedAction5(reason) {
11004
+ function failedAction4(reason) {
11014
11005
  return { type: "CLASSIFY_FAILED", payload: { reason }, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
11015
11006
  }
11016
11007
 
@@ -13490,7 +13481,8 @@ async function runExecutable(profileName, input) {
13490
13481
  return finishAndEnd({
13491
13482
  exitCode: ctx.output.exitCode ?? 0,
13492
13483
  prUrl: ctx.output.prUrl,
13493
- reason: ctx.output.reason
13484
+ reason: ctx.output.reason,
13485
+ nextDispatch: ctx.output.nextDispatch
13494
13486
  });
13495
13487
  } finally {
13496
13488
  clearStampedLifecycleLabels(profile, ctx);
@@ -14389,13 +14381,33 @@ ${CI_HELP}`);
14389
14381
  `);
14390
14382
  try {
14391
14383
  const config = earlyConfig ?? loadConfig(cwd);
14392
- const result = await runExecutable(dispatch2.executable, {
14384
+ let result = await runExecutable(dispatch2.executable, {
14393
14385
  cliArgs: dispatch2.cliArgs,
14394
14386
  cwd,
14395
14387
  config,
14396
14388
  verbose: args.verbose,
14397
14389
  quiet: args.quiet
14398
14390
  });
14391
+ const MAX_CHAIN_HOPS = 4;
14392
+ for (let hops = 1; result.nextDispatch && hops <= MAX_CHAIN_HOPS; hops++) {
14393
+ const next = result.nextDispatch;
14394
+ process.stdout.write(`\u2192 kody: in-process hand-off \u2192 ${next.executable} (hop ${hops}/${MAX_CHAIN_HOPS})
14395
+
14396
+ `);
14397
+ result = await runExecutable(next.executable, {
14398
+ cliArgs: next.cliArgs,
14399
+ cwd,
14400
+ config,
14401
+ verbose: args.verbose,
14402
+ quiet: args.quiet
14403
+ });
14404
+ }
14405
+ if (result.nextDispatch) {
14406
+ process.stderr.write(
14407
+ `[kody] in-process hand-off cap (${MAX_CHAIN_HOPS}) reached; not running ${result.nextDispatch.executable}
14408
+ `
14409
+ );
14410
+ }
14399
14411
  if (result.exitCode !== 0 && result.exitCode !== 1 && result.exitCode !== 2) {
14400
14412
  postFailureTail(issueNumber, cwd, result.reason || `exit ${result.exitCode}`);
14401
14413
  }
@@ -347,6 +347,14 @@ export interface Context {
347
347
  exitCode: number
348
348
  prUrl?: string
349
349
  reason?: string
350
+ /**
351
+ * In-process hand-off to the next stage. A stage (e.g. `classify`) sets
352
+ * this so the orchestrator runs the chosen sub-orchestrator
353
+ * (feature/bug/spec/chore) in the same process — instead of posting an
354
+ * `@kody <next>` comment, which is silently ignored when Kody comments as
355
+ * a GitHub App (bot author), stalling the pipeline at classify.
356
+ */
357
+ nextDispatch?: { executable: string; cliArgs: Record<string, unknown> }
350
358
  }
351
359
  /**
352
360
  * If a preflight script sets this to true, the executor skips the agent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.158",
3
+ "version": "0.4.160",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",