@kody-ade/kody-engine 0.4.372 → 0.4.373
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
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.373",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -3451,7 +3451,10 @@ async function runAgent(opts) {
|
|
|
3451
3451
|
errorMessage2 = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
|
|
3452
3452
|
if (typeof iterator.return === "function") {
|
|
3453
3453
|
try {
|
|
3454
|
-
await
|
|
3454
|
+
await Promise.race([
|
|
3455
|
+
iterator.return(void 0).catch(() => void 0),
|
|
3456
|
+
new Promise((resolve10) => setTimeout(resolve10, 1e4).unref())
|
|
3457
|
+
]);
|
|
3455
3458
|
} catch {
|
|
3456
3459
|
}
|
|
3457
3460
|
}
|
|
@@ -6334,7 +6337,7 @@ function locateLitellmScript() {
|
|
|
6334
6337
|
"python3",
|
|
6335
6338
|
[
|
|
6336
6339
|
"-c",
|
|
6337
|
-
"import os,sys;
|
|
6340
|
+
"import os,sys,site,sysconfig; c=[os.path.join(os.path.dirname(sys.executable),'litellm'),os.path.join(sysconfig.get_path('scripts'),'litellm'),os.path.join(site.USER_BASE,'bin','litellm')]; m=[p for p in c if os.path.exists(p)]; print(m[0] if m else '')"
|
|
6338
6341
|
],
|
|
6339
6342
|
{ encoding: "utf-8", timeout: 1e4 }
|
|
6340
6343
|
).trim();
|
|
@@ -11054,6 +11057,13 @@ var init_commitAndPush = __esm({
|
|
|
11054
11057
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
11055
11058
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
11056
11059
|
if (replay.salvagedFromMissingMarker) ctx.data.salvagedFromMissingMarker = true;
|
|
11060
|
+
if (typeof replay.commitCrash === "string") {
|
|
11061
|
+
ctx.data.commitCrash = replay.commitCrash;
|
|
11062
|
+
if (typeof replay.exitCode === "number" && (ctx.output.exitCode === void 0 || ctx.output.exitCode === 0)) {
|
|
11063
|
+
ctx.output.exitCode = replay.exitCode;
|
|
11064
|
+
}
|
|
11065
|
+
if (!ctx.output.reason && replay.reason) ctx.output.reason = replay.reason;
|
|
11066
|
+
}
|
|
11057
11067
|
ctx.data.commitIdempotencyReplay = true;
|
|
11058
11068
|
process.stderr.write(`[kody commitAndPush] idempotency replay (sentinel ${sentinel})
|
|
11059
11069
|
`);
|
|
@@ -11111,6 +11121,9 @@ var init_commitAndPush = __esm({
|
|
|
11111
11121
|
changedFiles: ctx.data.changedFiles,
|
|
11112
11122
|
hasCommitsAhead: ctx.data.hasCommitsAhead,
|
|
11113
11123
|
salvagedFromMissingMarker: ctx.data.salvagedFromMissingMarker === true,
|
|
11124
|
+
commitCrash: typeof ctx.data.commitCrash === "string" ? ctx.data.commitCrash : void 0,
|
|
11125
|
+
exitCode: ctx.output.exitCode,
|
|
11126
|
+
reason: ctx.output.reason,
|
|
11114
11127
|
writtenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11115
11128
|
},
|
|
11116
11129
|
null,
|
|
@@ -11309,7 +11322,10 @@ var init_composePrompt = __esm({
|
|
|
11309
11322
|
"issue.body",
|
|
11310
11323
|
"issue.commentsFormatted",
|
|
11311
11324
|
"pr.body",
|
|
11312
|
-
"pr.commentsFormatted"
|
|
11325
|
+
"pr.commentsFormatted",
|
|
11326
|
+
// Prior-art bundles PR diffs and review/issue comments — authorable by any
|
|
11327
|
+
// external GitHub user, so exactly as attacker-controllable as issue bodies.
|
|
11328
|
+
"priorArt"
|
|
11313
11329
|
]);
|
|
11314
11330
|
FENCE_END = "----- END UNTRUSTED INPUT -----";
|
|
11315
11331
|
composePrompt = async (ctx, profile) => {
|
|
@@ -12832,23 +12848,34 @@ function firstLine(s) {
|
|
|
12832
12848
|
const head = nl === -1 ? trimmed : trimmed.slice(0, nl);
|
|
12833
12849
|
return head.length > 200 ? `${head.slice(0, 197)}\u2026` : head;
|
|
12834
12850
|
}
|
|
12835
|
-
function
|
|
12851
|
+
function lookupExistingPr(branch, cwd) {
|
|
12836
12852
|
try {
|
|
12837
12853
|
const output = gh(
|
|
12838
|
-
["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,body", "--limit", "1"],
|
|
12854
|
+
["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,body,title,isDraft", "--limit", "1"],
|
|
12839
12855
|
{ cwd, preferRepoToken: true }
|
|
12840
12856
|
);
|
|
12841
12857
|
const arr = JSON.parse(output);
|
|
12842
12858
|
const first = Array.isArray(arr) ? arr[0] : null;
|
|
12843
12859
|
if (first && typeof first.number === "number" && typeof first.url === "string") {
|
|
12844
|
-
|
|
12845
|
-
|
|
12860
|
+
return {
|
|
12861
|
+
pr: {
|
|
12862
|
+
number: first.number,
|
|
12863
|
+
url: first.url,
|
|
12864
|
+
body: typeof first.body === "string" ? first.body : "",
|
|
12865
|
+
title: typeof first.title === "string" ? first.title : "",
|
|
12866
|
+
isDraft: first.isDraft === true
|
|
12867
|
+
},
|
|
12868
|
+
error: null
|
|
12869
|
+
};
|
|
12846
12870
|
}
|
|
12847
|
-
return null;
|
|
12848
|
-
} catch {
|
|
12849
|
-
return null;
|
|
12871
|
+
return { pr: null, error: null };
|
|
12872
|
+
} catch (err) {
|
|
12873
|
+
return { pr: null, error: err instanceof Error ? err.message : String(err) };
|
|
12850
12874
|
}
|
|
12851
12875
|
}
|
|
12876
|
+
function findExistingPr(branch, cwd) {
|
|
12877
|
+
return lookupExistingPr(branch, cwd).pr;
|
|
12878
|
+
}
|
|
12852
12879
|
function recoverSourceIssueNumber(existingBody, branch, prNumber) {
|
|
12853
12880
|
const bodyMatch = existingBody.match(/\bCloses #(\d+)\b/i);
|
|
12854
12881
|
if (bodyMatch) {
|
|
@@ -12875,16 +12902,35 @@ function git2(args, cwd) {
|
|
|
12875
12902
|
stdio: ["pipe", "pipe", "pipe"]
|
|
12876
12903
|
}).trim();
|
|
12877
12904
|
}
|
|
12878
|
-
function updateExistingPr(existing, body, draft, cwd) {
|
|
12905
|
+
function updateExistingPr(existing, body, draft, cwd, preserveBody) {
|
|
12879
12906
|
const stripped = existing.url.replace(/^https:\/\/github\.com\//, "");
|
|
12880
12907
|
const [owner, repo] = stripped.split("/");
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12908
|
+
if (!preserveBody) {
|
|
12909
|
+
try {
|
|
12910
|
+
gh(["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `body=${body}`], {
|
|
12911
|
+
cwd,
|
|
12912
|
+
preferRepoToken: true
|
|
12913
|
+
});
|
|
12914
|
+
} catch (err) {
|
|
12915
|
+
throw new Error(`gh api PATCH #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
12916
|
+
}
|
|
12917
|
+
}
|
|
12918
|
+
if (existing.isDraft === true && !draft) {
|
|
12919
|
+
try {
|
|
12920
|
+
gh(["pr", "ready", String(existing.number)], { cwd, preferRepoToken: true });
|
|
12921
|
+
const promotedTitle = existing.title?.replace(/^\[WIP\]\s*/, "");
|
|
12922
|
+
if (promotedTitle && promotedTitle !== existing.title) {
|
|
12923
|
+
gh(
|
|
12924
|
+
["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `title=${promotedTitle}`],
|
|
12925
|
+
{ cwd, preferRepoToken: true }
|
|
12926
|
+
);
|
|
12927
|
+
}
|
|
12928
|
+
} catch (err) {
|
|
12929
|
+
process.stderr.write(
|
|
12930
|
+
`[kody ensurePr] draft\u2192ready promotion of #${existing.number} failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
12931
|
+
`
|
|
12932
|
+
);
|
|
12933
|
+
}
|
|
12888
12934
|
}
|
|
12889
12935
|
return { url: existing.url, number: existing.number, draft, action: "updated" };
|
|
12890
12936
|
}
|
|
@@ -12897,8 +12943,13 @@ function createPr(branch, base, title, body, draft, cwd) {
|
|
|
12897
12943
|
return { url, number, draft, action: "created" };
|
|
12898
12944
|
}
|
|
12899
12945
|
function recoverFromExistingPr(branch, base, title, body, draft, cwd) {
|
|
12900
|
-
const raced =
|
|
12946
|
+
const { pr: raced, error: lookupError } = lookupExistingPr(branch, cwd);
|
|
12901
12947
|
if (raced) return updateExistingPr(raced, body, draft, cwd);
|
|
12948
|
+
if (lookupError) {
|
|
12949
|
+
throw new Error(
|
|
12950
|
+
`refusing phantom-PR recovery for '${branch}': PR lookup failed (${lookupError}) \u2014 a live PR may own this branch`
|
|
12951
|
+
);
|
|
12952
|
+
}
|
|
12902
12953
|
try {
|
|
12903
12954
|
git2(["push", "origin", "--delete", branch], cwd);
|
|
12904
12955
|
} catch {
|
|
@@ -12916,7 +12967,7 @@ function ensurePr(opts) {
|
|
|
12916
12967
|
const title = buildPrTitle(effectiveOpts.issueNumber, effectiveOpts.issueTitle, effectiveOpts.draft);
|
|
12917
12968
|
const body = buildPrBody(effectiveOpts);
|
|
12918
12969
|
if (existing) {
|
|
12919
|
-
return updateExistingPr(existing, body, opts.draft, opts.cwd);
|
|
12970
|
+
return updateExistingPr(existing, body, opts.draft, opts.cwd, opts.preserveBodyOnUpdate === true);
|
|
12920
12971
|
}
|
|
12921
12972
|
const base = opts.baseBranch && opts.baseBranch.length > 0 ? opts.baseBranch : opts.defaultBranch;
|
|
12922
12973
|
try {
|
|
@@ -13032,6 +13083,9 @@ var init_ensurePr = __esm({
|
|
|
13032
13083
|
changedFiles,
|
|
13033
13084
|
agentSummary: ctx.data.prSummary,
|
|
13034
13085
|
baseBranch,
|
|
13086
|
+
// No fresh commit this run → don't rebuild the body of an existing PR;
|
|
13087
|
+
// it would replace the original agent summary with the empty fallback.
|
|
13088
|
+
preserveBodyOnUpdate: !commitResult?.committed,
|
|
13035
13089
|
cwd: ctx.cwd
|
|
13036
13090
|
});
|
|
13037
13091
|
if (!result.url || result.url.trim().length === 0) {
|
|
@@ -18369,7 +18423,9 @@ function validateModelBundle(bundle, expectedKind) {
|
|
|
18369
18423
|
function readExpectedModelKind(args) {
|
|
18370
18424
|
const value = args?.modelKind;
|
|
18371
18425
|
if (typeof value === "string" && isModelKind(value)) return value;
|
|
18372
|
-
throw new Error(
|
|
18426
|
+
throw new Error(
|
|
18427
|
+
"validateAgencyModelProposal: with.modelKind must be intent, operation, agent, capability, goal, agentLoop, or workflow"
|
|
18428
|
+
);
|
|
18373
18429
|
}
|
|
18374
18430
|
function validateOneModel(rawModel, files, label, strictSingleModel, failures, expectedKind) {
|
|
18375
18431
|
if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) {
|
|
@@ -18378,7 +18434,9 @@ function validateOneModel(rawModel, files, label, strictSingleModel, failures, e
|
|
|
18378
18434
|
}
|
|
18379
18435
|
const model = rawModel;
|
|
18380
18436
|
const kind = stringField6(model.kind);
|
|
18381
|
-
if (!isModelKind(kind))
|
|
18437
|
+
if (!isModelKind(kind)) {
|
|
18438
|
+
failures.push(`${label}.kind must be intent, operation, agent, capability, goal, agentLoop, or workflow`);
|
|
18439
|
+
}
|
|
18382
18440
|
if (expectedKind && kind !== expectedKind) failures.push(`proposal must output model.kind ${expectedKind}`);
|
|
18383
18441
|
const slug = stringField6(model.slug);
|
|
18384
18442
|
if (!isSlug(slug)) failures.push(`${label}.slug must be a lowercase slug`);
|
|
@@ -18396,6 +18454,14 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
|
|
|
18396
18454
|
if (paths.some((filePath) => filePath === "implementations" || filePath.startsWith("implementations/"))) {
|
|
18397
18455
|
failures.push("files must not use obsolete implementation storage");
|
|
18398
18456
|
}
|
|
18457
|
+
if (kind === "intent") {
|
|
18458
|
+
requirePath(paths, `intents/${slug}/intent.json`, "intent state", failures);
|
|
18459
|
+
if (strictSingleModel) rejectOtherRoots(paths, [`intents/${slug}/`], "intent", failures);
|
|
18460
|
+
}
|
|
18461
|
+
if (kind === "operation") {
|
|
18462
|
+
requirePath(paths, `operations/${slug}/operation.json`, "operation contract", failures);
|
|
18463
|
+
if (strictSingleModel) rejectOtherRoots(paths, [`operations/${slug}/`], "operation", failures);
|
|
18464
|
+
}
|
|
18399
18465
|
if (kind === "agent") {
|
|
18400
18466
|
requirePath(paths, `agents/${slug}.md`, "agent file", failures);
|
|
18401
18467
|
if (strictSingleModel) rejectOtherRoots(paths, ["agents/"], "agent", failures);
|
|
@@ -18444,6 +18510,44 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
|
|
|
18444
18510
|
}
|
|
18445
18511
|
}
|
|
18446
18512
|
function validateModelShape(kind, model, files, slug, failures) {
|
|
18513
|
+
if (kind === "intent") {
|
|
18514
|
+
const intent = parseJsonFile(files, `intents/${slug}/intent.json`, failures);
|
|
18515
|
+
if (!stringField6(model.direction)) failures.push("intent model must declare direction");
|
|
18516
|
+
if (!stringField6(intent?.for)) failures.push("intent file must declare direction");
|
|
18517
|
+
if (!isFiniteNumber(model.priority)) failures.push("intent model must declare numeric priority");
|
|
18518
|
+
if (!isFiniteNumber(intent?.priority)) failures.push("intent file must declare numeric priority");
|
|
18519
|
+
if (!hasScope(model.scope)) failures.push("intent model scope must include a repo or area");
|
|
18520
|
+
if (!hasScope(intent?.scope)) failures.push("intent file scope must include a repo or area");
|
|
18521
|
+
if (stringArray4(model.principles).length === 0) failures.push("intent model principles must be non-empty");
|
|
18522
|
+
if (stringArray4(intent?.principles).length === 0) failures.push("intent file principles must be non-empty");
|
|
18523
|
+
if (stringArray4(model.successMeasures).length === 0) {
|
|
18524
|
+
failures.push("intent model successMeasures must be non-empty");
|
|
18525
|
+
}
|
|
18526
|
+
if (stringArray4(intent?.metrics).length === 0) failures.push("intent file metrics must be non-empty");
|
|
18527
|
+
if (!recordField7(model.policy)) failures.push("intent model must declare policy");
|
|
18528
|
+
if (!recordField7(intent?.policy)) failures.push("intent file must declare policy");
|
|
18529
|
+
if (stringField6(model.status) !== "paused" || stringField6(intent?.status) !== "paused") {
|
|
18530
|
+
failures.push("intent proposal status must be paused");
|
|
18531
|
+
}
|
|
18532
|
+
if (intent?.version !== 1) failures.push("intent file version must be 1");
|
|
18533
|
+
if (intent && stringField6(intent.id) !== slug) failures.push("intent id must match model.slug");
|
|
18534
|
+
requireStringArrayIncludes(model.doesNotOwn, "operations", "intent doesNotOwn", failures);
|
|
18535
|
+
requireStringArrayIncludes(model.doesNotOwn, "capability implementation", "intent doesNotOwn", failures);
|
|
18536
|
+
}
|
|
18537
|
+
if (kind === "operation") {
|
|
18538
|
+
const operation = parseJsonFile(files, `operations/${slug}/operation.json`, failures);
|
|
18539
|
+
if (!stringField6(model.responsibility)) failures.push("operation model must declare responsibility");
|
|
18540
|
+
if (!stringField6(operation?.responsibility)) failures.push("operation file must declare responsibility");
|
|
18541
|
+
if (stringArray4(model.intentIds).length === 0) failures.push("operation model intentIds must be non-empty");
|
|
18542
|
+
if (stringArray4(operation?.intentIds).length === 0) failures.push("operation file intentIds must be non-empty");
|
|
18543
|
+
if (stringArray4(model.doesNotOwn).length === 0) failures.push("operation model doesNotOwn must be non-empty");
|
|
18544
|
+
if (stringArray4(operation?.doesNotOwn).length === 0) failures.push("operation file doesNotOwn must be non-empty");
|
|
18545
|
+
if (stringField6(model.status) !== "proposed" || stringField6(operation?.status) !== "proposed") {
|
|
18546
|
+
failures.push("operation proposal status must be proposed");
|
|
18547
|
+
}
|
|
18548
|
+
if (operation?.version !== 1) failures.push("operation file version must be 1");
|
|
18549
|
+
if (operation && stringField6(operation.id) !== slug) failures.push("operation id must match model.slug");
|
|
18550
|
+
}
|
|
18447
18551
|
if (kind === "agent") {
|
|
18448
18552
|
const agentFile = textFile(files, `agents/${slug}.md`);
|
|
18449
18553
|
if (!stringArray4(model.owns).includes("identity") && !containsWord(agentFile, "identity")) {
|
|
@@ -18565,6 +18669,16 @@ function stringArray4(value) {
|
|
|
18565
18669
|
if (!Array.isArray(value)) return [];
|
|
18566
18670
|
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
18567
18671
|
}
|
|
18672
|
+
function recordField7(value) {
|
|
18673
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
18674
|
+
}
|
|
18675
|
+
function isFiniteNumber(value) {
|
|
18676
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
18677
|
+
}
|
|
18678
|
+
function hasScope(value) {
|
|
18679
|
+
const scope = recordField7(value);
|
|
18680
|
+
return stringArray4(scope?.repos).length > 0 || stringArray4(scope?.areas).length > 0;
|
|
18681
|
+
}
|
|
18568
18682
|
function requireStringArrayIncludes(value, expected, label, failures) {
|
|
18569
18683
|
if (!stringArray4(value).includes(expected)) failures.push(`${label} must include ${expected}`);
|
|
18570
18684
|
}
|
|
@@ -18572,7 +18686,7 @@ function isSlug(value) {
|
|
|
18572
18686
|
return /^[a-z][a-z0-9-]{0,63}$/.test(value);
|
|
18573
18687
|
}
|
|
18574
18688
|
function isModelKind(value) {
|
|
18575
|
-
return value === "agent" || value === "capability" || value === "goal" || value === "agentLoop" || value === "workflow";
|
|
18689
|
+
return value === "intent" || value === "operation" || value === "agent" || value === "capability" || value === "goal" || value === "agentLoop" || value === "workflow";
|
|
18576
18690
|
}
|
|
18577
18691
|
var REQUIRED_DOCS, validateAgencyModelProposal;
|
|
18578
18692
|
var init_validateAgencyModelProposal = __esm({
|
|
@@ -18580,6 +18694,8 @@ var init_validateAgencyModelProposal = __esm({
|
|
|
18580
18694
|
"use strict";
|
|
18581
18695
|
init_openAgencyModelReviewPr();
|
|
18582
18696
|
REQUIRED_DOCS = {
|
|
18697
|
+
intent: ["docs/intents.md", "docs/engine-company.md"],
|
|
18698
|
+
operation: ["docs/operations.md", "docs/engine-company.md"],
|
|
18583
18699
|
agent: ["docs/agents.md"],
|
|
18584
18700
|
capability: ["docs/capabilities.md", "docs/capability-kind-map.md", "docs/capability-implementations.md"],
|
|
18585
18701
|
goal: ["docs/goals.md", "docs/jobs-model.md", "docs/capabilities.md"],
|
|
@@ -19678,6 +19794,7 @@ var init_tools = __esm({
|
|
|
19678
19794
|
// src/executor.ts
|
|
19679
19795
|
import { spawn as spawn7 } from "child_process";
|
|
19680
19796
|
import * as fs46 from "fs";
|
|
19797
|
+
import * as os8 from "os";
|
|
19681
19798
|
import * as path44 from "path";
|
|
19682
19799
|
function isMutatingPostflight(scriptName) {
|
|
19683
19800
|
return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
|
|
@@ -19972,6 +20089,11 @@ async function runImplementation(profileName, input) {
|
|
|
19972
20089
|
});
|
|
19973
20090
|
};
|
|
19974
20091
|
ctx.data.__invokeAgent = invokeAgent;
|
|
20092
|
+
const cc = profile.claudeCode;
|
|
20093
|
+
const declaresPluginParts = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
|
|
20094
|
+
if (declaresPluginParts && !profile.scripts.preflight.some((e) => e.script === "buildSyntheticPlugin")) {
|
|
20095
|
+
profile.scripts.preflight = [{ script: "buildSyntheticPlugin" }, ...profile.scripts.preflight];
|
|
20096
|
+
}
|
|
19975
20097
|
try {
|
|
19976
20098
|
for (const entry of profile.scripts.preflight) {
|
|
19977
20099
|
const preLabel = entry.script ?? entry.shell ?? "<unknown>";
|
|
@@ -20147,6 +20269,9 @@ async function runImplementation(profileName, input) {
|
|
|
20147
20269
|
afterNextJob: ctx.output.afterNextJob,
|
|
20148
20270
|
taskState: ctx.data.taskState
|
|
20149
20271
|
});
|
|
20272
|
+
} catch (err) {
|
|
20273
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20274
|
+
return finishAndEnd({ exitCode: 99, reason: ctx.output.reason ?? msg });
|
|
20150
20275
|
} finally {
|
|
20151
20276
|
clearStampedLifecycleLabels(profile, ctx);
|
|
20152
20277
|
if (taskArtifacts) {
|
|
@@ -20463,7 +20588,11 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
20463
20588
|
return;
|
|
20464
20589
|
}
|
|
20465
20590
|
const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
|
|
20466
|
-
const
|
|
20591
|
+
const outputFile = path44.join(
|
|
20592
|
+
os8.tmpdir(),
|
|
20593
|
+
`kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
20594
|
+
);
|
|
20595
|
+
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", KODY_OUTPUT: outputFile };
|
|
20467
20596
|
for (const [k, v] of Object.entries(ctx.args)) {
|
|
20468
20597
|
if (v === void 0 || v === null) continue;
|
|
20469
20598
|
env[`KODY_ARG_${envKey(k)}`] = String(v);
|
|
@@ -20529,7 +20658,25 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
20529
20658
|
ctx.output.reason = `shell '${shellName}' failed to spawn: ${result.spawnErr.message}`;
|
|
20530
20659
|
return;
|
|
20531
20660
|
}
|
|
20532
|
-
|
|
20661
|
+
let sideChannelText = "";
|
|
20662
|
+
try {
|
|
20663
|
+
if (fs46.existsSync(outputFile)) {
|
|
20664
|
+
sideChannelText = fs46.readFileSync(outputFile, "utf-8");
|
|
20665
|
+
fs46.rmSync(outputFile, { force: true });
|
|
20666
|
+
}
|
|
20667
|
+
} catch {
|
|
20668
|
+
}
|
|
20669
|
+
if (sideChannelText.trim().length > 0) {
|
|
20670
|
+
collectShellSideChannels(ctx, sideChannelText);
|
|
20671
|
+
} else {
|
|
20672
|
+
if (SHELL_MARKER_RE.test(stdout)) {
|
|
20673
|
+
process.stderr.write(
|
|
20674
|
+
`[kody] shell '${shellName}': KODY_* markers read from stdout are deprecated \u2014 write them to "$KODY_OUTPUT" instead (stdout markers are forgeable by echoed untrusted text)
|
|
20675
|
+
`
|
|
20676
|
+
);
|
|
20677
|
+
}
|
|
20678
|
+
collectShellSideChannels(ctx, stdout);
|
|
20679
|
+
}
|
|
20533
20680
|
if (timedOut) {
|
|
20534
20681
|
ctx.skipAgent = true;
|
|
20535
20682
|
const seconds = Math.round(timeoutMs / 1e3);
|
|
@@ -20571,7 +20718,7 @@ function flattenConfig(obj, prefix = "") {
|
|
|
20571
20718
|
}
|
|
20572
20719
|
return out;
|
|
20573
20720
|
}
|
|
20574
|
-
var MUTATING_POSTFLIGHTS, MAX_CHAIN_HOPS, DEFAULT_SHELL_TIMEOUT_MS, SIGKILL_GRACE_MS;
|
|
20721
|
+
var MUTATING_POSTFLIGHTS, SHELL_MARKER_RE, MAX_CHAIN_HOPS, DEFAULT_SHELL_TIMEOUT_MS, SIGKILL_GRACE_MS;
|
|
20575
20722
|
var init_executor = __esm({
|
|
20576
20723
|
"src/executor.ts"() {
|
|
20577
20724
|
"use strict";
|
|
@@ -20602,6 +20749,7 @@ var init_executor = __esm({
|
|
|
20602
20749
|
"publishReport",
|
|
20603
20750
|
"openAgencyModelReviewPr"
|
|
20604
20751
|
]);
|
|
20752
|
+
SHELL_MARKER_RE = /^KODY_(SKIP_AGENT|PR_URL|REASON|CAPABILITY_REPORT|CAPABILITY_RESULT)=/m;
|
|
20605
20753
|
MAX_CHAIN_HOPS = 60;
|
|
20606
20754
|
DEFAULT_SHELL_TIMEOUT_MS = 3e5;
|
|
20607
20755
|
SIGKILL_GRACE_MS = 5e3;
|
|
@@ -22116,7 +22264,7 @@ function autoDispatch(opts) {
|
|
|
22116
22264
|
if (!hasKodyMention(rawBody)) return null;
|
|
22117
22265
|
const isBotAuthor = authorLogin === "kody-bot" || authorType === "Bot";
|
|
22118
22266
|
if (!associationAllowed(event, opts?.config)) return null;
|
|
22119
|
-
const body = rawBody
|
|
22267
|
+
const body = rawBody;
|
|
22120
22268
|
const targetNum = Number(issue?.number ?? 0);
|
|
22121
22269
|
const isPr = !!issue?.pull_request;
|
|
22122
22270
|
if (!targetNum) return null;
|
|
@@ -22215,7 +22363,7 @@ function autoDispatchTyped(opts) {
|
|
|
22215
22363
|
if (!targetNum) {
|
|
22216
22364
|
return { kind: "silent", reason: "comment has no associated issue/PR number" };
|
|
22217
22365
|
}
|
|
22218
|
-
const afterTag = extractAfterTag(rawBody
|
|
22366
|
+
const afterTag = extractAfterTag(rawBody);
|
|
22219
22367
|
const tokenRaw = extractSubcommand(afterTag) ?? "";
|
|
22220
22368
|
if ((authorLogin === "kody-bot" || authorType === "Bot") && tokenRaw && !POLITE_WORDS.has(tokenRaw)) {
|
|
22221
22369
|
return {
|
|
@@ -22315,12 +22463,12 @@ function hasKodyMention(body) {
|
|
|
22315
22463
|
function extractAfterTag(body) {
|
|
22316
22464
|
const m = body.match(KODY_MENTION_RE);
|
|
22317
22465
|
if (!m || m.index === void 0) return "";
|
|
22318
|
-
const at = body.indexOf("@kody", m.index);
|
|
22466
|
+
const at = body.toLowerCase().indexOf("@kody", m.index);
|
|
22319
22467
|
return body.slice(at + "@kody".length).trim();
|
|
22320
22468
|
}
|
|
22321
22469
|
function extractSubcommand(afterTag) {
|
|
22322
|
-
const match = afterTag.match(/^([a-
|
|
22323
|
-
return match ? match[1] : null;
|
|
22470
|
+
const match = afterTag.match(/^([a-zA-Z][a-zA-Z0-9-]{1,40})\b/);
|
|
22471
|
+
return match ? match[1].toLowerCase() : null;
|
|
22324
22472
|
}
|
|
22325
22473
|
function extractCommentRest(afterTag, consumedToken) {
|
|
22326
22474
|
let rest = afterTag;
|
|
@@ -22339,7 +22487,7 @@ function parseCommentArgs(rest, inputs) {
|
|
|
22339
22487
|
const t = tokens[i];
|
|
22340
22488
|
if (t.startsWith("--")) {
|
|
22341
22489
|
const eq = t.indexOf("=");
|
|
22342
|
-
const key = eq >= 0 ? t.slice(2, eq) : t.slice(2);
|
|
22490
|
+
const key = (eq >= 0 ? t.slice(2, eq) : t.slice(2)).toLowerCase();
|
|
22343
22491
|
const inlineValue = eq >= 0 ? t.slice(eq + 1) : void 0;
|
|
22344
22492
|
const spec = findInputByFlag(inputs, key);
|
|
22345
22493
|
if (!spec) {
|
|
@@ -22359,9 +22507,12 @@ function parseCommentArgs(rest, inputs) {
|
|
|
22359
22507
|
if (inlineValue === void 0) i++;
|
|
22360
22508
|
continue;
|
|
22361
22509
|
}
|
|
22362
|
-
const
|
|
22510
|
+
const tLower = t.toLowerCase();
|
|
22511
|
+
const enumHit = inputs.find(
|
|
22512
|
+
(s) => s.type === "enum" && s.values?.some((v) => v.toLowerCase() === tLower) && args[s.name] === void 0
|
|
22513
|
+
);
|
|
22363
22514
|
if (enumHit) {
|
|
22364
|
-
args[enumHit.name] =
|
|
22515
|
+
args[enumHit.name] = enumHit.values.find((v) => v.toLowerCase() === tLower);
|
|
22365
22516
|
continue;
|
|
22366
22517
|
}
|
|
22367
22518
|
if (/^-?\d+$/.test(t)) {
|
|
@@ -22371,7 +22522,7 @@ function parseCommentArgs(rest, inputs) {
|
|
|
22371
22522
|
continue;
|
|
22372
22523
|
}
|
|
22373
22524
|
}
|
|
22374
|
-
const boolHit = inputs.find((s) => s.type === "bool" && s.flag === `--${
|
|
22525
|
+
const boolHit = inputs.find((s) => s.type === "bool" && s.flag === `--${tLower}` && args[s.name] === void 0);
|
|
22375
22526
|
if (boolHit) {
|
|
22376
22527
|
args[boolHit.name] = true;
|
|
22377
22528
|
continue;
|
|
@@ -364,9 +364,12 @@ export interface ScriptEntry {
|
|
|
364
364
|
* Filename of a shell script colocated with the implementation
|
|
365
365
|
* (e.g. "apply-prefer.sh"). Resolved relative to the profile's
|
|
366
366
|
* directory. Invoked via `bash <path> <with-args>` with ctx.args
|
|
367
|
-
* exposed as env vars (KODY_ARG_<UPPER_NAME>=<value>).
|
|
368
|
-
*
|
|
369
|
-
*
|
|
367
|
+
* exposed as env vars (KODY_ARG_<UPPER_NAME>=<value>). Side-channel
|
|
368
|
+
* markers (KODY_SKIP_AGENT=true, KODY_PR_URL=, KODY_REASON=,
|
|
369
|
+
* KODY_CAPABILITY_RESULT=, KODY_CAPABILITY_REPORT=) are read from the
|
|
370
|
+
* file named by $KODY_OUTPUT (preferred — not forgeable by echoed
|
|
371
|
+
* untrusted text); plain-stdout markers remain a deprecated fallback.
|
|
372
|
+
* Non-zero exit is treated as a preflight failure.
|
|
370
373
|
*/
|
|
371
374
|
shell?: string
|
|
372
375
|
/**
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
|
-
"command": "node -e 'let s=\"\";process.stdin.on(\"data\",c=>s+=c).on(\"end\",()=>{try{const d=JSON.parse(s);const cmd=(d.tool_input&&d.tool_input.command)||\"\";
|
|
10
|
+
"command": "node -e 'let s=\"\";process.stdin.on(\"data\",c=>s+=c).on(\"end\",()=>{try{const d=JSON.parse(s);const cmd=(d.tool_input&&d.tool_input.command)||\"\";const vcs=t=>/^(?:\\S*\\/)?(git|gh)$/.test(t);const wrap=new Set([\"command\",\"exec\",\"xargs\",\"env\",\"nohup\",\"time\",\"nice\",\"sudo\",\"doas\"]);const seg=cmd.split(/[;&|\\n]+/).some(p=>{const t=p.trim().split(/\\s+/).filter(Boolean);let i=0;while(i<t.length&&(wrap.has(t[i])||/^[A-Za-z_][A-Za-z0-9_]*=/.test(t[i])))i++;return t[i]!==undefined&&vcs(t[i])});const shc=/\\b(?:ba|z|da|k)?sh\\s+[^;|&]*-c\\s*[\"\\x27`]+\\s*(?:\\S*\\/)?(?:git|gh)\\b/.test(cmd);const sub=/[$`]\\(?\\s*(?:\\S*\\/)?(?:git|gh)\\s/.test(cmd);if(seg||shc||sub){process.stderr.write(\"kody blocks git/gh — the wrapper handles VCS; do not run git or gh commands (any invocation form)\\n\");process.exit(2)}}catch{}})'"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
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.373",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|