@kody-ade/kody-engine 0.4.379 → 0.4.380

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.378",
18
+ version: "0.4.380",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -53,6 +53,7 @@ var init_package = __esm({
53
53
  "@actions/cache": "^6.0.0",
54
54
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
55
55
  "@modelcontextprotocol/sdk": "^1.29.0",
56
+ convex: "^1.17.0",
56
57
  zod: "^4.0.0"
57
58
  },
58
59
  devDependencies: {
@@ -1612,7 +1613,7 @@ function cmsHeaders(opts) {
1612
1613
  }
1613
1614
  };
1614
1615
  }
1615
- async function callDashboardCms(opts, path52, init = {}) {
1616
+ async function callDashboardCms(opts, path53, init = {}) {
1616
1617
  const baseUrl = dashboardBaseUrl(opts);
1617
1618
  if (!baseUrl) {
1618
1619
  return {
@@ -1624,7 +1625,7 @@ async function callDashboardCms(opts, path52, init = {}) {
1624
1625
  const headerResult = cmsHeaders(opts);
1625
1626
  if (!headerResult.ok) return headerResult;
1626
1627
  try {
1627
- const res = await fetch(`${baseUrl}${path52}`, {
1628
+ const res = await fetch(`${baseUrl}${path53}`, {
1628
1629
  ...init,
1629
1630
  headers: {
1630
1631
  ...headerResult.headers,
@@ -1696,8 +1697,8 @@ function documentArg(value) {
1696
1697
  function normalizeCmsDocumentIdInput(input) {
1697
1698
  const trimmed = stripWrappingQuotes(input.trim());
1698
1699
  const withoutQuery = trimmed.split(/[?#]/, 1)[0] ?? trimmed;
1699
- const path52 = parseDocumentPath(withoutQuery);
1700
- return path52 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1700
+ const path53 = parseDocumentPath(withoutQuery);
1701
+ return path53 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
1701
1702
  }
1702
1703
  function stripWrappingQuotes(value) {
1703
1704
  let current = value;
@@ -1708,9 +1709,9 @@ function stripWrappingQuotes(value) {
1708
1709
  }
1709
1710
  }
1710
1711
  function parseDocumentPath(value) {
1711
- const path52 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1712
- if (!path52?.includes("/content/entries/")) return null;
1713
- const parts = path52.split("/").filter(Boolean).map(decodePathPart);
1712
+ const path53 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
1713
+ if (!path53?.includes("/content/entries/")) return null;
1714
+ const parts = path53.split("/").filter(Boolean).map(decodePathPart);
1714
1715
  const entriesIndex = parts.findIndex((part, index) => part === "content" && parts[index + 1] === "entries");
1715
1716
  const idPart = parts[entriesIndex + 3];
1716
1717
  if (!idPart || idPart === "new") return null;
@@ -1863,6 +1864,16 @@ var init_dashboardCmsMcp = __esm({
1863
1864
  // src/capabilityFolders.ts
1864
1865
  import * as fs4 from "fs";
1865
1866
  import * as path6 from "path";
1867
+ function capabilityOutputConditionPaths(config) {
1868
+ const result = config.output?.result;
1869
+ if (!result) return /* @__PURE__ */ new Set();
1870
+ return /* @__PURE__ */ new Set([
1871
+ "result.status",
1872
+ "result.summary",
1873
+ "result.resultClass",
1874
+ ...result.facts.map((fact) => `result.facts.${fact}`)
1875
+ ]);
1876
+ }
1866
1877
  function listCapabilityFolderSlugs(absDir) {
1867
1878
  if (!fs4.existsSync(absDir)) return [];
1868
1879
  let entries;
@@ -1923,9 +1934,17 @@ function parseCapabilityConfig(raw) {
1923
1934
  stage: stringField(raw.stage),
1924
1935
  readsFrom: stringList(raw.readsFrom ?? raw.reads_from),
1925
1936
  writesTo: stringList(raw.writesTo ?? raw.writes_to),
1937
+ output: parseCapabilityOutput(raw.output),
1926
1938
  workflow: parseCapabilityWorkflow(raw.workflow)
1927
1939
  };
1928
1940
  }
1941
+ function parseCapabilityOutput(raw) {
1942
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
1943
+ const result = raw.result;
1944
+ if (!result || typeof result !== "object" || Array.isArray(result)) return void 0;
1945
+ const facts = stringList(result.facts);
1946
+ return { result: { facts } };
1947
+ }
1929
1948
  function parseCapabilityKind(raw) {
1930
1949
  return raw === "observe" || raw === "act" || raw === "verify" ? raw : void 0;
1931
1950
  }
@@ -4645,6 +4664,7 @@ function loadProfile(profilePath) {
4645
4664
  skills: parseStringArray2(r.skills),
4646
4665
  prompt: typeof r.prompt === "string" && r.prompt.trim() ? r.prompt.trim() : void 0,
4647
4666
  chatTools: parseStringArray2(r.chatTools),
4667
+ auth: parseAuth(profilePath, r.auth),
4648
4668
  describe: typeof r.describe === "string" ? r.describe : "",
4649
4669
  // Optional agent to run as. Empty/blank string → undefined (no agent).
4650
4670
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : void 0,
@@ -4752,6 +4772,80 @@ function parseStringArray2(raw) {
4752
4772
  const values = raw.map((t) => String(t).trim()).filter(Boolean);
4753
4773
  return values.length > 0 ? values : void 0;
4754
4774
  }
4775
+ function rejectUnknownAuthFields(p, value, allowed, prefix) {
4776
+ const known = new Set(allowed);
4777
+ const unknown = Object.keys(value).find((key) => !known.has(key));
4778
+ if (unknown) throw new ProfileError(p, `${prefix} has unknown field "${unknown}"`);
4779
+ }
4780
+ function parseAuth(p, raw) {
4781
+ if (raw === void 0 || raw === null) return void 0;
4782
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
4783
+ throw new ProfileError(p, `"auth" must be an object`);
4784
+ }
4785
+ const auth = raw;
4786
+ rejectUnknownAuthFields(p, auth, ["methods"], "auth");
4787
+ const methodsRaw = auth.methods;
4788
+ if (!Array.isArray(methodsRaw) || methodsRaw.length === 0) {
4789
+ throw new ProfileError(p, `auth.methods must be a non-empty array`);
4790
+ }
4791
+ const methods = methodsRaw.map((methodRaw, methodIndex) => {
4792
+ if (!methodRaw || typeof methodRaw !== "object" || Array.isArray(methodRaw)) {
4793
+ throw new ProfileError(p, `auth.methods[${methodIndex}] must be an object`);
4794
+ }
4795
+ const method = methodRaw;
4796
+ rejectUnknownAuthFields(p, method, ["name", "strategy", "adapter", "fields"], `auth.methods[${methodIndex}]`);
4797
+ const name = method.name;
4798
+ if (typeof name !== "string" || !AUTH_TEXT_RE.test(name.trim())) {
4799
+ throw new ProfileError(p, `auth.methods[${methodIndex}].name must be a single-line string of 1-120 characters`);
4800
+ }
4801
+ if (method.strategy !== "browser-storage-state") {
4802
+ throw new ProfileError(p, `auth.methods[${methodIndex}].strategy must be browser-storage-state`);
4803
+ }
4804
+ if (method.adapter !== "kody-repository") {
4805
+ throw new ProfileError(p, `auth.methods[${methodIndex}].adapter must be kody-repository`);
4806
+ }
4807
+ if (!Array.isArray(method.fields) || method.fields.length === 0) {
4808
+ throw new ProfileError(p, `auth.methods[${methodIndex}].fields must be a non-empty array`);
4809
+ }
4810
+ const fields = method.fields.map((fieldRaw, fieldIndex) => {
4811
+ const prefix = `auth.methods[${methodIndex}].fields[${fieldIndex}]`;
4812
+ if (!fieldRaw || typeof fieldRaw !== "object" || Array.isArray(fieldRaw)) {
4813
+ throw new ProfileError(p, `${prefix} must be an object`);
4814
+ }
4815
+ const field = fieldRaw;
4816
+ rejectUnknownAuthFields(p, field, ["label", "source", "key"], prefix);
4817
+ if (typeof field.label !== "string" || !AUTH_TEXT_RE.test(field.label.trim())) {
4818
+ throw new ProfileError(p, `${prefix}.label must be a single-line string of 1-120 characters`);
4819
+ }
4820
+ if (field.source !== "variable" && field.source !== "secret") {
4821
+ throw new ProfileError(p, `${prefix}.source must be variable or secret`);
4822
+ }
4823
+ if (typeof field.key !== "string" || !AUTH_KEY_RE.test(field.key)) {
4824
+ throw new ProfileError(p, `${prefix}.key must be an uppercase variable or secret name`);
4825
+ }
4826
+ return {
4827
+ label: field.label.trim(),
4828
+ source: field.source,
4829
+ key: field.key
4830
+ };
4831
+ });
4832
+ const variableFields = fields.filter((field) => field.source === "variable");
4833
+ const secretFields = fields.filter((field) => field.source === "secret");
4834
+ if (variableFields.length !== 1 || secretFields.length !== 1) {
4835
+ throw new ProfileError(
4836
+ p,
4837
+ `auth.methods[${methodIndex}] kody-repository requires exactly one variable field and one secret field`
4838
+ );
4839
+ }
4840
+ return {
4841
+ name: name.trim(),
4842
+ strategy: method.strategy,
4843
+ adapter: method.adapter,
4844
+ fields
4845
+ };
4846
+ });
4847
+ return { methods };
4848
+ }
4755
4849
  function parseCapabilityKind2(raw) {
4756
4850
  return raw === "observe" || raw === "act" || raw === "verify" ? raw : void 0;
4757
4851
  }
@@ -4996,7 +5090,7 @@ function parseScriptList(p, key, raw) {
4996
5090
  }
4997
5091
  return out;
4998
5092
  }
4999
- var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHILD_TARGETS, VALID_PHASES, KNOWN_PROFILE_KEYS;
5093
+ var VALID_INPUT_TYPES, VALID_PERMISSION_MODES, VALID_ROLES, VALID_CONTAINER_CHILD_TARGETS, VALID_PHASES, KNOWN_PROFILE_KEYS, AUTH_KEY_RE, AUTH_TEXT_RE;
5000
5094
  var init_profile = __esm({
5001
5095
  "src/profile.ts"() {
5002
5096
  "use strict";
@@ -5025,6 +5119,7 @@ var init_profile = __esm({
5025
5119
  "skills",
5026
5120
  "prompt",
5027
5121
  "chatTools",
5122
+ "auth",
5028
5123
  "agent",
5029
5124
  "every",
5030
5125
  "capabilityTools",
@@ -5058,6 +5153,8 @@ var init_profile = __esm({
5058
5153
  "resetBetweenChildren",
5059
5154
  "preloadContext"
5060
5155
  ]);
5156
+ AUTH_KEY_RE = /^[A-Z][A-Z0-9_]{0,127}$/;
5157
+ AUTH_TEXT_RE = /^[^\r\n]{1,120}$/;
5061
5158
  }
5062
5159
  });
5063
5160
 
@@ -6812,6 +6909,29 @@ var init_runIndex = __esm({
6812
6909
  }
6813
6910
  });
6814
6911
 
6912
+ // src/runtimeCleanup.ts
6913
+ function registeredCleanup(ctx) {
6914
+ return Array.isArray(ctx.data.__runtimeCleanup) ? ctx.data.__runtimeCleanup : [];
6915
+ }
6916
+ function registerRuntimeCleanup(ctx, cleanup) {
6917
+ ctx.data.__runtimeCleanup = [...registeredCleanup(ctx), cleanup];
6918
+ }
6919
+ function runRuntimeCleanup(ctx) {
6920
+ const callbacks = registeredCleanup(ctx);
6921
+ delete ctx.data.__runtimeCleanup;
6922
+ for (const cleanup of callbacks.reverse()) {
6923
+ try {
6924
+ cleanup();
6925
+ } catch {
6926
+ }
6927
+ }
6928
+ }
6929
+ var init_runtimeCleanup = __esm({
6930
+ "src/runtimeCleanup.ts"() {
6931
+ "use strict";
6932
+ }
6933
+ });
6934
+
6815
6935
  // src/scripts/evaluateAgencyBoundaries.ts
6816
6936
  function shouldEvaluateAgencyBoundaries(data, profile) {
6817
6937
  return Boolean(agencyBoundaryCapabilityKind(data, profile));
@@ -7723,10 +7843,10 @@ var init_state2 = __esm({
7723
7843
  "use strict";
7724
7844
  VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
7725
7845
  GoalStateError = class extends Error {
7726
- constructor(path52, message) {
7727
- super(`Invalid goal state at ${path52}:
7846
+ constructor(path53, message) {
7847
+ super(`Invalid goal state at ${path53}:
7728
7848
  ${message}`);
7729
- this.path = path52;
7849
+ this.path = path53;
7730
7850
  this.name = "GoalStateError";
7731
7851
  }
7732
7852
  path;
@@ -7739,9 +7859,9 @@ import * as fs25 from "fs";
7739
7859
  function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
7740
7860
  const logs = goalRunLogs(data);
7741
7861
  const existing = logs[goalId];
7742
- const path52 = existing?.path ?? goalRunLogPath(goalId, data);
7862
+ const path53 = existing?.path ?? goalRunLogPath(goalId, data);
7743
7863
  logs[goalId] = {
7744
- path: path52,
7864
+ path: path53,
7745
7865
  events: [...existing?.events ?? [], buildGoalRunLogEvent(data, goalId, event, at)]
7746
7866
  };
7747
7867
  }
@@ -8563,7 +8683,7 @@ function buildGoalTargetInstance(template, targetId, now) {
8563
8683
  extra.template = targetId;
8564
8684
  extra.sourceTemplate = targetId;
8565
8685
  extra.templateId = targetId;
8566
- if (!isPlainObject2(extra.facts)) extra.facts = {};
8686
+ if (!isPlainObject3(extra.facts)) extra.facts = {};
8567
8687
  if (!Array.isArray(extra.blockers)) extra.blockers = [];
8568
8688
  const at = isoNoMs(now);
8569
8689
  return {
@@ -8573,7 +8693,7 @@ function buildGoalTargetInstance(template, targetId, now) {
8573
8693
  extra
8574
8694
  };
8575
8695
  }
8576
- function isPlainObject2(value) {
8696
+ function isPlainObject3(value) {
8577
8697
  return !!value && typeof value === "object" && !Array.isArray(value);
8578
8698
  }
8579
8699
  function assertSafeGoalId(value, label) {
@@ -8858,6 +8978,7 @@ function validateWorkflow(value, options = {}) {
8858
8978
  if (!step) return;
8859
8979
  const id = text(step.id);
8860
8980
  if (!id) return;
8981
+ const sourceCapability = text(step.capability ?? step.action);
8861
8982
  const transitions = transitionList(step.next);
8862
8983
  adjacency.set(id, []);
8863
8984
  if (transitions.length > maxTransitions) {
@@ -8933,7 +9054,10 @@ function validateWorkflow(value, options = {}) {
8933
9054
  if (raw.default === true && raw.when !== void 0) {
8934
9055
  issue(issues, "conflicting_transition", base, "workflow connection cannot be both conditional and default");
8935
9056
  }
8936
- if (raw.when !== void 0) validateDataMatch(raw.when, `${base}.when`, issues);
9057
+ if (raw.when !== void 0) {
9058
+ const outputPaths = options.capabilityOutputs?.get(sourceCapability ?? "");
9059
+ validateDataMatch(raw.when, `${base}.when`, issues, outputPaths);
9060
+ }
8937
9061
  const targetIndex = ids.indexOf(target ?? "");
8938
9062
  const iterations = raw.maxIterations;
8939
9063
  if (targetIndex >= 0 && targetIndex <= index) {
@@ -8978,11 +9102,11 @@ function validateWorkflow(value, options = {}) {
8978
9102
  function formatWorkflowValidationIssues(issues) {
8979
9103
  return issues.map((entry) => `${entry.path}: ${entry.message}`);
8980
9104
  }
8981
- function validateDataMatch(value, path52, issues) {
9105
+ function validateDataMatch(value, path53, issues, capabilityOutputs) {
8982
9106
  if (value === void 0) return;
8983
9107
  const match = asRecord2(value);
8984
9108
  if (!match || Object.keys(match).length === 0) {
8985
- issue(issues, "invalid_condition", path52, "workflow condition must contain at least one match");
9109
+ issue(issues, "invalid_condition", path53, "workflow condition must contain at least one match");
8986
9110
  return;
8987
9111
  }
8988
9112
  for (const [field, expected] of Object.entries(match)) {
@@ -8990,12 +9114,20 @@ function validateDataMatch(value, path52, issues) {
8990
9114
  issue(
8991
9115
  issues,
8992
9116
  "invalid_data_path",
8993
- `${path52}.${field}`,
9117
+ `${path53}.${field}`,
8994
9118
  `workflow condition must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
8995
9119
  );
8996
9120
  }
9121
+ if (capabilityOutputs && field.startsWith("result.") && !capabilityOutputs.has(field)) {
9122
+ issue(
9123
+ issues,
9124
+ "undeclared_result_path",
9125
+ `${path53}.${field}`,
9126
+ `workflow condition reads ${field}, but the source capability does not declare it`
9127
+ );
9128
+ }
8997
9129
  if (!isComparable(expected)) {
8998
- issue(issues, "invalid_condition_value", `${path52}.${field}`, "workflow condition value must be a JSON scalar");
9130
+ issue(issues, "invalid_condition_value", `${path53}.${field}`, "workflow condition value must be a JSON scalar");
8999
9131
  }
9000
9132
  }
9001
9133
  }
@@ -9013,8 +9145,8 @@ function isComparable(value) {
9013
9145
  if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
9014
9146
  return Array.isArray(value) && value.length > 0 && value.every((item) => isComparable(item) && !Array.isArray(item));
9015
9147
  }
9016
- function issue(issues, code, path52, message) {
9017
- issues.push({ code, path: path52, message });
9148
+ function issue(issues, code, path53, message) {
9149
+ issues.push({ code, path: path53, message });
9018
9150
  }
9019
9151
  var SAFE_NAME, SAFE_STEP_ID, SAFE_DATA_PATH, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
9020
9152
  var init_workflowValidation = __esm({
@@ -14452,13 +14584,13 @@ function companyIntentPath(id) {
14452
14584
  assertIntentId(id);
14453
14585
  return `intents/${id}/intent.json`;
14454
14586
  }
14455
- function normalizeCompanyIntent(path52, raw) {
14587
+ function normalizeCompanyIntent(path53, raw) {
14456
14588
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
14457
- throw new Error(`${path52}: intent must be JSON object`);
14589
+ throw new Error(`${path53}: intent must be JSON object`);
14458
14590
  }
14459
14591
  const input = raw;
14460
14592
  const id = stringField5(input.id);
14461
- if (!id || !isCompanyIntentId(id)) throw new Error(`${path52}: invalid intent id`);
14593
+ if (!id || !isCompanyIntentId(id)) throw new Error(`${path53}: invalid intent id`);
14462
14594
  const createdAt = stringField5(input.createdAt) || nowIso();
14463
14595
  const updatedAt = stringField5(input.updatedAt) || createdAt;
14464
14596
  const description = stringField5(input.description);
@@ -14498,8 +14630,8 @@ function listCompanyIntents(config, cwd) {
14498
14630
  const records = [];
14499
14631
  for (const entry of entries) {
14500
14632
  if (entry.type !== "dir" || !entry.name || !isCompanyIntentId(entry.name)) continue;
14501
- const path52 = companyIntentPath(entry.name);
14502
- const file = readStateText(config, cwd, path52);
14633
+ const path53 = companyIntentPath(entry.name);
14634
+ const file = readStateText(config, cwd, path53);
14503
14635
  if (!file) continue;
14504
14636
  records.push({
14505
14637
  id: entry.name,
@@ -16880,6 +17012,196 @@ var init_postResearchComment = __esm({
16880
17012
  }
16881
17013
  });
16882
17014
 
17015
+ // src/scripts/prepareBrowserAuth.ts
17016
+ import * as fs42 from "fs";
17017
+ import * as os7 from "os";
17018
+ import * as path40 from "path";
17019
+ function appendAuthMessage(ctx, message) {
17020
+ const current = typeof ctx.data.qaAuthBlock === "string" ? ctx.data.qaAuthBlock.trim() : "";
17021
+ ctx.data.qaAuthBlock = current ? `${current}
17022
+
17023
+ ${message}` : message;
17024
+ }
17025
+ function githubRepositoryParts(repoUrl) {
17026
+ const parsed = new URL(repoUrl);
17027
+ if (parsed.protocol !== "https:" || parsed.hostname.toLowerCase() !== "github.com") {
17028
+ throw new Error("repository must be an https://github.com/owner/repo URL");
17029
+ }
17030
+ const parts = parsed.pathname.split("/").filter(Boolean);
17031
+ if (parts.length !== 2) throw new Error("repository URL must contain exactly owner/repo");
17032
+ const repo = parts[1].replace(/\.git$/, "");
17033
+ if (!parts[0] || !repo) throw new Error("repository URL is incomplete");
17034
+ return { owner: parts[0], repo };
17035
+ }
17036
+ function browserOrigin(ctx) {
17037
+ const raw = typeof ctx.data.previewUrl === "string" ? ctx.data.previewUrl : "";
17038
+ if (!raw) throw new Error("QA target URL is unavailable");
17039
+ const parsed = new URL(raw);
17040
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
17041
+ throw new Error("QA target URL must use http or https");
17042
+ }
17043
+ return parsed.origin;
17044
+ }
17045
+ async function githubJson(url, token) {
17046
+ const response = await fetch(url, {
17047
+ headers: {
17048
+ Accept: "application/vnd.github+json",
17049
+ Authorization: `Bearer ${token}`,
17050
+ "X-GitHub-Api-Version": "2022-11-28"
17051
+ }
17052
+ });
17053
+ if (!response.ok) throw new Error(`GitHub returned ${response.status}`);
17054
+ return await response.json();
17055
+ }
17056
+ function writeKodyStorageState(input) {
17057
+ const directory = fs42.mkdtempSync(path40.join(os7.tmpdir(), "kody-browser-auth-"));
17058
+ fs42.chmodSync(directory, 448);
17059
+ const file = path40.join(directory, "storage-state.json");
17060
+ const now = Date.now();
17061
+ const repoEntry = {
17062
+ repoUrl: input.repoUrl,
17063
+ owner: input.owner,
17064
+ repo: input.repo,
17065
+ token: input.token,
17066
+ addedAt: now,
17067
+ isLogin: true,
17068
+ user: input.user
17069
+ };
17070
+ const auth = {
17071
+ repoUrl: input.repoUrl,
17072
+ owner: input.owner,
17073
+ repo: input.repo,
17074
+ token: input.token,
17075
+ user: input.user,
17076
+ loggedInAt: now,
17077
+ repos: [repoEntry],
17078
+ currentRepoIndex: 0
17079
+ };
17080
+ const storageState = {
17081
+ cookies: [],
17082
+ origins: [
17083
+ {
17084
+ origin: input.origin,
17085
+ localStorage: [{ name: "kody_auth", value: JSON.stringify(auth) }]
17086
+ }
17087
+ ]
17088
+ };
17089
+ fs42.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
17090
+ return { directory, file };
17091
+ }
17092
+ function configurePlaywright(profile, storageStatePath) {
17093
+ const playwright = profile.claudeCode.mcpServers.find((server) => server.name === "playwright");
17094
+ if (!playwright) throw new Error("Playwright MCP server is not configured");
17095
+ const args = [];
17096
+ const currentArgs = playwright.args ?? [];
17097
+ for (let index = 0; index < currentArgs.length; index += 1) {
17098
+ const arg = currentArgs[index];
17099
+ if (arg === "--storage-state") {
17100
+ index += 1;
17101
+ continue;
17102
+ }
17103
+ if (arg.startsWith("--storage-state=")) continue;
17104
+ args.push(arg);
17105
+ }
17106
+ if (!args.includes("--isolated")) args.push("--isolated");
17107
+ args.push("--storage-state", storageStatePath);
17108
+ playwright.args = args;
17109
+ }
17110
+ function fieldsForKodyRepository(method) {
17111
+ const variables = method.fields.filter((field) => field.source === "variable");
17112
+ const secrets = method.fields.filter((field) => field.source === "secret");
17113
+ if (variables.length !== 1 || secrets.length !== 1) {
17114
+ throw new Error("kody-repository auth requires one variable field and one secret field");
17115
+ }
17116
+ return { repositoryKey: variables[0].key, credentialKey: secrets[0].key };
17117
+ }
17118
+ async function prepareMethod(ctx, profile, method) {
17119
+ const { repositoryKey, credentialKey } = fieldsForKodyRepository(method);
17120
+ const variables = readKodyVariables(ctx.cwd);
17121
+ const repositoryUrl = variables[repositoryKey]?.trim() ?? "";
17122
+ const credential = await resolveRuntimeSecret(credentialKey, ctx);
17123
+ ctx.data.qaAuthSecretSources = {
17124
+ ...ctx.data.qaAuthSecretSources ?? {},
17125
+ [credentialKey]: credential.source
17126
+ };
17127
+ if (credential.warning) {
17128
+ const warnings = Array.isArray(ctx.data.qaAuthWarnings) ? ctx.data.qaAuthWarnings : [];
17129
+ ctx.data.qaAuthWarnings = [...warnings, credential.warning];
17130
+ }
17131
+ if (!repositoryUrl && !credential.value) return false;
17132
+ if (!repositoryUrl) {
17133
+ appendAuthMessage(
17134
+ ctx,
17135
+ `Auth: ${method.name} is incomplete because no \`${repositoryKey}\` variable was found. Note this authenticated surface as a gap.`
17136
+ );
17137
+ return false;
17138
+ }
17139
+ if (!credential.value) {
17140
+ appendAuthMessage(
17141
+ ctx,
17142
+ `Auth: ${method.name} is incomplete because no \`${credentialKey}\` secret was found. Note this authenticated surface as a gap.`
17143
+ );
17144
+ return false;
17145
+ }
17146
+ let state;
17147
+ try {
17148
+ const requested = githubRepositoryParts(repositoryUrl);
17149
+ const [user, repository] = await Promise.all([
17150
+ githubJson("https://api.github.com/user", credential.value),
17151
+ githubJson(
17152
+ `https://api.github.com/repos/${encodeURIComponent(requested.owner)}/${encodeURIComponent(requested.repo)}`,
17153
+ credential.value
17154
+ )
17155
+ ]);
17156
+ const [owner, repo] = repository.full_name.split("/");
17157
+ if (!owner || !repo || !user.login || !user.avatar_url || typeof user.id !== "number") {
17158
+ throw new Error("GitHub returned incomplete identity data");
17159
+ }
17160
+ state = writeKodyStorageState({
17161
+ origin: browserOrigin(ctx),
17162
+ repoUrl: `https://github.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,
17163
+ owner,
17164
+ repo,
17165
+ token: credential.value,
17166
+ user
17167
+ });
17168
+ configurePlaywright(profile, state.file);
17169
+ const authDirectory = state.directory;
17170
+ registerRuntimeCleanup(ctx, () => {
17171
+ fs42.rmSync(authDirectory, { recursive: true, force: true });
17172
+ });
17173
+ appendAuthMessage(
17174
+ ctx,
17175
+ `Auth: ${method.name} is already authenticated by the engine-provided browser session. The credential is not available to you; never request, reveal, or report it.`
17176
+ );
17177
+ return true;
17178
+ } catch (error) {
17179
+ if (state) fs42.rmSync(state.directory, { recursive: true, force: true });
17180
+ const reason = error instanceof Error ? error.message : String(error);
17181
+ appendAuthMessage(
17182
+ ctx,
17183
+ `Auth: the engine could not prepare ${method.name} (${reason}). Note this authenticated surface as a gap.`
17184
+ );
17185
+ return false;
17186
+ }
17187
+ }
17188
+ var prepareBrowserAuth;
17189
+ var init_prepareBrowserAuth = __esm({
17190
+ "src/scripts/prepareBrowserAuth.ts"() {
17191
+ "use strict";
17192
+ init_runtimeCleanup();
17193
+ init_kodyVariables();
17194
+ init_runtimeSecrets();
17195
+ prepareBrowserAuth = async (ctx, profile) => {
17196
+ const methods = profile.auth?.methods ?? [];
17197
+ for (const method of methods) {
17198
+ if (method.strategy !== "browser-storage-state" || method.adapter !== "kody-repository") continue;
17199
+ if (await prepareMethod(ctx, profile, method)) return;
17200
+ }
17201
+ };
17202
+ }
17203
+ });
17204
+
16883
17205
  // src/scripts/promoteQaGoal.ts
16884
17206
  var REPORT_JSON_OPEN2, promoteQaGoal;
16885
17207
  var init_promoteQaGoal = __esm({
@@ -16967,9 +17289,9 @@ function latestResult(raw, agentResult) {
16967
17289
  function recordField6(value) {
16968
17290
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
16969
17291
  }
16970
- function resolveDotted(root, path52) {
16971
- if (!path52) return void 0;
16972
- return path52.split(".").reduce((value, key) => recordField6(value)?.[key], root);
17292
+ function resolveDotted(root, path53) {
17293
+ if (!path53) return void 0;
17294
+ return path53.split(".").reduce((value, key) => recordField6(value)?.[key], root);
16973
17295
  }
16974
17296
  function stringValue4(value) {
16975
17297
  return typeof value === "string" && value.trim() ? value.trim() : null;
@@ -17948,12 +18270,12 @@ fi
17948
18270
 
17949
18271
  // src/scripts/runPreviewBuild.ts
17950
18272
  import { copyFile, writeFile } from "fs/promises";
17951
- import * as path40 from "path";
18273
+ import * as path41 from "path";
17952
18274
  import { fileURLToPath } from "url";
17953
18275
  function bundledDockerfilePath(mode) {
17954
- const here = path40.dirname(fileURLToPath(import.meta.url));
18276
+ const here = path41.dirname(fileURLToPath(import.meta.url));
17955
18277
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
17956
- return path40.join(here, "preview-build-templates", file);
18278
+ return path41.join(here, "preview-build-templates", file);
17957
18279
  }
17958
18280
  function required(name) {
17959
18281
  const v = (process.env[name] ?? "").trim();
@@ -18204,10 +18526,10 @@ var init_runPreviewBuild = __esm({
18204
18526
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
18205
18527
  if (Object.keys(buildEnv).length > 0) {
18206
18528
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
18207
- await writeFile(path40.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
18529
+ await writeFile(path41.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
18208
18530
  `, "utf8");
18209
18531
  }
18210
- const consumerDockerfile = path40.join(ctx.cwd, "Dockerfile.preview");
18532
+ const consumerDockerfile = path41.join(ctx.cwd, "Dockerfile.preview");
18211
18533
  const { stat } = await import("fs/promises");
18212
18534
  let hasConsumerDockerfile = false;
18213
18535
  try {
@@ -18391,8 +18713,8 @@ var init_tickShellRunner = __esm({
18391
18713
  });
18392
18714
 
18393
18715
  // src/scripts/runScheduledImplementationTick.ts
18394
- import * as fs42 from "fs";
18395
- import * as path41 from "path";
18716
+ import * as fs43 from "fs";
18717
+ import * as path42 from "path";
18396
18718
  var runScheduledImplementationTick;
18397
18719
  var init_runScheduledImplementationTick = __esm({
18398
18720
  "src/scripts/runScheduledImplementationTick.ts"() {
@@ -18412,14 +18734,14 @@ var init_runScheduledImplementationTick = __esm({
18412
18734
  ctx.output.reason = `runScheduledImplementationTick: args.slug or ctx.args.${slugArg} must be non-empty capability slug`;
18413
18735
  return;
18414
18736
  }
18415
- const capability = resolveCapabilityFolder(slug, path41.join(ctx.cwd, jobsDir));
18737
+ const capability = resolveCapabilityFolder(slug, path42.join(ctx.cwd, jobsDir));
18416
18738
  if (!capability) {
18417
18739
  ctx.output.exitCode = 99;
18418
18740
  ctx.output.reason = `runScheduledImplementationTick: capability folder not found or incomplete: ${slug} (searched ${jobsDir} and company store)`;
18419
18741
  return;
18420
18742
  }
18421
- const shellPath = path41.join(profile.dir, shell);
18422
- if (!fs42.existsSync(shellPath)) {
18743
+ const shellPath = path42.join(profile.dir, shell);
18744
+ if (!fs43.existsSync(shellPath)) {
18423
18745
  ctx.output.exitCode = 99;
18424
18746
  ctx.output.reason = `runScheduledImplementationTick: shell not found: ${shell} (looked in ${profile.dir})`;
18425
18747
  return;
@@ -18450,8 +18772,8 @@ var init_runScheduledImplementationTick = __esm({
18450
18772
  });
18451
18773
 
18452
18774
  // src/scripts/runTickScript.ts
18453
- import * as fs43 from "fs";
18454
- import * as path42 from "path";
18775
+ import * as fs44 from "fs";
18776
+ import * as path43 from "path";
18455
18777
  var runTickScript;
18456
18778
  var init_runTickScript = __esm({
18457
18779
  "src/scripts/runTickScript.ts"() {
@@ -18470,10 +18792,10 @@ var init_runTickScript = __esm({
18470
18792
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
18471
18793
  return;
18472
18794
  }
18473
- const capability = readCapabilityFolder(path42.join(ctx.cwd, jobsDir), slug);
18795
+ const capability = readCapabilityFolder(path43.join(ctx.cwd, jobsDir), slug);
18474
18796
  if (!capability) {
18475
18797
  ctx.output.exitCode = 99;
18476
- ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path42.join(ctx.cwd, jobsDir, slug)}`;
18798
+ ctx.output.reason = `runTickScript: capability folder not found or incomplete: ${path43.join(ctx.cwd, jobsDir, slug)}`;
18477
18799
  return;
18478
18800
  }
18479
18801
  const tickScript = capability.config.tickScript;
@@ -18482,8 +18804,8 @@ var init_runTickScript = __esm({
18482
18804
  ctx.output.reason = `runTickScript: capability ${slug} has no \`tickScript\` in profile.json \u2014 route via capability-tick instead`;
18483
18805
  return;
18484
18806
  }
18485
- const scriptPath = path42.isAbsolute(tickScript) ? tickScript : path42.join(ctx.cwd, tickScript);
18486
- if (!fs43.existsSync(scriptPath)) {
18807
+ const scriptPath = path43.isAbsolute(tickScript) ? tickScript : path43.join(ctx.cwd, tickScript);
18808
+ if (!fs44.existsSync(scriptPath)) {
18487
18809
  ctx.output.exitCode = 99;
18488
18810
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
18489
18811
  return;
@@ -18765,7 +19087,7 @@ var init_syncFlow = __esm({
18765
19087
  });
18766
19088
 
18767
19089
  // src/scripts/validateAgencyModelProposal.ts
18768
- import * as path43 from "path";
19090
+ import * as path44 from "path";
18769
19091
  function validateModelBundle(bundle, expectedKind, options = {}) {
18770
19092
  const failures = [];
18771
19093
  validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind, options);
@@ -18862,8 +19184,11 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures, op
18862
19184
  const known = options.capabilityRoot ? getCapabilityRoots(options.capabilityRoot).flatMap((root) => listCapabilityFolderSlugs(root)) : [];
18863
19185
  const uniqueKnown = [...new Set(known)];
18864
19186
  const capabilityInputs = /* @__PURE__ */ new Map();
19187
+ const capabilityOutputs = /* @__PURE__ */ new Map();
18865
19188
  if (options.capabilityRoot) {
18866
19189
  for (const capability of uniqueKnown) {
19190
+ const folder = getCapabilityRoots(options.capabilityRoot).map((root) => readCapabilityFolder(root, capability)).find((entry) => entry !== null);
19191
+ capabilityOutputs.set(capability, folder ? capabilityOutputConditionPaths(folder.config) : /* @__PURE__ */ new Set());
18867
19192
  const inputs = getCapabilityActionInputs(capability, options.capabilityRoot);
18868
19193
  if (inputs) {
18869
19194
  capabilityInputs.set(
@@ -18877,7 +19202,8 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures, op
18877
19202
  ...formatWorkflowValidationIssues(
18878
19203
  validateWorkflow(workflow, {
18879
19204
  ...uniqueKnown.length > 0 ? { knownCapabilities: new Set(uniqueKnown) } : {},
18880
- ...capabilityInputs.size > 0 ? { capabilityInputs } : {}
19205
+ ...capabilityInputs.size > 0 ? { capabilityInputs } : {},
19206
+ ...capabilityOutputs.size > 0 ? { capabilityOutputs } : {}
18881
19207
  })
18882
19208
  )
18883
19209
  );
@@ -19087,7 +19413,7 @@ var init_validateAgencyModelProposal = __esm({
19087
19413
  const bundle = parseAgencyModelProposal(raw);
19088
19414
  const expectedKind = readExpectedModelKind(args);
19089
19415
  const failures = validateModelBundle(bundle, expectedKind, {
19090
- capabilityRoot: path43.join(ctx.cwd, ".kody", "capabilities")
19416
+ capabilityRoot: path44.join(ctx.cwd, ".kody", "capabilities")
19091
19417
  });
19092
19418
  if (failures.length > 0) {
19093
19419
  throw new Error(`validateAgencyModelProposal: ${failures.join("; ")}`);
@@ -19638,7 +19964,7 @@ var init_warmupMcp = __esm({
19638
19964
  });
19639
19965
 
19640
19966
  // src/scripts/writeAgentRunSummary.ts
19641
- import * as fs44 from "fs";
19967
+ import * as fs45 from "fs";
19642
19968
  var writeAgentRunSummary;
19643
19969
  var init_writeAgentRunSummary = __esm({
19644
19970
  "src/scripts/writeAgentRunSummary.ts"() {
@@ -19664,7 +19990,7 @@ var init_writeAgentRunSummary = __esm({
19664
19990
  if (reason) lines.push(`- **Reason:** ${reason}`);
19665
19991
  lines.push("");
19666
19992
  try {
19667
- fs44.appendFileSync(summaryPath, `${lines.join("\n")}
19993
+ fs45.appendFileSync(summaryPath, `${lines.join("\n")}
19668
19994
  `);
19669
19995
  } catch {
19670
19996
  }
@@ -19843,6 +20169,7 @@ var init_scripts = __esm({
19843
20169
  init_postPlanComment();
19844
20170
  init_postResearchComment();
19845
20171
  init_postReviewResult();
20172
+ init_prepareBrowserAuth();
19846
20173
  init_promoteQaGoal();
19847
20174
  init_publishReport();
19848
20175
  init_recordClassification();
@@ -19901,6 +20228,7 @@ var init_scripts = __esm({
19901
20228
  loadMemoryContext,
19902
20229
  loadPriorArt,
19903
20230
  loadQaContext,
20231
+ prepareBrowserAuth,
19904
20232
  buildSyntheticPlugin,
19905
20233
  resolveArtifacts,
19906
20234
  discoverQaContext,
@@ -19987,53 +20315,53 @@ var init_scripts = __esm({
19987
20315
  // src/stateWorkspace.ts
19988
20316
  import { execFileSync as execFileSync24 } from "child_process";
19989
20317
  import * as crypto3 from "crypto";
19990
- import * as fs45 from "fs";
19991
- import * as os7 from "os";
19992
- import * as path44 from "path";
20318
+ import * as fs46 from "fs";
20319
+ import * as os8 from "os";
20320
+ import * as path45 from "path";
19993
20321
  function writeLocalFile(cwd, relativePath, content) {
19994
- const fullPath = path44.join(cwd, relativePath);
19995
- fs45.mkdirSync(path44.dirname(fullPath), { recursive: true });
19996
- fs45.writeFileSync(fullPath, content);
20322
+ const fullPath = path45.join(cwd, relativePath);
20323
+ fs46.mkdirSync(path45.dirname(fullPath), { recursive: true });
20324
+ fs46.writeFileSync(fullPath, content);
19997
20325
  }
19998
20326
  function copyPath(source, target) {
19999
- const st = fs45.lstatSync(source);
20000
- fs45.rmSync(target, { recursive: true, force: true });
20327
+ const st = fs46.lstatSync(source);
20328
+ fs46.rmSync(target, { recursive: true, force: true });
20001
20329
  if (st.isSymbolicLink()) return;
20002
- fs45.mkdirSync(path44.dirname(target), { recursive: true });
20003
- fs45.cpSync(source, target, { recursive: true, force: true });
20330
+ fs46.mkdirSync(path45.dirname(target), { recursive: true });
20331
+ fs46.cpSync(source, target, { recursive: true, force: true });
20004
20332
  }
20005
20333
  function overlayDirectoryChildren(cwd, sourceDir, localDir) {
20006
- if (!fs45.existsSync(sourceDir)) return;
20007
- for (const entry of fs45.readdirSync(sourceDir, { withFileTypes: true })) {
20008
- const source = path44.join(sourceDir, entry.name);
20009
- const target = path44.join(cwd, localDir, entry.name);
20334
+ if (!fs46.existsSync(sourceDir)) return;
20335
+ for (const entry of fs46.readdirSync(sourceDir, { withFileTypes: true })) {
20336
+ const source = path45.join(sourceDir, entry.name);
20337
+ const target = path45.join(cwd, localDir, entry.name);
20010
20338
  copyPath(source, target);
20011
20339
  }
20012
20340
  }
20013
20341
  function hydrateStateWorkspace(config, cwd) {
20014
20342
  if (process.env.VITEST && process.env[TEST_FETCH_ENV] !== "1") return;
20015
20343
  const parsed = parseStateRepo(config);
20016
- const hydrateKey = `${path44.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
20344
+ const hydrateKey = `${path45.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
20017
20345
  if (hydratedWorkspaces.has(hydrateKey)) return;
20018
20346
  const snapshotRoot = fetchStateSnapshot(parsed);
20019
20347
  for (const mapping of DIR_MAPPINGS) {
20020
- overlayDirectoryChildren(cwd, path44.join(snapshotRoot, mapping.stateDir), mapping.localDir);
20348
+ overlayDirectoryChildren(cwd, path45.join(snapshotRoot, mapping.stateDir), mapping.localDir);
20021
20349
  }
20022
20350
  for (const mapping of FILE_MAPPINGS) {
20023
- const source = path44.join(snapshotRoot, mapping.statePath);
20024
- if (fs45.existsSync(source) && !fs45.lstatSync(source).isSymbolicLink() && fs45.statSync(source).isFile()) {
20025
- writeLocalFile(cwd, mapping.localPath, fs45.readFileSync(source, "utf-8"));
20351
+ const source = path45.join(snapshotRoot, mapping.statePath);
20352
+ if (fs46.existsSync(source) && !fs46.lstatSync(source).isSymbolicLink() && fs46.statSync(source).isFile()) {
20353
+ writeLocalFile(cwd, mapping.localPath, fs46.readFileSync(source, "utf-8"));
20026
20354
  }
20027
20355
  }
20028
20356
  hydratedWorkspaces.add(hydrateKey);
20029
20357
  }
20030
20358
  function fetchStateSnapshot(parsed) {
20031
- const cacheDir = path44.join(cacheRoot2(), cacheKey3(parsed));
20359
+ const cacheDir = path45.join(cacheRoot2(), cacheKey3(parsed));
20032
20360
  const url = `https://github.com/${parsed.owner}/${parsed.repo}.git`;
20033
20361
  try {
20034
- fs45.mkdirSync(path44.dirname(cacheDir), { recursive: true });
20035
- if (!fs45.existsSync(path44.join(cacheDir, ".git"))) {
20036
- fs45.rmSync(cacheDir, { recursive: true, force: true });
20362
+ fs46.mkdirSync(path45.dirname(cacheDir), { recursive: true });
20363
+ if (!fs46.existsSync(path45.join(cacheDir, ".git"))) {
20364
+ fs46.rmSync(cacheDir, { recursive: true, force: true });
20037
20365
  runGit3(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
20038
20366
  }
20039
20367
  runGit3(["-C", cacheDir, "remote", "set-url", "origin", url]);
@@ -20048,10 +20376,10 @@ function fetchStateSnapshot(parsed) {
20048
20376
  `stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${parsed.branch}: ${msg}`
20049
20377
  );
20050
20378
  }
20051
- return path44.join(cacheDir, parsed.basePath);
20379
+ return path45.join(cacheDir, parsed.basePath);
20052
20380
  }
20053
20381
  function cacheRoot2() {
20054
- return process.env[CACHE_ENV2]?.trim() || path44.join(os7.homedir(), ".cache", "kody", "state-repo");
20382
+ return process.env[CACHE_ENV2]?.trim() || path45.join(os8.homedir(), ".cache", "kody", "state-repo");
20055
20383
  }
20056
20384
  function cacheKey3(parsed) {
20057
20385
  return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${parsed.branch}#${parsed.basePath}`).digest("hex").slice(0, 24);
@@ -20091,16 +20419,16 @@ var init_stateWorkspace = __esm({
20091
20419
  "use strict";
20092
20420
  init_stateRepo();
20093
20421
  DIR_MAPPINGS = [
20094
- { stateDir: "capabilities", localDir: path44.join(".kody", "capabilities") },
20095
- { stateDir: "agents", localDir: path44.join(".kody", "agents") },
20096
- { stateDir: "context", localDir: path44.join(".kody", "context") },
20097
- { stateDir: "memory", localDir: path44.join(".kody", "memory") }
20422
+ { stateDir: "capabilities", localDir: path45.join(".kody", "capabilities") },
20423
+ { stateDir: "agents", localDir: path45.join(".kody", "agents") },
20424
+ { stateDir: "context", localDir: path45.join(".kody", "context") },
20425
+ { stateDir: "memory", localDir: path45.join(".kody", "memory") }
20098
20426
  ];
20099
20427
  FILE_MAPPINGS = [
20100
- { statePath: "instructions.md", localPath: path44.join(".kody", "instructions.md") },
20101
- { statePath: "system-prompt.md", localPath: path44.join(".kody", "system-prompt.md") },
20102
- { statePath: "variables.json", localPath: path44.join(".kody", "variables.json") },
20103
- { statePath: "secrets.enc", localPath: path44.join(".kody", "secrets.enc") }
20428
+ { statePath: "instructions.md", localPath: path45.join(".kody", "instructions.md") },
20429
+ { statePath: "system-prompt.md", localPath: path45.join(".kody", "system-prompt.md") },
20430
+ { statePath: "variables.json", localPath: path45.join(".kody", "variables.json") },
20431
+ { statePath: "secrets.enc", localPath: path45.join(".kody", "secrets.enc") }
20104
20432
  ];
20105
20433
  CACHE_ENV2 = "KODY_STATE_REPO_CACHE";
20106
20434
  TEST_FETCH_ENV = "KODY_STATE_WORKSPACE_FETCH_FOR_TESTS";
@@ -20174,9 +20502,9 @@ var init_tools = __esm({
20174
20502
 
20175
20503
  // src/executor.ts
20176
20504
  import { spawn as spawn7 } from "child_process";
20177
- import * as fs46 from "fs";
20178
- import * as os8 from "os";
20179
- import * as path45 from "path";
20505
+ import * as fs47 from "fs";
20506
+ import * as os9 from "os";
20507
+ import * as path46 from "path";
20180
20508
  function isMutatingPostflight(scriptName) {
20181
20509
  return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
20182
20510
  }
@@ -20399,7 +20727,7 @@ async function runImplementation(profileName, input) {
20399
20727
  const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
20400
20728
  const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
20401
20729
  const invokeAgent = async (prompt) => {
20402
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path45.isAbsolute(p) ? p : path45.resolve(profile.dir, p)).filter((p) => p.length > 0);
20730
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path46.isAbsolute(p) ? p : path46.resolve(profile.dir, p)).filter((p) => p.length > 0);
20403
20731
  const syntheticPath = ctx.data.syntheticPluginPath;
20404
20732
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
20405
20733
  const agents = loadSubagents(profile);
@@ -20655,6 +20983,7 @@ async function runImplementation(profileName, input) {
20655
20983
  const msg = err instanceof Error ? err.message : String(err);
20656
20984
  return finishAndEnd({ exitCode: 99, reason: ctx.output.reason ?? msg });
20657
20985
  } finally {
20986
+ runRuntimeCleanup(ctx);
20658
20987
  clearStampedLifecycleLabels(profile, ctx);
20659
20988
  if (taskArtifacts) {
20660
20989
  try {
@@ -20837,17 +21166,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
20837
21166
  function resolveProfilePath(profileName) {
20838
21167
  const found = resolveImplementation(profileName);
20839
21168
  if (found) return found;
20840
- const here = path45.dirname(new URL(import.meta.url).pathname);
21169
+ const here = path46.dirname(new URL(import.meta.url).pathname);
20841
21170
  const candidates = [
20842
- path45.join(here, "implementations", profileName, "profile.json"),
21171
+ path46.join(here, "implementations", profileName, "profile.json"),
20843
21172
  // same-dir sibling (dev)
20844
- path45.join(here, "..", "implementations", profileName, "profile.json"),
21173
+ path46.join(here, "..", "implementations", profileName, "profile.json"),
20845
21174
  // up one (prod: dist/bin → dist/implementations)
20846
- path45.join(here, "..", "src", "implementations", profileName, "profile.json")
21175
+ path46.join(here, "..", "src", "implementations", profileName, "profile.json")
20847
21176
  // fallback
20848
21177
  ];
20849
21178
  for (const c of candidates) {
20850
- if (fs46.existsSync(c)) return c;
21179
+ if (fs47.existsSync(c)) return c;
20851
21180
  }
20852
21181
  return candidates[0];
20853
21182
  }
@@ -20962,16 +21291,16 @@ function resolveShellTimeoutMs(entry) {
20962
21291
  }
20963
21292
  async function runShellEntry(entry, ctx, profile) {
20964
21293
  const shellName = entry.shell;
20965
- const shellPath = path45.join(profile.dir, shellName);
20966
- if (!fs46.existsSync(shellPath)) {
21294
+ const shellPath = path46.join(profile.dir, shellName);
21295
+ if (!fs47.existsSync(shellPath)) {
20967
21296
  ctx.skipAgent = true;
20968
21297
  ctx.output.exitCode = 99;
20969
21298
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
20970
21299
  return;
20971
21300
  }
20972
21301
  const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
20973
- const outputFile = path45.join(
20974
- os8.tmpdir(),
21302
+ const outputFile = path46.join(
21303
+ os9.tmpdir(),
20975
21304
  `kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
20976
21305
  );
20977
21306
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", KODY_OUTPUT: outputFile };
@@ -21042,9 +21371,9 @@ async function runShellEntry(entry, ctx, profile) {
21042
21371
  }
21043
21372
  let sideChannelText = "";
21044
21373
  try {
21045
- if (fs46.existsSync(outputFile)) {
21046
- sideChannelText = fs46.readFileSync(outputFile, "utf-8");
21047
- fs46.rmSync(outputFile, { force: true });
21374
+ if (fs47.existsSync(outputFile)) {
21375
+ sideChannelText = fs47.readFileSync(outputFile, "utf-8");
21376
+ fs47.rmSync(outputFile, { force: true });
21048
21377
  }
21049
21378
  } catch {
21050
21379
  }
@@ -21117,6 +21446,7 @@ var init_executor = __esm({
21117
21446
  init_profile();
21118
21447
  init_registry();
21119
21448
  init_runIndex();
21449
+ init_runtimeCleanup();
21120
21450
  init_runtimePaths();
21121
21451
  init_evaluateAgencyBoundaries();
21122
21452
  init_scripts();
@@ -21181,11 +21511,11 @@ function readWorkflowRunState(config, cwd, workflowId, runId) {
21181
21511
  }
21182
21512
  }
21183
21513
  function writeWorkflowRunState(config, cwd, workflowId, runId, state) {
21184
- const path52 = workflowRunStatePath(workflowId, runId);
21514
+ const path53 = workflowRunStatePath(workflowId, runId);
21185
21515
  upsertStateText(
21186
21516
  config,
21187
21517
  cwd,
21188
- path52,
21518
+ path53,
21189
21519
  `${JSON.stringify(state, null, 2)}
21190
21520
  `,
21191
21521
  `chore(workflows): update ${workflowId} run ${runId}`
@@ -21212,7 +21542,7 @@ __export(job_exports, {
21212
21542
  stableJobKey: () => stableJobKey,
21213
21543
  validateJob: () => validateJob
21214
21544
  });
21215
- import * as path46 from "path";
21545
+ import * as path47 from "path";
21216
21546
  function newJobId(flavor) {
21217
21547
  localJobSeq += 1;
21218
21548
  const runId = process.env.GITHUB_RUN_ID;
@@ -21280,7 +21610,7 @@ function parseJobEvidence(job) {
21280
21610
  async function runJob(job, base) {
21281
21611
  const valid = validateJob(job);
21282
21612
  const action = valid.action ?? valid.capability;
21283
- const projectCapabilitiesRoot = path46.join(base.cwd, ".kody", "capabilities");
21613
+ const projectCapabilitiesRoot = path47.join(base.cwd, ".kody", "capabilities");
21284
21614
  const resolvedCapability = !valid.workflow && action ? resolveCapabilityAction(action, projectCapabilitiesRoot) : null;
21285
21615
  const capabilityIdentity = valid.capability ?? resolvedCapability?.capability;
21286
21616
  const capabilityContext = valid.workflow ? null : loadCapabilityContext(capabilityIdentity, base.cwd);
@@ -21471,15 +21801,20 @@ function isGraphWorkflow(workflow) {
21471
21801
  return workflow.startAt !== void 0 || workflow.steps.some((step) => step.id !== void 0 || step.next !== void 0 || step.inputs !== void 0);
21472
21802
  }
21473
21803
  function workflowError(workflow, base) {
21474
- const projectCapabilitiesRoot = path46.join(base.cwd, ".kody", "capabilities");
21804
+ const projectCapabilitiesRoot = path47.join(base.cwd, ".kody", "capabilities");
21475
21805
  const knownCapabilities = /* @__PURE__ */ new Set();
21476
21806
  const capabilityInputs = /* @__PURE__ */ new Map();
21807
+ const capabilityOutputs = /* @__PURE__ */ new Map();
21477
21808
  for (const step of workflow.steps) {
21478
21809
  const action = step.action ?? step.capability;
21479
21810
  const resolvedAction = resolveCapabilityAction(action, projectCapabilitiesRoot);
21480
21811
  const resolvedFolder = resolveCapabilityFolder(step.capability, projectCapabilitiesRoot);
21481
21812
  if (!resolvedAction && !resolvedFolder) continue;
21482
21813
  knownCapabilities.add(step.capability);
21814
+ capabilityOutputs.set(
21815
+ step.capability,
21816
+ resolvedFolder ? capabilityOutputConditionPaths(resolvedFolder.config) : /* @__PURE__ */ new Set()
21817
+ );
21483
21818
  const inputs = getCapabilityActionInputs(action, projectCapabilitiesRoot);
21484
21819
  if (inputs) {
21485
21820
  capabilityInputs.set(
@@ -21488,7 +21823,9 @@ function workflowError(workflow, base) {
21488
21823
  );
21489
21824
  }
21490
21825
  }
21491
- return formatWorkflowValidationIssues(validateWorkflow(workflow, { knownCapabilities, capabilityInputs }))[0] ?? null;
21826
+ return formatWorkflowValidationIssues(
21827
+ validateWorkflow(workflow, { knownCapabilities, capabilityInputs, capabilityOutputs })
21828
+ )[0] ?? null;
21492
21829
  }
21493
21830
  function initialWorkflowState(parent, workflow) {
21494
21831
  const prior = parent.workflowState;
@@ -21611,6 +21948,14 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
21611
21948
  checkpoint?.(state);
21612
21949
  return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
21613
21950
  }
21951
+ const resultConditionPaths = workflowResultConditionPaths(step.next);
21952
+ if (resultConditionPaths.length > 0 && !result.capabilityResults?.at(-1)) {
21953
+ const reason = `workflow step ${step.id} did not emit the structured result required by its conditions: ${resultConditionPaths.join(", ")}`;
21954
+ state.status = "blocked";
21955
+ state.blocker = reason;
21956
+ checkpoint?.(state);
21957
+ return { ...result, exitCode: 64, reason, workflowState: state };
21958
+ }
21614
21959
  const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
21615
21960
  if (!transition) {
21616
21961
  const reason = `workflow step ${step.id} has no available connection`;
@@ -21658,8 +22003,13 @@ function selectWorkflowTransition(step, data, counts) {
21658
22003
  }
21659
22004
  return fallback;
21660
22005
  }
22006
+ function workflowResultConditionPaths(transitions) {
22007
+ return transitions.flatMap(
22008
+ (transition) => Object.keys(transition.when ?? {}).filter((path53) => path53.startsWith("result."))
22009
+ );
22010
+ }
21661
22011
  function conditionMatches(condition, context) {
21662
- return Object.entries(condition).every(([path52, expected]) => valueMatches(resolveDottedPath2(context, path52), expected));
22012
+ return Object.entries(condition).every(([path53, expected]) => valueMatches(resolveDottedPath2(context, path53), expected));
21663
22013
  }
21664
22014
  function withWorkflowBoundaryEval(capability, result) {
21665
22015
  const capabilityKind = capability.config.capabilityKind;
@@ -21755,14 +22105,10 @@ function workflowConditionContext(data) {
21755
22105
  };
21756
22106
  }
21757
22107
  function resolveDottedPath2(root, dotted) {
21758
- const direct = dotted.split(".").reduce((cur, part) => {
22108
+ return dotted.split(".").reduce((cur, part) => {
21759
22109
  if (!cur || typeof cur !== "object") return void 0;
21760
22110
  return cur[part];
21761
22111
  }, root);
21762
- if (direct !== void 0 || !dotted.startsWith("result.")) return direct;
21763
- const result = resolveDottedPath2(root, "result");
21764
- if (!result || typeof result !== "object" || Array.isArray(result)) return direct;
21765
- return resolveDottedPath2(result.facts, dotted.slice("result.".length));
21766
22112
  }
21767
22113
  function valueMatches(actual, expected) {
21768
22114
  if (Array.isArray(expected)) return expected.some((entry) => valueMatches(actual, entry));
@@ -21818,7 +22164,7 @@ function composeStepWhy(parentWhy, step) {
21818
22164
  }
21819
22165
  function loadCapabilityContext(slug, cwd) {
21820
22166
  if (!slug) return null;
21821
- return resolveCapabilityFolder(slug, path46.join(cwd, ".kody", "capabilities"));
22167
+ return resolveCapabilityFolder(slug, path47.join(cwd, ".kody", "capabilities"));
21822
22168
  }
21823
22169
  function loadWorkflowContext(slug, base) {
21824
22170
  if (!slug || !base.config || !isWorkflowDefinitionId(slug)) return null;
@@ -21854,6 +22200,7 @@ var init_job = __esm({
21854
22200
  "src/job.ts"() {
21855
22201
  "use strict";
21856
22202
  init_agencyBoundaryEval();
22203
+ init_capabilityFolders();
21857
22204
  init_executor();
21858
22205
  init_registry();
21859
22206
  init_workflowDefinitions();
@@ -21979,9 +22326,9 @@ function translateOpenAISseToBrain(opts) {
21979
22326
  }
21980
22327
 
21981
22328
  // src/servers/brain-serve.ts
21982
- import * as fs49 from "fs";
22329
+ import * as fs50 from "fs";
21983
22330
  import { createServer as createServer2 } from "http";
21984
- import * as path49 from "path";
22331
+ import * as path50 from "path";
21985
22332
 
21986
22333
  // src/chat/loop.ts
21987
22334
  init_agent();
@@ -22117,6 +22464,61 @@ function makeRunId(sessionId, suffix) {
22117
22464
  return `chat-${sessionId}-${suffix}`;
22118
22465
  }
22119
22466
 
22467
+ // src/chat/session-store.ts
22468
+ import { anyApi } from "convex/server";
22469
+
22470
+ // src/chat/convex-client.ts
22471
+ import { ConvexHttpClient } from "convex/browser";
22472
+ var ESCAPE_CHAR = "~";
22473
+ var NEEDS_ESCAPE = /^[$_~]/;
22474
+ function isPlainObject2(value) {
22475
+ if (value === null || typeof value !== "object") return false;
22476
+ const proto = Object.getPrototypeOf(value);
22477
+ return proto === Object.prototype || proto === null;
22478
+ }
22479
+ function deepMapKeys(value, mapKey) {
22480
+ if (Array.isArray(value)) return value.map((item) => deepMapKeys(item, mapKey));
22481
+ if (isPlainObject2(value)) {
22482
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [mapKey(key), deepMapKeys(item, mapKey)]));
22483
+ }
22484
+ return value;
22485
+ }
22486
+ function deepEscapeKeys(value) {
22487
+ return deepMapKeys(value, (k) => NEEDS_ESCAPE.test(k) ? `${ESCAPE_CHAR}${k}` : k);
22488
+ }
22489
+ function deepUnescapeKeys(value) {
22490
+ return deepMapKeys(value, (k) => k.startsWith(ESCAPE_CHAR) ? k.slice(1) : k);
22491
+ }
22492
+ var CALL_METHODS = ["query", "mutation", "action"];
22493
+ function injectServiceKey(args) {
22494
+ const serviceKey = process.env.KODY_SERVICE_KEY;
22495
+ if (!serviceKey) return args;
22496
+ if (args === void 0) return { serviceKey };
22497
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return args;
22498
+ return { ...args, serviceKey };
22499
+ }
22500
+ function withEscapedKeys(client) {
22501
+ return new Proxy(client, {
22502
+ get(target, prop, receiver) {
22503
+ if (CALL_METHODS.includes(prop)) {
22504
+ const method = Reflect.get(target, prop, target);
22505
+ return async (fn, args) => {
22506
+ const authed = injectServiceKey(args);
22507
+ const result = await method.call(target, fn, authed === void 0 ? void 0 : deepEscapeKeys(authed));
22508
+ return deepUnescapeKeys(result);
22509
+ };
22510
+ }
22511
+ const value = Reflect.get(target, prop, receiver);
22512
+ return typeof value === "function" ? value.bind(target) : value;
22513
+ }
22514
+ });
22515
+ }
22516
+ function createConvexClientFromEnv(env = process.env) {
22517
+ const url = env.CONVEX_URL?.trim();
22518
+ if (!url) return null;
22519
+ return withEscapedKeys(new ConvexHttpClient(url));
22520
+ }
22521
+
22120
22522
  // src/chat/session.ts
22121
22523
  import * as fs13 from "fs";
22122
22524
  import * as path15 from "path";
@@ -22182,6 +22584,87 @@ function seedInitialMessage(file, message) {
22182
22584
  return true;
22183
22585
  }
22184
22586
 
22587
+ // src/chat/session-store.ts
22588
+ function isChatTurn(value) {
22589
+ if (!value || typeof value !== "object") return false;
22590
+ const t = value;
22591
+ return (t.role === "user" || t.role === "assistant") && typeof t.content === "string";
22592
+ }
22593
+ function createSessionStore(opts) {
22594
+ const logger = opts.logger ?? {
22595
+ info: (m) => process.stdout.write(`[kody:chat:store] ${m}
22596
+ `),
22597
+ warn: (m) => process.stderr.write(`[kody:chat:store] ${m}
22598
+ `)
22599
+ };
22600
+ const client = opts.client !== void 0 ? opts.client : createConvexClientFromEnv();
22601
+ const tenantId = opts.tenantId ?? process.env.GITHUB_REPOSITORY ?? "";
22602
+ if (client && tenantId) {
22603
+ logger.info(`session ${opts.sessionId}: using Convex transcript store (tenant ${tenantId})`);
22604
+ return createConvexStore({
22605
+ client,
22606
+ tenantId,
22607
+ sessionId: opts.sessionId,
22608
+ sessionFile: opts.sessionFile,
22609
+ logger
22610
+ });
22611
+ }
22612
+ if (client && !tenantId) {
22613
+ logger.warn(`session ${opts.sessionId}: CONVEX_URL set but no tenant (GITHUB_REPOSITORY unset) \u2014 using JSONL`);
22614
+ } else {
22615
+ logger.info(`session ${opts.sessionId}: CONVEX_URL unset \u2014 using legacy state-repo JSONL store`);
22616
+ }
22617
+ return createJsonlStore(opts.sessionFile);
22618
+ }
22619
+ function createJsonlStore(sessionFile) {
22620
+ return {
22621
+ backend: "jsonl",
22622
+ readTurns: async () => readSession(sessionFile),
22623
+ appendTurn: async (turn) => {
22624
+ appendTurn(sessionFile, turn);
22625
+ }
22626
+ };
22627
+ }
22628
+ function createConvexStore(args) {
22629
+ const { client, tenantId, sessionId, sessionFile, logger } = args;
22630
+ let sessionUpserted = false;
22631
+ return {
22632
+ backend: "convex",
22633
+ readTurns: async () => {
22634
+ const docs = await client.query(anyApi.chatTurns.list, { tenantId, sessionId });
22635
+ return [...docs].sort((a, b) => a.seq - b.seq).map((doc) => doc.turn).filter(isChatTurn);
22636
+ },
22637
+ appendTurn: async (turn) => {
22638
+ const normalized = {
22639
+ role: turn.role,
22640
+ content: turn.content,
22641
+ timestamp: turn.timestamp,
22642
+ toolCalls: turn.toolCalls ?? []
22643
+ };
22644
+ if (!sessionUpserted) {
22645
+ try {
22646
+ const meta = readMeta(sessionFile) ?? { type: "meta", mode: "one-shot" };
22647
+ await client.mutation(anyApi.chatSessions.upsert, {
22648
+ tenantId,
22649
+ sessionId,
22650
+ meta,
22651
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
22652
+ });
22653
+ sessionUpserted = true;
22654
+ } catch (err) {
22655
+ logger.warn(`session ${sessionId}: chatSessions.upsert failed: ${err instanceof Error ? err.message : String(err)}`);
22656
+ }
22657
+ }
22658
+ await client.mutation(anyApi.chatTurns.append, { tenantId, sessionId, turn: normalized });
22659
+ try {
22660
+ appendTurn(sessionFile, normalized);
22661
+ } catch (err) {
22662
+ logger.warn(`session ${sessionId}: local JSONL mirror failed: ${err instanceof Error ? err.message : String(err)}`);
22663
+ }
22664
+ }
22665
+ };
22666
+ }
22667
+
22185
22668
  // src/chat/loop.ts
22186
22669
  var CHAT_SYSTEM_PROMPT = [
22187
22670
  "You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the",
@@ -22324,7 +22807,8 @@ function buildImplementationCatalog() {
22324
22807
  return lines.join("\n");
22325
22808
  }
22326
22809
  async function runChatTurn(opts) {
22327
- const turns = readSession(opts.sessionFile);
22810
+ const store = opts.store ?? createSessionStore({ sessionId: opts.sessionId, sessionFile: opts.sessionFile });
22811
+ const turns = await store.readTurns();
22328
22812
  if (turns.length === 0) {
22329
22813
  const error = "session file is empty \u2014 nothing to reply to";
22330
22814
  await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
@@ -22379,7 +22863,7 @@ async function runChatTurn(opts) {
22379
22863
  opts,
22380
22864
  turns: promptTurns,
22381
22865
  systemPrompt,
22382
- sessionFile: opts.sessionFile
22866
+ store
22383
22867
  });
22384
22868
  }
22385
22869
  let progressSeq = 0;
@@ -22455,7 +22939,7 @@ async function runChatTurn(opts) {
22455
22939
  return { exitCode: 99, error };
22456
22940
  }
22457
22941
  const now = (/* @__PURE__ */ new Date()).toISOString();
22458
- appendTurn(opts.sessionFile, {
22942
+ await store.appendTurn({
22459
22943
  role: "assistant",
22460
22944
  content: reply,
22461
22945
  timestamp: now
@@ -22491,7 +22975,7 @@ function readAgentIdentityBlock(cwd, agentIdentity) {
22491
22975
  return frameAgentIdentity(slug, body);
22492
22976
  }
22493
22977
  async function runOpenAIChatTurn(args) {
22494
- const { opts, turns, systemPrompt, sessionFile } = args;
22978
+ const { opts, turns, systemPrompt, store } = args;
22495
22979
  const doFetch = opts.fetchImpl ?? fetch;
22496
22980
  const url = `${opts.litellmUrl.replace(/\/+$/, "")}/v1/chat/completions`;
22497
22981
  try {
@@ -22524,7 +23008,7 @@ async function runOpenAIChatTurn(args) {
22524
23008
  return { exitCode: 99, error };
22525
23009
  }
22526
23010
  const now = (/* @__PURE__ */ new Date()).toISOString();
22527
- appendTurn(sessionFile, {
23011
+ await store.appendTurn({
22528
23012
  role: "assistant",
22529
23013
  content: reply,
22530
23014
  timestamp: now
@@ -22668,8 +23152,8 @@ init_config();
22668
23152
 
22669
23153
  // src/kody-cli.ts
22670
23154
  import { execFileSync as execFileSync26 } from "child_process";
22671
- import * as fs47 from "fs";
22672
- import * as path47 from "path";
23155
+ import * as fs48 from "fs";
23156
+ import * as path48 from "path";
22673
23157
 
22674
23158
  // src/app-auth.ts
22675
23159
  import { createSign } from "crypto";
@@ -23467,9 +23951,9 @@ async function resolveAuthToken(env = process.env) {
23467
23951
  return void 0;
23468
23952
  }
23469
23953
  function detectPackageManager2(cwd) {
23470
- if (fs47.existsSync(path47.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
23471
- if (fs47.existsSync(path47.join(cwd, "yarn.lock"))) return "yarn";
23472
- if (fs47.existsSync(path47.join(cwd, "bun.lockb"))) return "bun";
23954
+ if (fs48.existsSync(path48.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
23955
+ if (fs48.existsSync(path48.join(cwd, "yarn.lock"))) return "yarn";
23956
+ if (fs48.existsSync(path48.join(cwd, "bun.lockb"))) return "bun";
23473
23957
  return "npm";
23474
23958
  }
23475
23959
  function shouldChainScheduledWatch(match) {
@@ -23562,8 +24046,8 @@ function postFailureTail(issueNumber, cwd, reason) {
23562
24046
  const logPath = lastRunLogPath(cwd);
23563
24047
  let tail = "";
23564
24048
  try {
23565
- if (fs47.existsSync(logPath)) {
23566
- const content = fs47.readFileSync(logPath, "utf-8");
24049
+ if (fs48.existsSync(logPath)) {
24050
+ const content = fs48.readFileSync(logPath, "utf-8");
23567
24051
  tail = content.slice(-3e3);
23568
24052
  }
23569
24053
  } catch {
@@ -23592,7 +24076,7 @@ async function runCi(argv) {
23592
24076
  return 0;
23593
24077
  }
23594
24078
  const args = parseCiArgs(argv);
23595
- const cwd = args.cwd ? path47.resolve(args.cwd) : process.cwd();
24079
+ const cwd = args.cwd ? path48.resolve(args.cwd) : process.cwd();
23596
24080
  try {
23597
24081
  const n = unpackAllSecrets();
23598
24082
  if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
@@ -23652,9 +24136,9 @@ async function runCi(argv) {
23652
24136
  forceRunCliArgs = { goal: envForceMessage };
23653
24137
  }
23654
24138
  }
23655
- if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs47.existsSync(dispatchEventPath)) {
24139
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs48.existsSync(dispatchEventPath)) {
23656
24140
  try {
23657
- const evt = JSON.parse(fs47.readFileSync(dispatchEventPath, "utf-8"));
24141
+ const evt = JSON.parse(fs48.readFileSync(dispatchEventPath, "utf-8"));
23658
24142
  const inputs = objectValue2(evt.inputs);
23659
24143
  applyCompanyStoreRuntimeConfig(inputs);
23660
24144
  const issueInput = parseInt(String(inputs?.issue_number ?? ""), 10);
@@ -24028,8 +24512,8 @@ init_repoWorkspace();
24028
24512
 
24029
24513
  // src/scripts/brainTurnLog.ts
24030
24514
  init_runtimePaths();
24031
- import * as fs48 from "fs";
24032
- import * as path48 from "path";
24515
+ import * as fs49 from "fs";
24516
+ import * as path49 from "path";
24033
24517
  import posixPath4 from "path/posix";
24034
24518
  var live = /* @__PURE__ */ new Map();
24035
24519
  function brainEventsFilePath(dir, chatId) {
@@ -24040,8 +24524,8 @@ function brainEventsStatePath(chatId) {
24040
24524
  }
24041
24525
  function lastPersistedSeq(dir, chatId) {
24042
24526
  const p = brainEventsFilePath(dir, chatId);
24043
- if (!fs48.existsSync(p)) return 0;
24044
- const lines = fs48.readFileSync(p, "utf-8").split("\n").filter(Boolean);
24527
+ if (!fs49.existsSync(p)) return 0;
24528
+ const lines = fs49.readFileSync(p, "utf-8").split("\n").filter(Boolean);
24045
24529
  if (lines.length === 0) return 0;
24046
24530
  try {
24047
24531
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -24051,9 +24535,9 @@ function lastPersistedSeq(dir, chatId) {
24051
24535
  }
24052
24536
  function readSince(dir, chatId, since) {
24053
24537
  const p = brainEventsFilePath(dir, chatId);
24054
- if (!fs48.existsSync(p)) return [];
24538
+ if (!fs49.existsSync(p)) return [];
24055
24539
  const out = [];
24056
- for (const line of fs48.readFileSync(p, "utf-8").split("\n")) {
24540
+ for (const line of fs49.readFileSync(p, "utf-8").split("\n")) {
24057
24541
  if (!line) continue;
24058
24542
  try {
24059
24543
  const rec = JSON.parse(line);
@@ -24079,12 +24563,12 @@ function beginTurn(dir, chatId) {
24079
24563
  };
24080
24564
  live.set(chatId, state);
24081
24565
  const p = brainEventsFilePath(dir, chatId);
24082
- fs48.mkdirSync(path48.dirname(p), { recursive: true });
24566
+ fs49.mkdirSync(path49.dirname(p), { recursive: true });
24083
24567
  return (event) => {
24084
24568
  state.seq += 1;
24085
24569
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
24086
24570
  try {
24087
- fs48.appendFileSync(p, `${JSON.stringify(rec)}
24571
+ fs49.appendFileSync(p, `${JSON.stringify(rec)}
24088
24572
  `);
24089
24573
  } catch (err) {
24090
24574
  process.stderr.write(
@@ -24123,7 +24607,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
24123
24607
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
24124
24608
  };
24125
24609
  try {
24126
- fs48.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
24610
+ fs49.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
24127
24611
  `);
24128
24612
  } catch {
24129
24613
  }
@@ -24457,7 +24941,7 @@ async function handleChatTurn(req, res, chatId, opts) {
24457
24941
  );
24458
24942
  }
24459
24943
  }
24460
- fs49.mkdirSync(path49.dirname(sessionFile), { recursive: true });
24944
+ fs50.mkdirSync(path50.dirname(sessionFile), { recursive: true });
24461
24945
  appendTurn(sessionFile, {
24462
24946
  role: "user",
24463
24947
  content: message,
@@ -24532,7 +25016,7 @@ async function handleChatTurn(req, res, chatId, opts) {
24532
25016
  function buildServer(opts) {
24533
25017
  const runTurn = opts.runTurn ?? runChatTurn;
24534
25018
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
24535
- const reposRoot = opts.reposRoot ?? path49.join(path49.dirname(path49.resolve(opts.cwd)), "repos");
25019
+ const reposRoot = opts.reposRoot ?? path50.join(path50.dirname(path50.resolve(opts.cwd)), "repos");
24536
25020
  return createServer2(async (req, res) => {
24537
25021
  if (!req.method || !req.url) {
24538
25022
  sendJson(res, 400, { error: "bad request" });
@@ -25135,8 +25619,8 @@ async function loadConfigSafe() {
25135
25619
  }
25136
25620
 
25137
25621
  // src/chat-cli.ts
25138
- import * as fs51 from "fs";
25139
- import * as path51 from "path";
25622
+ import * as fs52 from "fs";
25623
+ import * as path52 from "path";
25140
25624
 
25141
25625
  // src/chat/inbox.ts
25142
25626
  import { execFileSync as execFileSync27 } from "child_process";
@@ -25152,7 +25636,8 @@ async function waitForNextUserMessage(opts) {
25152
25636
  const now = Date.now();
25153
25637
  if (now >= opts.deadlineMs) return { kind: "deadline" };
25154
25638
  if (now - idleStart >= opts.idleTimeoutMs) return { kind: "idle-timeout" };
25155
- if (opts.sync && !opts.skipPull) {
25639
+ if (opts.readTurns) {
25640
+ } else if (opts.sync && !opts.skipPull) {
25156
25641
  try {
25157
25642
  opts.sync();
25158
25643
  } catch (err) {
@@ -25176,7 +25661,18 @@ async function waitForNextUserMessage(opts) {
25176
25661
  logger.warn(`git pull failed (will retry): ${msg}`);
25177
25662
  }
25178
25663
  }
25179
- const turns = readSession(opts.sessionFile);
25664
+ let turns;
25665
+ if (opts.readTurns) {
25666
+ try {
25667
+ turns = await opts.readTurns();
25668
+ } catch (err) {
25669
+ const msg = err instanceof Error ? err.message : String(err);
25670
+ logger.warn(`convex transcript poll failed (will retry): ${msg}`);
25671
+ turns = [];
25672
+ }
25673
+ } else {
25674
+ turns = readSession(opts.sessionFile);
25675
+ }
25180
25676
  for (let i = opts.watermark; i < turns.length; i++) {
25181
25677
  const t = turns[i];
25182
25678
  if (t.role === "user") {
@@ -25208,8 +25704,8 @@ function currentBranch(cwd) {
25208
25704
 
25209
25705
  // src/chat/state-sync.ts
25210
25706
  init_stateRepo();
25211
- import * as fs50 from "fs";
25212
- import * as path50 from "path";
25707
+ import * as fs51 from "fs";
25708
+ import * as path51 from "path";
25213
25709
  function jsonlLines2(text2) {
25214
25710
  return text2.split("\n").filter((line) => line.length > 0);
25215
25711
  }
@@ -25226,15 +25722,15 @@ function mergeJsonl2(localText, remoteText) {
25226
25722
  function syncJsonlFileFromState(opts) {
25227
25723
  const remote = readStateText(opts.config, opts.cwd, opts.statePath);
25228
25724
  if (!remote) return;
25229
- const local = fs50.existsSync(opts.localPath) ? fs50.readFileSync(opts.localPath, "utf-8") : "";
25725
+ const local = fs51.existsSync(opts.localPath) ? fs51.readFileSync(opts.localPath, "utf-8") : "";
25230
25726
  const next = mergeJsonl2(local, remote.content);
25231
25727
  if (next === local) return;
25232
- fs50.mkdirSync(path50.dirname(opts.localPath), { recursive: true });
25233
- fs50.writeFileSync(opts.localPath, next);
25728
+ fs51.mkdirSync(path51.dirname(opts.localPath), { recursive: true });
25729
+ fs51.writeFileSync(opts.localPath, next);
25234
25730
  }
25235
25731
  function persistJsonlFileToState(opts) {
25236
- if (!fs50.existsSync(opts.localPath)) return;
25237
- const localText = fs50.readFileSync(opts.localPath, "utf-8");
25732
+ if (!fs51.existsSync(opts.localPath)) return;
25733
+ const localText = fs51.readFileSync(opts.localPath, "utf-8");
25238
25734
  for (let attempt = 1; attempt <= 3; attempt += 1) {
25239
25735
  const remote = readStateText(opts.config, opts.cwd, opts.statePath);
25240
25736
  const body = mergeJsonl2(localText, remote?.content ?? "");
@@ -25293,6 +25789,7 @@ var DEFAULT_HARD_CAP_MS = 30 * 6e4;
25293
25789
  var DEFAULT_POLL_MS2 = 3e3;
25294
25790
  async function runInteractiveMode(opts) {
25295
25791
  const sessionFile = sessionFilePath(opts.cwd, opts.sessionId);
25792
+ const store = opts.store ?? createSessionStore({ sessionId: opts.sessionId, sessionFile });
25296
25793
  const idleExitMs = opts.meta.idleExitMs ?? DEFAULT_IDLE_EXIT_MS;
25297
25794
  const hardCapMs = opts.meta.hardCapMs ?? DEFAULT_HARD_CAP_MS;
25298
25795
  const startedAt = Date.now();
@@ -25323,10 +25820,10 @@ async function runInteractiveMode(opts) {
25323
25820
  let watermark = 0;
25324
25821
  let turnsCompleted = 0;
25325
25822
  while (true) {
25326
- if (opts.stateConfig && !opts.skipGit) {
25823
+ if (store.backend !== "convex" && opts.stateConfig && !opts.skipGit) {
25327
25824
  syncChatSessionFromState(opts.stateConfig, opts.cwd, opts.sessionId);
25328
25825
  }
25329
- const turns = readSession(sessionFile);
25826
+ const turns = await store.readTurns();
25330
25827
  const pendingIdx = findNextUserTurn(turns, watermark);
25331
25828
  if (pendingIdx === -1) {
25332
25829
  const result = await waitForNextUserMessage({
@@ -25337,7 +25834,7 @@ async function runInteractiveMode(opts) {
25337
25834
  deadlineMs,
25338
25835
  pollIntervalMs: opts.pollIntervalMs ?? DEFAULT_POLL_MS2,
25339
25836
  skipPull: opts.skipGit,
25340
- ...opts.stateConfig ? {
25837
+ ...store.backend === "convex" ? { readTurns: () => store.readTurns() } : opts.stateConfig ? {
25341
25838
  sync: () => syncChatSessionFromState(opts.stateConfig, opts.cwd, opts.sessionId)
25342
25839
  } : {}
25343
25840
  });
@@ -25365,6 +25862,7 @@ async function runInteractiveMode(opts) {
25365
25862
  quiet: opts.quiet,
25366
25863
  invokeAgent: opts.invokeAgent,
25367
25864
  stateConfig: opts.stateConfig,
25865
+ store,
25368
25866
  ...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {}
25369
25867
  });
25370
25868
  } catch (err) {
@@ -25380,7 +25878,7 @@ async function runInteractiveMode(opts) {
25380
25878
  turnsCompleted += 1;
25381
25879
  if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false, opts.stateConfig ?? null);
25382
25880
  }
25383
- watermark = readSession(sessionFile).length;
25881
+ watermark = (await store.readTurns()).length;
25384
25882
  }
25385
25883
  }
25386
25884
  function findNextUserTurn(turns, fromIdx) {
@@ -25499,7 +25997,7 @@ async function runChat(argv) {
25499
25997
  ${CHAT_HELP}`);
25500
25998
  return 64;
25501
25999
  }
25502
- const cwd = args.cwd ? path51.resolve(args.cwd) : process.cwd();
26000
+ const cwd = args.cwd ? path52.resolve(args.cwd) : process.cwd();
25503
26001
  const sessionId = args.sessionId;
25504
26002
  const runRequest = readRunRequestFromEnv();
25505
26003
  if (runRequest && "request" in runRequest) {
@@ -25564,7 +26062,7 @@ ${CHAT_HELP}`);
25564
26062
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
25565
26063
  const meta = readMeta(sessionFile);
25566
26064
  process.stdout.write(
25567
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs51.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
26065
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs52.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
25568
26066
  `
25569
26067
  );
25570
26068
  try {
@@ -25679,8 +26177,8 @@ var FlyClient = class {
25679
26177
  get fetch() {
25680
26178
  return this.opts.fetchImpl ?? fetch;
25681
26179
  }
25682
- async call(path52, init = {}) {
25683
- const res = await this.fetch(`${FLY_API_BASE}${path52}`, {
26180
+ async call(path53, init = {}) {
26181
+ const res = await this.fetch(`${FLY_API_BASE}${path53}`, {
25684
26182
  method: init.method ?? "GET",
25685
26183
  headers: {
25686
26184
  Authorization: `Bearer ${this.opts.token}`,
@@ -25691,7 +26189,7 @@ var FlyClient = class {
25691
26189
  if (res.status === 404 && init.allow404) return null;
25692
26190
  if (!res.ok) {
25693
26191
  const text2 = await res.text().catch(() => "");
25694
- throw new Error(`Fly API ${res.status} on ${path52}: ${text2.slice(0, 200) || res.statusText}`);
26192
+ throw new Error(`Fly API ${res.status} on ${path53}: ${text2.slice(0, 200) || res.statusText}`);
25695
26193
  }
25696
26194
  if (res.status === 204) return null;
25697
26195
  const raw = await res.text();
@@ -26447,7 +26945,7 @@ async function poolServe() {
26447
26945
 
26448
26946
  // src/servers/runner-serve.ts
26449
26947
  import { spawn as spawn8 } from "child_process";
26450
- import * as fs52 from "fs";
26948
+ import * as fs53 from "fs";
26451
26949
  import { createServer as createServer6 } from "http";
26452
26950
  var DEFAULT_PORT2 = 8080;
26453
26951
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -26582,8 +27080,8 @@ async function defaultRunJob(job) {
26582
27080
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
26583
27081
  const branch = job.ref ?? "main";
26584
27082
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
26585
- fs52.rmSync(workdir, { recursive: true, force: true });
26586
- fs52.mkdirSync(workdir, { recursive: true });
27083
+ fs53.rmSync(workdir, { recursive: true, force: true });
27084
+ fs53.mkdirSync(workdir, { recursive: true });
26587
27085
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
26588
27086
  const target = job.runRequest.target;
26589
27087
  const interactive = target.type === "chat";