@kody-ade/kody-engine 0.4.445 → 0.4.447

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.445",
18
+ version: "0.4.447",
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",
@@ -1795,11 +1795,11 @@ function parseWorkflowTransitions(value) {
1795
1795
  const transitions = rawTransitions.map((raw) => {
1796
1796
  if (typeof raw === "string") {
1797
1797
  const to2 = raw.trim();
1798
- return isSafeStepId(to2) ? { to: to2 } : null;
1798
+ return to2 === "$end" || isSafeStepId(to2) ? { to: to2 } : null;
1799
1799
  }
1800
1800
  if (!isPlainObject(raw)) return null;
1801
1801
  const to = stringField(raw.to);
1802
- if (!to || !isSafeStepId(to)) return null;
1802
+ if (!to || to !== "$end" && !isSafeStepId(to)) return null;
1803
1803
  const maxIterations = typeof raw.maxIterations === "number" && Number.isInteger(raw.maxIterations) && raw.maxIterations > 0 ? raw.maxIterations : void 0;
1804
1804
  return {
1805
1805
  to,
@@ -1862,11 +1862,11 @@ function definitionsRoot(cwd = process.cwd()) {
1862
1862
  const override = process.env.KODY_DEFINITIONS_ROOT?.trim();
1863
1863
  const overrideCwd = process.env.KODY_DEFINITIONS_ROOT_CWD?.trim();
1864
1864
  if (override && overrideCwd && path6.resolve(cwd) === path6.resolve(overrideCwd)) {
1865
- return path6.resolve(override);
1865
+ return storeCatalogRoot(path6.resolve(override));
1866
1866
  }
1867
1867
  const hydrated = path6.join(cwd, ".kody-engine", "definitions");
1868
1868
  if (fs5.existsSync(hydrated)) return hydrated;
1869
- return override ? path6.resolve(override) : hydrated;
1869
+ return override ? storeCatalogRoot(path6.resolve(override)) : hydrated;
1870
1870
  }
1871
1871
  function hasExplicitDefinitionsRoot(cwd = process.cwd(), env = process.env) {
1872
1872
  const root = env.KODY_DEFINITIONS_ROOT?.trim();
@@ -1874,13 +1874,36 @@ function hasExplicitDefinitionsRoot(cwd = process.cwd(), env = process.env) {
1874
1874
  return Boolean(root && rootCwd && path6.resolve(cwd) === path6.resolve(rootCwd));
1875
1875
  }
1876
1876
  function capabilitiesRoot(cwd = process.cwd()) {
1877
- return path6.join(definitionsRoot(cwd), "capabilities");
1877
+ return storeAssetRoot(cwd, "capabilities") ?? path6.join(definitionsRoot(cwd), "capabilities");
1878
1878
  }
1879
1879
  function implementationsRoot(cwd = process.cwd()) {
1880
1880
  return path6.join(definitionsRoot(cwd), "implementations");
1881
1881
  }
1882
1882
  function agentsRoot(cwd = process.cwd()) {
1883
- return path6.join(definitionsRoot(cwd), "agents");
1883
+ return storeAssetRoot(cwd, "agent") ?? path6.join(definitionsRoot(cwd), "agents");
1884
+ }
1885
+ function storeCatalogRoot(root) {
1886
+ const manifest = readStoreManifest(root);
1887
+ const roots = ["capabilities", "workflows", "loops"].map((kind) => manifest?.assetRoots?.[kind]).filter((value) => typeof value === "string" && Boolean(value.trim())).map((value) => path6.dirname(value));
1888
+ return roots.length === 3 && new Set(roots).size === 1 ? path6.join(root, roots[0]) : root;
1889
+ }
1890
+ function storeAssetRoot(cwd, kind) {
1891
+ const override = process.env.KODY_DEFINITIONS_ROOT?.trim();
1892
+ if (!override) return null;
1893
+ const overrideCwd = process.env.KODY_DEFINITIONS_ROOT_CWD?.trim();
1894
+ if (overrideCwd && path6.resolve(cwd) !== path6.resolve(overrideCwd)) return null;
1895
+ const root = path6.resolve(override);
1896
+ const configured = readStoreManifest(root)?.assetRoots?.[kind];
1897
+ return typeof configured === "string" && configured.trim() ? path6.join(root, configured) : null;
1898
+ }
1899
+ function readStoreManifest(root) {
1900
+ const file = path6.join(root, "kody-store.json");
1901
+ if (!fs5.existsSync(file)) return null;
1902
+ try {
1903
+ return JSON.parse(fs5.readFileSync(file, "utf8"));
1904
+ } catch {
1905
+ return null;
1906
+ }
1884
1907
  }
1885
1908
  var init_definition_paths = __esm({
1886
1909
  "src/definition-paths.ts"() {
@@ -8959,10 +8982,13 @@ function validateWorkflow(value, options = {}) {
8959
8982
  }
8960
8983
  }
8961
8984
  const target = text(raw.to);
8962
- if (!target || !SAFE_STEP_ID.test(target)) {
8985
+ if (!target || target !== "$end" && !SAFE_STEP_ID.test(target)) {
8963
8986
  issue(issues, "invalid_transition_target", `${base}.to`, "workflow connection must name a valid target step");
8964
8987
  return;
8965
8988
  }
8989
+ if (target === "$end") {
8990
+ return;
8991
+ }
8966
8992
  if (!seen.has(target)) {
8967
8993
  issue(
8968
8994
  issues,
@@ -13798,6 +13824,204 @@ var init_dispatchAgencyLoops = __esm({
13798
13824
  }
13799
13825
  });
13800
13826
 
13827
+ // src/loopDefinitions.ts
13828
+ import * as fs36 from "fs";
13829
+ import * as path34 from "path";
13830
+ function normalizeLoopDefinition(value) {
13831
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
13832
+ const raw = value;
13833
+ if (Object.keys(raw).some((key) => !["id", "trigger", "target", "input", "enabled"].includes(key))) return null;
13834
+ if (typeof raw.id !== "string" || !ID.test(raw.id)) return null;
13835
+ if (typeof raw.enabled !== "boolean") return null;
13836
+ if (!isObject(raw.input) || !isObject(raw.trigger) || !isObject(raw.target)) return null;
13837
+ const targetKind = raw.target.kind;
13838
+ const targetId = raw.target.id;
13839
+ if (targetKind !== "workflow" && targetKind !== "capability" || typeof targetId !== "string" || !ID.test(targetId)) {
13840
+ return null;
13841
+ }
13842
+ const trigger = normalizeTrigger(raw.trigger);
13843
+ if (!trigger) return null;
13844
+ return {
13845
+ id: raw.id,
13846
+ trigger,
13847
+ target: { kind: targetKind, id: targetId },
13848
+ input: raw.input,
13849
+ enabled: raw.enabled
13850
+ };
13851
+ }
13852
+ function readLoopDefinition(cwd, id) {
13853
+ if (!ID.test(id)) return null;
13854
+ const roots = [path34.join(cwd, ".kody-engine", "runtime"), definitionsRoot(cwd)];
13855
+ for (const root of roots) {
13856
+ const filePath = path34.join(root, "loops", id, "loop.json");
13857
+ if (!fs36.existsSync(filePath)) continue;
13858
+ try {
13859
+ const loop = normalizeLoopDefinition(JSON.parse(fs36.readFileSync(filePath, "utf8")));
13860
+ if (loop?.id === id) return loop;
13861
+ process.stderr.write(`[kody] invalid simple Loop definition: ${filePath}
13862
+ `);
13863
+ } catch {
13864
+ process.stderr.write(`[kody] unreadable simple Loop definition: ${filePath}
13865
+ `);
13866
+ }
13867
+ }
13868
+ process.stderr.write(
13869
+ `[kody] simple Loop not found: ${id} (${roots.map((root) => path34.join(root, "loops", id, "loop.json")).join(", ")})
13870
+ `
13871
+ );
13872
+ return null;
13873
+ }
13874
+ function listLoopDefinitions(cwd) {
13875
+ const roots = [path34.join(cwd, ".kody-engine", "runtime"), definitionsRoot(cwd)];
13876
+ const byId = /* @__PURE__ */ new Map();
13877
+ for (const root of roots.reverse()) {
13878
+ const loopsDir = path34.join(root, "loops");
13879
+ if (!fs36.existsSync(loopsDir)) continue;
13880
+ for (const id of fs36.readdirSync(loopsDir).sort()) {
13881
+ if (!ID.test(id)) continue;
13882
+ const filePath = path34.join(loopsDir, id, "loop.json");
13883
+ if (!fs36.existsSync(filePath)) continue;
13884
+ try {
13885
+ const loop = normalizeLoopDefinition(JSON.parse(fs36.readFileSync(filePath, "utf8")));
13886
+ if (loop?.id === id) byId.set(id, loop);
13887
+ } catch {
13888
+ process.stderr.write(`[kody] unreadable simple Loop definition: ${filePath}
13889
+ `);
13890
+ }
13891
+ }
13892
+ }
13893
+ return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
13894
+ }
13895
+ function normalizeTrigger(raw) {
13896
+ if (raw.type === "manual" && Object.keys(raw).length === 1) return { type: "manual" };
13897
+ if (raw.type === "schedule" && typeof raw.every === "string" && /^\d+[mhd]$/.test(raw.every)) {
13898
+ if (raw.at === void 0 && Object.keys(raw).every((key) => key === "type" || key === "every")) {
13899
+ return { type: "schedule", every: raw.every };
13900
+ }
13901
+ if (isObject(raw.at) && typeof raw.at.time === "string" && typeof raw.at.timezone === "string" && Object.keys(raw.at).every((key) => key === "time" || key === "timezone") && Object.keys(raw).every((key) => key === "type" || key === "every" || key === "at")) {
13902
+ return { type: "schedule", every: raw.every, at: { time: raw.at.time, timezone: raw.at.timezone } };
13903
+ }
13904
+ }
13905
+ if ((raw.type === "event" || raw.type === "webhook") && typeof raw.event === "string" && raw.event.trim() && Object.keys(raw).every((key) => key === "type" || key === "event")) {
13906
+ return { type: raw.type, event: raw.event.trim() };
13907
+ }
13908
+ if (raw.type === "condition" && typeof raw.expression === "string" && raw.expression.trim() && Object.keys(raw).every((key) => key === "type" || key === "expression")) {
13909
+ return { type: "condition", expression: raw.expression.trim() };
13910
+ }
13911
+ return null;
13912
+ }
13913
+ function isObject(value) {
13914
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13915
+ }
13916
+ var ID;
13917
+ var init_loopDefinitions = __esm({
13918
+ "src/loopDefinitions.ts"() {
13919
+ "use strict";
13920
+ init_definition_paths();
13921
+ ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
13922
+ }
13923
+ });
13924
+
13925
+ // src/scripts/dispatchSimpleLoops.ts
13926
+ import { randomUUID as randomUUID2 } from "crypto";
13927
+ function dueSlot(loop, now) {
13928
+ if (!loop.enabled || loop.trigger.type !== "schedule") return null;
13929
+ const match = /^(\d+)([mhd])$/.exec(loop.trigger.every);
13930
+ if (!match) return null;
13931
+ const amount = Number(match[1]);
13932
+ const unit = match[2];
13933
+ const milliseconds = amount * (unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5);
13934
+ const slot = Math.floor(now.getTime() / milliseconds) * milliseconds;
13935
+ if (!loop.trigger.at) return new Date(slot).toISOString();
13936
+ const parts = new Intl.DateTimeFormat("en-CA", {
13937
+ timeZone: loop.trigger.at.timezone,
13938
+ year: "numeric",
13939
+ month: "2-digit",
13940
+ day: "2-digit",
13941
+ hour: "2-digit",
13942
+ minute: "2-digit",
13943
+ hourCycle: "h23"
13944
+ }).formatToParts(now);
13945
+ const hour = parts.find((part) => part.type === "hour")?.value;
13946
+ const minute = parts.find((part) => part.type === "minute")?.value;
13947
+ const [targetHour, targetMinute] = loop.trigger.at.time.split(":").map(Number);
13948
+ const localMinute = Number(hour) * 60 + Number(minute);
13949
+ const targetLocalMinute = Number(targetHour) * 60 + Number(targetMinute);
13950
+ const windowMinutes = Math.max(1, Math.ceil(Number(process.env.KODY_SCHEDULE_WINDOW_SEC || 300) / 60));
13951
+ if (localMinute < targetLocalMinute || localMinute >= targetLocalMinute + windowMinutes) return null;
13952
+ const year = parts.find((part) => part.type === "year")?.value;
13953
+ const month = parts.find((part) => part.type === "month")?.value;
13954
+ const day = parts.find((part) => part.type === "day")?.value;
13955
+ return `${year}-${month}-${day}T${loop.trigger.at.time}[${loop.trigger.at.timezone}]`;
13956
+ }
13957
+ function loopJob(loop) {
13958
+ const cliArgs = Object.fromEntries(
13959
+ Object.entries(loop.input).map(([key, value]) => [key, typeof value === "string" ? value : JSON.stringify(value)])
13960
+ );
13961
+ return loop.target.kind === "workflow" ? { workflow: loop.target.id, cliArgs, flavor: "scheduled" } : { capability: loop.target.id, cliArgs, flavor: "scheduled" };
13962
+ }
13963
+ function repositoryTenant2(config) {
13964
+ const [envOwner, envRepo] = (process.env.GITHUB_REPOSITORY ?? "").split("/");
13965
+ const owner = config.github?.owner?.trim() || envOwner?.trim();
13966
+ const repo = config.github?.repo?.trim() || envRepo?.trim();
13967
+ return owner && repo ? `${owner}/${repo}` : null;
13968
+ }
13969
+ var dispatchSimpleLoops;
13970
+ var init_dispatchSimpleLoops = __esm({
13971
+ "src/scripts/dispatchSimpleLoops.ts"() {
13972
+ "use strict";
13973
+ init_loopDefinitions();
13974
+ init_job();
13975
+ init_state_backend();
13976
+ dispatchSimpleLoops = async (ctx) => {
13977
+ const tenantId2 = repositoryTenant2(ctx.config);
13978
+ if (!tenantId2) throw new Error("Repository identity is required for Loop dispatch");
13979
+ const now = /* @__PURE__ */ new Date();
13980
+ const due = listLoopDefinitions(ctx.cwd).filter((loop) => dueSlot(loop, now) !== null);
13981
+ const backend = createStateBackendFromEnv();
13982
+ const results = [];
13983
+ for (const loop of due) {
13984
+ const slot = dueSlot(loop, now);
13985
+ if (!slot) continue;
13986
+ const reservationId = `reservation-${randomUUID2()}`;
13987
+ const idempotencyKey = `${loop.id}:${slot}`;
13988
+ const claimed = await backend.reserveAgencyDispatch(tenantId2, {
13989
+ idempotencyKey,
13990
+ loopId: loop.id,
13991
+ decision: { kind: "fire", reason: "local Loop schedule is due", scheduledAt: slot },
13992
+ leaseUntil: new Date(now.getTime() + 6 * 60 * 60 * 1e3).toISOString(),
13993
+ reservationId,
13994
+ correlationId: `corr-${randomUUID2()}`,
13995
+ policyHash: `loop:${loop.id}`,
13996
+ effectivePolicy: { source: "repository" },
13997
+ definitionRefs: [{ kind: "loop", id: loop.id }],
13998
+ maxConcurrentRuns: 1,
13999
+ requiresApproval: false,
14000
+ approvalScopeKind: "loop",
14001
+ approvalScopeId: loop.id,
14002
+ approvalAction: `${loop.target.kind}:${loop.target.id}`,
14003
+ now: now.toISOString()
14004
+ });
14005
+ if (!claimed.acquired) {
14006
+ results.push({ loopId: loop.id, status: "skipped", reason: claimed.reason ?? "already claimed" });
14007
+ continue;
14008
+ }
14009
+ const result = await runJob(loopJob(loop), {
14010
+ cwd: ctx.cwd,
14011
+ config: ctx.config,
14012
+ verbose: ctx.verbose,
14013
+ quiet: ctx.quiet,
14014
+ chain: false
14015
+ });
14016
+ const status = result.exitCode === 0 ? "dispatched" : "failed";
14017
+ await backend.finishAgencyDispatch(tenantId2, idempotencyKey, reservationId, status, (/* @__PURE__ */ new Date()).toISOString());
14018
+ results.push({ loopId: loop.id, status, reason: result.reason ?? status });
14019
+ }
14020
+ ctx.data.simpleLoopDispatchResults = results;
14021
+ };
14022
+ }
14023
+ });
14024
+
13801
14025
  // src/jobIdentity.ts
13802
14026
  function stableJobKey(job) {
13803
14027
  const capability = job.workflow ?? job.capability ?? job.action;
@@ -14818,15 +15042,15 @@ var init_fixFlow = __esm({
14818
15042
  });
14819
15043
 
14820
15044
  // src/workflow-template.ts
14821
- import * as fs36 from "fs";
14822
- import * as path34 from "path";
15045
+ import * as fs37 from "fs";
15046
+ import * as path35 from "path";
14823
15047
  import { fileURLToPath } from "url";
14824
15048
  function loadKodyWorkflowTemplate() {
14825
- const here = path34.dirname(fileURLToPath(import.meta.url));
14826
- const candidates = [path34.resolve(here, "../templates/kody.yml"), path34.resolve(here, "../../templates/kody.yml")];
14827
- const source = candidates.find((candidate) => fs36.existsSync(candidate));
15049
+ const here = path35.dirname(fileURLToPath(import.meta.url));
15050
+ const candidates = [path35.resolve(here, "../templates/kody.yml"), path35.resolve(here, "../../templates/kody.yml")];
15051
+ const source = candidates.find((candidate) => fs37.existsSync(candidate));
14828
15052
  if (!source) throw new Error(`Kody workflow template is missing: ${KODY_WORKFLOW_TEMPLATE_PATH}`);
14829
- return fs36.readFileSync(source, "utf8");
15053
+ return fs37.readFileSync(source, "utf8");
14830
15054
  }
14831
15055
  var KODY_WORKFLOW_TEMPLATE_PATH;
14832
15056
  var init_workflow_template = __esm({
@@ -14838,12 +15062,12 @@ var init_workflow_template = __esm({
14838
15062
 
14839
15063
  // src/scripts/initFlow.ts
14840
15064
  import { execFileSync as execFileSync14 } from "child_process";
14841
- import * as fs37 from "fs";
14842
- import * as path35 from "path";
15065
+ import * as fs38 from "fs";
15066
+ import * as path36 from "path";
14843
15067
  function detectPackageManager(cwd) {
14844
- if (fs37.existsSync(path35.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
14845
- if (fs37.existsSync(path35.join(cwd, "yarn.lock"))) return "yarn";
14846
- if (fs37.existsSync(path35.join(cwd, "bun.lockb"))) return "bun";
15068
+ if (fs38.existsSync(path36.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
15069
+ if (fs38.existsSync(path36.join(cwd, "yarn.lock"))) return "yarn";
15070
+ if (fs38.existsSync(path36.join(cwd, "bun.lockb"))) return "bun";
14847
15071
  return "npm";
14848
15072
  }
14849
15073
  function qualityCommandsFor(pm) {
@@ -14915,22 +15139,22 @@ function performInit(cwd, force) {
14915
15139
  const pm = detectPackageManager(cwd);
14916
15140
  const ownerRepo = detectOwnerRepo(cwd);
14917
15141
  const defaultBranch = defaultBranchFromGit(cwd);
14918
- const configPath = path35.join(cwd, "kody.config.json");
14919
- if (fs37.existsSync(configPath) && !force) {
15142
+ const configPath = path36.join(cwd, "kody.config.json");
15143
+ if (fs38.existsSync(configPath) && !force) {
14920
15144
  skipped.push("kody.config.json");
14921
15145
  } else {
14922
15146
  const cfg = makeConfig(pm, ownerRepo, defaultBranch);
14923
- fs37.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
15147
+ fs38.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
14924
15148
  `);
14925
15149
  wrote.push("kody.config.json");
14926
15150
  }
14927
- const workflowDir = path35.join(cwd, ".github", "workflows");
14928
- const workflowPath = path35.join(workflowDir, "kody.yml");
14929
- if (fs37.existsSync(workflowPath) && !force) {
15151
+ const workflowDir = path36.join(cwd, ".github", "workflows");
15152
+ const workflowPath = path36.join(workflowDir, "kody.yml");
15153
+ if (fs38.existsSync(workflowPath) && !force) {
14930
15154
  skipped.push(".github/workflows/kody.yml");
14931
15155
  } else {
14932
- fs37.mkdirSync(workflowDir, { recursive: true });
14933
- fs37.writeFileSync(workflowPath, loadKodyWorkflowTemplate());
15156
+ fs38.mkdirSync(workflowDir, { recursive: true });
15157
+ fs38.writeFileSync(workflowPath, loadKodyWorkflowTemplate());
14934
15158
  wrote.push(".github/workflows/kody.yml");
14935
15159
  }
14936
15160
  for (const exe of listRuntimeProfilesForCwd(cwd)) {
@@ -14941,12 +15165,12 @@ function performInit(cwd, force) {
14941
15165
  continue;
14942
15166
  }
14943
15167
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
14944
- const target = path35.join(workflowDir, `kody-${exe.name}.yml`);
14945
- if (fs37.existsSync(target) && !force) {
15168
+ const target = path36.join(workflowDir, `kody-${exe.name}.yml`);
15169
+ if (fs38.existsSync(target) && !force) {
14946
15170
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
14947
15171
  continue;
14948
15172
  }
14949
- fs37.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
15173
+ fs38.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
14950
15174
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
14951
15175
  }
14952
15176
  let labels;
@@ -15034,7 +15258,7 @@ Nothing to do. All files already present. (Use --force to overwrite.)
15034
15258
  });
15035
15259
 
15036
15260
  // src/scripts/loadAgentAdhoc.ts
15037
- import * as fs38 from "fs";
15261
+ import * as fs39 from "fs";
15038
15262
  function resolveMessage(messageArg) {
15039
15263
  const fromComment = readCommentBody();
15040
15264
  if (fromComment) return stripDirective(fromComment);
@@ -15042,9 +15266,9 @@ function resolveMessage(messageArg) {
15042
15266
  }
15043
15267
  function readCommentBody() {
15044
15268
  const eventPath = process.env.GITHUB_EVENT_PATH;
15045
- if (!eventPath || !fs38.existsSync(eventPath)) return "";
15269
+ if (!eventPath || !fs39.existsSync(eventPath)) return "";
15046
15270
  try {
15047
- const event = JSON.parse(fs38.readFileSync(eventPath, "utf-8"));
15271
+ const event = JSON.parse(fs39.readFileSync(eventPath, "utf-8"));
15048
15272
  return String(event.comment?.body ?? "");
15049
15273
  } catch {
15050
15274
  return "";
@@ -15098,10 +15322,10 @@ var init_loadAgentAdhoc = __esm({
15098
15322
  throw new Error("loadAgentAdhoc: ctx.args.agent must be a non-empty slug");
15099
15323
  }
15100
15324
  const agentPath = resolveAgentFile(ctx.cwd, agentSlug, agentsDir);
15101
- if (!fs38.existsSync(agentPath)) {
15325
+ if (!fs39.existsSync(agentPath)) {
15102
15326
  throw new Error(`loadAgentAdhoc: agent identity not found: ${agentPath}`);
15103
15327
  }
15104
- const { title, body } = parseAgentFile(fs38.readFileSync(agentPath, "utf-8"), agentSlug);
15328
+ const { title, body } = parseAgentFile(fs39.readFileSync(agentPath, "utf-8"), agentSlug);
15105
15329
  const message = resolveMessage(ctx.args.message);
15106
15330
  if (!message) {
15107
15331
  throw new Error(
@@ -15170,8 +15394,8 @@ var init_loadCapabilityState = __esm({
15170
15394
  });
15171
15395
 
15172
15396
  // src/scripts/loadSimpleCapability.ts
15173
- import * as fs39 from "fs";
15174
- import * as path36 from "path";
15397
+ import * as fs40 from "fs";
15398
+ import * as path37 from "path";
15175
15399
  function parseInput(supplied) {
15176
15400
  if (typeof supplied !== "string") return supplied;
15177
15401
  try {
@@ -15218,14 +15442,14 @@ function capabilityEnvironment(input) {
15218
15442
  return environment;
15219
15443
  }
15220
15444
  function listFiles(root) {
15221
- if (!fs39.existsSync(root)) return [];
15445
+ if (!fs40.existsSync(root)) return [];
15222
15446
  const files = [];
15223
15447
  const visit = (dir) => {
15224
- for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
15225
- const absolute = path36.join(dir, entry.name);
15448
+ for (const entry of fs40.readdirSync(dir, { withFileTypes: true })) {
15449
+ const absolute = path37.join(dir, entry.name);
15226
15450
  if (entry.isSymbolicLink()) continue;
15227
15451
  if (entry.isDirectory()) visit(absolute);
15228
- else if (entry.isFile()) files.push(path36.relative(root, absolute));
15452
+ else if (entry.isFile()) files.push(path37.relative(root, absolute));
15229
15453
  }
15230
15454
  };
15231
15455
  visit(root);
@@ -15246,8 +15470,8 @@ var init_loadSimpleCapability = __esm({
15246
15470
  if (!capability) {
15247
15471
  throw new Error(`Capability "${slug}" is not a valid simple capability folder`);
15248
15472
  }
15249
- const toolRoot = path36.join(capability.dir, "tools");
15250
- const skillRoot = path36.join(capability.dir, "skills");
15473
+ const toolRoot = path37.join(capability.dir, "tools");
15474
+ const skillRoot = path37.join(capability.dir, "skills");
15251
15475
  const toolFiles = listFiles(toolRoot);
15252
15476
  const skillFiles = listFiles(skillRoot);
15253
15477
  const input = parseInput(ctx.args.input);
@@ -15271,7 +15495,7 @@ var init_loadSimpleCapability = __esm({
15271
15495
  ...skillFiles.flatMap((file) => [
15272
15496
  `### ${file}`,
15273
15497
  "",
15274
- fs39.readFileSync(path36.join(skillRoot, file), "utf-8"),
15498
+ fs40.readFileSync(path37.join(skillRoot, file), "utf-8"),
15275
15499
  ""
15276
15500
  ])
15277
15501
  ] : [],
@@ -15280,7 +15504,7 @@ var init_loadSimpleCapability = __esm({
15280
15504
  "## Tools",
15281
15505
  "",
15282
15506
  "Inspect or run these capability-owned files when needed:",
15283
- ...toolFiles.map((file) => `- ${path36.join(toolRoot, file)}`)
15507
+ ...toolFiles.map((file) => `- ${path37.join(toolRoot, file)}`)
15284
15508
  ] : []
15285
15509
  ].join("\n");
15286
15510
  };
@@ -15588,8 +15812,8 @@ var init_loadIssueStateComment = __esm({
15588
15812
  });
15589
15813
 
15590
15814
  // src/scripts/loadJobFromFile.ts
15591
- import * as fs40 from "fs";
15592
- import * as path37 from "path";
15815
+ import * as fs41 from "fs";
15816
+ import * as path38 from "path";
15593
15817
  function parseJobFile(raw, slug) {
15594
15818
  let stripped = raw;
15595
15819
  if (stripped.startsWith("---\n")) {
@@ -15628,10 +15852,10 @@ var init_loadJobFromFile = __esm({
15628
15852
  if (!slug) {
15629
15853
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
15630
15854
  }
15631
- const capability = resolveCapabilityFolder(slug, path37.resolve(ctx.cwd, jobsDir));
15855
+ const capability = resolveCapabilityFolder(slug, path38.resolve(ctx.cwd, jobsDir));
15632
15856
  if (!capability) {
15633
15857
  throw new Error(
15634
- `loadJobFromFile: capability folder not found or incomplete: ${path37.resolve(ctx.cwd, jobsDir, slug)}`
15858
+ `loadJobFromFile: capability folder not found or incomplete: ${path38.resolve(ctx.cwd, jobsDir, slug)}`
15635
15859
  );
15636
15860
  }
15637
15861
  const { title, body, config } = capability;
@@ -15641,12 +15865,12 @@ var init_loadJobFromFile = __esm({
15641
15865
  let agentIdentity = "";
15642
15866
  if (agentSlug) {
15643
15867
  const agentPath = resolveAgentFile(ctx.cwd, agentSlug, agentsDir);
15644
- if (!fs40.existsSync(agentPath)) {
15868
+ if (!fs41.existsSync(agentPath)) {
15645
15869
  throw new Error(
15646
15870
  `loadJobFromFile: capability '${slug}' declares agent '${agentSlug}' but ${agentPath} does not exist`
15647
15871
  );
15648
15872
  }
15649
- const agentRaw = fs40.readFileSync(agentPath, "utf-8");
15873
+ const agentRaw = fs41.readFileSync(agentPath, "utf-8");
15650
15874
  const parsed = parseJobFile(agentRaw, agentSlug);
15651
15875
  agentTitle = parsed.title;
15652
15876
  agentIdentity = parsed.body;
@@ -15726,13 +15950,13 @@ ${truncate2(issue2.body, FINDING_BODY_MAX_BYTES)}`;
15726
15950
  });
15727
15951
 
15728
15952
  // src/scripts/kodyVariables.ts
15729
- import * as fs41 from "fs";
15730
- import * as path38 from "path";
15953
+ import * as fs42 from "fs";
15954
+ import * as path39 from "path";
15731
15955
  function readKodyVariables(cwd) {
15732
- const full = path38.join(cwd, KODY_VARIABLES_REL_PATH);
15956
+ const full = path39.join(cwd, KODY_VARIABLES_REL_PATH);
15733
15957
  let raw;
15734
15958
  try {
15735
- raw = fs41.readFileSync(full, "utf-8");
15959
+ raw = fs42.readFileSync(full, "utf-8");
15736
15960
  } catch {
15737
15961
  return {};
15738
15962
  }
@@ -15908,8 +16132,8 @@ var init_runtimeSecrets = __esm({
15908
16132
  });
15909
16133
 
15910
16134
  // src/scripts/loadQaContext.ts
15911
- import * as fs42 from "fs";
15912
- import * as path39 from "path";
16135
+ import * as fs43 from "fs";
16136
+ import * as path40 from "path";
15913
16137
  function parseSlugList(value) {
15914
16138
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
15915
16139
  return inner.split(",").map(
@@ -15938,18 +16162,18 @@ function readProfileAgents(raw) {
15938
16162
  return { agent: agent ?? legacy ?? ["kody"], body };
15939
16163
  }
15940
16164
  function readProfile(cwd) {
15941
- const dir = path39.join(cwd, CONTEXT_DIR_REL_PATH);
15942
- if (!fs42.existsSync(dir)) return "";
16165
+ const dir = path40.join(cwd, CONTEXT_DIR_REL_PATH);
16166
+ if (!fs43.existsSync(dir)) return "";
15943
16167
  let entries;
15944
16168
  try {
15945
- entries = fs42.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
16169
+ entries = fs43.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
15946
16170
  } catch {
15947
16171
  return "";
15948
16172
  }
15949
16173
  const blocks = [];
15950
16174
  for (const file of entries) {
15951
16175
  try {
15952
- const raw = fs42.readFileSync(path39.join(dir, file), "utf-8");
16176
+ const raw = fs43.readFileSync(path40.join(dir, file), "utf-8");
15953
16177
  const { agent, body } = readProfileAgents(raw);
15954
16178
  if (!agent.includes(QA_AGENT) && !agent.includes(ALL_AGENTS)) continue;
15955
16179
  blocks.push(`## ${file}
@@ -15998,8 +16222,8 @@ var init_loadQaContext = __esm({
15998
16222
  });
15999
16223
 
16000
16224
  // src/taskContext.ts
16001
- import * as fs43 from "fs";
16002
- import * as path40 from "path";
16225
+ import * as fs44 from "fs";
16226
+ import * as path41 from "path";
16003
16227
  function buildTaskContext(args) {
16004
16228
  return {
16005
16229
  schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
@@ -16015,9 +16239,9 @@ function buildTaskContext(args) {
16015
16239
  function persistTaskContext(cwd, ctx) {
16016
16240
  try {
16017
16241
  const dir = runtimeStatePath(cwd, "agent-runs", ctx.runId);
16018
- fs43.mkdirSync(dir, { recursive: true });
16019
- const file = path40.join(dir, "task-context.json");
16020
- fs43.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16242
+ fs44.mkdirSync(dir, { recursive: true });
16243
+ const file = path41.join(dir, "task-context.json");
16244
+ fs44.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16021
16245
  `);
16022
16246
  return file;
16023
16247
  } catch (err) {
@@ -16857,7 +17081,7 @@ function parseSingleJsonCandidate(candidates) {
16857
17081
  }
16858
17082
  return parsed.length === 1 ? { found: true, value: parsed[0] } : { found: false };
16859
17083
  }
16860
- function isObject(value) {
17084
+ function isObject2(value) {
16861
17085
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16862
17086
  }
16863
17087
  function stringValue4(value) {
@@ -16875,8 +17099,8 @@ var init_parseSimpleCapabilityOutput = __esm({
16875
17099
  return;
16876
17100
  }
16877
17101
  ctx.data.capabilityOutput = output;
16878
- const result = isObject(output) ? output : {};
16879
- const data = isObject(result.data) ? result.data : isObject(output) ? output : { output };
17102
+ const result = isObject2(output) ? output : {};
17103
+ const data = isObject2(result.data) ? result.data : isObject2(output) ? output : { output };
16880
17104
  const summary = typeof result.summary === "string" ? result.summary : "Capability completed";
16881
17105
  const reason = typeof result.reason === "string" ? result.reason : summary;
16882
17106
  const prUrl = stringValue4(data.pullRequestUrl) ?? stringValue4(data.prUrl) ?? stringValue4(result.pullRequestUrl) ?? stringValue4(result.prUrl);
@@ -17467,9 +17691,9 @@ var init_postResearchComment = __esm({
17467
17691
  });
17468
17692
 
17469
17693
  // src/scripts/prepareBrowserAuth.ts
17470
- import * as fs44 from "fs";
17694
+ import * as fs45 from "fs";
17471
17695
  import * as os6 from "os";
17472
- import * as path41 from "path";
17696
+ import * as path42 from "path";
17473
17697
  function appendAuthMessage(ctx, message) {
17474
17698
  const current = typeof ctx.data.qaAuthBlock === "string" ? ctx.data.qaAuthBlock.trim() : "";
17475
17699
  ctx.data.qaAuthBlock = current ? `${current}
@@ -17508,9 +17732,9 @@ async function githubJson(url, token) {
17508
17732
  return await response.json();
17509
17733
  }
17510
17734
  function writeKodyStorageState(input) {
17511
- const directory = fs44.mkdtempSync(path41.join(os6.tmpdir(), "kody-browser-auth-"));
17512
- fs44.chmodSync(directory, 448);
17513
- const file = path41.join(directory, "storage-state.json");
17735
+ const directory = fs45.mkdtempSync(path42.join(os6.tmpdir(), "kody-browser-auth-"));
17736
+ fs45.chmodSync(directory, 448);
17737
+ const file = path42.join(directory, "storage-state.json");
17514
17738
  const now = Date.now();
17515
17739
  const repoEntry = {
17516
17740
  repoUrl: input.repoUrl,
@@ -17540,7 +17764,7 @@ function writeKodyStorageState(input) {
17540
17764
  }
17541
17765
  ]
17542
17766
  };
17543
- fs44.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17767
+ fs45.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17544
17768
  return { directory, file };
17545
17769
  }
17546
17770
  function configurePlaywright(profile, storageStatePath) {
@@ -17622,7 +17846,7 @@ async function prepareMethod(ctx, profile, method) {
17622
17846
  configurePlaywright(profile, state.file);
17623
17847
  const authDirectory = state.directory;
17624
17848
  registerRuntimeCleanup(ctx, () => {
17625
- fs44.rmSync(authDirectory, { recursive: true, force: true });
17849
+ fs45.rmSync(authDirectory, { recursive: true, force: true });
17626
17850
  });
17627
17851
  appendAuthMessage(
17628
17852
  ctx,
@@ -17630,7 +17854,7 @@ async function prepareMethod(ctx, profile, method) {
17630
17854
  );
17631
17855
  return true;
17632
17856
  } catch (error) {
17633
- if (state) fs44.rmSync(state.directory, { recursive: true, force: true });
17857
+ if (state) fs45.rmSync(state.directory, { recursive: true, force: true });
17634
17858
  const reason = error instanceof Error ? error.message : String(error);
17635
17859
  appendAuthMessage(
17636
17860
  ctx,
@@ -18729,12 +18953,12 @@ fi
18729
18953
 
18730
18954
  // src/scripts/runPreviewBuild.ts
18731
18955
  import { copyFile, writeFile } from "fs/promises";
18732
- import * as path42 from "path";
18956
+ import * as path43 from "path";
18733
18957
  import { fileURLToPath as fileURLToPath2 } from "url";
18734
18958
  function bundledDockerfilePath(mode) {
18735
- const here = path42.dirname(fileURLToPath2(import.meta.url));
18959
+ const here = path43.dirname(fileURLToPath2(import.meta.url));
18736
18960
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
18737
- return path42.join(here, "preview-build-templates", file);
18961
+ return path43.join(here, "preview-build-templates", file);
18738
18962
  }
18739
18963
  function required(name) {
18740
18964
  const v = (process.env[name] ?? "").trim();
@@ -18969,10 +19193,10 @@ var init_runPreviewBuild = __esm({
18969
19193
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
18970
19194
  if (Object.keys(buildEnv).length > 0) {
18971
19195
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
18972
- await writeFile(path42.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
19196
+ await writeFile(path43.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
18973
19197
  `, "utf8");
18974
19198
  }
18975
- const consumerDockerfile = path42.join(ctx.cwd, "Dockerfile.preview");
19199
+ const consumerDockerfile = path43.join(ctx.cwd, "Dockerfile.preview");
18976
19200
  const { stat } = await import("fs/promises");
18977
19201
  let hasConsumerDockerfile = false;
18978
19202
  try {
@@ -19156,8 +19380,8 @@ var init_tickShellRunner = __esm({
19156
19380
  });
19157
19381
 
19158
19382
  // src/scripts/runScheduledImplementationTick.ts
19159
- import * as fs45 from "fs";
19160
- import * as path43 from "path";
19383
+ import * as fs46 from "fs";
19384
+ import * as path44 from "path";
19161
19385
  var runScheduledImplementationTick;
19162
19386
  var init_runScheduledImplementationTick = __esm({
19163
19387
  "src/scripts/runScheduledImplementationTick.ts"() {
@@ -19178,14 +19402,14 @@ var init_runScheduledImplementationTick = __esm({
19178
19402
  ctx.output.reason = `runScheduledImplementationTick: args.slug or ctx.args.${slugArg} must be non-empty capability slug`;
19179
19403
  return;
19180
19404
  }
19181
- const capability = resolveCapabilityFolder(slug, path43.resolve(ctx.cwd, jobsDir));
19405
+ const capability = resolveCapabilityFolder(slug, path44.resolve(ctx.cwd, jobsDir));
19182
19406
  if (!capability) {
19183
19407
  ctx.output.exitCode = 99;
19184
19408
  ctx.output.reason = `runScheduledImplementationTick: capability folder not found or incomplete: ${slug} (searched ${jobsDir} and company store)`;
19185
19409
  return;
19186
19410
  }
19187
- const shellPath = path43.join(profile.dir, shell);
19188
- if (!fs45.existsSync(shellPath)) {
19411
+ const shellPath = path44.join(profile.dir, shell);
19412
+ if (!fs46.existsSync(shellPath)) {
19189
19413
  ctx.output.exitCode = 99;
19190
19414
  ctx.output.reason = `runScheduledImplementationTick: shell not found: ${shell} (looked in ${profile.dir})`;
19191
19415
  return;
@@ -19216,8 +19440,8 @@ var init_runScheduledImplementationTick = __esm({
19216
19440
  });
19217
19441
 
19218
19442
  // src/scripts/runTickScript.ts
19219
- import * as fs46 from "fs";
19220
- import * as path44 from "path";
19443
+ import * as fs47 from "fs";
19444
+ import * as path45 from "path";
19221
19445
  var runTickScript;
19222
19446
  var init_runTickScript = __esm({
19223
19447
  "src/scripts/runTickScript.ts"() {
@@ -19237,10 +19461,10 @@ var init_runTickScript = __esm({
19237
19461
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
19238
19462
  return;
19239
19463
  }
19240
- const capability = readCapabilityFolder(path44.resolve(ctx.cwd, jobsDir), slug);
19464
+ const capability = readCapabilityFolder(path45.resolve(ctx.cwd, jobsDir), slug);
19241
19465
  if (!capability) {
19242
19466
  ctx.output.exitCode = 99;
19243
- ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path44.resolve(ctx.cwd, jobsDir, slug)}`;
19467
+ ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path45.resolve(ctx.cwd, jobsDir, slug)}`;
19244
19468
  return;
19245
19469
  }
19246
19470
  const tickScript = capability.config.tickScript;
@@ -19249,8 +19473,8 @@ var init_runTickScript = __esm({
19249
19473
  ctx.output.reason = `runTickScript: capability ${slug} has no \`tickScript\` in profile.json \u2014 route via capability-tick instead`;
19250
19474
  return;
19251
19475
  }
19252
- const scriptPath = path44.isAbsolute(tickScript) ? tickScript : path44.join(ctx.cwd, tickScript);
19253
- if (!fs46.existsSync(scriptPath)) {
19476
+ const scriptPath = path45.isAbsolute(tickScript) ? tickScript : path45.join(ctx.cwd, tickScript);
19477
+ if (!fs47.existsSync(scriptPath)) {
19254
19478
  ctx.output.exitCode = 99;
19255
19479
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
19256
19480
  return;
@@ -19532,7 +19756,7 @@ var init_syncFlow = __esm({
19532
19756
  });
19533
19757
 
19534
19758
  // src/scripts/validateAgencyModelProposal.ts
19535
- import * as path45 from "path";
19759
+ import * as path46 from "path";
19536
19760
  function validateModelBundle(bundle, expectedKind, options = {}) {
19537
19761
  const failures = [];
19538
19762
  validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind, options);
@@ -19850,7 +20074,7 @@ var init_validateAgencyModelProposal = __esm({
19850
20074
  const bundle = parseAgencyModelProposal(raw);
19851
20075
  const expectedKind = readExpectedModelKind(args);
19852
20076
  const failures = validateModelBundle(bundle, expectedKind, {
19853
- capabilityRoot: path45.join(ctx.cwd, ".kody", "capabilities")
20077
+ capabilityRoot: path46.join(ctx.cwd, ".kody", "capabilities")
19854
20078
  });
19855
20079
  if (failures.length > 0) {
19856
20080
  throw new Error(`validateAgencyModelProposal: ${failures.join("; ")}`);
@@ -20401,7 +20625,7 @@ var init_warmupMcp = __esm({
20401
20625
  });
20402
20626
 
20403
20627
  // src/scripts/writeAgentRunSummary.ts
20404
- import * as fs47 from "fs";
20628
+ import * as fs48 from "fs";
20405
20629
  var writeAgentRunSummary;
20406
20630
  var init_writeAgentRunSummary = __esm({
20407
20631
  "src/scripts/writeAgentRunSummary.ts"() {
@@ -20427,7 +20651,7 @@ var init_writeAgentRunSummary = __esm({
20427
20651
  if (reason) lines.push(`- **Reason:** ${reason}`);
20428
20652
  lines.push("");
20429
20653
  try {
20430
- fs47.appendFileSync(summaryPath, `${lines.join("\n")}
20654
+ fs48.appendFileSync(summaryPath, `${lines.join("\n")}
20431
20655
  `);
20432
20656
  } catch {
20433
20657
  }
@@ -20564,6 +20788,7 @@ var init_scripts = __esm({
20564
20788
  init_dispatchCapabilityTicks();
20565
20789
  init_dispatchClassified();
20566
20790
  init_dispatchAgencyLoops();
20791
+ init_dispatchSimpleLoops();
20567
20792
  init_dispatchNextTaskJob();
20568
20793
  init_ensurePr();
20569
20794
  init_evaluateAgencyBoundaries();
@@ -20688,6 +20913,7 @@ var init_scripts = __esm({
20688
20913
  warmupMcp,
20689
20914
  dispatchCapabilityTicks,
20690
20915
  dispatchAgencyLoops,
20916
+ dispatchSimpleLoops,
20691
20917
  dispatchCapabilityFileTicks,
20692
20918
  planTaskJobs,
20693
20919
  dispatchNextTaskJob,
@@ -20759,17 +20985,17 @@ var init_scripts = __esm({
20759
20985
  });
20760
20986
 
20761
20987
  // src/stateWorkspace.ts
20762
- import * as fs48 from "fs";
20763
- import * as path46 from "path";
20988
+ import * as fs49 from "fs";
20989
+ import * as path47 from "path";
20764
20990
  function tenantId(config) {
20765
20991
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
20766
20992
  const repo = config.github?.repo?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
20767
20993
  return owner && repo ? `${owner}/${repo}` : null;
20768
20994
  }
20769
20995
  function writeRuntimeFile(cwd, relativePath, content) {
20770
- const target = path46.join(cwd, RUNTIME_ROOT, relativePath);
20771
- fs48.mkdirSync(path46.dirname(target), { recursive: true });
20772
- fs48.writeFileSync(target, content, "utf8");
20996
+ const target = path47.join(cwd, RUNTIME_ROOT, relativePath);
20997
+ fs49.mkdirSync(path47.dirname(target), { recursive: true });
20998
+ fs49.writeFileSync(target, content, "utf8");
20773
20999
  }
20774
21000
  function record(value) {
20775
21001
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -20834,11 +21060,11 @@ async function hydrateStateWorkspace(config, cwd, backendOverride) {
20834
21060
  throw new Error("Kody backend access is required for runtime workspace documents");
20835
21061
  return;
20836
21062
  }
20837
- const key = `${path46.resolve(cwd)}|${tenant}`;
21063
+ const key = `${path47.resolve(cwd)}|${tenant}`;
20838
21064
  if (hydratedWorkspaces.has(key)) return;
20839
21065
  const backend = backendOverride ?? createStateBackendFromEnv();
20840
- const root = path46.join(cwd, RUNTIME_ROOT);
20841
- fs48.rmSync(root, { recursive: true, force: true });
21066
+ const root = path47.join(cwd, RUNTIME_ROOT);
21067
+ fs49.rmSync(root, { recursive: true, force: true });
20842
21068
  await Promise.all([
20843
21069
  hydratePrefix(backend, tenant, cwd, "context:"),
20844
21070
  hydratePrefix(backend, tenant, cwd, "memory:"),
@@ -20854,7 +21080,7 @@ var init_stateWorkspace = __esm({
20854
21080
  "src/stateWorkspace.ts"() {
20855
21081
  "use strict";
20856
21082
  init_state_backend();
20857
- RUNTIME_ROOT = path46.join(".kody-engine", "runtime");
21083
+ RUNTIME_ROOT = path47.join(".kody-engine", "runtime");
20858
21084
  hydratedWorkspaces = /* @__PURE__ */ new Set();
20859
21085
  }
20860
21086
  });
@@ -20925,9 +21151,9 @@ var init_tools = __esm({
20925
21151
 
20926
21152
  // src/executor.ts
20927
21153
  import { spawn as spawn8 } from "child_process";
20928
- import * as fs49 from "fs";
21154
+ import * as fs50 from "fs";
20929
21155
  import * as os7 from "os";
20930
- import * as path47 from "path";
21156
+ import * as path48 from "path";
20931
21157
  function isMutatingPostflight(scriptName) {
20932
21158
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
20933
21159
  }
@@ -21168,7 +21394,7 @@ async function runImplementation(profileName, input) {
21168
21394
  const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
21169
21395
  const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
21170
21396
  const invokeAgent = async (prompt) => {
21171
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path47.isAbsolute(p) ? p : path47.resolve(profile.dir, p)).filter((p) => p.length > 0);
21397
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path48.isAbsolute(p) ? p : path48.resolve(profile.dir, p)).filter((p) => p.length > 0);
21172
21398
  const syntheticPath = ctx.data.syntheticPluginPath;
21173
21399
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
21174
21400
  const agents = loadSubagents(profile);
@@ -21632,17 +21858,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
21632
21858
  function resolveProfilePath(profileName, cwd = process.cwd()) {
21633
21859
  const found = resolveImplementation(profileName, getRuntimeProfileRootsForCwd(cwd));
21634
21860
  if (found) return found;
21635
- const here = path47.dirname(new URL(import.meta.url).pathname);
21861
+ const here = path48.dirname(new URL(import.meta.url).pathname);
21636
21862
  const candidates = [
21637
- path47.join(here, "implementations", profileName, "profile.json"),
21863
+ path48.join(here, "implementations", profileName, "profile.json"),
21638
21864
  // same-dir sibling (dev)
21639
- path47.join(here, "..", "implementations", profileName, "profile.json"),
21865
+ path48.join(here, "..", "implementations", profileName, "profile.json"),
21640
21866
  // up one (prod: dist/bin → dist/implementations)
21641
- path47.join(here, "..", "src", "implementations", profileName, "profile.json")
21867
+ path48.join(here, "..", "src", "implementations", profileName, "profile.json")
21642
21868
  // fallback
21643
21869
  ];
21644
21870
  for (const c of candidates) {
21645
- if (fs49.existsSync(c)) return c;
21871
+ if (fs50.existsSync(c)) return c;
21646
21872
  }
21647
21873
  return candidates[0];
21648
21874
  }
@@ -21757,15 +21983,15 @@ function resolveShellTimeoutMs(entry) {
21757
21983
  }
21758
21984
  async function runShellEntry(entry, ctx, profile) {
21759
21985
  const shellName = entry.shell;
21760
- const shellPath = path47.join(profile.dir, shellName);
21761
- if (!fs49.existsSync(shellPath)) {
21986
+ const shellPath = path48.join(profile.dir, shellName);
21987
+ if (!fs50.existsSync(shellPath)) {
21762
21988
  ctx.skipAgent = true;
21763
21989
  ctx.output.exitCode = 99;
21764
21990
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
21765
21991
  return;
21766
21992
  }
21767
21993
  const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
21768
- const outputFile = path47.join(
21994
+ const outputFile = path48.join(
21769
21995
  os7.tmpdir(),
21770
21996
  `kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
21771
21997
  );
@@ -21837,9 +22063,9 @@ async function runShellEntry(entry, ctx, profile) {
21837
22063
  }
21838
22064
  let sideChannelText = "";
21839
22065
  try {
21840
- if (fs49.existsSync(outputFile)) {
21841
- sideChannelText = fs49.readFileSync(outputFile, "utf-8");
21842
- fs49.rmSync(outputFile, { force: true });
22066
+ if (fs50.existsSync(outputFile)) {
22067
+ sideChannelText = fs50.readFileSync(outputFile, "utf-8");
22068
+ fs50.rmSync(outputFile, { force: true });
21843
22069
  }
21844
22070
  } catch {
21845
22071
  }
@@ -22011,7 +22237,7 @@ __export(job_exports, {
22011
22237
  stableJobKey: () => stableJobKey,
22012
22238
  validateJob: () => validateJob
22013
22239
  });
22014
- import * as path48 from "path";
22240
+ import * as path49 from "path";
22015
22241
  function newJobId(flavor) {
22016
22242
  localJobSeq += 1;
22017
22243
  const runId = process.env.GITHUB_RUN_ID;
@@ -22487,6 +22713,13 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
22487
22713
  const key = `${step.id}->${transition.to}`;
22488
22714
  state.transitionCounts[key] = (state.transitionCounts[key] ?? 0) + 1;
22489
22715
  }
22716
+ if (transition.to === "$end") {
22717
+ state.status = "done";
22718
+ delete state.currentStepId;
22719
+ delete state.blocker;
22720
+ await checkpoint?.(state);
22721
+ return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
22722
+ }
22490
22723
  state.currentStepId = transition.to;
22491
22724
  state.status = "running";
22492
22725
  delete state.blocker;
@@ -22662,7 +22895,7 @@ function valueMatches(actual, expected) {
22662
22895
  }
22663
22896
  function workflowStepTargetNumber(step, parent, chainData) {
22664
22897
  if (step.target === "pr") return workflowPrNumber(chainData) ?? workflowTargetFactNumber(step, chainData);
22665
- if (step.target === "issue") return workflowIssueNumber(parent);
22898
+ if (step.target === "issue") return workflowIssueNumber(parent) ?? workflowTargetFactNumber(step, chainData);
22666
22899
  return typeof parent.target === "number" ? parent.target : targetFromCliArgs(parent.cliArgs);
22667
22900
  }
22668
22901
  function workflowResumeStartIndex(steps, evidence) {
@@ -22712,7 +22945,7 @@ function loadCapabilityContext(slug, cwd) {
22712
22945
  return resolveCapabilityFolder(slug, hydratedCapabilitiesRoot(cwd));
22713
22946
  }
22714
22947
  function hydratedCapabilitiesRoot(cwd) {
22715
- return path48.join(cwd, ".kody-engine", "definitions", "capabilities");
22948
+ return path49.join(cwd, ".kody-engine", "definitions", "capabilities");
22716
22949
  }
22717
22950
  function loadWorkflowContext(slug, base) {
22718
22951
  if (!slug || !isWorkflowDefinitionId(slug)) return null;
@@ -24688,82 +24921,7 @@ function readRunRequestFromEnv(env = process.env) {
24688
24921
 
24689
24922
  // src/kody-cli.ts
24690
24923
  init_runtimePaths();
24691
-
24692
- // src/loopDefinitions.ts
24693
- init_definition_paths();
24694
- import * as fs50 from "fs";
24695
- import * as path49 from "path";
24696
- var ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
24697
- function normalizeLoopDefinition(value) {
24698
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
24699
- const raw = value;
24700
- if (Object.keys(raw).some((key) => !["id", "trigger", "target", "input", "enabled"].includes(key))) return null;
24701
- if (typeof raw.id !== "string" || !ID.test(raw.id)) return null;
24702
- if (typeof raw.enabled !== "boolean") return null;
24703
- if (!isObject2(raw.input) || !isObject2(raw.trigger) || !isObject2(raw.target)) return null;
24704
- const targetKind = raw.target.kind;
24705
- const targetId = raw.target.id;
24706
- if (targetKind !== "workflow" && targetKind !== "capability" || typeof targetId !== "string" || !ID.test(targetId)) {
24707
- return null;
24708
- }
24709
- const trigger = normalizeTrigger(raw.trigger);
24710
- if (!trigger) return null;
24711
- return {
24712
- id: raw.id,
24713
- trigger,
24714
- target: { kind: targetKind, id: targetId },
24715
- input: raw.input,
24716
- enabled: raw.enabled
24717
- };
24718
- }
24719
- function readLoopDefinition(cwd, id) {
24720
- if (!ID.test(id)) return null;
24721
- const roots = [
24722
- path49.join(cwd, ".kody-engine", "runtime"),
24723
- definitionsRoot(cwd)
24724
- ];
24725
- for (const root of roots) {
24726
- const filePath = path49.join(root, "loops", id, "loop.json");
24727
- if (!fs50.existsSync(filePath)) continue;
24728
- try {
24729
- const loop = normalizeLoopDefinition(JSON.parse(fs50.readFileSync(filePath, "utf8")));
24730
- if (loop?.id === id) return loop;
24731
- process.stderr.write(`[kody] invalid simple Loop definition: ${filePath}
24732
- `);
24733
- } catch {
24734
- process.stderr.write(`[kody] unreadable simple Loop definition: ${filePath}
24735
- `);
24736
- }
24737
- }
24738
- process.stderr.write(
24739
- `[kody] simple Loop not found: ${id} (${roots.map((root) => path49.join(root, "loops", id, "loop.json")).join(", ")})
24740
- `
24741
- );
24742
- return null;
24743
- }
24744
- function normalizeTrigger(raw) {
24745
- if (raw.type === "manual" && Object.keys(raw).length === 1) return { type: "manual" };
24746
- if (raw.type === "schedule" && typeof raw.every === "string" && /^\d+[mhd]$/.test(raw.every)) {
24747
- if (raw.at === void 0 && Object.keys(raw).every((key) => key === "type" || key === "every")) {
24748
- return { type: "schedule", every: raw.every };
24749
- }
24750
- if (isObject2(raw.at) && typeof raw.at.time === "string" && typeof raw.at.timezone === "string" && Object.keys(raw.at).every((key) => key === "time" || key === "timezone") && Object.keys(raw).every((key) => key === "type" || key === "every" || key === "at")) {
24751
- return { type: "schedule", every: raw.every, at: { time: raw.at.time, timezone: raw.at.timezone } };
24752
- }
24753
- }
24754
- if ((raw.type === "event" || raw.type === "webhook") && typeof raw.event === "string" && raw.event.trim() && Object.keys(raw).every((key) => key === "type" || key === "event")) {
24755
- return { type: raw.type, event: raw.event.trim() };
24756
- }
24757
- if (raw.type === "condition" && typeof raw.expression === "string" && raw.expression.trim() && Object.keys(raw).every((key) => key === "type" || key === "expression")) {
24758
- return { type: "condition", expression: raw.expression.trim() };
24759
- }
24760
- return null;
24761
- }
24762
- function isObject2(value) {
24763
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
24764
- }
24765
-
24766
- // src/kody-cli.ts
24924
+ init_loopDefinitions();
24767
24925
  init_stateWorkspace();
24768
24926
  init_workflowDefinitions();
24769
24927
  var FAILED_DISPATCH_LABEL = {
@@ -26445,7 +26603,7 @@ init_config();
26445
26603
  init_fetchRepoMcp();
26446
26604
 
26447
26605
  // src/servers/mcpHttpServer.ts
26448
- import { randomUUID as randomUUID2 } from "crypto";
26606
+ import { randomUUID as randomUUID3 } from "crypto";
26449
26607
  import { createServer as createServer4 } from "http";
26450
26608
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
26451
26609
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -26454,7 +26612,7 @@ function buildMcpHttpServer(opts) {
26454
26612
  const transports = /* @__PURE__ */ new Map();
26455
26613
  for (const route of opts.routes) {
26456
26614
  const transport = new StreamableHTTPServerTransport({
26457
- sessionIdGenerator: () => randomUUID2()
26615
+ sessionIdGenerator: () => randomUUID3()
26458
26616
  });
26459
26617
  transports.set(route.path, transport);
26460
26618
  routes.set(route.path, route.name);
@@ -1,8 +1,5 @@
1
1
  {
2
2
  "internal": true,
3
- "role": "watch",
4
- "kind": "scheduled",
5
- "schedule": "*/5 * * * *",
6
3
  "claudeCode": {
7
4
  "model": "inherit",
8
5
  "permissionMode": "default",
@@ -33,9 +30,6 @@
33
30
  "outputArtifacts": [],
34
31
  "scripts": {
35
32
  "preflight": [
36
- {
37
- "script": "dispatchAgencyLoops"
38
- },
39
33
  {
40
34
  "shell": "scheduler.sh",
41
35
  "timeoutSec": 1800
@@ -0,0 +1,32 @@
1
+ {
2
+ "internal": true,
3
+ "role": "watch",
4
+ "kind": "scheduled",
5
+ "schedule": "*/5 * * * *",
6
+ "claudeCode": {
7
+ "model": "inherit",
8
+ "permissionMode": "default",
9
+ "maxTurns": 0,
10
+ "maxThinkingTokens": null,
11
+ "systemPromptAppend": null,
12
+ "tools": [],
13
+ "hooks": [],
14
+ "skills": [],
15
+ "commands": [],
16
+ "subagents": [],
17
+ "plugins": [],
18
+ "mcpServers": []
19
+ },
20
+ "cliTools": [],
21
+ "inputArtifacts": [],
22
+ "outputArtifacts": [],
23
+ "scripts": {
24
+ "preflight": [
25
+ { "script": "dispatchSimpleLoops" },
26
+ { "script": "skipAgent" }
27
+ ],
28
+ "postflight": []
29
+ },
30
+ "inputs": [],
31
+ "name": "loop-scheduler"
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.445",
3
+ "version": "0.4.447",
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",