@kody-ade/kody-engine 0.4.264 → 0.4.266

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.264",
18
+ version: "0.4.266",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -763,6 +763,8 @@ function parseReleaseConfig(raw) {
763
763
  if (typeof r.publishCommand === "string") out.publishCommand = r.publishCommand;
764
764
  if (typeof r.notifyCommand === "string") out.notifyCommand = r.notifyCommand;
765
765
  if (typeof r.e2eCommand === "string") out.e2eCommand = r.e2eCommand;
766
+ if (typeof r.productionUrl === "string") out.productionUrl = r.productionUrl;
767
+ if (typeof r.smokeCommand === "string") out.smokeCommand = r.smokeCommand;
766
768
  if (typeof r.draftRelease === "boolean") out.draftRelease = r.draftRelease;
767
769
  if (typeof r.releaseBranch === "string") out.releaseBranch = r.releaseBranch;
768
770
  if (typeof r.timeoutMs === "number" && r.timeoutMs > 0) out.timeoutMs = Math.floor(r.timeoutMs);
@@ -1407,12 +1409,12 @@ function parseCapabilityConfig(raw) {
1407
1409
  capabilityTools: tools,
1408
1410
  implementations,
1409
1411
  executables: stringList(raw.executables),
1410
- capabilityKind: capabilityKindField(raw.capabilityKind),
1411
1412
  role: stringField(raw.role),
1412
1413
  describe: stringField(raw.describe),
1413
1414
  stage: stringField(raw.stage),
1414
1415
  readsFrom: stringList(raw.readsFrom ?? raw.reads_from),
1415
- writesTo: stringList(raw.writesTo ?? raw.writes_to)
1416
+ writesTo: stringList(raw.writesTo ?? raw.writes_to),
1417
+ workflow: parseWorkflow(raw.workflow)
1416
1418
  };
1417
1419
  }
1418
1420
  function parseCapabilityBody(raw, slug2) {
@@ -1448,9 +1450,44 @@ function stringList(value) {
1448
1450
  }
1449
1451
  return [];
1450
1452
  }
1451
- function capabilityKindField(value) {
1452
- if (value === "observe" || value === "act" || value === "verify") return value;
1453
- return void 0;
1453
+ function parseWorkflow(value) {
1454
+ const stepsRaw = Array.isArray(value) ? value : value && typeof value === "object" && Array.isArray(value.steps) ? value.steps : [];
1455
+ const steps = stepsRaw.map(parseWorkflowStep).filter((step) => step !== null);
1456
+ return steps.length > 0 ? { steps } : void 0;
1457
+ }
1458
+ function parseWorkflowStep(value) {
1459
+ if (typeof value === "string") {
1460
+ const capability2 = value.trim();
1461
+ return isSafeSlug(capability2) ? { capability: capability2 } : null;
1462
+ }
1463
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1464
+ const raw = value;
1465
+ const capability = stringField(raw.capability ?? raw.action);
1466
+ if (!capability || !isSafeSlug(capability)) return null;
1467
+ const executable = stringField(raw.executable ?? raw.implementation);
1468
+ const action = stringField(raw.action);
1469
+ const agent = stringField(raw.agent);
1470
+ const reason = stringField(raw.reason);
1471
+ const target = stringField(raw.target);
1472
+ const cliArgs = raw.cliArgs;
1473
+ return {
1474
+ capability,
1475
+ ...action && isSafeSlug(action) ? { action } : {},
1476
+ ...executable && isSafeSlug(executable) ? { executable } : {},
1477
+ ...target === "issue" || target === "pr" ? { target } : {},
1478
+ ...agent && isSafeSlug(agent) ? { agent } : {},
1479
+ ...reason ? { reason } : {},
1480
+ ...cliArgs && typeof cliArgs === "object" && !Array.isArray(cliArgs) ? { cliArgs } : {},
1481
+ ...isPlainObject(raw.runWhen) ? { runWhen: raw.runWhen } : {},
1482
+ ...stringList(raw.continueOn ?? raw.continue_on).length > 0 ? { continueOn: stringList(raw.continueOn ?? raw.continue_on) } : {},
1483
+ ...raw.saveReport === true ? { saveReport: true } : {}
1484
+ };
1485
+ }
1486
+ function isPlainObject(value) {
1487
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1488
+ }
1489
+ function isSafeSlug(value) {
1490
+ return /^[a-z][a-z0-9-]*$/.test(value) && !value.includes("..");
1454
1491
  }
1455
1492
  var CAPABILITY_PROFILE_FILE, CAPABILITY_BODY_FILE;
1456
1493
  var init_capabilityFolders = __esm({
@@ -1483,7 +1520,8 @@ function getCompanyStoreAssetRoot(kind) {
1483
1520
  capabilities: "capabilities",
1484
1521
  executables: "executables",
1485
1522
  goals: "goals",
1486
- agents: "agents"
1523
+ agents: "agents",
1524
+ workflows: "workflows"
1487
1525
  };
1488
1526
  return path7.join(root, ".kody", folderByKind[kind]);
1489
1527
  }
@@ -1678,13 +1716,13 @@ function listCapabilityActions(projectCapabilitiesRoot = getProjectCapabilitiesR
1678
1716
  const projectExecutableRoots = [getProjectExecutablesRoot()];
1679
1717
  const storeExecutableRoot = getCompanyStoreExecutablesRoot();
1680
1718
  const storeExecutableRoots = storeExecutableRoot ? [storeExecutableRoot] : [];
1681
- for (const action of listFolderCapabilityActions(projectCapabilitiesRoot, "project-folder", true))
1719
+ for (const action of listFolderCapabilityActions(projectCapabilitiesRoot, "project-folder"))
1682
1720
  add(action);
1683
1721
  for (const root of projectExecutableRoots) {
1684
1722
  for (const action of listExecutableCapabilityActions(root, "project-executable")) add(action);
1685
1723
  }
1686
1724
  if (storeCapabilitiesRoot) {
1687
- for (const action of listFolderCapabilityActions(storeCapabilitiesRoot, "company-store", true)) add(action);
1725
+ for (const action of listFolderCapabilityActions(storeCapabilitiesRoot, "company-store")) add(action);
1688
1726
  }
1689
1727
  for (const root of storeExecutableRoots) {
1690
1728
  for (const action of listExecutableCapabilityActions(root, "company-store-executable")) add(action);
@@ -1707,7 +1745,16 @@ function resolveCapabilityFolder(slug2, projectCapabilitiesRoot = getProjectCapa
1707
1745
  }
1708
1746
  return null;
1709
1747
  }
1748
+ function getCapabilityActionInputs(action) {
1749
+ const resolved = resolveCapabilityAction(action);
1750
+ if (!resolved) return null;
1751
+ return getProfileInputs(resolved.executable);
1752
+ }
1710
1753
  function resolveCapabilityExecution(capability) {
1754
+ const firstWorkflowStep = capability.config.workflow?.steps[0];
1755
+ if (firstWorkflowStep) {
1756
+ return { executable: firstWorkflowStep.executable ?? firstWorkflowStep.capability, cliArgs: {} };
1757
+ }
1711
1758
  const executable = capability.config.implementation ?? capability.config.executable ?? capability.config.implementations?.[0] ?? capability.config.executables?.[0] ?? (capability.config.role ? capability.slug : void 0) ?? (capability.config.tickScript ? "capability-tick-scripted" : "capability-tick");
1712
1759
  const cliArgs = executableDeclaresInput(executable, "capability") ? { capability: capability.slug } : {};
1713
1760
  return { executable, cliArgs };
@@ -1755,7 +1802,7 @@ function listExecutableCapabilityActions(root, source) {
1755
1802
  const action = typeof raw.action === "string" && raw.action.trim() ? raw.action.trim() : "";
1756
1803
  if (!action) continue;
1757
1804
  if (!PUBLIC_EXECUTABLE_ROLES.has(String(raw.role))) continue;
1758
- if (!PUBLIC_EXECUTABLE_CAPABILITY_KINDS.has(String(raw.capabilityKind))) continue;
1805
+ if (typeof raw.kind !== "string" || !raw.kind.trim()) continue;
1759
1806
  if (!Array.isArray(raw.inputs)) continue;
1760
1807
  out.push({
1761
1808
  action,
@@ -1771,14 +1818,13 @@ function listExecutableCapabilityActions(root, source) {
1771
1818
  }
1772
1819
  return out.sort((a, b) => a.action.localeCompare(b.action));
1773
1820
  }
1774
- function listFolderCapabilityActions(root, source, requireCapabilityKind = false) {
1821
+ function listFolderCapabilityActions(root, source) {
1775
1822
  if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
1776
1823
  const out = [];
1777
1824
  for (const slug2 of listCapabilityFolderSlugs(root)) {
1778
1825
  if (!isSafeName(slug2)) continue;
1779
1826
  const capability = readCapabilityFolder(root, slug2);
1780
1827
  if (!capability) continue;
1781
- if (requireCapabilityKind && !capability.config.capabilityKind) continue;
1782
1828
  const action = capability.config.action ?? slug2;
1783
1829
  const { executable, cliArgs } = resolveCapabilityExecution(capability);
1784
1830
  out.push({
@@ -1788,7 +1834,6 @@ function listFolderCapabilityActions(root, source, requireCapabilityKind = false
1788
1834
  cliArgs,
1789
1835
  source,
1790
1836
  describe: capability.config.describe ?? capability.title,
1791
- capabilityKind: capability.config.capabilityKind,
1792
1837
  profilePath: capability.profilePath,
1793
1838
  bodyPath: capability.bodyPath
1794
1839
  });
@@ -1811,7 +1856,6 @@ function listBuiltinCapabilityActions(root = getBuiltinCapabilitiesRoot()) {
1811
1856
  cliArgs: {},
1812
1857
  source: "builtin",
1813
1858
  describe: capability.config.describe ?? capability.title,
1814
- capabilityKind: capability.config.capabilityKind,
1815
1859
  profilePath: capability.profilePath,
1816
1860
  bodyPath: capability.bodyPath
1817
1861
  });
@@ -1854,14 +1898,13 @@ function parseGenericFlags(argv) {
1854
1898
  if (positional.length > 0) args._ = positional;
1855
1899
  return args;
1856
1900
  }
1857
- var PUBLIC_EXECUTABLE_ROLES, PUBLIC_EXECUTABLE_CAPABILITY_KINDS;
1901
+ var PUBLIC_EXECUTABLE_ROLES;
1858
1902
  var init_registry = __esm({
1859
1903
  "src/registry.ts"() {
1860
1904
  "use strict";
1861
1905
  init_capabilityFolders();
1862
1906
  init_companyStore();
1863
1907
  PUBLIC_EXECUTABLE_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
1864
- PUBLIC_EXECUTABLE_CAPABILITY_KINDS = /* @__PURE__ */ new Set(["observe", "act", "verify"]);
1865
1908
  }
1866
1909
  });
1867
1910
 
@@ -3090,6 +3133,60 @@ var init_gha = __esm({
3090
3133
  }
3091
3134
  });
3092
3135
 
3136
+ // src/agents.ts
3137
+ import * as fs16 from "fs";
3138
+ import * as path16 from "path";
3139
+ function stripFrontmatter(raw) {
3140
+ const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
3141
+ return (match ? match[1] : raw).trim();
3142
+ }
3143
+ function loadAgentIdentity(cwd, slug2, agentsDir = DEFAULT_AGENT_DIR) {
3144
+ const trimmed = slug2.trim();
3145
+ if (!trimmed) throw new Error("loadAgentIdentity: empty agent slug");
3146
+ const agentPath = resolveAgentFile(cwd, trimmed, agentsDir);
3147
+ if (fs16.existsSync(agentPath)) {
3148
+ const body = stripFrontmatter(fs16.readFileSync(agentPath, "utf-8"));
3149
+ if (body) return body;
3150
+ const builtinForEmpty = BUILTIN_AGENTS[trimmed];
3151
+ if (builtinForEmpty) return builtinForEmpty;
3152
+ throw new Error(`loadAgentIdentity: agent '${trimmed}' agent identity body is empty (${agentPath})`);
3153
+ }
3154
+ const builtin = BUILTIN_AGENTS[trimmed];
3155
+ if (builtin) return builtin;
3156
+ throw new Error(`loadAgentIdentity: agent '${trimmed}' declared but ${agentPath} does not exist`);
3157
+ }
3158
+ function resolveAgentFile(cwd, slug2, agentsDir = DEFAULT_AGENT_DIR) {
3159
+ const localPath = path16.join(cwd, agentsDir, `${slug2}.md`);
3160
+ if (fs16.existsSync(localPath)) return localPath;
3161
+ const storeAgentRoot = getCompanyStoreAssetRoot("agents");
3162
+ if (storeAgentRoot) {
3163
+ const storePath = path16.join(storeAgentRoot, `${slug2}.md`);
3164
+ if (fs16.existsSync(storePath)) return storePath;
3165
+ }
3166
+ return localPath;
3167
+ }
3168
+ function frameAgentIdentity(slug2, agent) {
3169
+ return [
3170
+ `## Who you are \u2014 agent identity (authoritative identity)`,
3171
+ ``,
3172
+ `You are operating as agent \`${slug2}\`. This identity defines *who* you are:`,
3173
+ `your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
3174
+ `this identity's restrictions are stricter than the task, **the agent wins** \u2014 a task`,
3175
+ `can never grant you authority your agent withholds.`,
3176
+ ``,
3177
+ agent
3178
+ ].join("\n");
3179
+ }
3180
+ var DEFAULT_AGENT_DIR, BUILTIN_AGENTS;
3181
+ var init_agents = __esm({
3182
+ "src/agents.ts"() {
3183
+ "use strict";
3184
+ init_companyStore();
3185
+ DEFAULT_AGENT_DIR = ".kody/agents";
3186
+ BUILTIN_AGENTS = {};
3187
+ }
3188
+ });
3189
+
3093
3190
  // src/capabilityReport.ts
3094
3191
  function parseCapabilityReportsFromText(text) {
3095
3192
  const reports = [];
@@ -3255,60 +3352,6 @@ var init_capabilityResult = __esm({
3255
3352
  }
3256
3353
  });
3257
3354
 
3258
- // src/agents.ts
3259
- import * as fs16 from "fs";
3260
- import * as path16 from "path";
3261
- function stripFrontmatter(raw) {
3262
- const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
3263
- return (match ? match[1] : raw).trim();
3264
- }
3265
- function loadAgentIdentity(cwd, slug2, agentsDir = DEFAULT_AGENT_DIR) {
3266
- const trimmed = slug2.trim();
3267
- if (!trimmed) throw new Error("loadAgentIdentity: empty agent slug");
3268
- const agentPath = resolveAgentFile(cwd, trimmed, agentsDir);
3269
- if (fs16.existsSync(agentPath)) {
3270
- const body = stripFrontmatter(fs16.readFileSync(agentPath, "utf-8"));
3271
- if (body) return body;
3272
- const builtinForEmpty = BUILTIN_AGENTS[trimmed];
3273
- if (builtinForEmpty) return builtinForEmpty;
3274
- throw new Error(`loadAgentIdentity: agent '${trimmed}' agent identity body is empty (${agentPath})`);
3275
- }
3276
- const builtin = BUILTIN_AGENTS[trimmed];
3277
- if (builtin) return builtin;
3278
- throw new Error(`loadAgentIdentity: agent '${trimmed}' declared but ${agentPath} does not exist`);
3279
- }
3280
- function resolveAgentFile(cwd, slug2, agentsDir = DEFAULT_AGENT_DIR) {
3281
- const localPath = path16.join(cwd, agentsDir, `${slug2}.md`);
3282
- if (fs16.existsSync(localPath)) return localPath;
3283
- const storeAgentRoot = getCompanyStoreAssetRoot("agents");
3284
- if (storeAgentRoot) {
3285
- const storePath = path16.join(storeAgentRoot, `${slug2}.md`);
3286
- if (fs16.existsSync(storePath)) return storePath;
3287
- }
3288
- return localPath;
3289
- }
3290
- function frameAgentIdentity(slug2, agent) {
3291
- return [
3292
- `## Who you are \u2014 agent identity (authoritative identity)`,
3293
- ``,
3294
- `You are operating as agent \`${slug2}\`. This identity defines *who* you are:`,
3295
- `your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
3296
- `this identity's restrictions are stricter than the task, **the agent wins** \u2014 a task`,
3297
- `can never grant you authority your agent withholds.`,
3298
- ``,
3299
- agent
3300
- ].join("\n");
3301
- }
3302
- var DEFAULT_AGENT_DIR, BUILTIN_AGENTS;
3303
- var init_agents = __esm({
3304
- "src/agents.ts"() {
3305
- "use strict";
3306
- init_companyStore();
3307
- DEFAULT_AGENT_DIR = ".kody/agents";
3308
- BUILTIN_AGENTS = {};
3309
- }
3310
- });
3311
-
3312
3355
  // src/profile-error.ts
3313
3356
  var ProfileError;
3314
3357
  var init_profile_error = __esm({
@@ -3660,7 +3703,6 @@ function loadProfile(profilePath) {
3660
3703
  implementation: execRef,
3661
3704
  executable: execRef,
3662
3705
  describe: typeof r.describe === "string" ? r.describe : base.describe,
3663
- capabilityKind: parseCapabilityKind(profilePath, r.capabilityKind) ?? base.capabilityKind,
3664
3706
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : base.agent,
3665
3707
  capabilityTools: parseStringArray2(r.capabilityTools ?? r.capabilityTools ?? r.tools) ?? base.capabilityTools,
3666
3708
  mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : base.mentions
@@ -3705,7 +3747,6 @@ function loadProfile(profilePath) {
3705
3747
  implementation: void 0,
3706
3748
  executable: void 0,
3707
3749
  describe: typeof r.describe === "string" ? r.describe : "",
3708
- capabilityKind: parseCapabilityKind(profilePath, r.capabilityKind),
3709
3750
  // Optional agent to run as. Empty/blank string → undefined (no agent).
3710
3751
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : void 0,
3711
3752
  // Locked-toolbox palette + mentions from folder-capability profile metadata.
@@ -3800,13 +3841,6 @@ function requireString(p, r, key) {
3800
3841
  }
3801
3842
  return v;
3802
3843
  }
3803
- function parseCapabilityKind(p, raw) {
3804
- if (raw === void 0 || raw === null || raw === "") return void 0;
3805
- if (typeof raw !== "string" || !VALID_CAPABILITY_KINDS.has(raw)) {
3806
- throw new ProfileError(p, `"capabilityKind" must be one of: observe | act | verify`);
3807
- }
3808
- return raw;
3809
- }
3810
3844
  function parseStringArray2(raw) {
3811
3845
  if (!Array.isArray(raw)) return void 0;
3812
3846
  const values = raw.map((t) => String(t).trim()).filter(Boolean);
@@ -4053,7 +4087,7 @@ function parseScriptList(p, key, raw) {
4053
4087
  }
4054
4088
  return out;
4055
4089
  }
4056
- var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHILD_TARGETS, VALID_PHASES, VALID_CAPABILITY_KINDS, KNOWN_PROFILE_KEYS;
4090
+ var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHILD_TARGETS, VALID_PHASES, KNOWN_PROFILE_KEYS;
4057
4091
  var init_profile = __esm({
4058
4092
  "src/profile.ts"() {
4059
4093
  "use strict";
@@ -4069,7 +4103,6 @@ var init_profile = __esm({
4069
4103
  VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
4070
4104
  VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
4071
4105
  VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
4072
- VALID_CAPABILITY_KINDS = /* @__PURE__ */ new Set(["observe", "act", "verify"]);
4073
4106
  KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
4074
4107
  "name",
4075
4108
  "action",
@@ -4083,10 +4116,10 @@ var init_profile = __esm({
4083
4116
  "capabilityTools",
4084
4117
  "tools",
4085
4118
  "mentions",
4086
- "capabilityKind",
4087
4119
  "stage",
4088
4120
  "readsFrom",
4089
4121
  "writesTo",
4122
+ "workflow",
4090
4123
  "describe",
4091
4124
  "role",
4092
4125
  "kind",
@@ -6078,7 +6111,7 @@ function asPreferredRunTime(value) {
6078
6111
  function asLoopTarget(value) {
6079
6112
  const raw = asRecord(value);
6080
6113
  if (!raw) return void 0;
6081
- if (raw.type !== "goal" && raw.type !== "capability") return void 0;
6114
+ if (raw.type !== "goal" && raw.type !== "capability" && raw.type !== "workflow") return void 0;
6082
6115
  if (typeof raw.id !== "string" || raw.id.trim().length === 0) return void 0;
6083
6116
  return { type: raw.type, id: raw.id };
6084
6117
  }
@@ -6174,10 +6207,10 @@ var init_state2 = __esm({
6174
6207
  "use strict";
6175
6208
  VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
6176
6209
  GoalStateError = class extends Error {
6177
- constructor(path48, message) {
6178
- super(`Invalid goal state at ${path48}:
6210
+ constructor(path49, message) {
6211
+ super(`Invalid goal state at ${path49}:
6179
6212
  ${message}`);
6180
- this.path = path48;
6213
+ this.path = path49;
6181
6214
  this.name = "GoalStateError";
6182
6215
  }
6183
6216
  path;
@@ -6190,17 +6223,12 @@ import * as fs25 from "fs";
6190
6223
  function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
6191
6224
  const logs = goalRunLogs(data);
6192
6225
  const existing = logs[goalId];
6193
- const path48 = existing?.path ?? goalRunLogPath(goalId, data);
6226
+ const path49 = existing?.path ?? goalRunLogPath(goalId, data);
6194
6227
  logs[goalId] = {
6195
- path: path48,
6228
+ path: path49,
6196
6229
  events: [
6197
6230
  ...existing?.events ?? [],
6198
- {
6199
- version: 1,
6200
- time: at,
6201
- goalId,
6202
- ...event
6203
- }
6231
+ buildGoalRunLogEvent(data, goalId, event, at)
6204
6232
  ]
6205
6233
  };
6206
6234
  }
@@ -6298,15 +6326,33 @@ function githubRunId() {
6298
6326
  const attempt = process.env.GITHUB_RUN_ATTEMPT?.trim();
6299
6327
  return attempt ? `gh-${runId}-${attempt}` : `gh-${runId}`;
6300
6328
  }
6329
+ function buildGoalRunLogEvent(data, goalId, event, at) {
6330
+ const trigger = event.trigger ?? triggerContext();
6331
+ const job = event.job ?? jobContext(data);
6332
+ const base = {
6333
+ version: 1,
6334
+ time: at,
6335
+ goalId,
6336
+ ...event
6337
+ };
6338
+ if (trigger !== void 0) base.trigger = trigger;
6339
+ if (job !== void 0) base.job = job;
6340
+ const context = event.dispatchContext ?? dispatchContext(base, trigger, job);
6341
+ if (context !== void 0) base.dispatchContext = context;
6342
+ return base;
6343
+ }
6301
6344
  function enrichGoalRunLogEvent(config, data, logPath, event) {
6302
6345
  const stateRepo = stateRepoContext(config, event.goalId, logPath);
6346
+ const trigger = event.trigger ?? triggerContext();
6347
+ const job = event.job ?? jobContext(data);
6303
6348
  return pruneUndefined({
6304
6349
  ...event,
6305
6350
  run: event.run ?? runContext(data),
6306
6351
  repo: event.repo ?? repoContext(config),
6307
6352
  stateRepo: event.stateRepo ?? stateRepo,
6308
- trigger: event.trigger ?? triggerContext(),
6309
- job: event.job ?? jobContext(data),
6353
+ trigger,
6354
+ job,
6355
+ dispatchContext: event.dispatchContext ?? dispatchContext(event, trigger, job),
6310
6356
  links: event.links ?? linkContext(stateRepo)
6311
6357
  });
6312
6358
  }
@@ -6353,9 +6399,16 @@ function stateRepoContext(config, goalId, logPath) {
6353
6399
  function triggerContext() {
6354
6400
  const event = readGithubEvent();
6355
6401
  const inputs = recordValue2(event?.inputs);
6356
- return pruneUndefined({
6357
- eventName: process.env.GITHUB_EVENT_NAME?.trim() || void 0,
6358
- actor: process.env.GITHUB_ACTOR?.trim() || void 0,
6402
+ const eventName = process.env.GITHUB_EVENT_NAME?.trim() || void 0;
6403
+ const githubActor = process.env.GITHUB_ACTOR?.trim() || void 0;
6404
+ if (!eventName && !githubActor && !process.env.GITHUB_EVENT_PATH) return void 0;
6405
+ const trigger = pruneUndefined({
6406
+ source: eventName ? "github-actions" : "local",
6407
+ kind: triggerKind(eventName),
6408
+ eventName,
6409
+ actor: githubActor,
6410
+ githubActor,
6411
+ actorRole: triggerActorRole(eventName),
6359
6412
  eventPath: process.env.GITHUB_EVENT_PATH?.trim() || void 0,
6360
6413
  issue: numberValue(recordValue2(event?.issue)?.number),
6361
6414
  pullRequest: numberValue(recordValue2(event?.pull_request)?.number),
@@ -6363,23 +6416,70 @@ function triggerContext() {
6363
6416
  schedule: stringValue(event?.schedule),
6364
6417
  inputs: inputs ? pickRecord(inputs, ["issue_number", "sessionId", "message", "model", "title", "executable", "base"]) : void 0
6365
6418
  });
6419
+ return Object.keys(trigger).length > 0 ? trigger : void 0;
6420
+ }
6421
+ function triggerKind(eventName) {
6422
+ if (!eventName) return "local";
6423
+ if (eventName === "schedule") return "schedule";
6424
+ if (eventName === "workflow_dispatch") return "manual-workflow-dispatch";
6425
+ return eventName;
6426
+ }
6427
+ function triggerActorRole(eventName) {
6428
+ if (!eventName) return void 0;
6429
+ if (eventName === "schedule") return "github workflow run actor; not the manual dispatcher";
6430
+ if (eventName === "workflow_dispatch") return "manual workflow dispatcher";
6431
+ return "github event actor";
6366
6432
  }
6367
6433
  function jobContext(data) {
6368
6434
  const job = pruneUndefined({
6369
- id: stringValue(data.jobId),
6370
- key: stringValue(data.jobKey),
6371
- flavor: stringValue(data.jobFlavor),
6372
- action: stringValue(data.jobAction),
6373
- capability: stringValue(data.jobCapability),
6374
- executable: stringValue(data.jobExecutable),
6375
- agent: stringValue(data.jobAgent),
6376
- schedule: stringValue(data.jobSchedule),
6377
- target: data.jobTarget,
6435
+ id: stringValue(data.jobId) ?? void 0,
6436
+ key: stringValue(data.jobKey) ?? void 0,
6437
+ flavor: stringValue(data.jobFlavor) ?? void 0,
6438
+ action: stringValue(data.jobAction) ?? void 0,
6439
+ capability: stringValue(data.jobCapability) ?? void 0,
6440
+ executable: stringValue(data.jobExecutable) ?? void 0,
6441
+ agent: stringValue(data.jobAgent) ?? void 0,
6442
+ schedule: stringValue(data.jobSchedule) ?? void 0,
6443
+ target: data.jobTarget ?? void 0,
6378
6444
  why: truncateString(stringValue(data.jobWhy), 1e3),
6379
6445
  saveReport: data.jobSaveReport === true ? true : void 0
6380
6446
  });
6381
6447
  return Object.keys(job).length > 0 ? job : void 0;
6382
6448
  }
6449
+ function dispatchContext(event, trigger, job) {
6450
+ if (!event.dispatch && event.status !== "dispatch") return void 0;
6451
+ const dispatch2 = event.dispatch ?? {};
6452
+ const context = pruneUndefined({
6453
+ triggeredBy: triggerLabel(trigger),
6454
+ triggerKind: stringValue(trigger?.kind),
6455
+ dispatchMode: dispatchMode(trigger),
6456
+ githubActor: stringValue(trigger?.githubActor) ?? stringValue(trigger?.actor),
6457
+ githubActorRole: stringValue(trigger?.actorRole),
6458
+ decidedBy: event.source,
6459
+ dispatchedBy: event.source,
6460
+ target: event.target,
6461
+ action: dispatch2.action ?? stringValue(job?.action),
6462
+ capability: dispatch2.capability ?? stringValue(job?.capability),
6463
+ reason: event.reason
6464
+ });
6465
+ return Object.keys(context).length > 0 ? context : void 0;
6466
+ }
6467
+ function triggerLabel(trigger) {
6468
+ const kind = stringValue(trigger?.kind);
6469
+ if (kind === "schedule") return "GitHub schedule";
6470
+ if (kind === "manual-workflow-dispatch") return "manual workflow dispatch";
6471
+ if (kind === "issue_comment") return "GitHub issue comment";
6472
+ if (kind === "pull_request") return "GitHub pull request";
6473
+ if (kind) return `GitHub ${kind}`;
6474
+ return "local run";
6475
+ }
6476
+ function dispatchMode(trigger) {
6477
+ const kind = stringValue(trigger?.kind);
6478
+ if (kind === "schedule") return "automated";
6479
+ if (kind === "manual-workflow-dispatch") return "manual";
6480
+ if (kind) return "event-driven";
6481
+ return "local";
6482
+ }
6383
6483
  function linkContext(stateRepo) {
6384
6484
  const links = {};
6385
6485
  const runId = process.env.GITHUB_RUN_ID?.trim();
@@ -7004,12 +7104,20 @@ function isCapabilityCadenceGoal(goal, extra) {
7004
7104
  function isGoalTargetLoop(goal) {
7005
7105
  return goal.type === "agentLoop" && goal.loopTarget?.type === "goal" && goal.loopTarget.id.trim().length > 0;
7006
7106
  }
7007
- function planGoalTargetLoopSchedule(opts) {
7107
+ function isWorkflowTargetLoop(goal) {
7108
+ return goal.type === "agentLoop" && goal.loopTarget?.type === "workflow" && goal.loopTarget.id.trim().length > 0;
7109
+ }
7110
+ function planTargetLoopSchedule(opts) {
7008
7111
  const now = opts.now ?? /* @__PURE__ */ new Date();
7009
7112
  const at = now.toISOString();
7010
- const targetId = opts.goal.loopTarget?.type === "goal" ? opts.goal.loopTarget.id.trim() : "";
7113
+ const target = opts.goal.loopTarget;
7114
+ const targetId = target?.id.trim() ?? "";
7011
7115
  if (!targetId) {
7012
- const reason = "goal target loop missing target goal";
7116
+ const reason = "loop missing target";
7117
+ return targetLoopDecision("blocked", reason, at);
7118
+ }
7119
+ if (target?.type !== "goal" && target?.type !== "workflow") {
7120
+ const reason = `unsupported loop target: ${String(target?.type ?? "missing")}`;
7013
7121
  return targetLoopDecision("blocked", reason, at);
7014
7122
  }
7015
7123
  const preferred = opts.goal.preferredRunTime;
@@ -7017,18 +7125,22 @@ function planGoalTargetLoopSchedule(opts) {
7017
7125
  const gate = preferredRunTimeGate(preferred, now, opts.previousScheduleState);
7018
7126
  if (!gate.ok) return targetLoopDecision("idle", gate.reason, at);
7019
7127
  }
7128
+ const dispatch2 = target.type === "goal" ? { action: "goal-manager", executable: "goal-manager", cliArgs: { goal: targetId } } : { workflow: targetId, cliArgs: {} };
7020
7129
  return {
7021
7130
  kind: "dispatch",
7022
- reason: `dispatch goal ${targetId}`,
7023
- dispatch: { action: "goal-manager", executable: "goal-manager", cliArgs: { goal: targetId } },
7131
+ reason: `dispatch ${target.type} ${targetId}`,
7132
+ dispatch: dispatch2,
7024
7133
  scheduleState: {
7025
7134
  mode: "agentLoop",
7026
7135
  lastGoalTickAt: at,
7027
7136
  lastDecision: {
7028
7137
  kind: "dispatch",
7029
- targetType: "goal",
7138
+ targetType: target.type,
7030
7139
  targetId,
7031
- executable: "goal-manager",
7140
+ ...dispatch2.action ? { action: dispatch2.action } : {},
7141
+ ...dispatch2.capability ? { capability: dispatch2.capability } : {},
7142
+ ...dispatch2.workflow ? { workflow: dispatch2.workflow } : {},
7143
+ ...dispatch2.executable ? { executable: dispatch2.executable } : {},
7032
7144
  reason: preferred ? `preferred time ${preferred.time} ${preferred.timezone}` : "ready target loop tick",
7033
7145
  at
7034
7146
  },
@@ -7238,8 +7350,8 @@ function stageManagedGoalDecision(data, goalId, goal, goalState, decision, detai
7238
7350
  status: decision.kind,
7239
7351
  dispatch: {
7240
7352
  capability: decision.capability,
7241
- executable: decision.executable,
7242
- cliArgs: decision.cliArgs
7353
+ cliArgs: decision.cliArgs,
7354
+ ...decision.executable ? { executable: decision.executable } : {}
7243
7355
  },
7244
7356
  goal: details.goalSnapshot,
7245
7357
  inspection: details.inspection,
@@ -7248,8 +7360,8 @@ function stageManagedGoalDecision(data, goalId, goal, goalState, decision, detai
7248
7360
  evidence: decision.evidence,
7249
7361
  stage: decision.stage,
7250
7362
  capability: decision.capability,
7251
- executable: decision.executable,
7252
- cliArgs: decision.cliArgs
7363
+ cliArgs: decision.cliArgs,
7364
+ ...decision.executable ? { executable: decision.executable } : {}
7253
7365
  },
7254
7366
  change: details.change
7255
7367
  });
@@ -7444,18 +7556,20 @@ var init_advanceManagedGoal = __esm({
7444
7556
  ctx.output.reason = reason;
7445
7557
  return;
7446
7558
  }
7447
- if (isGoalTargetLoop(managed)) {
7559
+ if (isGoalTargetLoop(managed) || isWorkflowTargetLoop(managed)) {
7448
7560
  const beforeSnapshot = goalRunLogSnapshot(goal.id, goal.state, managed);
7449
7561
  const previousScheduleState = goal.raw.extra.scheduleState && typeof goal.raw.extra.scheduleState === "object" ? goal.raw.extra.scheduleState : void 0;
7450
- const decision2 = planGoalTargetLoopSchedule({ goal: managed, previousScheduleState });
7562
+ const decision2 = planTargetLoopSchedule({ goal: managed, previousScheduleState });
7451
7563
  restoreGoalIdFact();
7452
7564
  goal.raw = writeManagedGoalToState({ ...goal.raw, state: goal.state }, managed);
7453
7565
  goal.raw.extra.scheduleState = decision2.scheduleState;
7454
7566
  ctx.data.managedGoalDecision = decision2;
7455
7567
  if (decision2.kind === "dispatch" && decision2.dispatch) {
7456
7568
  ctx.output.nextDispatch = {
7457
- action: decision2.dispatch.action,
7458
- executable: decision2.dispatch.executable,
7569
+ ...decision2.dispatch.action ? { action: decision2.dispatch.action } : {},
7570
+ ...decision2.dispatch.capability ? { capability: decision2.dispatch.capability } : {},
7571
+ ...decision2.dispatch.workflow ? { workflow: decision2.dispatch.workflow } : {},
7572
+ ...decision2.dispatch.executable ? { executable: decision2.dispatch.executable } : {},
7459
7573
  cliArgs: decision2.dispatch.cliArgs
7460
7574
  };
7461
7575
  }
@@ -7576,8 +7690,8 @@ var init_advanceManagedGoal = __esm({
7576
7690
  }
7577
7691
  ctx.output.nextDispatch = {
7578
7692
  capability: decision.capability,
7579
- executable: decision.executable,
7580
7693
  cliArgs: decision.cliArgs,
7694
+ ...decision.executable ? { executable: decision.executable } : {},
7581
7695
  ...decision.saveReport === true ? { saveReport: true } : {}
7582
7696
  };
7583
7697
  ctx.output.reason = `dispatch ${decision.capability} for ${decision.evidence}`;
@@ -7817,13 +7931,13 @@ function companyIntentPath(id) {
7817
7931
  assertIntentId(id);
7818
7932
  return `intents/${id}/intent.json`;
7819
7933
  }
7820
- function normalizeCompanyIntent(path48, raw) {
7934
+ function normalizeCompanyIntent(path49, raw) {
7821
7935
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7822
- throw new Error(`${path48}: intent must be JSON object`);
7936
+ throw new Error(`${path49}: intent must be JSON object`);
7823
7937
  }
7824
7938
  const input = raw;
7825
7939
  const id = stringField2(input.id);
7826
- if (!id || !isCompanyIntentId(id)) throw new Error(`${path48}: invalid intent id`);
7940
+ if (!id || !isCompanyIntentId(id)) throw new Error(`${path49}: invalid intent id`);
7827
7941
  const createdAt = stringField2(input.createdAt) || nowIso();
7828
7942
  const updatedAt = stringField2(input.updatedAt) || createdAt;
7829
7943
  return {
@@ -7862,8 +7976,8 @@ function listCompanyIntents(config, cwd) {
7862
7976
  const records = [];
7863
7977
  for (const entry of entries) {
7864
7978
  if (entry.type !== "dir" || !entry.name || !isCompanyIntentId(entry.name)) continue;
7865
- const path48 = companyIntentPath(entry.name);
7866
- const file = readStateText(config, cwd, path48);
7979
+ const path49 = companyIntentPath(entry.name);
7980
+ const file = readStateText(config, cwd, path49);
7867
7981
  if (!file) continue;
7868
7982
  records.push({
7869
7983
  id: entry.name,
@@ -7874,8 +7988,8 @@ function listCompanyIntents(config, cwd) {
7874
7988
  return records.sort((a, b) => a.intent.priority - b.intent.priority || a.id.localeCompare(b.id));
7875
7989
  }
7876
7990
  function readCompanyIntent(config, cwd, id) {
7877
- const path48 = companyIntentPath(id);
7878
- const file = readStateText(config, cwd, path48);
7991
+ const path49 = companyIntentPath(id);
7992
+ const file = readStateText(config, cwd, path49);
7879
7993
  if (!file) return null;
7880
7994
  return { id, path: file.path, intent: normalizeCompanyIntent(file.path, JSON.parse(file.content)) };
7881
7995
  }
@@ -8362,6 +8476,9 @@ function goalReportBody(goalId, state, snapshot, latestEvent, evidenceItems) {
8362
8476
  `- Missing evidence: ${listOrNone(missingEvidence)}`,
8363
8477
  `- Blockers: ${listOrNone(blockers)}`,
8364
8478
  "",
8479
+ "## Dispatch",
8480
+ ...dispatchContextMarkdown(latestEvent),
8481
+ "",
8365
8482
  "## Capability Evidence",
8366
8483
  ...capabilityEvidenceMarkdown(outputs),
8367
8484
  "",
@@ -8399,6 +8516,27 @@ function evidenceOutputMarkdown(index, output) {
8399
8516
  ""
8400
8517
  ];
8401
8518
  }
8519
+ function dispatchContextMarkdown(latestEvent) {
8520
+ const context = recordField2(latestEvent, "dispatchContext");
8521
+ if (!context) return ["- none"];
8522
+ const githubActor = stringField3(context, "githubActor");
8523
+ const githubActorRole = stringField3(context, "githubActorRole");
8524
+ const target = dispatchTargetLabel(recordField2(context, "target"));
8525
+ return [
8526
+ `- Triggered by: ${stringField3(context, "triggeredBy") ?? "unknown"}`,
8527
+ `- Mode: ${stringField3(context, "dispatchMode") ?? "unknown"}`,
8528
+ `- GitHub actor: ${githubActor ? `${githubActor}${githubActorRole ? ` (${githubActorRole})` : ""}` : "none"}`,
8529
+ `- Decided by: ${stringField3(context, "decidedBy") ?? "unknown"}`,
8530
+ `- Dispatched by: ${stringField3(context, "dispatchedBy") ?? "unknown"}`,
8531
+ `- Target: ${target ?? "none"}`
8532
+ ];
8533
+ }
8534
+ function dispatchTargetLabel(target) {
8535
+ const type = stringField3(target, "type");
8536
+ const id = stringField3(target, "id");
8537
+ if (type && id) return `${type} ${id}`;
8538
+ return id ?? type;
8539
+ }
8402
8540
  function artifactMarkdown(artifacts) {
8403
8541
  if (artifacts.length === 0) return ["- none"];
8404
8542
  return artifacts.map((artifact) => {
@@ -10445,10 +10583,9 @@ var init_dispatchClassified = __esm({
10445
10583
 
10446
10584
  // src/jobIdentity.ts
10447
10585
  function stableJobKey(job) {
10448
- const capability = job.capability ?? job.action;
10586
+ const capability = job.workflow ?? job.capability ?? job.action;
10449
10587
  const executable = job.executable ?? capability ?? "unknown";
10450
- if (job.flavor === "scheduled" && job.capability)
10451
- return `scheduled:${job.capability}:${executable}`;
10588
+ if (job.flavor === "scheduled" && job.capability) return `scheduled:${job.capability}:${executable}`;
10452
10589
  const target = typeof job.target === "number" ? job.target : targetFromCliArgs(job.cliArgs);
10453
10590
  const work = capability && executable && executable !== capability ? `${capability}:${executable}` : capability ?? executable;
10454
10591
  return target === void 0 ? `${job.flavor}:${work}` : `${job.flavor}:${work}:${target}`;
@@ -16469,12 +16606,12 @@ function writeLocalFile(cwd, relativePath, content) {
16469
16606
  function hydrateDirectory(config, cwd, stateDir, localDir) {
16470
16607
  const entries = listStateDirectory(config, cwd, stateDir);
16471
16608
  if (entries.length === 0) return;
16472
- fs42.rmSync(path40.join(cwd, localDir), { recursive: true, force: true });
16473
16609
  for (const entry of entries) {
16474
16610
  if (!entry.name || !entry.type) continue;
16475
16611
  const childState = path40.posix.join(stateDir, entry.name);
16476
16612
  const childLocal = path40.join(localDir, entry.name);
16477
16613
  if (entry.type === "dir") {
16614
+ fs42.rmSync(path40.join(cwd, childLocal), { recursive: true, force: true });
16478
16615
  hydrateDirectory(config, cwd, childState, childLocal);
16479
16616
  } else if (entry.type === "file") {
16480
16617
  const file = readStateText(config, cwd, childState);
@@ -16623,14 +16760,16 @@ function jobReferenceBlock(profileName, profile, data) {
16623
16760
  const jobId = typeof data.jobId === "string" && data.jobId.length > 0 ? data.jobId : null;
16624
16761
  const flavor = typeof data.jobFlavor === "string" && data.jobFlavor.length > 0 ? data.jobFlavor : null;
16625
16762
  const schedule = typeof data.jobSchedule === "string" && data.jobSchedule.length > 0 ? data.jobSchedule : null;
16626
- const isJob2 = Boolean(
16627
- jobId || flavor || schedule || data.jobCapability || data.jobExecutable || data.jobWhy
16628
- );
16763
+ const isJob2 = Boolean(jobId || flavor || schedule || data.jobCapability || data.jobExecutable || data.jobWhy);
16629
16764
  if (!isJob2) return null;
16630
16765
  const capability = typeof data.jobCapability === "string" && data.jobCapability.length > 0 ? data.jobCapability : profile.executable ? profile.name : null;
16631
16766
  const executable = typeof profile.executable === "string" && profile.executable.length > 0 ? profile.executable : typeof data.jobExecutable === "string" && data.jobExecutable.length > 0 ? data.jobExecutable : profileName;
16632
16767
  const agent = typeof profile.agent === "string" && profile.agent.length > 0 ? profile.agent : typeof data.jobAgent === "string" && data.jobAgent.length > 0 ? data.jobAgent : null;
16633
16768
  const description = profile.describe.trim();
16769
+ const workflow = typeof data.workflowCapability === "string" && data.workflowCapability.length > 0 ? data.workflowCapability : null;
16770
+ const workflowStep = typeof data.workflowStep === "string" && data.workflowStep.length > 0 ? data.workflowStep : null;
16771
+ const workflowStepIndex = typeof data.workflowStepIndex === "number" && Number.isFinite(data.workflowStepIndex) ? data.workflowStepIndex : null;
16772
+ const workflowStepCount = typeof data.workflowStepCount === "number" && Number.isFinite(data.workflowStepCount) ? data.workflowStepCount : null;
16634
16773
  const lines = [
16635
16774
  "## Job reference",
16636
16775
  "",
@@ -16642,7 +16781,9 @@ function jobReferenceBlock(profileName, profile, data) {
16642
16781
  `- Capability: ${capability ?? "(none)"}`,
16643
16782
  `- Executable: ${executable}`,
16644
16783
  `- Agent: ${agent ?? "(none)"}`,
16645
- `- Description: ${description || "(none)"}`
16784
+ `- Description: ${description || "(none)"}`,
16785
+ ...workflow ? [`- Workflow: ${workflow}`] : [],
16786
+ ...workflowStep ? [`- Workflow step: ${workflowStepIndex ?? "?"}/${workflowStepCount ?? "?"} ${workflowStep}`] : []
16646
16787
  ];
16647
16788
  return lines.join("\n");
16648
16789
  }
@@ -17057,7 +17198,7 @@ async function runExecutableChain(profileName, input) {
17057
17198
  };
17058
17199
  }
17059
17200
  process.stdout.write(
17060
- `\u2192 kody: in-process return \u2192 ${afterJob.action ?? afterJob.capability} (hop ${hops}/${MAX_CHAIN_HOPS})
17201
+ `\u2192 kody: in-process return \u2192 ${afterJob.action ?? afterJob.capability ?? afterJob.workflow} (hop ${hops}/${MAX_CHAIN_HOPS})
17061
17202
 
17062
17203
  `
17063
17204
  );
@@ -17091,7 +17232,7 @@ async function runExecutableChain(profileName, input) {
17091
17232
  };
17092
17233
  }
17093
17234
  process.stdout.write(
17094
- `\u2192 kody: in-process hand-off \u2192 ${nextJob.action ?? nextJob.capability} (hop ${hops}/${MAX_CHAIN_HOPS})
17235
+ `\u2192 kody: in-process hand-off \u2192 ${nextJob.action ?? nextJob.capability ?? nextJob.workflow} (hop ${hops}/${MAX_CHAIN_HOPS})
17095
17236
 
17096
17237
  `
17097
17238
  );
@@ -17109,18 +17250,19 @@ async function runExecutableChain(profileName, input) {
17109
17250
  };
17110
17251
  }
17111
17252
  if (result.nextDispatch || result.nextJob) {
17112
- const pending = result.nextDispatch?.executable ?? result.nextJob?.executable ?? result.nextJob?.capability ?? "unknown";
17253
+ const pending = result.nextDispatch?.executable ?? result.nextDispatch?.workflow ?? result.nextJob?.executable ?? result.nextJob?.workflow ?? result.nextJob?.capability ?? "unknown";
17113
17254
  process.stderr.write(`[kody] in-process hand-off cap (${MAX_CHAIN_HOPS}) reached; not running ${pending}
17114
17255
  `);
17115
17256
  }
17116
17257
  return result;
17117
17258
  }
17118
17259
  function handoffToJob(handoff) {
17119
- const dutyOrAction = handoff.action ?? handoff.capability;
17260
+ const dutyOrAction = handoff.workflow ?? handoff.action ?? handoff.capability;
17120
17261
  if (!dutyOrAction) return null;
17121
17262
  return {
17122
17263
  action: handoff.action ?? handoff.capability,
17123
17264
  capability: handoff.capability,
17265
+ workflow: handoff.workflow,
17124
17266
  executable: handoff.executable,
17125
17267
  cliArgs: handoff.cliArgs,
17126
17268
  flavor: "instant",
@@ -17372,9 +17514,9 @@ var init_executor = __esm({
17372
17514
  "src/executor.ts"() {
17373
17515
  "use strict";
17374
17516
  init_agent();
17517
+ init_agents();
17375
17518
  init_capabilityReport();
17376
17519
  init_capabilityResult();
17377
- init_agents();
17378
17520
  init_config();
17379
17521
  init_container();
17380
17522
  init_discipline();
@@ -17401,6 +17543,99 @@ var init_executor = __esm({
17401
17543
  }
17402
17544
  });
17403
17545
 
17546
+ // src/workflowDefinitions.ts
17547
+ import * as fs44 from "fs";
17548
+ import * as path42 from "path";
17549
+ function isWorkflowDefinitionId(value) {
17550
+ return WORKFLOW_ID_PATTERN.test(value);
17551
+ }
17552
+ function workflowDefinitionPath(id) {
17553
+ if (!isWorkflowDefinitionId(id)) {
17554
+ throw new Error(`Invalid workflow id "${id}"`);
17555
+ }
17556
+ return `workflows/${id}/workflow.json`;
17557
+ }
17558
+ function normalizeWorkflowDefinition(value) {
17559
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
17560
+ const raw = value;
17561
+ const name = typeof raw.name === "string" ? raw.name.trim() : "";
17562
+ const instructions = typeof raw.instructions === "string" ? raw.instructions.trim() : "";
17563
+ const capabilities = normalizeWorkflowCapabilities(raw.capabilities);
17564
+ if (!name || !instructions || capabilities.length === 0) return null;
17565
+ return {
17566
+ version: 1,
17567
+ name,
17568
+ instructions,
17569
+ capabilities,
17570
+ ...typeof raw.createdAt === "string" ? { createdAt: raw.createdAt } : {},
17571
+ ...typeof raw.updatedAt === "string" ? { updatedAt: raw.updatedAt } : {}
17572
+ };
17573
+ }
17574
+ function readWorkflowDefinition(config, cwd, id) {
17575
+ const file = readStateText(config, cwd, workflowDefinitionPath(id));
17576
+ if (!file) return readCompanyStoreWorkflowDefinition(id);
17577
+ return parseWorkflowDefinition(file.content);
17578
+ }
17579
+ function workflowDefinitionToCapabilityFolder(id, workflow, source = workflowDefinitionPath(id)) {
17580
+ return {
17581
+ slug: id,
17582
+ dir: path42.dirname(source),
17583
+ profilePath: source,
17584
+ bodyPath: source,
17585
+ title: workflow.name,
17586
+ body: workflow.instructions,
17587
+ rawBody: workflow.instructions,
17588
+ rawProfile: { name: id, workflow },
17589
+ config: {
17590
+ action: id,
17591
+ workflow: workflowDefinitionToConfig(workflow),
17592
+ describe: workflow.instructions
17593
+ }
17594
+ };
17595
+ }
17596
+ function normalizeWorkflowCapabilities(value) {
17597
+ if (!Array.isArray(value)) return [];
17598
+ const seen = /* @__PURE__ */ new Set();
17599
+ const capabilities = [];
17600
+ for (const item of value) {
17601
+ if (typeof item !== "string") continue;
17602
+ const slug2 = item.trim();
17603
+ if (!CAPABILITY_ID_PATTERN.test(slug2) || seen.has(slug2)) continue;
17604
+ seen.add(slug2);
17605
+ capabilities.push(slug2);
17606
+ }
17607
+ return capabilities;
17608
+ }
17609
+ function workflowDefinitionToConfig(workflow) {
17610
+ return {
17611
+ steps: workflow.capabilities.map((capability) => ({ capability }))
17612
+ };
17613
+ }
17614
+ function readCompanyStoreWorkflowDefinition(id) {
17615
+ const root = getCompanyStoreAssetRoot("workflows");
17616
+ if (!root) return null;
17617
+ const filePath = path42.join(root, id, "workflow.json");
17618
+ if (!fs44.existsSync(filePath)) return null;
17619
+ return parseWorkflowDefinition(fs44.readFileSync(filePath, "utf8"));
17620
+ }
17621
+ function parseWorkflowDefinition(content) {
17622
+ try {
17623
+ return normalizeWorkflowDefinition(JSON.parse(content));
17624
+ } catch {
17625
+ return null;
17626
+ }
17627
+ }
17628
+ var WORKFLOW_ID_PATTERN, CAPABILITY_ID_PATTERN;
17629
+ var init_workflowDefinitions = __esm({
17630
+ "src/workflowDefinitions.ts"() {
17631
+ "use strict";
17632
+ init_companyStore();
17633
+ init_stateRepo();
17634
+ WORKFLOW_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,79}$/;
17635
+ CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/;
17636
+ }
17637
+ });
17638
+
17404
17639
  // src/job.ts
17405
17640
  var job_exports = {};
17406
17641
  __export(job_exports, {
@@ -17413,7 +17648,7 @@ __export(job_exports, {
17413
17648
  stableJobKey: () => stableJobKey,
17414
17649
  validateJob: () => validateJob
17415
17650
  });
17416
- import * as path42 from "path";
17651
+ import * as path43 from "path";
17417
17652
  function newJobId(flavor) {
17418
17653
  localJobSeq += 1;
17419
17654
  const runId = process.env.GITHUB_RUN_ID;
@@ -17425,8 +17660,8 @@ function validateJob(input) {
17425
17660
  throw new InvalidJobError("job must be an object");
17426
17661
  }
17427
17662
  const j = input;
17428
- if (typeof j.capability !== "string" && typeof j.action !== "string") {
17429
- throw new InvalidJobError("job must reference a capability action or capability");
17663
+ if (typeof j.capability !== "string" && typeof j.action !== "string" && typeof j.workflow !== "string") {
17664
+ throw new InvalidJobError("job must reference a capability action, capability, or workflow");
17430
17665
  }
17431
17666
  if (j.flavor !== "instant" && j.flavor !== "scheduled") {
17432
17667
  throw new InvalidJobError(`job.flavor must be "instant" or "scheduled" (got ${String(j.flavor)})`);
@@ -17438,6 +17673,7 @@ function validateJob(input) {
17438
17673
  action: typeof j.action === "string" ? j.action : void 0,
17439
17674
  executable: typeof j.executable === "string" ? j.executable : void 0,
17440
17675
  capability: typeof j.capability === "string" ? j.capability : void 0,
17676
+ workflow: typeof j.workflow === "string" ? j.workflow : void 0,
17441
17677
  why: typeof j.why === "string" ? j.why : void 0,
17442
17678
  agent: typeof j.agent === "string" ? j.agent : void 0,
17443
17679
  schedule: typeof j.schedule === "string" ? j.schedule : void 0,
@@ -17451,21 +17687,49 @@ function validateJob(input) {
17451
17687
  async function runJob(job, base) {
17452
17688
  const valid = validateJob(job);
17453
17689
  const action = valid.action ?? valid.capability;
17454
- const projectCapabilitiesRoot = path42.join(base.cwd, ".kody", "capabilities");
17455
- const resolvedCapability = action ? resolveCapabilityAction(action, projectCapabilitiesRoot) : null;
17690
+ const projectCapabilitiesRoot = path43.join(base.cwd, ".kody", "capabilities");
17691
+ const resolvedCapability = !valid.workflow && action ? resolveCapabilityAction(action, projectCapabilitiesRoot) : null;
17456
17692
  const capabilityIdentity = valid.capability ?? resolvedCapability?.capability;
17457
- const capabilityContext = loadCapabilityContext(capabilityIdentity, base.cwd);
17693
+ const capabilityContext = valid.workflow ? null : loadCapabilityContext(capabilityIdentity, base.cwd);
17694
+ const workflowContext = valid.workflow ? loadWorkflowContext(valid.workflow, base) : !capabilityContext && !resolvedCapability ? loadWorkflowContext(capabilityIdentity ?? action, base) : null;
17458
17695
  const explicitExecutableOnly = valid.executable !== void 0 && (valid.action === void 0 || valid.action === valid.executable) && (valid.capability === void 0 || valid.capability === valid.executable);
17459
- if (!resolvedCapability && !capabilityContext && !explicitExecutableOnly) {
17460
- throw new InvalidJobError(`job capability not found: ${action ?? valid.capability ?? "<none>"}`);
17696
+ if (!resolvedCapability && !capabilityContext && !workflowContext && !explicitExecutableOnly) {
17697
+ throw new InvalidJobError(
17698
+ `job capability/workflow not found: ${valid.workflow ?? action ?? valid.capability ?? "<none>"}`
17699
+ );
17461
17700
  }
17701
+ const workflow = capabilityContext?.config.workflow ?? workflowContext?.config.workflow;
17702
+ const workflowIdentity = valid.workflow ?? capabilityIdentity ?? workflowContext?.slug;
17462
17703
  const capabilitySelectedExecutable = resolvedCapability?.executable ?? capabilityContext?.config.executable ?? capabilityContext?.config.executables?.[0] ?? (capabilityContext?.config.tickScript ? "capability-tick-scripted" : void 0);
17463
17704
  const profileName = valid.executable ?? capabilitySelectedExecutable;
17464
- if (!profileName) {
17465
- throw new InvalidJobError(
17466
- `job capability resolves to no executable: ${capabilityIdentity ?? action}`
17467
- );
17705
+ if (workflow && shouldRunCapabilityWorkflow(valid, workflow, workflowIdentity, capabilitySelectedExecutable, base)) {
17706
+ const workflowCapability = capabilityContext ?? workflowContext;
17707
+ const workflowJob = workflowContext && !valid.why ? { ...valid, why: workflowContext.body } : valid;
17708
+ return runCapabilityWorkflow(workflowJob, workflow, workflowCapability, base);
17468
17709
  }
17710
+ if (!profileName) {
17711
+ throw new InvalidJobError(`job capability resolves to no executable: ${capabilityIdentity ?? action}`);
17712
+ }
17713
+ return runDefaultCapabilityWorkflow(
17714
+ valid,
17715
+ profileName,
17716
+ capabilityIdentity,
17717
+ capabilityContext,
17718
+ resolvedCapability,
17719
+ base
17720
+ );
17721
+ }
17722
+ async function runDefaultCapabilityWorkflow(job, profileName, capabilityIdentity, capabilityContext, resolvedCapability, base) {
17723
+ return runCapabilityImplementationStep(
17724
+ job,
17725
+ profileName,
17726
+ capabilityIdentity,
17727
+ capabilityContext,
17728
+ resolvedCapability,
17729
+ base
17730
+ );
17731
+ }
17732
+ async function runCapabilityImplementationStep(valid, profileName, capabilityIdentity, capabilityContext, resolvedCapability, base) {
17469
17733
  const preloadedData = { ...base.preloadedData ?? {} };
17470
17734
  preloadedData.jobId = newJobId(valid.flavor);
17471
17735
  preloadedData.jobKey = stableJobKey(valid);
@@ -17474,9 +17738,7 @@ async function runJob(job, base) {
17474
17738
  if (valid.action !== void 0 && valid.action.length > 0) preloadedData.jobAction = valid.action;
17475
17739
  if (capabilityIdentity !== void 0 && capabilityIdentity.length > 0)
17476
17740
  preloadedData.jobCapability = capabilityIdentity;
17477
- const executableIdentity = profileName;
17478
- if (executableIdentity !== void 0 && executableIdentity.length > 0)
17479
- preloadedData.jobExecutable = executableIdentity;
17741
+ preloadedData.jobExecutable = profileName;
17480
17742
  if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
17481
17743
  if (valid.saveReport === true) preloadedData.jobSaveReport = true;
17482
17744
  if (capabilityContext) {
@@ -17484,8 +17746,7 @@ async function runJob(job, base) {
17484
17746
  preloadedData.capabilityTitle = capabilityContext.title;
17485
17747
  preloadedData.dutyIntent = capabilityContext.body;
17486
17748
  preloadedData.jobIntent = capabilityContext.body;
17487
- if (preloadedData.jobCapability === void 0)
17488
- preloadedData.jobCapability = capabilityContext.slug;
17749
+ if (preloadedData.jobCapability === void 0) preloadedData.jobCapability = capabilityContext.slug;
17489
17750
  if (capabilityContext.config.agent && preloadedData.jobAgent === void 0) {
17490
17751
  preloadedData.jobAgent = capabilityContext.config.agent;
17491
17752
  }
@@ -17509,9 +17770,182 @@ async function runJob(job, base) {
17509
17770
  const run = base.chain === false ? runExecutable : runExecutableChain;
17510
17771
  return run(profileName, input);
17511
17772
  }
17773
+ function shouldRunCapabilityWorkflow(job, workflow, capabilityIdentity, selectedExecutable, base) {
17774
+ if (workflow.steps.length === 0) return false;
17775
+ if (!capabilityIdentity) return false;
17776
+ const stack = Array.isArray(base.preloadedData?.workflowStack) ? base.preloadedData.workflowStack.filter((entry) => typeof entry === "string") : [];
17777
+ if (stack.includes(capabilityIdentity)) return false;
17778
+ if (!job.executable) return true;
17779
+ return job.executable === selectedExecutable || job.executable === capabilityIdentity || job.executable === job.action;
17780
+ }
17781
+ async function runCapabilityWorkflow(parent, workflow, capability, base) {
17782
+ let chainData = {
17783
+ ...base.preloadedData ?? {},
17784
+ workflowCapability: capability.slug,
17785
+ workflowTitle: capability.title,
17786
+ workflowStepCount: workflow.steps.length,
17787
+ workflowIssueNumber: workflowIssueNumber(parent),
17788
+ workflowStack: [
17789
+ ...Array.isArray(base.preloadedData?.workflowStack) ? base.preloadedData.workflowStack.filter((entry) => typeof entry === "string") : [],
17790
+ capability.slug
17791
+ ]
17792
+ };
17793
+ let result = { exitCode: 0 };
17794
+ for (let index = 0; index < workflow.steps.length; index++) {
17795
+ const step = workflow.steps[index];
17796
+ const label = step.action ?? step.capability;
17797
+ if (!shouldRunWorkflowStep(step, chainData)) {
17798
+ process.stdout.write(
17799
+ `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label} (skipped)
17800
+
17801
+ `
17802
+ );
17803
+ continue;
17804
+ }
17805
+ const child = workflowStepToJob(step, parent, chainData);
17806
+ process.stdout.write(
17807
+ `\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
17808
+
17809
+ `
17810
+ );
17811
+ result = await runJob(child, {
17812
+ ...base,
17813
+ preloadedData: {
17814
+ ...chainData,
17815
+ workflowStep: label,
17816
+ workflowStepIndex: index + 1,
17817
+ workflowStepReason: step.reason
17818
+ }
17819
+ });
17820
+ const outcome = workflowOutcome(result);
17821
+ const prUrl = result.taskState?.core.prUrl ?? (typeof chainData.workflowPrUrl === "string" ? chainData.workflowPrUrl : void 0);
17822
+ chainData = {
17823
+ ...chainData,
17824
+ ...result.taskState ? { taskState: result.taskState } : {},
17825
+ ...outcome ? { workflowLastOutcome: outcome } : {},
17826
+ ...prUrl ? { workflowPrUrl: prUrl } : {},
17827
+ ...parsePrNumber5(prUrl) ? { workflowPrNumber: parsePrNumber5(prUrl) } : {}
17828
+ };
17829
+ if (result.exitCode !== 0 && !canContinueWorkflow(step, outcome)) {
17830
+ return {
17831
+ ...result,
17832
+ reason: result.reason ?? `workflow ${capability.slug} stopped at step ${index + 1}/${workflow.steps.length}: ${label}`
17833
+ };
17834
+ }
17835
+ }
17836
+ return result;
17837
+ }
17838
+ function workflowStepToJob(step, parent, chainData) {
17839
+ const action = step.action ?? step.capability;
17840
+ const rawArgs = {
17841
+ ...parent.cliArgs,
17842
+ ...step.cliArgs ?? {}
17843
+ };
17844
+ const targetNumber = workflowStepTargetNumber(step, parent, chainData);
17845
+ if (step.target === "pr") {
17846
+ if (typeof targetNumber !== "number") {
17847
+ throw new InvalidJobError(`workflow step ${action} needs a PR target but no prior PR URL is available`);
17848
+ }
17849
+ rawArgs.pr = targetNumber;
17850
+ } else if (step.target === "issue" && typeof targetNumber === "number") {
17851
+ rawArgs.issue = targetNumber;
17852
+ }
17853
+ const cliArgs = filterCliArgsForStep(action, rawArgs);
17854
+ const target = typeof targetNumber === "number" ? targetNumber : typeof parent.target === "number" ? parent.target : targetFromCliArgs(cliArgs);
17855
+ return {
17856
+ action,
17857
+ capability: step.capability,
17858
+ ...step.executable ? { executable: step.executable } : {},
17859
+ ...composeStepWhy(parent.why, step) ? { why: composeStepWhy(parent.why, step) } : {},
17860
+ ...step.agent ?? parent.agent ? { agent: step.agent ?? parent.agent } : {},
17861
+ ...parent.schedule ? { schedule: parent.schedule } : {},
17862
+ ...typeof target === "number" ? { target } : {},
17863
+ cliArgs,
17864
+ flavor: parent.flavor,
17865
+ force: parent.force,
17866
+ saveReport: step.saveReport === true || parent.saveReport === true
17867
+ };
17868
+ }
17869
+ function shouldRunWorkflowStep(step, data) {
17870
+ if (!step.runWhen) return true;
17871
+ const context = workflowConditionContext(data);
17872
+ return Object.entries(step.runWhen).every(
17873
+ ([path49, expected]) => valueMatches(resolveDottedPath2(context, path49), expected)
17874
+ );
17875
+ }
17876
+ function canContinueWorkflow(step, outcome) {
17877
+ if (!outcome || !step.continueOn || step.continueOn.length === 0) return false;
17878
+ return step.continueOn.includes(outcome.type);
17879
+ }
17880
+ function workflowOutcome(result) {
17881
+ return result.taskState?.core.lastOutcome ?? null;
17882
+ }
17883
+ function workflowConditionContext(data) {
17884
+ const lastOutcome = data.workflowLastOutcome;
17885
+ return {
17886
+ ...data,
17887
+ workflow: {
17888
+ lastOutcome,
17889
+ issueNumber: data.workflowIssueNumber,
17890
+ prNumber: data.workflowPrNumber,
17891
+ prUrl: data.workflowPrUrl
17892
+ },
17893
+ lastOutcome
17894
+ };
17895
+ }
17896
+ function resolveDottedPath2(root, dotted) {
17897
+ return dotted.split(".").reduce((cur, part) => {
17898
+ if (!cur || typeof cur !== "object") return void 0;
17899
+ return cur[part];
17900
+ }, root);
17901
+ }
17902
+ function valueMatches(actual, expected) {
17903
+ if (Array.isArray(expected)) return expected.some((entry) => valueMatches(actual, entry));
17904
+ return actual === expected;
17905
+ }
17906
+ function workflowStepTargetNumber(step, parent, chainData) {
17907
+ if (step.target === "pr") return workflowPrNumber(chainData) ?? targetFromCliArgs(step.cliArgs ?? {});
17908
+ if (step.target === "issue") return workflowIssueNumber(parent);
17909
+ return typeof parent.target === "number" ? parent.target : targetFromCliArgs({ ...parent.cliArgs, ...step.cliArgs ?? {} });
17910
+ }
17911
+ function workflowIssueNumber(parent) {
17912
+ return typeof parent.target === "number" ? parent.target : targetFromCliArgs(parent.cliArgs);
17913
+ }
17914
+ function workflowPrNumber(data) {
17915
+ if (typeof data.workflowPrNumber === "number" && Number.isFinite(data.workflowPrNumber)) return data.workflowPrNumber;
17916
+ const prUrl = typeof data.workflowPrUrl === "string" ? data.workflowPrUrl : typeof data.taskState?.core?.prUrl === "string" ? data.taskState.core.prUrl : void 0;
17917
+ return parsePrNumber5(prUrl) ?? void 0;
17918
+ }
17919
+ function parsePrNumber5(url) {
17920
+ if (!url) return null;
17921
+ const m = url.match(/\/pull\/(\d+)(?:[/?#]|$)/);
17922
+ if (!m) return null;
17923
+ const n = parseInt(m[1], 10);
17924
+ return Number.isFinite(n) ? n : null;
17925
+ }
17926
+ function filterCliArgsForStep(action, raw) {
17927
+ const inputs = getCapabilityActionInputs(action);
17928
+ if (!inputs) return raw;
17929
+ const allowed = /* @__PURE__ */ new Set(["_", "cwd", "verbose", "quiet"]);
17930
+ for (const input of inputs) {
17931
+ const flagKey = input.flag.replace(/^--/, "");
17932
+ allowed.add(input.name);
17933
+ allowed.add(flagKey);
17934
+ if (flagKey.includes("-")) allowed.add(flagKey.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase()));
17935
+ }
17936
+ return Object.fromEntries(Object.entries(raw).filter(([key]) => allowed.has(key)));
17937
+ }
17938
+ function composeStepWhy(parentWhy, step) {
17939
+ return [parentWhy?.trim(), step.reason ? `Workflow step: ${step.reason}` : ""].filter((part) => Boolean(part)).join("\n\n");
17940
+ }
17512
17941
  function loadCapabilityContext(slug2, cwd) {
17513
17942
  if (!slug2) return null;
17514
- return resolveCapabilityFolder(slug2, path42.join(cwd, ".kody", "capabilities"));
17943
+ return resolveCapabilityFolder(slug2, path43.join(cwd, ".kody", "capabilities"));
17944
+ }
17945
+ function loadWorkflowContext(slug2, base) {
17946
+ if (!slug2 || !base.config || !isWorkflowDefinitionId(slug2)) return null;
17947
+ const workflow = readWorkflowDefinition(base.config, base.cwd, slug2);
17948
+ return workflow ? workflowDefinitionToCapabilityFolder(slug2, workflow) : null;
17515
17949
  }
17516
17950
  function mintInstantJob(dispatch2, opts) {
17517
17951
  return {
@@ -17543,6 +17977,7 @@ var init_job = __esm({
17543
17977
  "use strict";
17544
17978
  init_executor();
17545
17979
  init_registry();
17980
+ init_workflowDefinitions();
17546
17981
  init_jobIdentity();
17547
17982
  init_jobIdentity();
17548
17983
  DEFAULT_INSTANT_AGENT = "kody";
@@ -17663,9 +18098,9 @@ function translateOpenAISseToBrain(opts) {
17663
18098
  }
17664
18099
 
17665
18100
  // src/servers/brain-serve.ts
17666
- import * as fs46 from "fs";
18101
+ import * as fs47 from "fs";
17667
18102
  import { createServer } from "http";
17668
- import * as path45 from "path";
18103
+ import * as path46 from "path";
17669
18104
 
17670
18105
  // src/chat/loop.ts
17671
18106
  init_agent();
@@ -18225,8 +18660,8 @@ init_config();
18225
18660
 
18226
18661
  // src/kody-cli.ts
18227
18662
  import { execFileSync as execFileSync25 } from "child_process";
18228
- import * as fs44 from "fs";
18229
- import * as path43 from "path";
18663
+ import * as fs45 from "fs";
18664
+ import * as path44 from "path";
18230
18665
 
18231
18666
  // src/app-auth.ts
18232
18667
  import { createSign } from "crypto";
@@ -18847,9 +19282,9 @@ async function resolveAuthToken(env = process.env) {
18847
19282
  return void 0;
18848
19283
  }
18849
19284
  function detectPackageManager2(cwd) {
18850
- if (fs44.existsSync(path43.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
18851
- if (fs44.existsSync(path43.join(cwd, "yarn.lock"))) return "yarn";
18852
- if (fs44.existsSync(path43.join(cwd, "bun.lockb"))) return "bun";
19285
+ if (fs45.existsSync(path44.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
19286
+ if (fs45.existsSync(path44.join(cwd, "yarn.lock"))) return "yarn";
19287
+ if (fs45.existsSync(path44.join(cwd, "bun.lockb"))) return "bun";
18853
19288
  return "npm";
18854
19289
  }
18855
19290
  function shouldChainScheduledWatch(match) {
@@ -18942,8 +19377,8 @@ function postFailureTail(issueNumber, cwd, reason) {
18942
19377
  const logPath = lastRunLogPath(cwd);
18943
19378
  let tail = "";
18944
19379
  try {
18945
- if (fs44.existsSync(logPath)) {
18946
- const content = fs44.readFileSync(logPath, "utf-8");
19380
+ if (fs45.existsSync(logPath)) {
19381
+ const content = fs45.readFileSync(logPath, "utf-8");
18947
19382
  tail = content.slice(-3e3);
18948
19383
  }
18949
19384
  } catch {
@@ -18968,7 +19403,7 @@ async function runCi(argv) {
18968
19403
  return 0;
18969
19404
  }
18970
19405
  const args = parseCiArgs(argv);
18971
- const cwd = args.cwd ? path43.resolve(args.cwd) : process.cwd();
19406
+ const cwd = args.cwd ? path44.resolve(args.cwd) : process.cwd();
18972
19407
  let earlyConfig;
18973
19408
  let earlyConfigError;
18974
19409
  try {
@@ -18983,9 +19418,9 @@ async function runCi(argv) {
18983
19418
  let manualWorkflowDispatch = false;
18984
19419
  let forceRunAction = null;
18985
19420
  let forceRunCliArgs = {};
18986
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs44.existsSync(dispatchEventPath)) {
19421
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs45.existsSync(dispatchEventPath)) {
18987
19422
  try {
18988
- const evt = JSON.parse(fs44.readFileSync(dispatchEventPath, "utf-8"));
19423
+ const evt = JSON.parse(fs45.readFileSync(dispatchEventPath, "utf-8"));
18989
19424
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
18990
19425
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
18991
19426
  const dutyInput = String(evt?.inputs?.capability ?? evt?.inputs?.executable ?? "").trim();
@@ -19317,8 +19752,8 @@ init_repoWorkspace();
19317
19752
 
19318
19753
  // src/scripts/brainTurnLog.ts
19319
19754
  init_runtimePaths();
19320
- import * as fs45 from "fs";
19321
- import * as path44 from "path";
19755
+ import * as fs46 from "fs";
19756
+ import * as path45 from "path";
19322
19757
  import posixPath4 from "path/posix";
19323
19758
  var live = /* @__PURE__ */ new Map();
19324
19759
  function brainEventsFilePath(dir, chatId) {
@@ -19329,8 +19764,8 @@ function brainEventsStatePath(chatId) {
19329
19764
  }
19330
19765
  function lastPersistedSeq(dir, chatId) {
19331
19766
  const p = brainEventsFilePath(dir, chatId);
19332
- if (!fs45.existsSync(p)) return 0;
19333
- const lines = fs45.readFileSync(p, "utf-8").split("\n").filter(Boolean);
19767
+ if (!fs46.existsSync(p)) return 0;
19768
+ const lines = fs46.readFileSync(p, "utf-8").split("\n").filter(Boolean);
19334
19769
  if (lines.length === 0) return 0;
19335
19770
  try {
19336
19771
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -19340,9 +19775,9 @@ function lastPersistedSeq(dir, chatId) {
19340
19775
  }
19341
19776
  function readSince(dir, chatId, since) {
19342
19777
  const p = brainEventsFilePath(dir, chatId);
19343
- if (!fs45.existsSync(p)) return [];
19778
+ if (!fs46.existsSync(p)) return [];
19344
19779
  const out = [];
19345
- for (const line of fs45.readFileSync(p, "utf-8").split("\n")) {
19780
+ for (const line of fs46.readFileSync(p, "utf-8").split("\n")) {
19346
19781
  if (!line) continue;
19347
19782
  try {
19348
19783
  const rec = JSON.parse(line);
@@ -19368,12 +19803,12 @@ function beginTurn(dir, chatId) {
19368
19803
  };
19369
19804
  live.set(chatId, state);
19370
19805
  const p = brainEventsFilePath(dir, chatId);
19371
- fs45.mkdirSync(path44.dirname(p), { recursive: true });
19806
+ fs46.mkdirSync(path45.dirname(p), { recursive: true });
19372
19807
  return (event) => {
19373
19808
  state.seq += 1;
19374
19809
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
19375
19810
  try {
19376
- fs45.appendFileSync(p, `${JSON.stringify(rec)}
19811
+ fs46.appendFileSync(p, `${JSON.stringify(rec)}
19377
19812
  `);
19378
19813
  } catch (err) {
19379
19814
  process.stderr.write(
@@ -19412,7 +19847,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
19412
19847
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
19413
19848
  };
19414
19849
  try {
19415
- fs45.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
19850
+ fs46.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
19416
19851
  `);
19417
19852
  } catch {
19418
19853
  }
@@ -19718,7 +20153,7 @@ async function handleChatTurn(req, res, chatId, opts) {
19718
20153
  );
19719
20154
  }
19720
20155
  }
19721
- fs46.mkdirSync(path45.dirname(sessionFile), { recursive: true });
20156
+ fs47.mkdirSync(path46.dirname(sessionFile), { recursive: true });
19722
20157
  appendTurn(sessionFile, {
19723
20158
  role: "user",
19724
20159
  content: message,
@@ -19783,7 +20218,7 @@ async function handleChatTurn(req, res, chatId, opts) {
19783
20218
  function buildServer(opts) {
19784
20219
  const runTurn = opts.runTurn ?? runChatTurn;
19785
20220
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
19786
- const reposRoot = opts.reposRoot ?? path45.join(path45.dirname(path45.resolve(opts.cwd)), "repos");
20221
+ const reposRoot = opts.reposRoot ?? path46.join(path46.dirname(path46.resolve(opts.cwd)), "repos");
19787
20222
  return createServer(async (req, res) => {
19788
20223
  if (!req.method || !req.url) {
19789
20224
  sendJson(res, 400, { error: "bad request" });
@@ -20378,8 +20813,8 @@ async function loadConfigSafe() {
20378
20813
  }
20379
20814
 
20380
20815
  // src/chat-cli.ts
20381
- import * as fs48 from "fs";
20382
- import * as path47 from "path";
20816
+ import * as fs49 from "fs";
20817
+ import * as path48 from "path";
20383
20818
 
20384
20819
  // src/chat/inbox.ts
20385
20820
  import { execFileSync as execFileSync26 } from "child_process";
@@ -20451,8 +20886,8 @@ function currentBranch(cwd) {
20451
20886
 
20452
20887
  // src/chat/state-sync.ts
20453
20888
  init_stateRepo();
20454
- import * as fs47 from "fs";
20455
- import * as path46 from "path";
20889
+ import * as fs48 from "fs";
20890
+ import * as path47 from "path";
20456
20891
  function jsonlLines2(text) {
20457
20892
  return text.split("\n").filter((line) => line.length > 0);
20458
20893
  }
@@ -20469,15 +20904,15 @@ function mergeJsonl2(localText, remoteText) {
20469
20904
  function syncJsonlFileFromState(opts) {
20470
20905
  const remote = readStateText(opts.config, opts.cwd, opts.statePath);
20471
20906
  if (!remote) return;
20472
- const local = fs47.existsSync(opts.localPath) ? fs47.readFileSync(opts.localPath, "utf-8") : "";
20907
+ const local = fs48.existsSync(opts.localPath) ? fs48.readFileSync(opts.localPath, "utf-8") : "";
20473
20908
  const next = mergeJsonl2(local, remote.content);
20474
20909
  if (next === local) return;
20475
- fs47.mkdirSync(path46.dirname(opts.localPath), { recursive: true });
20476
- fs47.writeFileSync(opts.localPath, next);
20910
+ fs48.mkdirSync(path47.dirname(opts.localPath), { recursive: true });
20911
+ fs48.writeFileSync(opts.localPath, next);
20477
20912
  }
20478
20913
  function persistJsonlFileToState(opts) {
20479
- if (!fs47.existsSync(opts.localPath)) return;
20480
- const localText = fs47.readFileSync(opts.localPath, "utf-8");
20914
+ if (!fs48.existsSync(opts.localPath)) return;
20915
+ const localText = fs48.readFileSync(opts.localPath, "utf-8");
20481
20916
  for (let attempt = 1; attempt <= 3; attempt += 1) {
20482
20917
  const remote = readStateText(opts.config, opts.cwd, opts.statePath);
20483
20918
  const body = mergeJsonl2(localText, remote?.content ?? "");
@@ -20737,7 +21172,7 @@ async function runChat(argv) {
20737
21172
  ${CHAT_HELP}`);
20738
21173
  return 64;
20739
21174
  }
20740
- const cwd = args.cwd ? path47.resolve(args.cwd) : process.cwd();
21175
+ const cwd = args.cwd ? path48.resolve(args.cwd) : process.cwd();
20741
21176
  const sessionId = args.sessionId;
20742
21177
  const unpackedSecrets = unpackAllSecrets();
20743
21178
  if (unpackedSecrets > 0) {
@@ -20798,7 +21233,7 @@ ${CHAT_HELP}`);
20798
21233
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
20799
21234
  const meta = readMeta(sessionFile);
20800
21235
  process.stdout.write(
20801
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs48.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
21236
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs49.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
20802
21237
  `
20803
21238
  );
20804
21239
  try {
@@ -20958,8 +21393,8 @@ var FlyClient = class {
20958
21393
  get fetch() {
20959
21394
  return this.opts.fetchImpl ?? fetch;
20960
21395
  }
20961
- async call(path48, init = {}) {
20962
- const res = await this.fetch(`${FLY_API_BASE}${path48}`, {
21396
+ async call(path49, init = {}) {
21397
+ const res = await this.fetch(`${FLY_API_BASE}${path49}`, {
20963
21398
  method: init.method ?? "GET",
20964
21399
  headers: {
20965
21400
  Authorization: `Bearer ${this.opts.token}`,
@@ -20970,7 +21405,7 @@ var FlyClient = class {
20970
21405
  if (res.status === 404 && init.allow404) return null;
20971
21406
  if (!res.ok) {
20972
21407
  const text = await res.text().catch(() => "");
20973
- throw new Error(`Fly API ${res.status} on ${path48}: ${text.slice(0, 200) || res.statusText}`);
21408
+ throw new Error(`Fly API ${res.status} on ${path49}: ${text.slice(0, 200) || res.statusText}`);
20974
21409
  }
20975
21410
  if (res.status === 204) return null;
20976
21411
  const raw = await res.text();
@@ -21685,7 +22120,7 @@ async function poolServe() {
21685
22120
 
21686
22121
  // src/servers/runner-serve.ts
21687
22122
  import { spawn as spawn8 } from "child_process";
21688
- import * as fs49 from "fs";
22123
+ import * as fs50 from "fs";
21689
22124
  import { createServer as createServer5 } from "http";
21690
22125
  var DEFAULT_PORT2 = 8080;
21691
22126
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -21765,8 +22200,8 @@ async function defaultRunJob(job) {
21765
22200
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
21766
22201
  const branch = job.ref ?? "main";
21767
22202
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
21768
- fs49.rmSync(workdir, { recursive: true, force: true });
21769
- fs49.mkdirSync(workdir, { recursive: true });
22203
+ fs50.rmSync(workdir, { recursive: true, force: true });
22204
+ fs50.mkdirSync(workdir, { recursive: true });
21770
22205
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
21771
22206
  const interactive = job.mode === "interactive";
21772
22207
  const scheduled = job.mode === "scheduled";