@kody-ade/kody-engine 0.4.642 → 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 +270 -25
- 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`, {
|
|
@@ -4108,7 +4202,7 @@ function capabilityToolDefinitions(opts) {
|
|
|
4108
4202
|
description: "Idempotently create, update, close, or reopen one canonical repository Todo for a recurring problem. The Todo family is derived from reportSlug and the stable item id prevents duplicates. Repeating the same state is a no-op; unrelated items in an existing Todo are preserved.",
|
|
4109
4203
|
inputSchema: {
|
|
4110
4204
|
slug: z3.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/).optional().describe("Deprecated; the canonical Todo slug is reportSlug."),
|
|
4111
|
-
itemId: z3.string().regex(/^[a-z0-9][a-z0-9_-]{0,79}$/)
|
|
4205
|
+
itemId: z3.string().regex(/^[a-z0-9][a-z0-9_-]{0,79}$/),
|
|
4112
4206
|
title: z3.string().min(1).max(160),
|
|
4113
4207
|
description: z3.string().max(2e4).optional(),
|
|
4114
4208
|
status: z3.enum(["open", "resolved"]),
|
|
@@ -4119,7 +4213,7 @@ function capabilityToolDefinitions(opts) {
|
|
|
4119
4213
|
handler: async (args) => {
|
|
4120
4214
|
const reportSlug = String(args.reportSlug);
|
|
4121
4215
|
const slug = reportSlug;
|
|
4122
|
-
const itemId =
|
|
4216
|
+
const itemId = String(args.itemId);
|
|
4123
4217
|
const title = String(args.title).trim();
|
|
4124
4218
|
const description = typeof args.description === "string" ? args.description.trim() : "";
|
|
4125
4219
|
const status = args.status === "resolved" ? "resolved" : "open";
|
|
@@ -4136,10 +4230,17 @@ function capabilityToolDefinitions(opts) {
|
|
|
4136
4230
|
const currentItems = Array.isArray(current.items) ? current.items.filter(
|
|
4137
4231
|
(item) => Boolean(item && typeof item === "object" && !Array.isArray(item))
|
|
4138
4232
|
) : [];
|
|
4139
|
-
const
|
|
4233
|
+
const isLegacyFallbackForReport = (item) => {
|
|
4234
|
+
if (itemId === "finding" || item.id !== "finding") return false;
|
|
4235
|
+
const meta = item.meta && typeof item.meta === "object" && !Array.isArray(item.meta) ? item.meta : {};
|
|
4236
|
+
return meta.reportSlug === reportSlug;
|
|
4237
|
+
};
|
|
4238
|
+
const removedLegacyFallback = currentItems.some(isLegacyFallbackForReport);
|
|
4239
|
+
const canonicalItems = currentItems.filter((item) => !isLegacyFallbackForReport(item));
|
|
4240
|
+
const previous = canonicalItems.find((item) => item.id === itemId);
|
|
4140
4241
|
const previousMeta = previous?.meta && typeof previous.meta === "object" && !Array.isArray(previous.meta) ? previous.meta : {};
|
|
4141
4242
|
const unchanged = Boolean(
|
|
4142
|
-
previous && previous.completed === completed && previous.title === title && String(previous.body ?? "") === evidence && previousMeta.reportSlug === reportSlug
|
|
4243
|
+
previous && !removedLegacyFallback && previous.completed === completed && previous.title === title && String(previous.body ?? "") === evidence && previousMeta.reportSlug === reportSlug
|
|
4143
4244
|
);
|
|
4144
4245
|
if (unchanged) {
|
|
4145
4246
|
return {
|
|
@@ -4162,7 +4263,7 @@ function capabilityToolDefinitions(opts) {
|
|
|
4162
4263
|
status
|
|
4163
4264
|
}
|
|
4164
4265
|
};
|
|
4165
|
-
const items = previous ?
|
|
4266
|
+
const items = previous ? canonicalItems.map((item) => item.id === itemId ? nextItem : item) : [...canonicalItems, nextItem];
|
|
4166
4267
|
const doc = {
|
|
4167
4268
|
...current,
|
|
4168
4269
|
version: 1,
|
|
@@ -5264,6 +5365,17 @@ function validateWorkflow(value, options = {}) {
|
|
|
5264
5365
|
"workflow step timeoutSeconds must be an integer from 1 to 3600"
|
|
5265
5366
|
);
|
|
5266
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
|
+
}
|
|
5267
5379
|
validateInputBindings(
|
|
5268
5380
|
step.inputs,
|
|
5269
5381
|
`${base}.inputs`,
|
|
@@ -5557,6 +5669,7 @@ var init_workflowValidation = __esm({
|
|
|
5557
5669
|
"targetFact",
|
|
5558
5670
|
"reason",
|
|
5559
5671
|
"timeoutSeconds",
|
|
5672
|
+
"approval",
|
|
5560
5673
|
"next",
|
|
5561
5674
|
"runWhen",
|
|
5562
5675
|
"continueOn",
|
|
@@ -17182,6 +17295,7 @@ var init_loadSimpleCapability = __esm({
|
|
|
17182
17295
|
if (requiredSubagents.length > 0) ctx.data.requiredSubagents = requiredSubagents;
|
|
17183
17296
|
if (capability.contract?.execution === "script") {
|
|
17184
17297
|
ctx.data.capabilityScriptPath = path43.join(capability.dir, "tools", "run.sh");
|
|
17298
|
+
ctx.data.capabilityConnectionIds = capability.contract.connections ?? [];
|
|
17185
17299
|
ctx.data.capabilitySecretNames = capability.contract.secrets ?? [];
|
|
17186
17300
|
ctx.data.capabilityScriptTimeoutMs = capability.contract.timeoutMs;
|
|
17187
17301
|
}
|
|
@@ -19454,6 +19568,9 @@ var init_prepareSimpleCapabilityRuntime = __esm({
|
|
|
19454
19568
|
prepareSimpleCapabilityRuntime = async (ctx, profile) => {
|
|
19455
19569
|
const requirements = requirementsFrom(ctx);
|
|
19456
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
|
+
}
|
|
19457
19574
|
configureBrowser(ctx, profile, requirements);
|
|
19458
19575
|
if (requirements.qaCredentials) {
|
|
19459
19576
|
await loadQaContext(ctx, profile);
|
|
@@ -21223,6 +21340,33 @@ var init_runScheduledImplementationTick = __esm({
|
|
|
21223
21340
|
}
|
|
21224
21341
|
});
|
|
21225
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
|
+
|
|
21226
21370
|
// src/scripts/runSimpleCapabilityScript.ts
|
|
21227
21371
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
21228
21372
|
import * as fs51 from "fs";
|
|
@@ -21245,6 +21389,7 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21245
21389
|
"src/scripts/runSimpleCapabilityScript.ts"() {
|
|
21246
21390
|
"use strict";
|
|
21247
21391
|
init_capabilityResult();
|
|
21392
|
+
init_runtimeConnections();
|
|
21248
21393
|
init_runtimeSecrets();
|
|
21249
21394
|
init_tickShellRunner();
|
|
21250
21395
|
DEFAULT_SCRIPT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -21258,6 +21403,14 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21258
21403
|
return;
|
|
21259
21404
|
}
|
|
21260
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
|
+
}
|
|
21261
21414
|
const capabilitySecrets = await resolveRuntimeSecrets(ctx.data.capabilitySecretNames, ctx);
|
|
21262
21415
|
for (const warning of capabilitySecrets.warnings) {
|
|
21263
21416
|
process.stderr.write(`\u2192 kody: WARNING ${warning}
|
|
@@ -21269,7 +21422,11 @@ var init_runSimpleCapabilityScript = __esm({
|
|
|
21269
21422
|
env: {
|
|
21270
21423
|
...buildTickChildEnv(process.env, false),
|
|
21271
21424
|
...capabilitySecrets.environment,
|
|
21272
|
-
...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
|
+
} : {}
|
|
21273
21430
|
},
|
|
21274
21431
|
stdio: ["ignore", "pipe", "pipe"],
|
|
21275
21432
|
encoding: "utf-8",
|
|
@@ -24272,7 +24429,7 @@ function workflowRunStatePath(workflowId, runId) {
|
|
|
24272
24429
|
function parseWorkflowRunState(raw) {
|
|
24273
24430
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
24274
24431
|
const state = raw;
|
|
24275
|
-
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")
|
|
24276
24433
|
return null;
|
|
24277
24434
|
const completedStepIds = Array.isArray(state.completedStepIds) ? state.completedStepIds.filter((value) => typeof value === "string") : [];
|
|
24278
24435
|
const transitionCounts = state.transitionCounts && typeof state.transitionCounts === "object" && !Array.isArray(state.transitionCounts) ? Object.fromEntries(
|
|
@@ -24292,6 +24449,7 @@ function parseWorkflowRunState(raw) {
|
|
|
24292
24449
|
...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
|
|
24293
24450
|
completedStepIds,
|
|
24294
24451
|
transitionCounts,
|
|
24452
|
+
...parseWorkflowApproval(state.approval) ? { approval: parseWorkflowApproval(state.approval) } : {},
|
|
24295
24453
|
...input ? { input: { ...input } } : {},
|
|
24296
24454
|
...typeof state.definitionHash === "string" && state.definitionHash.trim() ? { definitionHash: state.definitionHash.trim() } : {},
|
|
24297
24455
|
...steps ? { steps } : {},
|
|
@@ -24301,6 +24459,20 @@ function parseWorkflowRunState(raw) {
|
|
|
24301
24459
|
...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
|
|
24302
24460
|
};
|
|
24303
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
|
+
}
|
|
24304
24476
|
function parseWorkflowSteps(value) {
|
|
24305
24477
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
24306
24478
|
const steps = {};
|
|
@@ -24351,6 +24523,50 @@ var init_workflowRunState = __esm({
|
|
|
24351
24523
|
}
|
|
24352
24524
|
});
|
|
24353
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
|
+
|
|
24354
24570
|
// src/job.ts
|
|
24355
24571
|
var job_exports = {};
|
|
24356
24572
|
__export(job_exports, {
|
|
@@ -24521,7 +24737,7 @@ async function runJob(job, base) {
|
|
|
24521
24737
|
if (base.config && persistRun) {
|
|
24522
24738
|
await upsertRunIndexRowBestEffortAsync(base.config, base.cwd, {
|
|
24523
24739
|
...parentRow,
|
|
24524
|
-
status: result.exitCode === 0 ? "success" : "failed",
|
|
24740
|
+
status: result.workflowState?.status === "waiting-approval" ? "waiting" : result.exitCode === 0 ? "success" : "failed",
|
|
24525
24741
|
summary: result.reason,
|
|
24526
24742
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
24527
24743
|
});
|
|
@@ -24530,7 +24746,7 @@ async function runJob(job, base) {
|
|
|
24530
24746
|
await lease?.checkpoint();
|
|
24531
24747
|
await writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, result.workflowState);
|
|
24532
24748
|
}
|
|
24533
|
-
if (valid.workflowRunId && workflowIdentity && hasGitHubActionsIdentity()) {
|
|
24749
|
+
if (valid.workflowRunId && workflowIdentity && hasGitHubActionsIdentity() && result.workflowState?.status !== "waiting-approval") {
|
|
24534
24750
|
const facts = result.workflowState?.facts ?? {};
|
|
24535
24751
|
await notifyWorkflowCompleted({
|
|
24536
24752
|
workflowId: workflowIdentity,
|
|
@@ -24822,6 +25038,7 @@ function initialWorkflowState(parent, workflow) {
|
|
|
24822
25038
|
status: "done",
|
|
24823
25039
|
completedStepIds: [...prior.completedStepIds],
|
|
24824
25040
|
transitionCounts: { ...prior.transitionCounts },
|
|
25041
|
+
...prior.approval ? { approval: { ...prior.approval } } : {},
|
|
24825
25042
|
...prior.input ? { input: { ...prior.input } } : {},
|
|
24826
25043
|
...prior.steps ? { steps: cloneWorkflowSteps(prior.steps) } : {},
|
|
24827
25044
|
facts: { ...prior.facts },
|
|
@@ -24838,6 +25055,7 @@ function initialWorkflowState(parent, workflow) {
|
|
|
24838
25055
|
...currentStepId ? { currentStepId } : {},
|
|
24839
25056
|
completedStepIds: [...prior?.completedStepIds ?? []],
|
|
24840
25057
|
transitionCounts: { ...prior?.transitionCounts ?? {} },
|
|
25058
|
+
...prior?.approval ? { approval: { ...prior.approval } } : {},
|
|
24841
25059
|
steps: cloneWorkflowSteps(prior?.steps ?? {}),
|
|
24842
25060
|
facts: {
|
|
24843
25061
|
...workflowInputContext(parent.cliArgs),
|
|
@@ -24896,6 +25114,20 @@ async function runGraphCapabilityWorkflow(parent, workflow, capability, base, ch
|
|
|
24896
25114
|
return { ...result, exitCode: 64, reason, workflowState: state };
|
|
24897
25115
|
}
|
|
24898
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
|
+
}
|
|
24899
25131
|
await checkpoint?.(state);
|
|
24900
25132
|
let child;
|
|
24901
25133
|
try {
|
|
@@ -25111,7 +25343,8 @@ function workflowStepToJob(step, parent, chainData, cwd) {
|
|
|
25111
25343
|
const action = step.action ?? step.capability;
|
|
25112
25344
|
const targetNumber = workflowStepTargetNumber(step, parent, chainData);
|
|
25113
25345
|
const mappedInputs = resolveWorkflowStepInputs(step, chainData, cwd);
|
|
25114
|
-
const
|
|
25346
|
+
const genericCapability = usesGenericCapabilityInput(action, cwd);
|
|
25347
|
+
const rawArgs = mappedInputs ? { ...mappedInputs } : genericCapability ? inheritedGenericStepInput(step.capability, parent.cliArgs, cwd) : { ...parent.cliArgs };
|
|
25115
25348
|
if (step.target === "pr") {
|
|
25116
25349
|
if (typeof targetNumber !== "number") {
|
|
25117
25350
|
throw new InvalidJobError(`workflow step ${action} needs a PR target but no prior PR URL is available`);
|
|
@@ -25121,11 +25354,11 @@ function workflowStepToJob(step, parent, chainData, cwd) {
|
|
|
25121
25354
|
rawArgs.issue = targetNumber;
|
|
25122
25355
|
}
|
|
25123
25356
|
const genericInput = capabilityStepInput(
|
|
25124
|
-
step.input ?? mappedInputs ??
|
|
25357
|
+
step.input ?? mappedInputs ?? genericInputFromArgs(rawArgs),
|
|
25125
25358
|
step.target,
|
|
25126
25359
|
targetNumber
|
|
25127
25360
|
);
|
|
25128
|
-
const cliArgs =
|
|
25361
|
+
const cliArgs = genericCapability ? genericInput === void 0 ? {} : { input: JSON.stringify(genericInput) } : filterCliArgsForStep(action, rawArgs);
|
|
25129
25362
|
const target = typeof targetNumber === "number" ? targetNumber : typeof parent.target === "number" ? parent.target : targetFromCliArgs(cliArgs);
|
|
25130
25363
|
return {
|
|
25131
25364
|
action,
|
|
@@ -25176,6 +25409,17 @@ function capabilityInputNames(folder) {
|
|
|
25176
25409
|
if (!properties || typeof properties !== "object" || Array.isArray(properties)) return /* @__PURE__ */ new Set();
|
|
25177
25410
|
return new Set(Object.keys(properties));
|
|
25178
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
|
+
}
|
|
25179
25423
|
function cloneWorkflowSteps(steps) {
|
|
25180
25424
|
return Object.fromEntries(Object.entries(steps).map(([id, step]) => [id, { ...step }]));
|
|
25181
25425
|
}
|
|
@@ -25447,6 +25691,7 @@ var init_job = __esm({
|
|
|
25447
25691
|
init_workflowDefinitions();
|
|
25448
25692
|
init_workflowRunLease();
|
|
25449
25693
|
init_workflowRunState();
|
|
25694
|
+
init_workflowStepApproval();
|
|
25450
25695
|
init_workflowValidation();
|
|
25451
25696
|
init_jobIdentity();
|
|
25452
25697
|
init_jobIdentity();
|
|
@@ -29774,7 +30019,7 @@ import { createInterface as createInterface2 } from "readline";
|
|
|
29774
30019
|
|
|
29775
30020
|
// src/terminal/brain-terminal-adapters.ts
|
|
29776
30021
|
import { spawn as spawn9 } from "child_process";
|
|
29777
|
-
import { createHash as
|
|
30022
|
+
import { createHash as createHash10, randomBytes as randomBytes2 } from "crypto";
|
|
29778
30023
|
import { mkdir, readFile, rename, writeFile as writeFile2 } from "fs/promises";
|
|
29779
30024
|
import * as path57 from "path";
|
|
29780
30025
|
function runTerminalCommand(command, args, input) {
|
|
@@ -29797,7 +30042,7 @@ function runTerminalCommand(command, args, input) {
|
|
|
29797
30042
|
});
|
|
29798
30043
|
}
|
|
29799
30044
|
function storeKey(id) {
|
|
29800
|
-
return
|
|
30045
|
+
return createHash10("sha256").update(id).digest("hex");
|
|
29801
30046
|
}
|
|
29802
30047
|
function isStoredSession(value) {
|
|
29803
30048
|
if (!value || typeof value !== "object") return false;
|
|
@@ -29902,7 +30147,7 @@ var TmuxBrainTerminalRuntime = class {
|
|
|
29902
30147
|
};
|
|
29903
30148
|
|
|
29904
30149
|
// src/terminal/brain-terminal-session.ts
|
|
29905
|
-
import { createHash as
|
|
30150
|
+
import { createHash as createHash11 } from "crypto";
|
|
29906
30151
|
var MAX_CAPTURE_CHARS = 2e5;
|
|
29907
30152
|
function requiredIdentifier(value, name, max = 240) {
|
|
29908
30153
|
if (typeof value !== "string" || !value.trim() || value.length > max) {
|
|
@@ -29983,7 +30228,7 @@ function parseBrainTerminalCommand(value) {
|
|
|
29983
30228
|
}
|
|
29984
30229
|
}
|
|
29985
30230
|
function sessionName(id) {
|
|
29986
|
-
return `kody_${
|
|
30231
|
+
return `kody_${createHash11("sha256").update(id).digest("hex").slice(0, 32)}`;
|
|
29987
30232
|
}
|
|
29988
30233
|
function stateEvent(session) {
|
|
29989
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": {
|