@kody-ade/kody-engine 0.4.263 → 0.4.265

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.263",
18
+ version: "0.4.265",
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",
@@ -32,7 +32,8 @@ var init_package = __esm({
32
32
  serve: "tsx bin/kody.ts serve",
33
33
  "serve:vscode": "tsx bin/kody.ts serve vscode",
34
34
  "serve:claude": "tsx bin/kody.ts serve claude",
35
- build: "tsup && node scripts/copy-assets.cjs",
35
+ "clean:dist": "node scripts/clean-dist.cjs",
36
+ build: "pnpm clean:dist && tsup && node scripts/copy-assets.cjs",
36
37
  "check:modularity": "tsx scripts/check-script-modularity.ts",
37
38
  pretest: "pnpm check:modularity",
38
39
  test: "vitest run tests/unit tests/int --coverage",
@@ -762,6 +763,8 @@ function parseReleaseConfig(raw) {
762
763
  if (typeof r.publishCommand === "string") out.publishCommand = r.publishCommand;
763
764
  if (typeof r.notifyCommand === "string") out.notifyCommand = r.notifyCommand;
764
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;
765
768
  if (typeof r.draftRelease === "boolean") out.draftRelease = r.draftRelease;
766
769
  if (typeof r.releaseBranch === "string") out.releaseBranch = r.releaseBranch;
767
770
  if (typeof r.timeoutMs === "number" && r.timeoutMs > 0) out.timeoutMs = Math.floor(r.timeoutMs);
@@ -1406,12 +1409,12 @@ function parseCapabilityConfig(raw) {
1406
1409
  capabilityTools: tools,
1407
1410
  implementations,
1408
1411
  executables: stringList(raw.executables),
1409
- capabilityKind: capabilityKindField(raw.capabilityKind),
1410
1412
  role: stringField(raw.role),
1411
1413
  describe: stringField(raw.describe),
1412
1414
  stage: stringField(raw.stage),
1413
1415
  readsFrom: stringList(raw.readsFrom ?? raw.reads_from),
1414
- writesTo: stringList(raw.writesTo ?? raw.writes_to)
1416
+ writesTo: stringList(raw.writesTo ?? raw.writes_to),
1417
+ workflow: parseWorkflow(raw.workflow)
1415
1418
  };
1416
1419
  }
1417
1420
  function parseCapabilityBody(raw, slug2) {
@@ -1447,9 +1450,44 @@ function stringList(value) {
1447
1450
  }
1448
1451
  return [];
1449
1452
  }
1450
- function capabilityKindField(value) {
1451
- if (value === "observe" || value === "act" || value === "verify") return value;
1452
- 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("..");
1453
1491
  }
1454
1492
  var CAPABILITY_PROFILE_FILE, CAPABILITY_BODY_FILE;
1455
1493
  var init_capabilityFolders = __esm({
@@ -1482,7 +1520,8 @@ function getCompanyStoreAssetRoot(kind) {
1482
1520
  capabilities: "capabilities",
1483
1521
  executables: "executables",
1484
1522
  goals: "goals",
1485
- agents: "agents"
1523
+ agents: "agents",
1524
+ workflows: "workflows"
1486
1525
  };
1487
1526
  return path7.join(root, ".kody", folderByKind[kind]);
1488
1527
  }
@@ -1677,13 +1716,13 @@ function listCapabilityActions(projectCapabilitiesRoot = getProjectCapabilitiesR
1677
1716
  const projectExecutableRoots = [getProjectExecutablesRoot()];
1678
1717
  const storeExecutableRoot = getCompanyStoreExecutablesRoot();
1679
1718
  const storeExecutableRoots = storeExecutableRoot ? [storeExecutableRoot] : [];
1680
- for (const action of listFolderCapabilityActions(projectCapabilitiesRoot, "project-folder", true))
1719
+ for (const action of listFolderCapabilityActions(projectCapabilitiesRoot, "project-folder"))
1681
1720
  add(action);
1682
1721
  for (const root of projectExecutableRoots) {
1683
1722
  for (const action of listExecutableCapabilityActions(root, "project-executable")) add(action);
1684
1723
  }
1685
1724
  if (storeCapabilitiesRoot) {
1686
- for (const action of listFolderCapabilityActions(storeCapabilitiesRoot, "company-store", true)) add(action);
1725
+ for (const action of listFolderCapabilityActions(storeCapabilitiesRoot, "company-store")) add(action);
1687
1726
  }
1688
1727
  for (const root of storeExecutableRoots) {
1689
1728
  for (const action of listExecutableCapabilityActions(root, "company-store-executable")) add(action);
@@ -1706,7 +1745,16 @@ function resolveCapabilityFolder(slug2, projectCapabilitiesRoot = getProjectCapa
1706
1745
  }
1707
1746
  return null;
1708
1747
  }
1748
+ function getCapabilityActionInputs(action) {
1749
+ const resolved = resolveCapabilityAction(action);
1750
+ if (!resolved) return null;
1751
+ return getProfileInputs(resolved.executable);
1752
+ }
1709
1753
  function resolveCapabilityExecution(capability) {
1754
+ const firstWorkflowStep = capability.config.workflow?.steps[0];
1755
+ if (firstWorkflowStep) {
1756
+ return { executable: firstWorkflowStep.executable ?? firstWorkflowStep.capability, cliArgs: {} };
1757
+ }
1710
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");
1711
1759
  const cliArgs = executableDeclaresInput(executable, "capability") ? { capability: capability.slug } : {};
1712
1760
  return { executable, cliArgs };
@@ -1754,7 +1802,7 @@ function listExecutableCapabilityActions(root, source) {
1754
1802
  const action = typeof raw.action === "string" && raw.action.trim() ? raw.action.trim() : "";
1755
1803
  if (!action) continue;
1756
1804
  if (!PUBLIC_EXECUTABLE_ROLES.has(String(raw.role))) continue;
1757
- if (!PUBLIC_EXECUTABLE_CAPABILITY_KINDS.has(String(raw.capabilityKind))) continue;
1805
+ if (typeof raw.kind !== "string" || !raw.kind.trim()) continue;
1758
1806
  if (!Array.isArray(raw.inputs)) continue;
1759
1807
  out.push({
1760
1808
  action,
@@ -1770,14 +1818,13 @@ function listExecutableCapabilityActions(root, source) {
1770
1818
  }
1771
1819
  return out.sort((a, b) => a.action.localeCompare(b.action));
1772
1820
  }
1773
- function listFolderCapabilityActions(root, source, requireCapabilityKind = false) {
1821
+ function listFolderCapabilityActions(root, source) {
1774
1822
  if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
1775
1823
  const out = [];
1776
1824
  for (const slug2 of listCapabilityFolderSlugs(root)) {
1777
1825
  if (!isSafeName(slug2)) continue;
1778
1826
  const capability = readCapabilityFolder(root, slug2);
1779
1827
  if (!capability) continue;
1780
- if (requireCapabilityKind && !capability.config.capabilityKind) continue;
1781
1828
  const action = capability.config.action ?? slug2;
1782
1829
  const { executable, cliArgs } = resolveCapabilityExecution(capability);
1783
1830
  out.push({
@@ -1787,7 +1834,6 @@ function listFolderCapabilityActions(root, source, requireCapabilityKind = false
1787
1834
  cliArgs,
1788
1835
  source,
1789
1836
  describe: capability.config.describe ?? capability.title,
1790
- capabilityKind: capability.config.capabilityKind,
1791
1837
  profilePath: capability.profilePath,
1792
1838
  bodyPath: capability.bodyPath
1793
1839
  });
@@ -1810,7 +1856,6 @@ function listBuiltinCapabilityActions(root = getBuiltinCapabilitiesRoot()) {
1810
1856
  cliArgs: {},
1811
1857
  source: "builtin",
1812
1858
  describe: capability.config.describe ?? capability.title,
1813
- capabilityKind: capability.config.capabilityKind,
1814
1859
  profilePath: capability.profilePath,
1815
1860
  bodyPath: capability.bodyPath
1816
1861
  });
@@ -1853,14 +1898,13 @@ function parseGenericFlags(argv) {
1853
1898
  if (positional.length > 0) args._ = positional;
1854
1899
  return args;
1855
1900
  }
1856
- var PUBLIC_EXECUTABLE_ROLES, PUBLIC_EXECUTABLE_CAPABILITY_KINDS;
1901
+ var PUBLIC_EXECUTABLE_ROLES;
1857
1902
  var init_registry = __esm({
1858
1903
  "src/registry.ts"() {
1859
1904
  "use strict";
1860
1905
  init_capabilityFolders();
1861
1906
  init_companyStore();
1862
1907
  PUBLIC_EXECUTABLE_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
1863
- PUBLIC_EXECUTABLE_CAPABILITY_KINDS = /* @__PURE__ */ new Set(["observe", "act", "verify"]);
1864
1908
  }
1865
1909
  });
1866
1910
 
@@ -3089,6 +3133,60 @@ var init_gha = __esm({
3089
3133
  }
3090
3134
  });
3091
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
+
3092
3190
  // src/capabilityReport.ts
3093
3191
  function parseCapabilityReportsFromText(text) {
3094
3192
  const reports = [];
@@ -3254,60 +3352,6 @@ var init_capabilityResult = __esm({
3254
3352
  }
3255
3353
  });
3256
3354
 
3257
- // src/agents.ts
3258
- import * as fs16 from "fs";
3259
- import * as path16 from "path";
3260
- function stripFrontmatter(raw) {
3261
- const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
3262
- return (match ? match[1] : raw).trim();
3263
- }
3264
- function loadAgentIdentity(cwd, slug2, agentsDir = DEFAULT_AGENT_DIR) {
3265
- const trimmed = slug2.trim();
3266
- if (!trimmed) throw new Error("loadAgentIdentity: empty agent slug");
3267
- const agentPath = resolveAgentFile(cwd, trimmed, agentsDir);
3268
- if (fs16.existsSync(agentPath)) {
3269
- const body = stripFrontmatter(fs16.readFileSync(agentPath, "utf-8"));
3270
- if (body) return body;
3271
- const builtinForEmpty = BUILTIN_AGENTS[trimmed];
3272
- if (builtinForEmpty) return builtinForEmpty;
3273
- throw new Error(`loadAgentIdentity: agent '${trimmed}' agent identity body is empty (${agentPath})`);
3274
- }
3275
- const builtin = BUILTIN_AGENTS[trimmed];
3276
- if (builtin) return builtin;
3277
- throw new Error(`loadAgentIdentity: agent '${trimmed}' declared but ${agentPath} does not exist`);
3278
- }
3279
- function resolveAgentFile(cwd, slug2, agentsDir = DEFAULT_AGENT_DIR) {
3280
- const localPath = path16.join(cwd, agentsDir, `${slug2}.md`);
3281
- if (fs16.existsSync(localPath)) return localPath;
3282
- const storeAgentRoot = getCompanyStoreAssetRoot("agents");
3283
- if (storeAgentRoot) {
3284
- const storePath = path16.join(storeAgentRoot, `${slug2}.md`);
3285
- if (fs16.existsSync(storePath)) return storePath;
3286
- }
3287
- return localPath;
3288
- }
3289
- function frameAgentIdentity(slug2, agent) {
3290
- return [
3291
- `## Who you are \u2014 agent identity (authoritative identity)`,
3292
- ``,
3293
- `You are operating as agent \`${slug2}\`. This identity defines *who* you are:`,
3294
- `your authority, doctrine, voice, and hard limits. Honour it exactly. Where the`,
3295
- `this identity's restrictions are stricter than the task, **the agent wins** \u2014 a task`,
3296
- `can never grant you authority your agent withholds.`,
3297
- ``,
3298
- agent
3299
- ].join("\n");
3300
- }
3301
- var DEFAULT_AGENT_DIR, BUILTIN_AGENTS;
3302
- var init_agents = __esm({
3303
- "src/agents.ts"() {
3304
- "use strict";
3305
- init_companyStore();
3306
- DEFAULT_AGENT_DIR = ".kody/agents";
3307
- BUILTIN_AGENTS = {};
3308
- }
3309
- });
3310
-
3311
3355
  // src/profile-error.ts
3312
3356
  var ProfileError;
3313
3357
  var init_profile_error = __esm({
@@ -3659,7 +3703,6 @@ function loadProfile(profilePath) {
3659
3703
  implementation: execRef,
3660
3704
  executable: execRef,
3661
3705
  describe: typeof r.describe === "string" ? r.describe : base.describe,
3662
- capabilityKind: parseCapabilityKind(profilePath, r.capabilityKind) ?? base.capabilityKind,
3663
3706
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : base.agent,
3664
3707
  capabilityTools: parseStringArray2(r.capabilityTools ?? r.capabilityTools ?? r.tools) ?? base.capabilityTools,
3665
3708
  mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : base.mentions
@@ -3704,7 +3747,6 @@ function loadProfile(profilePath) {
3704
3747
  implementation: void 0,
3705
3748
  executable: void 0,
3706
3749
  describe: typeof r.describe === "string" ? r.describe : "",
3707
- capabilityKind: parseCapabilityKind(profilePath, r.capabilityKind),
3708
3750
  // Optional agent to run as. Empty/blank string → undefined (no agent).
3709
3751
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : void 0,
3710
3752
  // Locked-toolbox palette + mentions from folder-capability profile metadata.
@@ -3799,13 +3841,6 @@ function requireString(p, r, key) {
3799
3841
  }
3800
3842
  return v;
3801
3843
  }
3802
- function parseCapabilityKind(p, raw) {
3803
- if (raw === void 0 || raw === null || raw === "") return void 0;
3804
- if (typeof raw !== "string" || !VALID_CAPABILITY_KINDS.has(raw)) {
3805
- throw new ProfileError(p, `"capabilityKind" must be one of: observe | act | verify`);
3806
- }
3807
- return raw;
3808
- }
3809
3844
  function parseStringArray2(raw) {
3810
3845
  if (!Array.isArray(raw)) return void 0;
3811
3846
  const values = raw.map((t) => String(t).trim()).filter(Boolean);
@@ -4052,7 +4087,7 @@ function parseScriptList(p, key, raw) {
4052
4087
  }
4053
4088
  return out;
4054
4089
  }
4055
- 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;
4056
4091
  var init_profile = __esm({
4057
4092
  "src/profile.ts"() {
4058
4093
  "use strict";
@@ -4068,7 +4103,6 @@ var init_profile = __esm({
4068
4103
  VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
4069
4104
  VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
4070
4105
  VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
4071
- VALID_CAPABILITY_KINDS = /* @__PURE__ */ new Set(["observe", "act", "verify"]);
4072
4106
  KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
4073
4107
  "name",
4074
4108
  "action",
@@ -4082,10 +4116,10 @@ var init_profile = __esm({
4082
4116
  "capabilityTools",
4083
4117
  "tools",
4084
4118
  "mentions",
4085
- "capabilityKind",
4086
4119
  "stage",
4087
4120
  "readsFrom",
4088
4121
  "writesTo",
4122
+ "workflow",
4089
4123
  "describe",
4090
4124
  "role",
4091
4125
  "kind",
@@ -6077,7 +6111,7 @@ function asPreferredRunTime(value) {
6077
6111
  function asLoopTarget(value) {
6078
6112
  const raw = asRecord(value);
6079
6113
  if (!raw) return void 0;
6080
- if (raw.type !== "goal" && raw.type !== "capability") return void 0;
6114
+ if (raw.type !== "goal" && raw.type !== "capability" && raw.type !== "workflow") return void 0;
6081
6115
  if (typeof raw.id !== "string" || raw.id.trim().length === 0) return void 0;
6082
6116
  return { type: raw.type, id: raw.id };
6083
6117
  }
@@ -6173,10 +6207,10 @@ var init_state2 = __esm({
6173
6207
  "use strict";
6174
6208
  VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
6175
6209
  GoalStateError = class extends Error {
6176
- constructor(path48, message) {
6177
- super(`Invalid goal state at ${path48}:
6210
+ constructor(path49, message) {
6211
+ super(`Invalid goal state at ${path49}:
6178
6212
  ${message}`);
6179
- this.path = path48;
6213
+ this.path = path49;
6180
6214
  this.name = "GoalStateError";
6181
6215
  }
6182
6216
  path;
@@ -6189,17 +6223,12 @@ import * as fs25 from "fs";
6189
6223
  function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
6190
6224
  const logs = goalRunLogs(data);
6191
6225
  const existing = logs[goalId];
6192
- const path48 = existing?.path ?? goalRunLogPath(goalId, data);
6226
+ const path49 = existing?.path ?? goalRunLogPath(goalId, data);
6193
6227
  logs[goalId] = {
6194
- path: path48,
6228
+ path: path49,
6195
6229
  events: [
6196
6230
  ...existing?.events ?? [],
6197
- {
6198
- version: 1,
6199
- time: at,
6200
- goalId,
6201
- ...event
6202
- }
6231
+ buildGoalRunLogEvent(data, goalId, event, at)
6203
6232
  ]
6204
6233
  };
6205
6234
  }
@@ -6297,15 +6326,33 @@ function githubRunId() {
6297
6326
  const attempt = process.env.GITHUB_RUN_ATTEMPT?.trim();
6298
6327
  return attempt ? `gh-${runId}-${attempt}` : `gh-${runId}`;
6299
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
+ }
6300
6344
  function enrichGoalRunLogEvent(config, data, logPath, event) {
6301
6345
  const stateRepo = stateRepoContext(config, event.goalId, logPath);
6346
+ const trigger = event.trigger ?? triggerContext();
6347
+ const job = event.job ?? jobContext(data);
6302
6348
  return pruneUndefined({
6303
6349
  ...event,
6304
6350
  run: event.run ?? runContext(data),
6305
6351
  repo: event.repo ?? repoContext(config),
6306
6352
  stateRepo: event.stateRepo ?? stateRepo,
6307
- trigger: event.trigger ?? triggerContext(),
6308
- job: event.job ?? jobContext(data),
6353
+ trigger,
6354
+ job,
6355
+ dispatchContext: event.dispatchContext ?? dispatchContext(event, trigger, job),
6309
6356
  links: event.links ?? linkContext(stateRepo)
6310
6357
  });
6311
6358
  }
@@ -6352,9 +6399,16 @@ function stateRepoContext(config, goalId, logPath) {
6352
6399
  function triggerContext() {
6353
6400
  const event = readGithubEvent();
6354
6401
  const inputs = recordValue2(event?.inputs);
6355
- return pruneUndefined({
6356
- eventName: process.env.GITHUB_EVENT_NAME?.trim() || void 0,
6357
- 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),
6358
6412
  eventPath: process.env.GITHUB_EVENT_PATH?.trim() || void 0,
6359
6413
  issue: numberValue(recordValue2(event?.issue)?.number),
6360
6414
  pullRequest: numberValue(recordValue2(event?.pull_request)?.number),
@@ -6362,23 +6416,70 @@ function triggerContext() {
6362
6416
  schedule: stringValue(event?.schedule),
6363
6417
  inputs: inputs ? pickRecord(inputs, ["issue_number", "sessionId", "message", "model", "title", "executable", "base"]) : void 0
6364
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";
6365
6432
  }
6366
6433
  function jobContext(data) {
6367
6434
  const job = pruneUndefined({
6368
- id: stringValue(data.jobId),
6369
- key: stringValue(data.jobKey),
6370
- flavor: stringValue(data.jobFlavor),
6371
- action: stringValue(data.jobAction),
6372
- capability: stringValue(data.jobCapability),
6373
- executable: stringValue(data.jobExecutable),
6374
- agent: stringValue(data.jobAgent),
6375
- schedule: stringValue(data.jobSchedule),
6376
- 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,
6377
6444
  why: truncateString(stringValue(data.jobWhy), 1e3),
6378
6445
  saveReport: data.jobSaveReport === true ? true : void 0
6379
6446
  });
6380
6447
  return Object.keys(job).length > 0 ? job : void 0;
6381
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
+ }
6382
6483
  function linkContext(stateRepo) {
6383
6484
  const links = {};
6384
6485
  const runId = process.env.GITHUB_RUN_ID?.trim();
@@ -7003,12 +7104,20 @@ function isCapabilityCadenceGoal(goal, extra) {
7003
7104
  function isGoalTargetLoop(goal) {
7004
7105
  return goal.type === "agentLoop" && goal.loopTarget?.type === "goal" && goal.loopTarget.id.trim().length > 0;
7005
7106
  }
7006
- 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) {
7007
7111
  const now = opts.now ?? /* @__PURE__ */ new Date();
7008
7112
  const at = now.toISOString();
7009
- const targetId = opts.goal.loopTarget?.type === "goal" ? opts.goal.loopTarget.id.trim() : "";
7113
+ const target = opts.goal.loopTarget;
7114
+ const targetId = target?.id.trim() ?? "";
7010
7115
  if (!targetId) {
7011
- 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")}`;
7012
7121
  return targetLoopDecision("blocked", reason, at);
7013
7122
  }
7014
7123
  const preferred = opts.goal.preferredRunTime;
@@ -7016,18 +7125,22 @@ function planGoalTargetLoopSchedule(opts) {
7016
7125
  const gate = preferredRunTimeGate(preferred, now, opts.previousScheduleState);
7017
7126
  if (!gate.ok) return targetLoopDecision("idle", gate.reason, at);
7018
7127
  }
7128
+ const dispatch2 = target.type === "goal" ? { action: "goal-manager", executable: "goal-manager", cliArgs: { goal: targetId } } : { workflow: targetId, cliArgs: {} };
7019
7129
  return {
7020
7130
  kind: "dispatch",
7021
- reason: `dispatch goal ${targetId}`,
7022
- dispatch: { action: "goal-manager", executable: "goal-manager", cliArgs: { goal: targetId } },
7131
+ reason: `dispatch ${target.type} ${targetId}`,
7132
+ dispatch: dispatch2,
7023
7133
  scheduleState: {
7024
7134
  mode: "agentLoop",
7025
7135
  lastGoalTickAt: at,
7026
7136
  lastDecision: {
7027
7137
  kind: "dispatch",
7028
- targetType: "goal",
7138
+ targetType: target.type,
7029
7139
  targetId,
7030
- 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 } : {},
7031
7144
  reason: preferred ? `preferred time ${preferred.time} ${preferred.timezone}` : "ready target loop tick",
7032
7145
  at
7033
7146
  },
@@ -7237,8 +7350,8 @@ function stageManagedGoalDecision(data, goalId, goal, goalState, decision, detai
7237
7350
  status: decision.kind,
7238
7351
  dispatch: {
7239
7352
  capability: decision.capability,
7240
- executable: decision.executable,
7241
- cliArgs: decision.cliArgs
7353
+ cliArgs: decision.cliArgs,
7354
+ ...decision.executable ? { executable: decision.executable } : {}
7242
7355
  },
7243
7356
  goal: details.goalSnapshot,
7244
7357
  inspection: details.inspection,
@@ -7247,8 +7360,8 @@ function stageManagedGoalDecision(data, goalId, goal, goalState, decision, detai
7247
7360
  evidence: decision.evidence,
7248
7361
  stage: decision.stage,
7249
7362
  capability: decision.capability,
7250
- executable: decision.executable,
7251
- cliArgs: decision.cliArgs
7363
+ cliArgs: decision.cliArgs,
7364
+ ...decision.executable ? { executable: decision.executable } : {}
7252
7365
  },
7253
7366
  change: details.change
7254
7367
  });
@@ -7443,18 +7556,20 @@ var init_advanceManagedGoal = __esm({
7443
7556
  ctx.output.reason = reason;
7444
7557
  return;
7445
7558
  }
7446
- if (isGoalTargetLoop(managed)) {
7559
+ if (isGoalTargetLoop(managed) || isWorkflowTargetLoop(managed)) {
7447
7560
  const beforeSnapshot = goalRunLogSnapshot(goal.id, goal.state, managed);
7448
7561
  const previousScheduleState = goal.raw.extra.scheduleState && typeof goal.raw.extra.scheduleState === "object" ? goal.raw.extra.scheduleState : void 0;
7449
- const decision2 = planGoalTargetLoopSchedule({ goal: managed, previousScheduleState });
7562
+ const decision2 = planTargetLoopSchedule({ goal: managed, previousScheduleState });
7450
7563
  restoreGoalIdFact();
7451
7564
  goal.raw = writeManagedGoalToState({ ...goal.raw, state: goal.state }, managed);
7452
7565
  goal.raw.extra.scheduleState = decision2.scheduleState;
7453
7566
  ctx.data.managedGoalDecision = decision2;
7454
7567
  if (decision2.kind === "dispatch" && decision2.dispatch) {
7455
7568
  ctx.output.nextDispatch = {
7456
- action: decision2.dispatch.action,
7457
- 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 } : {},
7458
7573
  cliArgs: decision2.dispatch.cliArgs
7459
7574
  };
7460
7575
  }
@@ -7575,8 +7690,8 @@ var init_advanceManagedGoal = __esm({
7575
7690
  }
7576
7691
  ctx.output.nextDispatch = {
7577
7692
  capability: decision.capability,
7578
- executable: decision.executable,
7579
7693
  cliArgs: decision.cliArgs,
7694
+ ...decision.executable ? { executable: decision.executable } : {},
7580
7695
  ...decision.saveReport === true ? { saveReport: true } : {}
7581
7696
  };
7582
7697
  ctx.output.reason = `dispatch ${decision.capability} for ${decision.evidence}`;
@@ -7816,13 +7931,13 @@ function companyIntentPath(id) {
7816
7931
  assertIntentId(id);
7817
7932
  return `intents/${id}/intent.json`;
7818
7933
  }
7819
- function normalizeCompanyIntent(path48, raw) {
7934
+ function normalizeCompanyIntent(path49, raw) {
7820
7935
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7821
- throw new Error(`${path48}: intent must be JSON object`);
7936
+ throw new Error(`${path49}: intent must be JSON object`);
7822
7937
  }
7823
7938
  const input = raw;
7824
7939
  const id = stringField2(input.id);
7825
- if (!id || !isCompanyIntentId(id)) throw new Error(`${path48}: invalid intent id`);
7940
+ if (!id || !isCompanyIntentId(id)) throw new Error(`${path49}: invalid intent id`);
7826
7941
  const createdAt = stringField2(input.createdAt) || nowIso();
7827
7942
  const updatedAt = stringField2(input.updatedAt) || createdAt;
7828
7943
  return {
@@ -7861,8 +7976,8 @@ function listCompanyIntents(config, cwd) {
7861
7976
  const records = [];
7862
7977
  for (const entry of entries) {
7863
7978
  if (entry.type !== "dir" || !entry.name || !isCompanyIntentId(entry.name)) continue;
7864
- const path48 = companyIntentPath(entry.name);
7865
- const file = readStateText(config, cwd, path48);
7979
+ const path49 = companyIntentPath(entry.name);
7980
+ const file = readStateText(config, cwd, path49);
7866
7981
  if (!file) continue;
7867
7982
  records.push({
7868
7983
  id: entry.name,
@@ -7873,8 +7988,8 @@ function listCompanyIntents(config, cwd) {
7873
7988
  return records.sort((a, b) => a.intent.priority - b.intent.priority || a.id.localeCompare(b.id));
7874
7989
  }
7875
7990
  function readCompanyIntent(config, cwd, id) {
7876
- const path48 = companyIntentPath(id);
7877
- const file = readStateText(config, cwd, path48);
7991
+ const path49 = companyIntentPath(id);
7992
+ const file = readStateText(config, cwd, path49);
7878
7993
  if (!file) return null;
7879
7994
  return { id, path: file.path, intent: normalizeCompanyIntent(file.path, JSON.parse(file.content)) };
7880
7995
  }
@@ -8361,6 +8476,9 @@ function goalReportBody(goalId, state, snapshot, latestEvent, evidenceItems) {
8361
8476
  `- Missing evidence: ${listOrNone(missingEvidence)}`,
8362
8477
  `- Blockers: ${listOrNone(blockers)}`,
8363
8478
  "",
8479
+ "## Dispatch",
8480
+ ...dispatchContextMarkdown(latestEvent),
8481
+ "",
8364
8482
  "## Capability Evidence",
8365
8483
  ...capabilityEvidenceMarkdown(outputs),
8366
8484
  "",
@@ -8398,6 +8516,27 @@ function evidenceOutputMarkdown(index, output) {
8398
8516
  ""
8399
8517
  ];
8400
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
+ }
8401
8540
  function artifactMarkdown(artifacts) {
8402
8541
  if (artifacts.length === 0) return ["- none"];
8403
8542
  return artifacts.map((artifact) => {
@@ -10444,10 +10583,9 @@ var init_dispatchClassified = __esm({
10444
10583
 
10445
10584
  // src/jobIdentity.ts
10446
10585
  function stableJobKey(job) {
10447
- const capability = job.capability ?? job.action;
10586
+ const capability = job.workflow ?? job.capability ?? job.action;
10448
10587
  const executable = job.executable ?? capability ?? "unknown";
10449
- if (job.flavor === "scheduled" && job.capability)
10450
- return `scheduled:${job.capability}:${executable}`;
10588
+ if (job.flavor === "scheduled" && job.capability) return `scheduled:${job.capability}:${executable}`;
10451
10589
  const target = typeof job.target === "number" ? job.target : targetFromCliArgs(job.cliArgs);
10452
10590
  const work = capability && executable && executable !== capability ? `${capability}:${executable}` : capability ?? executable;
10453
10591
  return target === void 0 ? `${job.flavor}:${work}` : `${job.flavor}:${work}:${target}`;
@@ -16622,14 +16760,16 @@ function jobReferenceBlock(profileName, profile, data) {
16622
16760
  const jobId = typeof data.jobId === "string" && data.jobId.length > 0 ? data.jobId : null;
16623
16761
  const flavor = typeof data.jobFlavor === "string" && data.jobFlavor.length > 0 ? data.jobFlavor : null;
16624
16762
  const schedule = typeof data.jobSchedule === "string" && data.jobSchedule.length > 0 ? data.jobSchedule : null;
16625
- const isJob2 = Boolean(
16626
- jobId || flavor || schedule || data.jobCapability || data.jobExecutable || data.jobWhy
16627
- );
16763
+ const isJob2 = Boolean(jobId || flavor || schedule || data.jobCapability || data.jobExecutable || data.jobWhy);
16628
16764
  if (!isJob2) return null;
16629
16765
  const capability = typeof data.jobCapability === "string" && data.jobCapability.length > 0 ? data.jobCapability : profile.executable ? profile.name : null;
16630
16766
  const executable = typeof profile.executable === "string" && profile.executable.length > 0 ? profile.executable : typeof data.jobExecutable === "string" && data.jobExecutable.length > 0 ? data.jobExecutable : profileName;
16631
16767
  const agent = typeof profile.agent === "string" && profile.agent.length > 0 ? profile.agent : typeof data.jobAgent === "string" && data.jobAgent.length > 0 ? data.jobAgent : null;
16632
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;
16633
16773
  const lines = [
16634
16774
  "## Job reference",
16635
16775
  "",
@@ -16641,7 +16781,9 @@ function jobReferenceBlock(profileName, profile, data) {
16641
16781
  `- Capability: ${capability ?? "(none)"}`,
16642
16782
  `- Executable: ${executable}`,
16643
16783
  `- Agent: ${agent ?? "(none)"}`,
16644
- `- Description: ${description || "(none)"}`
16784
+ `- Description: ${description || "(none)"}`,
16785
+ ...workflow ? [`- Workflow: ${workflow}`] : [],
16786
+ ...workflowStep ? [`- Workflow step: ${workflowStepIndex ?? "?"}/${workflowStepCount ?? "?"} ${workflowStep}`] : []
16645
16787
  ];
16646
16788
  return lines.join("\n");
16647
16789
  }
@@ -17056,7 +17198,7 @@ async function runExecutableChain(profileName, input) {
17056
17198
  };
17057
17199
  }
17058
17200
  process.stdout.write(
17059
- `\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})
17060
17202
 
17061
17203
  `
17062
17204
  );
@@ -17090,7 +17232,7 @@ async function runExecutableChain(profileName, input) {
17090
17232
  };
17091
17233
  }
17092
17234
  process.stdout.write(
17093
- `\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})
17094
17236
 
17095
17237
  `
17096
17238
  );
@@ -17108,18 +17250,19 @@ async function runExecutableChain(profileName, input) {
17108
17250
  };
17109
17251
  }
17110
17252
  if (result.nextDispatch || result.nextJob) {
17111
- 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";
17112
17254
  process.stderr.write(`[kody] in-process hand-off cap (${MAX_CHAIN_HOPS}) reached; not running ${pending}
17113
17255
  `);
17114
17256
  }
17115
17257
  return result;
17116
17258
  }
17117
17259
  function handoffToJob(handoff) {
17118
- const dutyOrAction = handoff.action ?? handoff.capability;
17260
+ const dutyOrAction = handoff.workflow ?? handoff.action ?? handoff.capability;
17119
17261
  if (!dutyOrAction) return null;
17120
17262
  return {
17121
17263
  action: handoff.action ?? handoff.capability,
17122
17264
  capability: handoff.capability,
17265
+ workflow: handoff.workflow,
17123
17266
  executable: handoff.executable,
17124
17267
  cliArgs: handoff.cliArgs,
17125
17268
  flavor: "instant",
@@ -17371,9 +17514,9 @@ var init_executor = __esm({
17371
17514
  "src/executor.ts"() {
17372
17515
  "use strict";
17373
17516
  init_agent();
17517
+ init_agents();
17374
17518
  init_capabilityReport();
17375
17519
  init_capabilityResult();
17376
- init_agents();
17377
17520
  init_config();
17378
17521
  init_container();
17379
17522
  init_discipline();
@@ -17400,6 +17543,99 @@ var init_executor = __esm({
17400
17543
  }
17401
17544
  });
17402
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
+
17403
17639
  // src/job.ts
17404
17640
  var job_exports = {};
17405
17641
  __export(job_exports, {
@@ -17412,7 +17648,7 @@ __export(job_exports, {
17412
17648
  stableJobKey: () => stableJobKey,
17413
17649
  validateJob: () => validateJob
17414
17650
  });
17415
- import * as path42 from "path";
17651
+ import * as path43 from "path";
17416
17652
  function newJobId(flavor) {
17417
17653
  localJobSeq += 1;
17418
17654
  const runId = process.env.GITHUB_RUN_ID;
@@ -17424,8 +17660,8 @@ function validateJob(input) {
17424
17660
  throw new InvalidJobError("job must be an object");
17425
17661
  }
17426
17662
  const j = input;
17427
- if (typeof j.capability !== "string" && typeof j.action !== "string") {
17428
- 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");
17429
17665
  }
17430
17666
  if (j.flavor !== "instant" && j.flavor !== "scheduled") {
17431
17667
  throw new InvalidJobError(`job.flavor must be "instant" or "scheduled" (got ${String(j.flavor)})`);
@@ -17437,6 +17673,7 @@ function validateJob(input) {
17437
17673
  action: typeof j.action === "string" ? j.action : void 0,
17438
17674
  executable: typeof j.executable === "string" ? j.executable : void 0,
17439
17675
  capability: typeof j.capability === "string" ? j.capability : void 0,
17676
+ workflow: typeof j.workflow === "string" ? j.workflow : void 0,
17440
17677
  why: typeof j.why === "string" ? j.why : void 0,
17441
17678
  agent: typeof j.agent === "string" ? j.agent : void 0,
17442
17679
  schedule: typeof j.schedule === "string" ? j.schedule : void 0,
@@ -17450,21 +17687,49 @@ function validateJob(input) {
17450
17687
  async function runJob(job, base) {
17451
17688
  const valid = validateJob(job);
17452
17689
  const action = valid.action ?? valid.capability;
17453
- const projectCapabilitiesRoot = path42.join(base.cwd, ".kody", "capabilities");
17454
- 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;
17455
17692
  const capabilityIdentity = valid.capability ?? resolvedCapability?.capability;
17456
- 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;
17457
17695
  const explicitExecutableOnly = valid.executable !== void 0 && (valid.action === void 0 || valid.action === valid.executable) && (valid.capability === void 0 || valid.capability === valid.executable);
17458
- if (!resolvedCapability && !capabilityContext && !explicitExecutableOnly) {
17459
- 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
+ );
17460
17700
  }
17701
+ const workflow = capabilityContext?.config.workflow ?? workflowContext?.config.workflow;
17702
+ const workflowIdentity = valid.workflow ?? capabilityIdentity ?? workflowContext?.slug;
17461
17703
  const capabilitySelectedExecutable = resolvedCapability?.executable ?? capabilityContext?.config.executable ?? capabilityContext?.config.executables?.[0] ?? (capabilityContext?.config.tickScript ? "capability-tick-scripted" : void 0);
17462
17704
  const profileName = valid.executable ?? capabilitySelectedExecutable;
17463
- if (!profileName) {
17464
- throw new InvalidJobError(
17465
- `job capability resolves to no executable: ${capabilityIdentity ?? action}`
17466
- );
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);
17467
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) {
17468
17733
  const preloadedData = { ...base.preloadedData ?? {} };
17469
17734
  preloadedData.jobId = newJobId(valid.flavor);
17470
17735
  preloadedData.jobKey = stableJobKey(valid);
@@ -17473,9 +17738,7 @@ async function runJob(job, base) {
17473
17738
  if (valid.action !== void 0 && valid.action.length > 0) preloadedData.jobAction = valid.action;
17474
17739
  if (capabilityIdentity !== void 0 && capabilityIdentity.length > 0)
17475
17740
  preloadedData.jobCapability = capabilityIdentity;
17476
- const executableIdentity = profileName;
17477
- if (executableIdentity !== void 0 && executableIdentity.length > 0)
17478
- preloadedData.jobExecutable = executableIdentity;
17741
+ preloadedData.jobExecutable = profileName;
17479
17742
  if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
17480
17743
  if (valid.saveReport === true) preloadedData.jobSaveReport = true;
17481
17744
  if (capabilityContext) {
@@ -17483,8 +17746,7 @@ async function runJob(job, base) {
17483
17746
  preloadedData.capabilityTitle = capabilityContext.title;
17484
17747
  preloadedData.dutyIntent = capabilityContext.body;
17485
17748
  preloadedData.jobIntent = capabilityContext.body;
17486
- if (preloadedData.jobCapability === void 0)
17487
- preloadedData.jobCapability = capabilityContext.slug;
17749
+ if (preloadedData.jobCapability === void 0) preloadedData.jobCapability = capabilityContext.slug;
17488
17750
  if (capabilityContext.config.agent && preloadedData.jobAgent === void 0) {
17489
17751
  preloadedData.jobAgent = capabilityContext.config.agent;
17490
17752
  }
@@ -17508,9 +17770,182 @@ async function runJob(job, base) {
17508
17770
  const run = base.chain === false ? runExecutable : runExecutableChain;
17509
17771
  return run(profileName, input);
17510
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
+ }
17511
17941
  function loadCapabilityContext(slug2, cwd) {
17512
17942
  if (!slug2) return null;
17513
- 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;
17514
17949
  }
17515
17950
  function mintInstantJob(dispatch2, opts) {
17516
17951
  return {
@@ -17542,6 +17977,7 @@ var init_job = __esm({
17542
17977
  "use strict";
17543
17978
  init_executor();
17544
17979
  init_registry();
17980
+ init_workflowDefinitions();
17545
17981
  init_jobIdentity();
17546
17982
  init_jobIdentity();
17547
17983
  DEFAULT_INSTANT_AGENT = "kody";
@@ -17662,9 +18098,9 @@ function translateOpenAISseToBrain(opts) {
17662
18098
  }
17663
18099
 
17664
18100
  // src/servers/brain-serve.ts
17665
- import * as fs46 from "fs";
18101
+ import * as fs47 from "fs";
17666
18102
  import { createServer } from "http";
17667
- import * as path45 from "path";
18103
+ import * as path46 from "path";
17668
18104
 
17669
18105
  // src/chat/loop.ts
17670
18106
  init_agent();
@@ -18224,8 +18660,8 @@ init_config();
18224
18660
 
18225
18661
  // src/kody-cli.ts
18226
18662
  import { execFileSync as execFileSync25 } from "child_process";
18227
- import * as fs44 from "fs";
18228
- import * as path43 from "path";
18663
+ import * as fs45 from "fs";
18664
+ import * as path44 from "path";
18229
18665
 
18230
18666
  // src/app-auth.ts
18231
18667
  import { createSign } from "crypto";
@@ -18846,9 +19282,9 @@ async function resolveAuthToken(env = process.env) {
18846
19282
  return void 0;
18847
19283
  }
18848
19284
  function detectPackageManager2(cwd) {
18849
- if (fs44.existsSync(path43.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
18850
- if (fs44.existsSync(path43.join(cwd, "yarn.lock"))) return "yarn";
18851
- 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";
18852
19288
  return "npm";
18853
19289
  }
18854
19290
  function shouldChainScheduledWatch(match) {
@@ -18941,8 +19377,8 @@ function postFailureTail(issueNumber, cwd, reason) {
18941
19377
  const logPath = lastRunLogPath(cwd);
18942
19378
  let tail = "";
18943
19379
  try {
18944
- if (fs44.existsSync(logPath)) {
18945
- const content = fs44.readFileSync(logPath, "utf-8");
19380
+ if (fs45.existsSync(logPath)) {
19381
+ const content = fs45.readFileSync(logPath, "utf-8");
18946
19382
  tail = content.slice(-3e3);
18947
19383
  }
18948
19384
  } catch {
@@ -18967,7 +19403,7 @@ async function runCi(argv) {
18967
19403
  return 0;
18968
19404
  }
18969
19405
  const args = parseCiArgs(argv);
18970
- const cwd = args.cwd ? path43.resolve(args.cwd) : process.cwd();
19406
+ const cwd = args.cwd ? path44.resolve(args.cwd) : process.cwd();
18971
19407
  let earlyConfig;
18972
19408
  let earlyConfigError;
18973
19409
  try {
@@ -18982,9 +19418,9 @@ async function runCi(argv) {
18982
19418
  let manualWorkflowDispatch = false;
18983
19419
  let forceRunAction = null;
18984
19420
  let forceRunCliArgs = {};
18985
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs44.existsSync(dispatchEventPath)) {
19421
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs45.existsSync(dispatchEventPath)) {
18986
19422
  try {
18987
- const evt = JSON.parse(fs44.readFileSync(dispatchEventPath, "utf-8"));
19423
+ const evt = JSON.parse(fs45.readFileSync(dispatchEventPath, "utf-8"));
18988
19424
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
18989
19425
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
18990
19426
  const dutyInput = String(evt?.inputs?.capability ?? evt?.inputs?.executable ?? "").trim();
@@ -19316,8 +19752,8 @@ init_repoWorkspace();
19316
19752
 
19317
19753
  // src/scripts/brainTurnLog.ts
19318
19754
  init_runtimePaths();
19319
- import * as fs45 from "fs";
19320
- import * as path44 from "path";
19755
+ import * as fs46 from "fs";
19756
+ import * as path45 from "path";
19321
19757
  import posixPath4 from "path/posix";
19322
19758
  var live = /* @__PURE__ */ new Map();
19323
19759
  function brainEventsFilePath(dir, chatId) {
@@ -19328,8 +19764,8 @@ function brainEventsStatePath(chatId) {
19328
19764
  }
19329
19765
  function lastPersistedSeq(dir, chatId) {
19330
19766
  const p = brainEventsFilePath(dir, chatId);
19331
- if (!fs45.existsSync(p)) return 0;
19332
- 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);
19333
19769
  if (lines.length === 0) return 0;
19334
19770
  try {
19335
19771
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -19339,9 +19775,9 @@ function lastPersistedSeq(dir, chatId) {
19339
19775
  }
19340
19776
  function readSince(dir, chatId, since) {
19341
19777
  const p = brainEventsFilePath(dir, chatId);
19342
- if (!fs45.existsSync(p)) return [];
19778
+ if (!fs46.existsSync(p)) return [];
19343
19779
  const out = [];
19344
- for (const line of fs45.readFileSync(p, "utf-8").split("\n")) {
19780
+ for (const line of fs46.readFileSync(p, "utf-8").split("\n")) {
19345
19781
  if (!line) continue;
19346
19782
  try {
19347
19783
  const rec = JSON.parse(line);
@@ -19367,12 +19803,12 @@ function beginTurn(dir, chatId) {
19367
19803
  };
19368
19804
  live.set(chatId, state);
19369
19805
  const p = brainEventsFilePath(dir, chatId);
19370
- fs45.mkdirSync(path44.dirname(p), { recursive: true });
19806
+ fs46.mkdirSync(path45.dirname(p), { recursive: true });
19371
19807
  return (event) => {
19372
19808
  state.seq += 1;
19373
19809
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
19374
19810
  try {
19375
- fs45.appendFileSync(p, `${JSON.stringify(rec)}
19811
+ fs46.appendFileSync(p, `${JSON.stringify(rec)}
19376
19812
  `);
19377
19813
  } catch (err) {
19378
19814
  process.stderr.write(
@@ -19411,7 +19847,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
19411
19847
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
19412
19848
  };
19413
19849
  try {
19414
- fs45.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
19850
+ fs46.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
19415
19851
  `);
19416
19852
  } catch {
19417
19853
  }
@@ -19717,7 +20153,7 @@ async function handleChatTurn(req, res, chatId, opts) {
19717
20153
  );
19718
20154
  }
19719
20155
  }
19720
- fs46.mkdirSync(path45.dirname(sessionFile), { recursive: true });
20156
+ fs47.mkdirSync(path46.dirname(sessionFile), { recursive: true });
19721
20157
  appendTurn(sessionFile, {
19722
20158
  role: "user",
19723
20159
  content: message,
@@ -19782,7 +20218,7 @@ async function handleChatTurn(req, res, chatId, opts) {
19782
20218
  function buildServer(opts) {
19783
20219
  const runTurn = opts.runTurn ?? runChatTurn;
19784
20220
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
19785
- 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");
19786
20222
  return createServer(async (req, res) => {
19787
20223
  if (!req.method || !req.url) {
19788
20224
  sendJson(res, 400, { error: "bad request" });
@@ -20377,8 +20813,8 @@ async function loadConfigSafe() {
20377
20813
  }
20378
20814
 
20379
20815
  // src/chat-cli.ts
20380
- import * as fs48 from "fs";
20381
- import * as path47 from "path";
20816
+ import * as fs49 from "fs";
20817
+ import * as path48 from "path";
20382
20818
 
20383
20819
  // src/chat/inbox.ts
20384
20820
  import { execFileSync as execFileSync26 } from "child_process";
@@ -20450,8 +20886,8 @@ function currentBranch(cwd) {
20450
20886
 
20451
20887
  // src/chat/state-sync.ts
20452
20888
  init_stateRepo();
20453
- import * as fs47 from "fs";
20454
- import * as path46 from "path";
20889
+ import * as fs48 from "fs";
20890
+ import * as path47 from "path";
20455
20891
  function jsonlLines2(text) {
20456
20892
  return text.split("\n").filter((line) => line.length > 0);
20457
20893
  }
@@ -20468,15 +20904,15 @@ function mergeJsonl2(localText, remoteText) {
20468
20904
  function syncJsonlFileFromState(opts) {
20469
20905
  const remote = readStateText(opts.config, opts.cwd, opts.statePath);
20470
20906
  if (!remote) return;
20471
- 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") : "";
20472
20908
  const next = mergeJsonl2(local, remote.content);
20473
20909
  if (next === local) return;
20474
- fs47.mkdirSync(path46.dirname(opts.localPath), { recursive: true });
20475
- fs47.writeFileSync(opts.localPath, next);
20910
+ fs48.mkdirSync(path47.dirname(opts.localPath), { recursive: true });
20911
+ fs48.writeFileSync(opts.localPath, next);
20476
20912
  }
20477
20913
  function persistJsonlFileToState(opts) {
20478
- if (!fs47.existsSync(opts.localPath)) return;
20479
- const localText = fs47.readFileSync(opts.localPath, "utf-8");
20914
+ if (!fs48.existsSync(opts.localPath)) return;
20915
+ const localText = fs48.readFileSync(opts.localPath, "utf-8");
20480
20916
  for (let attempt = 1; attempt <= 3; attempt += 1) {
20481
20917
  const remote = readStateText(opts.config, opts.cwd, opts.statePath);
20482
20918
  const body = mergeJsonl2(localText, remote?.content ?? "");
@@ -20736,7 +21172,7 @@ async function runChat(argv) {
20736
21172
  ${CHAT_HELP}`);
20737
21173
  return 64;
20738
21174
  }
20739
- const cwd = args.cwd ? path47.resolve(args.cwd) : process.cwd();
21175
+ const cwd = args.cwd ? path48.resolve(args.cwd) : process.cwd();
20740
21176
  const sessionId = args.sessionId;
20741
21177
  const unpackedSecrets = unpackAllSecrets();
20742
21178
  if (unpackedSecrets > 0) {
@@ -20797,7 +21233,7 @@ ${CHAT_HELP}`);
20797
21233
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
20798
21234
  const meta = readMeta(sessionFile);
20799
21235
  process.stdout.write(
20800
- `\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"}
20801
21237
  `
20802
21238
  );
20803
21239
  try {
@@ -20957,8 +21393,8 @@ var FlyClient = class {
20957
21393
  get fetch() {
20958
21394
  return this.opts.fetchImpl ?? fetch;
20959
21395
  }
20960
- async call(path48, init = {}) {
20961
- const res = await this.fetch(`${FLY_API_BASE}${path48}`, {
21396
+ async call(path49, init = {}) {
21397
+ const res = await this.fetch(`${FLY_API_BASE}${path49}`, {
20962
21398
  method: init.method ?? "GET",
20963
21399
  headers: {
20964
21400
  Authorization: `Bearer ${this.opts.token}`,
@@ -20969,7 +21405,7 @@ var FlyClient = class {
20969
21405
  if (res.status === 404 && init.allow404) return null;
20970
21406
  if (!res.ok) {
20971
21407
  const text = await res.text().catch(() => "");
20972
- 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}`);
20973
21409
  }
20974
21410
  if (res.status === 204) return null;
20975
21411
  const raw = await res.text();
@@ -21684,7 +22120,7 @@ async function poolServe() {
21684
22120
 
21685
22121
  // src/servers/runner-serve.ts
21686
22122
  import { spawn as spawn8 } from "child_process";
21687
- import * as fs49 from "fs";
22123
+ import * as fs50 from "fs";
21688
22124
  import { createServer as createServer5 } from "http";
21689
22125
  var DEFAULT_PORT2 = 8080;
21690
22126
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -21764,8 +22200,8 @@ async function defaultRunJob(job) {
21764
22200
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
21765
22201
  const branch = job.ref ?? "main";
21766
22202
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
21767
- fs49.rmSync(workdir, { recursive: true, force: true });
21768
- fs49.mkdirSync(workdir, { recursive: true });
22203
+ fs50.rmSync(workdir, { recursive: true, force: true });
22204
+ fs50.mkdirSync(workdir, { recursive: true });
21769
22205
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
21770
22206
  const interactive = job.mode === "interactive";
21771
22207
  const scheduled = job.mode === "scheduled";