@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 +590 -474
- package/dist/executables/types.ts +11 -4
- package/package.json +1 -1
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.
|
|
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
|
|
553
|
-
if (ensuredStateBranches.has(
|
|
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(
|
|
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(
|
|
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: "
|
|
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 =
|
|
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/
|
|
8795
|
-
function
|
|
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("
|
|
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-
|
|
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(/
|
|
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-
|
|
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("
|
|
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
|
|
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
|
|
8961
|
-
"src/
|
|
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: "
|
|
9109
|
-
capability: "
|
|
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/
|
|
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
|
|
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
|
|
9210
|
-
var
|
|
9211
|
-
"src/scripts/
|
|
9248
|
+
var applyAgencyArchitectDecision;
|
|
9249
|
+
var init_applyAgencyArchitectDecision = __esm({
|
|
9250
|
+
"src/scripts/applyAgencyArchitectDecision.ts"() {
|
|
9212
9251
|
"use strict";
|
|
9213
|
-
|
|
9252
|
+
init_agencyArchitectDecision();
|
|
9214
9253
|
init_companyIntent();
|
|
9215
9254
|
init_stateStore();
|
|
9216
9255
|
init_state2();
|
|
9217
|
-
|
|
9218
|
-
const decision = ctx.data.
|
|
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.
|
|
9226
|
-
ctx.data.
|
|
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
|
-
|
|
9275
|
+
init_applyAgencyArchitectDecision();
|
|
9237
9276
|
appendCompanyIntentDecision2 = async (ctx) => {
|
|
9238
|
-
const applied2 = ctx.data.
|
|
9277
|
+
const applied2 = ctx.data.agencyArchitectApplied;
|
|
9239
9278
|
if (!applied2 || applied2.length === 0) return;
|
|
9240
9279
|
try {
|
|
9241
|
-
|
|
9280
|
+
logAppliedAgencyArchitectActions(ctx.config, ctx.cwd, applied2);
|
|
9242
9281
|
} catch (err) {
|
|
9243
9282
|
process.stderr.write(
|
|
9244
|
-
`[
|
|
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
|
-
|
|
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/
|
|
13428
|
-
import
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
|
|
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
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
|
|
13447
|
-
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
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
|
|
13502
|
+
return false;
|
|
13456
13503
|
}
|
|
13457
|
-
function
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
let
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
return
|
|
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
|
|
13481
|
-
|
|
13482
|
-
|
|
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
|
-
|
|
13485
|
-
|
|
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 (
|
|
13488
|
-
|
|
13552
|
+
if (typeof file.sha !== "string") {
|
|
13553
|
+
throw new Error(`${label} response missing sha`);
|
|
13489
13554
|
}
|
|
13490
|
-
return
|
|
13555
|
+
return {
|
|
13556
|
+
path: label,
|
|
13557
|
+
content: Buffer.from(file.content, "base64").toString("utf-8"),
|
|
13558
|
+
sha: file.sha
|
|
13559
|
+
};
|
|
13491
13560
|
}
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
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
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
|
|
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
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
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
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
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
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
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/
|
|
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
|
|
14310
|
-
var
|
|
14311
|
-
"src/scripts/
|
|
14692
|
+
var parseAgencyArchitectDecision;
|
|
14693
|
+
var init_parseAgencyArchitectDecision = __esm({
|
|
14694
|
+
"src/scripts/parseAgencyArchitectDecision.ts"() {
|
|
14312
14695
|
"use strict";
|
|
14313
|
-
|
|
14314
|
-
|
|
14696
|
+
init_agencyArchitectDecision();
|
|
14697
|
+
parseAgencyArchitectDecision = async (ctx, _profile, agentResult) => {
|
|
14315
14698
|
if (!agentResult) {
|
|
14316
|
-
ctx.data.
|
|
14317
|
-
ctx.data.action = makeAction3("
|
|
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 =
|
|
14322
|
-
ctx.data.
|
|
14323
|
-
ctx.data.action = makeAction3("
|
|
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.
|
|
14330
|
-
ctx.data.action = makeAction3("
|
|
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
|
|
16213
|
+
import { createDecipheriv as createDecipheriv2, createHash as createHash4, hkdfSync as hkdfSync2 } from "crypto";
|
|
16013
16214
|
function shortHash(s) {
|
|
16014
|
-
return
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(),
|
|
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
|
|
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
|
|
23087
|
-
|
|
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) {
|