@kody-ade/kody-engine 0.4.444 → 0.4.446

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.444",
18
+ version: "0.4.446",
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",
@@ -13798,6 +13798,204 @@ var init_dispatchAgencyLoops = __esm({
13798
13798
  }
13799
13799
  });
13800
13800
 
13801
+ // src/loopDefinitions.ts
13802
+ import * as fs36 from "fs";
13803
+ import * as path34 from "path";
13804
+ function normalizeLoopDefinition(value) {
13805
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
13806
+ const raw = value;
13807
+ if (Object.keys(raw).some((key) => !["id", "trigger", "target", "input", "enabled"].includes(key))) return null;
13808
+ if (typeof raw.id !== "string" || !ID.test(raw.id)) return null;
13809
+ if (typeof raw.enabled !== "boolean") return null;
13810
+ if (!isObject(raw.input) || !isObject(raw.trigger) || !isObject(raw.target)) return null;
13811
+ const targetKind = raw.target.kind;
13812
+ const targetId = raw.target.id;
13813
+ if (targetKind !== "workflow" && targetKind !== "capability" || typeof targetId !== "string" || !ID.test(targetId)) {
13814
+ return null;
13815
+ }
13816
+ const trigger = normalizeTrigger(raw.trigger);
13817
+ if (!trigger) return null;
13818
+ return {
13819
+ id: raw.id,
13820
+ trigger,
13821
+ target: { kind: targetKind, id: targetId },
13822
+ input: raw.input,
13823
+ enabled: raw.enabled
13824
+ };
13825
+ }
13826
+ function readLoopDefinition(cwd, id) {
13827
+ if (!ID.test(id)) return null;
13828
+ const roots = [path34.join(cwd, ".kody-engine", "runtime"), definitionsRoot(cwd)];
13829
+ for (const root of roots) {
13830
+ const filePath = path34.join(root, "loops", id, "loop.json");
13831
+ if (!fs36.existsSync(filePath)) continue;
13832
+ try {
13833
+ const loop = normalizeLoopDefinition(JSON.parse(fs36.readFileSync(filePath, "utf8")));
13834
+ if (loop?.id === id) return loop;
13835
+ process.stderr.write(`[kody] invalid simple Loop definition: ${filePath}
13836
+ `);
13837
+ } catch {
13838
+ process.stderr.write(`[kody] unreadable simple Loop definition: ${filePath}
13839
+ `);
13840
+ }
13841
+ }
13842
+ process.stderr.write(
13843
+ `[kody] simple Loop not found: ${id} (${roots.map((root) => path34.join(root, "loops", id, "loop.json")).join(", ")})
13844
+ `
13845
+ );
13846
+ return null;
13847
+ }
13848
+ function listLoopDefinitions(cwd) {
13849
+ const roots = [path34.join(cwd, ".kody-engine", "runtime"), definitionsRoot(cwd)];
13850
+ const byId = /* @__PURE__ */ new Map();
13851
+ for (const root of roots.reverse()) {
13852
+ const loopsDir = path34.join(root, "loops");
13853
+ if (!fs36.existsSync(loopsDir)) continue;
13854
+ for (const id of fs36.readdirSync(loopsDir).sort()) {
13855
+ if (!ID.test(id)) continue;
13856
+ const filePath = path34.join(loopsDir, 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) byId.set(id, loop);
13861
+ } catch {
13862
+ process.stderr.write(`[kody] unreadable simple Loop definition: ${filePath}
13863
+ `);
13864
+ }
13865
+ }
13866
+ }
13867
+ return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
13868
+ }
13869
+ function normalizeTrigger(raw) {
13870
+ if (raw.type === "manual" && Object.keys(raw).length === 1) return { type: "manual" };
13871
+ if (raw.type === "schedule" && typeof raw.every === "string" && /^\d+[mhd]$/.test(raw.every)) {
13872
+ if (raw.at === void 0 && Object.keys(raw).every((key) => key === "type" || key === "every")) {
13873
+ return { type: "schedule", every: raw.every };
13874
+ }
13875
+ 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")) {
13876
+ return { type: "schedule", every: raw.every, at: { time: raw.at.time, timezone: raw.at.timezone } };
13877
+ }
13878
+ }
13879
+ if ((raw.type === "event" || raw.type === "webhook") && typeof raw.event === "string" && raw.event.trim() && Object.keys(raw).every((key) => key === "type" || key === "event")) {
13880
+ return { type: raw.type, event: raw.event.trim() };
13881
+ }
13882
+ if (raw.type === "condition" && typeof raw.expression === "string" && raw.expression.trim() && Object.keys(raw).every((key) => key === "type" || key === "expression")) {
13883
+ return { type: "condition", expression: raw.expression.trim() };
13884
+ }
13885
+ return null;
13886
+ }
13887
+ function isObject(value) {
13888
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13889
+ }
13890
+ var ID;
13891
+ var init_loopDefinitions = __esm({
13892
+ "src/loopDefinitions.ts"() {
13893
+ "use strict";
13894
+ init_definition_paths();
13895
+ ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
13896
+ }
13897
+ });
13898
+
13899
+ // src/scripts/dispatchSimpleLoops.ts
13900
+ import { randomUUID as randomUUID2 } from "crypto";
13901
+ function dueSlot(loop, now) {
13902
+ if (!loop.enabled || loop.trigger.type !== "schedule") return null;
13903
+ const match = /^(\d+)([mhd])$/.exec(loop.trigger.every);
13904
+ if (!match) return null;
13905
+ const amount = Number(match[1]);
13906
+ const unit = match[2];
13907
+ const milliseconds = amount * (unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5);
13908
+ const slot = Math.floor(now.getTime() / milliseconds) * milliseconds;
13909
+ if (!loop.trigger.at) return new Date(slot).toISOString();
13910
+ const parts = new Intl.DateTimeFormat("en-CA", {
13911
+ timeZone: loop.trigger.at.timezone,
13912
+ year: "numeric",
13913
+ month: "2-digit",
13914
+ day: "2-digit",
13915
+ hour: "2-digit",
13916
+ minute: "2-digit",
13917
+ hourCycle: "h23"
13918
+ }).formatToParts(now);
13919
+ const hour = parts.find((part) => part.type === "hour")?.value;
13920
+ const minute = parts.find((part) => part.type === "minute")?.value;
13921
+ const [targetHour, targetMinute] = loop.trigger.at.time.split(":").map(Number);
13922
+ const localMinute = Number(hour) * 60 + Number(minute);
13923
+ const targetLocalMinute = Number(targetHour) * 60 + Number(targetMinute);
13924
+ const windowMinutes = Math.max(1, Math.ceil(Number(process.env.KODY_SCHEDULE_WINDOW_SEC || 300) / 60));
13925
+ if (localMinute < targetLocalMinute || localMinute >= targetLocalMinute + windowMinutes) return null;
13926
+ const year = parts.find((part) => part.type === "year")?.value;
13927
+ const month = parts.find((part) => part.type === "month")?.value;
13928
+ const day = parts.find((part) => part.type === "day")?.value;
13929
+ return `${year}-${month}-${day}T${loop.trigger.at.time}[${loop.trigger.at.timezone}]`;
13930
+ }
13931
+ function loopJob(loop) {
13932
+ const cliArgs = Object.fromEntries(
13933
+ Object.entries(loop.input).map(([key, value]) => [key, typeof value === "string" ? value : JSON.stringify(value)])
13934
+ );
13935
+ return loop.target.kind === "workflow" ? { workflow: loop.target.id, cliArgs, flavor: "scheduled" } : { capability: loop.target.id, cliArgs, flavor: "scheduled" };
13936
+ }
13937
+ function repositoryTenant2(config) {
13938
+ const [envOwner, envRepo] = (process.env.GITHUB_REPOSITORY ?? "").split("/");
13939
+ const owner = config.github?.owner?.trim() || envOwner?.trim();
13940
+ const repo = config.github?.repo?.trim() || envRepo?.trim();
13941
+ return owner && repo ? `${owner}/${repo}` : null;
13942
+ }
13943
+ var dispatchSimpleLoops;
13944
+ var init_dispatchSimpleLoops = __esm({
13945
+ "src/scripts/dispatchSimpleLoops.ts"() {
13946
+ "use strict";
13947
+ init_loopDefinitions();
13948
+ init_job();
13949
+ init_state_backend();
13950
+ dispatchSimpleLoops = async (ctx) => {
13951
+ const tenantId2 = repositoryTenant2(ctx.config);
13952
+ if (!tenantId2) throw new Error("Repository identity is required for Loop dispatch");
13953
+ const now = /* @__PURE__ */ new Date();
13954
+ const due = listLoopDefinitions(ctx.cwd).filter((loop) => dueSlot(loop, now) !== null);
13955
+ const backend = createStateBackendFromEnv();
13956
+ const results = [];
13957
+ for (const loop of due) {
13958
+ const slot = dueSlot(loop, now);
13959
+ if (!slot) continue;
13960
+ const reservationId = `reservation-${randomUUID2()}`;
13961
+ const idempotencyKey = `${loop.id}:${slot}`;
13962
+ const claimed = await backend.reserveAgencyDispatch(tenantId2, {
13963
+ idempotencyKey,
13964
+ loopId: loop.id,
13965
+ decision: { kind: "fire", reason: "local Loop schedule is due", scheduledAt: slot },
13966
+ leaseUntil: new Date(now.getTime() + 6 * 60 * 60 * 1e3).toISOString(),
13967
+ reservationId,
13968
+ correlationId: `corr-${randomUUID2()}`,
13969
+ policyHash: `loop:${loop.id}`,
13970
+ effectivePolicy: { source: "repository" },
13971
+ definitionRefs: [{ kind: "loop", id: loop.id }],
13972
+ maxConcurrentRuns: 1,
13973
+ requiresApproval: false,
13974
+ approvalScopeKind: "loop",
13975
+ approvalScopeId: loop.id,
13976
+ approvalAction: `${loop.target.kind}:${loop.target.id}`,
13977
+ now: now.toISOString()
13978
+ });
13979
+ if (!claimed.acquired) {
13980
+ results.push({ loopId: loop.id, status: "skipped", reason: claimed.reason ?? "already claimed" });
13981
+ continue;
13982
+ }
13983
+ const result = await runJob(loopJob(loop), {
13984
+ cwd: ctx.cwd,
13985
+ config: ctx.config,
13986
+ verbose: ctx.verbose,
13987
+ quiet: ctx.quiet,
13988
+ chain: false
13989
+ });
13990
+ const status = result.exitCode === 0 ? "dispatched" : "failed";
13991
+ await backend.finishAgencyDispatch(tenantId2, idempotencyKey, reservationId, status, (/* @__PURE__ */ new Date()).toISOString());
13992
+ results.push({ loopId: loop.id, status, reason: result.reason ?? status });
13993
+ }
13994
+ ctx.data.simpleLoopDispatchResults = results;
13995
+ };
13996
+ }
13997
+ });
13998
+
13801
13999
  // src/jobIdentity.ts
13802
14000
  function stableJobKey(job) {
13803
14001
  const capability = job.workflow ?? job.capability ?? job.action;
@@ -14818,15 +15016,15 @@ var init_fixFlow = __esm({
14818
15016
  });
14819
15017
 
14820
15018
  // src/workflow-template.ts
14821
- import * as fs36 from "fs";
14822
- import * as path34 from "path";
15019
+ import * as fs37 from "fs";
15020
+ import * as path35 from "path";
14823
15021
  import { fileURLToPath } from "url";
14824
15022
  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));
15023
+ const here = path35.dirname(fileURLToPath(import.meta.url));
15024
+ const candidates = [path35.resolve(here, "../templates/kody.yml"), path35.resolve(here, "../../templates/kody.yml")];
15025
+ const source = candidates.find((candidate) => fs37.existsSync(candidate));
14828
15026
  if (!source) throw new Error(`Kody workflow template is missing: ${KODY_WORKFLOW_TEMPLATE_PATH}`);
14829
- return fs36.readFileSync(source, "utf8");
15027
+ return fs37.readFileSync(source, "utf8");
14830
15028
  }
14831
15029
  var KODY_WORKFLOW_TEMPLATE_PATH;
14832
15030
  var init_workflow_template = __esm({
@@ -14838,12 +15036,12 @@ var init_workflow_template = __esm({
14838
15036
 
14839
15037
  // src/scripts/initFlow.ts
14840
15038
  import { execFileSync as execFileSync14 } from "child_process";
14841
- import * as fs37 from "fs";
14842
- import * as path35 from "path";
15039
+ import * as fs38 from "fs";
15040
+ import * as path36 from "path";
14843
15041
  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";
15042
+ if (fs38.existsSync(path36.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
15043
+ if (fs38.existsSync(path36.join(cwd, "yarn.lock"))) return "yarn";
15044
+ if (fs38.existsSync(path36.join(cwd, "bun.lockb"))) return "bun";
14847
15045
  return "npm";
14848
15046
  }
14849
15047
  function qualityCommandsFor(pm) {
@@ -14915,22 +15113,22 @@ function performInit(cwd, force) {
14915
15113
  const pm = detectPackageManager(cwd);
14916
15114
  const ownerRepo = detectOwnerRepo(cwd);
14917
15115
  const defaultBranch = defaultBranchFromGit(cwd);
14918
- const configPath = path35.join(cwd, "kody.config.json");
14919
- if (fs37.existsSync(configPath) && !force) {
15116
+ const configPath = path36.join(cwd, "kody.config.json");
15117
+ if (fs38.existsSync(configPath) && !force) {
14920
15118
  skipped.push("kody.config.json");
14921
15119
  } else {
14922
15120
  const cfg = makeConfig(pm, ownerRepo, defaultBranch);
14923
- fs37.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
15121
+ fs38.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
14924
15122
  `);
14925
15123
  wrote.push("kody.config.json");
14926
15124
  }
14927
- const workflowDir = path35.join(cwd, ".github", "workflows");
14928
- const workflowPath = path35.join(workflowDir, "kody.yml");
14929
- if (fs37.existsSync(workflowPath) && !force) {
15125
+ const workflowDir = path36.join(cwd, ".github", "workflows");
15126
+ const workflowPath = path36.join(workflowDir, "kody.yml");
15127
+ if (fs38.existsSync(workflowPath) && !force) {
14930
15128
  skipped.push(".github/workflows/kody.yml");
14931
15129
  } else {
14932
- fs37.mkdirSync(workflowDir, { recursive: true });
14933
- fs37.writeFileSync(workflowPath, loadKodyWorkflowTemplate());
15130
+ fs38.mkdirSync(workflowDir, { recursive: true });
15131
+ fs38.writeFileSync(workflowPath, loadKodyWorkflowTemplate());
14934
15132
  wrote.push(".github/workflows/kody.yml");
14935
15133
  }
14936
15134
  for (const exe of listRuntimeProfilesForCwd(cwd)) {
@@ -14941,12 +15139,12 @@ function performInit(cwd, force) {
14941
15139
  continue;
14942
15140
  }
14943
15141
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
14944
- const target = path35.join(workflowDir, `kody-${exe.name}.yml`);
14945
- if (fs37.existsSync(target) && !force) {
15142
+ const target = path36.join(workflowDir, `kody-${exe.name}.yml`);
15143
+ if (fs38.existsSync(target) && !force) {
14946
15144
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
14947
15145
  continue;
14948
15146
  }
14949
- fs37.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
15147
+ fs38.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
14950
15148
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
14951
15149
  }
14952
15150
  let labels;
@@ -15034,7 +15232,7 @@ Nothing to do. All files already present. (Use --force to overwrite.)
15034
15232
  });
15035
15233
 
15036
15234
  // src/scripts/loadAgentAdhoc.ts
15037
- import * as fs38 from "fs";
15235
+ import * as fs39 from "fs";
15038
15236
  function resolveMessage(messageArg) {
15039
15237
  const fromComment = readCommentBody();
15040
15238
  if (fromComment) return stripDirective(fromComment);
@@ -15042,9 +15240,9 @@ function resolveMessage(messageArg) {
15042
15240
  }
15043
15241
  function readCommentBody() {
15044
15242
  const eventPath = process.env.GITHUB_EVENT_PATH;
15045
- if (!eventPath || !fs38.existsSync(eventPath)) return "";
15243
+ if (!eventPath || !fs39.existsSync(eventPath)) return "";
15046
15244
  try {
15047
- const event = JSON.parse(fs38.readFileSync(eventPath, "utf-8"));
15245
+ const event = JSON.parse(fs39.readFileSync(eventPath, "utf-8"));
15048
15246
  return String(event.comment?.body ?? "");
15049
15247
  } catch {
15050
15248
  return "";
@@ -15098,10 +15296,10 @@ var init_loadAgentAdhoc = __esm({
15098
15296
  throw new Error("loadAgentAdhoc: ctx.args.agent must be a non-empty slug");
15099
15297
  }
15100
15298
  const agentPath = resolveAgentFile(ctx.cwd, agentSlug, agentsDir);
15101
- if (!fs38.existsSync(agentPath)) {
15299
+ if (!fs39.existsSync(agentPath)) {
15102
15300
  throw new Error(`loadAgentAdhoc: agent identity not found: ${agentPath}`);
15103
15301
  }
15104
- const { title, body } = parseAgentFile(fs38.readFileSync(agentPath, "utf-8"), agentSlug);
15302
+ const { title, body } = parseAgentFile(fs39.readFileSync(agentPath, "utf-8"), agentSlug);
15105
15303
  const message = resolveMessage(ctx.args.message);
15106
15304
  if (!message) {
15107
15305
  throw new Error(
@@ -15170,8 +15368,8 @@ var init_loadCapabilityState = __esm({
15170
15368
  });
15171
15369
 
15172
15370
  // src/scripts/loadSimpleCapability.ts
15173
- import * as fs39 from "fs";
15174
- import * as path36 from "path";
15371
+ import * as fs40 from "fs";
15372
+ import * as path37 from "path";
15175
15373
  function parseInput(supplied) {
15176
15374
  if (typeof supplied !== "string") return supplied;
15177
15375
  try {
@@ -15218,14 +15416,14 @@ function capabilityEnvironment(input) {
15218
15416
  return environment;
15219
15417
  }
15220
15418
  function listFiles(root) {
15221
- if (!fs39.existsSync(root)) return [];
15419
+ if (!fs40.existsSync(root)) return [];
15222
15420
  const files = [];
15223
15421
  const visit = (dir) => {
15224
- for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
15225
- const absolute = path36.join(dir, entry.name);
15422
+ for (const entry of fs40.readdirSync(dir, { withFileTypes: true })) {
15423
+ const absolute = path37.join(dir, entry.name);
15226
15424
  if (entry.isSymbolicLink()) continue;
15227
15425
  if (entry.isDirectory()) visit(absolute);
15228
- else if (entry.isFile()) files.push(path36.relative(root, absolute));
15426
+ else if (entry.isFile()) files.push(path37.relative(root, absolute));
15229
15427
  }
15230
15428
  };
15231
15429
  visit(root);
@@ -15246,8 +15444,8 @@ var init_loadSimpleCapability = __esm({
15246
15444
  if (!capability) {
15247
15445
  throw new Error(`Capability "${slug}" is not a valid simple capability folder`);
15248
15446
  }
15249
- const toolRoot = path36.join(capability.dir, "tools");
15250
- const skillRoot = path36.join(capability.dir, "skills");
15447
+ const toolRoot = path37.join(capability.dir, "tools");
15448
+ const skillRoot = path37.join(capability.dir, "skills");
15251
15449
  const toolFiles = listFiles(toolRoot);
15252
15450
  const skillFiles = listFiles(skillRoot);
15253
15451
  const input = parseInput(ctx.args.input);
@@ -15271,7 +15469,7 @@ var init_loadSimpleCapability = __esm({
15271
15469
  ...skillFiles.flatMap((file) => [
15272
15470
  `### ${file}`,
15273
15471
  "",
15274
- fs39.readFileSync(path36.join(skillRoot, file), "utf-8"),
15472
+ fs40.readFileSync(path37.join(skillRoot, file), "utf-8"),
15275
15473
  ""
15276
15474
  ])
15277
15475
  ] : [],
@@ -15280,7 +15478,7 @@ var init_loadSimpleCapability = __esm({
15280
15478
  "## Tools",
15281
15479
  "",
15282
15480
  "Inspect or run these capability-owned files when needed:",
15283
- ...toolFiles.map((file) => `- ${path36.join(toolRoot, file)}`)
15481
+ ...toolFiles.map((file) => `- ${path37.join(toolRoot, file)}`)
15284
15482
  ] : []
15285
15483
  ].join("\n");
15286
15484
  };
@@ -15588,8 +15786,8 @@ var init_loadIssueStateComment = __esm({
15588
15786
  });
15589
15787
 
15590
15788
  // src/scripts/loadJobFromFile.ts
15591
- import * as fs40 from "fs";
15592
- import * as path37 from "path";
15789
+ import * as fs41 from "fs";
15790
+ import * as path38 from "path";
15593
15791
  function parseJobFile(raw, slug) {
15594
15792
  let stripped = raw;
15595
15793
  if (stripped.startsWith("---\n")) {
@@ -15628,10 +15826,10 @@ var init_loadJobFromFile = __esm({
15628
15826
  if (!slug) {
15629
15827
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
15630
15828
  }
15631
- const capability = resolveCapabilityFolder(slug, path37.resolve(ctx.cwd, jobsDir));
15829
+ const capability = resolveCapabilityFolder(slug, path38.resolve(ctx.cwd, jobsDir));
15632
15830
  if (!capability) {
15633
15831
  throw new Error(
15634
- `loadJobFromFile: capability folder not found or incomplete: ${path37.resolve(ctx.cwd, jobsDir, slug)}`
15832
+ `loadJobFromFile: capability folder not found or incomplete: ${path38.resolve(ctx.cwd, jobsDir, slug)}`
15635
15833
  );
15636
15834
  }
15637
15835
  const { title, body, config } = capability;
@@ -15641,12 +15839,12 @@ var init_loadJobFromFile = __esm({
15641
15839
  let agentIdentity = "";
15642
15840
  if (agentSlug) {
15643
15841
  const agentPath = resolveAgentFile(ctx.cwd, agentSlug, agentsDir);
15644
- if (!fs40.existsSync(agentPath)) {
15842
+ if (!fs41.existsSync(agentPath)) {
15645
15843
  throw new Error(
15646
15844
  `loadJobFromFile: capability '${slug}' declares agent '${agentSlug}' but ${agentPath} does not exist`
15647
15845
  );
15648
15846
  }
15649
- const agentRaw = fs40.readFileSync(agentPath, "utf-8");
15847
+ const agentRaw = fs41.readFileSync(agentPath, "utf-8");
15650
15848
  const parsed = parseJobFile(agentRaw, agentSlug);
15651
15849
  agentTitle = parsed.title;
15652
15850
  agentIdentity = parsed.body;
@@ -15726,13 +15924,13 @@ ${truncate2(issue2.body, FINDING_BODY_MAX_BYTES)}`;
15726
15924
  });
15727
15925
 
15728
15926
  // src/scripts/kodyVariables.ts
15729
- import * as fs41 from "fs";
15730
- import * as path38 from "path";
15927
+ import * as fs42 from "fs";
15928
+ import * as path39 from "path";
15731
15929
  function readKodyVariables(cwd) {
15732
- const full = path38.join(cwd, KODY_VARIABLES_REL_PATH);
15930
+ const full = path39.join(cwd, KODY_VARIABLES_REL_PATH);
15733
15931
  let raw;
15734
15932
  try {
15735
- raw = fs41.readFileSync(full, "utf-8");
15933
+ raw = fs42.readFileSync(full, "utf-8");
15736
15934
  } catch {
15737
15935
  return {};
15738
15936
  }
@@ -15908,8 +16106,8 @@ var init_runtimeSecrets = __esm({
15908
16106
  });
15909
16107
 
15910
16108
  // src/scripts/loadQaContext.ts
15911
- import * as fs42 from "fs";
15912
- import * as path39 from "path";
16109
+ import * as fs43 from "fs";
16110
+ import * as path40 from "path";
15913
16111
  function parseSlugList(value) {
15914
16112
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
15915
16113
  return inner.split(",").map(
@@ -15938,18 +16136,18 @@ function readProfileAgents(raw) {
15938
16136
  return { agent: agent ?? legacy ?? ["kody"], body };
15939
16137
  }
15940
16138
  function readProfile(cwd) {
15941
- const dir = path39.join(cwd, CONTEXT_DIR_REL_PATH);
15942
- if (!fs42.existsSync(dir)) return "";
16139
+ const dir = path40.join(cwd, CONTEXT_DIR_REL_PATH);
16140
+ if (!fs43.existsSync(dir)) return "";
15943
16141
  let entries;
15944
16142
  try {
15945
- entries = fs42.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
16143
+ entries = fs43.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
15946
16144
  } catch {
15947
16145
  return "";
15948
16146
  }
15949
16147
  const blocks = [];
15950
16148
  for (const file of entries) {
15951
16149
  try {
15952
- const raw = fs42.readFileSync(path39.join(dir, file), "utf-8");
16150
+ const raw = fs43.readFileSync(path40.join(dir, file), "utf-8");
15953
16151
  const { agent, body } = readProfileAgents(raw);
15954
16152
  if (!agent.includes(QA_AGENT) && !agent.includes(ALL_AGENTS)) continue;
15955
16153
  blocks.push(`## ${file}
@@ -15998,8 +16196,8 @@ var init_loadQaContext = __esm({
15998
16196
  });
15999
16197
 
16000
16198
  // src/taskContext.ts
16001
- import * as fs43 from "fs";
16002
- import * as path40 from "path";
16199
+ import * as fs44 from "fs";
16200
+ import * as path41 from "path";
16003
16201
  function buildTaskContext(args) {
16004
16202
  return {
16005
16203
  schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
@@ -16015,9 +16213,9 @@ function buildTaskContext(args) {
16015
16213
  function persistTaskContext(cwd, ctx) {
16016
16214
  try {
16017
16215
  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)}
16216
+ fs44.mkdirSync(dir, { recursive: true });
16217
+ const file = path41.join(dir, "task-context.json");
16218
+ fs44.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16021
16219
  `);
16022
16220
  return file;
16023
16221
  } catch (err) {
@@ -16842,7 +17040,9 @@ function parseOutput(text2) {
16842
17040
  const labelledOutput = parseSingleJsonCandidate(jsonFences.map((match) => match[2]));
16843
17041
  if (labelledOutput.found) return labelledOutput.value;
16844
17042
  const plainOutput = parseSingleJsonCandidate(fences.filter((match) => !match[1]).map((match) => match[2]));
16845
- return plainOutput.found ? plainOutput.value : void 0;
17043
+ if (plainOutput.found) return plainOutput.value;
17044
+ const legacyText = text2.trim();
17045
+ return legacyText ? { summary: legacyText, output: legacyText } : void 0;
16846
17046
  }
16847
17047
  function parseSingleJsonCandidate(candidates) {
16848
17048
  const parsed = [];
@@ -16855,7 +17055,7 @@ function parseSingleJsonCandidate(candidates) {
16855
17055
  }
16856
17056
  return parsed.length === 1 ? { found: true, value: parsed[0] } : { found: false };
16857
17057
  }
16858
- function isObject(value) {
17058
+ function isObject2(value) {
16859
17059
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
16860
17060
  }
16861
17061
  function stringValue4(value) {
@@ -16873,8 +17073,8 @@ var init_parseSimpleCapabilityOutput = __esm({
16873
17073
  return;
16874
17074
  }
16875
17075
  ctx.data.capabilityOutput = output;
16876
- const result = isObject(output) ? output : {};
16877
- const data = isObject(result.data) ? result.data : isObject(output) ? output : { output };
17076
+ const result = isObject2(output) ? output : {};
17077
+ const data = isObject2(result.data) ? result.data : isObject2(output) ? output : { output };
16878
17078
  const summary = typeof result.summary === "string" ? result.summary : "Capability completed";
16879
17079
  const reason = typeof result.reason === "string" ? result.reason : summary;
16880
17080
  const prUrl = stringValue4(data.pullRequestUrl) ?? stringValue4(data.prUrl) ?? stringValue4(result.pullRequestUrl) ?? stringValue4(result.prUrl);
@@ -17465,9 +17665,9 @@ var init_postResearchComment = __esm({
17465
17665
  });
17466
17666
 
17467
17667
  // src/scripts/prepareBrowserAuth.ts
17468
- import * as fs44 from "fs";
17668
+ import * as fs45 from "fs";
17469
17669
  import * as os6 from "os";
17470
- import * as path41 from "path";
17670
+ import * as path42 from "path";
17471
17671
  function appendAuthMessage(ctx, message) {
17472
17672
  const current = typeof ctx.data.qaAuthBlock === "string" ? ctx.data.qaAuthBlock.trim() : "";
17473
17673
  ctx.data.qaAuthBlock = current ? `${current}
@@ -17506,9 +17706,9 @@ async function githubJson(url, token) {
17506
17706
  return await response.json();
17507
17707
  }
17508
17708
  function writeKodyStorageState(input) {
17509
- const directory = fs44.mkdtempSync(path41.join(os6.tmpdir(), "kody-browser-auth-"));
17510
- fs44.chmodSync(directory, 448);
17511
- const file = path41.join(directory, "storage-state.json");
17709
+ const directory = fs45.mkdtempSync(path42.join(os6.tmpdir(), "kody-browser-auth-"));
17710
+ fs45.chmodSync(directory, 448);
17711
+ const file = path42.join(directory, "storage-state.json");
17512
17712
  const now = Date.now();
17513
17713
  const repoEntry = {
17514
17714
  repoUrl: input.repoUrl,
@@ -17538,7 +17738,7 @@ function writeKodyStorageState(input) {
17538
17738
  }
17539
17739
  ]
17540
17740
  };
17541
- fs44.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17741
+ fs45.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17542
17742
  return { directory, file };
17543
17743
  }
17544
17744
  function configurePlaywright(profile, storageStatePath) {
@@ -17620,7 +17820,7 @@ async function prepareMethod(ctx, profile, method) {
17620
17820
  configurePlaywright(profile, state.file);
17621
17821
  const authDirectory = state.directory;
17622
17822
  registerRuntimeCleanup(ctx, () => {
17623
- fs44.rmSync(authDirectory, { recursive: true, force: true });
17823
+ fs45.rmSync(authDirectory, { recursive: true, force: true });
17624
17824
  });
17625
17825
  appendAuthMessage(
17626
17826
  ctx,
@@ -17628,7 +17828,7 @@ async function prepareMethod(ctx, profile, method) {
17628
17828
  );
17629
17829
  return true;
17630
17830
  } catch (error) {
17631
- if (state) fs44.rmSync(state.directory, { recursive: true, force: true });
17831
+ if (state) fs45.rmSync(state.directory, { recursive: true, force: true });
17632
17832
  const reason = error instanceof Error ? error.message : String(error);
17633
17833
  appendAuthMessage(
17634
17834
  ctx,
@@ -18727,12 +18927,12 @@ fi
18727
18927
 
18728
18928
  // src/scripts/runPreviewBuild.ts
18729
18929
  import { copyFile, writeFile } from "fs/promises";
18730
- import * as path42 from "path";
18930
+ import * as path43 from "path";
18731
18931
  import { fileURLToPath as fileURLToPath2 } from "url";
18732
18932
  function bundledDockerfilePath(mode) {
18733
- const here = path42.dirname(fileURLToPath2(import.meta.url));
18933
+ const here = path43.dirname(fileURLToPath2(import.meta.url));
18734
18934
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
18735
- return path42.join(here, "preview-build-templates", file);
18935
+ return path43.join(here, "preview-build-templates", file);
18736
18936
  }
18737
18937
  function required(name) {
18738
18938
  const v = (process.env[name] ?? "").trim();
@@ -18967,10 +19167,10 @@ var init_runPreviewBuild = __esm({
18967
19167
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
18968
19168
  if (Object.keys(buildEnv).length > 0) {
18969
19169
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
18970
- await writeFile(path42.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
19170
+ await writeFile(path43.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
18971
19171
  `, "utf8");
18972
19172
  }
18973
- const consumerDockerfile = path42.join(ctx.cwd, "Dockerfile.preview");
19173
+ const consumerDockerfile = path43.join(ctx.cwd, "Dockerfile.preview");
18974
19174
  const { stat } = await import("fs/promises");
18975
19175
  let hasConsumerDockerfile = false;
18976
19176
  try {
@@ -19154,8 +19354,8 @@ var init_tickShellRunner = __esm({
19154
19354
  });
19155
19355
 
19156
19356
  // src/scripts/runScheduledImplementationTick.ts
19157
- import * as fs45 from "fs";
19158
- import * as path43 from "path";
19357
+ import * as fs46 from "fs";
19358
+ import * as path44 from "path";
19159
19359
  var runScheduledImplementationTick;
19160
19360
  var init_runScheduledImplementationTick = __esm({
19161
19361
  "src/scripts/runScheduledImplementationTick.ts"() {
@@ -19176,14 +19376,14 @@ var init_runScheduledImplementationTick = __esm({
19176
19376
  ctx.output.reason = `runScheduledImplementationTick: args.slug or ctx.args.${slugArg} must be non-empty capability slug`;
19177
19377
  return;
19178
19378
  }
19179
- const capability = resolveCapabilityFolder(slug, path43.resolve(ctx.cwd, jobsDir));
19379
+ const capability = resolveCapabilityFolder(slug, path44.resolve(ctx.cwd, jobsDir));
19180
19380
  if (!capability) {
19181
19381
  ctx.output.exitCode = 99;
19182
19382
  ctx.output.reason = `runScheduledImplementationTick: capability folder not found or incomplete: ${slug} (searched ${jobsDir} and company store)`;
19183
19383
  return;
19184
19384
  }
19185
- const shellPath = path43.join(profile.dir, shell);
19186
- if (!fs45.existsSync(shellPath)) {
19385
+ const shellPath = path44.join(profile.dir, shell);
19386
+ if (!fs46.existsSync(shellPath)) {
19187
19387
  ctx.output.exitCode = 99;
19188
19388
  ctx.output.reason = `runScheduledImplementationTick: shell not found: ${shell} (looked in ${profile.dir})`;
19189
19389
  return;
@@ -19214,8 +19414,8 @@ var init_runScheduledImplementationTick = __esm({
19214
19414
  });
19215
19415
 
19216
19416
  // src/scripts/runTickScript.ts
19217
- import * as fs46 from "fs";
19218
- import * as path44 from "path";
19417
+ import * as fs47 from "fs";
19418
+ import * as path45 from "path";
19219
19419
  var runTickScript;
19220
19420
  var init_runTickScript = __esm({
19221
19421
  "src/scripts/runTickScript.ts"() {
@@ -19235,10 +19435,10 @@ var init_runTickScript = __esm({
19235
19435
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
19236
19436
  return;
19237
19437
  }
19238
- const capability = readCapabilityFolder(path44.resolve(ctx.cwd, jobsDir), slug);
19438
+ const capability = readCapabilityFolder(path45.resolve(ctx.cwd, jobsDir), slug);
19239
19439
  if (!capability) {
19240
19440
  ctx.output.exitCode = 99;
19241
- ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path44.resolve(ctx.cwd, jobsDir, slug)}`;
19441
+ ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path45.resolve(ctx.cwd, jobsDir, slug)}`;
19242
19442
  return;
19243
19443
  }
19244
19444
  const tickScript = capability.config.tickScript;
@@ -19247,8 +19447,8 @@ var init_runTickScript = __esm({
19247
19447
  ctx.output.reason = `runTickScript: capability ${slug} has no \`tickScript\` in profile.json \u2014 route via capability-tick instead`;
19248
19448
  return;
19249
19449
  }
19250
- const scriptPath = path44.isAbsolute(tickScript) ? tickScript : path44.join(ctx.cwd, tickScript);
19251
- if (!fs46.existsSync(scriptPath)) {
19450
+ const scriptPath = path45.isAbsolute(tickScript) ? tickScript : path45.join(ctx.cwd, tickScript);
19451
+ if (!fs47.existsSync(scriptPath)) {
19252
19452
  ctx.output.exitCode = 99;
19253
19453
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
19254
19454
  return;
@@ -19530,7 +19730,7 @@ var init_syncFlow = __esm({
19530
19730
  });
19531
19731
 
19532
19732
  // src/scripts/validateAgencyModelProposal.ts
19533
- import * as path45 from "path";
19733
+ import * as path46 from "path";
19534
19734
  function validateModelBundle(bundle, expectedKind, options = {}) {
19535
19735
  const failures = [];
19536
19736
  validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind, options);
@@ -19848,7 +20048,7 @@ var init_validateAgencyModelProposal = __esm({
19848
20048
  const bundle = parseAgencyModelProposal(raw);
19849
20049
  const expectedKind = readExpectedModelKind(args);
19850
20050
  const failures = validateModelBundle(bundle, expectedKind, {
19851
- capabilityRoot: path45.join(ctx.cwd, ".kody", "capabilities")
20051
+ capabilityRoot: path46.join(ctx.cwd, ".kody", "capabilities")
19852
20052
  });
19853
20053
  if (failures.length > 0) {
19854
20054
  throw new Error(`validateAgencyModelProposal: ${failures.join("; ")}`);
@@ -20399,7 +20599,7 @@ var init_warmupMcp = __esm({
20399
20599
  });
20400
20600
 
20401
20601
  // src/scripts/writeAgentRunSummary.ts
20402
- import * as fs47 from "fs";
20602
+ import * as fs48 from "fs";
20403
20603
  var writeAgentRunSummary;
20404
20604
  var init_writeAgentRunSummary = __esm({
20405
20605
  "src/scripts/writeAgentRunSummary.ts"() {
@@ -20425,7 +20625,7 @@ var init_writeAgentRunSummary = __esm({
20425
20625
  if (reason) lines.push(`- **Reason:** ${reason}`);
20426
20626
  lines.push("");
20427
20627
  try {
20428
- fs47.appendFileSync(summaryPath, `${lines.join("\n")}
20628
+ fs48.appendFileSync(summaryPath, `${lines.join("\n")}
20429
20629
  `);
20430
20630
  } catch {
20431
20631
  }
@@ -20562,6 +20762,7 @@ var init_scripts = __esm({
20562
20762
  init_dispatchCapabilityTicks();
20563
20763
  init_dispatchClassified();
20564
20764
  init_dispatchAgencyLoops();
20765
+ init_dispatchSimpleLoops();
20565
20766
  init_dispatchNextTaskJob();
20566
20767
  init_ensurePr();
20567
20768
  init_evaluateAgencyBoundaries();
@@ -20686,6 +20887,7 @@ var init_scripts = __esm({
20686
20887
  warmupMcp,
20687
20888
  dispatchCapabilityTicks,
20688
20889
  dispatchAgencyLoops,
20890
+ dispatchSimpleLoops,
20689
20891
  dispatchCapabilityFileTicks,
20690
20892
  planTaskJobs,
20691
20893
  dispatchNextTaskJob,
@@ -20757,17 +20959,17 @@ var init_scripts = __esm({
20757
20959
  });
20758
20960
 
20759
20961
  // src/stateWorkspace.ts
20760
- import * as fs48 from "fs";
20761
- import * as path46 from "path";
20962
+ import * as fs49 from "fs";
20963
+ import * as path47 from "path";
20762
20964
  function tenantId(config) {
20763
20965
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
20764
20966
  const repo = config.github?.repo?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
20765
20967
  return owner && repo ? `${owner}/${repo}` : null;
20766
20968
  }
20767
20969
  function writeRuntimeFile(cwd, relativePath, content) {
20768
- const target = path46.join(cwd, RUNTIME_ROOT, relativePath);
20769
- fs48.mkdirSync(path46.dirname(target), { recursive: true });
20770
- fs48.writeFileSync(target, content, "utf8");
20970
+ const target = path47.join(cwd, RUNTIME_ROOT, relativePath);
20971
+ fs49.mkdirSync(path47.dirname(target), { recursive: true });
20972
+ fs49.writeFileSync(target, content, "utf8");
20771
20973
  }
20772
20974
  function record(value) {
20773
20975
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -20832,11 +21034,11 @@ async function hydrateStateWorkspace(config, cwd, backendOverride) {
20832
21034
  throw new Error("Kody backend access is required for runtime workspace documents");
20833
21035
  return;
20834
21036
  }
20835
- const key = `${path46.resolve(cwd)}|${tenant}`;
21037
+ const key = `${path47.resolve(cwd)}|${tenant}`;
20836
21038
  if (hydratedWorkspaces.has(key)) return;
20837
21039
  const backend = backendOverride ?? createStateBackendFromEnv();
20838
- const root = path46.join(cwd, RUNTIME_ROOT);
20839
- fs48.rmSync(root, { recursive: true, force: true });
21040
+ const root = path47.join(cwd, RUNTIME_ROOT);
21041
+ fs49.rmSync(root, { recursive: true, force: true });
20840
21042
  await Promise.all([
20841
21043
  hydratePrefix(backend, tenant, cwd, "context:"),
20842
21044
  hydratePrefix(backend, tenant, cwd, "memory:"),
@@ -20852,7 +21054,7 @@ var init_stateWorkspace = __esm({
20852
21054
  "src/stateWorkspace.ts"() {
20853
21055
  "use strict";
20854
21056
  init_state_backend();
20855
- RUNTIME_ROOT = path46.join(".kody-engine", "runtime");
21057
+ RUNTIME_ROOT = path47.join(".kody-engine", "runtime");
20856
21058
  hydratedWorkspaces = /* @__PURE__ */ new Set();
20857
21059
  }
20858
21060
  });
@@ -20923,9 +21125,9 @@ var init_tools = __esm({
20923
21125
 
20924
21126
  // src/executor.ts
20925
21127
  import { spawn as spawn8 } from "child_process";
20926
- import * as fs49 from "fs";
21128
+ import * as fs50 from "fs";
20927
21129
  import * as os7 from "os";
20928
- import * as path47 from "path";
21130
+ import * as path48 from "path";
20929
21131
  function isMutatingPostflight(scriptName) {
20930
21132
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
20931
21133
  }
@@ -21166,7 +21368,7 @@ async function runImplementation(profileName, input) {
21166
21368
  const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
21167
21369
  const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
21168
21370
  const invokeAgent = async (prompt) => {
21169
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path47.isAbsolute(p) ? p : path47.resolve(profile.dir, p)).filter((p) => p.length > 0);
21371
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path48.isAbsolute(p) ? p : path48.resolve(profile.dir, p)).filter((p) => p.length > 0);
21170
21372
  const syntheticPath = ctx.data.syntheticPluginPath;
21171
21373
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
21172
21374
  const agents = loadSubagents(profile);
@@ -21630,17 +21832,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
21630
21832
  function resolveProfilePath(profileName, cwd = process.cwd()) {
21631
21833
  const found = resolveImplementation(profileName, getRuntimeProfileRootsForCwd(cwd));
21632
21834
  if (found) return found;
21633
- const here = path47.dirname(new URL(import.meta.url).pathname);
21835
+ const here = path48.dirname(new URL(import.meta.url).pathname);
21634
21836
  const candidates = [
21635
- path47.join(here, "implementations", profileName, "profile.json"),
21837
+ path48.join(here, "implementations", profileName, "profile.json"),
21636
21838
  // same-dir sibling (dev)
21637
- path47.join(here, "..", "implementations", profileName, "profile.json"),
21839
+ path48.join(here, "..", "implementations", profileName, "profile.json"),
21638
21840
  // up one (prod: dist/bin → dist/implementations)
21639
- path47.join(here, "..", "src", "implementations", profileName, "profile.json")
21841
+ path48.join(here, "..", "src", "implementations", profileName, "profile.json")
21640
21842
  // fallback
21641
21843
  ];
21642
21844
  for (const c of candidates) {
21643
- if (fs49.existsSync(c)) return c;
21845
+ if (fs50.existsSync(c)) return c;
21644
21846
  }
21645
21847
  return candidates[0];
21646
21848
  }
@@ -21755,15 +21957,15 @@ function resolveShellTimeoutMs(entry) {
21755
21957
  }
21756
21958
  async function runShellEntry(entry, ctx, profile) {
21757
21959
  const shellName = entry.shell;
21758
- const shellPath = path47.join(profile.dir, shellName);
21759
- if (!fs49.existsSync(shellPath)) {
21960
+ const shellPath = path48.join(profile.dir, shellName);
21961
+ if (!fs50.existsSync(shellPath)) {
21760
21962
  ctx.skipAgent = true;
21761
21963
  ctx.output.exitCode = 99;
21762
21964
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
21763
21965
  return;
21764
21966
  }
21765
21967
  const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
21766
- const outputFile = path47.join(
21968
+ const outputFile = path48.join(
21767
21969
  os7.tmpdir(),
21768
21970
  `kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
21769
21971
  );
@@ -21835,9 +22037,9 @@ async function runShellEntry(entry, ctx, profile) {
21835
22037
  }
21836
22038
  let sideChannelText = "";
21837
22039
  try {
21838
- if (fs49.existsSync(outputFile)) {
21839
- sideChannelText = fs49.readFileSync(outputFile, "utf-8");
21840
- fs49.rmSync(outputFile, { force: true });
22040
+ if (fs50.existsSync(outputFile)) {
22041
+ sideChannelText = fs50.readFileSync(outputFile, "utf-8");
22042
+ fs50.rmSync(outputFile, { force: true });
21841
22043
  }
21842
22044
  } catch {
21843
22045
  }
@@ -22009,7 +22211,7 @@ __export(job_exports, {
22009
22211
  stableJobKey: () => stableJobKey,
22010
22212
  validateJob: () => validateJob
22011
22213
  });
22012
- import * as path48 from "path";
22214
+ import * as path49 from "path";
22013
22215
  function newJobId(flavor) {
22014
22216
  localJobSeq += 1;
22015
22217
  const runId = process.env.GITHUB_RUN_ID;
@@ -22710,7 +22912,7 @@ function loadCapabilityContext(slug, cwd) {
22710
22912
  return resolveCapabilityFolder(slug, hydratedCapabilitiesRoot(cwd));
22711
22913
  }
22712
22914
  function hydratedCapabilitiesRoot(cwd) {
22713
- return path48.join(cwd, ".kody-engine", "definitions", "capabilities");
22915
+ return path49.join(cwd, ".kody-engine", "definitions", "capabilities");
22714
22916
  }
22715
22917
  function loadWorkflowContext(slug, base) {
22716
22918
  if (!slug || !isWorkflowDefinitionId(slug)) return null;
@@ -24686,82 +24888,7 @@ function readRunRequestFromEnv(env = process.env) {
24686
24888
 
24687
24889
  // src/kody-cli.ts
24688
24890
  init_runtimePaths();
24689
-
24690
- // src/loopDefinitions.ts
24691
- init_definition_paths();
24692
- import * as fs50 from "fs";
24693
- import * as path49 from "path";
24694
- var ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
24695
- function normalizeLoopDefinition(value) {
24696
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
24697
- const raw = value;
24698
- if (Object.keys(raw).some((key) => !["id", "trigger", "target", "input", "enabled"].includes(key))) return null;
24699
- if (typeof raw.id !== "string" || !ID.test(raw.id)) return null;
24700
- if (typeof raw.enabled !== "boolean") return null;
24701
- if (!isObject2(raw.input) || !isObject2(raw.trigger) || !isObject2(raw.target)) return null;
24702
- const targetKind = raw.target.kind;
24703
- const targetId = raw.target.id;
24704
- if (targetKind !== "workflow" && targetKind !== "capability" || typeof targetId !== "string" || !ID.test(targetId)) {
24705
- return null;
24706
- }
24707
- const trigger = normalizeTrigger(raw.trigger);
24708
- if (!trigger) return null;
24709
- return {
24710
- id: raw.id,
24711
- trigger,
24712
- target: { kind: targetKind, id: targetId },
24713
- input: raw.input,
24714
- enabled: raw.enabled
24715
- };
24716
- }
24717
- function readLoopDefinition(cwd, id) {
24718
- if (!ID.test(id)) return null;
24719
- const roots = [
24720
- path49.join(cwd, ".kody-engine", "runtime"),
24721
- definitionsRoot(cwd)
24722
- ];
24723
- for (const root of roots) {
24724
- const filePath = path49.join(root, "loops", id, "loop.json");
24725
- if (!fs50.existsSync(filePath)) continue;
24726
- try {
24727
- const loop = normalizeLoopDefinition(JSON.parse(fs50.readFileSync(filePath, "utf8")));
24728
- if (loop?.id === id) return loop;
24729
- process.stderr.write(`[kody] invalid simple Loop definition: ${filePath}
24730
- `);
24731
- } catch {
24732
- process.stderr.write(`[kody] unreadable simple Loop definition: ${filePath}
24733
- `);
24734
- }
24735
- }
24736
- process.stderr.write(
24737
- `[kody] simple Loop not found: ${id} (${roots.map((root) => path49.join(root, "loops", id, "loop.json")).join(", ")})
24738
- `
24739
- );
24740
- return null;
24741
- }
24742
- function normalizeTrigger(raw) {
24743
- if (raw.type === "manual" && Object.keys(raw).length === 1) return { type: "manual" };
24744
- if (raw.type === "schedule" && typeof raw.every === "string" && /^\d+[mhd]$/.test(raw.every)) {
24745
- if (raw.at === void 0 && Object.keys(raw).every((key) => key === "type" || key === "every")) {
24746
- return { type: "schedule", every: raw.every };
24747
- }
24748
- 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")) {
24749
- return { type: "schedule", every: raw.every, at: { time: raw.at.time, timezone: raw.at.timezone } };
24750
- }
24751
- }
24752
- if ((raw.type === "event" || raw.type === "webhook") && typeof raw.event === "string" && raw.event.trim() && Object.keys(raw).every((key) => key === "type" || key === "event")) {
24753
- return { type: raw.type, event: raw.event.trim() };
24754
- }
24755
- if (raw.type === "condition" && typeof raw.expression === "string" && raw.expression.trim() && Object.keys(raw).every((key) => key === "type" || key === "expression")) {
24756
- return { type: "condition", expression: raw.expression.trim() };
24757
- }
24758
- return null;
24759
- }
24760
- function isObject2(value) {
24761
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
24762
- }
24763
-
24764
- // src/kody-cli.ts
24891
+ init_loopDefinitions();
24765
24892
  init_stateWorkspace();
24766
24893
  init_workflowDefinitions();
24767
24894
  var FAILED_DISPATCH_LABEL = {
@@ -26443,7 +26570,7 @@ init_config();
26443
26570
  init_fetchRepoMcp();
26444
26571
 
26445
26572
  // src/servers/mcpHttpServer.ts
26446
- import { randomUUID as randomUUID2 } from "crypto";
26573
+ import { randomUUID as randomUUID3 } from "crypto";
26447
26574
  import { createServer as createServer4 } from "http";
26448
26575
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
26449
26576
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -26452,7 +26579,7 @@ function buildMcpHttpServer(opts) {
26452
26579
  const transports = /* @__PURE__ */ new Map();
26453
26580
  for (const route of opts.routes) {
26454
26581
  const transport = new StreamableHTTPServerTransport({
26455
- sessionIdGenerator: () => randomUUID2()
26582
+ sessionIdGenerator: () => randomUUID3()
26456
26583
  });
26457
26584
  transports.set(route.path, transport);
26458
26585
  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
File without changes
@@ -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.444",
3
+ "version": "0.4.446",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,29 +12,6 @@
12
12
  "templates",
13
13
  "kody.config.schema.json"
14
14
  ],
15
- "scripts": {
16
- "kody:run": "tsx bin/kody.ts",
17
- "serve": "tsx bin/kody.ts serve",
18
- "serve:vscode": "tsx bin/kody.ts serve vscode",
19
- "serve:claude": "tsx bin/kody.ts serve claude",
20
- "clean:dist": "node scripts/clean-dist.cjs",
21
- "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
22
- "check:modularity": "tsx scripts/check-script-modularity.ts",
23
- "pretest": "pnpm check:modularity",
24
- "test": "vitest run tests/unit tests/int --coverage",
25
- "posttest": "tsx scripts/check-coverage-floor.ts",
26
- "test:smoke": "vitest run tests/smoke --no-coverage",
27
- "test:e2e": "vitest run tests/e2e --no-coverage",
28
- "test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
29
- "test:all": "vitest run tests --no-coverage",
30
- "typecheck": "tsc --noEmit",
31
- "lint": "biome check",
32
- "lint:fix": "biome check --write",
33
- "format": "biome format --write",
34
- "verify:package": "node scripts/verify-package-tarball.cjs",
35
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
36
- "prepublishOnly": "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm test:runtime-services && pnpm build && pnpm verify:package"
37
- },
38
15
  "dependencies": {
39
16
  "@actions/cache": "^6.0.0",
40
17
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
@@ -61,5 +38,27 @@
61
38
  "url": "git+https://github.com/aharonyaircohen/kody-engine.git"
62
39
  },
63
40
  "homepage": "https://github.com/aharonyaircohen/kody-engine",
64
- "bugs": "https://github.com/aharonyaircohen/kody-engine/issues"
65
- }
41
+ "bugs": "https://github.com/aharonyaircohen/kody-engine/issues",
42
+ "scripts": {
43
+ "kody:run": "tsx bin/kody.ts",
44
+ "serve": "tsx bin/kody.ts serve",
45
+ "serve:vscode": "tsx bin/kody.ts serve vscode",
46
+ "serve:claude": "tsx bin/kody.ts serve claude",
47
+ "clean:dist": "node scripts/clean-dist.cjs",
48
+ "build": "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
49
+ "check:modularity": "tsx scripts/check-script-modularity.ts",
50
+ "pretest": "pnpm check:modularity",
51
+ "test": "vitest run tests/unit tests/int --coverage",
52
+ "posttest": "tsx scripts/check-coverage-floor.ts",
53
+ "test:smoke": "vitest run tests/smoke --no-coverage",
54
+ "test:e2e": "vitest run tests/e2e --no-coverage",
55
+ "test:runtime-services": "node --test \"tests/runtime-services/*.test.mjs\"",
56
+ "test:all": "vitest run tests --no-coverage",
57
+ "typecheck": "tsc --noEmit",
58
+ "lint": "biome check",
59
+ "lint:fix": "biome check --write",
60
+ "format": "biome format --write",
61
+ "verify:package": "node scripts/verify-package-tarball.cjs",
62
+ "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain --build-arg KODY_ENGINE_REF=$(git rev-parse HEAD) -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner"
63
+ }
64
+ }