@kody-ade/kody-engine 0.4.305 → 0.4.307

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.305",
18
+ version: "0.4.307",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
20
20
  license: "MIT",
21
21
  type: "module",
@@ -549,11 +549,11 @@ function branchApiPath(config, targetPath) {
549
549
  }
550
550
  function ensureStateBranch(config, cwd) {
551
551
  const parsed = parseStateRepo(config);
552
- const cacheKey3 = `${parsed.owner}/${parsed.repo}:${parsed.branch}`;
553
- if (ensuredStateBranches.has(cacheKey3)) return;
552
+ const cacheKey4 = `${parsed.owner}/${parsed.repo}:${parsed.branch}`;
553
+ if (ensuredStateBranches.has(cacheKey4)) return;
554
554
  try {
555
555
  gh(["api", `/repos/${parsed.owner}/${parsed.repo}/git/ref/heads/${parsed.branch}`], { cwd });
556
- ensuredStateBranches.add(cacheKey3);
556
+ ensuredStateBranches.add(cacheKey4);
557
557
  return;
558
558
  } catch (err) {
559
559
  if (!is404(err)) throw err;
@@ -572,7 +572,7 @@ function ensureStateBranch(config, cwd) {
572
572
  } catch (err) {
573
573
  if (!isAlreadyExists(err)) throw err;
574
574
  }
575
- ensuredStateBranches.add(cacheKey3);
575
+ ensuredStateBranches.add(cacheKey4);
576
576
  }
577
577
  function readStateText(config, cwd, filePath) {
578
578
  const targetPath = stateRepoPath(config, filePath);
@@ -1914,6 +1914,7 @@ function parseCapabilityConfig(raw) {
1914
1914
  mentions: stringList(raw.mentions).map((m) => m.replace(/^@/, "")),
1915
1915
  tools,
1916
1916
  capabilityTools: tools,
1917
+ capabilityToolMode: parseCapabilityToolMode(raw.capabilityToolMode),
1917
1918
  implementations,
1918
1919
  executables: stringList(raw.executables),
1919
1920
  role: stringField(raw.role),
@@ -1924,6 +1925,11 @@ function parseCapabilityConfig(raw) {
1924
1925
  workflow: parseWorkflow(raw.workflow)
1925
1926
  };
1926
1927
  }
1928
+ function parseCapabilityToolMode(raw) {
1929
+ if (raw === void 0 || raw === null || raw === "") return void 0;
1930
+ if (raw === "lock" || raw === "append") return raw;
1931
+ return void 0;
1932
+ }
1927
1933
  function parseCapabilityBody(raw, slug2) {
1928
1934
  const trimmed = raw.trim();
1929
1935
  const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
@@ -2414,7 +2420,8 @@ __export(capabilityMcp_exports, {
2414
2420
  parseCapabilityTrustMode: () => parseCapabilityTrustMode,
2415
2421
  readCapabilityTrustMode: () => readCapabilityTrustMode,
2416
2422
  readCheckRuns: () => readCheckRuns,
2417
- readThread: () => readThread
2423
+ readThread: () => readThread,
2424
+ startCapability: () => startCapability
2418
2425
  });
2419
2426
  import { createSdkMcpServer as createSdkMcpServer4, tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
2420
2427
  import { z as z3 } from "zod";
@@ -2663,6 +2670,9 @@ function dispatchWorkflow(workflowFile, capability, issueNumber, repoSlug) {
2663
2670
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
2664
2671
  }
2665
2672
  }
2673
+ function startCapability(workflowFile, name, issue, repoSlug) {
2674
+ return dispatchWorkflow(workflowFile, name, issue, repoSlug);
2675
+ }
2666
2676
  function expectedDispatchTarget(capability) {
2667
2677
  const route = resolveCapabilityAction(capability);
2668
2678
  if (!route) return null;
@@ -2835,9 +2845,31 @@ function capabilityToolDefinitions(opts) {
2835
2845
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2836
2846
  }
2837
2847
  };
2848
+ const startCapabilityTool = {
2849
+ name: "start_capability",
2850
+ description: "Start a known Kody capability on an issue or PR through workflow_dispatch. Use this instead of shelling out to `gh workflow run` or posting bot-authored `@kody` command comments. E.g. start_capability({name:'qa-engineer', issue:<n>}). Returns {ok} or {ok:false,error}.",
2851
+ inputSchema: {
2852
+ name: z3.string().min(1).describe("Capability action to start (e.g. 'qa-engineer', 'run', 'sync')."),
2853
+ issue: z3.number().int().positive().optional().describe("Issue or PR number forwarded as issue_number."),
2854
+ issueNumber: z3.number().int().positive().optional().describe("Deprecated alias for issue.")
2855
+ },
2856
+ handler: async (args) => {
2857
+ const name = String(args.name ?? "");
2858
+ const issue = Number(args.issue ?? args.issueNumber);
2859
+ if (!Number.isFinite(issue) || issue <= 0) {
2860
+ return { content: [{ type: "text", text: "Start failed: `issue` is required and must be a positive number." }] };
2861
+ }
2862
+ if (isDispatchGated(name, readCapabilityTrustMode(opts.state, opts.repoSlug, opts.capabilitySlug))) {
2863
+ return { content: [{ type: "text", text: trustRefusal(opts.capabilitySlug) }] };
2864
+ }
2865
+ const result = startCapability(workflowFile, name, issue, opts.repoSlug);
2866
+ const text = result.ok ? `Started capability \`${name}\` on #${issue} via workflow_dispatch.` : `Start failed for capability \`${name}\` on #${issue}: ${result.error}`;
2867
+ return { content: [{ type: "text", text }] };
2868
+ }
2869
+ };
2838
2870
  const dispatchTool = {
2839
2871
  name: "dispatch_workflow",
2840
- description: "Dispatch a kody.yml workflow_dispatch run for a capability action against an issue (the cross-run bot\u2192engine path; a bot `@kody` comment would be dropped). E.g. dispatch_workflow({capability:'run', issueNumber:<n>}) opens a fix PR from a tracking issue. Returns {ok} or {ok:false,error}.",
2872
+ description: "Legacy alias for start_capability. Dispatches a kody.yml workflow_dispatch run for a capability action against an issue. Prefer start_capability({name:'run', issue:<n>}). Returns {ok} or {ok:false,error}.",
2841
2873
  inputSchema: {
2842
2874
  capability: z3.string().min(1).optional().describe("Capability action to run (e.g. 'run')."),
2843
2875
  executable: z3.string().min(1).optional().describe("Deprecated alias for capability."),
@@ -2849,7 +2881,7 @@ function capabilityToolDefinitions(opts) {
2849
2881
  if (isDispatchGated(capability, readCapabilityTrustMode(opts.state, opts.repoSlug, opts.capabilitySlug))) {
2850
2882
  return { content: [{ type: "text", text: trustRefusal(opts.capabilitySlug) }] };
2851
2883
  }
2852
- const result = dispatchWorkflow(workflowFile, capability, issueNumber, opts.repoSlug);
2884
+ const result = startCapability(workflowFile, capability, issueNumber, opts.repoSlug);
2853
2885
  const text = result.ok ? `Dispatched capability \`${capability}\` on #${issueNumber} via workflow_dispatch.` : `Dispatch failed for capability \`${capability}\` on #${issueNumber}: ${result.error}`;
2854
2886
  return { content: [{ type: "text", text }] };
2855
2887
  }
@@ -2869,6 +2901,7 @@ function capabilityToolDefinitions(opts) {
2869
2901
  readThreadTool,
2870
2902
  ensureIssueTool,
2871
2903
  ensureCommentTool,
2904
+ startCapabilityTool,
2872
2905
  dispatchTool,
2873
2906
  ...cmsTools
2874
2907
  ];
@@ -2918,6 +2951,7 @@ var init_capabilityMcp = __esm({
2918
2951
  "read_thread",
2919
2952
  "ensure_issue",
2920
2953
  "ensure_comment",
2954
+ "start_capability",
2921
2955
  "dispatch_workflow",
2922
2956
  ...DASHBOARD_CMS_MCP_TOOL_NAMES
2923
2957
  ];
@@ -4264,6 +4298,7 @@ function loadProfile(profilePath) {
4264
4298
  describe: typeof r.describe === "string" ? r.describe : base.describe,
4265
4299
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : base.agent,
4266
4300
  capabilityTools: parseStringArray2(r.capabilityTools ?? r.capabilityTools ?? r.tools) ?? base.capabilityTools,
4301
+ capabilityToolMode: parseCapabilityToolMode2(profilePath, r.capabilityToolMode) ?? base.capabilityToolMode,
4267
4302
  mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : base.mentions
4268
4303
  };
4269
4304
  }
@@ -4319,6 +4354,7 @@ function loadProfile(profilePath) {
4319
4354
  agent: typeof r.agent === "string" && r.agent.trim() ? r.agent.trim() : void 0,
4320
4355
  // Locked-toolbox palette + mentions from folder-capability profile metadata.
4321
4356
  capabilityTools: parseStringArray2(r.capabilityTools ?? r.capabilityTools ?? r.tools),
4357
+ capabilityToolMode: parseCapabilityToolMode2(profilePath, r.capabilityToolMode),
4322
4358
  mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : void 0,
4323
4359
  role,
4324
4360
  kind,
@@ -4360,12 +4396,7 @@ function loadProfile(profilePath) {
4360
4396
  const preNames = new Set(profile.scripts.preflight.map((e) => e.script).filter(Boolean));
4361
4397
  const postNames = profile.scripts.postflight.map((e) => e.script).filter(Boolean);
4362
4398
  const needsState = postNames.includes("writeJobStateFile") || postNames.includes("parseJobStateFromAgentResult");
4363
- const STATE_LOADERS = [
4364
- "loadCapabilityState",
4365
- "loadJobFromFile",
4366
- "runTickScript",
4367
- "runScheduledExecutableTick"
4368
- ];
4399
+ const STATE_LOADERS = ["loadCapabilityState", "loadJobFromFile", "runTickScript", "runScheduledExecutableTick"];
4369
4400
  if (needsState && !STATE_LOADERS.some((s) => preNames.has(s))) {
4370
4401
  throw new ProfileError(
4371
4402
  profilePath,
@@ -4375,6 +4406,11 @@ function loadProfile(profilePath) {
4375
4406
  profile.subagentTemplates = captureSubagentTemplates(profile);
4376
4407
  return profile;
4377
4408
  }
4409
+ function parseCapabilityToolMode2(profilePath, raw) {
4410
+ if (raw === void 0 || raw === null || raw === "") return void 0;
4411
+ if (raw === "lock" || raw === "append") return raw;
4412
+ throw new ProfileError(profilePath, `"capabilityToolMode" must be "lock" or "append"`);
4413
+ }
4378
4414
  function readPromptTemplates(dir) {
4379
4415
  const out = {};
4380
4416
  const read = (p) => {
@@ -4694,6 +4730,7 @@ var init_profile = __esm({
4694
4730
  "capabilityTools",
4695
4731
  "capabilityTools",
4696
4732
  "tools",
4733
+ "capabilityToolMode",
4697
4734
  "mentions",
4698
4735
  "stage",
4699
4736
  "readsFrom",
@@ -8791,12 +8828,12 @@ var init_appendCompanyActivity = __esm({
8791
8828
  }
8792
8829
  });
8793
8830
 
8794
- // src/companyManagerDecision.ts
8795
- function parseCompanyManagerDecisionText(finalText) {
8831
+ // src/agencyArchitectDecision.ts
8832
+ function parseAgencyArchitectDecisionText(finalText) {
8796
8833
  const raw = extractDecisionJson(finalText);
8797
8834
  const parsed = JSON.parse(raw);
8798
8835
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8799
- throw new Error("company-manager decision must be JSON object");
8836
+ throw new Error("agency-architect decision must be JSON object");
8800
8837
  }
8801
8838
  const input = parsed;
8802
8839
  const actions = Array.isArray(input.actions) ? input.actions.map(parseAction) : [];
@@ -8844,15 +8881,15 @@ function buildAgentLoopState(action) {
8844
8881
  };
8845
8882
  }
8846
8883
  function extractDecisionJson(finalText) {
8847
- const fence = finalText.match(/```(?:kody-company-manager-decision|json)\s*([\s\S]*?)```/i);
8884
+ const fence = finalText.match(/```(?:kody-agency-architect-decision|json)\s*([\s\S]*?)```/i);
8848
8885
  if (fence?.[1]) return fence[1].trim();
8849
- const line = finalText.match(/KODY_COMPANY_MANAGER_DECISION=(\{[\s\S]*\})/);
8886
+ const line = finalText.match(/KODY_AGENCY_ARCHITECT_DECISION=(\{[\s\S]*\})/);
8850
8887
  if (line?.[1]) return line[1].trim();
8851
- throw new Error("missing kody-company-manager-decision JSON");
8888
+ throw new Error("missing kody-agency-architect-decision JSON");
8852
8889
  }
8853
8890
  function parseAction(value) {
8854
8891
  if (!value || typeof value !== "object" || Array.isArray(value)) {
8855
- throw new Error("company-manager action must be object");
8892
+ throw new Error("agency-architect action must be object");
8856
8893
  }
8857
8894
  const input = value;
8858
8895
  const kind = input.kind;
@@ -8861,7 +8898,7 @@ function parseAction(value) {
8861
8898
  if (kind === "setGoalLifecycle") return parseSetGoalLifecycle(input);
8862
8899
  if (kind === "updateIntentPortfolio") return parseUpdateIntentPortfolio(input);
8863
8900
  if (kind === "note") return parseNote(input);
8864
- throw new Error(`unsupported company-manager action kind: ${String(kind)}`);
8901
+ throw new Error(`unsupported agency-architect action kind: ${String(kind)}`);
8865
8902
  }
8866
8903
  function parseCreateManagedGoal(input) {
8867
8904
  const route = Array.isArray(input.route) ? input.route.map(parseRouteStep) : [];
@@ -8957,8 +8994,8 @@ function oneOf(value, allowed, fallback) {
8957
8994
  return typeof value === "string" && allowed.includes(value) ? value : fallback;
8958
8995
  }
8959
8996
  var SLUG_RE;
8960
- var init_companyManagerDecision = __esm({
8961
- "src/companyManagerDecision.ts"() {
8997
+ var init_agencyArchitectDecision = __esm({
8998
+ "src/agencyArchitectDecision.ts"() {
8962
8999
  "use strict";
8963
9000
  init_state2();
8964
9001
  SLUG_RE = /^[a-z][a-z0-9-]{0,63}$/;
@@ -8982,11 +9019,13 @@ function normalizeCompanyIntent(path50, raw) {
8982
9019
  if (!id || !isCompanyIntentId(id)) throw new Error(`${path50}: invalid intent id`);
8983
9020
  const createdAt = stringField3(input.createdAt) || nowIso();
8984
9021
  const updatedAt = stringField3(input.updatedAt) || createdAt;
9022
+ const description = stringField3(input.description);
8985
9023
  return {
8986
9024
  version: 1,
8987
9025
  id,
8988
9026
  status: oneOf2(input.status, ["active", "paused", "archived"], "active"),
8989
9027
  for: stringField3(input.for),
9028
+ ...description ? { description } : {},
8990
9029
  priority: numberField(input.priority, 100),
8991
9030
  posture: oneOf2(
8992
9031
  input.posture,
@@ -9105,8 +9144,8 @@ function normalizeAutomationPolicy(raw) {
9105
9144
  function normalizeManager(raw) {
9106
9145
  return {
9107
9146
  agent: "cto",
9108
- loop: "company-manager-loop",
9109
- capability: "company-manager",
9147
+ loop: "agency-architect-loop",
9148
+ capability: "agency-architect",
9110
9149
  reviewEvery: oneOf2(raw?.reviewEvery, ["1d", "1w"], "1d"),
9111
9150
  ...typeof raw?.lastReviewedAt === "string" ? { lastReviewedAt: raw.lastReviewedAt } : {}
9112
9151
  };
@@ -9122,7 +9161,7 @@ var init_companyIntent = __esm({
9122
9161
  }
9123
9162
  });
9124
9163
 
9125
- // src/scripts/applyCompanyManagerDecision.ts
9164
+ // src/scripts/applyAgencyArchitectDecision.ts
9126
9165
  function applyAction(config, cwd, action) {
9127
9166
  if (action.kind === "createManagedGoal") {
9128
9167
  const existing = fetchGoalState(config, action.id, cwd);
@@ -9191,7 +9230,7 @@ function applied(action, changed, reason, resource) {
9191
9230
  function mergeUnique(left, right) {
9192
9231
  return [.../* @__PURE__ */ new Set([...left, ...right])].sort();
9193
9232
  }
9194
- function logAppliedCompanyManagerActions(config, cwd, appliedActions) {
9233
+ function logAppliedAgencyArchitectActions(config, cwd, appliedActions) {
9195
9234
  const at = nowIso();
9196
9235
  for (const action of appliedActions) {
9197
9236
  if (!action.intentId) continue;
@@ -9206,24 +9245,24 @@ function logAppliedCompanyManagerActions(config, cwd, appliedActions) {
9206
9245
  });
9207
9246
  }
9208
9247
  }
9209
- var applyCompanyManagerDecision;
9210
- var init_applyCompanyManagerDecision = __esm({
9211
- "src/scripts/applyCompanyManagerDecision.ts"() {
9248
+ var applyAgencyArchitectDecision;
9249
+ var init_applyAgencyArchitectDecision = __esm({
9250
+ "src/scripts/applyAgencyArchitectDecision.ts"() {
9212
9251
  "use strict";
9213
- init_companyManagerDecision();
9252
+ init_agencyArchitectDecision();
9214
9253
  init_companyIntent();
9215
9254
  init_stateStore();
9216
9255
  init_state2();
9217
- applyCompanyManagerDecision = async (ctx) => {
9218
- const decision = ctx.data.companyManagerDecision;
9256
+ applyAgencyArchitectDecision = async (ctx) => {
9257
+ const decision = ctx.data.agencyArchitectDecision;
9219
9258
  if (!decision || !Array.isArray(decision.actions)) return;
9220
9259
  if (ctx.output.exitCode !== 0) return;
9221
9260
  const applied2 = [];
9222
9261
  for (const action of decision.actions) {
9223
9262
  applied2.push(applyAction(ctx.config, ctx.cwd, action));
9224
9263
  }
9225
- ctx.data.companyManagerApplied = applied2;
9226
- ctx.data.companyManagerApplySummary = `company-manager applied ${applied2.filter((item) => item.changed).length}/${applied2.length} action(s)`;
9264
+ ctx.data.agencyArchitectApplied = applied2;
9265
+ ctx.data.agencyArchitectApplySummary = `agency-architect applied ${applied2.filter((item) => item.changed).length}/${applied2.length} action(s)`;
9227
9266
  };
9228
9267
  }
9229
9268
  });
@@ -9233,15 +9272,15 @@ var appendCompanyIntentDecision2;
9233
9272
  var init_appendCompanyIntentDecision = __esm({
9234
9273
  "src/scripts/appendCompanyIntentDecision.ts"() {
9235
9274
  "use strict";
9236
- init_applyCompanyManagerDecision();
9275
+ init_applyAgencyArchitectDecision();
9237
9276
  appendCompanyIntentDecision2 = async (ctx) => {
9238
- const applied2 = ctx.data.companyManagerApplied;
9277
+ const applied2 = ctx.data.agencyArchitectApplied;
9239
9278
  if (!applied2 || applied2.length === 0) return;
9240
9279
  try {
9241
- logAppliedCompanyManagerActions(ctx.config, ctx.cwd, applied2);
9280
+ logAppliedAgencyArchitectActions(ctx.config, ctx.cwd, applied2);
9242
9281
  } catch (err) {
9243
9282
  process.stderr.write(
9244
- `[company-manager] failed append intent decision log: ${err instanceof Error ? err.message : String(err)}
9283
+ `[agency-architect] failed append intent decision log: ${err instanceof Error ? err.message : String(err)}
9245
9284
  `
9246
9285
  );
9247
9286
  }
@@ -13074,10 +13113,16 @@ var init_loadCapabilityState = __esm({
13074
13113
  `loadCapabilityState: capability '${slug2}' declared capabilityTools not in the kody-capability palette: ${unknown.join(", ")}. Available: ${[...CAPABILITY_MCP_TOOL_NAMES].join(", ")}`
13075
13114
  );
13076
13115
  }
13116
+ const mode = profile.capabilityToolMode ?? "lock";
13077
13117
  ctx.data.capabilityTools = declaredTools;
13118
+ ctx.data.capabilityToolMode = mode;
13078
13119
  ctx.data.capabilityToolsList = declaredTools.map((name) => `- \`${name}\``).join("\n");
13079
13120
  ctx.data.capabilityOperatorMention = mentions;
13080
13121
  const mcpToolNames = declaredTools.map((name) => `mcp__kody-capability__${name}`);
13122
+ if (mode === "append") {
13123
+ profile.claudeCode.tools = [.../* @__PURE__ */ new Set([...profile.claudeCode.tools ?? [], ...mcpToolNames])];
13124
+ return;
13125
+ }
13081
13126
  profile.claudeCode.tools = [...mcpToolNames, "mcp__kody-submit__submit_state"];
13082
13127
  profile.claudeCode.enableSubmitTool = true;
13083
13128
  }
@@ -13289,8 +13334,8 @@ var CAPABILITY_TOOL_PALETTE2, loadJobFromFile;
13289
13334
  var init_loadJobFromFile = __esm({
13290
13335
  "src/scripts/loadJobFromFile.ts"() {
13291
13336
  "use strict";
13292
- init_capabilityMcp();
13293
13337
  init_agents();
13338
+ init_capabilityMcp();
13294
13339
  init_registry();
13295
13340
  init_jobState();
13296
13341
  CAPABILITY_TOOL_PALETTE2 = new Set(CAPABILITY_MCP_TOOL_NAMES);
@@ -13304,9 +13349,7 @@ var init_loadJobFromFile = __esm({
13304
13349
  }
13305
13350
  const capability = resolveCapabilityFolder(slug2, path33.join(ctx.cwd, jobsDir));
13306
13351
  if (!capability) {
13307
- throw new Error(
13308
- `loadJobFromFile: capability folder not found or incomplete: ${path33.join(ctx.cwd, jobsDir, slug2)}`
13309
- );
13352
+ throw new Error(`loadJobFromFile: capability folder not found or incomplete: ${path33.join(ctx.cwd, jobsDir, slug2)}`);
13310
13353
  }
13311
13354
  const { title, body, config } = capability;
13312
13355
  const mentions = (config.mentions ?? []).map((login) => `@${login}`).join(" ");
@@ -13351,10 +13394,16 @@ var init_loadJobFromFile = __esm({
13351
13394
  );
13352
13395
  }
13353
13396
  const mcpToolNames = declaredTools.map((name) => `mcp__kody-capability__${name}`);
13354
- profile.claudeCode.tools = [...mcpToolNames, "mcp__kody-submit__submit_state"];
13397
+ const mode = config.capabilityToolMode ?? "lock";
13398
+ if (mode === "append") {
13399
+ profile.claudeCode.tools = [.../* @__PURE__ */ new Set([...profile.claudeCode.tools ?? [], ...mcpToolNames])];
13400
+ } else {
13401
+ profile.claudeCode.tools = [...mcpToolNames, "mcp__kody-submit__submit_state"];
13402
+ ctx.data.promptTemplate = "prompts/locked.md";
13403
+ }
13355
13404
  ctx.data.capabilityTools = declaredTools;
13405
+ ctx.data.capabilityToolMode = mode;
13356
13406
  ctx.data.capabilityOperatorMention = mentions;
13357
- ctx.data.promptTemplate = "prompts/locked.md";
13358
13407
  ctx.data.capabilityToolsList = declaredTools.map((name) => `- \`${name}\``).join("\n");
13359
13408
  }
13360
13409
  };
@@ -13424,155 +13473,489 @@ var init_kodyVariables = __esm({
13424
13473
  }
13425
13474
  });
13426
13475
 
13427
- // src/scripts/loadQaContext.ts
13428
- import * as fs37 from "fs";
13429
- import * as path35 from "path";
13430
- function parseSlugList(value) {
13431
- const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
13432
- return inner.split(",").map(
13433
- (s) => s.trim().replace(/^["']|["']$/g, "").toLowerCase()
13434
- ).filter(Boolean);
13476
+ // src/pool/keys.ts
13477
+ import { hkdfSync } from "crypto";
13478
+ function masterKeyBytes(raw) {
13479
+ const v = raw.trim();
13480
+ if (!v) throw new Error("KODY_MASTER_KEY is empty");
13481
+ if (/^[0-9a-fA-F]+$/.test(v) && v.length === 64) {
13482
+ return Buffer.from(v, "hex");
13483
+ }
13484
+ return Buffer.from(v.replace(/-/g, "+").replace(/_/g, "/"), "base64");
13435
13485
  }
13436
- function readProfileAgents(raw) {
13437
- const m = FRONTMATTER_RE.exec(raw);
13438
- if (!m) return { agent: ["kody"], body: raw };
13439
- const body = raw.slice(m[0].length);
13440
- let agent = null;
13441
- let legacy = null;
13442
- for (const line of (m[1] ?? "").split(/\r?\n/)) {
13443
- const t = line.trim();
13444
- const c = t.indexOf(":");
13445
- if (c < 0) continue;
13446
- const key = t.slice(0, c).trim();
13447
- const value = t.slice(c + 1).trim();
13448
- if (key === "agent") {
13449
- agent = parseSlugList(value);
13450
- } else if (key === "audience" || key === "for") {
13451
- const mapped = parseSlugList(value).map((tok) => LEGACY_AUDIENCE_TO_AGENT[tok]).filter(Boolean);
13452
- if (mapped.length > 0) legacy = mapped;
13453
- }
13486
+ function deriveKey(master, info, length = 32) {
13487
+ return Buffer.from(hkdfSync("sha256", master, Buffer.alloc(0), info, length)).toString("hex");
13488
+ }
13489
+ function derivePoolApiKey(master) {
13490
+ return deriveKey(master, POOL_API_KEY_INFO);
13491
+ }
13492
+ function deriveRunnerApiKey(master) {
13493
+ return deriveKey(master, RUNNER_API_KEY_INFO);
13494
+ }
13495
+ function bearerOk(headerAuth, xApiKey, expected) {
13496
+ const x = (xApiKey ?? "").trim();
13497
+ if (x && timingEqual(x, expected)) return true;
13498
+ const a = (headerAuth ?? "").trim();
13499
+ if (a.toLowerCase().startsWith("bearer ")) {
13500
+ return timingEqual(a.slice(7).trim(), expected);
13454
13501
  }
13455
- return { agent: agent ?? legacy ?? ["kody"], body };
13502
+ return false;
13456
13503
  }
13457
- function readProfile(cwd) {
13458
- const dir = path35.join(cwd, CONTEXT_DIR_REL_PATH);
13459
- if (!fs37.existsSync(dir)) return "";
13460
- let entries;
13461
- try {
13462
- entries = fs37.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
13463
- } catch {
13464
- return "";
13504
+ function timingEqual(a, b) {
13505
+ if (a.length !== b.length) return false;
13506
+ let diff = 0;
13507
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
13508
+ return diff === 0;
13509
+ }
13510
+ var POOL_API_KEY_INFO, RUNNER_API_KEY_INFO;
13511
+ var init_keys = __esm({
13512
+ "src/pool/keys.ts"() {
13513
+ "use strict";
13514
+ POOL_API_KEY_INFO = "kody-pool-api:v1";
13515
+ RUNNER_API_KEY_INFO = "kody-runner-api:v1";
13465
13516
  }
13466
- const blocks = [];
13467
- for (const file of entries) {
13468
- try {
13469
- const raw = fs37.readFileSync(path35.join(dir, file), "utf-8");
13470
- const { agent, body } = readProfileAgents(raw);
13471
- if (!agent.includes(QA_AGENT) && !agent.includes(ALL_AGENTS)) continue;
13472
- blocks.push(`## ${file}
13517
+ });
13473
13518
 
13474
- ${body.trim()}`);
13475
- } catch {
13476
- }
13477
- }
13478
- return blocks.join("\n\n");
13519
+ // src/stateRepoGithub.ts
13520
+ import * as fs37 from "fs";
13521
+ import * as path35 from "path";
13522
+ function recordValue3(value) {
13523
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
13479
13524
  }
13480
- function composeAuthBlock(authProfile, login, password) {
13481
- if (authProfile && authProfile.trim().length > 0) {
13482
- return `Auth: a saved Playwright \`storageState.json\` is available at \`${authProfile}\`. Pass it to the browser via the \`storageState\` parameter so the session starts pre-authenticated.`;
13525
+ function contentsUrl(owner, repo, filePath) {
13526
+ const encodedPath = filePath.split("/").filter(Boolean).map((part) => encodeURIComponent(part)).join("/");
13527
+ return `${GITHUB_API}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/contents/${encodedPath}`;
13528
+ }
13529
+ async function githubContentsFile(opts) {
13530
+ const doFetch = opts.fetchImpl ?? fetch;
13531
+ const res = await doFetch(contentsUrl(opts.owner, opts.repo, opts.path), {
13532
+ headers: {
13533
+ Authorization: `Bearer ${opts.githubToken}`,
13534
+ Accept: "application/vnd.github+json",
13535
+ "X-GitHub-Api-Version": "2022-11-28",
13536
+ "User-Agent": "kody-engine"
13537
+ },
13538
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
13539
+ });
13540
+ if (res.status === 404) return null;
13541
+ if (!res.ok) {
13542
+ throw new Error(
13543
+ `GitHub ${opts.owner}/${opts.repo}:${opts.path}: ${res.status} ${(await res.text().catch(() => "")).slice(0, 160)}`
13544
+ );
13483
13545
  }
13484
- if (login && password) {
13485
- return `Auth: log in once at the app's login page. Username: \`${login}\` \xB7 Password: \`${password}\`. Type each field key-by-key (Playwright \`locator.pressSequentially()\` / the MCP \`browser_type\` tool), NOT a one-shot \`fill()\` or value assignment: pasting a value in a single step often fails to fire the login form's framework onChange handler, so the form submits empty and you get a FALSE "invalid email or password". After typing, confirm the field shows the value before clicking submit; if the first attempt is rejected, re-type key-by-key before treating the credentials as wrong. If a login form's inputs don't respond to typing-by-label/placeholder, inspect the DOM and target them by their \`id\`/\`name\` attribute instead \u2014 e.g. a Payload CMS admin login at \`/admin\` uses \`#field-email\` and \`#field-password\`. The app may have TWO separate logins (a public/frontend one and a Payload \`/admin\` one); if a change you must verify lives behind the admin, log into that form too. Re-use the session afterwards.`;
13546
+ return await res.json();
13547
+ }
13548
+ function decodeContentsFile(file, label) {
13549
+ if (file.type !== "file" || file.encoding !== "base64" || typeof file.content !== "string") {
13550
+ throw new Error(`${label} is not a base64 file`);
13486
13551
  }
13487
- if (login) {
13488
- return `Auth: username \`${login}\` is configured but no \`LOGIN_PASSWORD\` secret was found. Note auth-gated surfaces as gaps.`;
13552
+ if (typeof file.sha !== "string") {
13553
+ throw new Error(`${label} response missing sha`);
13489
13554
  }
13490
- return "Auth: no QA credentials configured (set the `LOGIN_USER` variable and the `LOGIN_PASSWORD` vault secret). Browse public routes only; note auth-gated surfaces as gaps.";
13555
+ return {
13556
+ path: label,
13557
+ content: Buffer.from(file.content, "base64").toString("utf-8"),
13558
+ sha: file.sha
13559
+ };
13491
13560
  }
13492
- var CONTEXT_DIR_REL_PATH, QA_AGENT, ALL_AGENTS, LEGACY_AUDIENCE_TO_AGENT, FRONTMATTER_RE, loadQaContext;
13493
- var init_loadQaContext = __esm({
13494
- "src/scripts/loadQaContext.ts"() {
13495
- "use strict";
13496
- init_kodyVariables();
13497
- CONTEXT_DIR_REL_PATH = ".kody/context";
13498
- QA_AGENT = "qa-engineer";
13499
- ALL_AGENTS = "*";
13500
- LEGACY_AUDIENCE_TO_AGENT = { chat: "kody", qa: QA_AGENT };
13501
- FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
13502
- loadQaContext = async (ctx) => {
13503
- const vars = readKodyVariables(ctx.cwd);
13504
- const login = vars.LOGIN_USER ?? "";
13505
- const password = process.env.LOGIN_PASSWORD ?? "";
13506
- const authProfile = ctx.args.authProfile;
13507
- ctx.data.qaLogin = login;
13508
- ctx.data.qaProfile = readProfile(ctx.cwd);
13509
- ctx.data.qaAuthBlock = composeAuthBlock(authProfile, login, password);
13510
- };
13561
+ async function loadGithubStateConfig(opts) {
13562
+ const configFile = await githubContentsFile({
13563
+ owner: opts.owner,
13564
+ repo: opts.repo,
13565
+ path: "kody.config.json",
13566
+ githubToken: opts.githubToken,
13567
+ fetchImpl: opts.fetchImpl
13568
+ });
13569
+ let raw = {};
13570
+ if (configFile) {
13571
+ const decoded = decodeContentsFile(configFile, "kody.config.json");
13572
+ try {
13573
+ raw = JSON.parse(decoded.content);
13574
+ } catch (err) {
13575
+ throw new Error(`kody.config.json is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
13576
+ }
13511
13577
  }
13512
- });
13513
-
13514
- // src/taskContext.ts
13515
- import * as fs38 from "fs";
13516
- import * as path36 from "path";
13517
- function buildTaskContext(args) {
13578
+ const githubRaw = recordValue3(raw.github) ?? {};
13579
+ const github = {
13580
+ owner: typeof githubRaw.owner === "string" && githubRaw.owner.trim() ? githubRaw.owner.trim() : opts.owner,
13581
+ repo: typeof githubRaw.repo === "string" && githubRaw.repo.trim() ? githubRaw.repo.trim() : opts.repo
13582
+ };
13583
+ const nestedState = recordValue3(raw.state) ?? {};
13584
+ const repoRaw = typeof raw.stateRepo === "string" ? raw.stateRepo : nestedState.repo;
13585
+ const pathRaw = typeof raw.statePath === "string" ? raw.statePath : nestedState.path;
13586
+ const stateRepo = typeof repoRaw === "string" && repoRaw.trim().length > 0 ? repoRaw.trim() : `https://github.com/${github.owner}/kody-state`;
13587
+ parseStateRepoSlug(stateRepo);
13588
+ const statePath = typeof pathRaw === "string" && pathRaw.trim().length > 0 ? pathRaw.trim() : github.repo;
13518
13589
  return {
13519
- schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
13520
- builtAt: (/* @__PURE__ */ new Date()).toISOString(),
13521
- runId: args.runId,
13522
- issue: args.issue,
13523
- conventions: args.conventions ?? [],
13524
- priorArt: args.priorArt ?? "",
13525
- memoryContext: args.memoryContext ?? "",
13526
- coverageRules: args.coverageRules ?? []
13590
+ github,
13591
+ state: {
13592
+ repo: stateRepo,
13593
+ path: normalizeStatePath(statePath)
13594
+ }
13527
13595
  };
13528
13596
  }
13529
- function persistTaskContext(cwd, ctx) {
13530
- try {
13531
- const dir = runtimeStatePath(cwd, "agent-runs", ctx.runId);
13532
- fs38.mkdirSync(dir, { recursive: true });
13533
- const file = path36.join(dir, "task-context.json");
13534
- fs38.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
13535
- `);
13536
- return file;
13537
- } catch (err) {
13538
- const msg = err instanceof Error ? err.message : String(err);
13539
- process.stderr.write(`[kody taskContext] persist failed: ${msg}
13540
- `);
13541
- return null;
13542
- }
13597
+ async function readGithubStateText(opts) {
13598
+ const config = await loadGithubStateConfig(opts);
13599
+ return readGithubStateTextWithConfig({
13600
+ config,
13601
+ filePath: opts.filePath,
13602
+ githubToken: opts.githubToken,
13603
+ fetchImpl: opts.fetchImpl
13604
+ });
13543
13605
  }
13544
- var TASK_CONTEXT_SCHEMA_VERSION;
13545
- var init_taskContext = __esm({
13546
- "src/taskContext.ts"() {
13547
- "use strict";
13548
- init_runtimePaths();
13549
- TASK_CONTEXT_SCHEMA_VERSION = 1;
13550
- }
13551
- });
13552
-
13553
- // src/scripts/loadTaskContext.ts
13554
- var loadTaskContext;
13555
- var init_loadTaskContext = __esm({
13556
- "src/scripts/loadTaskContext.ts"() {
13557
- "use strict";
13558
- init_events();
13559
- init_taskContext();
13560
- loadTaskContext = async (ctx) => {
13561
- const runId = resolveRunId();
13562
- const rawIssue = ctx.data.issue;
13563
- const issue = rawIssue ? {
13564
- ...rawIssue,
13565
- commentsFormatted: rawIssue.commentsFormatted ?? "",
13566
- labelsFormatted: rawIssue.labelsFormatted ?? ""
13567
- } : void 0;
13568
- const taskContext = buildTaskContext({
13569
- runId,
13570
- issue,
13571
- conventions: ctx.data.conventions,
13572
- priorArt: typeof ctx.data.priorArt === "string" ? ctx.data.priorArt : "",
13573
- memoryContext: typeof ctx.data.memoryContext === "string" ? ctx.data.memoryContext : "",
13574
- coverageRules: ctx.data.coverageRules
13575
- });
13606
+ async function readGithubStateTextWithConfig(opts) {
13607
+ const target = stateRepoPath(opts.config, opts.filePath);
13608
+ const parsed = parseStateRepo(opts.config);
13609
+ const file = await githubContentsFile({
13610
+ owner: parsed.owner,
13611
+ repo: parsed.repo,
13612
+ path: target,
13613
+ githubToken: opts.githubToken,
13614
+ fetchImpl: opts.fetchImpl
13615
+ });
13616
+ return file ? decodeContentsFile(file, target) : null;
13617
+ }
13618
+ async function writeGithubStateTextWithConfig(opts) {
13619
+ const target = stateRepoPath(opts.config, opts.filePath);
13620
+ const parsed = parseStateRepo(opts.config);
13621
+ const payload = {
13622
+ message: opts.message,
13623
+ content: Buffer.from(opts.content, "utf-8").toString("base64")
13624
+ };
13625
+ if (opts.sha) payload.sha = opts.sha;
13626
+ const doFetch = opts.fetchImpl ?? fetch;
13627
+ const res = await doFetch(contentsUrl(parsed.owner, parsed.repo, target), {
13628
+ method: "PUT",
13629
+ headers: {
13630
+ Authorization: `Bearer ${opts.githubToken}`,
13631
+ Accept: "application/vnd.github+json",
13632
+ "Content-Type": "application/json",
13633
+ "X-GitHub-Api-Version": "2022-11-28",
13634
+ "User-Agent": "kody-engine"
13635
+ },
13636
+ body: JSON.stringify(payload),
13637
+ signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
13638
+ });
13639
+ if (!res.ok) {
13640
+ throw new Error(
13641
+ `GitHub write ${parsed.owner}/${parsed.repo}:${target}: ${res.status} ${(await res.text().catch(() => "")).slice(0, 160)}`
13642
+ );
13643
+ }
13644
+ }
13645
+ function jsonlLines(text) {
13646
+ return text.split("\n").filter((line) => line.length > 0);
13647
+ }
13648
+ function renderJsonl(lines) {
13649
+ return lines.length > 0 ? `${lines.join("\n")}
13650
+ ` : "";
13651
+ }
13652
+ function mergeJsonl(localText, remoteText) {
13653
+ const remoteLines = jsonlLines(remoteText);
13654
+ const seen = new Set(remoteLines);
13655
+ const localOnly = jsonlLines(localText).filter((line) => !seen.has(line));
13656
+ return renderJsonl([...remoteLines, ...localOnly]);
13657
+ }
13658
+ async function syncJsonlFileFromGithubState(opts) {
13659
+ const remote = await readGithubStateTextWithConfig(opts);
13660
+ if (!remote) return;
13661
+ const local = fs37.existsSync(opts.localPath) ? fs37.readFileSync(opts.localPath, "utf-8") : "";
13662
+ const next = mergeJsonl(local, remote.content);
13663
+ if (next === local) return;
13664
+ fs37.mkdirSync(path35.dirname(opts.localPath), { recursive: true });
13665
+ fs37.writeFileSync(opts.localPath, next);
13666
+ }
13667
+ async function persistJsonlFileToGithubState(opts) {
13668
+ if (!fs37.existsSync(opts.localPath)) return;
13669
+ const localText = fs37.readFileSync(opts.localPath, "utf-8");
13670
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
13671
+ const remote = await readGithubStateTextWithConfig(opts);
13672
+ const body = mergeJsonl(localText, remote?.content ?? "");
13673
+ try {
13674
+ await writeGithubStateTextWithConfig({
13675
+ config: opts.config,
13676
+ filePath: opts.filePath,
13677
+ content: body,
13678
+ message: opts.message,
13679
+ githubToken: opts.githubToken,
13680
+ sha: remote?.sha,
13681
+ fetchImpl: opts.fetchImpl
13682
+ });
13683
+ return;
13684
+ } catch (err) {
13685
+ const msg = err instanceof Error ? err.message : String(err);
13686
+ const conflict = /409|422|does not match|is at|but expected/i.test(msg);
13687
+ if (!conflict || attempt === 3) throw err;
13688
+ }
13689
+ }
13690
+ }
13691
+ var GITHUB_API, REQ_TIMEOUT_MS;
13692
+ var init_stateRepoGithub = __esm({
13693
+ "src/stateRepoGithub.ts"() {
13694
+ "use strict";
13695
+ init_stateRepo();
13696
+ GITHUB_API = "https://api.github.com";
13697
+ REQ_TIMEOUT_MS = 3e4;
13698
+ }
13699
+ });
13700
+
13701
+ // src/stateRepoVault.ts
13702
+ import { createDecipheriv, createHash as createHash3 } from "crypto";
13703
+ function cacheKey2(owner, repo, masterKey) {
13704
+ const keyHash = createHash3("sha256").update(masterKey).digest("hex").slice(0, 16);
13705
+ return `${owner}/${repo}:${keyHash}`.toLowerCase();
13706
+ }
13707
+ function decryptVault(payload, masterKey) {
13708
+ const parts = payload.split(":");
13709
+ if (parts.length !== 4 || parts[0] !== "v1") {
13710
+ throw new Error("invalid vault payload format");
13711
+ }
13712
+ const [, ivB64, ctB64, tagB64] = parts;
13713
+ const iv = Buffer.from(ivB64, "base64");
13714
+ const ct = Buffer.from(ctB64, "base64");
13715
+ const tag = Buffer.from(tagB64, "base64");
13716
+ const decipher = createDecipheriv("aes-256-gcm", masterKey, iv);
13717
+ decipher.setAuthTag(tag);
13718
+ return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
13719
+ }
13720
+ async function readVaultSecrets(opts) {
13721
+ const key = cacheKey2(opts.owner, opts.repo, opts.masterKey);
13722
+ const hit = cache.get(key);
13723
+ if (hit && hit.expiresAt > Date.now()) return hit.secrets;
13724
+ const file = await readGithubStateText({
13725
+ owner: opts.owner,
13726
+ repo: opts.repo,
13727
+ filePath: VAULT_PATH,
13728
+ githubToken: opts.githubToken,
13729
+ fetchImpl: opts.fetchImpl
13730
+ });
13731
+ if (!file) {
13732
+ cache.set(key, { secrets: {}, expiresAt: Date.now() + CACHE_TTL_MS });
13733
+ return {};
13734
+ }
13735
+ const ciphertext = file.content.trim();
13736
+ const doc = JSON.parse(decryptVault(ciphertext, opts.masterKey));
13737
+ const flat = {};
13738
+ for (const [name, entry] of Object.entries(doc.secrets ?? {})) {
13739
+ if (entry && typeof entry.value === "string") flat[name] = entry.value;
13740
+ }
13741
+ cache.set(key, { secrets: flat, expiresAt: Date.now() + CACHE_TTL_MS });
13742
+ return flat;
13743
+ }
13744
+ async function readRepoSecret(opts) {
13745
+ const secrets = await readVaultSecrets(opts);
13746
+ const v = secrets[opts.name];
13747
+ return v?.trim() ? v : null;
13748
+ }
13749
+ async function readRepoSecrets(opts) {
13750
+ return readVaultSecrets(opts);
13751
+ }
13752
+ var VAULT_PATH, CACHE_TTL_MS, cache;
13753
+ var init_stateRepoVault = __esm({
13754
+ "src/stateRepoVault.ts"() {
13755
+ "use strict";
13756
+ init_stateRepoGithub();
13757
+ VAULT_PATH = "secrets.enc";
13758
+ CACHE_TTL_MS = 6e4;
13759
+ cache = /* @__PURE__ */ new Map();
13760
+ }
13761
+ });
13762
+
13763
+ // src/scripts/runtimeSecrets.ts
13764
+ function tokenFromEnv(env) {
13765
+ return (env.KODY_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN ?? env.GH_PAT ?? "").trim();
13766
+ }
13767
+ function envSecret(name, env) {
13768
+ const value = env[name]?.trim() ? env[name] : "";
13769
+ return value ? { value, source: "env" } : { value: "", source: "missing" };
13770
+ }
13771
+ async function resolveRuntimeSecret(name, ctx, opts = {}) {
13772
+ const env = opts.env ?? process.env;
13773
+ const masterRaw = env.KODY_MASTER_KEY?.trim() ?? "";
13774
+ const githubToken2 = tokenFromEnv(env);
13775
+ if (!masterRaw || !githubToken2) return envSecret(name, env);
13776
+ try {
13777
+ const masterKey = masterKeyBytes(masterRaw);
13778
+ if (masterKey.length !== 32) {
13779
+ throw new Error("KODY_MASTER_KEY must decode to 32 bytes");
13780
+ }
13781
+ const value = await readRepoSecret({
13782
+ owner: ctx.config.github.owner,
13783
+ repo: ctx.config.github.repo,
13784
+ name,
13785
+ githubToken: githubToken2,
13786
+ masterKey,
13787
+ fetchImpl: opts.fetchImpl
13788
+ });
13789
+ if (value) return { value, source: "vault" };
13790
+ } catch (err) {
13791
+ const fallback = envSecret(name, env);
13792
+ return {
13793
+ ...fallback,
13794
+ warning: `vault read failed for ${name}: ${err instanceof Error ? err.message : String(err)}`
13795
+ };
13796
+ }
13797
+ return envSecret(name, env);
13798
+ }
13799
+ var init_runtimeSecrets = __esm({
13800
+ "src/scripts/runtimeSecrets.ts"() {
13801
+ "use strict";
13802
+ init_keys();
13803
+ init_stateRepoVault();
13804
+ }
13805
+ });
13806
+
13807
+ // src/scripts/loadQaContext.ts
13808
+ import * as fs38 from "fs";
13809
+ import * as path36 from "path";
13810
+ function parseSlugList(value) {
13811
+ const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
13812
+ return inner.split(",").map(
13813
+ (s) => s.trim().replace(/^["']|["']$/g, "").toLowerCase()
13814
+ ).filter(Boolean);
13815
+ }
13816
+ function readProfileAgents(raw) {
13817
+ const m = FRONTMATTER_RE.exec(raw);
13818
+ if (!m) return { agent: ["kody"], body: raw };
13819
+ const body = raw.slice(m[0].length);
13820
+ let agent = null;
13821
+ let legacy = null;
13822
+ for (const line of (m[1] ?? "").split(/\r?\n/)) {
13823
+ const t = line.trim();
13824
+ const c = t.indexOf(":");
13825
+ if (c < 0) continue;
13826
+ const key = t.slice(0, c).trim();
13827
+ const value = t.slice(c + 1).trim();
13828
+ if (key === "agent") {
13829
+ agent = parseSlugList(value);
13830
+ } else if (key === "audience" || key === "for") {
13831
+ const mapped = parseSlugList(value).map((tok) => LEGACY_AUDIENCE_TO_AGENT[tok]).filter(Boolean);
13832
+ if (mapped.length > 0) legacy = mapped;
13833
+ }
13834
+ }
13835
+ return { agent: agent ?? legacy ?? ["kody"], body };
13836
+ }
13837
+ function readProfile(cwd) {
13838
+ const dir = path36.join(cwd, CONTEXT_DIR_REL_PATH);
13839
+ if (!fs38.existsSync(dir)) return "";
13840
+ let entries;
13841
+ try {
13842
+ entries = fs38.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
13843
+ } catch {
13844
+ return "";
13845
+ }
13846
+ const blocks = [];
13847
+ for (const file of entries) {
13848
+ try {
13849
+ const raw = fs38.readFileSync(path36.join(dir, file), "utf-8");
13850
+ const { agent, body } = readProfileAgents(raw);
13851
+ if (!agent.includes(QA_AGENT) && !agent.includes(ALL_AGENTS)) continue;
13852
+ blocks.push(`## ${file}
13853
+
13854
+ ${body.trim()}`);
13855
+ } catch {
13856
+ }
13857
+ }
13858
+ return blocks.join("\n\n");
13859
+ }
13860
+ function composeAuthBlock(authProfile, login, password) {
13861
+ if (authProfile && authProfile.trim().length > 0) {
13862
+ return `Auth: a saved Playwright \`storageState.json\` is available at \`${authProfile}\`. Pass it to the browser via the \`storageState\` parameter so the session starts pre-authenticated.`;
13863
+ }
13864
+ if (login && password) {
13865
+ return `Auth: log in once at the app's login page. Username: \`${login}\` \xB7 Password: \`${password}\`. Type each field key-by-key (Playwright \`locator.pressSequentially()\` / the MCP \`browser_type\` tool), NOT a one-shot \`fill()\` or value assignment: pasting a value in a single step often fails to fire the login form's framework onChange handler, so the form submits empty and you get a FALSE "invalid email or password". After typing, confirm the field shows the value before clicking submit; if the first attempt is rejected, re-type key-by-key before treating the credentials as wrong. If a login form's inputs don't respond to typing-by-label/placeholder, inspect the DOM and target them by their \`id\`/\`name\` attribute instead \u2014 e.g. a Payload CMS admin login at \`/admin\` uses \`#field-email\` and \`#field-password\`. The app may have TWO separate logins (a public/frontend one and a Payload \`/admin\` one); if a change you must verify lives behind the admin, log into that form too. Re-use the session afterwards.`;
13866
+ }
13867
+ if (login) {
13868
+ return `Auth: username \`${login}\` is configured but no \`LOGIN_PASSWORD\` secret was found. Note auth-gated surfaces as gaps.`;
13869
+ }
13870
+ return "Auth: no QA credentials configured (set the `LOGIN_USER` variable and the `LOGIN_PASSWORD` vault secret). Browse public routes only; note auth-gated surfaces as gaps.";
13871
+ }
13872
+ var CONTEXT_DIR_REL_PATH, QA_AGENT, ALL_AGENTS, LEGACY_AUDIENCE_TO_AGENT, FRONTMATTER_RE, loadQaContext;
13873
+ var init_loadQaContext = __esm({
13874
+ "src/scripts/loadQaContext.ts"() {
13875
+ "use strict";
13876
+ init_kodyVariables();
13877
+ init_runtimeSecrets();
13878
+ CONTEXT_DIR_REL_PATH = ".kody/context";
13879
+ QA_AGENT = "qa-engineer";
13880
+ ALL_AGENTS = "*";
13881
+ LEGACY_AUDIENCE_TO_AGENT = { chat: "kody", qa: QA_AGENT };
13882
+ FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
13883
+ loadQaContext = async (ctx) => {
13884
+ const vars = readKodyVariables(ctx.cwd);
13885
+ const login = vars.LOGIN_USER ?? "";
13886
+ const password = await resolveRuntimeSecret("LOGIN_PASSWORD", ctx);
13887
+ const authProfile = ctx.args.authProfile;
13888
+ ctx.data.qaLogin = login;
13889
+ ctx.data.qaPasswordSource = password.source;
13890
+ if (password.warning) ctx.data.qaPasswordWarning = password.warning;
13891
+ ctx.data.qaProfile = readProfile(ctx.cwd);
13892
+ ctx.data.qaAuthBlock = composeAuthBlock(authProfile, login, password.value);
13893
+ };
13894
+ }
13895
+ });
13896
+
13897
+ // src/taskContext.ts
13898
+ import * as fs39 from "fs";
13899
+ import * as path37 from "path";
13900
+ function buildTaskContext(args) {
13901
+ return {
13902
+ schemaVersion: TASK_CONTEXT_SCHEMA_VERSION,
13903
+ builtAt: (/* @__PURE__ */ new Date()).toISOString(),
13904
+ runId: args.runId,
13905
+ issue: args.issue,
13906
+ conventions: args.conventions ?? [],
13907
+ priorArt: args.priorArt ?? "",
13908
+ memoryContext: args.memoryContext ?? "",
13909
+ coverageRules: args.coverageRules ?? []
13910
+ };
13911
+ }
13912
+ function persistTaskContext(cwd, ctx) {
13913
+ try {
13914
+ const dir = runtimeStatePath(cwd, "agent-runs", ctx.runId);
13915
+ fs39.mkdirSync(dir, { recursive: true });
13916
+ const file = path37.join(dir, "task-context.json");
13917
+ fs39.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
13918
+ `);
13919
+ return file;
13920
+ } catch (err) {
13921
+ const msg = err instanceof Error ? err.message : String(err);
13922
+ process.stderr.write(`[kody taskContext] persist failed: ${msg}
13923
+ `);
13924
+ return null;
13925
+ }
13926
+ }
13927
+ var TASK_CONTEXT_SCHEMA_VERSION;
13928
+ var init_taskContext = __esm({
13929
+ "src/taskContext.ts"() {
13930
+ "use strict";
13931
+ init_runtimePaths();
13932
+ TASK_CONTEXT_SCHEMA_VERSION = 1;
13933
+ }
13934
+ });
13935
+
13936
+ // src/scripts/loadTaskContext.ts
13937
+ var loadTaskContext;
13938
+ var init_loadTaskContext = __esm({
13939
+ "src/scripts/loadTaskContext.ts"() {
13940
+ "use strict";
13941
+ init_events();
13942
+ init_taskContext();
13943
+ loadTaskContext = async (ctx) => {
13944
+ const runId = resolveRunId();
13945
+ const rawIssue = ctx.data.issue;
13946
+ const issue = rawIssue ? {
13947
+ ...rawIssue,
13948
+ commentsFormatted: rawIssue.commentsFormatted ?? "",
13949
+ labelsFormatted: rawIssue.labelsFormatted ?? ""
13950
+ } : void 0;
13951
+ const taskContext = buildTaskContext({
13952
+ runId,
13953
+ issue,
13954
+ conventions: ctx.data.conventions,
13955
+ priorArt: typeof ctx.data.priorArt === "string" ? ctx.data.priorArt : "",
13956
+ memoryContext: typeof ctx.data.memoryContext === "string" ? ctx.data.memoryContext : "",
13957
+ coverageRules: ctx.data.coverageRules
13958
+ });
13576
13959
  ctx.data.taskContext = taskContext;
13577
13960
  const persistedPath = persistTaskContext(ctx.cwd, taskContext);
13578
13961
  if (persistedPath && ctx.verbose) {
@@ -14302,32 +14685,32 @@ var init_parseAgentResult = __esm({
14302
14685
  }
14303
14686
  });
14304
14687
 
14305
- // src/scripts/parseCompanyManagerDecision.ts
14688
+ // src/scripts/parseAgencyArchitectDecision.ts
14306
14689
  function makeAction3(type, payload) {
14307
14690
  return { type, payload, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
14308
14691
  }
14309
- var parseCompanyManagerDecision;
14310
- var init_parseCompanyManagerDecision = __esm({
14311
- "src/scripts/parseCompanyManagerDecision.ts"() {
14692
+ var parseAgencyArchitectDecision;
14693
+ var init_parseAgencyArchitectDecision = __esm({
14694
+ "src/scripts/parseAgencyArchitectDecision.ts"() {
14312
14695
  "use strict";
14313
- init_companyManagerDecision();
14314
- parseCompanyManagerDecision = async (ctx, _profile, agentResult) => {
14696
+ init_agencyArchitectDecision();
14697
+ parseAgencyArchitectDecision = async (ctx, _profile, agentResult) => {
14315
14698
  if (!agentResult) {
14316
- ctx.data.companyManagerDecision = { summary: "", actions: [] };
14317
- ctx.data.action = makeAction3("COMPANY_MANAGER_NOT_RUN", { reason: "no agent result" });
14699
+ ctx.data.agencyArchitectDecision = { summary: "", actions: [] };
14700
+ ctx.data.action = makeAction3("AGENCY_ARCHITECT_NOT_RUN", { reason: "no agent result" });
14318
14701
  return;
14319
14702
  }
14320
14703
  try {
14321
- const decision = parseCompanyManagerDecisionText(agentResult.finalText);
14322
- ctx.data.companyManagerDecision = decision;
14323
- ctx.data.action = makeAction3("COMPANY_MANAGER_DECIDED", {
14704
+ const decision = parseAgencyArchitectDecisionText(agentResult.finalText);
14705
+ ctx.data.agencyArchitectDecision = decision;
14706
+ ctx.data.action = makeAction3("AGENCY_ARCHITECT_DECIDED", {
14324
14707
  summary: decision.summary,
14325
14708
  actionCount: decision.actions.length
14326
14709
  });
14327
14710
  } catch (err) {
14328
14711
  const reason = err instanceof Error ? err.message : String(err);
14329
- ctx.data.companyManagerDecisionError = reason;
14330
- ctx.data.action = makeAction3("COMPANY_MANAGER_FAILED", { reason });
14712
+ ctx.data.agencyArchitectDecisionError = reason;
14713
+ ctx.data.action = makeAction3("AGENCY_ARCHITECT_FAILED", { reason });
14331
14714
  ctx.output.exitCode = 1;
14332
14715
  ctx.output.reason = reason;
14333
14716
  }
@@ -15826,192 +16209,10 @@ var init_runFlow = __esm({
15826
16209
  }
15827
16210
  });
15828
16211
 
15829
- // src/stateRepoGithub.ts
15830
- import * as fs39 from "fs";
15831
- import * as path37 from "path";
15832
- function recordValue3(value) {
15833
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
15834
- }
15835
- function contentsUrl(owner, repo, filePath) {
15836
- const encodedPath = filePath.split("/").filter(Boolean).map((part) => encodeURIComponent(part)).join("/");
15837
- return `${GITHUB_API}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/contents/${encodedPath}`;
15838
- }
15839
- async function githubContentsFile(opts) {
15840
- const doFetch = opts.fetchImpl ?? fetch;
15841
- const res = await doFetch(contentsUrl(opts.owner, opts.repo, opts.path), {
15842
- headers: {
15843
- Authorization: `Bearer ${opts.githubToken}`,
15844
- Accept: "application/vnd.github+json",
15845
- "X-GitHub-Api-Version": "2022-11-28",
15846
- "User-Agent": "kody-engine"
15847
- },
15848
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
15849
- });
15850
- if (res.status === 404) return null;
15851
- if (!res.ok) {
15852
- throw new Error(
15853
- `GitHub ${opts.owner}/${opts.repo}:${opts.path}: ${res.status} ${(await res.text().catch(() => "")).slice(0, 160)}`
15854
- );
15855
- }
15856
- return await res.json();
15857
- }
15858
- function decodeContentsFile(file, label) {
15859
- if (file.type !== "file" || file.encoding !== "base64" || typeof file.content !== "string") {
15860
- throw new Error(`${label} is not a base64 file`);
15861
- }
15862
- if (typeof file.sha !== "string") {
15863
- throw new Error(`${label} response missing sha`);
15864
- }
15865
- return {
15866
- path: label,
15867
- content: Buffer.from(file.content, "base64").toString("utf-8"),
15868
- sha: file.sha
15869
- };
15870
- }
15871
- async function loadGithubStateConfig(opts) {
15872
- const configFile = await githubContentsFile({
15873
- owner: opts.owner,
15874
- repo: opts.repo,
15875
- path: "kody.config.json",
15876
- githubToken: opts.githubToken,
15877
- fetchImpl: opts.fetchImpl
15878
- });
15879
- let raw = {};
15880
- if (configFile) {
15881
- const decoded = decodeContentsFile(configFile, "kody.config.json");
15882
- try {
15883
- raw = JSON.parse(decoded.content);
15884
- } catch (err) {
15885
- throw new Error(`kody.config.json is invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
15886
- }
15887
- }
15888
- const githubRaw = recordValue3(raw.github) ?? {};
15889
- const github = {
15890
- owner: typeof githubRaw.owner === "string" && githubRaw.owner.trim() ? githubRaw.owner.trim() : opts.owner,
15891
- repo: typeof githubRaw.repo === "string" && githubRaw.repo.trim() ? githubRaw.repo.trim() : opts.repo
15892
- };
15893
- const nestedState = recordValue3(raw.state) ?? {};
15894
- const repoRaw = typeof raw.stateRepo === "string" ? raw.stateRepo : nestedState.repo;
15895
- const pathRaw = typeof raw.statePath === "string" ? raw.statePath : nestedState.path;
15896
- const stateRepo = typeof repoRaw === "string" && repoRaw.trim().length > 0 ? repoRaw.trim() : `https://github.com/${github.owner}/kody-state`;
15897
- parseStateRepoSlug(stateRepo);
15898
- const statePath = typeof pathRaw === "string" && pathRaw.trim().length > 0 ? pathRaw.trim() : github.repo;
15899
- return {
15900
- github,
15901
- state: {
15902
- repo: stateRepo,
15903
- path: normalizeStatePath(statePath)
15904
- }
15905
- };
15906
- }
15907
- async function readGithubStateText(opts) {
15908
- const config = await loadGithubStateConfig(opts);
15909
- return readGithubStateTextWithConfig({
15910
- config,
15911
- filePath: opts.filePath,
15912
- githubToken: opts.githubToken,
15913
- fetchImpl: opts.fetchImpl
15914
- });
15915
- }
15916
- async function readGithubStateTextWithConfig(opts) {
15917
- const target = stateRepoPath(opts.config, opts.filePath);
15918
- const parsed = parseStateRepo(opts.config);
15919
- const file = await githubContentsFile({
15920
- owner: parsed.owner,
15921
- repo: parsed.repo,
15922
- path: target,
15923
- githubToken: opts.githubToken,
15924
- fetchImpl: opts.fetchImpl
15925
- });
15926
- return file ? decodeContentsFile(file, target) : null;
15927
- }
15928
- async function writeGithubStateTextWithConfig(opts) {
15929
- const target = stateRepoPath(opts.config, opts.filePath);
15930
- const parsed = parseStateRepo(opts.config);
15931
- const payload = {
15932
- message: opts.message,
15933
- content: Buffer.from(opts.content, "utf-8").toString("base64")
15934
- };
15935
- if (opts.sha) payload.sha = opts.sha;
15936
- const doFetch = opts.fetchImpl ?? fetch;
15937
- const res = await doFetch(contentsUrl(parsed.owner, parsed.repo, target), {
15938
- method: "PUT",
15939
- headers: {
15940
- Authorization: `Bearer ${opts.githubToken}`,
15941
- Accept: "application/vnd.github+json",
15942
- "Content-Type": "application/json",
15943
- "X-GitHub-Api-Version": "2022-11-28",
15944
- "User-Agent": "kody-engine"
15945
- },
15946
- body: JSON.stringify(payload),
15947
- signal: AbortSignal.timeout(REQ_TIMEOUT_MS)
15948
- });
15949
- if (!res.ok) {
15950
- throw new Error(
15951
- `GitHub write ${parsed.owner}/${parsed.repo}:${target}: ${res.status} ${(await res.text().catch(() => "")).slice(0, 160)}`
15952
- );
15953
- }
15954
- }
15955
- function jsonlLines(text) {
15956
- return text.split("\n").filter((line) => line.length > 0);
15957
- }
15958
- function renderJsonl(lines) {
15959
- return lines.length > 0 ? `${lines.join("\n")}
15960
- ` : "";
15961
- }
15962
- function mergeJsonl(localText, remoteText) {
15963
- const remoteLines = jsonlLines(remoteText);
15964
- const seen = new Set(remoteLines);
15965
- const localOnly = jsonlLines(localText).filter((line) => !seen.has(line));
15966
- return renderJsonl([...remoteLines, ...localOnly]);
15967
- }
15968
- async function syncJsonlFileFromGithubState(opts) {
15969
- const remote = await readGithubStateTextWithConfig(opts);
15970
- if (!remote) return;
15971
- const local = fs39.existsSync(opts.localPath) ? fs39.readFileSync(opts.localPath, "utf-8") : "";
15972
- const next = mergeJsonl(local, remote.content);
15973
- if (next === local) return;
15974
- fs39.mkdirSync(path37.dirname(opts.localPath), { recursive: true });
15975
- fs39.writeFileSync(opts.localPath, next);
15976
- }
15977
- async function persistJsonlFileToGithubState(opts) {
15978
- if (!fs39.existsSync(opts.localPath)) return;
15979
- const localText = fs39.readFileSync(opts.localPath, "utf-8");
15980
- for (let attempt = 1; attempt <= 3; attempt += 1) {
15981
- const remote = await readGithubStateTextWithConfig(opts);
15982
- const body = mergeJsonl(localText, remote?.content ?? "");
15983
- try {
15984
- await writeGithubStateTextWithConfig({
15985
- config: opts.config,
15986
- filePath: opts.filePath,
15987
- content: body,
15988
- message: opts.message,
15989
- githubToken: opts.githubToken,
15990
- sha: remote?.sha,
15991
- fetchImpl: opts.fetchImpl
15992
- });
15993
- return;
15994
- } catch (err) {
15995
- const msg = err instanceof Error ? err.message : String(err);
15996
- const conflict = /409|422|does not match|is at|but expected/i.test(msg);
15997
- if (!conflict || attempt === 3) throw err;
15998
- }
15999
- }
16000
- }
16001
- var GITHUB_API, REQ_TIMEOUT_MS;
16002
- var init_stateRepoGithub = __esm({
16003
- "src/stateRepoGithub.ts"() {
16004
- "use strict";
16005
- init_stateRepo();
16006
- GITHUB_API = "https://api.github.com";
16007
- REQ_TIMEOUT_MS = 3e4;
16008
- }
16009
- });
16010
-
16011
16212
  // src/scripts/previewBuildHelpers.ts
16012
- import { createDecipheriv, createHash as createHash3, hkdfSync } from "crypto";
16213
+ import { createDecipheriv as createDecipheriv2, createHash as createHash4, hkdfSync as hkdfSync2 } from "crypto";
16013
16214
  function shortHash(s) {
16014
- return createHash3("sha256").update(s).digest("hex").slice(0, 6);
16215
+ return createHash4("sha256").update(s).digest("hex").slice(0, 6);
16015
16216
  }
16016
16217
  function previewAppName(repo, pr) {
16017
16218
  const [owner, name] = repo.split("/");
@@ -16040,7 +16241,7 @@ function decryptVaultPayload(payload, keyRaw) {
16040
16241
  const iv = Buffer.from(ivB64, "base64");
16041
16242
  const ct = Buffer.from(ctB64, "base64");
16042
16243
  const tag = Buffer.from(tagB64, "base64");
16043
- const decipher = createDecipheriv("aes-256-gcm", key, iv);
16244
+ const decipher = createDecipheriv2("aes-256-gcm", key, iv);
16044
16245
  decipher.setAuthTag(tag);
16045
16246
  return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
16046
16247
  }
@@ -16053,7 +16254,7 @@ function derivePreviewVerifyKey(masterKeyRaw) {
16053
16254
  if (masterKey.length !== 32) {
16054
16255
  throw new Error("KODY_MASTER_KEY must decode to 32 bytes");
16055
16256
  }
16056
- return Buffer.from(hkdfSync("sha256", masterKey, Buffer.alloc(0), PREVIEW_KEY_INFO, 32)).toString("hex");
16257
+ return Buffer.from(hkdfSync2("sha256", masterKey, Buffer.alloc(0), PREVIEW_KEY_INFO, 32)).toString("hex");
16057
16258
  }
16058
16259
  function previewRuntimeEnv(args) {
16059
16260
  return {
@@ -16084,7 +16285,7 @@ function formatPreviewComment(args) {
16084
16285
  ].join("\n");
16085
16286
  }
16086
16287
  function defaultImageTag(repo, ref) {
16087
- return createHash3("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
16288
+ return createHash4("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
16088
16289
  }
16089
16290
  var NEVER_PASS_TO_BUILD, PREVIEW_KEY_INFO;
16090
16291
  var init_previewBuildHelpers = __esm({
@@ -17699,7 +17900,7 @@ var init_scripts = __esm({
17699
17900
  init_appendCompanyActivity();
17700
17901
  init_appendCompanyIntentDecision();
17701
17902
  init_applyCapabilityReports();
17702
- init_applyCompanyManagerDecision();
17903
+ init_applyAgencyArchitectDecision();
17703
17904
  init_buildSyntheticPlugin();
17704
17905
  init_checkCoverageWithRetry();
17705
17906
  init_classifyByLabel();
@@ -17746,7 +17947,7 @@ var init_scripts = __esm({
17746
17947
  init_openAgentFactoryStatePr();
17747
17948
  init_openQaIssue();
17748
17949
  init_parseAgentResult();
17749
- init_parseCompanyManagerDecision();
17950
+ init_parseAgencyArchitectDecision();
17750
17951
  init_parseIssueStateFromAgentResult();
17751
17952
  init_parseJobStateFromAgentResult();
17752
17953
  init_parseReproOutput();
@@ -17841,7 +18042,7 @@ var init_scripts = __esm({
17841
18042
  };
17842
18043
  postflightScripts = {
17843
18044
  parseAgentResult: parseAgentResult2,
17844
- parseCompanyManagerDecision,
18045
+ parseAgencyArchitectDecision,
17845
18046
  parseIssueStateFromAgentResult,
17846
18047
  parseJobStateFromAgentResult,
17847
18048
  parseReproOutput,
@@ -17849,7 +18050,7 @@ var init_scripts = __esm({
17849
18050
  writeJobStateFile,
17850
18051
  appendCompanyActivity,
17851
18052
  appendCompanyIntentDecision: appendCompanyIntentDecision2,
17852
- applyCompanyManagerDecision,
18053
+ applyAgencyArchitectDecision,
17853
18054
  requireFeedbackActions,
17854
18055
  requirePlanDeviations,
17855
18056
  verify,
@@ -17940,7 +18141,7 @@ function hydrateStateWorkspace(config, cwd) {
17940
18141
  hydratedWorkspaces.add(hydrateKey);
17941
18142
  }
17942
18143
  function fetchStateSnapshot(parsed) {
17943
- const cacheDir = path41.join(cacheRoot2(), cacheKey2(parsed));
18144
+ const cacheDir = path41.join(cacheRoot2(), cacheKey3(parsed));
17944
18145
  const url = `https://github.com/${parsed.owner}/${parsed.repo}.git`;
17945
18146
  try {
17946
18147
  fs43.mkdirSync(path41.dirname(cacheDir), { recursive: true });
@@ -17965,7 +18166,7 @@ function fetchStateSnapshot(parsed) {
17965
18166
  function cacheRoot2() {
17966
18167
  return process.env[CACHE_ENV2]?.trim() || path41.join(os7.homedir(), ".cache", "kody", "state-repo");
17967
18168
  }
17968
- function cacheKey2(parsed) {
18169
+ function cacheKey3(parsed) {
17969
18170
  return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${parsed.branch}#${parsed.basePath}`).digest("hex").slice(0, 24);
17970
18171
  }
17971
18172
  function runGit3(args) {
@@ -23083,42 +23284,8 @@ async function runCapabilityFallbackTick(deps) {
23083
23284
  return { ran: true, claimed };
23084
23285
  }
23085
23286
 
23086
- // src/pool/keys.ts
23087
- import { hkdfSync as hkdfSync2 } from "crypto";
23088
- var POOL_API_KEY_INFO = "kody-pool-api:v1";
23089
- var RUNNER_API_KEY_INFO = "kody-runner-api:v1";
23090
- function masterKeyBytes(raw) {
23091
- const v = raw.trim();
23092
- if (!v) throw new Error("KODY_MASTER_KEY is empty");
23093
- if (/^[0-9a-fA-F]+$/.test(v) && v.length === 64) {
23094
- return Buffer.from(v, "hex");
23095
- }
23096
- return Buffer.from(v.replace(/-/g, "+").replace(/_/g, "/"), "base64");
23097
- }
23098
- function deriveKey(master, info, length = 32) {
23099
- return Buffer.from(hkdfSync2("sha256", master, Buffer.alloc(0), info, length)).toString("hex");
23100
- }
23101
- function derivePoolApiKey(master) {
23102
- return deriveKey(master, POOL_API_KEY_INFO);
23103
- }
23104
- function deriveRunnerApiKey(master) {
23105
- return deriveKey(master, RUNNER_API_KEY_INFO);
23106
- }
23107
- function bearerOk(headerAuth, xApiKey, expected) {
23108
- const x = (xApiKey ?? "").trim();
23109
- if (x && timingEqual(x, expected)) return true;
23110
- const a = (headerAuth ?? "").trim();
23111
- if (a.toLowerCase().startsWith("bearer ")) {
23112
- return timingEqual(a.slice(7).trim(), expected);
23113
- }
23114
- return false;
23115
- }
23116
- function timingEqual(a, b) {
23117
- if (a.length !== b.length) return false;
23118
- let diff = 0;
23119
- for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
23120
- return diff === 0;
23121
- }
23287
+ // src/servers/pool-serve.ts
23288
+ init_keys();
23122
23289
 
23123
23290
  // src/pool/fly.ts
23124
23291
  var FLY_API_BASE = "https://api.machines.dev/v1";
@@ -23483,59 +23650,8 @@ function isSuspendedWithIp(m) {
23483
23650
  return (m.state === "suspended" || m.state === "suspending") && !!m.private_ip;
23484
23651
  }
23485
23652
 
23486
- // src/pool/vault.ts
23487
- init_stateRepoGithub();
23488
- import { createDecipheriv as createDecipheriv2 } from "crypto";
23489
- var VAULT_PATH = "secrets.enc";
23490
- var CACHE_TTL_MS = 6e4;
23491
- var cache = /* @__PURE__ */ new Map();
23492
- function decryptVault(payload, masterKey) {
23493
- const parts = payload.split(":");
23494
- if (parts.length !== 4 || parts[0] !== "v1") {
23495
- throw new Error("invalid vault payload format");
23496
- }
23497
- const [, ivB64, ctB64, tagB64] = parts;
23498
- const iv = Buffer.from(ivB64, "base64");
23499
- const ct = Buffer.from(ctB64, "base64");
23500
- const tag = Buffer.from(tagB64, "base64");
23501
- const decipher = createDecipheriv2("aes-256-gcm", masterKey, iv);
23502
- decipher.setAuthTag(tag);
23503
- return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
23504
- }
23505
- async function readVaultSecrets(opts) {
23506
- const key = `${opts.owner}/${opts.repo}`.toLowerCase();
23507
- const hit = cache.get(key);
23508
- if (hit && hit.expiresAt > Date.now()) return hit.secrets;
23509
- const file = await readGithubStateText({
23510
- owner: opts.owner,
23511
- repo: opts.repo,
23512
- filePath: VAULT_PATH,
23513
- githubToken: opts.githubToken,
23514
- fetchImpl: opts.fetchImpl
23515
- });
23516
- if (!file) {
23517
- cache.set(key, { secrets: {}, expiresAt: Date.now() + CACHE_TTL_MS });
23518
- return {};
23519
- }
23520
- const ciphertext = file.content.trim();
23521
- const doc = JSON.parse(decryptVault(ciphertext, opts.masterKey));
23522
- const flat = {};
23523
- for (const [name, entry] of Object.entries(doc.secrets ?? {})) {
23524
- if (entry && typeof entry.value === "string") flat[name] = entry.value;
23525
- }
23526
- cache.set(key, { secrets: flat, expiresAt: Date.now() + CACHE_TTL_MS });
23527
- return flat;
23528
- }
23529
- async function readRepoSecret(opts) {
23530
- const secrets = await readVaultSecrets(opts);
23531
- const v = secrets[opts.name];
23532
- return v?.trim() ? v : null;
23533
- }
23534
- async function readRepoSecrets(opts) {
23535
- return readVaultSecrets(opts);
23536
- }
23537
-
23538
23653
  // src/pool/registry.ts
23654
+ init_stateRepoVault();
23539
23655
  var POOL_MIN_VAULT_KEY = "POOL_MIN";
23540
23656
  var POOL_MIN_MAX = 10;
23541
23657
  function parsePoolMin(raw, dflt) {