@kody-ade/kody-engine 0.4.643 → 0.4.644
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 +258 -20
- 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.644",
|
|
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",
|
|
@@ -17189,6 +17295,7 @@ var init_loadSimpleCapability = __esm({
|
|
|
17189
17295
|
if (requiredSubagents.length > 0) ctx.data.requiredSubagents = requiredSubagents;
|
|
17190
17296
|
if (capability.contract?.execution === "script") {
|
|
17191
17297
|
ctx.data.capabilityScriptPath = path43.join(capability.dir, "tools", "run.sh");
|
|
17298
|
+
ctx.data.capabilityConnectionIds = capability.contract.connections ?? [];
|
|
17192
17299
|
ctx.data.capabilitySecretNames = capability.contract.secrets ?? [];
|
|
17193
17300
|
ctx.data.capabilityScriptTimeoutMs = capability.contract.timeoutMs;
|
|
17194
17301
|
}
|
|
@@ -19461,6 +19568,9 @@ var init_prepareSimpleCapabilityRuntime = __esm({
|
|
|
19461
19568
|
prepareSimpleCapabilityRuntime = async (ctx, profile) => {
|
|
19462
19569
|
const requirements = requirementsFrom(ctx);
|
|
19463
19570
|
if (!requirements.browser) return;
|
|
19571
|
+
if (requirements.browserSession === "user") {
|
|
19572
|
+
throw new Error("Capability requires the Dashboard user browser session and cannot run in CI");
|
|
19573
|
+
}
|
|
19464
19574
|
configureBrowser(ctx, profile, requirements);
|
|
19465
19575
|
if (requirements.qaCredentials) {
|
|
19466
19576
|
await loadQaContext(ctx, profile);
|
|
@@ -21230,6 +21340,33 @@ var init_runScheduledImplementationTick = __esm({
|
|
|
21230
21340
|
}
|
|
21231
21341
|
});
|
|
21232
21342
|
|
|
21343
|
+
// src/scripts/runtimeConnections.ts
|
|
21344
|
+
async function resolveRuntimeConnections(ids, declaredSecrets, load = readRuntimeConnectionFromKody) {
|
|
21345
|
+
const requested = Array.isArray(ids) ? [...new Set(ids.filter((id) => typeof id === "string" && /^[a-z0-9][a-z0-9-]{0,63}$/.test(id)))] : [];
|
|
21346
|
+
const allowedSecrets = new Set(
|
|
21347
|
+
Array.isArray(declaredSecrets) ? declaredSecrets.filter((name) => typeof name === "string") : []
|
|
21348
|
+
);
|
|
21349
|
+
const connections = [];
|
|
21350
|
+
for (const id of requested) {
|
|
21351
|
+
const connection = await load(id);
|
|
21352
|
+
if (!connection) throw new Error(`Connection ${id} was not found`);
|
|
21353
|
+
if (connection.status !== "connected") throw new Error(`Connection ${id} is not connected`);
|
|
21354
|
+
for (const secretName of Object.values(connection.credentialRefs)) {
|
|
21355
|
+
if (!allowedSecrets.has(secretName)) {
|
|
21356
|
+
throw new Error(`Connection ${id} credential ${secretName} is not allowlisted by the Capability`);
|
|
21357
|
+
}
|
|
21358
|
+
}
|
|
21359
|
+
connections.push(connection);
|
|
21360
|
+
}
|
|
21361
|
+
return connections;
|
|
21362
|
+
}
|
|
21363
|
+
var init_runtimeConnections = __esm({
|
|
21364
|
+
"src/scripts/runtimeConnections.ts"() {
|
|
21365
|
+
"use strict";
|
|
21366
|
+
init_kody_api_client();
|
|
21367
|
+
}
|
|
21368
|
+
});
|
|
21369
|
+
|
|
21233
21370
|
// src/scripts/runSimpleCapabilityScript.ts
|
|
21234
21371
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
21235
21372
|
import * as fs51 from "fs";
|
|
@@ -21252,6 +21389,7 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21252
21389
|
"src/scripts/runSimpleCapabilityScript.ts"() {
|
|
21253
21390
|
"use strict";
|
|
21254
21391
|
init_capabilityResult();
|
|
21392
|
+
init_runtimeConnections();
|
|
21255
21393
|
init_runtimeSecrets();
|
|
21256
21394
|
init_tickShellRunner();
|
|
21257
21395
|
DEFAULT_SCRIPT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -21265,6 +21403,14 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21265
21403
|
return;
|
|
21266
21404
|
}
|
|
21267
21405
|
const capabilityEnvironment = isStringRecord(ctx.data.capabilityEnvironment) ? ctx.data.capabilityEnvironment : {};
|
|
21406
|
+
let connections;
|
|
21407
|
+
try {
|
|
21408
|
+
connections = await resolveRuntimeConnections(ctx.data.capabilityConnectionIds, ctx.data.capabilitySecretNames);
|
|
21409
|
+
} catch (error) {
|
|
21410
|
+
ctx.output.exitCode = 78;
|
|
21411
|
+
ctx.output.reason = error instanceof Error ? error.message : "Capability Connection loading failed";
|
|
21412
|
+
return;
|
|
21413
|
+
}
|
|
21268
21414
|
const capabilitySecrets = await resolveRuntimeSecrets(ctx.data.capabilitySecretNames, ctx);
|
|
21269
21415
|
for (const warning of capabilitySecrets.warnings) {
|
|
21270
21416
|
process.stderr.write(`\u2192 kody: WARNING ${warning}
|
|
@@ -21276,7 +21422,11 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21276
21422
|
env: {
|
|
21277
21423
|
...buildTickChildEnv(process.env, false),
|
|
21278
21424
|
...capabilitySecrets.environment,
|
|
21279
|
-
...capabilityEnvironment
|
|
21425
|
+
...capabilityEnvironment,
|
|
21426
|
+
...connections.length > 0 ? {
|
|
21427
|
+
KODY_CONNECTIONS_JSON: JSON.stringify(connections),
|
|
21428
|
+
...connections.length === 1 ? { KODY_CONNECTION_JSON: JSON.stringify(connections[0]) } : {}
|
|
21429
|
+
} : {}
|
|
21280
21430
|
},
|
|
21281
21431
|
stdio: ["ignore", "pipe", "pipe"],
|
|
21282
21432
|
encoding: "utf-8",
|
|
@@ -24279,7 +24429,7 @@ function workflowRunStatePath(workflowId, runId) {
|
|
|
24279
24429
|
function parseWorkflowRunState(raw) {
|
|
24280
24430
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
24281
24431
|
const state = raw;
|
|
24282
|
-
if (state.status !== "running" && state.status !== "blocked" && state.status !== "failed" && state.status !== "done")
|
|
24432
|
+
if (state.status !== "running" && state.status !== "waiting-approval" && state.status !== "blocked" && state.status !== "failed" && state.status !== "done")
|
|
24283
24433
|
return null;
|
|
24284
24434
|
const completedStepIds = Array.isArray(state.completedStepIds) ? state.completedStepIds.filter((value) => typeof value === "string") : [];
|
|
24285
24435
|
const transitionCounts = state.transitionCounts && typeof state.transitionCounts === "object" && !Array.isArray(state.transitionCounts) ? Object.fromEntries(
|
|
@@ -24299,6 +24449,7 @@ function parseWorkflowRunState(raw) {
|
|
|
24299
24449
|
...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
|
|
24300
24450
|
completedStepIds,
|
|
24301
24451
|
transitionCounts,
|
|
24452
|
+
...parseWorkflowApproval(state.approval) ? { approval: parseWorkflowApproval(state.approval) } : {},
|
|
24302
24453
|
...input ? { input: { ...input } } : {},
|
|
24303
24454
|
...typeof state.definitionHash === "string" && state.definitionHash.trim() ? { definitionHash: state.definitionHash.trim() } : {},
|
|
24304
24455
|
...steps ? { steps } : {},
|
|
@@ -24308,6 +24459,20 @@ function parseWorkflowRunState(raw) {
|
|
|
24308
24459
|
...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
|
|
24309
24460
|
};
|
|
24310
24461
|
}
|
|
24462
|
+
function parseWorkflowApproval(value) {
|
|
24463
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
24464
|
+
const approval = value;
|
|
24465
|
+
if (typeof approval.stepId !== "string" || typeof approval.action !== "string" || typeof approval.contextHash !== "string" || approval.status !== "pending" && approval.status !== "approved" && approval.status !== "consumed")
|
|
24466
|
+
return void 0;
|
|
24467
|
+
return {
|
|
24468
|
+
stepId: approval.stepId,
|
|
24469
|
+
action: approval.action,
|
|
24470
|
+
contextHash: approval.contextHash,
|
|
24471
|
+
status: approval.status,
|
|
24472
|
+
...typeof approval.approvedAt === "string" ? { approvedAt: approval.approvedAt } : {},
|
|
24473
|
+
...typeof approval.approvedBy === "string" ? { approvedBy: approval.approvedBy } : {}
|
|
24474
|
+
};
|
|
24475
|
+
}
|
|
24311
24476
|
function parseWorkflowSteps(value) {
|
|
24312
24477
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
24313
24478
|
const steps = {};
|
|
@@ -24358,6 +24523,50 @@ var init_workflowRunState = __esm({
|
|
|
24358
24523
|
}
|
|
24359
24524
|
});
|
|
24360
24525
|
|
|
24526
|
+
// src/workflowStepApproval.ts
|
|
24527
|
+
import { createHash as createHash9 } from "crypto";
|
|
24528
|
+
function workflowStepApprovalContextHash(state, stepId) {
|
|
24529
|
+
const value = stableJson2({
|
|
24530
|
+
stepId,
|
|
24531
|
+
definitionHash: state.definitionHash ?? null,
|
|
24532
|
+
input: state.input ?? {},
|
|
24533
|
+
facts: state.facts ?? {}
|
|
24534
|
+
});
|
|
24535
|
+
return `sha256:${createHash9("sha256").update(value).digest("hex")}`;
|
|
24536
|
+
}
|
|
24537
|
+
function requireWorkflowStepApproval(state, stepId) {
|
|
24538
|
+
const contextHash = workflowStepApprovalContextHash(state, stepId);
|
|
24539
|
+
if (state.approval?.stepId === stepId && state.approval.contextHash === contextHash && state.approval.status === "approved") {
|
|
24540
|
+
return {
|
|
24541
|
+
...state,
|
|
24542
|
+
status: "running",
|
|
24543
|
+
approval: { ...state.approval, status: "consumed" }
|
|
24544
|
+
};
|
|
24545
|
+
}
|
|
24546
|
+
return {
|
|
24547
|
+
...state,
|
|
24548
|
+
status: "waiting-approval",
|
|
24549
|
+
approval: {
|
|
24550
|
+
stepId,
|
|
24551
|
+
action: `workflow-step:${stepId}`,
|
|
24552
|
+
contextHash,
|
|
24553
|
+
status: "pending"
|
|
24554
|
+
}
|
|
24555
|
+
};
|
|
24556
|
+
}
|
|
24557
|
+
function stableJson2(value) {
|
|
24558
|
+
if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
|
|
24559
|
+
if (value && typeof value === "object") {
|
|
24560
|
+
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson2(item)}`).join(",")}}`;
|
|
24561
|
+
}
|
|
24562
|
+
return JSON.stringify(value) ?? "undefined";
|
|
24563
|
+
}
|
|
24564
|
+
var init_workflowStepApproval = __esm({
|
|
24565
|
+
"src/workflowStepApproval.ts"() {
|
|
24566
|
+
"use strict";
|
|
24567
|
+
}
|
|
24568
|
+
});
|
|
24569
|
+
|
|
24361
24570
|
// src/job.ts
|
|
24362
24571
|
var job_exports = {};
|
|
24363
24572
|
__export(job_exports, {
|
|
@@ -24528,7 +24737,7 @@ async function runJob(job, base) {
|
|
|
24528
24737
|
if (base.config && persistRun) {
|
|
24529
24738
|
await upsertRunIndexRowBestEffortAsync(base.config, base.cwd, {
|
|
24530
24739
|
...parentRow,
|
|
24531
|
-
status: result.exitCode === 0 ? "success" : "failed",
|
|
24740
|
+
status: result.workflowState?.status === "waiting-approval" ? "waiting" : result.exitCode === 0 ? "success" : "failed",
|
|
24532
24741
|
summary: result.reason,
|
|
24533
24742
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
24534
24743
|
});
|
|
@@ -24537,7 +24746,7 @@ async function runJob(job, base) {
|
|
|
24537
24746
|
await lease?.checkpoint();
|
|
24538
24747
|
await writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, result.workflowState);
|
|
24539
24748
|
}
|
|
24540
|
-
if (valid.workflowRunId && workflowIdentity && hasGitHubActionsIdentity()) {
|
|
24749
|
+
if (valid.workflowRunId && workflowIdentity && hasGitHubActionsIdentity() && result.workflowState?.status !== "waiting-approval") {
|
|
24541
24750
|
const facts = result.workflowState?.facts ?? {};
|
|
24542
24751
|
await notifyWorkflowCompleted({
|
|
24543
24752
|
workflowId: workflowIdentity,
|
|
@@ -24829,6 +25038,7 @@ function initialWorkflowState(parent, workflow) {
|
|
|
24829
25038
|
status: "done",
|
|
24830
25039
|
completedStepIds: [...prior.completedStepIds],
|
|
24831
25040
|
transitionCounts: { ...prior.transitionCounts },
|
|
25041
|
+
...prior.approval ? { approval: { ...prior.approval } } : {},
|
|
24832
25042
|
...prior.input ? { input: { ...prior.input } } : {},
|
|
24833
25043
|
...prior.steps ? { steps: cloneWorkflowSteps(prior.steps) } : {},
|
|
24834
25044
|
facts: { ...prior.facts },
|
|
@@ -24845,6 +25055,7 @@ function initialWorkflowState(parent, workflow) {
|
|
|
24845
25055
|
...currentStepId ? { currentStepId } : {},
|
|
24846
25056
|
completedStepIds: [...prior?.completedStepIds ?? []],
|
|
24847
25057
|
transitionCounts: { ...prior?.transitionCounts ?? {} },
|
|
25058
|
+
...prior?.approval ? { approval: { ...prior.approval } } : {},
|
|
24848
25059
|
steps: cloneWorkflowSteps(prior?.steps ?? {}),
|
|
24849
25060
|
facts: {
|
|
24850
25061
|
...workflowInputContext(parent.cliArgs),
|
|
@@ -24903,6 +25114,20 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
|
|
|
24903
25114
|
return { ...result, exitCode: 64, reason, workflowState: state };
|
|
24904
25115
|
}
|
|
24905
25116
|
const label = step.action ?? step.capability;
|
|
25117
|
+
if (step.approval === "required") {
|
|
25118
|
+
const approvalState = requireWorkflowStepApproval(state, step.id);
|
|
25119
|
+
state.status = approvalState.status;
|
|
25120
|
+
state.approval = approvalState.approval;
|
|
25121
|
+
await checkpoint?.(state);
|
|
25122
|
+
if (state.status === "waiting-approval") {
|
|
25123
|
+
return {
|
|
25124
|
+
...result,
|
|
25125
|
+
exitCode: 0,
|
|
25126
|
+
reason: `Approval required before workflow step ${step.id}`,
|
|
25127
|
+
workflowState: state
|
|
25128
|
+
};
|
|
25129
|
+
}
|
|
25130
|
+
}
|
|
24906
25131
|
await checkpoint?.(state);
|
|
24907
25132
|
let child;
|
|
24908
25133
|
try {
|
|
@@ -25118,7 +25343,8 @@ function workflowStepToJob(step, parent, chainData, cwd) {
|
|
|
25118
25343
|
const action = step.action ?? step.capability;
|
|
25119
25344
|
const targetNumber = workflowStepTargetNumber(step, parent, chainData);
|
|
25120
25345
|
const mappedInputs = resolveWorkflowStepInputs(step, chainData, cwd);
|
|
25121
|
-
const
|
|
25346
|
+
const genericCapability = usesGenericCapabilityInput(action, cwd);
|
|
25347
|
+
const rawArgs = mappedInputs ? { ...mappedInputs } : genericCapability ? inheritedGenericStepInput(step.capability, parent.cliArgs, cwd) : { ...parent.cliArgs };
|
|
25122
25348
|
if (step.target === "pr") {
|
|
25123
25349
|
if (typeof targetNumber !== "number") {
|
|
25124
25350
|
throw new InvalidJobError(`workflow step ${action} needs a PR target but no prior PR URL is available`);
|
|
@@ -25128,11 +25354,11 @@ function workflowStepToJob(step, parent, chainData, cwd) {
|
|
|
25128
25354
|
rawArgs.issue = targetNumber;
|
|
25129
25355
|
}
|
|
25130
25356
|
const genericInput = capabilityStepInput(
|
|
25131
|
-
step.input ?? mappedInputs ??
|
|
25357
|
+
step.input ?? mappedInputs ?? genericInputFromArgs(rawArgs),
|
|
25132
25358
|
step.target,
|
|
25133
25359
|
targetNumber
|
|
25134
25360
|
);
|
|
25135
|
-
const cliArgs =
|
|
25361
|
+
const cliArgs = genericCapability ? genericInput === void 0 ? {} : { input: JSON.stringify(genericInput) } : filterCliArgsForStep(action, rawArgs);
|
|
25136
25362
|
const target = typeof targetNumber === "number" ? targetNumber : typeof parent.target === "number" ? parent.target : targetFromCliArgs(cliArgs);
|
|
25137
25363
|
return {
|
|
25138
25364
|
action,
|
|
@@ -25183,6 +25409,17 @@ function capabilityInputNames(folder) {
|
|
|
25183
25409
|
if (!properties || typeof properties !== "object" || Array.isArray(properties)) return /* @__PURE__ */ new Set();
|
|
25184
25410
|
return new Set(Object.keys(properties));
|
|
25185
25411
|
}
|
|
25412
|
+
function inheritedGenericStepInput(capability, parentArgs, cwd) {
|
|
25413
|
+
const input = workflowInputContext(parentArgs);
|
|
25414
|
+
const folder = resolveCapabilityFolder(capability, hydratedCapabilitiesRoot(cwd));
|
|
25415
|
+
if (!folder) return input;
|
|
25416
|
+
if (!folder.contractPath) return input;
|
|
25417
|
+
if (folder.config.inputSchema?.additionalProperties !== false) return input;
|
|
25418
|
+
const accepted = capabilityInputNames(folder);
|
|
25419
|
+
if (accepted.size === 0) return input;
|
|
25420
|
+
const routing = /* @__PURE__ */ new Set(["base"]);
|
|
25421
|
+
return Object.fromEntries(Object.entries(input).filter(([name]) => accepted.has(name) || routing.has(name)));
|
|
25422
|
+
}
|
|
25186
25423
|
function cloneWorkflowSteps(steps) {
|
|
25187
25424
|
return Object.fromEntries(Object.entries(steps).map(([id, step]) => [id, { ...step }]));
|
|
25188
25425
|
}
|
|
@@ -25454,6 +25691,7 @@ var init_job = __esm({
|
|
|
25454
25691
|
init_workflowDefinitions();
|
|
25455
25692
|
init_workflowRunLease();
|
|
25456
25693
|
init_workflowRunState();
|
|
25694
|
+
init_workflowStepApproval();
|
|
25457
25695
|
init_workflowValidation();
|
|
25458
25696
|
init_jobIdentity();
|
|
25459
25697
|
init_jobIdentity();
|
|
@@ -29781,7 +30019,7 @@ import { createInterface as createInterface2 } from "readline";
|
|
|
29781
30019
|
|
|
29782
30020
|
// src/terminal/brain-terminal-adapters.ts
|
|
29783
30021
|
import { spawn as spawn9 } from "child_process";
|
|
29784
|
-
import { createHash as
|
|
30022
|
+
import { createHash as createHash10, randomBytes as randomBytes2 } from "crypto";
|
|
29785
30023
|
import { mkdir, readFile, rename, writeFile as writeFile2 } from "fs/promises";
|
|
29786
30024
|
import * as path57 from "path";
|
|
29787
30025
|
function runTerminalCommand(command, args, input) {
|
|
@@ -29804,7 +30042,7 @@ function runTerminalCommand(command, args, input) {
|
|
|
29804
30042
|
});
|
|
29805
30043
|
}
|
|
29806
30044
|
function storeKey(id) {
|
|
29807
|
-
return
|
|
30045
|
+
return createHash10("sha256").update(id).digest("hex");
|
|
29808
30046
|
}
|
|
29809
30047
|
function isStoredSession(value) {
|
|
29810
30048
|
if (!value || typeof value !== "object") return false;
|
|
@@ -29909,7 +30147,7 @@ var TmuxBrainTerminalRuntime = class {
|
|
|
29909
30147
|
};
|
|
29910
30148
|
|
|
29911
30149
|
// src/terminal/brain-terminal-session.ts
|
|
29912
|
-
import { createHash as
|
|
30150
|
+
import { createHash as createHash11 } from "crypto";
|
|
29913
30151
|
var MAX_CAPTURE_CHARS = 2e5;
|
|
29914
30152
|
function requiredIdentifier(value, name, max = 240) {
|
|
29915
30153
|
if (typeof value !== "string" || !value.trim() || value.length > max) {
|
|
@@ -29990,7 +30228,7 @@ function parseBrainTerminalCommand(value) {
|
|
|
29990
30228
|
}
|
|
29991
30229
|
}
|
|
29992
30230
|
function sessionName(id) {
|
|
29993
|
-
return `kody_${
|
|
30231
|
+
return `kody_${createHash11("sha256").update(id).digest("hex").slice(0, 32)}`;
|
|
29994
30232
|
}
|
|
29995
30233
|
function stateEvent(session) {
|
|
29996
30234
|
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.644",
|
|
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": {
|