@kody-ade/kody-engine 0.4.379 → 0.4.381

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.381",
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,103 @@ 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 normalizeTurn(turn) {
22629
+ return {
22630
+ role: turn.role,
22631
+ content: turn.content,
22632
+ timestamp: turn.timestamp,
22633
+ toolCalls: turn.toolCalls ?? []
22634
+ };
22635
+ }
22636
+ function createConvexStore(args) {
22637
+ const { client, tenantId, sessionId, sessionFile, logger } = args;
22638
+ let sessionUpserted = false;
22639
+ const appendToConvex = async (turn) => {
22640
+ if (!sessionUpserted) {
22641
+ try {
22642
+ const meta = readMeta(sessionFile) ?? { type: "meta", mode: "one-shot" };
22643
+ await client.mutation(anyApi.chatSessions.upsert, {
22644
+ tenantId,
22645
+ sessionId,
22646
+ meta,
22647
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
22648
+ });
22649
+ sessionUpserted = true;
22650
+ } catch (err) {
22651
+ logger.warn(`session ${sessionId}: chatSessions.upsert failed: ${err instanceof Error ? err.message : String(err)}`);
22652
+ }
22653
+ }
22654
+ await client.mutation(anyApi.chatTurns.append, { tenantId, sessionId, turn });
22655
+ };
22656
+ return {
22657
+ backend: "convex",
22658
+ readTurns: async () => {
22659
+ const docs = await client.query(anyApi.chatTurns.list, { tenantId, sessionId });
22660
+ const convexTurns = [...docs].sort((a, b) => a.seq - b.seq).map((doc) => doc.turn).filter(isChatTurn);
22661
+ const localTurns = readSession(sessionFile);
22662
+ if (localTurns.length <= convexTurns.length) return convexTurns;
22663
+ const tail = localTurns.slice(convexTurns.length).map(normalizeTurn);
22664
+ logger.info(
22665
+ `session ${sessionId}: backfilling ${tail.length} local JSONL turn(s) into Convex (convex=${convexTurns.length}, local=${localTurns.length})`
22666
+ );
22667
+ for (const turn of tail) {
22668
+ await appendToConvex(turn);
22669
+ }
22670
+ return [...convexTurns, ...tail];
22671
+ },
22672
+ appendTurn: async (turn) => {
22673
+ const normalized = normalizeTurn(turn);
22674
+ await appendToConvex(normalized);
22675
+ try {
22676
+ appendTurn(sessionFile, normalized);
22677
+ } catch (err) {
22678
+ logger.warn(`session ${sessionId}: local JSONL mirror failed: ${err instanceof Error ? err.message : String(err)}`);
22679
+ }
22680
+ }
22681
+ };
22682
+ }
22683
+
22185
22684
  // src/chat/loop.ts
22186
22685
  var CHAT_SYSTEM_PROMPT = [
22187
22686
  "You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the",
@@ -22324,7 +22823,8 @@ function buildImplementationCatalog() {
22324
22823
  return lines.join("\n");
22325
22824
  }
22326
22825
  async function runChatTurn(opts) {
22327
- const turns = readSession(opts.sessionFile);
22826
+ const store = opts.store ?? createSessionStore({ sessionId: opts.sessionId, sessionFile: opts.sessionFile });
22827
+ const turns = await store.readTurns();
22328
22828
  if (turns.length === 0) {
22329
22829
  const error = "session file is empty \u2014 nothing to reply to";
22330
22830
  await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
@@ -22379,7 +22879,7 @@ async function runChatTurn(opts) {
22379
22879
  opts,
22380
22880
  turns: promptTurns,
22381
22881
  systemPrompt,
22382
- sessionFile: opts.sessionFile
22882
+ store
22383
22883
  });
22384
22884
  }
22385
22885
  let progressSeq = 0;
@@ -22455,7 +22955,7 @@ async function runChatTurn(opts) {
22455
22955
  return { exitCode: 99, error };
22456
22956
  }
22457
22957
  const now = (/* @__PURE__ */ new Date()).toISOString();
22458
- appendTurn(opts.sessionFile, {
22958
+ await store.appendTurn({
22459
22959
  role: "assistant",
22460
22960
  content: reply,
22461
22961
  timestamp: now
@@ -22491,7 +22991,7 @@ function readAgentIdentityBlock(cwd, agentIdentity) {
22491
22991
  return frameAgentIdentity(slug, body);
22492
22992
  }
22493
22993
  async function runOpenAIChatTurn(args) {
22494
- const { opts, turns, systemPrompt, sessionFile } = args;
22994
+ const { opts, turns, systemPrompt, store } = args;
22495
22995
  const doFetch = opts.fetchImpl ?? fetch;
22496
22996
  const url = `${opts.litellmUrl.replace(/\/+$/, "")}/v1/chat/completions`;
22497
22997
  try {
@@ -22524,7 +23024,7 @@ async function runOpenAIChatTurn(args) {
22524
23024
  return { exitCode: 99, error };
22525
23025
  }
22526
23026
  const now = (/* @__PURE__ */ new Date()).toISOString();
22527
- appendTurn(sessionFile, {
23027
+ await store.appendTurn({
22528
23028
  role: "assistant",
22529
23029
  content: reply,
22530
23030
  timestamp: now
@@ -22668,8 +23168,8 @@ init_config();
22668
23168
 
22669
23169
  // src/kody-cli.ts
22670
23170
  import { execFileSync as execFileSync26 } from "child_process";
22671
- import * as fs47 from "fs";
22672
- import * as path47 from "path";
23171
+ import * as fs48 from "fs";
23172
+ import * as path48 from "path";
22673
23173
 
22674
23174
  // src/app-auth.ts
22675
23175
  import { createSign } from "crypto";
@@ -23467,9 +23967,9 @@ async function resolveAuthToken(env = process.env) {
23467
23967
  return void 0;
23468
23968
  }
23469
23969
  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";
23970
+ if (fs48.existsSync(path48.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
23971
+ if (fs48.existsSync(path48.join(cwd, "yarn.lock"))) return "yarn";
23972
+ if (fs48.existsSync(path48.join(cwd, "bun.lockb"))) return "bun";
23473
23973
  return "npm";
23474
23974
  }
23475
23975
  function shouldChainScheduledWatch(match) {
@@ -23562,8 +24062,8 @@ function postFailureTail(issueNumber, cwd, reason) {
23562
24062
  const logPath = lastRunLogPath(cwd);
23563
24063
  let tail = "";
23564
24064
  try {
23565
- if (fs47.existsSync(logPath)) {
23566
- const content = fs47.readFileSync(logPath, "utf-8");
24065
+ if (fs48.existsSync(logPath)) {
24066
+ const content = fs48.readFileSync(logPath, "utf-8");
23567
24067
  tail = content.slice(-3e3);
23568
24068
  }
23569
24069
  } catch {
@@ -23592,7 +24092,7 @@ async function runCi(argv) {
23592
24092
  return 0;
23593
24093
  }
23594
24094
  const args = parseCiArgs(argv);
23595
- const cwd = args.cwd ? path47.resolve(args.cwd) : process.cwd();
24095
+ const cwd = args.cwd ? path48.resolve(args.cwd) : process.cwd();
23596
24096
  try {
23597
24097
  const n = unpackAllSecrets();
23598
24098
  if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
@@ -23652,9 +24152,9 @@ async function runCi(argv) {
23652
24152
  forceRunCliArgs = { goal: envForceMessage };
23653
24153
  }
23654
24154
  }
23655
- if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs47.existsSync(dispatchEventPath)) {
24155
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs48.existsSync(dispatchEventPath)) {
23656
24156
  try {
23657
- const evt = JSON.parse(fs47.readFileSync(dispatchEventPath, "utf-8"));
24157
+ const evt = JSON.parse(fs48.readFileSync(dispatchEventPath, "utf-8"));
23658
24158
  const inputs = objectValue2(evt.inputs);
23659
24159
  applyCompanyStoreRuntimeConfig(inputs);
23660
24160
  const issueInput = parseInt(String(inputs?.issue_number ?? ""), 10);
@@ -24028,8 +24528,8 @@ init_repoWorkspace();
24028
24528
 
24029
24529
  // src/scripts/brainTurnLog.ts
24030
24530
  init_runtimePaths();
24031
- import * as fs48 from "fs";
24032
- import * as path48 from "path";
24531
+ import * as fs49 from "fs";
24532
+ import * as path49 from "path";
24033
24533
  import posixPath4 from "path/posix";
24034
24534
  var live = /* @__PURE__ */ new Map();
24035
24535
  function brainEventsFilePath(dir, chatId) {
@@ -24040,8 +24540,8 @@ function brainEventsStatePath(chatId) {
24040
24540
  }
24041
24541
  function lastPersistedSeq(dir, chatId) {
24042
24542
  const p = brainEventsFilePath(dir, chatId);
24043
- if (!fs48.existsSync(p)) return 0;
24044
- const lines = fs48.readFileSync(p, "utf-8").split("\n").filter(Boolean);
24543
+ if (!fs49.existsSync(p)) return 0;
24544
+ const lines = fs49.readFileSync(p, "utf-8").split("\n").filter(Boolean);
24045
24545
  if (lines.length === 0) return 0;
24046
24546
  try {
24047
24547
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -24051,9 +24551,9 @@ function lastPersistedSeq(dir, chatId) {
24051
24551
  }
24052
24552
  function readSince(dir, chatId, since) {
24053
24553
  const p = brainEventsFilePath(dir, chatId);
24054
- if (!fs48.existsSync(p)) return [];
24554
+ if (!fs49.existsSync(p)) return [];
24055
24555
  const out = [];
24056
- for (const line of fs48.readFileSync(p, "utf-8").split("\n")) {
24556
+ for (const line of fs49.readFileSync(p, "utf-8").split("\n")) {
24057
24557
  if (!line) continue;
24058
24558
  try {
24059
24559
  const rec = JSON.parse(line);
@@ -24079,12 +24579,12 @@ function beginTurn(dir, chatId) {
24079
24579
  };
24080
24580
  live.set(chatId, state);
24081
24581
  const p = brainEventsFilePath(dir, chatId);
24082
- fs48.mkdirSync(path48.dirname(p), { recursive: true });
24582
+ fs49.mkdirSync(path49.dirname(p), { recursive: true });
24083
24583
  return (event) => {
24084
24584
  state.seq += 1;
24085
24585
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
24086
24586
  try {
24087
- fs48.appendFileSync(p, `${JSON.stringify(rec)}
24587
+ fs49.appendFileSync(p, `${JSON.stringify(rec)}
24088
24588
  `);
24089
24589
  } catch (err) {
24090
24590
  process.stderr.write(
@@ -24123,7 +24623,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
24123
24623
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
24124
24624
  };
24125
24625
  try {
24126
- fs48.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
24626
+ fs49.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
24127
24627
  `);
24128
24628
  } catch {
24129
24629
  }
@@ -24457,7 +24957,7 @@ async function handleChatTurn(req, res, chatId, opts) {
24457
24957
  );
24458
24958
  }
24459
24959
  }
24460
- fs49.mkdirSync(path49.dirname(sessionFile), { recursive: true });
24960
+ fs50.mkdirSync(path50.dirname(sessionFile), { recursive: true });
24461
24961
  appendTurn(sessionFile, {
24462
24962
  role: "user",
24463
24963
  content: message,
@@ -24532,7 +25032,7 @@ async function handleChatTurn(req, res, chatId, opts) {
24532
25032
  function buildServer(opts) {
24533
25033
  const runTurn = opts.runTurn ?? runChatTurn;
24534
25034
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
24535
- const reposRoot = opts.reposRoot ?? path49.join(path49.dirname(path49.resolve(opts.cwd)), "repos");
25035
+ const reposRoot = opts.reposRoot ?? path50.join(path50.dirname(path50.resolve(opts.cwd)), "repos");
24536
25036
  return createServer2(async (req, res) => {
24537
25037
  if (!req.method || !req.url) {
24538
25038
  sendJson(res, 400, { error: "bad request" });
@@ -25135,8 +25635,8 @@ async function loadConfigSafe() {
25135
25635
  }
25136
25636
 
25137
25637
  // src/chat-cli.ts
25138
- import * as fs51 from "fs";
25139
- import * as path51 from "path";
25638
+ import * as fs52 from "fs";
25639
+ import * as path52 from "path";
25140
25640
 
25141
25641
  // src/chat/inbox.ts
25142
25642
  import { execFileSync as execFileSync27 } from "child_process";
@@ -25152,7 +25652,8 @@ async function waitForNextUserMessage(opts) {
25152
25652
  const now = Date.now();
25153
25653
  if (now >= opts.deadlineMs) return { kind: "deadline" };
25154
25654
  if (now - idleStart >= opts.idleTimeoutMs) return { kind: "idle-timeout" };
25155
- if (opts.sync && !opts.skipPull) {
25655
+ if (opts.readTurns) {
25656
+ } else if (opts.sync && !opts.skipPull) {
25156
25657
  try {
25157
25658
  opts.sync();
25158
25659
  } catch (err) {
@@ -25176,7 +25677,18 @@ async function waitForNextUserMessage(opts) {
25176
25677
  logger.warn(`git pull failed (will retry): ${msg}`);
25177
25678
  }
25178
25679
  }
25179
- const turns = readSession(opts.sessionFile);
25680
+ let turns;
25681
+ if (opts.readTurns) {
25682
+ try {
25683
+ turns = await opts.readTurns();
25684
+ } catch (err) {
25685
+ const msg = err instanceof Error ? err.message : String(err);
25686
+ logger.warn(`convex transcript poll failed (will retry): ${msg}`);
25687
+ turns = [];
25688
+ }
25689
+ } else {
25690
+ turns = readSession(opts.sessionFile);
25691
+ }
25180
25692
  for (let i = opts.watermark; i < turns.length; i++) {
25181
25693
  const t = turns[i];
25182
25694
  if (t.role === "user") {
@@ -25208,8 +25720,8 @@ function currentBranch(cwd) {
25208
25720
 
25209
25721
  // src/chat/state-sync.ts
25210
25722
  init_stateRepo();
25211
- import * as fs50 from "fs";
25212
- import * as path50 from "path";
25723
+ import * as fs51 from "fs";
25724
+ import * as path51 from "path";
25213
25725
  function jsonlLines2(text2) {
25214
25726
  return text2.split("\n").filter((line) => line.length > 0);
25215
25727
  }
@@ -25226,15 +25738,15 @@ function mergeJsonl2(localText, remoteText) {
25226
25738
  function syncJsonlFileFromState(opts) {
25227
25739
  const remote = readStateText(opts.config, opts.cwd, opts.statePath);
25228
25740
  if (!remote) return;
25229
- const local = fs50.existsSync(opts.localPath) ? fs50.readFileSync(opts.localPath, "utf-8") : "";
25741
+ const local = fs51.existsSync(opts.localPath) ? fs51.readFileSync(opts.localPath, "utf-8") : "";
25230
25742
  const next = mergeJsonl2(local, remote.content);
25231
25743
  if (next === local) return;
25232
- fs50.mkdirSync(path50.dirname(opts.localPath), { recursive: true });
25233
- fs50.writeFileSync(opts.localPath, next);
25744
+ fs51.mkdirSync(path51.dirname(opts.localPath), { recursive: true });
25745
+ fs51.writeFileSync(opts.localPath, next);
25234
25746
  }
25235
25747
  function persistJsonlFileToState(opts) {
25236
- if (!fs50.existsSync(opts.localPath)) return;
25237
- const localText = fs50.readFileSync(opts.localPath, "utf-8");
25748
+ if (!fs51.existsSync(opts.localPath)) return;
25749
+ const localText = fs51.readFileSync(opts.localPath, "utf-8");
25238
25750
  for (let attempt = 1; attempt <= 3; attempt += 1) {
25239
25751
  const remote = readStateText(opts.config, opts.cwd, opts.statePath);
25240
25752
  const body = mergeJsonl2(localText, remote?.content ?? "");
@@ -25293,6 +25805,7 @@ var DEFAULT_HARD_CAP_MS = 30 * 6e4;
25293
25805
  var DEFAULT_POLL_MS2 = 3e3;
25294
25806
  async function runInteractiveMode(opts) {
25295
25807
  const sessionFile = sessionFilePath(opts.cwd, opts.sessionId);
25808
+ const store = opts.store ?? createSessionStore({ sessionId: opts.sessionId, sessionFile });
25296
25809
  const idleExitMs = opts.meta.idleExitMs ?? DEFAULT_IDLE_EXIT_MS;
25297
25810
  const hardCapMs = opts.meta.hardCapMs ?? DEFAULT_HARD_CAP_MS;
25298
25811
  const startedAt = Date.now();
@@ -25323,10 +25836,10 @@ async function runInteractiveMode(opts) {
25323
25836
  let watermark = 0;
25324
25837
  let turnsCompleted = 0;
25325
25838
  while (true) {
25326
- if (opts.stateConfig && !opts.skipGit) {
25839
+ if (store.backend !== "convex" && opts.stateConfig && !opts.skipGit) {
25327
25840
  syncChatSessionFromState(opts.stateConfig, opts.cwd, opts.sessionId);
25328
25841
  }
25329
- const turns = readSession(sessionFile);
25842
+ const turns = await store.readTurns();
25330
25843
  const pendingIdx = findNextUserTurn(turns, watermark);
25331
25844
  if (pendingIdx === -1) {
25332
25845
  const result = await waitForNextUserMessage({
@@ -25337,7 +25850,7 @@ async function runInteractiveMode(opts) {
25337
25850
  deadlineMs,
25338
25851
  pollIntervalMs: opts.pollIntervalMs ?? DEFAULT_POLL_MS2,
25339
25852
  skipPull: opts.skipGit,
25340
- ...opts.stateConfig ? {
25853
+ ...store.backend === "convex" ? { readTurns: () => store.readTurns() } : opts.stateConfig ? {
25341
25854
  sync: () => syncChatSessionFromState(opts.stateConfig, opts.cwd, opts.sessionId)
25342
25855
  } : {}
25343
25856
  });
@@ -25365,6 +25878,7 @@ async function runInteractiveMode(opts) {
25365
25878
  quiet: opts.quiet,
25366
25879
  invokeAgent: opts.invokeAgent,
25367
25880
  stateConfig: opts.stateConfig,
25881
+ store,
25368
25882
  ...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {}
25369
25883
  });
25370
25884
  } catch (err) {
@@ -25380,7 +25894,7 @@ async function runInteractiveMode(opts) {
25380
25894
  turnsCompleted += 1;
25381
25895
  if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false, opts.stateConfig ?? null);
25382
25896
  }
25383
- watermark = readSession(sessionFile).length;
25897
+ watermark = (await store.readTurns()).length;
25384
25898
  }
25385
25899
  }
25386
25900
  function findNextUserTurn(turns, fromIdx) {
@@ -25499,7 +26013,7 @@ async function runChat(argv) {
25499
26013
  ${CHAT_HELP}`);
25500
26014
  return 64;
25501
26015
  }
25502
- const cwd = args.cwd ? path51.resolve(args.cwd) : process.cwd();
26016
+ const cwd = args.cwd ? path52.resolve(args.cwd) : process.cwd();
25503
26017
  const sessionId = args.sessionId;
25504
26018
  const runRequest = readRunRequestFromEnv();
25505
26019
  if (runRequest && "request" in runRequest) {
@@ -25564,7 +26078,7 @@ ${CHAT_HELP}`);
25564
26078
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
25565
26079
  const meta = readMeta(sessionFile);
25566
26080
  process.stdout.write(
25567
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs51.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
26081
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs52.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
25568
26082
  `
25569
26083
  );
25570
26084
  try {
@@ -25679,8 +26193,8 @@ var FlyClient = class {
25679
26193
  get fetch() {
25680
26194
  return this.opts.fetchImpl ?? fetch;
25681
26195
  }
25682
- async call(path52, init = {}) {
25683
- const res = await this.fetch(`${FLY_API_BASE}${path52}`, {
26196
+ async call(path53, init = {}) {
26197
+ const res = await this.fetch(`${FLY_API_BASE}${path53}`, {
25684
26198
  method: init.method ?? "GET",
25685
26199
  headers: {
25686
26200
  Authorization: `Bearer ${this.opts.token}`,
@@ -25691,7 +26205,7 @@ var FlyClient = class {
25691
26205
  if (res.status === 404 && init.allow404) return null;
25692
26206
  if (!res.ok) {
25693
26207
  const text2 = await res.text().catch(() => "");
25694
- throw new Error(`Fly API ${res.status} on ${path52}: ${text2.slice(0, 200) || res.statusText}`);
26208
+ throw new Error(`Fly API ${res.status} on ${path53}: ${text2.slice(0, 200) || res.statusText}`);
25695
26209
  }
25696
26210
  if (res.status === 204) return null;
25697
26211
  const raw = await res.text();
@@ -26447,7 +26961,7 @@ async function poolServe() {
26447
26961
 
26448
26962
  // src/servers/runner-serve.ts
26449
26963
  import { spawn as spawn8 } from "child_process";
26450
- import * as fs52 from "fs";
26964
+ import * as fs53 from "fs";
26451
26965
  import { createServer as createServer6 } from "http";
26452
26966
  var DEFAULT_PORT2 = 8080;
26453
26967
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -26582,8 +27096,8 @@ async function defaultRunJob(job) {
26582
27096
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
26583
27097
  const branch = job.ref ?? "main";
26584
27098
  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 });
27099
+ fs53.rmSync(workdir, { recursive: true, force: true });
27100
+ fs53.mkdirSync(workdir, { recursive: true });
26587
27101
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
26588
27102
  const target = job.runRequest.target;
26589
27103
  const interactive = target.type === "chat";