@kody-ade/kody-engine 0.4.643 → 0.4.645
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 +261 -21
- package/dist/implementations/types.ts +9 -1
- 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.645",
|
|
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
|
repository: {
|
|
@@ -2334,7 +2334,7 @@ function parseCapabilityContract(raw) {
|
|
|
2334
2334
|
if (parsed.execution !== void 0 && parsed.execution !== "agent" && parsed.execution !== "script") {
|
|
2335
2335
|
throw new Error('contract.json execution must be "agent" or "script"');
|
|
2336
2336
|
}
|
|
2337
|
-
const requirements = parseCapabilityRequirements(parsed.requirements);
|
|
2337
|
+
const requirements = parseCapabilityRequirements(parsed.requirements, parsed.execution);
|
|
2338
2338
|
const secrets = parsed.secrets === void 0 ? void 0 : Array.isArray(parsed.secrets) && parsed.secrets.every((name) => typeof name === "string" && /^[A-Z][A-Z0-9_]*$/.test(name)) ? [...new Set(parsed.secrets)] : null;
|
|
2339
2339
|
if (secrets === null) {
|
|
2340
2340
|
throw new Error("contract.json secrets must contain valid environment variable names");
|
|
@@ -2342,6 +2342,13 @@ function parseCapabilityContract(raw) {
|
|
|
2342
2342
|
if (secrets && parsed.execution !== "script") {
|
|
2343
2343
|
throw new Error('contract.json secrets are supported only when execution is "script"');
|
|
2344
2344
|
}
|
|
2345
|
+
const connections = parsed.connections === void 0 ? void 0 : Array.isArray(parsed.connections) && parsed.connections.length > 0 && parsed.connections.every((id) => typeof id === "string" && /^[a-z0-9][a-z0-9_-]{0,79}$/.test(id)) ? [...new Set(parsed.connections)] : null;
|
|
2346
|
+
if (connections === null) {
|
|
2347
|
+
throw new Error("contract.json connections must contain valid Connection ids");
|
|
2348
|
+
}
|
|
2349
|
+
if (connections && parsed.execution !== "script") {
|
|
2350
|
+
throw new Error('contract.json connections are supported only when execution is "script"');
|
|
2351
|
+
}
|
|
2345
2352
|
const timeoutMs = parsed.timeoutMs === void 0 ? void 0 : typeof parsed.timeoutMs === "number" && Number.isInteger(parsed.timeoutMs) && parsed.timeoutMs >= 1e3 && parsed.timeoutMs <= 6 * 60 * 60 * 1e3 ? parsed.timeoutMs : null;
|
|
2346
2353
|
if (timeoutMs === null) {
|
|
2347
2354
|
throw new Error("contract.json timeoutMs must be an integer from 1000 to 21600000");
|
|
@@ -2362,7 +2369,7 @@ function parseCapabilityContract(raw) {
|
|
|
2362
2369
|
throw new Error("contract.json deliveryConfigAllowlist files must also be deliveryPathAllowlist entries");
|
|
2363
2370
|
}
|
|
2364
2371
|
const unsupported = Object.keys(parsed).filter(
|
|
2365
|
-
(key) => key !== "execution" && key !== "deliveryPolicy" && key !== "deliveryPathAllowlist" && key !== "deliveryConfigAllowlist" && key !== "requirements" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
|
|
2372
|
+
(key) => key !== "execution" && key !== "deliveryPolicy" && key !== "deliveryPathAllowlist" && key !== "deliveryConfigAllowlist" && key !== "requirements" && key !== "connections" && key !== "secrets" && key !== "timeoutMs" && key !== "requiredSubagents" && key !== "input" && key !== "output"
|
|
2366
2373
|
);
|
|
2367
2374
|
if (unsupported.length > 0) {
|
|
2368
2375
|
throw new Error(`contract.json contains unsupported fields: ${unsupported.join(", ")}`);
|
|
@@ -2379,6 +2386,7 @@ function parseCapabilityContract(raw) {
|
|
|
2379
2386
|
...deliveryPathAllowlist ? { deliveryPathAllowlist } : {},
|
|
2380
2387
|
...deliveryConfigAllowlist ? { deliveryConfigAllowlist } : {},
|
|
2381
2388
|
...requirements ? { requirements } : {},
|
|
2389
|
+
...connections ? { connections } : {},
|
|
2382
2390
|
...secrets ? { secrets } : {},
|
|
2383
2391
|
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
2384
2392
|
...requiredSubagents ? { requiredSubagents } : {},
|
|
@@ -2425,11 +2433,11 @@ function parseDeliveryPathAllowlist(raw) {
|
|
|
2425
2433
|
}
|
|
2426
2434
|
return paths;
|
|
2427
2435
|
}
|
|
2428
|
-
function parseCapabilityRequirements(raw) {
|
|
2436
|
+
function parseCapabilityRequirements(raw, execution) {
|
|
2429
2437
|
if (raw === void 0) return void 0;
|
|
2430
2438
|
if (!isPlainObject(raw)) throw new Error("contract.json requirements must be an object");
|
|
2431
2439
|
const unsupported = Object.keys(raw).filter(
|
|
2432
|
-
(key) => key !== "cms" && key !== "browser" && key !== "qaCredentials" && key !== "githubTestToken" && key !== "qaAccountCredentials" && key !== "qaAccountModelSettings" && key !== "browserOnly"
|
|
2440
|
+
(key) => key !== "cms" && key !== "browser" && key !== "qaCredentials" && key !== "githubTestToken" && key !== "qaAccountCredentials" && key !== "qaAccountModelSettings" && key !== "browserOnly" && key !== "browserSession" && key !== "browserActions" && key !== "browserOrigins" && key !== "browserFileRoots"
|
|
2433
2441
|
);
|
|
2434
2442
|
if (unsupported.length > 0) {
|
|
2435
2443
|
throw new Error(`contract.json requirements contains unsupported fields: ${unsupported.join(", ")}`);
|
|
@@ -2455,8 +2463,30 @@ function parseCapabilityRequirements(raw) {
|
|
|
2455
2463
|
if (raw.browserOnly !== void 0 && typeof raw.browserOnly !== "boolean") {
|
|
2456
2464
|
throw new Error("contract.json requirements.browserOnly must be boolean");
|
|
2457
2465
|
}
|
|
2458
|
-
if (
|
|
2459
|
-
throw new Error(
|
|
2466
|
+
if (raw.browserSession !== void 0 && raw.browserSession !== "user") {
|
|
2467
|
+
throw new Error('contract.json requirements.browserSession must be "user"');
|
|
2468
|
+
}
|
|
2469
|
+
const browserActions = parseUserBrowserActions(raw.browserActions);
|
|
2470
|
+
const browserOrigins = parseUserBrowserOrigins(raw.browserOrigins);
|
|
2471
|
+
const browserFileRoots = parseUserBrowserFileRoots(raw.browserFileRoots);
|
|
2472
|
+
if ((raw.qaCredentials === true || raw.githubTestToken === true || raw.qaAccountCredentials !== void 0 || raw.qaAccountModelSettings !== void 0 || raw.browserOnly === true || raw.browserSession === "user") && raw.browser !== true) {
|
|
2473
|
+
throw new Error("contract.json protected browser requirement requires browser");
|
|
2474
|
+
}
|
|
2475
|
+
if (raw.browserSession === "user") {
|
|
2476
|
+
if (execution !== "agent") {
|
|
2477
|
+
throw new Error('contract.json requirements.browserSession "user" is supported only when execution is "agent"');
|
|
2478
|
+
}
|
|
2479
|
+
if (!browserActions?.length) {
|
|
2480
|
+
throw new Error("contract.json user browser requirements need browserActions");
|
|
2481
|
+
}
|
|
2482
|
+
if (!browserOrigins?.length) {
|
|
2483
|
+
throw new Error("contract.json user browser requirements need browserOrigins");
|
|
2484
|
+
}
|
|
2485
|
+
if (browserActions.includes("upload") && !browserFileRoots?.length) {
|
|
2486
|
+
throw new Error("contract.json browser upload requires browserFileRoots");
|
|
2487
|
+
}
|
|
2488
|
+
} else if (browserActions !== void 0 || browserOrigins !== void 0 || browserFileRoots !== void 0) {
|
|
2489
|
+
throw new Error('contract.json browserActions, browserOrigins, and browserFileRoots require browserSession "user"');
|
|
2460
2490
|
}
|
|
2461
2491
|
const requirements = {
|
|
2462
2492
|
...raw.cms === true ? { cms: true } : {},
|
|
@@ -2465,10 +2495,58 @@ function parseCapabilityRequirements(raw) {
|
|
|
2465
2495
|
...raw.githubTestToken === true ? { githubTestToken: true } : {},
|
|
2466
2496
|
...Array.isArray(raw.qaAccountCredentials) ? { qaAccountCredentials: [...new Set(raw.qaAccountCredentials)] } : {},
|
|
2467
2497
|
...isPlainObject(raw.qaAccountModelSettings) ? { qaAccountModelSettings: raw.qaAccountModelSettings } : {},
|
|
2468
|
-
...raw.browserOnly === true ? { browserOnly: true } : {}
|
|
2498
|
+
...raw.browserOnly === true ? { browserOnly: true } : {},
|
|
2499
|
+
...raw.browserSession === "user" ? { browserSession: "user" } : {},
|
|
2500
|
+
...browserActions ? { browserActions } : {},
|
|
2501
|
+
...browserOrigins ? { browserOrigins } : {},
|
|
2502
|
+
...browserFileRoots ? { browserFileRoots } : {}
|
|
2469
2503
|
};
|
|
2470
2504
|
return Object.keys(requirements).length > 0 ? requirements : void 0;
|
|
2471
2505
|
}
|
|
2506
|
+
function parseUserBrowserActions(value) {
|
|
2507
|
+
if (value === void 0) return void 0;
|
|
2508
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > USER_BROWSER_ACTIONS.length || !value.every(
|
|
2509
|
+
(action) => typeof action === "string" && USER_BROWSER_ACTIONS.includes(action)
|
|
2510
|
+
)) {
|
|
2511
|
+
throw new Error(`contract.json requirements.browserActions must contain only ${USER_BROWSER_ACTIONS.join(", ")}`);
|
|
2512
|
+
}
|
|
2513
|
+
return [...new Set(value)];
|
|
2514
|
+
}
|
|
2515
|
+
function parseUserBrowserOrigins(value) {
|
|
2516
|
+
if (value === void 0) return void 0;
|
|
2517
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 20) {
|
|
2518
|
+
throw new Error("contract.json requirements.browserOrigins must be a non-empty array");
|
|
2519
|
+
}
|
|
2520
|
+
const origins = value.map((raw) => {
|
|
2521
|
+
if (typeof raw !== "string") throw new Error("invalid browser origin");
|
|
2522
|
+
let parsed;
|
|
2523
|
+
try {
|
|
2524
|
+
parsed = new URL(raw);
|
|
2525
|
+
} catch {
|
|
2526
|
+
throw new Error("invalid browser origin");
|
|
2527
|
+
}
|
|
2528
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.origin !== raw.replace(/\/$/, "")) {
|
|
2529
|
+
throw new Error("contract.json requirements.browserOrigins must contain HTTPS origins only");
|
|
2530
|
+
}
|
|
2531
|
+
return parsed.origin;
|
|
2532
|
+
});
|
|
2533
|
+
return [...new Set(origins)];
|
|
2534
|
+
}
|
|
2535
|
+
function parseUserBrowserFileRoots(value) {
|
|
2536
|
+
if (value === void 0) return void 0;
|
|
2537
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 20) {
|
|
2538
|
+
throw new Error("contract.json requirements.browserFileRoots must be a non-empty array");
|
|
2539
|
+
}
|
|
2540
|
+
const roots = value.map((raw) => {
|
|
2541
|
+
if (typeof raw !== "string") throw new Error("invalid browser file root");
|
|
2542
|
+
const root = raw.replaceAll("\\", "/").replace(/^\/+|\/+$/g, "");
|
|
2543
|
+
if (!root || root.length > 300 || root.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
2544
|
+
throw new Error("contract.json requirements.browserFileRoots contains an unsafe path");
|
|
2545
|
+
}
|
|
2546
|
+
return root;
|
|
2547
|
+
});
|
|
2548
|
+
return [...new Set(roots)];
|
|
2549
|
+
}
|
|
2472
2550
|
function isRegularFile(filePath) {
|
|
2473
2551
|
try {
|
|
2474
2552
|
const stat = fs8.lstatSync(filePath);
|
|
@@ -2545,6 +2623,7 @@ function parseWorkflowStep(value) {
|
|
|
2545
2623
|
const target = stringField(raw.target);
|
|
2546
2624
|
const delivery = stringField(raw.delivery);
|
|
2547
2625
|
const targetFact = stringField(raw.targetFact ?? raw.target_fact);
|
|
2626
|
+
const approval = stringField(raw.approval);
|
|
2548
2627
|
const timeoutSeconds = typeof raw.timeoutSeconds === "number" && Number.isInteger(raw.timeoutSeconds) && raw.timeoutSeconds > 0 && raw.timeoutSeconds <= 3600 ? raw.timeoutSeconds : void 0;
|
|
2549
2628
|
const hasInput = Object.hasOwn(raw, "input");
|
|
2550
2629
|
const inputs = parseWorkflowInputBindings(raw.inputs);
|
|
@@ -2562,6 +2641,7 @@ function parseWorkflowStep(value) {
|
|
|
2562
2641
|
...targetFact ? { targetFact } : {},
|
|
2563
2642
|
...reason ? { reason } : {},
|
|
2564
2643
|
...timeoutSeconds ? { timeoutSeconds } : {},
|
|
2644
|
+
...approval === "required" ? { approval: "required" } : {},
|
|
2565
2645
|
...next ? { next } : {},
|
|
2566
2646
|
...isPlainObject(raw.runWhen) ? { runWhen: raw.runWhen } : {},
|
|
2567
2647
|
...stringList(raw.continueOn ?? raw.continue_on).length > 0 ? { continueOn: stringList(raw.continueOn ?? raw.continue_on) } : {},
|
|
@@ -2636,7 +2716,7 @@ function isSafeSlug(value) {
|
|
|
2636
2716
|
function isSafeStepId(value) {
|
|
2637
2717
|
return /^[A-Za-z][A-Za-z0-9_-]*$/.test(value) && !value.includes("..");
|
|
2638
2718
|
}
|
|
2639
|
-
var CAPABILITY_BODY_FILE, CAPABILITY_CONTRACT_FILE, CAPABILITY_PROFILE_FILE, CANONICAL_CAPABILITY_BODY_FILE, CANONICAL_CAPABILITY_DEFINITION_FILE;
|
|
2719
|
+
var CAPABILITY_BODY_FILE, CAPABILITY_CONTRACT_FILE, CAPABILITY_PROFILE_FILE, CANONICAL_CAPABILITY_BODY_FILE, CANONICAL_CAPABILITY_DEFINITION_FILE, USER_BROWSER_ACTIONS;
|
|
2640
2720
|
var init_capabilityFolders = __esm({
|
|
2641
2721
|
"src/capabilityFolders.ts"() {
|
|
2642
2722
|
"use strict";
|
|
@@ -2645,6 +2725,7 @@ var init_capabilityFolders = __esm({
|
|
|
2645
2725
|
CAPABILITY_PROFILE_FILE = CAPABILITY_BODY_FILE;
|
|
2646
2726
|
CANONICAL_CAPABILITY_BODY_FILE = "capability.md";
|
|
2647
2727
|
CANONICAL_CAPABILITY_DEFINITION_FILE = "definition.json";
|
|
2728
|
+
USER_BROWSER_ACTIONS = ["navigate", "click", "fill", "upload", "scroll", "wait"];
|
|
2648
2729
|
}
|
|
2649
2730
|
});
|
|
2650
2731
|
|
|
@@ -3165,6 +3246,19 @@ async function readRuntimeSecretFromKody(name, env = process.env) {
|
|
|
3165
3246
|
const body = await response.json();
|
|
3166
3247
|
return typeof body.value === "string" ? body.value : null;
|
|
3167
3248
|
}
|
|
3249
|
+
async function readRuntimeConnectionFromKody(id, env = process.env) {
|
|
3250
|
+
const token = await githubOidcToken(env);
|
|
3251
|
+
const response = await fetch(`${resolveKodyApiUrl(env)}/api/kody/engine/connection`, {
|
|
3252
|
+
method: "POST",
|
|
3253
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
3254
|
+
body: JSON.stringify({ id }),
|
|
3255
|
+
signal: AbortSignal.timeout(15e3)
|
|
3256
|
+
});
|
|
3257
|
+
if (response.status === 404) return null;
|
|
3258
|
+
if (!response.ok) throw new Error(`Kody Connection request failed (${response.status})`);
|
|
3259
|
+
const body = await response.json();
|
|
3260
|
+
return body.connection ?? null;
|
|
3261
|
+
}
|
|
3168
3262
|
async function writeRuntimeSecretsToKody(secrets, env = process.env) {
|
|
3169
3263
|
const token = await githubOidcToken(env);
|
|
3170
3264
|
const response = await fetch(`${resolveKodyApiUrl(env)}/api/kody/engine/secret`, {
|
|
@@ -5271,6 +5365,17 @@ function validateWorkflow(value, options = {}) {
|
|
|
5271
5365
|
"workflow step timeoutSeconds must be an integer from 1 to 3600"
|
|
5272
5366
|
);
|
|
5273
5367
|
}
|
|
5368
|
+
if (step.approval !== void 0 && step.approval !== "required") {
|
|
5369
|
+
issue(issues, "invalid_step_approval", `${base}.approval`, 'workflow step approval must be "required"');
|
|
5370
|
+
}
|
|
5371
|
+
if (step.approval === "required" && !text(step.id)) {
|
|
5372
|
+
issue(
|
|
5373
|
+
issues,
|
|
5374
|
+
"approval_requires_step_id",
|
|
5375
|
+
`${base}.approval`,
|
|
5376
|
+
"an approval-gated workflow step must have a stable id"
|
|
5377
|
+
);
|
|
5378
|
+
}
|
|
5274
5379
|
validateInputBindings(
|
|
5275
5380
|
step.inputs,
|
|
5276
5381
|
`${base}.inputs`,
|
|
@@ -5564,6 +5669,7 @@ var init_workflowValidation = __esm({
|
|
|
5564
5669
|
"targetFact",
|
|
5565
5670
|
"reason",
|
|
5566
5671
|
"timeoutSeconds",
|
|
5672
|
+
"approval",
|
|
5567
5673
|
"next",
|
|
5568
5674
|
"runWhen",
|
|
5569
5675
|
"continueOn",
|
|
@@ -8184,7 +8290,9 @@ import * as os4 from "os";
|
|
|
8184
8290
|
import * as path27 from "path";
|
|
8185
8291
|
async function checkLitellmHealth(url) {
|
|
8186
8292
|
try {
|
|
8187
|
-
const response = await fetch(`${url}/health`, {
|
|
8293
|
+
const response = await fetch(`${url.replace(/\/+$/, "")}/health/liveliness`, {
|
|
8294
|
+
signal: AbortSignal.timeout(3e3)
|
|
8295
|
+
});
|
|
8188
8296
|
return response.ok;
|
|
8189
8297
|
} catch {
|
|
8190
8298
|
return false;
|
|
@@ -17189,6 +17297,7 @@ var init_loadSimpleCapability = __esm({
|
|
|
17189
17297
|
if (requiredSubagents.length > 0) ctx.data.requiredSubagents = requiredSubagents;
|
|
17190
17298
|
if (capability.contract?.execution === "script") {
|
|
17191
17299
|
ctx.data.capabilityScriptPath = path43.join(capability.dir, "tools", "run.sh");
|
|
17300
|
+
ctx.data.capabilityConnectionIds = capability.contract.connections ?? [];
|
|
17192
17301
|
ctx.data.capabilitySecretNames = capability.contract.secrets ?? [];
|
|
17193
17302
|
ctx.data.capabilityScriptTimeoutMs = capability.contract.timeoutMs;
|
|
17194
17303
|
}
|
|
@@ -19461,6 +19570,9 @@ var init_prepareSimpleCapabilityRuntime = __esm({
|
|
|
19461
19570
|
prepareSimpleCapabilityRuntime = async (ctx, profile) => {
|
|
19462
19571
|
const requirements = requirementsFrom(ctx);
|
|
19463
19572
|
if (!requirements.browser) return;
|
|
19573
|
+
if (requirements.browserSession === "user") {
|
|
19574
|
+
throw new Error("Capability requires the Dashboard user browser session and cannot run in CI");
|
|
19575
|
+
}
|
|
19464
19576
|
configureBrowser(ctx, profile, requirements);
|
|
19465
19577
|
if (requirements.qaCredentials) {
|
|
19466
19578
|
await loadQaContext(ctx, profile);
|
|
@@ -21230,6 +21342,33 @@ var init_runScheduledImplementationTick = __esm({
|
|
|
21230
21342
|
}
|
|
21231
21343
|
});
|
|
21232
21344
|
|
|
21345
|
+
// src/scripts/runtimeConnections.ts
|
|
21346
|
+
async function resolveRuntimeConnections(ids, declaredSecrets, load = readRuntimeConnectionFromKody) {
|
|
21347
|
+
const requested = Array.isArray(ids) ? [...new Set(ids.filter((id) => typeof id === "string" && /^[a-z0-9][a-z0-9-]{0,63}$/.test(id)))] : [];
|
|
21348
|
+
const allowedSecrets = new Set(
|
|
21349
|
+
Array.isArray(declaredSecrets) ? declaredSecrets.filter((name) => typeof name === "string") : []
|
|
21350
|
+
);
|
|
21351
|
+
const connections = [];
|
|
21352
|
+
for (const id of requested) {
|
|
21353
|
+
const connection = await load(id);
|
|
21354
|
+
if (!connection) throw new Error(`Connection ${id} was not found`);
|
|
21355
|
+
if (connection.status !== "connected") throw new Error(`Connection ${id} is not connected`);
|
|
21356
|
+
for (const secretName of Object.values(connection.credentialRefs)) {
|
|
21357
|
+
if (!allowedSecrets.has(secretName)) {
|
|
21358
|
+
throw new Error(`Connection ${id} credential ${secretName} is not allowlisted by the Capability`);
|
|
21359
|
+
}
|
|
21360
|
+
}
|
|
21361
|
+
connections.push(connection);
|
|
21362
|
+
}
|
|
21363
|
+
return connections;
|
|
21364
|
+
}
|
|
21365
|
+
var init_runtimeConnections = __esm({
|
|
21366
|
+
"src/scripts/runtimeConnections.ts"() {
|
|
21367
|
+
"use strict";
|
|
21368
|
+
init_kody_api_client();
|
|
21369
|
+
}
|
|
21370
|
+
});
|
|
21371
|
+
|
|
21233
21372
|
// src/scripts/runSimpleCapabilityScript.ts
|
|
21234
21373
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
21235
21374
|
import * as fs51 from "fs";
|
|
@@ -21252,6 +21391,7 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21252
21391
|
"src/scripts/runSimpleCapabilityScript.ts"() {
|
|
21253
21392
|
"use strict";
|
|
21254
21393
|
init_capabilityResult();
|
|
21394
|
+
init_runtimeConnections();
|
|
21255
21395
|
init_runtimeSecrets();
|
|
21256
21396
|
init_tickShellRunner();
|
|
21257
21397
|
DEFAULT_SCRIPT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -21265,6 +21405,14 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21265
21405
|
return;
|
|
21266
21406
|
}
|
|
21267
21407
|
const capabilityEnvironment = isStringRecord(ctx.data.capabilityEnvironment) ? ctx.data.capabilityEnvironment : {};
|
|
21408
|
+
let connections;
|
|
21409
|
+
try {
|
|
21410
|
+
connections = await resolveRuntimeConnections(ctx.data.capabilityConnectionIds, ctx.data.capabilitySecretNames);
|
|
21411
|
+
} catch (error) {
|
|
21412
|
+
ctx.output.exitCode = 78;
|
|
21413
|
+
ctx.output.reason = error instanceof Error ? error.message : "Capability Connection loading failed";
|
|
21414
|
+
return;
|
|
21415
|
+
}
|
|
21268
21416
|
const capabilitySecrets = await resolveRuntimeSecrets(ctx.data.capabilitySecretNames, ctx);
|
|
21269
21417
|
for (const warning of capabilitySecrets.warnings) {
|
|
21270
21418
|
process.stderr.write(`\u2192 kody: WARNING ${warning}
|
|
@@ -21276,7 +21424,11 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21276
21424
|
env: {
|
|
21277
21425
|
...buildTickChildEnv(process.env, false),
|
|
21278
21426
|
...capabilitySecrets.environment,
|
|
21279
|
-
...capabilityEnvironment
|
|
21427
|
+
...capabilityEnvironment,
|
|
21428
|
+
...connections.length > 0 ? {
|
|
21429
|
+
KODY_CONNECTIONS_JSON: JSON.stringify(connections),
|
|
21430
|
+
...connections.length === 1 ? { KODY_CONNECTION_JSON: JSON.stringify(connections[0]) } : {}
|
|
21431
|
+
} : {}
|
|
21280
21432
|
},
|
|
21281
21433
|
stdio: ["ignore", "pipe", "pipe"],
|
|
21282
21434
|
encoding: "utf-8",
|
|
@@ -24279,7 +24431,7 @@ function workflowRunStatePath(workflowId, runId) {
|
|
|
24279
24431
|
function parseWorkflowRunState(raw) {
|
|
24280
24432
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
24281
24433
|
const state = raw;
|
|
24282
|
-
if (state.status !== "running" && state.status !== "blocked" && state.status !== "failed" && state.status !== "done")
|
|
24434
|
+
if (state.status !== "running" && state.status !== "waiting-approval" && state.status !== "blocked" && state.status !== "failed" && state.status !== "done")
|
|
24283
24435
|
return null;
|
|
24284
24436
|
const completedStepIds = Array.isArray(state.completedStepIds) ? state.completedStepIds.filter((value) => typeof value === "string") : [];
|
|
24285
24437
|
const transitionCounts = state.transitionCounts && typeof state.transitionCounts === "object" && !Array.isArray(state.transitionCounts) ? Object.fromEntries(
|
|
@@ -24299,6 +24451,7 @@ function parseWorkflowRunState(raw) {
|
|
|
24299
24451
|
...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
|
|
24300
24452
|
completedStepIds,
|
|
24301
24453
|
transitionCounts,
|
|
24454
|
+
...parseWorkflowApproval(state.approval) ? { approval: parseWorkflowApproval(state.approval) } : {},
|
|
24302
24455
|
...input ? { input: { ...input } } : {},
|
|
24303
24456
|
...typeof state.definitionHash === "string" && state.definitionHash.trim() ? { definitionHash: state.definitionHash.trim() } : {},
|
|
24304
24457
|
...steps ? { steps } : {},
|
|
@@ -24308,6 +24461,20 @@ function parseWorkflowRunState(raw) {
|
|
|
24308
24461
|
...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
|
|
24309
24462
|
};
|
|
24310
24463
|
}
|
|
24464
|
+
function parseWorkflowApproval(value) {
|
|
24465
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
24466
|
+
const approval = value;
|
|
24467
|
+
if (typeof approval.stepId !== "string" || typeof approval.action !== "string" || typeof approval.contextHash !== "string" || approval.status !== "pending" && approval.status !== "approved" && approval.status !== "consumed")
|
|
24468
|
+
return void 0;
|
|
24469
|
+
return {
|
|
24470
|
+
stepId: approval.stepId,
|
|
24471
|
+
action: approval.action,
|
|
24472
|
+
contextHash: approval.contextHash,
|
|
24473
|
+
status: approval.status,
|
|
24474
|
+
...typeof approval.approvedAt === "string" ? { approvedAt: approval.approvedAt } : {},
|
|
24475
|
+
...typeof approval.approvedBy === "string" ? { approvedBy: approval.approvedBy } : {}
|
|
24476
|
+
};
|
|
24477
|
+
}
|
|
24311
24478
|
function parseWorkflowSteps(value) {
|
|
24312
24479
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
24313
24480
|
const steps = {};
|
|
@@ -24358,6 +24525,50 @@ var init_workflowRunState = __esm({
|
|
|
24358
24525
|
}
|
|
24359
24526
|
});
|
|
24360
24527
|
|
|
24528
|
+
// src/workflowStepApproval.ts
|
|
24529
|
+
import { createHash as createHash9 } from "crypto";
|
|
24530
|
+
function workflowStepApprovalContextHash(state, stepId) {
|
|
24531
|
+
const value = stableJson2({
|
|
24532
|
+
stepId,
|
|
24533
|
+
definitionHash: state.definitionHash ?? null,
|
|
24534
|
+
input: state.input ?? {},
|
|
24535
|
+
facts: state.facts ?? {}
|
|
24536
|
+
});
|
|
24537
|
+
return `sha256:${createHash9("sha256").update(value).digest("hex")}`;
|
|
24538
|
+
}
|
|
24539
|
+
function requireWorkflowStepApproval(state, stepId) {
|
|
24540
|
+
const contextHash = workflowStepApprovalContextHash(state, stepId);
|
|
24541
|
+
if (state.approval?.stepId === stepId && state.approval.contextHash === contextHash && state.approval.status === "approved") {
|
|
24542
|
+
return {
|
|
24543
|
+
...state,
|
|
24544
|
+
status: "running",
|
|
24545
|
+
approval: { ...state.approval, status: "consumed" }
|
|
24546
|
+
};
|
|
24547
|
+
}
|
|
24548
|
+
return {
|
|
24549
|
+
...state,
|
|
24550
|
+
status: "waiting-approval",
|
|
24551
|
+
approval: {
|
|
24552
|
+
stepId,
|
|
24553
|
+
action: `workflow-step:${stepId}`,
|
|
24554
|
+
contextHash,
|
|
24555
|
+
status: "pending"
|
|
24556
|
+
}
|
|
24557
|
+
};
|
|
24558
|
+
}
|
|
24559
|
+
function stableJson2(value) {
|
|
24560
|
+
if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
|
|
24561
|
+
if (value && typeof value === "object") {
|
|
24562
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
|
|
24563
|
+
}
|
|
24564
|
+
return JSON.stringify(value) ?? "undefined";
|
|
24565
|
+
}
|
|
24566
|
+
var init_workflowStepApproval = __esm({
|
|
24567
|
+
"src/workflowStepApproval.ts"() {
|
|
24568
|
+
"use strict";
|
|
24569
|
+
}
|
|
24570
|
+
});
|
|
24571
|
+
|
|
24361
24572
|
// src/job.ts
|
|
24362
24573
|
var job_exports = {};
|
|
24363
24574
|
__export(job_exports, {
|
|
@@ -24528,7 +24739,7 @@ async function runJob(job, base) {
|
|
|
24528
24739
|
if (base.config && persistRun) {
|
|
24529
24740
|
await upsertRunIndexRowBestEffortAsync(base.config, base.cwd, {
|
|
24530
24741
|
...parentRow,
|
|
24531
|
-
status: result.exitCode === 0 ? "success" : "failed",
|
|
24742
|
+
status: result.workflowState?.status === "waiting-approval" ? "waiting" : result.exitCode === 0 ? "success" : "failed",
|
|
24532
24743
|
summary: result.reason,
|
|
24533
24744
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
24534
24745
|
});
|
|
@@ -24537,7 +24748,7 @@ async function runJob(job, base) {
|
|
|
24537
24748
|
await lease?.checkpoint();
|
|
24538
24749
|
await writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, result.workflowState);
|
|
24539
24750
|
}
|
|
24540
|
-
if (valid.workflowRunId && workflowIdentity && hasGitHubActionsIdentity()) {
|
|
24751
|
+
if (valid.workflowRunId && workflowIdentity && hasGitHubActionsIdentity() && result.workflowState?.status !== "waiting-approval") {
|
|
24541
24752
|
const facts = result.workflowState?.facts ?? {};
|
|
24542
24753
|
await notifyWorkflowCompleted({
|
|
24543
24754
|
workflowId: workflowIdentity,
|
|
@@ -24829,6 +25040,7 @@ function initialWorkflowState(parent, workflow) {
|
|
|
24829
25040
|
status: "done",
|
|
24830
25041
|
completedStepIds: [...prior.completedStepIds],
|
|
24831
25042
|
transitionCounts: { ...prior.transitionCounts },
|
|
25043
|
+
...prior.approval ? { approval: { ...prior.approval } } : {},
|
|
24832
25044
|
...prior.input ? { input: { ...prior.input } } : {},
|
|
24833
25045
|
...prior.steps ? { steps: cloneWorkflowSteps(prior.steps) } : {},
|
|
24834
25046
|
facts: { ...prior.facts },
|
|
@@ -24845,6 +25057,7 @@ function initialWorkflowState(parent, workflow) {
|
|
|
24845
25057
|
...currentStepId ? { currentStepId } : {},
|
|
24846
25058
|
completedStepIds: [...prior?.completedStepIds ?? []],
|
|
24847
25059
|
transitionCounts: { ...prior?.transitionCounts ?? {} },
|
|
25060
|
+
...prior?.approval ? { approval: { ...prior.approval } } : {},
|
|
24848
25061
|
steps: cloneWorkflowSteps(prior?.steps ?? {}),
|
|
24849
25062
|
facts: {
|
|
24850
25063
|
...workflowInputContext(parent.cliArgs),
|
|
@@ -24903,6 +25116,20 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
|
|
|
24903
25116
|
return { ...result, exitCode: 64, reason, workflowState: state };
|
|
24904
25117
|
}
|
|
24905
25118
|
const label = step.action ?? step.capability;
|
|
25119
|
+
if (step.approval === "required") {
|
|
25120
|
+
const approvalState = requireWorkflowStepApproval(state, step.id);
|
|
25121
|
+
state.status = approvalState.status;
|
|
25122
|
+
state.approval = approvalState.approval;
|
|
25123
|
+
await checkpoint?.(state);
|
|
25124
|
+
if (state.status === "waiting-approval") {
|
|
25125
|
+
return {
|
|
25126
|
+
...result,
|
|
25127
|
+
exitCode: 0,
|
|
25128
|
+
reason: `Approval required before workflow step ${step.id}`,
|
|
25129
|
+
workflowState: state
|
|
25130
|
+
};
|
|
25131
|
+
}
|
|
25132
|
+
}
|
|
24906
25133
|
await checkpoint?.(state);
|
|
24907
25134
|
let child;
|
|
24908
25135
|
try {
|
|
@@ -25118,7 +25345,8 @@ function workflowStepToJob(step, parent, chainData, cwd) {
|
|
|
25118
25345
|
const action = step.action ?? step.capability;
|
|
25119
25346
|
const targetNumber = workflowStepTargetNumber(step, parent, chainData);
|
|
25120
25347
|
const mappedInputs = resolveWorkflowStepInputs(step, chainData, cwd);
|
|
25121
|
-
const
|
|
25348
|
+
const genericCapability = usesGenericCapabilityInput(action, cwd);
|
|
25349
|
+
const rawArgs = mappedInputs ? { ...mappedInputs } : genericCapability ? inheritedGenericStepInput(step.capability, parent.cliArgs, cwd) : { ...parent.cliArgs };
|
|
25122
25350
|
if (step.target === "pr") {
|
|
25123
25351
|
if (typeof targetNumber !== "number") {
|
|
25124
25352
|
throw new InvalidJobError(`workflow step ${action} needs a PR target but no prior PR URL is available`);
|
|
@@ -25128,11 +25356,11 @@ function workflowStepToJob(step, parent, chainData, cwd) {
|
|
|
25128
25356
|
rawArgs.issue = targetNumber;
|
|
25129
25357
|
}
|
|
25130
25358
|
const genericInput = capabilityStepInput(
|
|
25131
|
-
step.input ?? mappedInputs ??
|
|
25359
|
+
step.input ?? mappedInputs ?? genericInputFromArgs(rawArgs),
|
|
25132
25360
|
step.target,
|
|
25133
25361
|
targetNumber
|
|
25134
25362
|
);
|
|
25135
|
-
const cliArgs =
|
|
25363
|
+
const cliArgs = genericCapability ? genericInput === void 0 ? {} : { input: JSON.stringify(genericInput) } : filterCliArgsForStep(action, rawArgs);
|
|
25136
25364
|
const target = typeof targetNumber === "number" ? targetNumber : typeof parent.target === "number" ? parent.target : targetFromCliArgs(cliArgs);
|
|
25137
25365
|
return {
|
|
25138
25366
|
action,
|
|
@@ -25183,6 +25411,17 @@ function capabilityInputNames(folder) {
|
|
|
25183
25411
|
if (!properties || typeof properties !== "object" || Array.isArray(properties)) return /* @__PURE__ */ new Set();
|
|
25184
25412
|
return new Set(Object.keys(properties));
|
|
25185
25413
|
}
|
|
25414
|
+
function inheritedGenericStepInput(capability, parentArgs, cwd) {
|
|
25415
|
+
const input = workflowInputContext(parentArgs);
|
|
25416
|
+
const folder = resolveCapabilityFolder(capability, hydratedCapabilitiesRoot(cwd));
|
|
25417
|
+
if (!folder) return input;
|
|
25418
|
+
if (!folder.contractPath) return input;
|
|
25419
|
+
if (folder.config.inputSchema?.additionalProperties !== false) return input;
|
|
25420
|
+
const accepted = capabilityInputNames(folder);
|
|
25421
|
+
if (accepted.size === 0) return input;
|
|
25422
|
+
const routing = /* @__PURE__ */ new Set(["base"]);
|
|
25423
|
+
return Object.fromEntries(Object.entries(input).filter(([name]) => accepted.has(name) || routing.has(name)));
|
|
25424
|
+
}
|
|
25186
25425
|
function cloneWorkflowSteps(steps) {
|
|
25187
25426
|
return Object.fromEntries(Object.entries(steps).map(([id, step]) => [id, { ...step }]));
|
|
25188
25427
|
}
|
|
@@ -25454,6 +25693,7 @@ var init_job = __esm({
|
|
|
25454
25693
|
init_workflowDefinitions();
|
|
25455
25694
|
init_workflowRunLease();
|
|
25456
25695
|
init_workflowRunState();
|
|
25696
|
+
init_workflowStepApproval();
|
|
25457
25697
|
init_workflowValidation();
|
|
25458
25698
|
init_jobIdentity();
|
|
25459
25699
|
init_jobIdentity();
|
|
@@ -29781,7 +30021,7 @@ import { createInterface as createInterface2 } from "readline";
|
|
|
29781
30021
|
|
|
29782
30022
|
// src/terminal/brain-terminal-adapters.ts
|
|
29783
30023
|
import { spawn as spawn9 } from "child_process";
|
|
29784
|
-
import { createHash as
|
|
30024
|
+
import { createHash as createHash10, randomBytes as randomBytes2 } from "crypto";
|
|
29785
30025
|
import { mkdir, readFile, rename, writeFile as writeFile2 } from "fs/promises";
|
|
29786
30026
|
import * as path57 from "path";
|
|
29787
30027
|
function runTerminalCommand(command, args, input) {
|
|
@@ -29804,7 +30044,7 @@ function runTerminalCommand(command, args, input) {
|
|
|
29804
30044
|
});
|
|
29805
30045
|
}
|
|
29806
30046
|
function storeKey(id) {
|
|
29807
|
-
return
|
|
30047
|
+
return createHash10("sha256").update(id).digest("hex");
|
|
29808
30048
|
}
|
|
29809
30049
|
function isStoredSession(value) {
|
|
29810
30050
|
if (!value || typeof value !== "object") return false;
|
|
@@ -29909,7 +30149,7 @@ var TmuxBrainTerminalRuntime = class {
|
|
|
29909
30149
|
};
|
|
29910
30150
|
|
|
29911
30151
|
// src/terminal/brain-terminal-session.ts
|
|
29912
|
-
import { createHash as
|
|
30152
|
+
import { createHash as createHash11 } from "crypto";
|
|
29913
30153
|
var MAX_CAPTURE_CHARS = 2e5;
|
|
29914
30154
|
function requiredIdentifier(value, name, max = 240) {
|
|
29915
30155
|
if (typeof value !== "string" || !value.trim() || value.length > max) {
|
|
@@ -29990,7 +30230,7 @@ function parseBrainTerminalCommand(value) {
|
|
|
29990
30230
|
}
|
|
29991
30231
|
}
|
|
29992
30232
|
function sessionName(id) {
|
|
29993
|
-
return `kody_${
|
|
30233
|
+
return `kody_${createHash11("sha256").update(id).digest("hex").slice(0, 32)}`;
|
|
29994
30234
|
}
|
|
29995
30235
|
function stateEvent(session) {
|
|
29996
30236
|
return {
|
|
@@ -600,7 +600,7 @@ export interface Job {
|
|
|
600
600
|
}
|
|
601
601
|
|
|
602
602
|
export interface WorkflowRunState {
|
|
603
|
-
status: "running" | "blocked" | "failed" | "done"
|
|
603
|
+
status: "running" | "waiting-approval" | "blocked" | "failed" | "done"
|
|
604
604
|
/** Immutable input supplied when this workflow run started. */
|
|
605
605
|
input?: Record<string, unknown>
|
|
606
606
|
/** Hash of the workflow definition used by this run. */
|
|
@@ -608,6 +608,14 @@ export interface WorkflowRunState {
|
|
|
608
608
|
currentStepId?: string
|
|
609
609
|
completedStepIds: string[]
|
|
610
610
|
transitionCounts: Record<string, number>
|
|
611
|
+
approval?: {
|
|
612
|
+
stepId: string
|
|
613
|
+
action: string
|
|
614
|
+
contextHash: string
|
|
615
|
+
status: "pending" | "approved" | "consumed"
|
|
616
|
+
approvedAt?: string
|
|
617
|
+
approvedBy?: string
|
|
618
|
+
}
|
|
611
619
|
/** Exact per-step handoffs for audit, resume, and debugging. */
|
|
612
620
|
steps?: Record<
|
|
613
621
|
string,
|
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.645",
|
|
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
|
"repository": {
|