@kody-ade/kody-engine 0.4.408 → 0.4.410
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 +191 -114
- 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.410",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -2219,6 +2219,108 @@ var init_convex_client = __esm({
|
|
|
2219
2219
|
}
|
|
2220
2220
|
});
|
|
2221
2221
|
|
|
2222
|
+
// src/kody-api-client.ts
|
|
2223
|
+
import { getFunctionName } from "convex/server";
|
|
2224
|
+
function apiUrl(env) {
|
|
2225
|
+
return (env.KODY_API_URL?.trim() || DEFAULT_KODY_API_URL).replace(/\/$/, "");
|
|
2226
|
+
}
|
|
2227
|
+
function oidcRequestUrl(env) {
|
|
2228
|
+
const raw = env.ACTIONS_ID_TOKEN_REQUEST_URL?.trim();
|
|
2229
|
+
if (!raw) throw new Error("GitHub Actions OIDC request URL is unavailable");
|
|
2230
|
+
const url = new URL(raw);
|
|
2231
|
+
if (url.protocol !== "https:") throw new Error("GitHub Actions OIDC request URL must use HTTPS");
|
|
2232
|
+
url.searchParams.set("audience", OIDC_AUDIENCE);
|
|
2233
|
+
return url;
|
|
2234
|
+
}
|
|
2235
|
+
function tokenExpiry(token) {
|
|
2236
|
+
try {
|
|
2237
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8"));
|
|
2238
|
+
return typeof payload.exp === "number" ? payload.exp * 1e3 : Date.now() + 4 * 6e4;
|
|
2239
|
+
} catch {
|
|
2240
|
+
return Date.now() + 4 * 6e4;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
async function githubOidcToken(env, force = false) {
|
|
2244
|
+
if (!force && cachedToken && cachedToken.expiresAt - 6e4 > Date.now()) return cachedToken.value;
|
|
2245
|
+
const requestToken = env.ACTIONS_ID_TOKEN_REQUEST_TOKEN?.trim();
|
|
2246
|
+
if (!requestToken) throw new Error("GitHub Actions OIDC request token is unavailable");
|
|
2247
|
+
const response = await fetch(oidcRequestUrl(env), {
|
|
2248
|
+
headers: { Authorization: `Bearer ${requestToken}` },
|
|
2249
|
+
signal: AbortSignal.timeout(15e3)
|
|
2250
|
+
});
|
|
2251
|
+
if (!response.ok) throw new Error(`GitHub Actions identity request failed (${response.status})`);
|
|
2252
|
+
const body = await response.json();
|
|
2253
|
+
if (typeof body.value !== "string" || !body.value) throw new Error("GitHub Actions identity response was invalid");
|
|
2254
|
+
cachedToken = { value: body.value, expiresAt: tokenExpiry(body.value) };
|
|
2255
|
+
return body.value;
|
|
2256
|
+
}
|
|
2257
|
+
function operationName(fn) {
|
|
2258
|
+
return getFunctionName(fn).replace(":", ".");
|
|
2259
|
+
}
|
|
2260
|
+
async function callKodyApi(kind, fn, args, env) {
|
|
2261
|
+
const call = async (forceToken) => {
|
|
2262
|
+
const token = await githubOidcToken(env, forceToken);
|
|
2263
|
+
return fetch(`${apiUrl(env)}/api/kody/engine/backend`, {
|
|
2264
|
+
method: "POST",
|
|
2265
|
+
headers: {
|
|
2266
|
+
Authorization: `Bearer ${token}`,
|
|
2267
|
+
"Content-Type": "application/json"
|
|
2268
|
+
},
|
|
2269
|
+
body: JSON.stringify({ kind, operation: operationName(fn), args }),
|
|
2270
|
+
signal: AbortSignal.timeout(3e4)
|
|
2271
|
+
});
|
|
2272
|
+
};
|
|
2273
|
+
let response = await call(false);
|
|
2274
|
+
if (response.status === 401) response = await call(true);
|
|
2275
|
+
if (!response.ok) throw new Error(`Kody backend request failed (${response.status})`);
|
|
2276
|
+
const body = await response.json();
|
|
2277
|
+
return body.result;
|
|
2278
|
+
}
|
|
2279
|
+
function hasGitHubActionsIdentity(env = process.env) {
|
|
2280
|
+
return Boolean(
|
|
2281
|
+
env.GITHUB_ACTIONS === "true" && env.ACTIONS_ID_TOKEN_REQUEST_URL?.trim() && env.ACTIONS_ID_TOKEN_REQUEST_TOKEN?.trim()
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
function createKodyApiBackendClient(env = process.env) {
|
|
2285
|
+
if (!hasGitHubActionsIdentity(env)) throw new Error("GitHub Actions workflow identity is unavailable");
|
|
2286
|
+
return {
|
|
2287
|
+
query: (fn, args) => callKodyApi("query", fn, args, env),
|
|
2288
|
+
mutation: (fn, args) => callKodyApi("mutation", fn, args, env)
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
async function readRuntimeSecretFromKody(name, env = process.env) {
|
|
2292
|
+
const token = await githubOidcToken(env);
|
|
2293
|
+
const response = await fetch(`${apiUrl(env)}/api/kody/engine/secret`, {
|
|
2294
|
+
method: "POST",
|
|
2295
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
2296
|
+
body: JSON.stringify({ name }),
|
|
2297
|
+
signal: AbortSignal.timeout(3e4)
|
|
2298
|
+
});
|
|
2299
|
+
if (response.status === 404) return null;
|
|
2300
|
+
if (!response.ok) throw new Error(`Kody secret request failed (${response.status})`);
|
|
2301
|
+
const body = await response.json();
|
|
2302
|
+
return typeof body.value === "string" ? body.value : null;
|
|
2303
|
+
}
|
|
2304
|
+
async function readPreviewContextFromKody(env = process.env) {
|
|
2305
|
+
const token = await githubOidcToken(env);
|
|
2306
|
+
const response = await fetch(`${apiUrl(env)}/api/kody/engine/preview-context`, {
|
|
2307
|
+
method: "POST",
|
|
2308
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
2309
|
+
signal: AbortSignal.timeout(3e4)
|
|
2310
|
+
});
|
|
2311
|
+
if (!response.ok) throw new Error(`Kody preview context request failed (${response.status})`);
|
|
2312
|
+
return await response.json();
|
|
2313
|
+
}
|
|
2314
|
+
var DEFAULT_KODY_API_URL, OIDC_AUDIENCE, cachedToken;
|
|
2315
|
+
var init_kody_api_client = __esm({
|
|
2316
|
+
"src/kody-api-client.ts"() {
|
|
2317
|
+
"use strict";
|
|
2318
|
+
DEFAULT_KODY_API_URL = "https://kody-dashboard-khaki.vercel.app";
|
|
2319
|
+
OIDC_AUDIENCE = "kody-api";
|
|
2320
|
+
cachedToken = null;
|
|
2321
|
+
}
|
|
2322
|
+
});
|
|
2323
|
+
|
|
2222
2324
|
// src/state-backend.ts
|
|
2223
2325
|
import { anyApi } from "convex/server";
|
|
2224
2326
|
function requireTenant(tenantId2) {
|
|
@@ -2236,10 +2338,16 @@ function requireNonEmpty(value, name) {
|
|
|
2236
2338
|
return normalized;
|
|
2237
2339
|
}
|
|
2238
2340
|
function createStateBackendFromEnv(env = process.env, client) {
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
if (!
|
|
2242
|
-
|
|
2341
|
+
let transport = client;
|
|
2342
|
+
if (!transport && hasGitHubActionsIdentity(env)) transport = createKodyApiBackendClient(env);
|
|
2343
|
+
if (!transport) {
|
|
2344
|
+
const url = env.CONVEX_URL?.trim();
|
|
2345
|
+
const serviceKey = env.KODY_SERVICE_KEY?.trim();
|
|
2346
|
+
if (!url || !serviceKey) {
|
|
2347
|
+
throw new Error("GitHub Actions identity or direct Kody backend credentials are required");
|
|
2348
|
+
}
|
|
2349
|
+
transport = createConvexClientFromEnv(env);
|
|
2350
|
+
}
|
|
2243
2351
|
return {
|
|
2244
2352
|
async get(tenantId2, taskKey, kind) {
|
|
2245
2353
|
const result = await transport.query(anyApi.taskState.get, {
|
|
@@ -2412,10 +2520,14 @@ function createStateBackendFromEnv(env = process.env, client) {
|
|
|
2412
2520
|
}
|
|
2413
2521
|
};
|
|
2414
2522
|
}
|
|
2523
|
+
function hasStateBackendConfig(env = process.env) {
|
|
2524
|
+
return hasGitHubActionsIdentity(env) || Boolean(env.CONVEX_URL?.trim() && env.KODY_SERVICE_KEY?.trim());
|
|
2525
|
+
}
|
|
2415
2526
|
var init_state_backend = __esm({
|
|
2416
2527
|
"src/state-backend.ts"() {
|
|
2417
2528
|
"use strict";
|
|
2418
2529
|
init_convex_client();
|
|
2530
|
+
init_kody_api_client();
|
|
2419
2531
|
}
|
|
2420
2532
|
});
|
|
2421
2533
|
|
|
@@ -2990,6 +3102,17 @@ var init_capabilityMcp = __esm({
|
|
|
2990
3102
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
2991
3103
|
import * as fs7 from "fs";
|
|
2992
3104
|
import * as path8 from "path";
|
|
3105
|
+
function buildCloneProcess(repo, token, baseEnv = process.env) {
|
|
3106
|
+
const url = `https://github.com/${repo}.git`;
|
|
3107
|
+
const env = { ...baseEnv };
|
|
3108
|
+
if (!token) return { url, env };
|
|
3109
|
+
const parsedCount = Number.parseInt(env.GIT_CONFIG_COUNT ?? "0", 10);
|
|
3110
|
+
const count = Number.isInteger(parsedCount) && parsedCount >= 0 ? parsedCount : 0;
|
|
3111
|
+
env.GIT_CONFIG_COUNT = String(count + 1);
|
|
3112
|
+
env[`GIT_CONFIG_KEY_${count}`] = "http.https://github.com/.extraHeader";
|
|
3113
|
+
env[`GIT_CONFIG_VALUE_${count}`] = `Authorization: Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`;
|
|
3114
|
+
return { url, env };
|
|
3115
|
+
}
|
|
2993
3116
|
async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
|
|
2994
3117
|
const name = repo?.trim();
|
|
2995
3118
|
if (!name || !REPO_RE.test(name)) return null;
|
|
@@ -3020,17 +3143,19 @@ async function fetchRepo(opts) {
|
|
|
3020
3143
|
}
|
|
3021
3144
|
return dir;
|
|
3022
3145
|
}
|
|
3023
|
-
var REPO_RE, repoClones, defaultCloneRepo;
|
|
3146
|
+
var REPO_RE, repoClones, GIT_CREDENTIAL_HELPER, defaultCloneRepo;
|
|
3024
3147
|
var init_repoWorkspace = __esm({
|
|
3025
3148
|
"src/repoWorkspace.ts"() {
|
|
3026
3149
|
"use strict";
|
|
3027
3150
|
REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
3028
3151
|
repoClones = /* @__PURE__ */ new Map();
|
|
3152
|
+
GIT_CREDENTIAL_HELPER = `!f() { if [ "$1" = get ] && [ -n "$GITHUB_TOKEN" ]; then printf '%s\\n' username=x-access-token "password=$GITHUB_TOKEN"; fi; }; f`;
|
|
3029
3153
|
defaultCloneRepo = (repo, token, dir) => {
|
|
3030
3154
|
fs7.mkdirSync(path8.dirname(dir), { recursive: true });
|
|
3031
|
-
const
|
|
3155
|
+
const clone = buildCloneProcess(repo, token);
|
|
3032
3156
|
return new Promise((resolve16, reject) => {
|
|
3033
|
-
const child = spawn2("git", ["clone", "--depth=1",
|
|
3157
|
+
const child = spawn2("git", ["clone", "--depth=1", clone.url, dir], {
|
|
3158
|
+
env: clone.env,
|
|
3034
3159
|
stdio: "inherit"
|
|
3035
3160
|
});
|
|
3036
3161
|
child.on("exit", (code) => {
|
|
@@ -3043,6 +3168,9 @@ var init_repoWorkspace = __esm({
|
|
|
3043
3168
|
const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
|
|
3044
3169
|
spawnSync("git", ["-C", dir, "config", "user.name", name]);
|
|
3045
3170
|
spawnSync("git", ["-C", dir, "config", "user.email", email]);
|
|
3171
|
+
if (token) {
|
|
3172
|
+
spawnSync("git", ["-C", dir, "config", "credential.helper", GIT_CREDENTIAL_HELPER]);
|
|
3173
|
+
}
|
|
3046
3174
|
} catch {
|
|
3047
3175
|
}
|
|
3048
3176
|
resolve16();
|
|
@@ -3169,24 +3297,26 @@ function stripAgentSecrets(env) {
|
|
|
3169
3297
|
}
|
|
3170
3298
|
return out;
|
|
3171
3299
|
}
|
|
3172
|
-
|
|
3173
|
-
const ndjsonDir = opts.ndjsonDir ?? agentRunDir(opts.cwd);
|
|
3174
|
-
fs8.mkdirSync(ndjsonDir, { recursive: true });
|
|
3175
|
-
const ndjsonPath = path9.join(ndjsonDir, "last-run.jsonl");
|
|
3300
|
+
function buildAgentEnvironment(baseEnv, repoToken) {
|
|
3176
3301
|
const env = stripAgentSecrets({
|
|
3177
|
-
...
|
|
3302
|
+
...baseEnv,
|
|
3178
3303
|
SKIP_HOOKS: "1",
|
|
3179
3304
|
HUSKY: "0",
|
|
3180
|
-
CI:
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
// init while servers are still in `pending`, so their tools never
|
|
3184
|
-
// reach the model. Block until each MCP completes its handshake (or
|
|
3185
|
-
// the timeout below elapses) so the tool list is complete on first
|
|
3186
|
-
// turn.
|
|
3187
|
-
MCP_CONNECTION_NONBLOCKING: process.env.MCP_CONNECTION_NONBLOCKING ?? "false",
|
|
3188
|
-
MCP_TIMEOUT: process.env.MCP_TIMEOUT ?? "60000"
|
|
3305
|
+
CI: baseEnv.CI ?? "1",
|
|
3306
|
+
MCP_CONNECTION_NONBLOCKING: baseEnv.MCP_CONNECTION_NONBLOCKING ?? "false",
|
|
3307
|
+
MCP_TIMEOUT: baseEnv.MCP_TIMEOUT ?? "60000"
|
|
3189
3308
|
});
|
|
3309
|
+
if (repoToken) {
|
|
3310
|
+
env.GITHUB_TOKEN = repoToken;
|
|
3311
|
+
env.GH_TOKEN = repoToken;
|
|
3312
|
+
}
|
|
3313
|
+
return env;
|
|
3314
|
+
}
|
|
3315
|
+
async function runAgent(opts) {
|
|
3316
|
+
const ndjsonDir = opts.ndjsonDir ?? agentRunDir(opts.cwd);
|
|
3317
|
+
fs8.mkdirSync(ndjsonDir, { recursive: true });
|
|
3318
|
+
const ndjsonPath = path9.join(ndjsonDir, "last-run.jsonl");
|
|
3319
|
+
const env = buildAgentEnvironment(process.env, opts.repoToken);
|
|
3190
3320
|
if (opts.litellmUrl) {
|
|
3191
3321
|
env.ANTHROPIC_BASE_URL = opts.litellmUrl;
|
|
3192
3322
|
env.ANTHROPIC_API_KEY = getAnthropicApiKeyOrDummy();
|
|
@@ -3681,12 +3811,10 @@ function verifyTaskArtifacts(absDir) {
|
|
|
3681
3811
|
}
|
|
3682
3812
|
async function persistTaskArtifactsToState(config, _cwd, artifacts) {
|
|
3683
3813
|
const tenantId2 = config.github?.owner && config.github.repo ? `${config.github.owner}/${config.github.repo}` : process.env.GITHUB_REPOSITORY?.trim();
|
|
3684
|
-
if (process.env.GITHUB_ACTIONS === "true" && (!
|
|
3685
|
-
throw new Error(
|
|
3686
|
-
"Convex artifact backend is required in GitHub Actions (CONVEX_URL, KODY_SERVICE_KEY, and repository identity)"
|
|
3687
|
-
);
|
|
3814
|
+
if (process.env.GITHUB_ACTIONS === "true" && (!hasStateBackendConfig() || !tenantId2)) {
|
|
3815
|
+
throw new Error("Kody backend access and repository identity are required in GitHub Actions");
|
|
3688
3816
|
}
|
|
3689
|
-
if (
|
|
3817
|
+
if (hasStateBackendConfig() && tenantId2) {
|
|
3690
3818
|
const backend = createStateBackendFromEnv();
|
|
3691
3819
|
for (const file of TASK_ARTIFACT_FILES) {
|
|
3692
3820
|
const full = path11.join(artifacts.absDir, file);
|
|
@@ -5816,7 +5944,7 @@ var init_loadMemoryContext = __esm({
|
|
|
5816
5944
|
loadMemoryContext = async (ctx) => {
|
|
5817
5945
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
5818
5946
|
const tenantId2 = ctx.config.github?.owner && ctx.config.github.repo ? `${ctx.config.github.owner}/${ctx.config.github.repo}` : process.env.GITHUB_REPOSITORY?.trim();
|
|
5819
|
-
if (
|
|
5947
|
+
if (hasStateBackendConfig() && tenantId2) {
|
|
5820
5948
|
try {
|
|
5821
5949
|
const backend = createStateBackendFromEnv();
|
|
5822
5950
|
const docs = await backend.listRepoDocs(tenantId2, "memory:");
|
|
@@ -9517,7 +9645,7 @@ function subjectCandidates(kind, id, state) {
|
|
|
9517
9645
|
return [...ids].map((candidate) => ({ kind, id: candidate }));
|
|
9518
9646
|
}
|
|
9519
9647
|
async function firstTrustOverride(ctx, subjects) {
|
|
9520
|
-
const backendConfigured =
|
|
9648
|
+
const backendConfigured = hasStateBackendConfig();
|
|
9521
9649
|
if (!backendConfigured) return null;
|
|
9522
9650
|
const repoSlug = ctx.config.github?.owner && ctx.config.github?.repo ? `${ctx.config.github.owner}/${ctx.config.github.repo}` : "";
|
|
9523
9651
|
for (const subject of subjects) {
|
|
@@ -9733,6 +9861,7 @@ var init_advanceManagedGoal = __esm({
|
|
|
9733
9861
|
init_typeDefinitions();
|
|
9734
9862
|
init_issue();
|
|
9735
9863
|
init_registry();
|
|
9864
|
+
init_state_backend();
|
|
9736
9865
|
init_trustPolicy();
|
|
9737
9866
|
init_workflowDefinitions();
|
|
9738
9867
|
init_goalCapabilityScheduling();
|
|
@@ -10030,12 +10159,12 @@ function resolveTrigger(force) {
|
|
|
10030
10159
|
}
|
|
10031
10160
|
async function appendActivity(ctx, record2) {
|
|
10032
10161
|
const tenantId2 = ctx.config.github?.owner && ctx.config.github.repo ? `${ctx.config.github.owner}/${ctx.config.github.repo}` : process.env.GITHUB_REPOSITORY;
|
|
10033
|
-
if (
|
|
10162
|
+
if (hasStateBackendConfig() && tenantId2) {
|
|
10034
10163
|
await createStateBackendFromEnv().appendDailyLog(tenantId2, "activity", record2.ts.slice(0, 10), record2);
|
|
10035
10164
|
return;
|
|
10036
10165
|
}
|
|
10037
10166
|
if (process.env.GITHUB_ACTIONS === "true") {
|
|
10038
|
-
throw new Error("
|
|
10167
|
+
throw new Error("Kody backend access is required for company activity in GitHub Actions");
|
|
10039
10168
|
}
|
|
10040
10169
|
}
|
|
10041
10170
|
var appendCompanyActivity;
|
|
@@ -14755,6 +14884,18 @@ function envSecret(name, env) {
|
|
|
14755
14884
|
}
|
|
14756
14885
|
async function resolveRuntimeSecret(name, ctx, opts = {}) {
|
|
14757
14886
|
const env = opts.env ?? process.env;
|
|
14887
|
+
if (hasGitHubActionsIdentity(env)) {
|
|
14888
|
+
try {
|
|
14889
|
+
const value = await readRuntimeSecretFromKody(name, env);
|
|
14890
|
+
return value ? { value, source: "vault" } : envSecret(name, env);
|
|
14891
|
+
} catch (err) {
|
|
14892
|
+
const fallback = envSecret(name, env);
|
|
14893
|
+
return {
|
|
14894
|
+
...fallback,
|
|
14895
|
+
warning: `Kody secret read failed for ${name}: ${err instanceof Error ? err.message : String(err)}`
|
|
14896
|
+
};
|
|
14897
|
+
}
|
|
14898
|
+
}
|
|
14758
14899
|
const masterRaw = env.KODY_MASTER_KEY?.trim() ?? "";
|
|
14759
14900
|
if (!masterRaw || !env.CONVEX_URL?.trim() || !env.KODY_SERVICE_KEY?.trim()) return envSecret(name, env);
|
|
14760
14901
|
try {
|
|
@@ -14782,6 +14923,7 @@ var init_runtimeSecrets = __esm({
|
|
|
14782
14923
|
"src/scripts/runtimeSecrets.ts"() {
|
|
14783
14924
|
"use strict";
|
|
14784
14925
|
init_backendVault();
|
|
14926
|
+
init_kody_api_client();
|
|
14785
14927
|
init_keys();
|
|
14786
14928
|
}
|
|
14787
14929
|
});
|
|
@@ -16600,7 +16742,7 @@ var init_publishReport = __esm({
|
|
|
16600
16742
|
});
|
|
16601
16743
|
const runId = generatedAt.replace(/\.\d{3}Z$/, "Z").replace(/:/g, "-");
|
|
16602
16744
|
const tenantId2 = ctx.config.github?.owner && ctx.config.github.repo ? `${ctx.config.github.owner}/${ctx.config.github.repo}` : process.env.GITHUB_REPOSITORY;
|
|
16603
|
-
if (
|
|
16745
|
+
if (hasStateBackendConfig() && tenantId2) {
|
|
16604
16746
|
await createStateBackendFromEnv().saveReport(
|
|
16605
16747
|
tenantId2,
|
|
16606
16748
|
slug,
|
|
@@ -16616,7 +16758,7 @@ var init_publishReport = __esm({
|
|
|
16616
16758
|
generatedAt
|
|
16617
16759
|
);
|
|
16618
16760
|
} else if (process.env.GITHUB_ACTIONS === "true") {
|
|
16619
|
-
throw new Error("
|
|
16761
|
+
throw new Error("Kody backend access is required for reports in GitHub Actions");
|
|
16620
16762
|
}
|
|
16621
16763
|
};
|
|
16622
16764
|
}
|
|
@@ -17429,53 +17571,14 @@ function basePreviewAppName(repo) {
|
|
|
17429
17571
|
}
|
|
17430
17572
|
return `kp-${shortHash(owner)}-${shortHash(name)}-base`;
|
|
17431
17573
|
}
|
|
17432
|
-
function decryptVaultPayload(payload, keyRaw) {
|
|
17433
|
-
const parts = payload.split(":");
|
|
17434
|
-
if (parts.length !== 4 || parts[0] !== "v1") {
|
|
17435
|
-
throw new Error("invalid vault payload format");
|
|
17436
|
-
}
|
|
17437
|
-
const [, ivB64, ctB64, tagB64] = parts;
|
|
17438
|
-
const key = decodeMasterKey(keyRaw);
|
|
17439
|
-
if (key.length !== 32) {
|
|
17440
|
-
throw new Error("KODY_MASTER_KEY must decode to 32 bytes");
|
|
17441
|
-
}
|
|
17442
|
-
const iv = Buffer.from(ivB64, "base64");
|
|
17443
|
-
const ct = Buffer.from(ctB64, "base64");
|
|
17444
|
-
const tag = Buffer.from(tagB64, "base64");
|
|
17445
|
-
const decipher = createDecipheriv2("aes-256-gcm", key, iv);
|
|
17446
|
-
decipher.setAuthTag(tag);
|
|
17447
|
-
return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
|
|
17448
|
-
}
|
|
17449
|
-
function decodeMasterKey(keyRaw) {
|
|
17450
|
-
if (/^[0-9a-fA-F]{64}$/.test(keyRaw)) return Buffer.from(keyRaw, "hex");
|
|
17451
|
-
return Buffer.from(keyRaw.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
17452
|
-
}
|
|
17453
|
-
function derivePreviewVerifyKey(masterKeyRaw) {
|
|
17454
|
-
const masterKey = decodeMasterKey(masterKeyRaw);
|
|
17455
|
-
if (masterKey.length !== 32) {
|
|
17456
|
-
throw new Error("KODY_MASTER_KEY must decode to 32 bytes");
|
|
17457
|
-
}
|
|
17458
|
-
return Buffer.from(hkdfSync2("sha256", masterKey, Buffer.alloc(0), PREVIEW_KEY_INFO, 32)).toString("hex");
|
|
17459
|
-
}
|
|
17460
17574
|
function previewRuntimeEnv(args) {
|
|
17461
17575
|
return {
|
|
17462
17576
|
...args.buildEnv,
|
|
17463
|
-
KODY_PREVIEW_VERIFY_KEY:
|
|
17577
|
+
KODY_PREVIEW_VERIFY_KEY: args.previewVerifyKey,
|
|
17464
17578
|
KODY_REPO_CONTEXT: args.repo,
|
|
17465
17579
|
KODY_PR: String(args.pr)
|
|
17466
17580
|
};
|
|
17467
17581
|
}
|
|
17468
|
-
function buildEnvFromVault(doc) {
|
|
17469
|
-
const buildEnv = {};
|
|
17470
|
-
for (const [name, entry] of Object.entries(doc.secrets ?? {})) {
|
|
17471
|
-
if (!entry?.value) continue;
|
|
17472
|
-
if (NEVER_PASS_TO_BUILD.has(name)) continue;
|
|
17473
|
-
buildEnv[name] = entry.value;
|
|
17474
|
-
}
|
|
17475
|
-
const raw = doc.secrets?.KODY_PREVIEW_BUILD_MODE?.value;
|
|
17476
|
-
const buildMode = raw?.toLowerCase().trim() === "dev" ? "dev" : "prod";
|
|
17477
|
-
return { buildEnv, buildMode };
|
|
17478
|
-
}
|
|
17479
17582
|
function formatPreviewComment(args) {
|
|
17480
17583
|
return [
|
|
17481
17584
|
"<!-- kody-fly-preview -->",
|
|
@@ -17487,23 +17590,9 @@ function formatPreviewComment(args) {
|
|
|
17487
17590
|
function defaultImageTag(repo, ref) {
|
|
17488
17591
|
return createHash4("sha256").update(`${repo}@${ref}`).digest("hex").slice(0, 12);
|
|
17489
17592
|
}
|
|
17490
|
-
var NEVER_PASS_TO_BUILD, PREVIEW_KEY_INFO;
|
|
17491
17593
|
var init_previewBuildHelpers = __esm({
|
|
17492
17594
|
"src/scripts/previewBuildHelpers.ts"() {
|
|
17493
17595
|
"use strict";
|
|
17494
|
-
NEVER_PASS_TO_BUILD = /* @__PURE__ */ new Set([
|
|
17495
|
-
"FLY_API_TOKEN",
|
|
17496
|
-
"FLY_ORG_SLUG",
|
|
17497
|
-
"FLY_DEFAULT_REGION",
|
|
17498
|
-
"KODY_MASTER_KEY",
|
|
17499
|
-
// Preview-config knob; consumed by the dispatcher before spawn.
|
|
17500
|
-
"KODY_PREVIEW_BUILD_MODE",
|
|
17501
|
-
"KODY_PREVIEW_VERIFY_KEY",
|
|
17502
|
-
"KODY_REPO_CONTEXT",
|
|
17503
|
-
"KODY_PR",
|
|
17504
|
-
"KODY_BRANCH"
|
|
17505
|
-
]);
|
|
17506
|
-
PREVIEW_KEY_INFO = "kody-preview:v1";
|
|
17507
17596
|
}
|
|
17508
17597
|
});
|
|
17509
17598
|
|
|
@@ -17612,16 +17701,6 @@ function flyHeaders(token) {
|
|
|
17612
17701
|
"Content-Type": "application/json"
|
|
17613
17702
|
};
|
|
17614
17703
|
}
|
|
17615
|
-
async function fetchVaultDoc(repo, masterKey) {
|
|
17616
|
-
const [owner, name] = repo.split("/", 2);
|
|
17617
|
-
if (!owner || !name) throw new Error(`invalid GITHUB_REPOSITORY "${repo}"`);
|
|
17618
|
-
const record2 = await createStateBackendFromEnv().getRepoDoc(`${owner}/${name}`, "secrets.enc");
|
|
17619
|
-
const raw = record2?.doc;
|
|
17620
|
-
const payload = raw && typeof raw === "object" && !Array.isArray(raw) && typeof raw.ciphertext === "string" ? raw.ciphertext : "";
|
|
17621
|
-
if (!payload) throw new Error("backend vault is empty \u2014 save secrets from the dashboard first");
|
|
17622
|
-
const plaintext = decryptVaultPayload(payload, masterKey);
|
|
17623
|
-
return JSON.parse(plaintext);
|
|
17624
|
-
}
|
|
17625
17704
|
async function flyAppExists(name, token) {
|
|
17626
17705
|
const res = await fetch(`${FLY_MACHINES}/apps/${encodeURIComponent(name)}`, {
|
|
17627
17706
|
headers: flyHeaders(token),
|
|
@@ -17796,7 +17875,7 @@ var FLY_MACHINES, FLY_GRAPHQL, REQ_TIMEOUT_MS2, runPreviewBuild;
|
|
|
17796
17875
|
var init_runPreviewBuild = __esm({
|
|
17797
17876
|
"src/scripts/runPreviewBuild.ts"() {
|
|
17798
17877
|
"use strict";
|
|
17799
|
-
|
|
17878
|
+
init_kody_api_client();
|
|
17800
17879
|
init_previewBuildHelpers();
|
|
17801
17880
|
init_previewBuildNamespace();
|
|
17802
17881
|
init_previewBuildRun();
|
|
@@ -17813,12 +17892,10 @@ var init_runPreviewBuild = __esm({
|
|
|
17813
17892
|
}
|
|
17814
17893
|
let repo;
|
|
17815
17894
|
let ref;
|
|
17816
|
-
let masterKey;
|
|
17817
17895
|
let ghToken3;
|
|
17818
17896
|
try {
|
|
17819
17897
|
repo = required("GITHUB_REPOSITORY");
|
|
17820
17898
|
ref = required("GITHUB_SHA");
|
|
17821
|
-
masterKey = required("KODY_MASTER_KEY");
|
|
17822
17899
|
ghToken3 = (process.env.KODY_TOKEN ?? process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN ?? process.env.GH_PAT ?? "").trim();
|
|
17823
17900
|
if (!ghToken3) {
|
|
17824
17901
|
throw new Error("GitHub auth token missing (KODY_TOKEN / GH_TOKEN / GITHUB_TOKEN / GH_PAT all empty)");
|
|
@@ -17832,17 +17909,17 @@ var init_runPreviewBuild = __esm({
|
|
|
17832
17909
|
const appName = previewAppName(repo, pr);
|
|
17833
17910
|
const tag = defaultImageTag(repo, ref);
|
|
17834
17911
|
try {
|
|
17835
|
-
const
|
|
17836
|
-
const { buildEnv, buildMode } =
|
|
17837
|
-
const flyToken =
|
|
17912
|
+
const previewContext = await readPreviewContextFromKody();
|
|
17913
|
+
const { buildEnv, buildMode } = previewContext;
|
|
17914
|
+
const flyToken = previewContext.flyApiToken?.trim();
|
|
17838
17915
|
if (!flyToken) {
|
|
17839
17916
|
ctx.output.exitCode = 99;
|
|
17840
17917
|
ctx.output.reason = "runPreviewBuild: vault has no FLY_API_TOKEN \u2014 add it via the dashboard's /secrets page";
|
|
17841
17918
|
return;
|
|
17842
17919
|
}
|
|
17843
|
-
const orgSlug =
|
|
17844
|
-
const region =
|
|
17845
|
-
const nscTenantId =
|
|
17920
|
+
const orgSlug = previewContext.flyOrgSlug?.trim() || (process.env.FLY_ORG_SLUG ?? "personal").trim();
|
|
17921
|
+
const region = previewContext.flyRegion?.trim() || (process.env.FLY_REGION ?? "fra").trim();
|
|
17922
|
+
const nscTenantId = previewContext.namespaceTenantId?.trim() || "";
|
|
17846
17923
|
console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
|
|
17847
17924
|
if (Object.keys(buildEnv).length > 0) {
|
|
17848
17925
|
const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
@@ -17924,7 +18001,7 @@ var init_runPreviewBuild = __esm({
|
|
|
17924
18001
|
appName,
|
|
17925
18002
|
region,
|
|
17926
18003
|
image: `registry.fly.io/${appName}:${tag}`,
|
|
17927
|
-
env: previewRuntimeEnv({ buildEnv,
|
|
18004
|
+
env: previewRuntimeEnv({ buildEnv, previewVerifyKey: previewContext.previewVerifyKey, pr, repo })
|
|
17928
18005
|
},
|
|
17929
18006
|
flyToken
|
|
17930
18007
|
);
|
|
@@ -19707,10 +19784,10 @@ async function hydrateWorkflows(backend, tenant, cwd) {
|
|
|
19707
19784
|
}
|
|
19708
19785
|
async function hydrateStateWorkspace(config, cwd, backendOverride) {
|
|
19709
19786
|
const tenant = tenantId(config);
|
|
19710
|
-
const configured =
|
|
19787
|
+
const configured = hasStateBackendConfig();
|
|
19711
19788
|
if (!tenant || !configured) {
|
|
19712
19789
|
if (process.env.GITHUB_ACTIONS === "true")
|
|
19713
|
-
throw new Error("
|
|
19790
|
+
throw new Error("Kody backend access is required for runtime workspace documents");
|
|
19714
19791
|
return;
|
|
19715
19792
|
}
|
|
19716
19793
|
const key = `${path43.resolve(cwd)}|${tenant}`;
|
|
@@ -22371,6 +22448,7 @@ async function runChatTurn(opts) {
|
|
|
22371
22448
|
],
|
|
22372
22449
|
systemPromptAppend: systemPrompt,
|
|
22373
22450
|
...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {},
|
|
22451
|
+
...opts.repoToken ? { repoToken: opts.repoToken } : {},
|
|
22374
22452
|
// Cross-repo work is opt-in. Repo Brain's default path remains focused
|
|
22375
22453
|
// on the selected repo even though the server stores clones under
|
|
22376
22454
|
// reposRoot.
|
|
@@ -24334,12 +24412,12 @@ var BrokerSink = class {
|
|
|
24334
24412
|
const be = translateChatEvent(event, this.chatId);
|
|
24335
24413
|
if (!be) return;
|
|
24336
24414
|
this.emitToLog(be);
|
|
24337
|
-
if (this.tenantId &&
|
|
24415
|
+
if (this.tenantId && hasStateBackendConfig2()) {
|
|
24338
24416
|
await createStateBackendFromEnv().appendChatEvent(this.tenantId, this.chatId, be);
|
|
24339
24417
|
}
|
|
24340
24418
|
}
|
|
24341
24419
|
};
|
|
24342
|
-
function
|
|
24420
|
+
function hasStateBackendConfig2(env = process.env) {
|
|
24343
24421
|
return Boolean(env.CONVEX_URL?.trim() && env.KODY_SERVICE_KEY?.trim());
|
|
24344
24422
|
}
|
|
24345
24423
|
var chatQueues = /* @__PURE__ */ new Map();
|
|
@@ -25604,10 +25682,9 @@ async function hydrateDefinitions(options) {
|
|
|
25604
25682
|
}
|
|
25605
25683
|
async function hydrateDefinitionsFromEnv(cwd = process.cwd(), env = process.env) {
|
|
25606
25684
|
const tenantId2 = env.GITHUB_REPOSITORY?.trim();
|
|
25607
|
-
|
|
25608
|
-
if (!hasCredentials) {
|
|
25685
|
+
if (!hasStateBackendConfig(env)) {
|
|
25609
25686
|
if (env.GITHUB_ACTIONS === "true") {
|
|
25610
|
-
throw new Error("
|
|
25687
|
+
throw new Error("GitHub Actions workflow identity is required for backend definitions");
|
|
25611
25688
|
}
|
|
25612
25689
|
return null;
|
|
25613
25690
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.410",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|