@kody-ade/kody-engine 0.4.434 → 0.4.436

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.434",
18
+ version: "0.4.436",
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",
@@ -47,7 +47,7 @@ var init_package = __esm({
47
47
  "lint:fix": "biome check --write",
48
48
  format: "biome format --write",
49
49
  "verify:package": "node scripts/verify-package-tarball.cjs",
50
- "brain:publish": "docker buildx build --platform linux/amd64 -f runner/Dockerfile.brain -t ghcr.io/${KODY_BRAIN_GHCR_OWNER:-aharonyaircohen}/kody-brain:latest --push runner",
50
+ "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",
51
51
  prepublishOnly: "pnpm typecheck && vitest run tests/unit tests/int --no-coverage && pnpm test:runtime-services && pnpm build && pnpm verify:package"
52
52
  },
53
53
  dependencies: {
@@ -1104,7 +1104,7 @@ function cmsHeaders(opts) {
1104
1104
  }
1105
1105
  };
1106
1106
  }
1107
- async function callDashboardCms(opts, path52, init = {}) {
1107
+ async function callDashboardCms(opts, path54, init = {}) {
1108
1108
  const baseUrl = dashboardBaseUrl(opts);
1109
1109
  if (!baseUrl) {
1110
1110
  return {
@@ -1116,7 +1116,7 @@ async function callDashboardCms(opts, path52, init = {}) {
1116
1116
  const headerResult = cmsHeaders(opts);
1117
1117
  if (!headerResult.ok) return headerResult;
1118
1118
  try {
1119
- const res = await fetch(`${baseUrl}${path52}`, {
1119
+ const res = await fetch(`${baseUrl}${path54}`, {
1120
1120
  ...init,
1121
1121
  headers: {
1122
1122
  ...headerResult.headers,
@@ -1188,8 +1188,8 @@ function documentArg(value) {
1188
1188
  function normalizeCmsDocumentIdInput(input) {
1189
1189
  const trimmed = stripWrappingQuotes(input.trim());
1190
1190
  const withoutQuery = trimmed.split(/[?#]/, 1)[0] ?? trimmed;
1191
- const path52 = parseDocumentPath(withoutQuery);
1192
- return path52 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1191
+ const path54 = parseDocumentPath(withoutQuery);
1192
+ return path54 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1193
1193
  }
1194
1194
  function stripWrappingQuotes(value) {
1195
1195
  let current = value;
@@ -1200,9 +1200,9 @@ function stripWrappingQuotes(value) {
1200
1200
  }
1201
1201
  }
1202
1202
  function parseDocumentPath(value) {
1203
- const path52 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1204
- if (!path52?.includes("/content/entries/")) return null;
1205
- const parts = path52.split("/").filter(Boolean).map(decodePathPart);
1203
+ const path54 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1204
+ if (!path54?.includes("/content/entries/")) return null;
1205
+ const parts = path54.split("/").filter(Boolean).map(decodePathPart);
1206
1206
  const entriesIndex = parts.findIndex((part, index) => part === "content" && parts[index + 1] === "entries");
1207
1207
  const idPart = parts[entriesIndex + 3];
1208
1208
  if (!idPart || idPart === "new") return null;
@@ -1646,56 +1646,6 @@ var init_issue = __esm({
1646
1646
  }
1647
1647
  });
1648
1648
 
1649
- // src/agency/implementation-resolution.ts
1650
- function resolveCapabilityImplementation(input) {
1651
- const compatible = input.implementations.filter(
1652
- (implementation) => implementation.capabilityRef.id === input.capabilityId && implementation.compatibleCapabilityRevision === input.capabilityRevision
1653
- );
1654
- if (input.explicitOverride) {
1655
- if (!input.authorizeOverride?.(input.explicitOverride)) {
1656
- throw new ImplementationResolutionError(`Implementation override "${input.explicitOverride}" is not authorized`);
1657
- }
1658
- return selectNamed(input.explicitOverride, input, compatible, "override");
1659
- }
1660
- if (input.repositoryBinding) {
1661
- return selectNamed(input.repositoryBinding, input, compatible, "repository binding");
1662
- }
1663
- if (compatible.length === 1) return compatible[0];
1664
- if (compatible.length === 0) {
1665
- throw new ImplementationResolutionError(
1666
- `No compatible Implementation is available for Capability "${input.capabilityId}" at revision "${input.capabilityRevision}"`
1667
- );
1668
- }
1669
- throw new ImplementationResolutionError(
1670
- `Capability "${input.capabilityId}" has ${compatible.length} compatible Implementations; configure a repository binding`
1671
- );
1672
- }
1673
- function selectNamed(id, input, compatible, source) {
1674
- const known = input.implementations.find((implementation) => implementation.id === id);
1675
- if (!known) {
1676
- throw new ImplementationResolutionError(`Implementation ${source} "${id}" is not available`);
1677
- }
1678
- const selected = compatible.find((implementation) => implementation.id === id);
1679
- if (!selected) {
1680
- throw new ImplementationResolutionError(
1681
- `Implementation ${source} "${id}" is not compatible with Capability "${input.capabilityId}" at revision "${input.capabilityRevision}"`
1682
- );
1683
- }
1684
- return selected;
1685
- }
1686
- var ImplementationResolutionError;
1687
- var init_implementation_resolution = __esm({
1688
- "src/agency/implementation-resolution.ts"() {
1689
- "use strict";
1690
- ImplementationResolutionError = class extends Error {
1691
- constructor(message) {
1692
- super(message);
1693
- this.name = "ImplementationResolutionError";
1694
- }
1695
- };
1696
- }
1697
- });
1698
-
1699
1649
  // src/capabilityFolders.ts
1700
1650
  import * as fs4 from "fs";
1701
1651
  import * as path5 from "path";
@@ -1731,19 +1681,29 @@ function listCapabilityFolderSlugs(absDir) {
1731
1681
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => isCapabilityFolder(path5.join(absDir, e.name))).map((e) => e.name).sort();
1732
1682
  }
1733
1683
  function isCapabilityFolder(dir) {
1734
- return (fs4.existsSync(path5.join(dir, CAPABILITY_DEFINITION_FILE)) || fs4.existsSync(path5.join(dir, CAPABILITY_PROFILE_FILE))) && fs4.existsSync(path5.join(dir, CAPABILITY_BODY_FILE));
1684
+ if (!fs4.existsSync(path5.join(dir, CAPABILITY_DEFINITION_FILE))) return false;
1685
+ if (!fs4.existsSync(path5.join(dir, CAPABILITY_BODY_FILE))) return false;
1686
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
1687
+ return entries.every(
1688
+ (entry) => entry.name === CAPABILITY_DEFINITION_FILE || entry.name === CAPABILITY_BODY_FILE || entry.isDirectory() && (entry.name === "skills" || entry.name === "tools")
1689
+ );
1735
1690
  }
1736
1691
  function readCapabilityFolder(root, slug) {
1737
1692
  const dir = path5.join(root, slug);
1738
1693
  const definitionPath = path5.join(dir, CAPABILITY_DEFINITION_FILE);
1739
- const legacyProfilePath = path5.join(dir, CAPABILITY_PROFILE_FILE);
1740
- const profilePath = fs4.existsSync(definitionPath) ? definitionPath : legacyProfilePath;
1694
+ const profilePath = definitionPath;
1741
1695
  const bodyPath = path5.join(dir, CAPABILITY_BODY_FILE);
1742
1696
  if (!fs4.existsSync(profilePath) || !fs4.statSync(profilePath).isFile()) return null;
1743
1697
  if (!fs4.existsSync(bodyPath) || !fs4.statSync(bodyPath).isFile()) return null;
1744
1698
  try {
1745
1699
  const rawDefinition = JSON.parse(fs4.readFileSync(profilePath, "utf-8"));
1746
- const rawProfile = rawDefinition;
1700
+ const contract = parseCapabilityContract(rawDefinition);
1701
+ if (!contract) return null;
1702
+ const rawProfile = {
1703
+ inputSchema: contract.input.schema,
1704
+ outputSchema: contract.output.schema,
1705
+ contract
1706
+ };
1747
1707
  const rawBody = fs4.readFileSync(bodyPath, "utf-8");
1748
1708
  const { title, body } = parseCapabilityBody(rawBody, slug);
1749
1709
  return {
@@ -1754,54 +1714,28 @@ function readCapabilityFolder(root, slug) {
1754
1714
  title,
1755
1715
  body,
1756
1716
  rawBody,
1757
- config: parseCapabilityConfig(rawProfile),
1717
+ config: {
1718
+ action: slug,
1719
+ describe: title,
1720
+ outputSchema: contract.output.schema
1721
+ },
1758
1722
  rawProfile
1759
1723
  };
1760
1724
  } catch {
1761
1725
  return null;
1762
1726
  }
1763
1727
  }
1764
- function parseCapabilityConfig(raw) {
1765
- const tools = stringList(raw.tools ?? raw.capabilityTools ?? raw.capabilityTools);
1766
- const implementations = stringList(raw.implementations);
1767
- return {
1768
- action: stringField(raw.action),
1769
- implementation: stringField(raw.implementation),
1770
- tickScript: stringField(raw.tickScript),
1771
- capabilityKind: parseCapabilityKind(raw.capabilityKind),
1772
- disabled: typeof raw.disabled === "boolean" ? raw.disabled : void 0,
1773
- internal: typeof raw.internal === "boolean" ? raw.internal : void 0,
1774
- public: typeof raw.public === "boolean" ? raw.public : void 0,
1775
- agent: stringField(raw.agent),
1776
- mentions: stringList(raw.mentions).map((m) => m.replace(/^@/, "")),
1777
- tools,
1778
- capabilityTools: tools,
1779
- capabilityToolMode: parseCapabilityToolMode(raw.capabilityToolMode),
1780
- implementations,
1781
- role: stringField(raw.role),
1782
- describe: stringField(raw.describe) ?? stringField(raw.purpose),
1783
- stage: stringField(raw.stage),
1784
- readsFrom: stringList(raw.readsFrom ?? raw.reads_from),
1785
- writesTo: stringList(raw.writesTo ?? raw.writes_to),
1786
- output: parseCapabilityOutput(raw.output),
1787
- outputSchema: isPlainObject(raw.outputSchema) ? raw.outputSchema : void 0,
1788
- workflow: parseCapabilityWorkflow(raw.workflow)
1728
+ function parseCapabilityContract(raw) {
1729
+ if (Object.keys(raw).length !== 2 || !isPlainObject(raw.input) || !isPlainObject(raw.output)) return null;
1730
+ const parseValue = (value) => {
1731
+ if (Object.keys(value).some((key) => key !== "name" && key !== "schema") || typeof value.name !== "string" || !/^[a-z][a-z0-9-]*$/.test(value.name) || !isPlainObject(value.schema)) {
1732
+ return null;
1733
+ }
1734
+ return { name: value.name, schema: value.schema };
1789
1735
  };
1790
- }
1791
- function parseCapabilityOutput(raw) {
1792
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
1793
- const result = raw.result;
1794
- if (!result || typeof result !== "object" || Array.isArray(result)) return void 0;
1795
- const facts = stringList(result.facts);
1796
- return { result: { facts } };
1797
- }
1798
- function parseCapabilityKind(raw) {
1799
- return raw === "observe" || raw === "act" || raw === "verify" ? raw : void 0;
1800
- }
1801
- function parseCapabilityToolMode(raw) {
1802
- if (raw === void 0 || raw === null || raw === "") return void 0;
1803
- if (raw === "lock" || raw === "append") return raw;
1804
- return void 0;
1736
+ const input = parseValue(raw.input);
1737
+ const output = parseValue(raw.output);
1738
+ return input && output ? { input, output } : null;
1805
1739
  }
1806
1740
  function parseCapabilityBody(raw, slug) {
1807
1741
  const trimmed = raw.trim();
@@ -1859,7 +1793,6 @@ function parseWorkflowStep(value) {
1859
1793
  const id = stringField(raw.id);
1860
1794
  const action = stringField(raw.action);
1861
1795
  const evidence = stringField(raw.evidence);
1862
- const agent = stringField(raw.agent);
1863
1796
  const reason = stringField(raw.reason);
1864
1797
  const target = stringField(raw.target);
1865
1798
  const targetFact = stringField(raw.targetFact ?? raw.target_fact);
@@ -1875,7 +1808,6 @@ function parseWorkflowStep(value) {
1875
1808
  ...evidence ? { evidence } : {},
1876
1809
  ...target === "issue" || target === "pr" ? { target } : {},
1877
1810
  ...targetFact ? { targetFact } : {},
1878
- ...agent && isSafeSlug(agent) ? { agent } : {},
1879
1811
  ...reason ? { reason } : {},
1880
1812
  ...cliArgs && typeof cliArgs === "object" && !Array.isArray(cliArgs) ? { cliArgs } : {},
1881
1813
  ...inputs ? { inputs } : {},
@@ -1957,9 +1889,9 @@ var CAPABILITY_PROFILE_FILE, CAPABILITY_DEFINITION_FILE, CAPABILITY_BODY_FILE;
1957
1889
  var init_capabilityFolders = __esm({
1958
1890
  "src/capabilityFolders.ts"() {
1959
1891
  "use strict";
1960
- CAPABILITY_PROFILE_FILE = "profile.json";
1961
- CAPABILITY_DEFINITION_FILE = "definition.json";
1962
- CAPABILITY_BODY_FILE = "capability.md";
1892
+ CAPABILITY_PROFILE_FILE = "contract.json";
1893
+ CAPABILITY_DEFINITION_FILE = "contract.json";
1894
+ CAPABILITY_BODY_FILE = "instructions.md";
1963
1895
  }
1964
1896
  });
1965
1897
 
@@ -1997,7 +1929,6 @@ var init_definition_paths = __esm({
1997
1929
  });
1998
1930
 
1999
1931
  // src/registry.ts
2000
- import { createHash as createHash2 } from "crypto";
2001
1932
  import * as fs6 from "fs";
2002
1933
  import * as path7 from "path";
2003
1934
  function getImplementationsRoot() {
@@ -2049,10 +1980,12 @@ function getImplementationRoots() {
2049
1980
  return getImplementationRootsForCwd(process.cwd());
2050
1981
  }
2051
1982
  function getImplementationRootsForCwd(cwd) {
2052
- return [implementationsRoot(cwd), getImplementationsRoot()];
1983
+ void cwd;
1984
+ return [getImplementationsRoot()];
2053
1985
  }
2054
1986
  function getRuntimeProfileRootsForCwd(cwd) {
2055
- return [...getImplementationRootsForCwd(cwd), getRuntimeServicesRoot()];
1987
+ const roots = [...getImplementationRootsForCwd(cwd), getRuntimeServicesRoot()];
1988
+ return process.env.NODE_ENV === "test" ? [implementationsRoot(cwd), ...roots] : roots;
2056
1989
  }
2057
1990
  function getCapabilityRoots(projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
2058
1991
  return [projectCapabilitiesRoot, getBuiltinCapabilitiesRoot()];
@@ -2127,7 +2060,7 @@ function getCapabilityActionInputs(action, projectCapabilitiesRoot = getProjectC
2127
2060
  const resolved = resolveCapabilityAction(action, projectCapabilitiesRoot);
2128
2061
  if (!resolved) return null;
2129
2062
  const capability = resolveCapabilityFolder(resolved.capability, projectCapabilitiesRoot);
2130
- if (capability && path7.basename(capability.profilePath) === "definition.json") {
2063
+ if (capability && path7.basename(capability.profilePath) === "contract.json") {
2131
2064
  const schema = capability.rawProfile.inputSchema;
2132
2065
  if (!schema || typeof schema !== "object" || Array.isArray(schema)) return [];
2133
2066
  const properties = schema.properties;
@@ -2151,17 +2084,8 @@ function getCapabilityActionInputs(action, projectCapabilitiesRoot = getProjectC
2151
2084
  return getProfileInputs(resolved.implementation);
2152
2085
  }
2153
2086
  function resolveCapabilityExecution(capability, cwd = process.cwd()) {
2154
- if (path7.basename(capability.profilePath) === "definition.json") {
2155
- const implementations = readExternalImplementations(cwd);
2156
- const definition = JSON.parse(fs6.readFileSync(capability.profilePath, "utf-8"));
2157
- const capabilityRevision = createHash2("sha256").update(canonical(definition)).digest("hex");
2158
- const selected = resolveCapabilityImplementation({
2159
- capabilityId: capability.slug,
2160
- capabilityRevision,
2161
- implementations,
2162
- repositoryBinding: repositoryImplementationBinding(capability.slug, cwd)
2163
- });
2164
- return { implementation: selected.id, cliArgs: {} };
2087
+ if (path7.basename(capability.profilePath) === "contract.json") {
2088
+ return { implementation: "capability-run", cliArgs: { capability: capability.slug } };
2165
2089
  }
2166
2090
  const firstWorkflowStep = capability.config.workflow?.steps[0];
2167
2091
  if (firstWorkflowStep) {
@@ -2172,36 +2096,6 @@ function resolveCapabilityExecution(capability, cwd = process.cwd()) {
2172
2096
  const cliArgs = implementationDeclaresInput(implementation, "capability", cwd) ? { capability: capability.slug } : {};
2173
2097
  return { implementation, cliArgs };
2174
2098
  }
2175
- function repositoryImplementationBinding(capabilityId, cwd) {
2176
- try {
2177
- return loadConfig(cwd).execution?.capabilityBindings[capabilityId];
2178
- } catch {
2179
- return void 0;
2180
- }
2181
- }
2182
- function readExternalImplementations(cwd) {
2183
- const root = implementationsRoot(cwd);
2184
- if (!fs6.existsSync(root)) return [];
2185
- const definitions = [];
2186
- for (const entry of fs6.readdirSync(root, { withFileTypes: true })) {
2187
- if (!entry.isDirectory() || !isSafeName(entry.name)) continue;
2188
- const definitionPath = path7.join(root, entry.name, "definition.json");
2189
- if (!fs6.existsSync(definitionPath)) continue;
2190
- try {
2191
- const definition = JSON.parse(fs6.readFileSync(definitionPath, "utf-8"));
2192
- definitions.push(definition);
2193
- } catch {
2194
- }
2195
- }
2196
- return definitions;
2197
- }
2198
- function canonical(value) {
2199
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
2200
- if (value && typeof value === "object") {
2201
- return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
2202
- }
2203
- return JSON.stringify(value);
2204
- }
2205
2099
  function implementationDeclaresInput(implementation, inputName, cwd = process.cwd()) {
2206
2100
  const profilePath = resolveImplementation(implementation, getRuntimeProfileRootsForCwd(cwd));
2207
2101
  if (!profilePath) return false;
@@ -2229,7 +2123,10 @@ function isCapabilityRoot(root) {
2229
2123
  }
2230
2124
  function implementationRuntimePath(root, name) {
2231
2125
  const runtimePath = path7.join(root, name, "runtime.json");
2232
- return fs6.existsSync(runtimePath) ? runtimePath : path7.join(root, name, CAPABILITY_PROFILE_FILE);
2126
+ if (fs6.existsSync(runtimePath)) return runtimePath;
2127
+ const internalProfilePath = path7.join(root, name, "profile.json");
2128
+ if (fs6.existsSync(internalProfilePath)) return internalProfilePath;
2129
+ return path7.join(root, name, CAPABILITY_PROFILE_FILE);
2233
2130
  }
2234
2131
  function isImplementationProfile(profilePath, requireImplementationProfile) {
2235
2132
  if (!requireImplementationProfile) return true;
@@ -2336,9 +2233,7 @@ var PUBLIC_IMPLEMENTATION_ROLES;
2336
2233
  var init_registry = __esm({
2337
2234
  "src/registry.ts"() {
2338
2235
  "use strict";
2339
- init_implementation_resolution();
2340
2236
  init_capabilityFolders();
2341
- init_config();
2342
2237
  init_definition_paths();
2343
2238
  PUBLIC_IMPLEMENTATION_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
2344
2239
  }
@@ -4931,7 +4826,7 @@ var init_subagents = __esm({
4931
4826
  });
4932
4827
 
4933
4828
  // src/profile.ts
4934
- import { createHash as createHash4 } from "crypto";
4829
+ import { createHash as createHash3 } from "crypto";
4935
4830
  import * as fs21 from "fs";
4936
4831
  import * as path20 from "path";
4937
4832
  function loadProfile(profilePath) {
@@ -4972,7 +4867,7 @@ function loadProfile(profilePath) {
4972
4867
  implementation: execRef,
4973
4868
  internal: typeof r.internal === "boolean" ? r.internal : base.internal,
4974
4869
  public: typeof r.public === "boolean" ? r.public : base.public,
4975
- capabilityKind: parseCapabilityKind2(r.capabilityKind) ?? base.capabilityKind,
4870
+ capabilityKind: parseCapabilityKind(r.capabilityKind) ?? base.capabilityKind,
4976
4871
  slug: typeof r.slug === "string" && r.slug.trim() ? r.slug.trim() : base.slug,
4977
4872
  title: typeof r.title === "string" && r.title.trim() ? r.title.trim() : base.title,
4978
4873
  skills: parseStringArray2(r.skills) ?? base.skills,
@@ -4981,7 +4876,7 @@ function loadProfile(profilePath) {
4981
4876
  describe: typeof r.describe === "string" ? r.describe : base.describe,
4982
4877
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : base.agent,
4983
4878
  capabilityTools: parseStringArray2(r.capabilityTools ?? r.capabilityTools ?? r.tools) ?? base.capabilityTools,
4984
- capabilityToolMode: parseCapabilityToolMode2(profilePath, r.capabilityToolMode) ?? base.capabilityToolMode,
4879
+ capabilityToolMode: parseCapabilityToolMode(profilePath, r.capabilityToolMode) ?? base.capabilityToolMode,
4985
4880
  mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : base.mentions
4986
4881
  };
4987
4882
  }
@@ -5025,7 +4920,7 @@ function loadProfile(profilePath) {
5025
4920
  implementation: void 0,
5026
4921
  internal: typeof r.internal === "boolean" ? r.internal : void 0,
5027
4922
  public: typeof r.public === "boolean" ? r.public : void 0,
5028
- capabilityKind: parseCapabilityKind2(r.capabilityKind),
4923
+ capabilityKind: parseCapabilityKind(r.capabilityKind),
5029
4924
  slug: typeof r.slug === "string" && r.slug.trim() ? r.slug.trim() : void 0,
5030
4925
  title: typeof r.title === "string" && r.title.trim() ? r.title.trim() : void 0,
5031
4926
  skills: parseStringArray2(r.skills),
@@ -5037,7 +4932,7 @@ function loadProfile(profilePath) {
5037
4932
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : void 0,
5038
4933
  // Locked-toolbox palette + mentions from folder-capability profile metadata.
5039
4934
  capabilityTools: parseStringArray2(r.capabilityTools ?? r.capabilityTools ?? r.tools),
5040
- capabilityToolMode: parseCapabilityToolMode2(profilePath, r.capabilityToolMode),
4935
+ capabilityToolMode: parseCapabilityToolMode(profilePath, r.capabilityToolMode),
5041
4936
  mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : void 0,
5042
4937
  role,
5043
4938
  kind,
@@ -5130,18 +5025,18 @@ function compileRuntimeDocument(runtimePath, document) {
5130
5025
  agent: agentRef,
5131
5026
  canonicalContract: {
5132
5027
  capabilityId,
5133
- capabilityRevision: createHash4("sha256").update(canonical2(capability)).digest("hex"),
5028
+ capabilityRevision: createHash3("sha256").update(canonical(capability)).digest("hex"),
5134
5029
  implementationId: String(implementation.id),
5135
- implementationRevision: createHash4("sha256").update(canonical2(implementation)).digest("hex"),
5030
+ implementationRevision: createHash3("sha256").update(canonical(implementation)).digest("hex"),
5136
5031
  inputSchema: capability.inputSchema,
5137
5032
  outputSchema: capability.outputSchema
5138
5033
  }
5139
5034
  };
5140
5035
  }
5141
- function canonical2(value) {
5142
- if (Array.isArray(value)) return `[${value.map(canonical2).join(",")}]`;
5036
+ function canonical(value) {
5037
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
5143
5038
  if (value && typeof value === "object") {
5144
- return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical2(item)}`).join(",")}}`;
5039
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
5145
5040
  }
5146
5041
  return JSON.stringify(value);
5147
5042
  }
@@ -5156,7 +5051,7 @@ function readJsonObject(filePath, label) {
5156
5051
  throw new ProfileError(filePath, `${label} is invalid: ${error instanceof Error ? error.message : String(error)}`);
5157
5052
  }
5158
5053
  }
5159
- function parseCapabilityToolMode2(profilePath, raw) {
5054
+ function parseCapabilityToolMode(profilePath, raw) {
5160
5055
  if (raw === void 0 || raw === null || raw === "") return void 0;
5161
5056
  if (raw === "lock" || raw === "append") return raw;
5162
5057
  throw new ProfileError(profilePath, `"capabilityToolMode" must be "lock" or "append"`);
@@ -5274,7 +5169,7 @@ function parseAuth(p, raw) {
5274
5169
  });
5275
5170
  return { methods };
5276
5171
  }
5277
- function parseCapabilityKind2(raw) {
5172
+ function parseCapabilityKind(raw) {
5278
5173
  return raw === "observe" || raw === "act" || raw === "verify" ? raw : void 0;
5279
5174
  }
5280
5175
  function parseInputs(p, raw) {
@@ -8227,9 +8122,9 @@ import * as fs27 from "fs";
8227
8122
  function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
8228
8123
  const logs = goalRunLogs(data);
8229
8124
  const existing = logs[goalId];
8230
- const path52 = existing?.path ?? goalRunLogPath(goalId, data);
8125
+ const path54 = existing?.path ?? goalRunLogPath(goalId, data);
8231
8126
  logs[goalId] = {
8232
- path: path52,
8127
+ path: path54,
8233
8128
  events: [...existing?.events ?? [], buildGoalRunLogEvent(data, goalId, event, at)]
8234
8129
  };
8235
8130
  }
@@ -9202,11 +9097,11 @@ function validateWorkflow(value, options = {}) {
9202
9097
  function formatWorkflowValidationIssues(issues) {
9203
9098
  return issues.map((entry) => `${entry.path}: ${entry.message}`);
9204
9099
  }
9205
- function validateDataMatch(value, path52, issues, capabilityOutputs) {
9100
+ function validateDataMatch(value, path54, issues, capabilityOutputs) {
9206
9101
  if (value === void 0) return;
9207
9102
  const match = asRecord2(value);
9208
9103
  if (!match || Object.keys(match).length === 0) {
9209
- issue(issues, "invalid_condition", path52, "workflow condition must contain at least one match");
9104
+ issue(issues, "invalid_condition", path54, "workflow condition must contain at least one match");
9210
9105
  return;
9211
9106
  }
9212
9107
  for (const [field, expected] of Object.entries(match)) {
@@ -9214,7 +9109,7 @@ function validateDataMatch(value, path52, issues, capabilityOutputs) {
9214
9109
  issue(
9215
9110
  issues,
9216
9111
  "invalid_data_path",
9217
- `${path52}.${field}`,
9112
+ `${path54}.${field}`,
9218
9113
  `workflow condition must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
9219
9114
  );
9220
9115
  }
@@ -9222,12 +9117,12 @@ function validateDataMatch(value, path52, issues, capabilityOutputs) {
9222
9117
  issue(
9223
9118
  issues,
9224
9119
  "undeclared_result_path",
9225
- `${path52}.${field}`,
9120
+ `${path54}.${field}`,
9226
9121
  `workflow condition reads ${field}, but the source capability does not declare it`
9227
9122
  );
9228
9123
  }
9229
9124
  if (!isComparable(expected)) {
9230
- issue(issues, "invalid_condition_value", `${path52}.${field}`, "workflow condition value must be a JSON scalar");
9125
+ issue(issues, "invalid_condition_value", `${path54}.${field}`, "workflow condition value must be a JSON scalar");
9231
9126
  }
9232
9127
  }
9233
9128
  }
@@ -9245,8 +9140,8 @@ function isComparable(value) {
9245
9140
  if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
9246
9141
  return Array.isArray(value) && value.length > 0 && value.every((item) => isComparable(item) && !Array.isArray(item));
9247
9142
  }
9248
- function issue(issues, code, path52, message) {
9249
- issues.push({ code, path: path52, message });
9143
+ function issue(issues, code, path54, message) {
9144
+ issues.push({ code, path: path54, message });
9250
9145
  }
9251
9146
  var SAFE_NAME, SAFE_STEP_ID, SAFE_DATA_PATH, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
9252
9147
  var init_workflowValidation = __esm({
@@ -9264,7 +9159,6 @@ var init_workflowValidation = __esm({
9264
9159
  "target",
9265
9160
  "targetFact",
9266
9161
  "reason",
9267
- "agent",
9268
9162
  "cliArgs",
9269
9163
  "inputs",
9270
9164
  "next",
@@ -9293,6 +9187,8 @@ function normalizeWorkflowDefinition(value) {
9293
9187
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
9294
9188
  const raw = value;
9295
9189
  const name = typeof raw.name === "string" ? raw.name.trim() : "";
9190
+ const requestedAgent = typeof raw.agent === "string" ? raw.agent.trim() : "";
9191
+ const agent = /^[a-z][a-z0-9-]*$/.test(requestedAgent) ? requestedAgent : "kody";
9296
9192
  const hasGraphConnections = Array.isArray(raw.steps) && raw.steps.some(
9297
9193
  (step) => step && typeof step === "object" && !Array.isArray(step) && (step.next !== void 0 || step.inputs !== void 0)
9298
9194
  );
@@ -9309,8 +9205,8 @@ function normalizeWorkflowDefinition(value) {
9309
9205
  const capabilities = steps ? steps.map((step) => step.capability) : normalizeWorkflowCapabilities(raw.capabilities);
9310
9206
  if (!name || capabilities.length === 0) return null;
9311
9207
  return {
9312
- version: 1,
9313
9208
  name,
9209
+ agent,
9314
9210
  capabilities,
9315
9211
  ...raw.runWithoutApproval === true ? { runWithoutApproval: true } : {},
9316
9212
  ...steps ? { steps } : {},
@@ -9324,7 +9220,7 @@ function readWorkflowDefinition(_config, cwd, id) {
9324
9220
  const relativePath = workflowDefinitionPath(id);
9325
9221
  const candidates = [
9326
9222
  path26.join(root, ".kody-engine", "runtime", relativePath),
9327
- path26.join(root, ".kody-engine", "definitions", relativePath)
9223
+ path26.join(definitionsRoot(root), relativePath)
9328
9224
  ];
9329
9225
  for (const filePath of candidates) {
9330
9226
  if (!fs29.existsSync(filePath)) continue;
@@ -9346,7 +9242,8 @@ function workflowDefinitionToCapabilityFolder(id, workflow, source = workflowDef
9346
9242
  config: {
9347
9243
  action: id,
9348
9244
  workflow: workflowDefinitionToConfig(workflow),
9349
- describe: workflow.name
9245
+ describe: workflow.name,
9246
+ agent: workflow.agent
9350
9247
  }
9351
9248
  };
9352
9249
  }
@@ -9381,6 +9278,7 @@ var init_workflowDefinitions = __esm({
9381
9278
  "src/workflowDefinitions.ts"() {
9382
9279
  "use strict";
9383
9280
  init_capabilityFolders();
9281
+ init_definition_paths();
9384
9282
  init_workflowValidation();
9385
9283
  WORKFLOW_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,79}$/;
9386
9284
  CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/;
@@ -9513,15 +9411,15 @@ var init_backendStateBackend = __esm({
9513
9411
  this.jobsDir = opts.jobsDir.replace(/\/+$/, "");
9514
9412
  }
9515
9413
  async load(slug) {
9516
- const path52 = stateFilePath(this.jobsDir, slug);
9414
+ const path54 = stateFilePath(this.jobsDir, slug);
9517
9415
  const loaded = await createStateBackendFromEnv().get(this.tenantId, `capabilities/${slug}`, "job-state");
9518
9416
  if (!loaded) {
9519
- return { path: path52, handle: null, state: initialStateEnvelope("seed"), created: true };
9417
+ return { path: path54, handle: null, state: initialStateEnvelope("seed"), created: true };
9520
9418
  }
9521
9419
  if (!isStateEnvelope(loaded.doc)) {
9522
9420
  throw new Error(`BackendStateBackend: capabilities/${slug} is not a StateEnvelope`);
9523
9421
  }
9524
- return { path: path52, handle: loaded.updatedAt, state: loaded.doc, created: false };
9422
+ return { path: path54, handle: loaded.updatedAt, state: loaded.doc, created: false };
9525
9423
  }
9526
9424
  async save(loaded, next) {
9527
9425
  if (!loaded.created && isStateUnchanged(loaded.state, next)) return false;
@@ -13524,7 +13422,7 @@ var init_triggerDispatcher = __esm({
13524
13422
  });
13525
13423
 
13526
13424
  // src/goal/policyResolver.ts
13527
- import { createHash as createHash5 } from "crypto";
13425
+ import { createHash as createHash4 } from "crypto";
13528
13426
  function resolveDispatchPolicy(input) {
13529
13427
  const operation = input.catalog.operations.get(input.owner.definition.operationId);
13530
13428
  if (!operation) throw new Error(`Dispatch blocked: Operation "${input.owner.definition.operationId}" is unresolved`);
@@ -13542,7 +13440,7 @@ function resolveDispatchPolicy(input) {
13542
13440
  const snapshotValue = { policy, constraints };
13543
13441
  return {
13544
13442
  snapshot: {
13545
- hash: createHash5("sha256").update(stableJson(snapshotValue)).digest("hex"),
13443
+ hash: createHash4("sha256").update(stableJson(snapshotValue)).digest("hex"),
13546
13444
  ...snapshotValue
13547
13445
  },
13548
13446
  operation,
@@ -15343,17 +15241,103 @@ var init_loadCapabilityState = __esm({
15343
15241
  }
15344
15242
  });
15345
15243
 
15244
+ // src/scripts/loadSimpleCapability.ts
15245
+ import * as fs39 from "fs";
15246
+ import * as path36 from "path";
15247
+ function listFiles(root) {
15248
+ if (!fs39.existsSync(root)) return [];
15249
+ const files = [];
15250
+ const visit = (dir) => {
15251
+ for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
15252
+ const absolute = path36.join(dir, entry.name);
15253
+ if (entry.isSymbolicLink()) continue;
15254
+ if (entry.isDirectory()) visit(absolute);
15255
+ else if (entry.isFile()) files.push(path36.relative(root, absolute));
15256
+ }
15257
+ };
15258
+ visit(root);
15259
+ return files.sort();
15260
+ }
15261
+ var loadSimpleCapability;
15262
+ var init_loadSimpleCapability = __esm({
15263
+ "src/scripts/loadSimpleCapability.ts"() {
15264
+ "use strict";
15265
+ init_definition_paths();
15266
+ init_capabilityFolders();
15267
+ loadSimpleCapability = async (ctx) => {
15268
+ const slug = typeof ctx.args.capability === "string" ? ctx.args.capability.trim() : "";
15269
+ if (!/^[a-z][a-z0-9-]*$/.test(slug)) {
15270
+ throw new Error("capability-run requires a valid capability slug");
15271
+ }
15272
+ const capability = readCapabilityFolder(capabilitiesRoot(ctx.cwd), slug);
15273
+ if (!capability) {
15274
+ throw new Error(`Capability "${slug}" is not a valid simple capability folder`);
15275
+ }
15276
+ const contract = capability.rawProfile.contract;
15277
+ const toolRoot = path36.join(capability.dir, "tools");
15278
+ const skillRoot = path36.join(capability.dir, "skills");
15279
+ const toolFiles = listFiles(toolRoot);
15280
+ const skillFiles = listFiles(skillRoot);
15281
+ const supplied = ctx.args.input;
15282
+ let input = supplied;
15283
+ if (typeof supplied === "string") {
15284
+ try {
15285
+ input = JSON.parse(supplied);
15286
+ } catch {
15287
+ input = supplied;
15288
+ }
15289
+ }
15290
+ ctx.data.jobCapability = slug;
15291
+ ctx.data.capabilityInput = input;
15292
+ ctx.data.capabilityContract = contract;
15293
+ ctx.data.prompt = [
15294
+ capability.rawBody.trim(),
15295
+ "",
15296
+ "## Input",
15297
+ "",
15298
+ "```json",
15299
+ JSON.stringify({ [contract.input.name]: input }, null, 2),
15300
+ "```",
15301
+ "",
15302
+ "Return one JSON value matching the output contract:",
15303
+ "",
15304
+ "```json",
15305
+ JSON.stringify({ [contract.output.name]: contract.output.schema }, null, 2),
15306
+ "```",
15307
+ ...skillFiles.length ? [
15308
+ "",
15309
+ "## Skills",
15310
+ "",
15311
+ ...skillFiles.flatMap((file) => [
15312
+ `### ${file}`,
15313
+ "",
15314
+ fs39.readFileSync(path36.join(skillRoot, file), "utf-8"),
15315
+ ""
15316
+ ])
15317
+ ] : [],
15318
+ ...toolFiles.length ? [
15319
+ "",
15320
+ "## Tools",
15321
+ "",
15322
+ "Inspect or run these capability-owned files when needed:",
15323
+ ...toolFiles.map((file) => `- ${path36.join(toolRoot, file)}`)
15324
+ ] : []
15325
+ ].join("\n");
15326
+ };
15327
+ }
15328
+ });
15329
+
15346
15330
  // src/companyIntent.ts
15347
15331
  function isCompanyIntentId(value) {
15348
15332
  return SLUG_RE2.test(value);
15349
15333
  }
15350
- function normalizeCompanyIntent(path52, raw) {
15334
+ function normalizeCompanyIntent(path54, raw) {
15351
15335
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
15352
- throw new Error(`${path52}: intent must be JSON object`);
15336
+ throw new Error(`${path54}: intent must be JSON object`);
15353
15337
  }
15354
15338
  const input = raw;
15355
15339
  const id = stringField4(input.id);
15356
- if (!id || !isCompanyIntentId(id)) throw new Error(`${path52}: invalid intent id`);
15340
+ if (!id || !isCompanyIntentId(id)) throw new Error(`${path54}: invalid intent id`);
15357
15341
  const createdAt = stringField4(input.createdAt) || nowIso();
15358
15342
  const updatedAt = stringField4(input.updatedAt) || createdAt;
15359
15343
  const description = stringField4(input.description);
@@ -15644,8 +15628,8 @@ var init_loadIssueStateComment = __esm({
15644
15628
  });
15645
15629
 
15646
15630
  // src/scripts/loadJobFromFile.ts
15647
- import * as fs39 from "fs";
15648
- import * as path36 from "path";
15631
+ import * as fs40 from "fs";
15632
+ import * as path37 from "path";
15649
15633
  function parseJobFile(raw, slug) {
15650
15634
  let stripped = raw;
15651
15635
  if (stripped.startsWith("---\n")) {
@@ -15684,10 +15668,10 @@ var init_loadJobFromFile = __esm({
15684
15668
  if (!slug) {
15685
15669
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
15686
15670
  }
15687
- const capability = resolveCapabilityFolder(slug, path36.resolve(ctx.cwd, jobsDir));
15671
+ const capability = resolveCapabilityFolder(slug, path37.resolve(ctx.cwd, jobsDir));
15688
15672
  if (!capability) {
15689
15673
  throw new Error(
15690
- `loadJobFromFile: capability folder not found or incomplete: ${path36.resolve(ctx.cwd, jobsDir, slug)}`
15674
+ `loadJobFromFile: capability folder not found or incomplete: ${path37.resolve(ctx.cwd, jobsDir, slug)}`
15691
15675
  );
15692
15676
  }
15693
15677
  const { title, body, config } = capability;
@@ -15697,12 +15681,12 @@ var init_loadJobFromFile = __esm({
15697
15681
  let agentIdentity = "";
15698
15682
  if (agentSlug) {
15699
15683
  const agentPath = resolveAgentFile(ctx.cwd, agentSlug, agentsDir);
15700
- if (!fs39.existsSync(agentPath)) {
15684
+ if (!fs40.existsSync(agentPath)) {
15701
15685
  throw new Error(
15702
15686
  `loadJobFromFile: capability '${slug}' declares agent '${agentSlug}' but ${agentPath} does not exist`
15703
15687
  );
15704
15688
  }
15705
- const agentRaw = fs39.readFileSync(agentPath, "utf-8");
15689
+ const agentRaw = fs40.readFileSync(agentPath, "utf-8");
15706
15690
  const parsed = parseJobFile(agentRaw, agentSlug);
15707
15691
  agentTitle = parsed.title;
15708
15692
  agentIdentity = parsed.body;
@@ -15782,13 +15766,13 @@ ${truncate2(issue2.body, FINDING_BODY_MAX_BYTES)}`;
15782
15766
  });
15783
15767
 
15784
15768
  // src/scripts/kodyVariables.ts
15785
- import * as fs40 from "fs";
15786
- import * as path37 from "path";
15769
+ import * as fs41 from "fs";
15770
+ import * as path38 from "path";
15787
15771
  function readKodyVariables(cwd) {
15788
- const full = path37.join(cwd, KODY_VARIABLES_REL_PATH);
15772
+ const full = path38.join(cwd, KODY_VARIABLES_REL_PATH);
15789
15773
  let raw;
15790
15774
  try {
15791
- raw = fs40.readFileSync(full, "utf-8");
15775
+ raw = fs41.readFileSync(full, "utf-8");
15792
15776
  } catch {
15793
15777
  return {};
15794
15778
  }
@@ -15813,9 +15797,9 @@ var init_kodyVariables = __esm({
15813
15797
  });
15814
15798
 
15815
15799
  // src/backendVault.ts
15816
- import { createDecipheriv, createHash as createHash6 } from "crypto";
15800
+ import { createDecipheriv, createHash as createHash5 } from "crypto";
15817
15801
  function cacheKey(owner, repo, masterKey) {
15818
- const keyHash = createHash6("sha256").update(masterKey).digest("hex").slice(0, 16);
15802
+ const keyHash = createHash5("sha256").update(masterKey).digest("hex").slice(0, 16);
15819
15803
  return `${owner}/${repo}:${keyHash}`.toLowerCase();
15820
15804
  }
15821
15805
  function decryptVault(payload, masterKey) {
@@ -15964,8 +15948,8 @@ var init_runtimeSecrets = __esm({
15964
15948
  });
15965
15949
 
15966
15950
  // src/scripts/loadQaContext.ts
15967
- import * as fs41 from "fs";
15968
- import * as path38 from "path";
15951
+ import * as fs42 from "fs";
15952
+ import * as path39 from "path";
15969
15953
  function parseSlugList(value) {
15970
15954
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
15971
15955
  return inner.split(",").map(
@@ -15994,18 +15978,18 @@ function readProfileAgents(raw) {
15994
15978
  return { agent: agent ?? legacy ?? ["kody"], body };
15995
15979
  }
15996
15980
  function readProfile(cwd) {
15997
- const dir = path38.join(cwd, CONTEXT_DIR_REL_PATH);
15998
- if (!fs41.existsSync(dir)) return "";
15981
+ const dir = path39.join(cwd, CONTEXT_DIR_REL_PATH);
15982
+ if (!fs42.existsSync(dir)) return "";
15999
15983
  let entries;
16000
15984
  try {
16001
- entries = fs41.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
15985
+ entries = fs42.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
16002
15986
  } catch {
16003
15987
  return "";
16004
15988
  }
16005
15989
  const blocks = [];
16006
15990
  for (const file of entries) {
16007
15991
  try {
16008
- const raw = fs41.readFileSync(path38.join(dir, file), "utf-8");
15992
+ const raw = fs42.readFileSync(path39.join(dir, file), "utf-8");
16009
15993
  const { agent, body } = readProfileAgents(raw);
16010
15994
  if (!agent.includes(QA_AGENT) && !agent.includes(ALL_AGENTS)) continue;
16011
15995
  blocks.push(`## ${file}
@@ -16054,8 +16038,8 @@ var init_loadQaContext = __esm({
16054
16038
  });
16055
16039
 
16056
16040
  // src/taskContext.ts
16057
- import * as fs42 from "fs";
16058
- import * as path39 from "path";
16041
+ import * as fs43 from "fs";
16042
+ import * as path40 from "path";
16059
16043
  function buildTaskContext(args) {
16060
16044
  return {
16061
16045
  schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
@@ -16071,9 +16055,9 @@ function buildTaskContext(args) {
16071
16055
  function persistTaskContext(cwd, ctx) {
16072
16056
  try {
16073
16057
  const dir = runtimeStatePath(cwd, "agent-runs", ctx.runId);
16074
- fs42.mkdirSync(dir, { recursive: true });
16075
- const file = path39.join(dir, "task-context.json");
16076
- fs42.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16058
+ fs43.mkdirSync(dir, { recursive: true });
16059
+ const file = path40.join(dir, "task-context.json");
16060
+ fs43.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
16077
16061
  `);
16078
16062
  return file;
16079
16063
  } catch (err) {
@@ -16464,7 +16448,7 @@ var init_notifyTerminal = __esm({
16464
16448
  });
16465
16449
 
16466
16450
  // src/scripts/openAgencyModelReviewPr.ts
16467
- import { createHash as createHash7 } from "crypto";
16451
+ import { createHash as createHash6 } from "crypto";
16468
16452
  function parseAgencyModelProposal(raw) {
16469
16453
  const text2 = raw.trim();
16470
16454
  const jsonText = (text2.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i)?.[1] ?? text2).trim();
@@ -16500,23 +16484,23 @@ function parseAgencyModelProposal(raw) {
16500
16484
  function normalizeBundleFiles(bundle) {
16501
16485
  const seen = /* @__PURE__ */ new Set();
16502
16486
  return bundle.files.map((file, index) => {
16503
- const path52 = file.path.replace(/^\/+/, "");
16504
- const parts = path52.split("/");
16505
- if (!path52 || file.path.startsWith("/") || file.path.includes("\\") || parts.some((part) => !part || part === "." || part === "..")) {
16487
+ const path54 = file.path.replace(/^\/+/, "");
16488
+ const parts = path54.split("/");
16489
+ if (!path54 || file.path.startsWith("/") || file.path.includes("\\") || parts.some((part) => !part || part === "." || part === "..")) {
16506
16490
  throw new Error(`openAgencyModelReviewPr: files[${index}].path is unsafe`);
16507
16491
  }
16508
16492
  if (!/^(agents\/[^/]+\.md|capabilities\/[^/]+\/.+|goals\/(?:templates\/)?[^/]+\/.+|workflows\/[^/]+\.json)$/.test(
16509
- path52
16493
+ path54
16510
16494
  )) {
16511
16495
  throw new Error(`openAgencyModelReviewPr: files[${index}].path is not a supported definition path`);
16512
16496
  }
16513
- if (seen.has(path52)) throw new Error(`openAgencyModelReviewPr: duplicate generated file path: ${path52}`);
16514
- seen.add(path52);
16515
- return { path: path52, content: file.content.replace(/\r\n?/g, "\n") };
16497
+ if (seen.has(path54)) throw new Error(`openAgencyModelReviewPr: duplicate generated file path: ${path54}`);
16498
+ seen.add(path54);
16499
+ return { path: path54, content: file.content.replace(/\r\n?/g, "\n") };
16516
16500
  });
16517
16501
  }
16518
16502
  function buildProposalId(issueNumber, bundle, sourceLabel) {
16519
- const digest = createHash7("sha256").update(JSON.stringify({ issueNumber, sourceLabel, title: bundle.title, files: normalizeBundleFiles(bundle) })).digest("hex").slice(0, 16);
16503
+ const digest = createHash6("sha256").update(JSON.stringify({ issueNumber, sourceLabel, title: bundle.title, files: normalizeBundleFiles(bundle) })).digest("hex").slice(0, 16);
16520
16504
  return `issue-${issueNumber}-${digest}`;
16521
16505
  }
16522
16506
  function isDryRun(ctx) {
@@ -17456,9 +17440,9 @@ var init_postResearchComment = __esm({
17456
17440
  });
17457
17441
 
17458
17442
  // src/scripts/prepareBrowserAuth.ts
17459
- import * as fs43 from "fs";
17443
+ import * as fs44 from "fs";
17460
17444
  import * as os6 from "os";
17461
- import * as path40 from "path";
17445
+ import * as path41 from "path";
17462
17446
  function appendAuthMessage(ctx, message) {
17463
17447
  const current = typeof ctx.data.qaAuthBlock === "string" ? ctx.data.qaAuthBlock.trim() : "";
17464
17448
  ctx.data.qaAuthBlock = current ? `${current}
@@ -17497,9 +17481,9 @@ async function githubJson(url, token) {
17497
17481
  return await response.json();
17498
17482
  }
17499
17483
  function writeKodyStorageState(input) {
17500
- const directory = fs43.mkdtempSync(path40.join(os6.tmpdir(), "kody-browser-auth-"));
17501
- fs43.chmodSync(directory, 448);
17502
- const file = path40.join(directory, "storage-state.json");
17484
+ const directory = fs44.mkdtempSync(path41.join(os6.tmpdir(), "kody-browser-auth-"));
17485
+ fs44.chmodSync(directory, 448);
17486
+ const file = path41.join(directory, "storage-state.json");
17503
17487
  const now = Date.now();
17504
17488
  const repoEntry = {
17505
17489
  repoUrl: input.repoUrl,
@@ -17529,7 +17513,7 @@ function writeKodyStorageState(input) {
17529
17513
  }
17530
17514
  ]
17531
17515
  };
17532
- fs43.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17516
+ fs44.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17533
17517
  return { directory, file };
17534
17518
  }
17535
17519
  function configurePlaywright(profile, storageStatePath) {
@@ -17611,7 +17595,7 @@ async function prepareMethod(ctx, profile, method) {
17611
17595
  configurePlaywright(profile, state.file);
17612
17596
  const authDirectory = state.directory;
17613
17597
  registerRuntimeCleanup(ctx, () => {
17614
- fs43.rmSync(authDirectory, { recursive: true, force: true });
17598
+ fs44.rmSync(authDirectory, { recursive: true, force: true });
17615
17599
  });
17616
17600
  appendAuthMessage(
17617
17601
  ctx,
@@ -17619,7 +17603,7 @@ async function prepareMethod(ctx, profile, method) {
17619
17603
  );
17620
17604
  return true;
17621
17605
  } catch (error) {
17622
- if (state) fs43.rmSync(state.directory, { recursive: true, force: true });
17606
+ if (state) fs44.rmSync(state.directory, { recursive: true, force: true });
17623
17607
  const reason = error instanceof Error ? error.message : String(error);
17624
17608
  appendAuthMessage(
17625
17609
  ctx,
@@ -17732,9 +17716,9 @@ function latestResult(raw, agentResult) {
17732
17716
  function recordField4(value) {
17733
17717
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
17734
17718
  }
17735
- function resolveDotted(root, path52) {
17736
- if (!path52) return void 0;
17737
- return path52.split(".").reduce((value, key) => recordField4(value)?.[key], root);
17719
+ function resolveDotted(root, path54) {
17720
+ if (!path54) return void 0;
17721
+ return path54.split(".").reduce((value, key) => recordField4(value)?.[key], root);
17738
17722
  }
17739
17723
  function stringValue4(value) {
17740
17724
  return typeof value === "string" && value.trim() ? value.trim() : null;
@@ -18588,9 +18572,9 @@ var init_runFlow = __esm({
18588
18572
  });
18589
18573
 
18590
18574
  // src/scripts/previewBuildHelpers.ts
18591
- import { createDecipheriv as createDecipheriv2, createHash as createHash8, hkdfSync as hkdfSync2 } from "crypto";
18575
+ import { createDecipheriv as createDecipheriv2, createHash as createHash7, hkdfSync as hkdfSync2 } from "crypto";
18592
18576
  function shortHash(s) {
18593
- return createHash8("sha256").update(s).digest("hex").slice(0, 6);
18577
+ return createHash7("sha256").update(s).digest("hex").slice(0, 6);
18594
18578
  }
18595
18579
  function previewAppName(repo, pr) {
18596
18580
  const [owner, name] = repo.split("/");
@@ -18623,7 +18607,7 @@ function formatPreviewComment(args) {
18623
18607
  ].join("\n");
18624
18608
  }
18625
18609
  function defaultImageTag(repo, ref) {
18626
- return createHash8("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
18610
+ return createHash7("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
18627
18611
  }
18628
18612
  var init_previewBuildHelpers = __esm({
18629
18613
  "src/scripts/previewBuildHelpers.ts"() {
@@ -18718,12 +18702,12 @@ fi
18718
18702
 
18719
18703
  // src/scripts/runPreviewBuild.ts
18720
18704
  import { copyFile, writeFile } from "fs/promises";
18721
- import * as path41 from "path";
18705
+ import * as path42 from "path";
18722
18706
  import { fileURLToPath as fileURLToPath2 } from "url";
18723
18707
  function bundledDockerfilePath(mode) {
18724
- const here = path41.dirname(fileURLToPath2(import.meta.url));
18708
+ const here = path42.dirname(fileURLToPath2(import.meta.url));
18725
18709
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
18726
- return path41.join(here, "preview-build-templates", file);
18710
+ return path42.join(here, "preview-build-templates", file);
18727
18711
  }
18728
18712
  function required(name) {
18729
18713
  const v = (process.env[name] ?? "").trim();
@@ -18958,10 +18942,10 @@ var init_runPreviewBuild = __esm({
18958
18942
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
18959
18943
  if (Object.keys(buildEnv).length > 0) {
18960
18944
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
18961
- await writeFile(path41.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
18945
+ await writeFile(path42.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
18962
18946
  `, "utf8");
18963
18947
  }
18964
- const consumerDockerfile = path41.join(ctx.cwd, "Dockerfile.preview");
18948
+ const consumerDockerfile = path42.join(ctx.cwd, "Dockerfile.preview");
18965
18949
  const { stat } = await import("fs/promises");
18966
18950
  let hasConsumerDockerfile = false;
18967
18951
  try {
@@ -19145,8 +19129,8 @@ var init_tickShellRunner = __esm({
19145
19129
  });
19146
19130
 
19147
19131
  // src/scripts/runScheduledImplementationTick.ts
19148
- import * as fs44 from "fs";
19149
- import * as path42 from "path";
19132
+ import * as fs45 from "fs";
19133
+ import * as path43 from "path";
19150
19134
  var runScheduledImplementationTick;
19151
19135
  var init_runScheduledImplementationTick = __esm({
19152
19136
  "src/scripts/runScheduledImplementationTick.ts"() {
@@ -19167,14 +19151,14 @@ var init_runScheduledImplementationTick = __esm({
19167
19151
  ctx.output.reason = `runScheduledImplementationTick: args.slug or ctx.args.${slugArg} must be non-empty capability slug`;
19168
19152
  return;
19169
19153
  }
19170
- const capability = resolveCapabilityFolder(slug, path42.resolve(ctx.cwd, jobsDir));
19154
+ const capability = resolveCapabilityFolder(slug, path43.resolve(ctx.cwd, jobsDir));
19171
19155
  if (!capability) {
19172
19156
  ctx.output.exitCode = 99;
19173
19157
  ctx.output.reason = `runScheduledImplementationTick: capability folder not found or incomplete: ${slug} (searched ${jobsDir} and company store)`;
19174
19158
  return;
19175
19159
  }
19176
- const shellPath = path42.join(profile.dir, shell);
19177
- if (!fs44.existsSync(shellPath)) {
19160
+ const shellPath = path43.join(profile.dir, shell);
19161
+ if (!fs45.existsSync(shellPath)) {
19178
19162
  ctx.output.exitCode = 99;
19179
19163
  ctx.output.reason = `runScheduledImplementationTick: shell not found: ${shell} (looked in ${profile.dir})`;
19180
19164
  return;
@@ -19205,8 +19189,8 @@ var init_runScheduledImplementationTick = __esm({
19205
19189
  });
19206
19190
 
19207
19191
  // src/scripts/runTickScript.ts
19208
- import * as fs45 from "fs";
19209
- import * as path43 from "path";
19192
+ import * as fs46 from "fs";
19193
+ import * as path44 from "path";
19210
19194
  var runTickScript;
19211
19195
  var init_runTickScript = __esm({
19212
19196
  "src/scripts/runTickScript.ts"() {
@@ -19226,10 +19210,10 @@ var init_runTickScript = __esm({
19226
19210
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
19227
19211
  return;
19228
19212
  }
19229
- const capability = readCapabilityFolder(path43.resolve(ctx.cwd, jobsDir), slug);
19213
+ const capability = readCapabilityFolder(path44.resolve(ctx.cwd, jobsDir), slug);
19230
19214
  if (!capability) {
19231
19215
  ctx.output.exitCode = 99;
19232
- ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path43.resolve(ctx.cwd, jobsDir, slug)}`;
19216
+ ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path44.resolve(ctx.cwd, jobsDir, slug)}`;
19233
19217
  return;
19234
19218
  }
19235
19219
  const tickScript = capability.config.tickScript;
@@ -19238,8 +19222,8 @@ var init_runTickScript = __esm({
19238
19222
  ctx.output.reason = `runTickScript: capability ${slug} has no \`tickScript\` in profile.json \u2014 route via capability-tick instead`;
19239
19223
  return;
19240
19224
  }
19241
- const scriptPath = path43.isAbsolute(tickScript) ? tickScript : path43.join(ctx.cwd, tickScript);
19242
- if (!fs45.existsSync(scriptPath)) {
19225
+ const scriptPath = path44.isAbsolute(tickScript) ? tickScript : path44.join(ctx.cwd, tickScript);
19226
+ if (!fs46.existsSync(scriptPath)) {
19243
19227
  ctx.output.exitCode = 99;
19244
19228
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
19245
19229
  return;
@@ -19521,7 +19505,7 @@ var init_syncFlow = __esm({
19521
19505
  });
19522
19506
 
19523
19507
  // src/scripts/validateAgencyModelProposal.ts
19524
- import * as path44 from "path";
19508
+ import * as path45 from "path";
19525
19509
  function validateModelBundle(bundle, expectedKind, options = {}) {
19526
19510
  const failures = [];
19527
19511
  validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind, options);
@@ -19847,7 +19831,7 @@ var init_validateAgencyModelProposal = __esm({
19847
19831
  const bundle = parseAgencyModelProposal(raw);
19848
19832
  const expectedKind = readExpectedModelKind(args);
19849
19833
  const failures = validateModelBundle(bundle, expectedKind, {
19850
- capabilityRoot: path44.join(ctx.cwd, ".kody", "capabilities")
19834
+ capabilityRoot: path45.join(ctx.cwd, ".kody", "capabilities")
19851
19835
  });
19852
19836
  if (failures.length > 0) {
19853
19837
  throw new Error(`validateAgencyModelProposal: ${failures.join("; ")}`);
@@ -20398,7 +20382,7 @@ var init_warmupMcp = __esm({
20398
20382
  });
20399
20383
 
20400
20384
  // src/scripts/writeAgentRunSummary.ts
20401
- import * as fs46 from "fs";
20385
+ import * as fs47 from "fs";
20402
20386
  var writeAgentRunSummary;
20403
20387
  var init_writeAgentRunSummary = __esm({
20404
20388
  "src/scripts/writeAgentRunSummary.ts"() {
@@ -20424,7 +20408,7 @@ var init_writeAgentRunSummary = __esm({
20424
20408
  if (reason) lines.push(`- **Reason:** ${reason}`);
20425
20409
  lines.push("");
20426
20410
  try {
20427
- fs46.appendFileSync(summaryPath, `${lines.join("\n")}
20411
+ fs47.appendFileSync(summaryPath, `${lines.join("\n")}
20428
20412
  `);
20429
20413
  } catch {
20430
20414
  }
@@ -20572,6 +20556,7 @@ var init_scripts = __esm({
20572
20556
  init_initFlow();
20573
20557
  init_loadAgentAdhoc();
20574
20558
  init_loadCapabilityState();
20559
+ init_loadSimpleCapability();
20575
20560
  init_loadCompanyIntents();
20576
20561
  init_loadCompanyPortfolio();
20577
20562
  init_loadConventions();
@@ -20656,6 +20641,7 @@ var init_scripts = __esm({
20656
20641
  loadIssueStateComment,
20657
20642
  loadJobFromFile,
20658
20643
  loadCapabilityState,
20644
+ loadSimpleCapability,
20659
20645
  loadCompanyIntents,
20660
20646
  loadCompanyPortfolio,
20661
20647
  loadAgentAdhoc,
@@ -20752,17 +20738,17 @@ var init_scripts = __esm({
20752
20738
  });
20753
20739
 
20754
20740
  // src/stateWorkspace.ts
20755
- import * as fs47 from "fs";
20756
- import * as path45 from "path";
20741
+ import * as fs48 from "fs";
20742
+ import * as path46 from "path";
20757
20743
  function tenantId(config) {
20758
20744
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
20759
20745
  const repo = config.github?.repo?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[1]?.trim();
20760
20746
  return owner && repo ? `${owner}/${repo}` : null;
20761
20747
  }
20762
20748
  function writeRuntimeFile(cwd, relativePath, content) {
20763
- const target = path45.join(cwd, RUNTIME_ROOT, relativePath);
20764
- fs47.mkdirSync(path45.dirname(target), { recursive: true });
20765
- fs47.writeFileSync(target, content, "utf8");
20749
+ const target = path46.join(cwd, RUNTIME_ROOT, relativePath);
20750
+ fs48.mkdirSync(path46.dirname(target), { recursive: true });
20751
+ fs48.writeFileSync(target, content, "utf8");
20766
20752
  }
20767
20753
  function record(value) {
20768
20754
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -20827,11 +20813,11 @@ async function hydrateStateWorkspace(config, cwd, backendOverride) {
20827
20813
  throw new Error("Kody backend access is required for runtime workspace documents");
20828
20814
  return;
20829
20815
  }
20830
- const key = `${path45.resolve(cwd)}|${tenant}`;
20816
+ const key = `${path46.resolve(cwd)}|${tenant}`;
20831
20817
  if (hydratedWorkspaces.has(key)) return;
20832
20818
  const backend = backendOverride ?? createStateBackendFromEnv();
20833
- const root = path45.join(cwd, RUNTIME_ROOT);
20834
- fs47.rmSync(root, { recursive: true, force: true });
20819
+ const root = path46.join(cwd, RUNTIME_ROOT);
20820
+ fs48.rmSync(root, { recursive: true, force: true });
20835
20821
  await Promise.all([
20836
20822
  hydratePrefix(backend, tenant, cwd, "context:"),
20837
20823
  hydratePrefix(backend, tenant, cwd, "memory:"),
@@ -20847,7 +20833,7 @@ var init_stateWorkspace = __esm({
20847
20833
  "src/stateWorkspace.ts"() {
20848
20834
  "use strict";
20849
20835
  init_state_backend();
20850
- RUNTIME_ROOT = path45.join(".kody-engine", "runtime");
20836
+ RUNTIME_ROOT = path46.join(".kody-engine", "runtime");
20851
20837
  hydratedWorkspaces = /* @__PURE__ */ new Set();
20852
20838
  }
20853
20839
  });
@@ -20918,9 +20904,9 @@ var init_tools = __esm({
20918
20904
 
20919
20905
  // src/executor.ts
20920
20906
  import { spawn as spawn8 } from "child_process";
20921
- import * as fs48 from "fs";
20907
+ import * as fs49 from "fs";
20922
20908
  import * as os7 from "os";
20923
- import * as path46 from "path";
20909
+ import * as path47 from "path";
20924
20910
  function isMutatingPostflight(scriptName) {
20925
20911
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
20926
20912
  }
@@ -21161,7 +21147,7 @@ async function runImplementation(profileName, input) {
21161
21147
  const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
21162
21148
  const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
21163
21149
  const invokeAgent = async (prompt) => {
21164
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path46.isAbsolute(p) ? p : path46.resolve(profile.dir, p)).filter((p) => p.length > 0);
21150
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path47.isAbsolute(p) ? p : path47.resolve(profile.dir, p)).filter((p) => p.length > 0);
21165
21151
  const syntheticPath = ctx.data.syntheticPluginPath;
21166
21152
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
21167
21153
  const agents = loadSubagents(profile);
@@ -21623,17 +21609,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
21623
21609
  function resolveProfilePath(profileName, cwd = process.cwd()) {
21624
21610
  const found = resolveImplementation(profileName, getRuntimeProfileRootsForCwd(cwd));
21625
21611
  if (found) return found;
21626
- const here = path46.dirname(new URL(import.meta.url).pathname);
21612
+ const here = path47.dirname(new URL(import.meta.url).pathname);
21627
21613
  const candidates = [
21628
- path46.join(here, "implementations", profileName, "profile.json"),
21614
+ path47.join(here, "implementations", profileName, "profile.json"),
21629
21615
  // same-dir sibling (dev)
21630
- path46.join(here, "..", "implementations", profileName, "profile.json"),
21616
+ path47.join(here, "..", "implementations", profileName, "profile.json"),
21631
21617
  // up one (prod: dist/bin → dist/implementations)
21632
- path46.join(here, "..", "src", "implementations", profileName, "profile.json")
21618
+ path47.join(here, "..", "src", "implementations", profileName, "profile.json")
21633
21619
  // fallback
21634
21620
  ];
21635
21621
  for (const c of candidates) {
21636
- if (fs48.existsSync(c)) return c;
21622
+ if (fs49.existsSync(c)) return c;
21637
21623
  }
21638
21624
  return candidates[0];
21639
21625
  }
@@ -21748,15 +21734,15 @@ function resolveShellTimeoutMs(entry) {
21748
21734
  }
21749
21735
  async function runShellEntry(entry, ctx, profile) {
21750
21736
  const shellName = entry.shell;
21751
- const shellPath = path46.join(profile.dir, shellName);
21752
- if (!fs48.existsSync(shellPath)) {
21737
+ const shellPath = path47.join(profile.dir, shellName);
21738
+ if (!fs49.existsSync(shellPath)) {
21753
21739
  ctx.skipAgent = true;
21754
21740
  ctx.output.exitCode = 99;
21755
21741
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
21756
21742
  return;
21757
21743
  }
21758
21744
  const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
21759
- const outputFile = path46.join(
21745
+ const outputFile = path47.join(
21760
21746
  os7.tmpdir(),
21761
21747
  `kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
21762
21748
  );
@@ -21828,9 +21814,9 @@ async function runShellEntry(entry, ctx, profile) {
21828
21814
  }
21829
21815
  let sideChannelText = "";
21830
21816
  try {
21831
- if (fs48.existsSync(outputFile)) {
21832
- sideChannelText = fs48.readFileSync(outputFile, "utf-8");
21833
- fs48.rmSync(outputFile, { force: true });
21817
+ if (fs49.existsSync(outputFile)) {
21818
+ sideChannelText = fs49.readFileSync(outputFile, "utf-8");
21819
+ fs49.rmSync(outputFile, { force: true });
21834
21820
  }
21835
21821
  } catch {
21836
21822
  }
@@ -22002,7 +21988,7 @@ __export(job_exports, {
22002
21988
  stableJobKey: () => stableJobKey,
22003
21989
  validateJob: () => validateJob
22004
21990
  });
22005
- import * as path47 from "path";
21991
+ import * as path48 from "path";
22006
21992
  function newJobId(flavor) {
22007
21993
  localJobSeq += 1;
22008
21994
  const runId = process.env.GITHUB_RUN_ID;
@@ -22091,6 +22077,7 @@ async function runJob(job, base) {
22091
22077
  const persistedState = valid.workflowRunId && workflowIdentity && base.config ? await readWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId) : null;
22092
22078
  const workflowJob = {
22093
22079
  ...workflowContext && !valid.why ? { ...valid, why: workflowContext.body } : valid,
22080
+ ...workflowCapability.config.agent ? { agent: workflowCapability.config.agent } : {},
22094
22081
  ...valid.workflowState ?? persistedState ? { workflowState: valid.workflowState ?? persistedState ?? void 0 } : {}
22095
22082
  };
22096
22083
  const checkpoint = valid.workflowRunId && workflowIdentity && base.config ? (state) => writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, state) : void 0;
@@ -22517,11 +22504,11 @@ function selectWorkflowTransition(step, data, counts) {
22517
22504
  }
22518
22505
  function workflowResultConditionPaths(transitions) {
22519
22506
  return transitions.flatMap(
22520
- (transition) => Object.keys(transition.when ?? {}).filter((path52) => path52.startsWith("result."))
22507
+ (transition) => Object.keys(transition.when ?? {}).filter((path54) => path54.startsWith("result."))
22521
22508
  );
22522
22509
  }
22523
22510
  function conditionMatches(condition, context) {
22524
- return Object.entries(condition).every(([path52, expected]) => valueMatches(resolveDottedPath2(context, path52), expected));
22511
+ return Object.entries(condition).every(([path54, expected]) => valueMatches(resolveDottedPath2(context, path54), expected));
22525
22512
  }
22526
22513
  function withWorkflowBoundaryEval(capability, result) {
22527
22514
  const capabilityKind = capability.config.capabilityKind;
@@ -22573,7 +22560,7 @@ function workflowStepToJob(step, parent, chainData) {
22573
22560
  capability: step.capability,
22574
22561
  ...step.implementation ? { implementation: step.implementation } : {},
22575
22562
  ...composeStepWhy(parent.why, step) ? { why: composeStepWhy(parent.why, step) } : {},
22576
- ...step.agent ?? parent.agent ? { agent: step.agent ?? parent.agent } : {},
22563
+ ...parent.agent ? { agent: parent.agent } : {},
22577
22564
  ...parent.schedule ? { schedule: parent.schedule } : {},
22578
22565
  ...typeof target === "number" ? { target } : {},
22579
22566
  cliArgs,
@@ -22679,7 +22666,7 @@ function loadCapabilityContext(slug, cwd) {
22679
22666
  return resolveCapabilityFolder(slug, hydratedCapabilitiesRoot(cwd));
22680
22667
  }
22681
22668
  function hydratedCapabilitiesRoot(cwd) {
22682
- return path47.join(cwd, ".kody-engine", "definitions", "capabilities");
22669
+ return path48.join(cwd, ".kody-engine", "definitions", "capabilities");
22683
22670
  }
22684
22671
  function loadWorkflowContext(slug, base) {
22685
22672
  if (!slug || !isWorkflowDefinitionId(slug)) return null;
@@ -22844,7 +22831,7 @@ function translateOpenAISseToBrain(opts) {
22844
22831
 
22845
22832
  // src/servers/brain-serve.ts
22846
22833
  import { createServer as createServer2 } from "http";
22847
- import * as path50 from "path";
22834
+ import * as path52 from "path";
22848
22835
 
22849
22836
  // src/chat/loop.ts
22850
22837
  init_agent();
@@ -23886,7 +23873,7 @@ init_config();
23886
23873
 
23887
23874
  // src/definition-hydration.ts
23888
23875
  init_state_backend();
23889
- import { createHash as createHash3 } from "crypto";
23876
+ import { createHash as createHash2 } from "crypto";
23890
23877
  import * as fs16 from "fs";
23891
23878
  import * as path17 from "path";
23892
23879
  var SLUG_RE = /^[a-z0-9][a-z0-9_-]{0,127}$/;
@@ -23907,7 +23894,7 @@ function normalizeDefinitionBundle(bundle) {
23907
23894
  return { schemaVersion: 1, files };
23908
23895
  }
23909
23896
  function definitionVersion(bundle) {
23910
- return `sha256:${createHash3("sha256").update(JSON.stringify(normalizeDefinitionBundle(bundle))).digest("hex")}`;
23897
+ return `sha256:${createHash2("sha256").update(JSON.stringify(normalizeDefinitionBundle(bundle))).digest("hex")}`;
23911
23898
  }
23912
23899
  function verifyDefinition(definition) {
23913
23900
  if (!SLUG_RE.test(definition.slug)) throw new Error(`invalid definition slug: ${definition.slug}`);
@@ -24018,8 +24005,8 @@ async function hydrateDefinitionsFromEnv(cwd = process.cwd(), env = process.env)
24018
24005
 
24019
24006
  // src/kody-cli.ts
24020
24007
  import { execFileSync as execFileSync24 } from "child_process";
24021
- import * as fs49 from "fs";
24022
- import * as path48 from "path";
24008
+ import * as fs51 from "fs";
24009
+ import * as path50 from "path";
24023
24010
 
24024
24011
  // src/app-auth.ts
24025
24012
  import { createSign } from "crypto";
@@ -24655,6 +24642,74 @@ function readRunRequestFromEnv(env = process.env) {
24655
24642
 
24656
24643
  // src/kody-cli.ts
24657
24644
  init_runtimePaths();
24645
+
24646
+ // src/loopDefinitions.ts
24647
+ init_definition_paths();
24648
+ import * as fs50 from "fs";
24649
+ import * as path49 from "path";
24650
+ var ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
24651
+ function normalizeLoopDefinition(value) {
24652
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
24653
+ const raw = value;
24654
+ if (Object.keys(raw).some((key) => !["id", "trigger", "target", "input", "enabled"].includes(key))) return null;
24655
+ if (typeof raw.id !== "string" || !ID.test(raw.id)) return null;
24656
+ if (typeof raw.enabled !== "boolean") return null;
24657
+ if (!isObject(raw.input) || !isObject(raw.trigger) || !isObject(raw.target)) return null;
24658
+ const targetKind = raw.target.kind;
24659
+ const targetId = raw.target.id;
24660
+ if (targetKind !== "workflow" && targetKind !== "capability" || typeof targetId !== "string" || !ID.test(targetId)) {
24661
+ return null;
24662
+ }
24663
+ const trigger = normalizeTrigger(raw.trigger);
24664
+ if (!trigger) return null;
24665
+ return {
24666
+ id: raw.id,
24667
+ trigger,
24668
+ target: { kind: targetKind, id: targetId },
24669
+ input: raw.input,
24670
+ enabled: raw.enabled
24671
+ };
24672
+ }
24673
+ function readLoopDefinition(cwd, id) {
24674
+ if (!ID.test(id)) return null;
24675
+ const roots = [
24676
+ path49.join(cwd, ".kody-engine", "runtime"),
24677
+ definitionsRoot(cwd)
24678
+ ];
24679
+ for (const root of roots) {
24680
+ const filePath = path49.join(root, "loops", id, "loop.json");
24681
+ if (!fs50.existsSync(filePath)) continue;
24682
+ try {
24683
+ const loop = normalizeLoopDefinition(JSON.parse(fs50.readFileSync(filePath, "utf8")));
24684
+ if (loop?.id === id) return loop;
24685
+ } catch {
24686
+ }
24687
+ }
24688
+ return null;
24689
+ }
24690
+ function normalizeTrigger(raw) {
24691
+ if (raw.type === "manual" && Object.keys(raw).length === 1) return { type: "manual" };
24692
+ if (raw.type === "schedule" && typeof raw.every === "string" && /^\d+[mhd]$/.test(raw.every)) {
24693
+ if (raw.at === void 0 && Object.keys(raw).every((key) => key === "type" || key === "every")) {
24694
+ return { type: "schedule", every: raw.every };
24695
+ }
24696
+ 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")) {
24697
+ return { type: "schedule", every: raw.every, at: { time: raw.at.time, timezone: raw.at.timezone } };
24698
+ }
24699
+ }
24700
+ if ((raw.type === "event" || raw.type === "webhook") && typeof raw.event === "string" && raw.event.trim() && Object.keys(raw).every((key) => key === "type" || key === "event")) {
24701
+ return { type: raw.type, event: raw.event.trim() };
24702
+ }
24703
+ if (raw.type === "condition" && typeof raw.expression === "string" && raw.expression.trim() && Object.keys(raw).every((key) => key === "type" || key === "expression")) {
24704
+ return { type: "condition", expression: raw.expression.trim() };
24705
+ }
24706
+ return null;
24707
+ }
24708
+ function isObject(value) {
24709
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
24710
+ }
24711
+
24712
+ // src/kody-cli.ts
24658
24713
  init_stateWorkspace();
24659
24714
  init_workflowDefinitions();
24660
24715
  var FAILED_DISPATCH_LABEL = {
@@ -24832,9 +24887,9 @@ async function resolveAuthToken(env = process.env) {
24832
24887
  return void 0;
24833
24888
  }
24834
24889
  function detectPackageManager2(cwd) {
24835
- if (fs49.existsSync(path48.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
24836
- if (fs49.existsSync(path48.join(cwd, "yarn.lock"))) return "yarn";
24837
- if (fs49.existsSync(path48.join(cwd, "bun.lockb"))) return "bun";
24890
+ if (fs51.existsSync(path50.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
24891
+ if (fs51.existsSync(path50.join(cwd, "yarn.lock"))) return "yarn";
24892
+ if (fs51.existsSync(path50.join(cwd, "bun.lockb"))) return "bun";
24838
24893
  return "npm";
24839
24894
  }
24840
24895
  function shouldChainScheduledWatch(match) {
@@ -24927,8 +24982,8 @@ function postFailureTail(issueNumber, cwd, reason) {
24927
24982
  const logPath = lastRunLogPath(cwd);
24928
24983
  let tail = "";
24929
24984
  try {
24930
- if (fs49.existsSync(logPath)) {
24931
- const content = fs49.readFileSync(logPath, "utf-8");
24985
+ if (fs51.existsSync(logPath)) {
24986
+ const content = fs51.readFileSync(logPath, "utf-8");
24932
24987
  tail = content.slice(-3e3);
24933
24988
  }
24934
24989
  } catch {
@@ -24957,7 +25012,7 @@ async function runCi(argv) {
24957
25012
  return 0;
24958
25013
  }
24959
25014
  const args = parseCiArgs(argv);
24960
- const cwd = args.cwd ? path48.resolve(args.cwd) : process.cwd();
25015
+ const cwd = args.cwd ? path50.resolve(args.cwd) : process.cwd();
24961
25016
  try {
24962
25017
  const n = unpackAllSecrets();
24963
25018
  if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
@@ -25016,9 +25071,9 @@ async function runCi(argv) {
25016
25071
  forceRunCliArgs = { goal: envForceMessage };
25017
25072
  }
25018
25073
  }
25019
- if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs49.existsSync(dispatchEventPath)) {
25074
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs51.existsSync(dispatchEventPath)) {
25020
25075
  try {
25021
- const evt = JSON.parse(fs49.readFileSync(dispatchEventPath, "utf-8"));
25076
+ const evt = JSON.parse(fs51.readFileSync(dispatchEventPath, "utf-8"));
25022
25077
  const inputs = objectValue2(evt.inputs);
25023
25078
  const issueInput = parseInt(String(inputs?.issue_number ?? ""), 10);
25024
25079
  const sessionInput = String(inputs?.sessionId ?? "");
@@ -25068,7 +25123,9 @@ async function runCi(argv) {
25068
25123
  workflow: forceRunAction,
25069
25124
  cliArgs: {}
25070
25125
  };
25071
- const scheduledWatchRoute = manualGoalManager || capabilityRoute || workflowRoute ? void 0 : dispatchScheduledWatches({ force: true, cwd }).find(
25126
+ const loop = manualGoalManager || capabilityRoute || workflowRoute ? null : readLoopDefinition(cwd, forceRunAction);
25127
+ const loopRoute = loop ? loop.target.kind === "workflow" ? { workflow: loop.target.id, cliArgs: loop.input } : { capability: loop.target.id, action: loop.target.id, cliArgs: loop.input } : void 0;
25128
+ const scheduledWatchRoute = manualGoalManager || capabilityRoute || workflowRoute || loopRoute ? void 0 : dispatchScheduledWatches({ force: true, cwd }).find(
25072
25129
  (match) => match.action === forceRunAction || match.capability === forceRunAction || match.implementation === forceRunAction
25073
25130
  );
25074
25131
  const route = manualGoalManager ? {
@@ -25076,7 +25133,7 @@ async function runCi(argv) {
25076
25133
  capability: "goal-manager",
25077
25134
  implementation: "goal-manager",
25078
25135
  cliArgs: forceRunCliArgs
25079
- } : capabilityRoute ?? workflowRoute ?? scheduledWatchRoute;
25136
+ } : capabilityRoute ?? workflowRoute ?? loopRoute ?? scheduledWatchRoute;
25080
25137
  if (!route) {
25081
25138
  const root = capabilitiesRoot(cwd);
25082
25139
  const available = listCapabilityActions(root).map((item) => item.action);
@@ -25425,8 +25482,8 @@ init_repoWorkspace();
25425
25482
 
25426
25483
  // src/scripts/brainTurnLog.ts
25427
25484
  init_runtimePaths();
25428
- import * as fs50 from "fs";
25429
- import * as path49 from "path";
25485
+ import * as fs52 from "fs";
25486
+ import * as path51 from "path";
25430
25487
  import posixPath4 from "path/posix";
25431
25488
  var live = /* @__PURE__ */ new Map();
25432
25489
  function brainEventsFilePath(dir, chatId) {
@@ -25434,8 +25491,8 @@ function brainEventsFilePath(dir, chatId) {
25434
25491
  }
25435
25492
  function lastPersistedSeq(dir, chatId) {
25436
25493
  const p = brainEventsFilePath(dir, chatId);
25437
- if (!fs50.existsSync(p)) return 0;
25438
- const lines = fs50.readFileSync(p, "utf-8").split("\n").filter(Boolean);
25494
+ if (!fs52.existsSync(p)) return 0;
25495
+ const lines = fs52.readFileSync(p, "utf-8").split("\n").filter(Boolean);
25439
25496
  if (lines.length === 0) return 0;
25440
25497
  try {
25441
25498
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -25445,9 +25502,9 @@ function lastPersistedSeq(dir, chatId) {
25445
25502
  }
25446
25503
  function readSince(dir, chatId, since) {
25447
25504
  const p = brainEventsFilePath(dir, chatId);
25448
- if (!fs50.existsSync(p)) return [];
25505
+ if (!fs52.existsSync(p)) return [];
25449
25506
  const out = [];
25450
- for (const line of fs50.readFileSync(p, "utf-8").split("\n")) {
25507
+ for (const line of fs52.readFileSync(p, "utf-8").split("\n")) {
25451
25508
  if (!line) continue;
25452
25509
  try {
25453
25510
  const rec = JSON.parse(line);
@@ -25473,12 +25530,12 @@ function beginTurn(dir, chatId) {
25473
25530
  };
25474
25531
  live.set(chatId, state);
25475
25532
  const p = brainEventsFilePath(dir, chatId);
25476
- fs50.mkdirSync(path49.dirname(p), { recursive: true });
25533
+ fs52.mkdirSync(path51.dirname(p), { recursive: true });
25477
25534
  return (event) => {
25478
25535
  state.seq += 1;
25479
25536
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
25480
25537
  try {
25481
- fs50.appendFileSync(p, `${JSON.stringify(rec)}
25538
+ fs52.appendFileSync(p, `${JSON.stringify(rec)}
25482
25539
  `);
25483
25540
  } catch (err) {
25484
25541
  process.stderr.write(
@@ -25517,7 +25574,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
25517
25574
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
25518
25575
  };
25519
25576
  try {
25520
- fs50.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
25577
+ fs52.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
25521
25578
  `);
25522
25579
  } catch {
25523
25580
  }
@@ -25910,7 +25967,7 @@ function buildServer(opts) {
25910
25967
  const runTurn = opts.runTurn ?? runChatTurn;
25911
25968
  const createStore = opts.createStore ?? createSessionStore;
25912
25969
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
25913
- const reposRoot = opts.reposRoot ?? path50.join(path50.dirname(path50.resolve(opts.cwd)), "repos");
25970
+ const reposRoot = opts.reposRoot ?? path52.join(path52.dirname(path52.resolve(opts.cwd)), "repos");
25914
25971
  return createServer2(async (req, res) => {
25915
25972
  if (!req.method || !req.url) {
25916
25973
  sendJson(res, 400, { error: "bad request" });
@@ -26514,7 +26571,7 @@ async function loadConfigSafe() {
26514
26571
  }
26515
26572
 
26516
26573
  // src/chat-cli.ts
26517
- import * as path51 from "path";
26574
+ import * as path53 from "path";
26518
26575
 
26519
26576
  // src/chat/inbox.ts
26520
26577
  import { execFileSync as execFileSync25 } from "child_process";
@@ -26795,7 +26852,7 @@ async function runChat(argv) {
26795
26852
  ${CHAT_HELP}`);
26796
26853
  return 64;
26797
26854
  }
26798
- const cwd = args.cwd ? path51.resolve(args.cwd) : process.cwd();
26855
+ const cwd = args.cwd ? path53.resolve(args.cwd) : process.cwd();
26799
26856
  const sessionId = args.sessionId;
26800
26857
  const runRequest = readRunRequestFromEnv();
26801
26858
  if (runRequest && "request" in runRequest) {
@@ -26985,8 +27042,8 @@ var FlyClient = class {
26985
27042
  get fetch() {
26986
27043
  return this.opts.fetchImpl ?? fetch;
26987
27044
  }
26988
- async call(path52, init = {}) {
26989
- const res = await this.fetch(`${FLY_API_BASE}${path52}`, {
27045
+ async call(path54, init = {}) {
27046
+ const res = await this.fetch(`${FLY_API_BASE}${path54}`, {
26990
27047
  method: init.method ?? "GET",
26991
27048
  headers: {
26992
27049
  Authorization: `Bearer ${this.opts.token}`,
@@ -26997,7 +27054,7 @@ var FlyClient = class {
26997
27054
  if (res.status === 404 && init.allow404) return null;
26998
27055
  if (!res.ok) {
26999
27056
  const text2 = await res.text().catch(() => "");
27000
- throw new Error(`Fly API ${res.status} on ${path52}: ${text2.slice(0, 200) || res.statusText}`);
27057
+ throw new Error(`Fly API ${res.status} on ${path54}: ${text2.slice(0, 200) || res.statusText}`);
27001
27058
  }
27002
27059
  if (res.status === 204) return null;
27003
27060
  const raw = await res.text();
@@ -27750,7 +27807,7 @@ async function poolServe() {
27750
27807
 
27751
27808
  // src/servers/runner-serve.ts
27752
27809
  import { spawn as spawn9 } from "child_process";
27753
- import * as fs51 from "fs";
27810
+ import * as fs53 from "fs";
27754
27811
  import { createServer as createServer6 } from "http";
27755
27812
  var DEFAULT_PORT2 = 8080;
27756
27813
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -27885,8 +27942,8 @@ async function defaultRunJob(job) {
27885
27942
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
27886
27943
  const branch = job.ref ?? "main";
27887
27944
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
27888
- fs51.rmSync(workdir, { recursive: true, force: true });
27889
- fs51.mkdirSync(workdir, { recursive: true });
27945
+ fs53.rmSync(workdir, { recursive: true, force: true });
27946
+ fs53.mkdirSync(workdir, { recursive: true });
27890
27947
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
27891
27948
  const target = job.runRequest.target;
27892
27949
  const interactive = target.type === "chat";