@kody-ade/kody-engine 0.4.372 → 0.4.374
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 +1148 -317
- package/dist/implementations/types.ts +21 -3
- package/dist/plugins/hooks/block-git.json +1 -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.374",
|
|
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",
|
|
@@ -1064,8 +1064,8 @@ function formatAssistant(msg, _opts) {
|
|
|
1064
1064
|
const lines = [];
|
|
1065
1065
|
for (const block of content) {
|
|
1066
1066
|
if (block.type === "text") {
|
|
1067
|
-
const
|
|
1068
|
-
if (
|
|
1067
|
+
const text2 = block.text.trim();
|
|
1068
|
+
if (text2) lines.push(text2);
|
|
1069
1069
|
} else if (block.type === "tool_use") {
|
|
1070
1070
|
const tu = block;
|
|
1071
1071
|
lines.push(`\u2192 ${tu.name}${summarizeToolInput(tu.name, tu.input)}`);
|
|
@@ -1079,14 +1079,14 @@ function formatUserToolResult(msg, opts) {
|
|
|
1079
1079
|
for (const block of content) {
|
|
1080
1080
|
if (block.type === "tool_result") {
|
|
1081
1081
|
const tr = block;
|
|
1082
|
-
const
|
|
1083
|
-
const lineCount =
|
|
1084
|
-
const sizeBytes =
|
|
1082
|
+
const text2 = stringifyToolContent(tr.content);
|
|
1083
|
+
const lineCount = text2.split("\n").length;
|
|
1084
|
+
const sizeBytes = text2.length;
|
|
1085
1085
|
const flag = tr.is_error ? " ERROR" : "";
|
|
1086
1086
|
const summary = ` \u21B3${flag} ${lineCount} lines, ${formatBytes(sizeBytes)}`;
|
|
1087
1087
|
if (opts.verbose) {
|
|
1088
1088
|
lines.push(`${summary}
|
|
1089
|
-
${truncate2(
|
|
1089
|
+
${truncate2(text2, 4e3)}`);
|
|
1090
1090
|
} else {
|
|
1091
1091
|
lines.push(summary);
|
|
1092
1092
|
}
|
|
@@ -1612,7 +1612,7 @@ function cmsHeaders(opts) {
|
|
|
1612
1612
|
}
|
|
1613
1613
|
};
|
|
1614
1614
|
}
|
|
1615
|
-
async function callDashboardCms(opts,
|
|
1615
|
+
async function callDashboardCms(opts, path52, init = {}) {
|
|
1616
1616
|
const baseUrl = dashboardBaseUrl(opts);
|
|
1617
1617
|
if (!baseUrl) {
|
|
1618
1618
|
return {
|
|
@@ -1624,7 +1624,7 @@ async function callDashboardCms(opts, path51, init = {}) {
|
|
|
1624
1624
|
const headerResult = cmsHeaders(opts);
|
|
1625
1625
|
if (!headerResult.ok) return headerResult;
|
|
1626
1626
|
try {
|
|
1627
|
-
const res = await fetch(`${baseUrl}${
|
|
1627
|
+
const res = await fetch(`${baseUrl}${path52}`, {
|
|
1628
1628
|
...init,
|
|
1629
1629
|
headers: {
|
|
1630
1630
|
...headerResult.headers,
|
|
@@ -1696,8 +1696,8 @@ function documentArg(value) {
|
|
|
1696
1696
|
function normalizeCmsDocumentIdInput(input) {
|
|
1697
1697
|
const trimmed = stripWrappingQuotes(input.trim());
|
|
1698
1698
|
const withoutQuery = trimmed.split(/[?#]/, 1)[0] ?? trimmed;
|
|
1699
|
-
const
|
|
1700
|
-
return
|
|
1699
|
+
const path52 = parseDocumentPath(withoutQuery);
|
|
1700
|
+
return path52 ?? parseDocumentIdSegment(withoutQuery) ?? withoutQuery;
|
|
1701
1701
|
}
|
|
1702
1702
|
function stripWrappingQuotes(value) {
|
|
1703
1703
|
let current = value;
|
|
@@ -1708,9 +1708,9 @@ function stripWrappingQuotes(value) {
|
|
|
1708
1708
|
}
|
|
1709
1709
|
}
|
|
1710
1710
|
function parseDocumentPath(value) {
|
|
1711
|
-
const
|
|
1712
|
-
if (!
|
|
1713
|
-
const parts =
|
|
1711
|
+
const path52 = value.startsWith("http://") || value.startsWith("https://") ? urlPathname(value) : value;
|
|
1712
|
+
if (!path52?.includes("/content/entries/")) return null;
|
|
1713
|
+
const parts = path52.split("/").filter(Boolean).map(decodePathPart);
|
|
1714
1714
|
const entriesIndex = parts.findIndex((part, index) => part === "content" && parts[index + 1] === "entries");
|
|
1715
1715
|
const idPart = parts[entriesIndex + 3];
|
|
1716
1716
|
if (!idPart || idPart === "new") return null;
|
|
@@ -1970,7 +1970,12 @@ function stringList(value) {
|
|
|
1970
1970
|
function parseCapabilityWorkflow(value) {
|
|
1971
1971
|
const stepsRaw = Array.isArray(value) ? value : value && typeof value === "object" && Array.isArray(value.steps) ? value.steps : [];
|
|
1972
1972
|
const steps = stepsRaw.map(parseWorkflowStep).filter((step) => step !== null);
|
|
1973
|
-
|
|
1973
|
+
if (steps.length === 0) return void 0;
|
|
1974
|
+
const startAt = value && typeof value === "object" && !Array.isArray(value) ? stringField(value.startAt) : void 0;
|
|
1975
|
+
return {
|
|
1976
|
+
steps,
|
|
1977
|
+
...startAt && isSafeSlug(startAt) ? { startAt } : {}
|
|
1978
|
+
};
|
|
1974
1979
|
}
|
|
1975
1980
|
function parseWorkflowStep(value) {
|
|
1976
1981
|
if (typeof value === "string") {
|
|
@@ -1982,6 +1987,7 @@ function parseWorkflowStep(value) {
|
|
|
1982
1987
|
const capability = stringField(raw.capability ?? raw.action);
|
|
1983
1988
|
if (!capability || !isSafeSlug(capability)) return null;
|
|
1984
1989
|
const implementation = stringField(raw.implementation);
|
|
1990
|
+
const id = stringField(raw.id);
|
|
1985
1991
|
const action = stringField(raw.action);
|
|
1986
1992
|
const evidence = stringField(raw.evidence);
|
|
1987
1993
|
const agent = stringField(raw.agent);
|
|
@@ -1989,9 +1995,12 @@ function parseWorkflowStep(value) {
|
|
|
1989
1995
|
const target = stringField(raw.target);
|
|
1990
1996
|
const targetFact = stringField(raw.targetFact ?? raw.target_fact);
|
|
1991
1997
|
const cliArgs = raw.cliArgs;
|
|
1998
|
+
const inputs = parseWorkflowInputs(raw.inputs);
|
|
1999
|
+
const next = parseWorkflowTransitions(raw.next);
|
|
1992
2000
|
const report = parseReportPublication(raw.report);
|
|
1993
2001
|
return {
|
|
1994
2002
|
capability,
|
|
2003
|
+
...id && isSafeSlug(id) ? { id } : {},
|
|
1995
2004
|
...action && isSafeSlug(action) ? { action } : {},
|
|
1996
2005
|
...implementation && isSafeSlug(implementation) ? { implementation } : {},
|
|
1997
2006
|
...evidence ? { evidence } : {},
|
|
@@ -2000,12 +2009,45 @@ function parseWorkflowStep(value) {
|
|
|
2000
2009
|
...agent && isSafeSlug(agent) ? { agent } : {},
|
|
2001
2010
|
...reason ? { reason } : {},
|
|
2002
2011
|
...cliArgs && typeof cliArgs === "object" && !Array.isArray(cliArgs) ? { cliArgs } : {},
|
|
2012
|
+
...inputs ? { inputs } : {},
|
|
2013
|
+
...next ? { next } : {},
|
|
2003
2014
|
...isPlainObject(raw.runWhen) ? { runWhen: raw.runWhen } : {},
|
|
2004
2015
|
...stringList(raw.continueOn ?? raw.continue_on).length > 0 ? { continueOn: stringList(raw.continueOn ?? raw.continue_on) } : {},
|
|
2005
2016
|
...raw.saveReport === true ? { saveReport: true } : {},
|
|
2006
2017
|
...report ? { report } : {}
|
|
2007
2018
|
};
|
|
2008
2019
|
}
|
|
2020
|
+
function parseWorkflowInputs(value) {
|
|
2021
|
+
if (!isPlainObject(value)) return void 0;
|
|
2022
|
+
const inputs = {};
|
|
2023
|
+
for (const [name, raw] of Object.entries(value)) {
|
|
2024
|
+
if (!isSafeSlug(name) || !isPlainObject(raw)) continue;
|
|
2025
|
+
const from = stringField(raw.from);
|
|
2026
|
+
if (!from) continue;
|
|
2027
|
+
inputs[name] = { from };
|
|
2028
|
+
}
|
|
2029
|
+
return Object.keys(inputs).length > 0 ? inputs : void 0;
|
|
2030
|
+
}
|
|
2031
|
+
function parseWorkflowTransitions(value) {
|
|
2032
|
+
const rawTransitions = Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
2033
|
+
const transitions = rawTransitions.map((raw) => {
|
|
2034
|
+
if (typeof raw === "string") {
|
|
2035
|
+
const to2 = raw.trim();
|
|
2036
|
+
return isSafeSlug(to2) ? { to: to2 } : null;
|
|
2037
|
+
}
|
|
2038
|
+
if (!isPlainObject(raw)) return null;
|
|
2039
|
+
const to = stringField(raw.to);
|
|
2040
|
+
if (!to || !isSafeSlug(to)) return null;
|
|
2041
|
+
const maxIterations = typeof raw.maxIterations === "number" && Number.isInteger(raw.maxIterations) && raw.maxIterations > 0 ? raw.maxIterations : void 0;
|
|
2042
|
+
return {
|
|
2043
|
+
to,
|
|
2044
|
+
...isPlainObject(raw.when) ? { when: raw.when } : {},
|
|
2045
|
+
...raw.default === true ? { default: true } : {},
|
|
2046
|
+
...maxIterations ? { maxIterations } : {}
|
|
2047
|
+
};
|
|
2048
|
+
}).filter((transition) => transition !== null);
|
|
2049
|
+
return transitions.length > 0 ? transitions : void 0;
|
|
2050
|
+
}
|
|
2009
2051
|
function parseReportPublication(value) {
|
|
2010
2052
|
if (!isPlainObject(value)) return void 0;
|
|
2011
2053
|
const type = stringField(value.type);
|
|
@@ -2317,8 +2359,8 @@ function resolveCapabilityFolder(slug, projectCapabilitiesRoot = getProjectCapab
|
|
|
2317
2359
|
}
|
|
2318
2360
|
return null;
|
|
2319
2361
|
}
|
|
2320
|
-
function getCapabilityActionInputs(action) {
|
|
2321
|
-
const resolved = resolveCapabilityAction(action);
|
|
2362
|
+
function getCapabilityActionInputs(action, projectCapabilitiesRoot = getProjectCapabilitiesRoot()) {
|
|
2363
|
+
const resolved = resolveCapabilityAction(action, projectCapabilitiesRoot);
|
|
2322
2364
|
if (!resolved) return null;
|
|
2323
2365
|
return getProfileInputs(resolved.implementation);
|
|
2324
2366
|
}
|
|
@@ -2637,20 +2679,20 @@ function readLedger(label) {
|
|
|
2637
2679
|
const raw = gh(["issue", "list", "--state", "open", "--label", label, "--limit", "5", "--json", "number,body"]);
|
|
2638
2680
|
const issues = JSON.parse(raw);
|
|
2639
2681
|
if (issues.length === 0) return { found: false, payload: null };
|
|
2640
|
-
const
|
|
2641
|
-
const body =
|
|
2682
|
+
const issue2 = issues.sort((a, b) => a.number - b.number)[0];
|
|
2683
|
+
const body = issue2?.body ?? "";
|
|
2642
2684
|
const startIdx = body.indexOf(startTag);
|
|
2643
2685
|
const endIdx = body.indexOf(endTag);
|
|
2644
2686
|
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
2645
|
-
return { found: true, issueNumber:
|
|
2687
|
+
return { found: true, issueNumber: issue2?.number, payload: null };
|
|
2646
2688
|
}
|
|
2647
2689
|
const between = body.slice(startIdx + startTag.length, endIdx);
|
|
2648
2690
|
const fenceMatch = between.match(/```json\s*([\s\S]*?)```/);
|
|
2649
|
-
if (!fenceMatch) return { found: true, issueNumber:
|
|
2691
|
+
if (!fenceMatch) return { found: true, issueNumber: issue2?.number, payload: null };
|
|
2650
2692
|
try {
|
|
2651
|
-
return { found: true, issueNumber:
|
|
2693
|
+
return { found: true, issueNumber: issue2?.number, payload: JSON.parse(fenceMatch[1]) };
|
|
2652
2694
|
} catch {
|
|
2653
|
-
return { found: true, issueNumber:
|
|
2695
|
+
return { found: true, issueNumber: issue2?.number, payload: null };
|
|
2654
2696
|
}
|
|
2655
2697
|
} catch (err) {
|
|
2656
2698
|
return { found: false, payload: { error: err instanceof Error ? err.message : String(err) } };
|
|
@@ -2698,12 +2740,7 @@ function readCheckRuns(repoSlug, ref, ignoreNames) {
|
|
|
2698
2740
|
let rawStatuses = "";
|
|
2699
2741
|
try {
|
|
2700
2742
|
rawStatuses = gh(
|
|
2701
|
-
[
|
|
2702
|
-
"api",
|
|
2703
|
-
`repos/${repoSlug}/commits/${sha}/status`,
|
|
2704
|
-
"--jq",
|
|
2705
|
-
".statuses[] | {context, state, target_url}"
|
|
2706
|
-
],
|
|
2743
|
+
["api", `repos/${repoSlug}/commits/${sha}/status`, "--jq", ".statuses[] | {context, state, target_url}"],
|
|
2707
2744
|
ghOptions
|
|
2708
2745
|
);
|
|
2709
2746
|
} catch {
|
|
@@ -2745,14 +2782,14 @@ ${marker}`
|
|
|
2745
2782
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
2746
2783
|
}
|
|
2747
2784
|
}
|
|
2748
|
-
function ensureComment(repoSlug,
|
|
2785
|
+
function ensureComment(repoSlug, issue2, key, body) {
|
|
2749
2786
|
const marker = commentMarker(key);
|
|
2750
2787
|
try {
|
|
2751
|
-
const raw = gh(["issue", "view", String(
|
|
2788
|
+
const raw = gh(["issue", "view", String(issue2), "-R", repoSlug, "--json", "comments"]);
|
|
2752
2789
|
const parsed = JSON.parse(raw);
|
|
2753
2790
|
const already = (parsed.comments ?? []).some((c) => (c.body ?? "").includes(marker));
|
|
2754
2791
|
if (already) return { posted: false };
|
|
2755
|
-
gh(["issue", "comment", String(
|
|
2792
|
+
gh(["issue", "comment", String(issue2), "-R", repoSlug, "--body-file", "-"], { input: `${body}
|
|
2756
2793
|
|
|
2757
2794
|
${marker}` });
|
|
2758
2795
|
return { posted: true };
|
|
@@ -2794,9 +2831,9 @@ function dispatchWorkflow(workflowFile, capability, issueNumber, repoSlug, ref)
|
|
|
2794
2831
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
2795
2832
|
}
|
|
2796
2833
|
}
|
|
2797
|
-
function startCapability(workflowFile, name,
|
|
2834
|
+
function startCapability(workflowFile, name, issue2, repoSlug, ref) {
|
|
2798
2835
|
const acceptsIssue = capabilityAcceptsIssue(name);
|
|
2799
|
-
const forwardedIssue = acceptsIssue === false ? void 0 :
|
|
2836
|
+
const forwardedIssue = acceptsIssue === false ? void 0 : issue2;
|
|
2800
2837
|
return dispatchWorkflow(workflowFile, name, forwardedIssue, repoSlug, ref);
|
|
2801
2838
|
}
|
|
2802
2839
|
function capabilityAcceptsIssue(capability) {
|
|
@@ -2862,8 +2899,8 @@ function capabilityToolDefinitions(opts) {
|
|
|
2862
2899
|
handler: async (args) => {
|
|
2863
2900
|
const pr = Number(args.pr);
|
|
2864
2901
|
const result = dispatchVerb(workflowFile, opts.repoSlug, verb, pr);
|
|
2865
|
-
const
|
|
2866
|
-
return { content: [{ type: "text", text }] };
|
|
2902
|
+
const text2 = result.ok ? `Dispatched \`${verb}\` on PR #${pr}. The repair runs in its own workflow_dispatch \u2014 wait for the next tick to see the new headSha.` : `Dispatch failed for \`${verb}\` on PR #${pr}: ${result.error}`;
|
|
2903
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
2867
2904
|
}
|
|
2868
2905
|
});
|
|
2869
2906
|
const syncTool = makeDispatch(
|
|
@@ -2889,8 +2926,8 @@ function capabilityToolDefinitions(opts) {
|
|
|
2889
2926
|
const pr = Number(args.pr);
|
|
2890
2927
|
const body = String(args.body ?? "");
|
|
2891
2928
|
const result = postRecommendation(opts.repoSlug, pr, opts.operatorMention, body, opts.capabilitySlug);
|
|
2892
|
-
const
|
|
2893
|
-
return { content: [{ type: "text", text }] };
|
|
2929
|
+
const text2 = result.ok ? result.posted ? `Recommendation posted on PR #${pr}.` : `Recommendation already exists on PR #${pr}; skipped.` : `Recommendation failed on PR #${pr}: ${result.error}`;
|
|
2930
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
2894
2931
|
}
|
|
2895
2932
|
};
|
|
2896
2933
|
const ledgerTool = {
|
|
@@ -2964,10 +3001,10 @@ function capabilityToolDefinitions(opts) {
|
|
|
2964
3001
|
body: z3.string().min(1).describe("Comment body markdown.")
|
|
2965
3002
|
},
|
|
2966
3003
|
handler: async (args) => {
|
|
2967
|
-
const
|
|
3004
|
+
const issue2 = Number(args.issue);
|
|
2968
3005
|
const key = String(args.key ?? "");
|
|
2969
3006
|
const body = String(args.body ?? "");
|
|
2970
|
-
const result = ensureComment(opts.repoSlug,
|
|
3007
|
+
const result = ensureComment(opts.repoSlug, issue2, key, body);
|
|
2971
3008
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2972
3009
|
}
|
|
2973
3010
|
};
|
|
@@ -2982,19 +3019,13 @@ function capabilityToolDefinitions(opts) {
|
|
|
2982
3019
|
handler: async (args) => {
|
|
2983
3020
|
const name = String(args.name ?? "");
|
|
2984
3021
|
const rawIssue = args.issue ?? args.issueNumber;
|
|
2985
|
-
const
|
|
2986
|
-
if (
|
|
3022
|
+
const issue2 = rawIssue == null ? void 0 : Number(rawIssue);
|
|
3023
|
+
if (issue2 !== void 0 && (!Number.isFinite(issue2) || issue2 <= 0)) {
|
|
2987
3024
|
return { content: [{ type: "text", text: "Start failed: `issue` must be a positive number when provided." }] };
|
|
2988
3025
|
}
|
|
2989
|
-
const result = startCapability(
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
issue,
|
|
2993
|
-
opts.repoSlug,
|
|
2994
|
-
opts.defaultBranch
|
|
2995
|
-
);
|
|
2996
|
-
const text = JSON.stringify(result);
|
|
2997
|
-
return { content: [{ type: "text", text }] };
|
|
3026
|
+
const result = startCapability(workflowFile, name, issue2, opts.repoSlug, opts.defaultBranch);
|
|
3027
|
+
const text2 = JSON.stringify(result);
|
|
3028
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
2998
3029
|
}
|
|
2999
3030
|
};
|
|
3000
3031
|
const cmsTools = dashboardCmsToolDefinitions({
|
|
@@ -3209,8 +3240,8 @@ function classifySubtype(subtype) {
|
|
|
3209
3240
|
if (lower.includes("error")) return "model_error";
|
|
3210
3241
|
return "generic_failed";
|
|
3211
3242
|
}
|
|
3212
|
-
function isClaudeLoginRequiredText(
|
|
3213
|
-
const normalized =
|
|
3243
|
+
function isClaudeLoginRequiredText(text2) {
|
|
3244
|
+
const normalized = text2.toLowerCase();
|
|
3214
3245
|
return normalized.includes("not logged in") && normalized.includes("/login");
|
|
3215
3246
|
}
|
|
3216
3247
|
function resolveTurnTimeoutMs(opts) {
|
|
@@ -3451,7 +3482,10 @@ async function runAgent(opts) {
|
|
|
3451
3482
|
errorMessage2 = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
|
|
3452
3483
|
if (typeof iterator.return === "function") {
|
|
3453
3484
|
try {
|
|
3454
|
-
await
|
|
3485
|
+
await Promise.race([
|
|
3486
|
+
iterator.return(void 0).catch(() => void 0),
|
|
3487
|
+
new Promise((resolve10) => setTimeout(resolve10, 1e4).unref())
|
|
3488
|
+
]);
|
|
3455
3489
|
} catch {
|
|
3456
3490
|
}
|
|
3457
3491
|
}
|
|
@@ -3546,9 +3580,9 @@ async function runAgent(opts) {
|
|
|
3546
3580
|
outcome = "completed";
|
|
3547
3581
|
outcomeKind = "ok";
|
|
3548
3582
|
sawTerminalSuccess = true;
|
|
3549
|
-
const
|
|
3550
|
-
if (isClaudeLoginRequiredText(
|
|
3551
|
-
if (
|
|
3583
|
+
const text2 = (typeof m.result === "string" ? m.result : "").trim();
|
|
3584
|
+
if (isClaudeLoginRequiredText(text2)) sawLoginRequired = true;
|
|
3585
|
+
if (text2) resultTexts.push(text2);
|
|
3552
3586
|
} else {
|
|
3553
3587
|
outcome = "failed";
|
|
3554
3588
|
outcomeKind = classifySubtype(m.subtype);
|
|
@@ -3963,9 +3997,9 @@ var init_agencyBoundaryEval = __esm({
|
|
|
3963
3997
|
});
|
|
3964
3998
|
|
|
3965
3999
|
// src/capabilityReport.ts
|
|
3966
|
-
function parseCapabilityReportsFromText(
|
|
4000
|
+
function parseCapabilityReportsFromText(text2) {
|
|
3967
4001
|
const reports = [];
|
|
3968
|
-
for (const match of
|
|
4002
|
+
for (const match of text2.matchAll(REPORT_LINE)) {
|
|
3969
4003
|
const raw = match[1]?.trim();
|
|
3970
4004
|
if (!raw) continue;
|
|
3971
4005
|
try {
|
|
@@ -4093,9 +4127,9 @@ var init_evidenceState = __esm({
|
|
|
4093
4127
|
});
|
|
4094
4128
|
|
|
4095
4129
|
// src/capabilityResult.ts
|
|
4096
|
-
function parseCapabilityResultsFromText(
|
|
4130
|
+
function parseCapabilityResultsFromText(text2) {
|
|
4097
4131
|
const results = [];
|
|
4098
|
-
for (const match of
|
|
4132
|
+
for (const match of text2.matchAll(RESULT_LINE)) {
|
|
4099
4133
|
const raw = match[1]?.trim();
|
|
4100
4134
|
if (!raw) continue;
|
|
4101
4135
|
try {
|
|
@@ -5416,8 +5450,8 @@ function loadProjectConventions(projectDir) {
|
|
|
5416
5450
|
return out;
|
|
5417
5451
|
}
|
|
5418
5452
|
function parseAgentResult(finalText) {
|
|
5419
|
-
const
|
|
5420
|
-
if (!
|
|
5453
|
+
const text2 = (finalText || "").trim();
|
|
5454
|
+
if (!text2)
|
|
5421
5455
|
return {
|
|
5422
5456
|
done: false,
|
|
5423
5457
|
commitMessage: "",
|
|
@@ -5431,7 +5465,7 @@ function parseAgentResult(finalText) {
|
|
|
5431
5465
|
const MARKDOWN_PREFIX = "[\\s>*_#`~\\-]*";
|
|
5432
5466
|
const FAILED_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}FAILED${MARKDOWN_PREFIX}\\s*:\\s*(.+?)\\s*$`, "s");
|
|
5433
5467
|
const DONE_RE = new RegExp(`(?:^|\\n)${MARKDOWN_PREFIX}DONE\\b`);
|
|
5434
|
-
const scanText = stripFencedCodeBlocks(
|
|
5468
|
+
const scanText = stripFencedCodeBlocks(text2);
|
|
5435
5469
|
const failedMatch = scanText.match(FAILED_RE);
|
|
5436
5470
|
if (failedMatch) {
|
|
5437
5471
|
return {
|
|
@@ -5446,32 +5480,32 @@ function parseAgentResult(finalText) {
|
|
|
5446
5480
|
};
|
|
5447
5481
|
}
|
|
5448
5482
|
const hasDoneMarker = DONE_RE.test(scanText);
|
|
5449
|
-
const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(
|
|
5450
|
-
const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(
|
|
5483
|
+
const hasCommitMsg = /^[\s>*_#`~-]*COMMIT_MSG\s*:/im.test(text2);
|
|
5484
|
+
const hasPrSummary = /^[\s>*_#`~-]*PR_SUMMARY\s*:/im.test(text2);
|
|
5451
5485
|
const markerMissing = !hasDoneMarker && !hasCommitMsg && !hasPrSummary;
|
|
5452
|
-
const commitMatch =
|
|
5486
|
+
const commitMatch = text2.match(/^[\s>*_#`~-]*COMMIT_MSG[\s>*_#`~-]*\s*:\s*(.+)$/im);
|
|
5453
5487
|
const commitMessage = commitMatch ? stripMarkdownEmphasis(commitMatch[1]) : "";
|
|
5454
5488
|
const feedbackActions = extractBlock(
|
|
5455
|
-
|
|
5489
|
+
text2,
|
|
5456
5490
|
/(?:^|\n)[ \t]*FEEDBACK_ACTIONS\s*:[ \t]*\n/i,
|
|
5457
5491
|
/(?:^|\n)[ \t]*(?:PLAN_DEVIATIONS|COMMIT_MSG|PR_SUMMARY|PRIOR_ART)\s*:/i
|
|
5458
5492
|
);
|
|
5459
5493
|
let planDeviations = extractBlock(
|
|
5460
|
-
|
|
5494
|
+
text2,
|
|
5461
5495
|
/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*\n/i,
|
|
5462
5496
|
/(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|FEEDBACK_ACTIONS|PRIOR_ART)\s*:/i
|
|
5463
5497
|
);
|
|
5464
5498
|
if (!planDeviations) {
|
|
5465
|
-
const inline =
|
|
5499
|
+
const inline = text2.match(/(?:^|\n)[ \t]*PLAN_DEVIATIONS\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
5466
5500
|
if (inline) planDeviations = inline[1].trim();
|
|
5467
5501
|
}
|
|
5468
5502
|
let priorArt = "";
|
|
5469
|
-
const priorArtInline =
|
|
5503
|
+
const priorArtInline = text2.match(/(?:^|\n)[ \t]*PRIOR_ART\s*:[ \t]*(.+?)[ \t]*(?:\n|$)/i);
|
|
5470
5504
|
if (priorArtInline) priorArt = priorArtInline[1].trim();
|
|
5471
|
-
const summaryStart =
|
|
5505
|
+
const summaryStart = text2.search(/(^|\n)[ \t]*PR_SUMMARY\s*:[ \t]*\n/i);
|
|
5472
5506
|
let prSummary = "";
|
|
5473
5507
|
if (summaryStart !== -1) {
|
|
5474
|
-
const afterMarker =
|
|
5508
|
+
const afterMarker = text2.slice(summaryStart).replace(/^[\s\S]*?PR_SUMMARY\s*:[ \t]*\n/i, "");
|
|
5475
5509
|
prSummary = afterMarker.replace(/\n\s*```\s*$/g, "").replace(/```\s*$/g, "").trim();
|
|
5476
5510
|
}
|
|
5477
5511
|
return {
|
|
@@ -5491,10 +5525,10 @@ function stripMarkdownEmphasis(s) {
|
|
|
5491
5525
|
function stripFencedCodeBlocks(s) {
|
|
5492
5526
|
return s.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "");
|
|
5493
5527
|
}
|
|
5494
|
-
function extractBlock(
|
|
5495
|
-
const startIdx =
|
|
5528
|
+
function extractBlock(text2, startMarker, endMarker) {
|
|
5529
|
+
const startIdx = text2.search(startMarker);
|
|
5496
5530
|
if (startIdx === -1) return "";
|
|
5497
|
-
const afterStart =
|
|
5531
|
+
const afterStart = text2.slice(startIdx).replace(startMarker, "");
|
|
5498
5532
|
const endIdx = afterStart.search(endMarker);
|
|
5499
5533
|
const body = endIdx === -1 ? afterStart : afterStart.slice(0, endIdx);
|
|
5500
5534
|
return body.replace(/\n\s*```\s*$/g, "").trim();
|
|
@@ -5670,9 +5704,9 @@ function collectPages(memoryAbs) {
|
|
|
5670
5704
|
}
|
|
5671
5705
|
function extractQueryTerms(ctx) {
|
|
5672
5706
|
const terms = [];
|
|
5673
|
-
const
|
|
5707
|
+
const issue2 = ctx.data.issue;
|
|
5674
5708
|
const pr = ctx.data.pr;
|
|
5675
|
-
if (
|
|
5709
|
+
if (issue2?.title) terms.push(...tokenize(issue2.title));
|
|
5676
5710
|
if (pr?.title) terms.push(...tokenize(pr.title));
|
|
5677
5711
|
return Array.from(new Set(terms)).slice(0, 20);
|
|
5678
5712
|
}
|
|
@@ -6334,7 +6368,7 @@ function locateLitellmScript() {
|
|
|
6334
6368
|
"python3",
|
|
6335
6369
|
[
|
|
6336
6370
|
"-c",
|
|
6337
|
-
"import os,sys;
|
|
6371
|
+
"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
6372
|
],
|
|
6339
6373
|
{ encoding: "utf-8", timeout: 1e4 }
|
|
6340
6374
|
).trim();
|
|
@@ -7686,10 +7720,10 @@ var init_state2 = __esm({
|
|
|
7686
7720
|
"use strict";
|
|
7687
7721
|
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
|
|
7688
7722
|
GoalStateError = class extends Error {
|
|
7689
|
-
constructor(
|
|
7690
|
-
super(`Invalid goal state at ${
|
|
7723
|
+
constructor(path52, message) {
|
|
7724
|
+
super(`Invalid goal state at ${path52}:
|
|
7691
7725
|
${message}`);
|
|
7692
|
-
this.path =
|
|
7726
|
+
this.path = path52;
|
|
7693
7727
|
this.name = "GoalStateError";
|
|
7694
7728
|
}
|
|
7695
7729
|
path;
|
|
@@ -7702,9 +7736,9 @@ import * as fs25 from "fs";
|
|
|
7702
7736
|
function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
|
|
7703
7737
|
const logs = goalRunLogs(data);
|
|
7704
7738
|
const existing = logs[goalId];
|
|
7705
|
-
const
|
|
7739
|
+
const path52 = existing?.path ?? goalRunLogPath(goalId, data);
|
|
7706
7740
|
logs[goalId] = {
|
|
7707
|
-
path:
|
|
7741
|
+
path: path52,
|
|
7708
7742
|
events: [...existing?.events ?? [], buildGoalRunLogEvent(data, goalId, event, at)]
|
|
7709
7743
|
};
|
|
7710
7744
|
}
|
|
@@ -8723,6 +8757,290 @@ var init_typeDefinitions = __esm({
|
|
|
8723
8757
|
}
|
|
8724
8758
|
});
|
|
8725
8759
|
|
|
8760
|
+
// src/workflowValidation.ts
|
|
8761
|
+
function validateWorkflow(value, options = {}) {
|
|
8762
|
+
const issues = [];
|
|
8763
|
+
const workflow = asRecord2(value);
|
|
8764
|
+
const rawSteps = Array.isArray(value) ? value : Array.isArray(workflow?.steps) ? workflow.steps : [];
|
|
8765
|
+
const maxSteps = options.maxSteps ?? 100;
|
|
8766
|
+
const maxTransitions = options.maxTransitionsPerStep ?? 20;
|
|
8767
|
+
const maxLoopIterations = options.maxLoopIterations ?? 100;
|
|
8768
|
+
if (rawSteps.length === 0) {
|
|
8769
|
+
issue(issues, "steps_required", "steps", "workflow must contain at least one step");
|
|
8770
|
+
return issues;
|
|
8771
|
+
}
|
|
8772
|
+
if (rawSteps.length > maxSteps) {
|
|
8773
|
+
issue(issues, "too_many_steps", "steps", `workflow has ${rawSteps.length} steps; maximum is ${maxSteps}`);
|
|
8774
|
+
}
|
|
8775
|
+
const graphMode = workflow?.startAt !== void 0 || rawSteps.some((entry) => {
|
|
8776
|
+
const step = asRecord2(entry);
|
|
8777
|
+
return Boolean(step && (step.id !== void 0 || step.next !== void 0 || step.inputs !== void 0));
|
|
8778
|
+
});
|
|
8779
|
+
const steps = rawSteps.map(
|
|
8780
|
+
(entry) => typeof entry === "string" ? { capability: entry } : asRecord2(entry)
|
|
8781
|
+
);
|
|
8782
|
+
const ids = [];
|
|
8783
|
+
steps.forEach((step, index) => {
|
|
8784
|
+
const base = `steps[${index}]`;
|
|
8785
|
+
if (!step) {
|
|
8786
|
+
issue(issues, "invalid_step", base, "workflow step must be a capability name or an object");
|
|
8787
|
+
return;
|
|
8788
|
+
}
|
|
8789
|
+
for (const field of Object.keys(step)) {
|
|
8790
|
+
if (!SUPPORTED_STEP_FIELDS.has(field)) {
|
|
8791
|
+
issue(issues, "unsupported_step_field", `${base}.${field}`, `workflow step field ${field} is not supported`);
|
|
8792
|
+
}
|
|
8793
|
+
}
|
|
8794
|
+
const capability = text(step.capability ?? step.action);
|
|
8795
|
+
if (!capability || !SAFE_NAME.test(capability)) {
|
|
8796
|
+
issue(issues, "invalid_capability", `${base}.capability`, "workflow step must name a valid capability");
|
|
8797
|
+
} else if (options.knownCapabilities && !options.knownCapabilities.has(capability)) {
|
|
8798
|
+
issue(
|
|
8799
|
+
issues,
|
|
8800
|
+
"unknown_capability",
|
|
8801
|
+
`${base}.capability`,
|
|
8802
|
+
`workflow step references unknown capability ${capability}`
|
|
8803
|
+
);
|
|
8804
|
+
}
|
|
8805
|
+
if (graphMode) {
|
|
8806
|
+
const id = text(step.id);
|
|
8807
|
+
if (!id || !SAFE_NAME.test(id)) {
|
|
8808
|
+
issue(issues, "invalid_step_id", `${base}.id`, "graph workflow steps must each have a valid id");
|
|
8809
|
+
} else {
|
|
8810
|
+
ids.push(id);
|
|
8811
|
+
}
|
|
8812
|
+
}
|
|
8813
|
+
validateDataMatch(step.runWhen, `${base}.runWhen`, issues);
|
|
8814
|
+
const inputs = asRecord2(step.inputs);
|
|
8815
|
+
if (step.inputs !== void 0 && !inputs) {
|
|
8816
|
+
issue(issues, "invalid_inputs", `${base}.inputs`, "workflow step inputs must be an object");
|
|
8817
|
+
}
|
|
8818
|
+
if (inputs) {
|
|
8819
|
+
for (const [name, mapping] of Object.entries(inputs)) {
|
|
8820
|
+
const inputPath = `${base}.inputs.${name}`;
|
|
8821
|
+
if (!SAFE_NAME.test(name)) issue(issues, "invalid_input_name", inputPath, `invalid input name ${name}`);
|
|
8822
|
+
const from = text(asRecord2(mapping)?.from);
|
|
8823
|
+
if (!from || !SAFE_DATA_PATH.test(from)) {
|
|
8824
|
+
issue(
|
|
8825
|
+
issues,
|
|
8826
|
+
"invalid_data_path",
|
|
8827
|
+
`${inputPath}.from`,
|
|
8828
|
+
`workflow input ${name} must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
|
|
8829
|
+
);
|
|
8830
|
+
}
|
|
8831
|
+
const declared = capability ? options.capabilityInputs?.get(capability) : void 0;
|
|
8832
|
+
if (declared && !declared.has(name)) {
|
|
8833
|
+
issue(
|
|
8834
|
+
issues,
|
|
8835
|
+
"unknown_capability_input",
|
|
8836
|
+
inputPath,
|
|
8837
|
+
`capability ${capability} does not declare input ${name}`
|
|
8838
|
+
);
|
|
8839
|
+
}
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
});
|
|
8843
|
+
if (!graphMode) return issues;
|
|
8844
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8845
|
+
ids.forEach((id, index) => {
|
|
8846
|
+
if (seen.has(id)) issue(issues, "duplicate_step_id", `steps[${index}].id`, `workflow step id ${id} is duplicated`);
|
|
8847
|
+
seen.add(id);
|
|
8848
|
+
});
|
|
8849
|
+
const startAt = text(workflow?.startAt) ?? text(steps[0]?.id);
|
|
8850
|
+
if (!startAt || !seen.has(startAt)) {
|
|
8851
|
+
issue(issues, "missing_start_step", "startAt", `workflow startAt references missing step ${startAt ?? "<none>"}`);
|
|
8852
|
+
}
|
|
8853
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
8854
|
+
steps.forEach((step, index) => {
|
|
8855
|
+
if (!step) return;
|
|
8856
|
+
const id = text(step.id);
|
|
8857
|
+
if (!id) return;
|
|
8858
|
+
const transitions = transitionList(step.next);
|
|
8859
|
+
adjacency.set(id, []);
|
|
8860
|
+
if (transitions.length > maxTransitions) {
|
|
8861
|
+
issue(
|
|
8862
|
+
issues,
|
|
8863
|
+
"too_many_transitions",
|
|
8864
|
+
`steps[${index}].next`,
|
|
8865
|
+
`workflow step ${id} has ${transitions.length} connections; maximum is ${maxTransitions}`
|
|
8866
|
+
);
|
|
8867
|
+
}
|
|
8868
|
+
const defaults = transitions.filter((transition) => asRecord2(transition)?.default === true);
|
|
8869
|
+
const conditionals = transitions.filter((transition) => asRecord2(transition)?.when !== void 0);
|
|
8870
|
+
const unconditional = transitions.filter((transition) => {
|
|
8871
|
+
const raw = asRecord2(transition);
|
|
8872
|
+
return typeof transition === "string" || Boolean(raw && raw.when === void 0 && raw.default !== true && raw.maxIterations === void 0);
|
|
8873
|
+
});
|
|
8874
|
+
if (defaults.length > 1) {
|
|
8875
|
+
issue(
|
|
8876
|
+
issues,
|
|
8877
|
+
"multiple_default_transitions",
|
|
8878
|
+
`steps[${index}].next`,
|
|
8879
|
+
`workflow step ${id} has more than one default connection`
|
|
8880
|
+
);
|
|
8881
|
+
}
|
|
8882
|
+
if (conditionals.length > 0 && defaults.length !== 1) {
|
|
8883
|
+
issue(
|
|
8884
|
+
issues,
|
|
8885
|
+
"missing_default_transition",
|
|
8886
|
+
`steps[${index}].next`,
|
|
8887
|
+
`workflow step ${id} has conditions and needs one default connection`
|
|
8888
|
+
);
|
|
8889
|
+
}
|
|
8890
|
+
if (unconditional.length > 1 || unconditional.length > 0 && transitions.length > 1) {
|
|
8891
|
+
issue(
|
|
8892
|
+
issues,
|
|
8893
|
+
"ambiguous_transition",
|
|
8894
|
+
`steps[${index}].next`,
|
|
8895
|
+
`workflow step ${id} mixes an unconditional connection with other connections`
|
|
8896
|
+
);
|
|
8897
|
+
}
|
|
8898
|
+
transitions.forEach((transition, transitionIndex) => {
|
|
8899
|
+
const raw = typeof transition === "string" ? { to: transition } : asRecord2(transition);
|
|
8900
|
+
const base = `steps[${index}].next[${transitionIndex}]`;
|
|
8901
|
+
if (!raw) {
|
|
8902
|
+
issue(issues, "invalid_transition", base, "workflow connection must be a step id or an object");
|
|
8903
|
+
return;
|
|
8904
|
+
}
|
|
8905
|
+
for (const field of Object.keys(raw)) {
|
|
8906
|
+
if (!SUPPORTED_TRANSITION_FIELDS.has(field)) {
|
|
8907
|
+
issue(
|
|
8908
|
+
issues,
|
|
8909
|
+
"unsupported_transition_field",
|
|
8910
|
+
`${base}.${field}`,
|
|
8911
|
+
`workflow connection field ${field} is not supported`
|
|
8912
|
+
);
|
|
8913
|
+
}
|
|
8914
|
+
}
|
|
8915
|
+
const target = text(raw.to);
|
|
8916
|
+
if (!target || !SAFE_NAME.test(target)) {
|
|
8917
|
+
issue(issues, "invalid_transition_target", `${base}.to`, "workflow connection must name a valid target step");
|
|
8918
|
+
return;
|
|
8919
|
+
}
|
|
8920
|
+
if (!seen.has(target)) {
|
|
8921
|
+
issue(
|
|
8922
|
+
issues,
|
|
8923
|
+
"missing_transition_target",
|
|
8924
|
+
`${base}.to`,
|
|
8925
|
+
`workflow step ${id} connects to missing step ${target}`
|
|
8926
|
+
);
|
|
8927
|
+
} else {
|
|
8928
|
+
adjacency.get(id)?.push(target);
|
|
8929
|
+
}
|
|
8930
|
+
if (raw.default === true && raw.when !== void 0) {
|
|
8931
|
+
issue(issues, "conflicting_transition", base, "workflow connection cannot be both conditional and default");
|
|
8932
|
+
}
|
|
8933
|
+
if (raw.when !== void 0) validateDataMatch(raw.when, `${base}.when`, issues);
|
|
8934
|
+
const targetIndex = ids.indexOf(target ?? "");
|
|
8935
|
+
const iterations = raw.maxIterations;
|
|
8936
|
+
if (targetIndex >= 0 && targetIndex <= index) {
|
|
8937
|
+
if (!Number.isInteger(iterations) || Number(iterations) < 1) {
|
|
8938
|
+
issue(
|
|
8939
|
+
issues,
|
|
8940
|
+
"unbounded_loop",
|
|
8941
|
+
`${base}.maxIterations`,
|
|
8942
|
+
`workflow loop ${id}->${target} must set maxIterations`
|
|
8943
|
+
);
|
|
8944
|
+
} else if (Number(iterations) > maxLoopIterations) {
|
|
8945
|
+
issue(
|
|
8946
|
+
issues,
|
|
8947
|
+
"loop_limit_too_high",
|
|
8948
|
+
`${base}.maxIterations`,
|
|
8949
|
+
`workflow loop ${id}->${target} exceeds maximum ${maxLoopIterations}`
|
|
8950
|
+
);
|
|
8951
|
+
}
|
|
8952
|
+
} else if (iterations !== void 0 && (!Number.isInteger(iterations) || Number(iterations) < 1)) {
|
|
8953
|
+
issue(issues, "invalid_loop_limit", `${base}.maxIterations`, "maxIterations must be a positive integer");
|
|
8954
|
+
}
|
|
8955
|
+
});
|
|
8956
|
+
});
|
|
8957
|
+
if (startAt && seen.has(startAt)) {
|
|
8958
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
8959
|
+
const pending = [startAt];
|
|
8960
|
+
while (pending.length > 0) {
|
|
8961
|
+
const id = pending.pop();
|
|
8962
|
+
if (reachable.has(id)) continue;
|
|
8963
|
+
reachable.add(id);
|
|
8964
|
+
pending.push(...adjacency.get(id) ?? []);
|
|
8965
|
+
}
|
|
8966
|
+
ids.forEach((id, index) => {
|
|
8967
|
+
if (!reachable.has(id)) issue(issues, "unreachable_step", `steps[${index}]`, `workflow step ${id} is unreachable`);
|
|
8968
|
+
});
|
|
8969
|
+
if (![...reachable].some((id) => (adjacency.get(id) ?? []).length === 0)) {
|
|
8970
|
+
issue(issues, "missing_terminal_step", "steps", "workflow has no reachable final step");
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
8973
|
+
return issues;
|
|
8974
|
+
}
|
|
8975
|
+
function formatWorkflowValidationIssues(issues) {
|
|
8976
|
+
return issues.map((entry) => `${entry.path}: ${entry.message}`);
|
|
8977
|
+
}
|
|
8978
|
+
function validateDataMatch(value, path52, issues) {
|
|
8979
|
+
if (value === void 0) return;
|
|
8980
|
+
const match = asRecord2(value);
|
|
8981
|
+
if (!match || Object.keys(match).length === 0) {
|
|
8982
|
+
issue(issues, "invalid_condition", path52, "workflow condition must contain at least one match");
|
|
8983
|
+
return;
|
|
8984
|
+
}
|
|
8985
|
+
for (const [field, expected] of Object.entries(match)) {
|
|
8986
|
+
if (!SAFE_DATA_PATH.test(field)) {
|
|
8987
|
+
issue(
|
|
8988
|
+
issues,
|
|
8989
|
+
"invalid_data_path",
|
|
8990
|
+
`${path52}.${field}`,
|
|
8991
|
+
`workflow condition must read from facts, evidence, artifacts, result, workflow, or lastOutcome`
|
|
8992
|
+
);
|
|
8993
|
+
}
|
|
8994
|
+
if (!isComparable(expected)) {
|
|
8995
|
+
issue(issues, "invalid_condition_value", `${path52}.${field}`, "workflow condition value must be a JSON scalar");
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
}
|
|
8999
|
+
function transitionList(value) {
|
|
9000
|
+
if (value === void 0) return [];
|
|
9001
|
+
return Array.isArray(value) ? value : [value];
|
|
9002
|
+
}
|
|
9003
|
+
function asRecord2(value) {
|
|
9004
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
9005
|
+
}
|
|
9006
|
+
function text(value) {
|
|
9007
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
9008
|
+
}
|
|
9009
|
+
function isComparable(value) {
|
|
9010
|
+
if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
|
|
9011
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => isComparable(item) && !Array.isArray(item));
|
|
9012
|
+
}
|
|
9013
|
+
function issue(issues, code, path52, message) {
|
|
9014
|
+
issues.push({ code, path: path52, message });
|
|
9015
|
+
}
|
|
9016
|
+
var SAFE_NAME, SAFE_DATA_PATH, SUPPORTED_STEP_FIELDS, SUPPORTED_TRANSITION_FIELDS;
|
|
9017
|
+
var init_workflowValidation = __esm({
|
|
9018
|
+
"src/workflowValidation.ts"() {
|
|
9019
|
+
"use strict";
|
|
9020
|
+
SAFE_NAME = /^[a-z][a-z0-9-]*$/;
|
|
9021
|
+
SAFE_DATA_PATH = /^(facts|evidence|artifacts|result|workflow|lastOutcome)(?:\.[A-Za-z_][A-Za-z0-9_-]*)+$/;
|
|
9022
|
+
SUPPORTED_STEP_FIELDS = /* @__PURE__ */ new Set([
|
|
9023
|
+
"id",
|
|
9024
|
+
"capability",
|
|
9025
|
+
"action",
|
|
9026
|
+
"implementation",
|
|
9027
|
+
"evidence",
|
|
9028
|
+
"target",
|
|
9029
|
+
"targetFact",
|
|
9030
|
+
"reason",
|
|
9031
|
+
"agent",
|
|
9032
|
+
"cliArgs",
|
|
9033
|
+
"inputs",
|
|
9034
|
+
"next",
|
|
9035
|
+
"runWhen",
|
|
9036
|
+
"continueOn",
|
|
9037
|
+
"saveReport",
|
|
9038
|
+
"report"
|
|
9039
|
+
]);
|
|
9040
|
+
SUPPORTED_TRANSITION_FIELDS = /* @__PURE__ */ new Set(["to", "when", "default", "maxIterations"]);
|
|
9041
|
+
}
|
|
9042
|
+
});
|
|
9043
|
+
|
|
8726
9044
|
// src/workflowDefinitions.ts
|
|
8727
9045
|
import * as fs28 from "fs";
|
|
8728
9046
|
import * as path26 from "path";
|
|
@@ -8739,7 +9057,18 @@ function normalizeWorkflowDefinition(value) {
|
|
|
8739
9057
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
8740
9058
|
const raw = value;
|
|
8741
9059
|
const name = typeof raw.name === "string" ? raw.name.trim() : "";
|
|
8742
|
-
const
|
|
9060
|
+
const hasGraphConnections = Array.isArray(raw.steps) && raw.steps.some(
|
|
9061
|
+
(step) => step && typeof step === "object" && !Array.isArray(step) && (step.next !== void 0 || step.inputs !== void 0)
|
|
9062
|
+
);
|
|
9063
|
+
if (hasGraphConnections) {
|
|
9064
|
+
if (validateWorkflow({ steps: raw.steps, ...raw.startAt !== void 0 ? { startAt: raw.startAt } : {} }).length > 0) {
|
|
9065
|
+
return null;
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
const workflow = parseCapabilityWorkflow({
|
|
9069
|
+
steps: raw.steps,
|
|
9070
|
+
startAt: raw.startAt
|
|
9071
|
+
});
|
|
8743
9072
|
const steps = workflow?.steps;
|
|
8744
9073
|
const capabilities = steps ? steps.map((step) => step.capability) : normalizeWorkflowCapabilities(raw.capabilities);
|
|
8745
9074
|
if (!name || capabilities.length === 0) return null;
|
|
@@ -8749,6 +9078,7 @@ function normalizeWorkflowDefinition(value) {
|
|
|
8749
9078
|
capabilities,
|
|
8750
9079
|
...raw.runWithoutApproval === true ? { runWithoutApproval: true } : {},
|
|
8751
9080
|
...steps ? { steps } : {},
|
|
9081
|
+
...workflow?.startAt ? { startAt: workflow.startAt } : {},
|
|
8752
9082
|
...typeof raw.createdAt === "string" ? { createdAt: raw.createdAt } : {},
|
|
8753
9083
|
...typeof raw.updatedAt === "string" ? { updatedAt: raw.updatedAt } : {}
|
|
8754
9084
|
};
|
|
@@ -8790,7 +9120,8 @@ function normalizeWorkflowCapabilities(value) {
|
|
|
8790
9120
|
}
|
|
8791
9121
|
function workflowDefinitionToConfig(workflow) {
|
|
8792
9122
|
return {
|
|
8793
|
-
steps: workflow.steps ?? workflow.capabilities.map((capability) => ({ capability }))
|
|
9123
|
+
steps: workflow.steps ?? workflow.capabilities.map((capability) => ({ capability })),
|
|
9124
|
+
...workflow.startAt ? { startAt: workflow.startAt } : {}
|
|
8794
9125
|
};
|
|
8795
9126
|
}
|
|
8796
9127
|
function readCompanyStoreWorkflowDefinition(id) {
|
|
@@ -8814,6 +9145,7 @@ var init_workflowDefinitions = __esm({
|
|
|
8814
9145
|
init_capabilityFolders();
|
|
8815
9146
|
init_companyStore();
|
|
8816
9147
|
init_stateRepo();
|
|
9148
|
+
init_workflowValidation();
|
|
8817
9149
|
WORKFLOW_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,79}$/;
|
|
8818
9150
|
CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/;
|
|
8819
9151
|
}
|
|
@@ -9636,7 +9968,7 @@ function readSimpleGoalTaskSummary(goalId, cwd) {
|
|
|
9636
9968
|
);
|
|
9637
9969
|
const issues = JSON.parse(raw);
|
|
9638
9970
|
const total = issues.length;
|
|
9639
|
-
const open = issues.filter((
|
|
9971
|
+
const open = issues.filter((issue2) => String(issue2.state ?? "").toLowerCase() === "open").length;
|
|
9640
9972
|
return { total, open };
|
|
9641
9973
|
}
|
|
9642
9974
|
function previousDispatchWasTargetInstance(managed, previousScheduleState) {
|
|
@@ -9685,7 +10017,7 @@ function findExistingGoalIssue(goalId, cwd) {
|
|
|
9685
10017
|
const marker = goalIssueMarker(goalId);
|
|
9686
10018
|
const raw = gh(["issue", "list", "--state", "all", "--limit", "100", "--json", "number,body"], { cwd });
|
|
9687
10019
|
const issues = JSON.parse(raw);
|
|
9688
|
-
const match = issues.find((
|
|
10020
|
+
const match = issues.find((issue2) => typeof issue2.number === "number" && issue2.body?.includes(marker));
|
|
9689
10021
|
return match?.number ?? null;
|
|
9690
10022
|
}
|
|
9691
10023
|
function createGoalIssue(goal, goalId, cwd) {
|
|
@@ -10534,13 +10866,13 @@ function ensureNeedsFixIssue(ctx, goalId, state, evidence, evidenceKey) {
|
|
|
10534
10866
|
const evidenceState = parseGoalEvidenceState(state.extra.evidenceState);
|
|
10535
10867
|
const progress = evidenceState[evidenceKey];
|
|
10536
10868
|
if (progress?.issue) return state;
|
|
10537
|
-
const
|
|
10869
|
+
const issue2 = findExistingNeedsFixIssue(goalId, evidenceKey, ctx.cwd) ?? createNeedsFixIssue(goalId, evidenceKey, evidence, ctx.cwd);
|
|
10538
10870
|
const nextEvidenceState = mergeGoalEvidenceProgress(evidenceState, evidenceKey, {
|
|
10539
10871
|
resultClass: "needsFix",
|
|
10540
10872
|
attempts: progress?.attempts ?? 1,
|
|
10541
10873
|
reason: evidence.summary,
|
|
10542
|
-
nextAction: `fix issue #${
|
|
10543
|
-
issue,
|
|
10874
|
+
nextAction: `fix issue #${issue2}`,
|
|
10875
|
+
issue: issue2,
|
|
10544
10876
|
updatedAt: nowIso()
|
|
10545
10877
|
});
|
|
10546
10878
|
return {
|
|
@@ -10549,7 +10881,7 @@ function ensureNeedsFixIssue(ctx, goalId, state, evidence, evidenceKey) {
|
|
|
10549
10881
|
...state.extra,
|
|
10550
10882
|
evidenceState: nextEvidenceState,
|
|
10551
10883
|
reason: evidence.summary,
|
|
10552
|
-
nextAction: `fix issue #${
|
|
10884
|
+
nextAction: `fix issue #${issue2}`
|
|
10553
10885
|
}
|
|
10554
10886
|
};
|
|
10555
10887
|
}
|
|
@@ -10567,7 +10899,7 @@ function findExistingNeedsFixIssue(goalId, evidence, cwd) {
|
|
|
10567
10899
|
const marker = needsFixIssueMarker(goalId, evidence);
|
|
10568
10900
|
const raw = gh(["issue", "list", "--state", "all", "--limit", "100", "--json", "number,body"], { cwd });
|
|
10569
10901
|
const issues = JSON.parse(raw);
|
|
10570
|
-
const match = issues.find((
|
|
10902
|
+
const match = issues.find((issue2) => typeof issue2.number === "number" && issue2.body?.includes(marker));
|
|
10571
10903
|
return match?.number ?? null;
|
|
10572
10904
|
}
|
|
10573
10905
|
function createNeedsFixIssue(goalId, evidence, result, cwd) {
|
|
@@ -11005,8 +11337,8 @@ var init_classifyByLabel = __esm({
|
|
|
11005
11337
|
"use strict";
|
|
11006
11338
|
VALID_CLASSES = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
|
|
11007
11339
|
classifyByLabel = async (ctx) => {
|
|
11008
|
-
const
|
|
11009
|
-
const labels =
|
|
11340
|
+
const issue2 = ctx.data.issue;
|
|
11341
|
+
const labels = issue2?.labels;
|
|
11010
11342
|
if (!labels || labels.length === 0) return;
|
|
11011
11343
|
const cfgMap = ctx.config.classify?.labelMap;
|
|
11012
11344
|
const map = cfgMap ?? defaultLabelMap();
|
|
@@ -11054,6 +11386,13 @@ var init_commitAndPush = __esm({
|
|
|
11054
11386
|
if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
|
|
11055
11387
|
if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
|
|
11056
11388
|
if (replay.salvagedFromMissingMarker) ctx.data.salvagedFromMissingMarker = true;
|
|
11389
|
+
if (typeof replay.commitCrash === "string") {
|
|
11390
|
+
ctx.data.commitCrash = replay.commitCrash;
|
|
11391
|
+
if (typeof replay.exitCode === "number" && (ctx.output.exitCode === void 0 || ctx.output.exitCode === 0)) {
|
|
11392
|
+
ctx.output.exitCode = replay.exitCode;
|
|
11393
|
+
}
|
|
11394
|
+
if (!ctx.output.reason && replay.reason) ctx.output.reason = replay.reason;
|
|
11395
|
+
}
|
|
11057
11396
|
ctx.data.commitIdempotencyReplay = true;
|
|
11058
11397
|
process.stderr.write(`[kody commitAndPush] idempotency replay (sentinel ${sentinel})
|
|
11059
11398
|
`);
|
|
@@ -11111,6 +11450,9 @@ var init_commitAndPush = __esm({
|
|
|
11111
11450
|
changedFiles: ctx.data.changedFiles,
|
|
11112
11451
|
hasCommitsAhead: ctx.data.hasCommitsAhead,
|
|
11113
11452
|
salvagedFromMissingMarker: ctx.data.salvagedFromMissingMarker === true,
|
|
11453
|
+
commitCrash: typeof ctx.data.commitCrash === "string" ? ctx.data.commitCrash : void 0,
|
|
11454
|
+
exitCode: ctx.output.exitCode,
|
|
11455
|
+
reason: ctx.output.reason,
|
|
11114
11456
|
writtenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11115
11457
|
},
|
|
11116
11458
|
null,
|
|
@@ -11309,7 +11651,10 @@ var init_composePrompt = __esm({
|
|
|
11309
11651
|
"issue.body",
|
|
11310
11652
|
"issue.commentsFormatted",
|
|
11311
11653
|
"pr.body",
|
|
11312
|
-
"pr.commentsFormatted"
|
|
11654
|
+
"pr.commentsFormatted",
|
|
11655
|
+
// Prior-art bundles PR diffs and review/issue comments — authorable by any
|
|
11656
|
+
// external GitHub user, so exactly as attacker-controllable as issue bodies.
|
|
11657
|
+
"priorArt"
|
|
11313
11658
|
]);
|
|
11314
11659
|
FENCE_END = "----- END UNTRUSTED INPUT -----";
|
|
11315
11660
|
composePrompt = async (ctx, profile) => {
|
|
@@ -11519,19 +11864,19 @@ function buildGoalName(scope, verdict) {
|
|
|
11519
11864
|
const verdictTag = verdict === "UNKNOWN" ? "REPORT" : verdict;
|
|
11520
11865
|
return `QA: ${focus} \u2014 ${verdictTag} \u2014 ${todayIso()}`.slice(0, 240);
|
|
11521
11866
|
}
|
|
11522
|
-
function splitReport(
|
|
11523
|
-
const open =
|
|
11867
|
+
function splitReport(text2) {
|
|
11868
|
+
const open = text2.indexOf(REPORT_JSON_OPEN);
|
|
11524
11869
|
if (open < 0) {
|
|
11525
|
-
const fallback = parseFallbackFindingsJson(
|
|
11870
|
+
const fallback = parseFallbackFindingsJson(text2);
|
|
11526
11871
|
if (fallback) return fallback;
|
|
11527
|
-
return { markdown:
|
|
11872
|
+
return { markdown: text2.trim(), data: null, jsonError: "no JSON block marker" };
|
|
11528
11873
|
}
|
|
11529
|
-
const closeRel =
|
|
11874
|
+
const closeRel = text2.slice(open + REPORT_JSON_OPEN.length).indexOf(REPORT_JSON_CLOSE);
|
|
11530
11875
|
if (closeRel < 0) {
|
|
11531
|
-
return { markdown:
|
|
11876
|
+
return { markdown: text2.slice(0, open).trim(), data: null, jsonError: "JSON block not terminated" };
|
|
11532
11877
|
}
|
|
11533
11878
|
const closeAbs = open + REPORT_JSON_OPEN.length + closeRel;
|
|
11534
|
-
const rawJson =
|
|
11879
|
+
const rawJson = text2.slice(open + REPORT_JSON_OPEN.length, closeAbs).trim();
|
|
11535
11880
|
const fenced = rawJson.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
|
|
11536
11881
|
const cleanJson = fenced ? fenced[1].trim() : rawJson;
|
|
11537
11882
|
let parsed = null;
|
|
@@ -11546,11 +11891,11 @@ function splitReport(text) {
|
|
|
11546
11891
|
} catch (err) {
|
|
11547
11892
|
parseError = err instanceof Error ? err.message : String(err);
|
|
11548
11893
|
}
|
|
11549
|
-
const markdown =
|
|
11894
|
+
const markdown = text2.slice(0, open).trim();
|
|
11550
11895
|
return { markdown, data: parsed, jsonError: parseError };
|
|
11551
11896
|
}
|
|
11552
|
-
function parseFallbackFindingsJson(
|
|
11553
|
-
const fences = [...
|
|
11897
|
+
function parseFallbackFindingsJson(text2) {
|
|
11898
|
+
const fences = [...text2.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/gi)];
|
|
11554
11899
|
for (let i = fences.length - 1; i >= 0; i--) {
|
|
11555
11900
|
const match = fences[i];
|
|
11556
11901
|
const raw = match[1]?.trim();
|
|
@@ -11559,18 +11904,18 @@ function parseFallbackFindingsJson(text) {
|
|
|
11559
11904
|
const parsed = JSON.parse(raw);
|
|
11560
11905
|
if (!parsed || !Array.isArray(parsed.findings)) {
|
|
11561
11906
|
return {
|
|
11562
|
-
markdown: removeFence(
|
|
11907
|
+
markdown: removeFence(text2, match).trim(),
|
|
11563
11908
|
data: null,
|
|
11564
11909
|
jsonError: "fallback JSON missing 'findings' array"
|
|
11565
11910
|
};
|
|
11566
11911
|
}
|
|
11567
11912
|
return {
|
|
11568
|
-
markdown: removeFence(
|
|
11913
|
+
markdown: removeFence(text2, match).trim(),
|
|
11569
11914
|
data: { findings: parsed.findings.map((f, idx) => normalizeFallbackFinding(f, idx)) }
|
|
11570
11915
|
};
|
|
11571
11916
|
} catch (err) {
|
|
11572
11917
|
return {
|
|
11573
|
-
markdown: removeFence(
|
|
11918
|
+
markdown: removeFence(text2, match).trim(),
|
|
11574
11919
|
data: null,
|
|
11575
11920
|
jsonError: err instanceof Error ? err.message : String(err)
|
|
11576
11921
|
};
|
|
@@ -11578,10 +11923,10 @@ function parseFallbackFindingsJson(text) {
|
|
|
11578
11923
|
}
|
|
11579
11924
|
return null;
|
|
11580
11925
|
}
|
|
11581
|
-
function removeFence(
|
|
11926
|
+
function removeFence(text2, match) {
|
|
11582
11927
|
const start = match.index ?? -1;
|
|
11583
|
-
if (start < 0) return
|
|
11584
|
-
return `${
|
|
11928
|
+
if (start < 0) return text2;
|
|
11929
|
+
return `${text2.slice(0, start)}${text2.slice(start + match[0].length)}`;
|
|
11585
11930
|
}
|
|
11586
11931
|
function normalizeFallbackFinding(raw, idx) {
|
|
11587
11932
|
const finding = raw && typeof raw === "object" ? raw : {};
|
|
@@ -11638,9 +11983,9 @@ function loadManifest(cwd) {
|
|
|
11638
11983
|
return { number: null, manifest: { version: 1, goals: [] } };
|
|
11639
11984
|
}
|
|
11640
11985
|
if (arr.length === 0) return { number: null, manifest: { version: 1, goals: [] } };
|
|
11641
|
-
const
|
|
11642
|
-
const manifest = parseManifestBody(
|
|
11643
|
-
return { number:
|
|
11986
|
+
const issue2 = arr[0];
|
|
11987
|
+
const manifest = parseManifestBody(issue2.body);
|
|
11988
|
+
return { number: issue2.number, manifest };
|
|
11644
11989
|
}
|
|
11645
11990
|
function parseManifestBody(body) {
|
|
11646
11991
|
if (!body) return { version: 1, goals: [] };
|
|
@@ -11866,8 +12211,8 @@ ${markdown}`, ctx.cwd);
|
|
|
11866
12211
|
const failed = [];
|
|
11867
12212
|
for (const f of findings) {
|
|
11868
12213
|
try {
|
|
11869
|
-
const
|
|
11870
|
-
opened.push({ ...
|
|
12214
|
+
const issue2 = createTaskIssue(f, goalId, manifestIssueNumber, ctx.cwd);
|
|
12215
|
+
opened.push({ ...issue2, severity: f.severity });
|
|
11871
12216
|
} catch (err) {
|
|
11872
12217
|
const reason = err instanceof Error ? err.message : String(err);
|
|
11873
12218
|
failed.push({ title: f.title, reason });
|
|
@@ -12009,8 +12354,8 @@ var init_deriveQaScopeFromIssue = __esm({
|
|
|
12009
12354
|
if (!Number.isFinite(issueNumber) || issueNumber <= 0) return;
|
|
12010
12355
|
let title = "";
|
|
12011
12356
|
try {
|
|
12012
|
-
const
|
|
12013
|
-
title = (
|
|
12357
|
+
const issue2 = getIssue(issueNumber, ctx.cwd);
|
|
12358
|
+
title = (issue2.title ?? "").trim();
|
|
12014
12359
|
} catch (err) {
|
|
12015
12360
|
process.stderr.write(
|
|
12016
12361
|
`[kody] deriveQaScopeFromIssue: could not read #${issueNumber}: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -12615,28 +12960,28 @@ var init_dispatchCapabilityTicks = __esm({
|
|
|
12615
12960
|
process.stdout.write(`[jobs] ticking ${issues.length} issue(s) via ${targetImplementation}
|
|
12616
12961
|
`);
|
|
12617
12962
|
const results = [];
|
|
12618
|
-
for (const
|
|
12619
|
-
process.stdout.write(`[jobs] \u2192 tick #${
|
|
12963
|
+
for (const issue2 of issues) {
|
|
12964
|
+
process.stdout.write(`[jobs] \u2192 tick #${issue2.number}: ${issue2.title}
|
|
12620
12965
|
`);
|
|
12621
12966
|
try {
|
|
12622
12967
|
const out = await runJob(
|
|
12623
12968
|
mintScheduledJob({
|
|
12624
12969
|
capability: targetImplementation,
|
|
12625
12970
|
implementation: targetImplementation,
|
|
12626
|
-
cliArgs: { [issueArg]:
|
|
12971
|
+
cliArgs: { [issueArg]: issue2.number }
|
|
12627
12972
|
}),
|
|
12628
12973
|
{ cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false }
|
|
12629
12974
|
);
|
|
12630
|
-
results.push({ issue:
|
|
12975
|
+
results.push({ issue: issue2.number, exitCode: out.exitCode, reason: out.reason });
|
|
12631
12976
|
if (out.exitCode !== 0) {
|
|
12632
|
-
process.stderr.write(`[jobs] tick #${
|
|
12977
|
+
process.stderr.write(`[jobs] tick #${issue2.number} failed (exit ${out.exitCode}): ${out.reason ?? ""}
|
|
12633
12978
|
`);
|
|
12634
12979
|
}
|
|
12635
12980
|
} catch (err) {
|
|
12636
12981
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12637
|
-
process.stderr.write(`[jobs] tick #${
|
|
12982
|
+
process.stderr.write(`[jobs] tick #${issue2.number} crashed: ${msg}
|
|
12638
12983
|
`);
|
|
12639
|
-
results.push({ issue:
|
|
12984
|
+
results.push({ issue: issue2.number, exitCode: 99, reason: msg });
|
|
12640
12985
|
}
|
|
12641
12986
|
}
|
|
12642
12987
|
ctx.data.jobTickResults = results;
|
|
@@ -12832,23 +13177,34 @@ function firstLine(s) {
|
|
|
12832
13177
|
const head = nl === -1 ? trimmed : trimmed.slice(0, nl);
|
|
12833
13178
|
return head.length > 200 ? `${head.slice(0, 197)}\u2026` : head;
|
|
12834
13179
|
}
|
|
12835
|
-
function
|
|
13180
|
+
function lookupExistingPr(branch, cwd) {
|
|
12836
13181
|
try {
|
|
12837
13182
|
const output = gh(
|
|
12838
|
-
["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,body", "--limit", "1"],
|
|
13183
|
+
["pr", "list", "--head", branch, "--state", "open", "--json", "number,url,body,title,isDraft", "--limit", "1"],
|
|
12839
13184
|
{ cwd, preferRepoToken: true }
|
|
12840
13185
|
);
|
|
12841
13186
|
const arr = JSON.parse(output);
|
|
12842
13187
|
const first = Array.isArray(arr) ? arr[0] : null;
|
|
12843
13188
|
if (first && typeof first.number === "number" && typeof first.url === "string") {
|
|
12844
|
-
|
|
12845
|
-
|
|
13189
|
+
return {
|
|
13190
|
+
pr: {
|
|
13191
|
+
number: first.number,
|
|
13192
|
+
url: first.url,
|
|
13193
|
+
body: typeof first.body === "string" ? first.body : "",
|
|
13194
|
+
title: typeof first.title === "string" ? first.title : "",
|
|
13195
|
+
isDraft: first.isDraft === true
|
|
13196
|
+
},
|
|
13197
|
+
error: null
|
|
13198
|
+
};
|
|
12846
13199
|
}
|
|
12847
|
-
return null;
|
|
12848
|
-
} catch {
|
|
12849
|
-
return null;
|
|
13200
|
+
return { pr: null, error: null };
|
|
13201
|
+
} catch (err) {
|
|
13202
|
+
return { pr: null, error: err instanceof Error ? err.message : String(err) };
|
|
12850
13203
|
}
|
|
12851
13204
|
}
|
|
13205
|
+
function findExistingPr(branch, cwd) {
|
|
13206
|
+
return lookupExistingPr(branch, cwd).pr;
|
|
13207
|
+
}
|
|
12852
13208
|
function recoverSourceIssueNumber(existingBody, branch, prNumber) {
|
|
12853
13209
|
const bodyMatch = existingBody.match(/\bCloses #(\d+)\b/i);
|
|
12854
13210
|
if (bodyMatch) {
|
|
@@ -12875,16 +13231,42 @@ function git2(args, cwd) {
|
|
|
12875
13231
|
stdio: ["pipe", "pipe", "pipe"]
|
|
12876
13232
|
}).trim();
|
|
12877
13233
|
}
|
|
12878
|
-
function updateExistingPr(existing, body, draft, cwd) {
|
|
13234
|
+
function updateExistingPr(existing, body, draft, cwd, preserveBody) {
|
|
12879
13235
|
const stripped = existing.url.replace(/^https:\/\/github\.com\//, "");
|
|
12880
13236
|
const [owner, repo] = stripped.split("/");
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
13237
|
+
if (!preserveBody) {
|
|
13238
|
+
try {
|
|
13239
|
+
gh(["api", "--method", "PATCH", `repos/${owner}/${repo}/pulls/${existing.number}`, "-f", `body=${body}`], {
|
|
13240
|
+
cwd,
|
|
13241
|
+
preferRepoToken: true
|
|
13242
|
+
});
|
|
13243
|
+
} catch (err) {
|
|
13244
|
+
throw new Error(`gh api PATCH #${existing.number} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
13245
|
+
}
|
|
13246
|
+
}
|
|
13247
|
+
if (existing.isDraft === true && !draft) {
|
|
13248
|
+
try {
|
|
13249
|
+
gh(["pr", "ready", String(existing.number)], { cwd, preferRepoToken: true });
|
|
13250
|
+
const promotedTitle = existing.title?.replace(/^\[WIP\]\s*/, "");
|
|
13251
|
+
if (promotedTitle && promotedTitle !== existing.title) {
|
|
13252
|
+
gh(
|
|
13253
|
+
[
|
|
13254
|
+
"api",
|
|
13255
|
+
"--method",
|
|
13256
|
+
"PATCH",
|
|
13257
|
+
`repos/${owner}/${repo}/pulls/${existing.number}`,
|
|
13258
|
+
"-f",
|
|
13259
|
+
`title=${promotedTitle}`
|
|
13260
|
+
],
|
|
13261
|
+
{ cwd, preferRepoToken: true }
|
|
13262
|
+
);
|
|
13263
|
+
}
|
|
13264
|
+
} catch (err) {
|
|
13265
|
+
process.stderr.write(
|
|
13266
|
+
`[kody ensurePr] draft\u2192ready promotion of #${existing.number} failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
13267
|
+
`
|
|
13268
|
+
);
|
|
13269
|
+
}
|
|
12888
13270
|
}
|
|
12889
13271
|
return { url: existing.url, number: existing.number, draft, action: "updated" };
|
|
12890
13272
|
}
|
|
@@ -12897,8 +13279,13 @@ function createPr(branch, base, title, body, draft, cwd) {
|
|
|
12897
13279
|
return { url, number, draft, action: "created" };
|
|
12898
13280
|
}
|
|
12899
13281
|
function recoverFromExistingPr(branch, base, title, body, draft, cwd) {
|
|
12900
|
-
const raced =
|
|
13282
|
+
const { pr: raced, error: lookupError } = lookupExistingPr(branch, cwd);
|
|
12901
13283
|
if (raced) return updateExistingPr(raced, body, draft, cwd);
|
|
13284
|
+
if (lookupError) {
|
|
13285
|
+
throw new Error(
|
|
13286
|
+
`refusing phantom-PR recovery for '${branch}': PR lookup failed (${lookupError}) \u2014 a live PR may own this branch`
|
|
13287
|
+
);
|
|
13288
|
+
}
|
|
12902
13289
|
try {
|
|
12903
13290
|
git2(["push", "origin", "--delete", branch], cwd);
|
|
12904
13291
|
} catch {
|
|
@@ -12916,7 +13303,7 @@ function ensurePr(opts) {
|
|
|
12916
13303
|
const title = buildPrTitle(effectiveOpts.issueNumber, effectiveOpts.issueTitle, effectiveOpts.draft);
|
|
12917
13304
|
const body = buildPrBody(effectiveOpts);
|
|
12918
13305
|
if (existing) {
|
|
12919
|
-
return updateExistingPr(existing, body, opts.draft, opts.cwd);
|
|
13306
|
+
return updateExistingPr(existing, body, opts.draft, opts.cwd, opts.preserveBodyOnUpdate === true);
|
|
12920
13307
|
}
|
|
12921
13308
|
const base = opts.baseBranch && opts.baseBranch.length > 0 ? opts.baseBranch : opts.defaultBranch;
|
|
12922
13309
|
try {
|
|
@@ -13016,10 +13403,10 @@ var init_ensurePr = __esm({
|
|
|
13016
13403
|
const failureReason = computeFailureReason(ctx);
|
|
13017
13404
|
const isFailure = failureReason.length > 0;
|
|
13018
13405
|
const changedFiles = ctx.data.changedFiles ?? [];
|
|
13019
|
-
const
|
|
13406
|
+
const issue2 = ctx.data.issue;
|
|
13020
13407
|
const pr = ctx.data.pr;
|
|
13021
13408
|
const targetNumber = Number(ctx.data.commentTargetNumber ?? 0);
|
|
13022
|
-
const title =
|
|
13409
|
+
const title = issue2?.title ?? pr?.title ?? `kody changes`;
|
|
13023
13410
|
const baseBranch = ctx.data.baseBranch;
|
|
13024
13411
|
try {
|
|
13025
13412
|
const result = ensurePr({
|
|
@@ -13032,6 +13419,9 @@ var init_ensurePr = __esm({
|
|
|
13032
13419
|
changedFiles,
|
|
13033
13420
|
agentSummary: ctx.data.prSummary,
|
|
13034
13421
|
baseBranch,
|
|
13422
|
+
// No fresh commit this run → don't rebuild the body of an existing PR;
|
|
13423
|
+
// it would replace the original agent summary with the empty fallback.
|
|
13424
|
+
preserveBodyOnUpdate: !commitResult?.committed,
|
|
13035
13425
|
cwd: ctx.cwd
|
|
13036
13426
|
});
|
|
13037
13427
|
if (!result.url || result.url.trim().length === 0) {
|
|
@@ -13067,12 +13457,12 @@ var init_failOnceTaskJob = __esm({
|
|
|
13067
13457
|
init_jobIdentity();
|
|
13068
13458
|
failOnceTaskJob = async (ctx, profile) => {
|
|
13069
13459
|
ctx.skipAgent = true;
|
|
13070
|
-
const
|
|
13460
|
+
const issue2 = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
|
|
13071
13461
|
const fallbackJob = {
|
|
13072
13462
|
capability: profile.action ?? profile.name,
|
|
13073
13463
|
implementation: profile.name,
|
|
13074
13464
|
flavor: "instant",
|
|
13075
|
-
...typeof
|
|
13465
|
+
...typeof issue2 === "number" ? { target: issue2, cliArgs: { issue: issue2 } } : { cliArgs: {} }
|
|
13076
13466
|
};
|
|
13077
13467
|
const jobKey = typeof ctx.data.jobKey === "string" ? ctx.data.jobKey : stableJobKey(fallbackJob);
|
|
13078
13468
|
const state = ctx.data.taskState;
|
|
@@ -14058,13 +14448,13 @@ function companyIntentPath(id) {
|
|
|
14058
14448
|
assertIntentId(id);
|
|
14059
14449
|
return `intents/${id}/intent.json`;
|
|
14060
14450
|
}
|
|
14061
|
-
function normalizeCompanyIntent(
|
|
14451
|
+
function normalizeCompanyIntent(path52, raw) {
|
|
14062
14452
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
14063
|
-
throw new Error(`${
|
|
14453
|
+
throw new Error(`${path52}: intent must be JSON object`);
|
|
14064
14454
|
}
|
|
14065
14455
|
const input = raw;
|
|
14066
14456
|
const id = stringField5(input.id);
|
|
14067
|
-
if (!id || !isCompanyIntentId(id)) throw new Error(`${
|
|
14457
|
+
if (!id || !isCompanyIntentId(id)) throw new Error(`${path52}: invalid intent id`);
|
|
14068
14458
|
const createdAt = stringField5(input.createdAt) || nowIso();
|
|
14069
14459
|
const updatedAt = stringField5(input.updatedAt) || createdAt;
|
|
14070
14460
|
const description = stringField5(input.description);
|
|
@@ -14104,8 +14494,8 @@ function listCompanyIntents(config, cwd) {
|
|
|
14104
14494
|
const records = [];
|
|
14105
14495
|
for (const entry of entries) {
|
|
14106
14496
|
if (entry.type !== "dir" || !entry.name || !isCompanyIntentId(entry.name)) continue;
|
|
14107
|
-
const
|
|
14108
|
-
const file = readStateText(config, cwd,
|
|
14497
|
+
const path52 = companyIntentPath(entry.name);
|
|
14498
|
+
const file = readStateText(config, cwd, path52);
|
|
14109
14499
|
if (!file) continue;
|
|
14110
14500
|
records.push({
|
|
14111
14501
|
id: entry.name,
|
|
@@ -14314,14 +14704,14 @@ var init_loadIssueContext = __esm({
|
|
|
14314
14704
|
if (!ctx.data.commentTargetNumber) ctx.data.commentTargetNumber = issueNumber;
|
|
14315
14705
|
return;
|
|
14316
14706
|
}
|
|
14317
|
-
const
|
|
14707
|
+
const issue2 = getIssue(issueNumber, ctx.cwd);
|
|
14318
14708
|
const cfgCtx = ctx.config.issueContext ?? {};
|
|
14319
14709
|
const limit = cfgCtx.commentLimit ?? DEFAULT_COMMENT_LIMIT;
|
|
14320
14710
|
const maxBytes = cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES;
|
|
14321
|
-
const commentsFormatted = formatIssueComments(
|
|
14322
|
-
const labels =
|
|
14711
|
+
const commentsFormatted = formatIssueComments(issue2.comments, limit, maxBytes);
|
|
14712
|
+
const labels = issue2.labels ?? [];
|
|
14323
14713
|
const labelsFormatted = labels.length === 0 ? "(no labels)" : labels.map((l) => `\`${l}\``).join(", ");
|
|
14324
|
-
ctx.data.issue = { ...
|
|
14714
|
+
ctx.data.issue = { ...issue2, commentsFormatted, labelsFormatted };
|
|
14325
14715
|
ctx.data.commentTargetType = "issue";
|
|
14326
14716
|
ctx.data.commentTargetNumber = issueNumber;
|
|
14327
14717
|
};
|
|
@@ -14350,11 +14740,11 @@ var init_loadIssueStateComment = __esm({
|
|
|
14350
14740
|
if (!owner || !repo) {
|
|
14351
14741
|
throw new Error("loadIssueStateComment: ctx.config.github.owner/repo must be set");
|
|
14352
14742
|
}
|
|
14353
|
-
const
|
|
14743
|
+
const issue2 = getIssue(issueNumber, ctx.cwd);
|
|
14354
14744
|
const loaded = findStateComment(owner, repo, issueNumber, marker, ctx.cwd);
|
|
14355
14745
|
ctx.data.stateMarker = marker;
|
|
14356
|
-
ctx.data.issueIntent =
|
|
14357
|
-
ctx.data.issueTitle =
|
|
14746
|
+
ctx.data.issueIntent = issue2.body;
|
|
14747
|
+
ctx.data.issueTitle = issue2.title;
|
|
14358
14748
|
ctx.data.issueNumber = String(issueNumber);
|
|
14359
14749
|
ctx.data.issueStateComment = loaded;
|
|
14360
14750
|
ctx.data.issueStateJson = loaded ? JSON.stringify(loaded.state, null, 2) : "null";
|
|
@@ -14484,15 +14874,15 @@ var init_loadLinkedFinding = __esm({
|
|
|
14484
14874
|
if (!pr) return;
|
|
14485
14875
|
const findingNumber = resolveFindingNumber(pr);
|
|
14486
14876
|
if (!findingNumber) return;
|
|
14487
|
-
let
|
|
14877
|
+
let issue2;
|
|
14488
14878
|
try {
|
|
14489
|
-
|
|
14879
|
+
issue2 = getIssue(findingNumber, ctx.cwd);
|
|
14490
14880
|
} catch {
|
|
14491
14881
|
return;
|
|
14492
14882
|
}
|
|
14493
|
-
ctx.data.linkedFinding = `Issue #${
|
|
14883
|
+
ctx.data.linkedFinding = `Issue #${issue2.number}: ${issue2.title}
|
|
14494
14884
|
|
|
14495
|
-
${truncate(
|
|
14885
|
+
${truncate(issue2.body, FINDING_BODY_MAX_BYTES)}`;
|
|
14496
14886
|
};
|
|
14497
14887
|
}
|
|
14498
14888
|
});
|
|
@@ -14697,8 +15087,8 @@ async function writeGithubStateTextWithConfig(opts) {
|
|
|
14697
15087
|
);
|
|
14698
15088
|
}
|
|
14699
15089
|
}
|
|
14700
|
-
function jsonlLines(
|
|
14701
|
-
return
|
|
15090
|
+
function jsonlLines(text2) {
|
|
15091
|
+
return text2.split("\n").filter((line) => line.length > 0);
|
|
14702
15092
|
}
|
|
14703
15093
|
function renderJsonl(lines) {
|
|
14704
15094
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
@@ -14998,14 +15388,14 @@ var init_loadTaskContext = __esm({
|
|
|
14998
15388
|
loadTaskContext = async (ctx) => {
|
|
14999
15389
|
const runId = resolveRunId();
|
|
15000
15390
|
const rawIssue = ctx.data.issue;
|
|
15001
|
-
const
|
|
15391
|
+
const issue2 = rawIssue ? {
|
|
15002
15392
|
...rawIssue,
|
|
15003
15393
|
commentsFormatted: rawIssue.commentsFormatted ?? "",
|
|
15004
15394
|
labelsFormatted: rawIssue.labelsFormatted ?? ""
|
|
15005
15395
|
} : void 0;
|
|
15006
15396
|
const taskContext = buildTaskContext({
|
|
15007
15397
|
runId,
|
|
15008
|
-
issue,
|
|
15398
|
+
issue: issue2,
|
|
15009
15399
|
conventions: ctx.data.conventions,
|
|
15010
15400
|
priorArt: typeof ctx.data.priorArt === "string" ? ctx.data.priorArt : "",
|
|
15011
15401
|
memoryContext: typeof ctx.data.memoryContext === "string" ? ctx.data.memoryContext : "",
|
|
@@ -15360,6 +15750,12 @@ var init_notifyTerminal = __esm({
|
|
|
15360
15750
|
});
|
|
15361
15751
|
|
|
15362
15752
|
// src/scripts/openAgencyModelReviewPr.ts
|
|
15753
|
+
function isDryRun(ctx) {
|
|
15754
|
+
const arg = ctx.args.dry_run ?? ctx.args.dryRun;
|
|
15755
|
+
if (arg === true) return true;
|
|
15756
|
+
if (typeof arg === "string" && ["1", "true", "yes"].includes(arg.trim().toLowerCase())) return true;
|
|
15757
|
+
return ["1", "true", "yes"].includes((process.env.KODY_DRY_RUN ?? "").trim().toLowerCase());
|
|
15758
|
+
}
|
|
15363
15759
|
function parseAgencyModelProposal(raw) {
|
|
15364
15760
|
const jsonText = stripJsonFence(raw);
|
|
15365
15761
|
let parsed;
|
|
@@ -15429,9 +15825,9 @@ function normalizeBundleFiles(ctx, bundle) {
|
|
|
15429
15825
|
});
|
|
15430
15826
|
}
|
|
15431
15827
|
function stripJsonFence(raw) {
|
|
15432
|
-
const
|
|
15433
|
-
const fence =
|
|
15434
|
-
return (fence ? fence[1] :
|
|
15828
|
+
const text2 = raw.trim();
|
|
15829
|
+
const fence = text2.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i);
|
|
15830
|
+
return (fence ? fence[1] : text2).trim();
|
|
15435
15831
|
}
|
|
15436
15832
|
function readIssueNumber(ctx) {
|
|
15437
15833
|
const issueNumber = ctx.args.issue;
|
|
@@ -15441,9 +15837,9 @@ function readIssueNumber(ctx) {
|
|
|
15441
15837
|
return issueNumber;
|
|
15442
15838
|
}
|
|
15443
15839
|
function readRequiredJsonString(value, field) {
|
|
15444
|
-
const
|
|
15445
|
-
if (!
|
|
15446
|
-
return
|
|
15840
|
+
const text2 = readJsonString(value, field).trim();
|
|
15841
|
+
if (!text2) throw new Error(`openAgencyModelReviewPr: ${field} must be a non-empty string`);
|
|
15842
|
+
return text2;
|
|
15447
15843
|
}
|
|
15448
15844
|
function readJsonString(value, field) {
|
|
15449
15845
|
if (typeof value !== "string") throw new Error(`openAgencyModelReviewPr: ${field} must be a string`);
|
|
@@ -15523,6 +15919,16 @@ var init_openAgencyModelReviewPr = __esm({
|
|
|
15523
15919
|
const stateRepo = parseStateRepo(ctx.config);
|
|
15524
15920
|
const baseBranch = "main";
|
|
15525
15921
|
const branch = buildStatePrBranchName(sourceLabel, issueNumber, bundle.title);
|
|
15922
|
+
if (isDryRun(ctx)) {
|
|
15923
|
+
ctx.data.agencyModelReviewPr = {
|
|
15924
|
+
dryRun: true,
|
|
15925
|
+
repo: `${stateRepo.owner}/${stateRepo.repo}`,
|
|
15926
|
+
branch,
|
|
15927
|
+
base: baseBranch,
|
|
15928
|
+
files: normalizedFiles.map((file) => file.targetPath)
|
|
15929
|
+
};
|
|
15930
|
+
return;
|
|
15931
|
+
}
|
|
15526
15932
|
const baseRef = ghJson(
|
|
15527
15933
|
["api", `/repos/${stateRepo.owner}/${stateRepo.repo}/git/ref/heads/${baseBranch}`],
|
|
15528
15934
|
ctx.cwd
|
|
@@ -15635,10 +16041,10 @@ function ensureLabel2(cwd) {
|
|
|
15635
16041
|
return false;
|
|
15636
16042
|
}
|
|
15637
16043
|
}
|
|
15638
|
-
function markIssueWithReportLabel(
|
|
16044
|
+
function markIssueWithReportLabel(issue2, cwd) {
|
|
15639
16045
|
if (!ensureLabel2(cwd)) return;
|
|
15640
16046
|
try {
|
|
15641
|
-
gh(["issue", "edit", String(
|
|
16047
|
+
gh(["issue", "edit", String(issue2), "--add-label", QA_LABEL], { cwd });
|
|
15642
16048
|
} catch {
|
|
15643
16049
|
}
|
|
15644
16050
|
}
|
|
@@ -15777,13 +16183,13 @@ function isPartialEnvelope(x) {
|
|
|
15777
16183
|
function escapeRegex(s) {
|
|
15778
16184
|
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
15779
16185
|
}
|
|
15780
|
-
function extractFencedBlock(
|
|
16186
|
+
function extractFencedBlock(text2, label) {
|
|
15781
16187
|
const re = new RegExp(`\`\`\`${escapeRegex(label)}\\s*\\n([\\s\\S]*?)\\n\`\`\``, "m");
|
|
15782
|
-
const m = re.exec(
|
|
16188
|
+
const m = re.exec(text2);
|
|
15783
16189
|
return m ? m[1].trim() : null;
|
|
15784
16190
|
}
|
|
15785
|
-
function extractNextStateFromText(
|
|
15786
|
-
const inner = extractFencedBlock(
|
|
16191
|
+
function extractNextStateFromText(text2, fenceLabel, prevRev) {
|
|
16192
|
+
const inner = extractFencedBlock(text2, fenceLabel);
|
|
15787
16193
|
if (inner === null) {
|
|
15788
16194
|
return { error: `missing \`${fenceLabel}\` fenced block` };
|
|
15789
16195
|
}
|
|
@@ -15902,15 +16308,15 @@ var init_parseJobStateFromAgentResult = __esm({
|
|
|
15902
16308
|
});
|
|
15903
16309
|
|
|
15904
16310
|
// src/scripts/parseReproOutput.ts
|
|
15905
|
-
function extractTestPath(
|
|
15906
|
-
const m =
|
|
16311
|
+
function extractTestPath(text2) {
|
|
16312
|
+
const m = text2.match(/^[\s>*_#`~-]*TEST_PATH[\s>*_#`~-]*\s*:\s*(.+?)\s*$/im);
|
|
15907
16313
|
if (!m) return "";
|
|
15908
16314
|
return stripMarkdownEmphasis2(m[1] ?? "");
|
|
15909
16315
|
}
|
|
15910
|
-
function extractFailureSignatureBlock(
|
|
15911
|
-
const startIdx =
|
|
16316
|
+
function extractFailureSignatureBlock(text2) {
|
|
16317
|
+
const startIdx = text2.search(/(?:^|\n)[ \t]*FAILURE_SIGNATURE\s*:[ \t]*/i);
|
|
15912
16318
|
if (startIdx === -1) return "";
|
|
15913
|
-
const afterMarker =
|
|
16319
|
+
const afterMarker = text2.slice(startIdx).replace(/^[\s\S]*?FAILURE_SIGNATURE\s*:[ \t]*\n?/i, "");
|
|
15914
16320
|
const stopRe = /(?:^|\n)[ \t]*(?:COMMIT_MSG|PR_SUMMARY|TEST_PATH)\s*:/i;
|
|
15915
16321
|
const stopIdx = afterMarker.search(stopRe);
|
|
15916
16322
|
let block = stopIdx === -1 ? afterMarker : afterMarker.slice(0, stopIdx);
|
|
@@ -15927,14 +16333,14 @@ function normalizeFailureSignatureBlock(block) {
|
|
|
15927
16333
|
const jsonObject = extractFirstJsonObject(s);
|
|
15928
16334
|
return jsonObject || s;
|
|
15929
16335
|
}
|
|
15930
|
-
function extractFirstJsonObject(
|
|
15931
|
-
const start =
|
|
16336
|
+
function extractFirstJsonObject(text2) {
|
|
16337
|
+
const start = text2.indexOf("{");
|
|
15932
16338
|
if (start === -1) return "";
|
|
15933
16339
|
let depth = 0;
|
|
15934
16340
|
let inString = false;
|
|
15935
16341
|
let escaped = false;
|
|
15936
|
-
for (let i = start; i <
|
|
15937
|
-
const ch =
|
|
16342
|
+
for (let i = start; i < text2.length; i++) {
|
|
16343
|
+
const ch = text2[i];
|
|
15938
16344
|
if (inString) {
|
|
15939
16345
|
if (escaped) {
|
|
15940
16346
|
escaped = false;
|
|
@@ -15951,7 +16357,7 @@ function extractFirstJsonObject(text) {
|
|
|
15951
16357
|
depth++;
|
|
15952
16358
|
} else if (ch === "}") {
|
|
15953
16359
|
depth--;
|
|
15954
|
-
if (depth === 0) return
|
|
16360
|
+
if (depth === 0) return text2.slice(start, i + 1).trim();
|
|
15955
16361
|
}
|
|
15956
16362
|
}
|
|
15957
16363
|
return "";
|
|
@@ -15977,9 +16383,9 @@ var init_parseReproOutput = __esm({
|
|
|
15977
16383
|
"use strict";
|
|
15978
16384
|
parseReproOutput = async (ctx, _profile, agentResult) => {
|
|
15979
16385
|
if (!agentResult || ctx.data.agentDone === false) return;
|
|
15980
|
-
const
|
|
15981
|
-
const testPath = extractTestPath(
|
|
15982
|
-
const signatureRaw = extractFailureSignatureBlock(
|
|
16386
|
+
const text2 = agentResult.finalText ?? "";
|
|
16387
|
+
const testPath = extractTestPath(text2);
|
|
16388
|
+
const signatureRaw = extractFailureSignatureBlock(text2);
|
|
15983
16389
|
if (!testPath) {
|
|
15984
16390
|
downgrade(ctx, "reproduce missing TEST_PATH line in final message");
|
|
15985
16391
|
return;
|
|
@@ -16158,8 +16564,8 @@ var init_planTaskJobs = __esm({
|
|
|
16158
16564
|
ctx.output.reason = "planTaskJobs requires --issue";
|
|
16159
16565
|
return;
|
|
16160
16566
|
}
|
|
16161
|
-
const
|
|
16162
|
-
const specs = parseTaskJobSpecs(
|
|
16567
|
+
const issue2 = ctx.data.issue;
|
|
16568
|
+
const specs = parseTaskJobSpecs(issue2?.body ?? "");
|
|
16163
16569
|
if (specs.length === 0) {
|
|
16164
16570
|
ctx.skipAgent = true;
|
|
16165
16571
|
ctx.output.exitCode = 64;
|
|
@@ -16489,8 +16895,8 @@ var init_promoteQaGoal = __esm({
|
|
|
16489
16895
|
}
|
|
16490
16896
|
let report;
|
|
16491
16897
|
try {
|
|
16492
|
-
const
|
|
16493
|
-
const reportComment = [...
|
|
16898
|
+
const issue2 = getIssue(issueNum, ctx.cwd);
|
|
16899
|
+
const reportComment = [...issue2.comments].reverse().find((c) => c.body.includes(REPORT_JSON_OPEN2));
|
|
16494
16900
|
if (!reportComment) {
|
|
16495
16901
|
ctx.output.exitCode = 3;
|
|
16496
16902
|
ctx.output.reason = `no QA report (${REPORT_JSON_OPEN2} \u2026) found on issue #${issueNum}`;
|
|
@@ -16557,9 +16963,9 @@ function latestResult(raw, agentResult) {
|
|
|
16557
16963
|
function recordField6(value) {
|
|
16558
16964
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
16559
16965
|
}
|
|
16560
|
-
function resolveDotted(root,
|
|
16561
|
-
if (!
|
|
16562
|
-
return
|
|
16966
|
+
function resolveDotted(root, path52) {
|
|
16967
|
+
if (!path52) return void 0;
|
|
16968
|
+
return path52.split(".").reduce((value, key) => recordField6(value)?.[key], root);
|
|
16563
16969
|
}
|
|
16564
16970
|
function stringValue4(value) {
|
|
16565
16971
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
@@ -16601,13 +17007,7 @@ var init_publishReport = __esm({
|
|
|
16601
17007
|
...publication.reviewArea ? { reviewArea: publication.reviewArea } : {}
|
|
16602
17008
|
});
|
|
16603
17009
|
const runId = generatedAt.replace(/\.\d{3}Z$/, "Z").replace(/:/g, "-");
|
|
16604
|
-
writeStateText(
|
|
16605
|
-
ctx.config,
|
|
16606
|
-
ctx.cwd,
|
|
16607
|
-
`reports/${slug}/runs/${runId}.md`,
|
|
16608
|
-
markdown,
|
|
16609
|
-
`chore(reports): add ${slug} run`
|
|
16610
|
-
);
|
|
17010
|
+
writeStateText(ctx.config, ctx.cwd, `reports/${slug}/runs/${runId}.md`, markdown, `chore(reports): add ${slug} run`);
|
|
16611
17011
|
};
|
|
16612
17012
|
}
|
|
16613
17013
|
});
|
|
@@ -17315,15 +17715,15 @@ var init_runFlow = __esm({
|
|
|
17315
17715
|
init_issue();
|
|
17316
17716
|
runFlow = async (ctx) => {
|
|
17317
17717
|
const issueNumber = ctx.args.issue;
|
|
17318
|
-
const
|
|
17718
|
+
const issue2 = getIssue(issueNumber, ctx.cwd);
|
|
17319
17719
|
const cfgCtx = ctx.config.issueContext ?? {};
|
|
17320
17720
|
const commentsFormatted = formatIssueComments(
|
|
17321
|
-
|
|
17721
|
+
issue2.comments,
|
|
17322
17722
|
cfgCtx.commentLimit ?? DEFAULT_COMMENT_LIMIT,
|
|
17323
17723
|
cfgCtx.commentMaxBytes ?? DEFAULT_COMMENT_MAX_BYTES
|
|
17324
17724
|
);
|
|
17325
|
-
ctx.data.issue = { ...
|
|
17326
|
-
if (
|
|
17725
|
+
ctx.data.issue = { ...issue2, commentsFormatted };
|
|
17726
|
+
if (issue2.isPullRequest) {
|
|
17327
17727
|
ctx.data.commentTargetType = "pr";
|
|
17328
17728
|
ctx.data.commentTargetNumber = issueNumber;
|
|
17329
17729
|
ctx.skipAgent = true;
|
|
@@ -17347,7 +17747,7 @@ var init_runFlow = __esm({
|
|
|
17347
17747
|
}
|
|
17348
17748
|
const branchInfo = ensureFeatureBranch(
|
|
17349
17749
|
issueNumber,
|
|
17350
|
-
|
|
17750
|
+
issue2.title,
|
|
17351
17751
|
ctx.config.git.defaultBranch,
|
|
17352
17752
|
ctx.cwd,
|
|
17353
17753
|
base ?? void 0
|
|
@@ -17596,8 +17996,8 @@ async function flyCreateApp(name, orgSlug, token) {
|
|
|
17596
17996
|
});
|
|
17597
17997
|
if (res.status === 422) return;
|
|
17598
17998
|
if (!res.ok) {
|
|
17599
|
-
const
|
|
17600
|
-
throw new Error(`createApp ${name}: ${res.status} ${
|
|
17999
|
+
const text2 = await res.text().catch(() => "");
|
|
18000
|
+
throw new Error(`createApp ${name}: ${res.status} ${text2.slice(0, 200)}`);
|
|
17601
18001
|
}
|
|
17602
18002
|
}
|
|
17603
18003
|
async function flyAllocateSharedIps(appName, token) {
|
|
@@ -17704,9 +18104,9 @@ async function flyCreatePreviewMachine(args, token) {
|
|
|
17704
18104
|
const { id } = await res.json();
|
|
17705
18105
|
return id;
|
|
17706
18106
|
}
|
|
17707
|
-
const
|
|
17708
|
-
lastErr = new Error(`createPreviewMachine ${res.status}: ${
|
|
17709
|
-
if (!/MANIFEST_UNKNOWN|manifest unknown/i.test(
|
|
18107
|
+
const text2 = await res.text().catch(() => "");
|
|
18108
|
+
lastErr = new Error(`createPreviewMachine ${res.status}: ${text2.slice(0, 300)}`);
|
|
18109
|
+
if (!/MANIFEST_UNKNOWN|manifest unknown/i.test(text2)) break;
|
|
17710
18110
|
await new Promise((r) => setTimeout(r, 2e3 * (attempt + 1)));
|
|
17711
18111
|
}
|
|
17712
18112
|
throw lastErr ?? new Error("createPreviewMachine failed (unknown)");
|
|
@@ -18145,8 +18545,8 @@ var init_setCommentTarget = __esm({
|
|
|
18145
18545
|
|
|
18146
18546
|
// src/scripts/setLifecycleLabel.ts
|
|
18147
18547
|
function resolveTargetNumber(args) {
|
|
18148
|
-
const
|
|
18149
|
-
if (typeof
|
|
18548
|
+
const issue2 = args.issue;
|
|
18549
|
+
if (typeof issue2 === "number" && Number.isFinite(issue2)) return issue2;
|
|
18150
18550
|
const pr = args.pr;
|
|
18151
18551
|
if (typeof pr === "number" && Number.isFinite(pr)) return pr;
|
|
18152
18552
|
return void 0;
|
|
@@ -18361,24 +18761,29 @@ var init_syncFlow = __esm({
|
|
|
18361
18761
|
});
|
|
18362
18762
|
|
|
18363
18763
|
// src/scripts/validateAgencyModelProposal.ts
|
|
18364
|
-
|
|
18764
|
+
import * as path43 from "path";
|
|
18765
|
+
function validateModelBundle(bundle, expectedKind, options = {}) {
|
|
18365
18766
|
const failures = [];
|
|
18366
|
-
validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind);
|
|
18767
|
+
validateOneModel(bundle.model, bundle.files, "model", true, failures, expectedKind, options);
|
|
18367
18768
|
return failures;
|
|
18368
18769
|
}
|
|
18369
18770
|
function readExpectedModelKind(args) {
|
|
18370
18771
|
const value = args?.modelKind;
|
|
18371
18772
|
if (typeof value === "string" && isModelKind(value)) return value;
|
|
18372
|
-
throw new Error(
|
|
18773
|
+
throw new Error(
|
|
18774
|
+
"validateAgencyModelProposal: with.modelKind must be intent, operation, agent, capability, goal, agentLoop, or workflow"
|
|
18775
|
+
);
|
|
18373
18776
|
}
|
|
18374
|
-
function validateOneModel(rawModel, files, label, strictSingleModel, failures, expectedKind) {
|
|
18777
|
+
function validateOneModel(rawModel, files, label, strictSingleModel, failures, expectedKind, options = {}) {
|
|
18375
18778
|
if (!rawModel || typeof rawModel !== "object" || Array.isArray(rawModel)) {
|
|
18376
18779
|
failures.push(`${label} must be an object`);
|
|
18377
18780
|
return;
|
|
18378
18781
|
}
|
|
18379
18782
|
const model = rawModel;
|
|
18380
18783
|
const kind = stringField6(model.kind);
|
|
18381
|
-
if (!isModelKind(kind))
|
|
18784
|
+
if (!isModelKind(kind)) {
|
|
18785
|
+
failures.push(`${label}.kind must be intent, operation, agent, capability, goal, agentLoop, or workflow`);
|
|
18786
|
+
}
|
|
18382
18787
|
if (expectedKind && kind !== expectedKind) failures.push(`proposal must output model.kind ${expectedKind}`);
|
|
18383
18788
|
const slug = stringField6(model.slug);
|
|
18384
18789
|
if (!isSlug(slug)) failures.push(`${label}.slug must be a lowercase slug`);
|
|
@@ -18387,15 +18792,23 @@ function validateOneModel(rawModel, files, label, strictSingleModel, failures, e
|
|
|
18387
18792
|
for (const doc of REQUIRED_DOCS[kind]) {
|
|
18388
18793
|
if (!docsUsed.includes(doc)) failures.push(`${label} docsUsed missing ${doc}`);
|
|
18389
18794
|
}
|
|
18390
|
-
validateFilesForKind(kind, slug, files, strictSingleModel, failures);
|
|
18795
|
+
validateFilesForKind(kind, slug, files, strictSingleModel, failures, options);
|
|
18391
18796
|
validateModelShape(kind, model, files, slug, failures);
|
|
18392
18797
|
}
|
|
18393
18798
|
}
|
|
18394
|
-
function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
|
|
18799
|
+
function validateFilesForKind(kind, slug, files, strictSingleModel, failures, options) {
|
|
18395
18800
|
const paths = files.map((file) => normalizeBundlePath(file.path));
|
|
18396
18801
|
if (paths.some((filePath) => filePath === "implementations" || filePath.startsWith("implementations/"))) {
|
|
18397
18802
|
failures.push("files must not use obsolete implementation storage");
|
|
18398
18803
|
}
|
|
18804
|
+
if (kind === "intent") {
|
|
18805
|
+
requirePath(paths, `intents/${slug}/intent.json`, "intent state", failures);
|
|
18806
|
+
if (strictSingleModel) rejectOtherRoots(paths, [`intents/${slug}/`], "intent", failures);
|
|
18807
|
+
}
|
|
18808
|
+
if (kind === "operation") {
|
|
18809
|
+
requirePath(paths, `operations/${slug}/operation.json`, "operation contract", failures);
|
|
18810
|
+
if (strictSingleModel) rejectOtherRoots(paths, [`operations/${slug}/`], "operation", failures);
|
|
18811
|
+
}
|
|
18399
18812
|
if (kind === "agent") {
|
|
18400
18813
|
requirePath(paths, `agents/${slug}.md`, "agent file", failures);
|
|
18401
18814
|
if (strictSingleModel) rejectOtherRoots(paths, ["agents/"], "agent", failures);
|
|
@@ -18430,6 +18843,7 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
|
|
|
18430
18843
|
}
|
|
18431
18844
|
if (kind === "workflow") {
|
|
18432
18845
|
requirePath(paths, `capabilities/${slug}/profile.json`, "workflow capability profile", failures);
|
|
18846
|
+
requirePath(paths, `capabilities/${slug}/capability.md`, "workflow capability body", failures);
|
|
18433
18847
|
const profile = parseJsonFile(files, `capabilities/${slug}/profile.json`, failures);
|
|
18434
18848
|
if (profile) {
|
|
18435
18849
|
if (profile.capabilityKind !== void 0) {
|
|
@@ -18439,11 +18853,73 @@ function validateFilesForKind(kind, slug, files, strictSingleModel, failures) {
|
|
|
18439
18853
|
const hasTopLevelSteps = Array.isArray(profile.steps) && profile.steps.length > 0;
|
|
18440
18854
|
if (!hasWorkflowObject && !hasTopLevelSteps) {
|
|
18441
18855
|
failures.push("workflow profile must include workflow object or top-level steps");
|
|
18856
|
+
} else {
|
|
18857
|
+
const workflow = hasWorkflowObject ? profile.workflow : { steps: profile.steps, ...profile.startAt !== void 0 ? { startAt: profile.startAt } : {} };
|
|
18858
|
+
const known = options.capabilityRoot ? getCapabilityRoots(options.capabilityRoot).flatMap((root) => listCapabilityFolderSlugs(root)) : [];
|
|
18859
|
+
const uniqueKnown = [...new Set(known)];
|
|
18860
|
+
const capabilityInputs = /* @__PURE__ */ new Map();
|
|
18861
|
+
if (options.capabilityRoot) {
|
|
18862
|
+
for (const capability of uniqueKnown) {
|
|
18863
|
+
const inputs = getCapabilityActionInputs(capability, options.capabilityRoot);
|
|
18864
|
+
if (inputs) {
|
|
18865
|
+
capabilityInputs.set(
|
|
18866
|
+
capability,
|
|
18867
|
+
new Set(inputs.flatMap((input) => [input.name, input.flag.replace(/^--/, "")]))
|
|
18868
|
+
);
|
|
18869
|
+
}
|
|
18870
|
+
}
|
|
18871
|
+
}
|
|
18872
|
+
failures.push(
|
|
18873
|
+
...formatWorkflowValidationIssues(
|
|
18874
|
+
validateWorkflow(workflow, {
|
|
18875
|
+
...uniqueKnown.length > 0 ? { knownCapabilities: new Set(uniqueKnown) } : {},
|
|
18876
|
+
...capabilityInputs.size > 0 ? { capabilityInputs } : {}
|
|
18877
|
+
})
|
|
18878
|
+
)
|
|
18879
|
+
);
|
|
18442
18880
|
}
|
|
18443
18881
|
}
|
|
18444
18882
|
}
|
|
18445
18883
|
}
|
|
18446
18884
|
function validateModelShape(kind, model, files, slug, failures) {
|
|
18885
|
+
if (kind === "intent") {
|
|
18886
|
+
const intent = parseJsonFile(files, `intents/${slug}/intent.json`, failures);
|
|
18887
|
+
if (!stringField6(model.direction)) failures.push("intent model must declare direction");
|
|
18888
|
+
if (!stringField6(intent?.for)) failures.push("intent file must declare direction");
|
|
18889
|
+
if (!isFiniteNumber(model.priority)) failures.push("intent model must declare numeric priority");
|
|
18890
|
+
if (!isFiniteNumber(intent?.priority)) failures.push("intent file must declare numeric priority");
|
|
18891
|
+
if (!hasScope(model.scope)) failures.push("intent model scope must include a repo or area");
|
|
18892
|
+
if (!hasScope(intent?.scope)) failures.push("intent file scope must include a repo or area");
|
|
18893
|
+
if (stringArray4(model.principles).length === 0) failures.push("intent model principles must be non-empty");
|
|
18894
|
+
if (stringArray4(intent?.principles).length === 0) failures.push("intent file principles must be non-empty");
|
|
18895
|
+
if (stringArray4(model.successMeasures).length === 0) {
|
|
18896
|
+
failures.push("intent model successMeasures must be non-empty");
|
|
18897
|
+
}
|
|
18898
|
+
if (stringArray4(intent?.metrics).length === 0) failures.push("intent file metrics must be non-empty");
|
|
18899
|
+
if (!recordField7(model.policy)) failures.push("intent model must declare policy");
|
|
18900
|
+
if (!recordField7(intent?.policy)) failures.push("intent file must declare policy");
|
|
18901
|
+
if (stringField6(model.status) !== "paused" || stringField6(intent?.status) !== "paused") {
|
|
18902
|
+
failures.push("intent proposal status must be paused");
|
|
18903
|
+
}
|
|
18904
|
+
if (intent?.version !== 1) failures.push("intent file version must be 1");
|
|
18905
|
+
if (intent && stringField6(intent.id) !== slug) failures.push("intent id must match model.slug");
|
|
18906
|
+
requireStringArrayIncludes(model.doesNotOwn, "operations", "intent doesNotOwn", failures);
|
|
18907
|
+
requireStringArrayIncludes(model.doesNotOwn, "capability implementation", "intent doesNotOwn", failures);
|
|
18908
|
+
}
|
|
18909
|
+
if (kind === "operation") {
|
|
18910
|
+
const operation = parseJsonFile(files, `operations/${slug}/operation.json`, failures);
|
|
18911
|
+
if (!stringField6(model.responsibility)) failures.push("operation model must declare responsibility");
|
|
18912
|
+
if (!stringField6(operation?.responsibility)) failures.push("operation file must declare responsibility");
|
|
18913
|
+
if (stringArray4(model.intentIds).length === 0) failures.push("operation model intentIds must be non-empty");
|
|
18914
|
+
if (stringArray4(operation?.intentIds).length === 0) failures.push("operation file intentIds must be non-empty");
|
|
18915
|
+
if (stringArray4(model.doesNotOwn).length === 0) failures.push("operation model doesNotOwn must be non-empty");
|
|
18916
|
+
if (stringArray4(operation?.doesNotOwn).length === 0) failures.push("operation file doesNotOwn must be non-empty");
|
|
18917
|
+
if (stringField6(model.status) !== "proposed" || stringField6(operation?.status) !== "proposed") {
|
|
18918
|
+
failures.push("operation proposal status must be proposed");
|
|
18919
|
+
}
|
|
18920
|
+
if (operation?.version !== 1) failures.push("operation file version must be 1");
|
|
18921
|
+
if (operation && stringField6(operation.id) !== slug) failures.push("operation id must match model.slug");
|
|
18922
|
+
}
|
|
18447
18923
|
if (kind === "agent") {
|
|
18448
18924
|
const agentFile = textFile(files, `agents/${slug}.md`);
|
|
18449
18925
|
if (!stringArray4(model.owns).includes("identity") && !containsWord(agentFile, "identity")) {
|
|
@@ -18565,6 +19041,16 @@ function stringArray4(value) {
|
|
|
18565
19041
|
if (!Array.isArray(value)) return [];
|
|
18566
19042
|
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
18567
19043
|
}
|
|
19044
|
+
function recordField7(value) {
|
|
19045
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
19046
|
+
}
|
|
19047
|
+
function isFiniteNumber(value) {
|
|
19048
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
19049
|
+
}
|
|
19050
|
+
function hasScope(value) {
|
|
19051
|
+
const scope = recordField7(value);
|
|
19052
|
+
return stringArray4(scope?.repos).length > 0 || stringArray4(scope?.areas).length > 0;
|
|
19053
|
+
}
|
|
18568
19054
|
function requireStringArrayIncludes(value, expected, label, failures) {
|
|
18569
19055
|
if (!stringArray4(value).includes(expected)) failures.push(`${label} must include ${expected}`);
|
|
18570
19056
|
}
|
|
@@ -18572,14 +19058,19 @@ function isSlug(value) {
|
|
|
18572
19058
|
return /^[a-z][a-z0-9-]{0,63}$/.test(value);
|
|
18573
19059
|
}
|
|
18574
19060
|
function isModelKind(value) {
|
|
18575
|
-
return value === "agent" || value === "capability" || value === "goal" || value === "agentLoop" || value === "workflow";
|
|
19061
|
+
return value === "intent" || value === "operation" || value === "agent" || value === "capability" || value === "goal" || value === "agentLoop" || value === "workflow";
|
|
18576
19062
|
}
|
|
18577
19063
|
var REQUIRED_DOCS, validateAgencyModelProposal;
|
|
18578
19064
|
var init_validateAgencyModelProposal = __esm({
|
|
18579
19065
|
"src/scripts/validateAgencyModelProposal.ts"() {
|
|
18580
19066
|
"use strict";
|
|
19067
|
+
init_capabilityFolders();
|
|
19068
|
+
init_registry();
|
|
19069
|
+
init_workflowValidation();
|
|
18581
19070
|
init_openAgencyModelReviewPr();
|
|
18582
19071
|
REQUIRED_DOCS = {
|
|
19072
|
+
intent: ["docs/intents.md", "docs/engine-company.md"],
|
|
19073
|
+
operation: ["docs/operations.md", "docs/engine-company.md"],
|
|
18583
19074
|
agent: ["docs/agents.md"],
|
|
18584
19075
|
capability: ["docs/capabilities.md", "docs/capability-kind-map.md", "docs/capability-implementations.md"],
|
|
18585
19076
|
goal: ["docs/goals.md", "docs/jobs-model.md", "docs/capabilities.md"],
|
|
@@ -18591,7 +19082,9 @@ var init_validateAgencyModelProposal = __esm({
|
|
|
18591
19082
|
const raw = String(ctx.data.prSummary ?? "");
|
|
18592
19083
|
const bundle = parseAgencyModelProposal(raw);
|
|
18593
19084
|
const expectedKind = readExpectedModelKind(args);
|
|
18594
|
-
const failures = validateModelBundle(bundle, expectedKind
|
|
19085
|
+
const failures = validateModelBundle(bundle, expectedKind, {
|
|
19086
|
+
capabilityRoot: path43.join(ctx.cwd, ".kody", "capabilities")
|
|
19087
|
+
});
|
|
18595
19088
|
if (failures.length > 0) {
|
|
18596
19089
|
throw new Error(`validateAgencyModelProposal: ${failures.join("; ")}`);
|
|
18597
19090
|
}
|
|
@@ -19150,9 +19643,9 @@ var init_writeAgentRunSummary = __esm({
|
|
|
19150
19643
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
19151
19644
|
if (!summaryPath) return;
|
|
19152
19645
|
const implementation = profile.name;
|
|
19153
|
-
const
|
|
19646
|
+
const issue2 = ctx.args.issue;
|
|
19154
19647
|
const pr = ctx.args.pr;
|
|
19155
|
-
const target =
|
|
19648
|
+
const target = issue2 ? `issue #${issue2}` : pr ? `PR #${pr}` : "(unknown)";
|
|
19156
19649
|
const prUrl = ctx.output.prUrl;
|
|
19157
19650
|
const exitCode = ctx.output.exitCode ?? 0;
|
|
19158
19651
|
const reason = ctx.output.reason;
|
|
@@ -19492,38 +19985,38 @@ import { execFileSync as execFileSync24 } from "child_process";
|
|
|
19492
19985
|
import * as crypto3 from "crypto";
|
|
19493
19986
|
import * as fs45 from "fs";
|
|
19494
19987
|
import * as os7 from "os";
|
|
19495
|
-
import * as
|
|
19988
|
+
import * as path44 from "path";
|
|
19496
19989
|
function writeLocalFile(cwd, relativePath, content) {
|
|
19497
|
-
const fullPath =
|
|
19498
|
-
fs45.mkdirSync(
|
|
19990
|
+
const fullPath = path44.join(cwd, relativePath);
|
|
19991
|
+
fs45.mkdirSync(path44.dirname(fullPath), { recursive: true });
|
|
19499
19992
|
fs45.writeFileSync(fullPath, content);
|
|
19500
19993
|
}
|
|
19501
19994
|
function copyPath(source, target) {
|
|
19502
19995
|
const st = fs45.lstatSync(source);
|
|
19503
19996
|
fs45.rmSync(target, { recursive: true, force: true });
|
|
19504
19997
|
if (st.isSymbolicLink()) return;
|
|
19505
|
-
fs45.mkdirSync(
|
|
19998
|
+
fs45.mkdirSync(path44.dirname(target), { recursive: true });
|
|
19506
19999
|
fs45.cpSync(source, target, { recursive: true, force: true });
|
|
19507
20000
|
}
|
|
19508
20001
|
function overlayDirectoryChildren(cwd, sourceDir, localDir) {
|
|
19509
20002
|
if (!fs45.existsSync(sourceDir)) return;
|
|
19510
20003
|
for (const entry of fs45.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
19511
|
-
const source =
|
|
19512
|
-
const target =
|
|
20004
|
+
const source = path44.join(sourceDir, entry.name);
|
|
20005
|
+
const target = path44.join(cwd, localDir, entry.name);
|
|
19513
20006
|
copyPath(source, target);
|
|
19514
20007
|
}
|
|
19515
20008
|
}
|
|
19516
20009
|
function hydrateStateWorkspace(config, cwd) {
|
|
19517
20010
|
if (process.env.VITEST && process.env[TEST_FETCH_ENV] !== "1") return;
|
|
19518
20011
|
const parsed = parseStateRepo(config);
|
|
19519
|
-
const hydrateKey = `${
|
|
20012
|
+
const hydrateKey = `${path44.resolve(cwd)}|${parsed.owner}/${parsed.repo}|${parsed.basePath}|${parsed.branch}`;
|
|
19520
20013
|
if (hydratedWorkspaces.has(hydrateKey)) return;
|
|
19521
20014
|
const snapshotRoot = fetchStateSnapshot(parsed);
|
|
19522
20015
|
for (const mapping of DIR_MAPPINGS) {
|
|
19523
|
-
overlayDirectoryChildren(cwd,
|
|
20016
|
+
overlayDirectoryChildren(cwd, path44.join(snapshotRoot, mapping.stateDir), mapping.localDir);
|
|
19524
20017
|
}
|
|
19525
20018
|
for (const mapping of FILE_MAPPINGS) {
|
|
19526
|
-
const source =
|
|
20019
|
+
const source = path44.join(snapshotRoot, mapping.statePath);
|
|
19527
20020
|
if (fs45.existsSync(source) && !fs45.lstatSync(source).isSymbolicLink() && fs45.statSync(source).isFile()) {
|
|
19528
20021
|
writeLocalFile(cwd, mapping.localPath, fs45.readFileSync(source, "utf-8"));
|
|
19529
20022
|
}
|
|
@@ -19531,11 +20024,11 @@ function hydrateStateWorkspace(config, cwd) {
|
|
|
19531
20024
|
hydratedWorkspaces.add(hydrateKey);
|
|
19532
20025
|
}
|
|
19533
20026
|
function fetchStateSnapshot(parsed) {
|
|
19534
|
-
const cacheDir =
|
|
20027
|
+
const cacheDir = path44.join(cacheRoot2(), cacheKey3(parsed));
|
|
19535
20028
|
const url = `https://github.com/${parsed.owner}/${parsed.repo}.git`;
|
|
19536
20029
|
try {
|
|
19537
|
-
fs45.mkdirSync(
|
|
19538
|
-
if (!fs45.existsSync(
|
|
20030
|
+
fs45.mkdirSync(path44.dirname(cacheDir), { recursive: true });
|
|
20031
|
+
if (!fs45.existsSync(path44.join(cacheDir, ".git"))) {
|
|
19539
20032
|
fs45.rmSync(cacheDir, { recursive: true, force: true });
|
|
19540
20033
|
runGit3(["clone", "--no-checkout", "--filter=blob:none", url, cacheDir]);
|
|
19541
20034
|
}
|
|
@@ -19551,10 +20044,10 @@ function fetchStateSnapshot(parsed) {
|
|
|
19551
20044
|
`stateWorkspace: failed to fetch ${parsed.owner}/${parsed.repo}:${parsed.basePath}@${parsed.branch}: ${msg}`
|
|
19552
20045
|
);
|
|
19553
20046
|
}
|
|
19554
|
-
return
|
|
20047
|
+
return path44.join(cacheDir, parsed.basePath);
|
|
19555
20048
|
}
|
|
19556
20049
|
function cacheRoot2() {
|
|
19557
|
-
return process.env[CACHE_ENV2]?.trim() ||
|
|
20050
|
+
return process.env[CACHE_ENV2]?.trim() || path44.join(os7.homedir(), ".cache", "kody", "state-repo");
|
|
19558
20051
|
}
|
|
19559
20052
|
function cacheKey3(parsed) {
|
|
19560
20053
|
return crypto3.createHash("sha256").update(`${parsed.owner}/${parsed.repo}#${parsed.branch}#${parsed.basePath}`).digest("hex").slice(0, 24);
|
|
@@ -19594,16 +20087,16 @@ var init_stateWorkspace = __esm({
|
|
|
19594
20087
|
"use strict";
|
|
19595
20088
|
init_stateRepo();
|
|
19596
20089
|
DIR_MAPPINGS = [
|
|
19597
|
-
{ stateDir: "capabilities", localDir:
|
|
19598
|
-
{ stateDir: "agents", localDir:
|
|
19599
|
-
{ stateDir: "context", localDir:
|
|
19600
|
-
{ stateDir: "memory", localDir:
|
|
20090
|
+
{ stateDir: "capabilities", localDir: path44.join(".kody", "capabilities") },
|
|
20091
|
+
{ stateDir: "agents", localDir: path44.join(".kody", "agents") },
|
|
20092
|
+
{ stateDir: "context", localDir: path44.join(".kody", "context") },
|
|
20093
|
+
{ stateDir: "memory", localDir: path44.join(".kody", "memory") }
|
|
19601
20094
|
];
|
|
19602
20095
|
FILE_MAPPINGS = [
|
|
19603
|
-
{ statePath: "instructions.md", localPath:
|
|
19604
|
-
{ statePath: "system-prompt.md", localPath:
|
|
19605
|
-
{ statePath: "variables.json", localPath:
|
|
19606
|
-
{ statePath: "secrets.enc", localPath:
|
|
20096
|
+
{ statePath: "instructions.md", localPath: path44.join(".kody", "instructions.md") },
|
|
20097
|
+
{ statePath: "system-prompt.md", localPath: path44.join(".kody", "system-prompt.md") },
|
|
20098
|
+
{ statePath: "variables.json", localPath: path44.join(".kody", "variables.json") },
|
|
20099
|
+
{ statePath: "secrets.enc", localPath: path44.join(".kody", "secrets.enc") }
|
|
19607
20100
|
];
|
|
19608
20101
|
CACHE_ENV2 = "KODY_STATE_REPO_CACHE";
|
|
19609
20102
|
TEST_FETCH_ENV = "KODY_STATE_WORKSPACE_FETCH_FOR_TESTS";
|
|
@@ -19678,7 +20171,8 @@ var init_tools = __esm({
|
|
|
19678
20171
|
// src/executor.ts
|
|
19679
20172
|
import { spawn as spawn7 } from "child_process";
|
|
19680
20173
|
import * as fs46 from "fs";
|
|
19681
|
-
import * as
|
|
20174
|
+
import * as os8 from "os";
|
|
20175
|
+
import * as path45 from "path";
|
|
19682
20176
|
function isMutatingPostflight(scriptName) {
|
|
19683
20177
|
return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
|
|
19684
20178
|
}
|
|
@@ -19706,9 +20200,9 @@ function collectShellSideChannels(ctx, stdout) {
|
|
|
19706
20200
|
}
|
|
19707
20201
|
}
|
|
19708
20202
|
function operatorRequestBlock(why) {
|
|
19709
|
-
const
|
|
19710
|
-
if (!
|
|
19711
|
-
const safe =
|
|
20203
|
+
const text2 = why.trim();
|
|
20204
|
+
if (!text2) return null;
|
|
20205
|
+
const safe = text2.replace(/-{3,}\s*END UNTRUSTED INPUT\s*-{3,}/gi, "[END UNTRUSTED INPUT]");
|
|
19712
20206
|
return [
|
|
19713
20207
|
"## The request that triggered this run",
|
|
19714
20208
|
"",
|
|
@@ -19901,7 +20395,7 @@ async function runImplementation(profileName, input) {
|
|
|
19901
20395
|
const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
|
|
19902
20396
|
const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
|
|
19903
20397
|
const invokeAgent = async (prompt) => {
|
|
19904
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
20398
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path45.isAbsolute(p) ? p : path45.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
19905
20399
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
19906
20400
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
19907
20401
|
const agents = loadSubagents(profile);
|
|
@@ -19972,6 +20466,11 @@ async function runImplementation(profileName, input) {
|
|
|
19972
20466
|
});
|
|
19973
20467
|
};
|
|
19974
20468
|
ctx.data.__invokeAgent = invokeAgent;
|
|
20469
|
+
const cc = profile.claudeCode;
|
|
20470
|
+
const declaresPluginParts = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
|
|
20471
|
+
if (declaresPluginParts && !profile.scripts.preflight.some((e) => e.script === "buildSyntheticPlugin")) {
|
|
20472
|
+
profile.scripts.preflight = [{ script: "buildSyntheticPlugin" }, ...profile.scripts.preflight];
|
|
20473
|
+
}
|
|
19975
20474
|
try {
|
|
19976
20475
|
for (const entry of profile.scripts.preflight) {
|
|
19977
20476
|
const preLabel = entry.script ?? entry.shell ?? "<unknown>";
|
|
@@ -20145,8 +20644,12 @@ async function runImplementation(profileName, input) {
|
|
|
20145
20644
|
nextDispatch: ctx.output.nextDispatch,
|
|
20146
20645
|
nextJob: ctx.output.nextJob,
|
|
20147
20646
|
afterNextJob: ctx.output.afterNextJob,
|
|
20148
|
-
taskState: ctx.data.taskState
|
|
20647
|
+
taskState: ctx.data.taskState,
|
|
20648
|
+
capabilityResults: Array.isArray(ctx.data.capabilityResults) ? ctx.data.capabilityResults : void 0
|
|
20149
20649
|
});
|
|
20650
|
+
} catch (err) {
|
|
20651
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20652
|
+
return finishAndEnd({ exitCode: 99, reason: ctx.output.reason ?? msg });
|
|
20150
20653
|
} finally {
|
|
20151
20654
|
clearStampedLifecycleLabels(profile, ctx);
|
|
20152
20655
|
if (taskArtifacts) {
|
|
@@ -20330,13 +20833,13 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
20330
20833
|
function resolveProfilePath(profileName) {
|
|
20331
20834
|
const found = resolveImplementation(profileName);
|
|
20332
20835
|
if (found) return found;
|
|
20333
|
-
const here =
|
|
20836
|
+
const here = path45.dirname(new URL(import.meta.url).pathname);
|
|
20334
20837
|
const candidates = [
|
|
20335
|
-
|
|
20838
|
+
path45.join(here, "implementations", profileName, "profile.json"),
|
|
20336
20839
|
// same-dir sibling (dev)
|
|
20337
|
-
|
|
20840
|
+
path45.join(here, "..", "implementations", profileName, "profile.json"),
|
|
20338
20841
|
// up one (prod: dist/bin → dist/implementations)
|
|
20339
|
-
|
|
20842
|
+
path45.join(here, "..", "src", "implementations", profileName, "profile.json")
|
|
20340
20843
|
// fallback
|
|
20341
20844
|
];
|
|
20342
20845
|
for (const c of candidates) {
|
|
@@ -20455,7 +20958,7 @@ function resolveShellTimeoutMs(entry) {
|
|
|
20455
20958
|
}
|
|
20456
20959
|
async function runShellEntry(entry, ctx, profile) {
|
|
20457
20960
|
const shellName = entry.shell;
|
|
20458
|
-
const shellPath =
|
|
20961
|
+
const shellPath = path45.join(profile.dir, shellName);
|
|
20459
20962
|
if (!fs46.existsSync(shellPath)) {
|
|
20460
20963
|
ctx.skipAgent = true;
|
|
20461
20964
|
ctx.output.exitCode = 99;
|
|
@@ -20463,7 +20966,11 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
20463
20966
|
return;
|
|
20464
20967
|
}
|
|
20465
20968
|
const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
|
|
20466
|
-
const
|
|
20969
|
+
const outputFile = path45.join(
|
|
20970
|
+
os8.tmpdir(),
|
|
20971
|
+
`kody-shell-output-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
20972
|
+
);
|
|
20973
|
+
const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", KODY_OUTPUT: outputFile };
|
|
20467
20974
|
for (const [k, v] of Object.entries(ctx.args)) {
|
|
20468
20975
|
if (v === void 0 || v === null) continue;
|
|
20469
20976
|
env[`KODY_ARG_${envKey(k)}`] = String(v);
|
|
@@ -20529,7 +21036,25 @@ async function runShellEntry(entry, ctx, profile) {
|
|
|
20529
21036
|
ctx.output.reason = `shell '${shellName}' failed to spawn: ${result.spawnErr.message}`;
|
|
20530
21037
|
return;
|
|
20531
21038
|
}
|
|
20532
|
-
|
|
21039
|
+
let sideChannelText = "";
|
|
21040
|
+
try {
|
|
21041
|
+
if (fs46.existsSync(outputFile)) {
|
|
21042
|
+
sideChannelText = fs46.readFileSync(outputFile, "utf-8");
|
|
21043
|
+
fs46.rmSync(outputFile, { force: true });
|
|
21044
|
+
}
|
|
21045
|
+
} catch {
|
|
21046
|
+
}
|
|
21047
|
+
if (sideChannelText.trim().length > 0) {
|
|
21048
|
+
collectShellSideChannels(ctx, sideChannelText);
|
|
21049
|
+
} else {
|
|
21050
|
+
if (SHELL_MARKER_RE.test(stdout)) {
|
|
21051
|
+
process.stderr.write(
|
|
21052
|
+
`[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)
|
|
21053
|
+
`
|
|
21054
|
+
);
|
|
21055
|
+
}
|
|
21056
|
+
collectShellSideChannels(ctx, stdout);
|
|
21057
|
+
}
|
|
20533
21058
|
if (timedOut) {
|
|
20534
21059
|
ctx.skipAgent = true;
|
|
20535
21060
|
const seconds = Math.round(timeoutMs / 1e3);
|
|
@@ -20571,7 +21096,7 @@ function flattenConfig(obj, prefix = "") {
|
|
|
20571
21096
|
}
|
|
20572
21097
|
return out;
|
|
20573
21098
|
}
|
|
20574
|
-
var MUTATING_POSTFLIGHTS, MAX_CHAIN_HOPS, DEFAULT_SHELL_TIMEOUT_MS, SIGKILL_GRACE_MS;
|
|
21099
|
+
var MUTATING_POSTFLIGHTS, SHELL_MARKER_RE, MAX_CHAIN_HOPS, DEFAULT_SHELL_TIMEOUT_MS, SIGKILL_GRACE_MS;
|
|
20575
21100
|
var init_executor = __esm({
|
|
20576
21101
|
"src/executor.ts"() {
|
|
20577
21102
|
"use strict";
|
|
@@ -20602,12 +21127,75 @@ var init_executor = __esm({
|
|
|
20602
21127
|
"publishReport",
|
|
20603
21128
|
"openAgencyModelReviewPr"
|
|
20604
21129
|
]);
|
|
21130
|
+
SHELL_MARKER_RE = /^KODY_(SKIP_AGENT|PR_URL|REASON|CAPABILITY_REPORT|CAPABILITY_RESULT)=/m;
|
|
20605
21131
|
MAX_CHAIN_HOPS = 60;
|
|
20606
21132
|
DEFAULT_SHELL_TIMEOUT_MS = 3e5;
|
|
20607
21133
|
SIGKILL_GRACE_MS = 5e3;
|
|
20608
21134
|
}
|
|
20609
21135
|
});
|
|
20610
21136
|
|
|
21137
|
+
// src/workflowRunState.ts
|
|
21138
|
+
function workflowRunStatePath(workflowId, runId) {
|
|
21139
|
+
if (!SAFE_ID.test(workflowId)) throw new Error(`invalid workflow id ${workflowId}`);
|
|
21140
|
+
if (!SAFE_ID.test(runId)) throw new Error(`invalid workflow run id ${runId}`);
|
|
21141
|
+
return `workflows/${workflowId}/runs/${runId}.json`;
|
|
21142
|
+
}
|
|
21143
|
+
function parseWorkflowRunState(raw) {
|
|
21144
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
21145
|
+
const state = raw;
|
|
21146
|
+
if (state.status !== "running" && state.status !== "blocked" && state.status !== "failed" && state.status !== "done")
|
|
21147
|
+
return null;
|
|
21148
|
+
const completedStepIds = Array.isArray(state.completedStepIds) ? state.completedStepIds.filter((value) => typeof value === "string") : [];
|
|
21149
|
+
const transitionCounts = state.transitionCounts && typeof state.transitionCounts === "object" && !Array.isArray(state.transitionCounts) ? Object.fromEntries(
|
|
21150
|
+
Object.entries(state.transitionCounts).filter(
|
|
21151
|
+
(entry) => typeof entry[1] === "number" && Number.isInteger(entry[1]) && entry[1] >= 0
|
|
21152
|
+
)
|
|
21153
|
+
) : {};
|
|
21154
|
+
const facts = state.facts && typeof state.facts === "object" && !Array.isArray(state.facts) ? state.facts : {};
|
|
21155
|
+
const evidenceEntries = state.evidence && typeof state.evidence === "object" && !Array.isArray(state.evidence) ? Object.entries(state.evidence).filter((entry) => typeof entry[1] === "boolean") : [];
|
|
21156
|
+
const artifacts = Array.isArray(state.artifacts) ? state.artifacts.filter(
|
|
21157
|
+
(artifact) => !!artifact && typeof artifact === "object" && typeof artifact.label === "string" && (artifact.url === void 0 || typeof artifact.url === "string") && (artifact.path === void 0 || typeof artifact.path === "string")
|
|
21158
|
+
) : [];
|
|
21159
|
+
return {
|
|
21160
|
+
status: state.status,
|
|
21161
|
+
...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
|
|
21162
|
+
completedStepIds,
|
|
21163
|
+
transitionCounts,
|
|
21164
|
+
facts: { ...facts },
|
|
21165
|
+
evidence: Object.fromEntries(evidenceEntries),
|
|
21166
|
+
artifacts: artifacts.map((artifact) => ({ ...artifact })),
|
|
21167
|
+
...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
|
|
21168
|
+
};
|
|
21169
|
+
}
|
|
21170
|
+
function readWorkflowRunState(config, cwd, workflowId, runId) {
|
|
21171
|
+
const file = readStateText(config, cwd, workflowRunStatePath(workflowId, runId));
|
|
21172
|
+
if (!file) return null;
|
|
21173
|
+
try {
|
|
21174
|
+
return parseWorkflowRunState(JSON.parse(file.content));
|
|
21175
|
+
} catch {
|
|
21176
|
+
return null;
|
|
21177
|
+
}
|
|
21178
|
+
}
|
|
21179
|
+
function writeWorkflowRunState(config, cwd, workflowId, runId, state) {
|
|
21180
|
+
const path52 = workflowRunStatePath(workflowId, runId);
|
|
21181
|
+
upsertStateText(
|
|
21182
|
+
config,
|
|
21183
|
+
cwd,
|
|
21184
|
+
path52,
|
|
21185
|
+
`${JSON.stringify(state, null, 2)}
|
|
21186
|
+
`,
|
|
21187
|
+
`chore(workflows): update ${workflowId} run ${runId}`
|
|
21188
|
+
);
|
|
21189
|
+
}
|
|
21190
|
+
var SAFE_ID;
|
|
21191
|
+
var init_workflowRunState = __esm({
|
|
21192
|
+
"src/workflowRunState.ts"() {
|
|
21193
|
+
"use strict";
|
|
21194
|
+
init_stateRepo();
|
|
21195
|
+
SAFE_ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
|
|
21196
|
+
}
|
|
21197
|
+
});
|
|
21198
|
+
|
|
20611
21199
|
// src/job.ts
|
|
20612
21200
|
var job_exports = {};
|
|
20613
21201
|
__export(job_exports, {
|
|
@@ -20620,7 +21208,7 @@ __export(job_exports, {
|
|
|
20620
21208
|
stableJobKey: () => stableJobKey,
|
|
20621
21209
|
validateJob: () => validateJob
|
|
20622
21210
|
});
|
|
20623
|
-
import * as
|
|
21211
|
+
import * as path46 from "path";
|
|
20624
21212
|
function newJobId(flavor) {
|
|
20625
21213
|
localJobSeq += 1;
|
|
20626
21214
|
const runId = process.env.GITHUB_RUN_ID;
|
|
@@ -20652,6 +21240,8 @@ function validateJob(input) {
|
|
|
20652
21240
|
target: typeof j.target === "number" ? j.target : void 0,
|
|
20653
21241
|
cliArgs: j.cliArgs ?? {},
|
|
20654
21242
|
workflowFacts: j.workflowFacts && typeof j.workflowFacts === "object" && !Array.isArray(j.workflowFacts) ? j.workflowFacts : void 0,
|
|
21243
|
+
workflowState: parseWorkflowRunState(j.workflowState) ?? void 0,
|
|
21244
|
+
workflowRunId: typeof j.workflowRunId === "string" && j.workflowRunId.trim() ? j.workflowRunId.trim() : void 0,
|
|
20655
21245
|
evidence: parseJobEvidence(j),
|
|
20656
21246
|
flavor: j.flavor,
|
|
20657
21247
|
force: j.force === true,
|
|
@@ -20686,7 +21276,7 @@ function parseJobEvidence(job) {
|
|
|
20686
21276
|
async function runJob(job, base) {
|
|
20687
21277
|
const valid = validateJob(job);
|
|
20688
21278
|
const action = valid.action ?? valid.capability;
|
|
20689
|
-
const projectCapabilitiesRoot =
|
|
21279
|
+
const projectCapabilitiesRoot = path46.join(base.cwd, ".kody", "capabilities");
|
|
20690
21280
|
const resolvedCapability = !valid.workflow && action ? resolveCapabilityAction(action, projectCapabilitiesRoot) : null;
|
|
20691
21281
|
const capabilityIdentity = valid.capability ?? resolvedCapability?.capability;
|
|
20692
21282
|
const capabilityContext = valid.workflow ? null : loadCapabilityContext(capabilityIdentity, base.cwd);
|
|
@@ -20704,8 +21294,17 @@ async function runJob(job, base) {
|
|
|
20704
21294
|
const profileName = explicitImplementation ?? capabilitySelectedImplementation;
|
|
20705
21295
|
if (workflow && shouldRunCapabilityWorkflow(valid, workflow, workflowIdentity, capabilitySelectedImplementation, base)) {
|
|
20706
21296
|
const workflowCapability = capabilityContext ?? workflowContext;
|
|
20707
|
-
const
|
|
20708
|
-
|
|
21297
|
+
const persistedState = valid.workflowRunId && workflowIdentity && base.config ? readWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId) : null;
|
|
21298
|
+
const workflowJob = {
|
|
21299
|
+
...workflowContext && !valid.why ? { ...valid, why: workflowContext.body } : valid,
|
|
21300
|
+
...valid.workflowState ?? persistedState ? { workflowState: valid.workflowState ?? persistedState ?? void 0 } : {}
|
|
21301
|
+
};
|
|
21302
|
+
const checkpoint = valid.workflowRunId && workflowIdentity && base.config ? (state) => writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, state) : void 0;
|
|
21303
|
+
const result = await runCapabilityWorkflow(workflowJob, workflow, workflowCapability, base, checkpoint);
|
|
21304
|
+
if (valid.workflowRunId && workflowIdentity && base.config && result.workflowState) {
|
|
21305
|
+
writeWorkflowRunState(base.config, base.cwd, workflowIdentity, valid.workflowRunId, result.workflowState);
|
|
21306
|
+
}
|
|
21307
|
+
return result;
|
|
20709
21308
|
}
|
|
20710
21309
|
if (!profileName) {
|
|
20711
21310
|
throw new InvalidJobError(`job capability resolves to no implementation: ${capabilityIdentity ?? action}`);
|
|
@@ -20785,7 +21384,22 @@ function shouldRunCapabilityWorkflow(job, workflow, capabilityIdentity, selected
|
|
|
20785
21384
|
if (!requestedImplementation) return true;
|
|
20786
21385
|
return requestedImplementation === selectedImplementation || requestedImplementation === capabilityIdentity || requestedImplementation === job.action;
|
|
20787
21386
|
}
|
|
20788
|
-
async function runCapabilityWorkflow(parent, workflow, capability, base) {
|
|
21387
|
+
async function runCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
|
|
21388
|
+
const invalid = workflowError(workflow, base);
|
|
21389
|
+
if (invalid) {
|
|
21390
|
+
if (isGraphWorkflow(workflow)) {
|
|
21391
|
+
const state = initialWorkflowState(parent, workflow);
|
|
21392
|
+
state.status = "blocked";
|
|
21393
|
+
state.blocker = invalid;
|
|
21394
|
+
checkpoint?.(state);
|
|
21395
|
+
return { exitCode: 64, reason: invalid, workflowState: state };
|
|
21396
|
+
}
|
|
21397
|
+
return { exitCode: 64, reason: invalid };
|
|
21398
|
+
}
|
|
21399
|
+
if (isGraphWorkflow(workflow)) return runGraphCapabilityWorkflow(parent, workflow, capability, base, checkpoint);
|
|
21400
|
+
return runLinearCapabilityWorkflow(parent, workflow, capability, base);
|
|
21401
|
+
}
|
|
21402
|
+
async function runLinearCapabilityWorkflow(parent, workflow, capability, base) {
|
|
20789
21403
|
let chainData = {
|
|
20790
21404
|
...base.preloadedData ?? {},
|
|
20791
21405
|
runSubjectType: "workflow",
|
|
@@ -20849,6 +21463,200 @@ async function runCapabilityWorkflow(parent, workflow, capability, base) {
|
|
|
20849
21463
|
}
|
|
20850
21464
|
return withWorkflowBoundaryEval(capability, result);
|
|
20851
21465
|
}
|
|
21466
|
+
function isGraphWorkflow(workflow) {
|
|
21467
|
+
return workflow.startAt !== void 0 || workflow.steps.some((step) => step.id !== void 0 || step.next !== void 0 || step.inputs !== void 0);
|
|
21468
|
+
}
|
|
21469
|
+
function workflowError(workflow, base) {
|
|
21470
|
+
const projectCapabilitiesRoot = path46.join(base.cwd, ".kody", "capabilities");
|
|
21471
|
+
const knownCapabilities = /* @__PURE__ */ new Set();
|
|
21472
|
+
const capabilityInputs = /* @__PURE__ */ new Map();
|
|
21473
|
+
for (const step of workflow.steps) {
|
|
21474
|
+
const action = step.action ?? step.capability;
|
|
21475
|
+
const resolvedAction = resolveCapabilityAction(action, projectCapabilitiesRoot);
|
|
21476
|
+
const resolvedFolder = resolveCapabilityFolder(step.capability, projectCapabilitiesRoot);
|
|
21477
|
+
if (!resolvedAction && !resolvedFolder) continue;
|
|
21478
|
+
knownCapabilities.add(step.capability);
|
|
21479
|
+
const inputs = getCapabilityActionInputs(action, projectCapabilitiesRoot);
|
|
21480
|
+
if (inputs) {
|
|
21481
|
+
capabilityInputs.set(
|
|
21482
|
+
step.capability,
|
|
21483
|
+
new Set(inputs.flatMap((input) => [input.name, input.flag.replace(/^--/, "")]))
|
|
21484
|
+
);
|
|
21485
|
+
}
|
|
21486
|
+
}
|
|
21487
|
+
return formatWorkflowValidationIssues(validateWorkflow(workflow, { knownCapabilities, capabilityInputs }))[0] ?? null;
|
|
21488
|
+
}
|
|
21489
|
+
function initialWorkflowState(parent, workflow) {
|
|
21490
|
+
const prior = parent.workflowState;
|
|
21491
|
+
if (prior?.status === "done") {
|
|
21492
|
+
return {
|
|
21493
|
+
...prior,
|
|
21494
|
+
status: "done",
|
|
21495
|
+
completedStepIds: [...prior.completedStepIds],
|
|
21496
|
+
transitionCounts: { ...prior.transitionCounts },
|
|
21497
|
+
facts: { ...prior.facts },
|
|
21498
|
+
evidence: { ...prior.evidence },
|
|
21499
|
+
artifacts: prior.artifacts.map((artifact) => ({ ...artifact }))
|
|
21500
|
+
};
|
|
21501
|
+
}
|
|
21502
|
+
const firstStepId = workflow.startAt ?? workflow.steps[0]?.id;
|
|
21503
|
+
const currentStepId = prior?.currentStepId ?? firstStepId;
|
|
21504
|
+
return {
|
|
21505
|
+
status: "running",
|
|
21506
|
+
...currentStepId ? { currentStepId } : {},
|
|
21507
|
+
completedStepIds: [...prior?.completedStepIds ?? []],
|
|
21508
|
+
transitionCounts: { ...prior?.transitionCounts ?? {} },
|
|
21509
|
+
facts: { ...parent.workflowFacts ?? {}, ...prior?.facts ?? {} },
|
|
21510
|
+
evidence: { ...prior?.evidence ?? {} },
|
|
21511
|
+
artifacts: (prior?.artifacts ?? []).map((artifact) => ({ ...artifact }))
|
|
21512
|
+
};
|
|
21513
|
+
}
|
|
21514
|
+
function workflowChainData(parent, capability, base, state) {
|
|
21515
|
+
return {
|
|
21516
|
+
...base.preloadedData ?? {},
|
|
21517
|
+
runSubjectType: "workflow",
|
|
21518
|
+
runSubjectId: capability.slug,
|
|
21519
|
+
runSubjectLabel: capability.title,
|
|
21520
|
+
runSubjectWorkflow: capability.slug,
|
|
21521
|
+
workflowCapability: capability.slug,
|
|
21522
|
+
workflowTitle: capability.title,
|
|
21523
|
+
workflowStepCount: capability.config.workflow?.steps.length ?? 0,
|
|
21524
|
+
workflowIssueNumber: workflowIssueNumber(parent),
|
|
21525
|
+
workflowFacts: state.facts,
|
|
21526
|
+
workflowEvidence: state.evidence,
|
|
21527
|
+
workflowArtifacts: state.artifacts,
|
|
21528
|
+
workflowStack: [
|
|
21529
|
+
...Array.isArray(base.preloadedData?.workflowStack) ? base.preloadedData.workflowStack.filter((entry) => typeof entry === "string") : [],
|
|
21530
|
+
capability.slug
|
|
21531
|
+
]
|
|
21532
|
+
};
|
|
21533
|
+
}
|
|
21534
|
+
async function runGraphCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
|
|
21535
|
+
const state = initialWorkflowState(parent, workflow);
|
|
21536
|
+
let chainData = workflowChainData(parent, capability, base, state);
|
|
21537
|
+
let result = { exitCode: 0 };
|
|
21538
|
+
let executedSteps = 0;
|
|
21539
|
+
const maxExecutedSteps = 1e3;
|
|
21540
|
+
while (state.currentStepId) {
|
|
21541
|
+
executedSteps += 1;
|
|
21542
|
+
if (executedSteps > maxExecutedSteps) {
|
|
21543
|
+
const reason = `workflow ${capability.slug} exceeded ${maxExecutedSteps} executed steps`;
|
|
21544
|
+
state.status = "blocked";
|
|
21545
|
+
state.blocker = reason;
|
|
21546
|
+
checkpoint?.(state);
|
|
21547
|
+
return { ...result, exitCode: 64, reason, workflowState: state };
|
|
21548
|
+
}
|
|
21549
|
+
const index = workflow.steps.findIndex((step2) => step2.id === state.currentStepId);
|
|
21550
|
+
const step = workflow.steps[index];
|
|
21551
|
+
if (!step) {
|
|
21552
|
+
const reason = `workflow ${capability.slug} current step ${state.currentStepId} is missing`;
|
|
21553
|
+
state.status = "blocked";
|
|
21554
|
+
state.blocker = reason;
|
|
21555
|
+
checkpoint?.(state);
|
|
21556
|
+
return { ...result, exitCode: 64, reason, workflowState: state };
|
|
21557
|
+
}
|
|
21558
|
+
const label = step.action ?? step.capability;
|
|
21559
|
+
checkpoint?.(state);
|
|
21560
|
+
let child;
|
|
21561
|
+
try {
|
|
21562
|
+
child = workflowStepToJob(step, parent, chainData);
|
|
21563
|
+
} catch (error) {
|
|
21564
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
21565
|
+
state.status = "blocked";
|
|
21566
|
+
state.blocker = reason;
|
|
21567
|
+
checkpoint?.(state);
|
|
21568
|
+
return { exitCode: 64, reason, workflowState: state };
|
|
21569
|
+
}
|
|
21570
|
+
process.stdout.write(
|
|
21571
|
+
`\u2192 kody: workflow ${capability.slug} step ${index + 1}/${workflow.steps.length} \u2192 ${label}
|
|
21572
|
+
|
|
21573
|
+
`
|
|
21574
|
+
);
|
|
21575
|
+
result = await runJob(child, {
|
|
21576
|
+
...base,
|
|
21577
|
+
preloadedData: {
|
|
21578
|
+
...chainData,
|
|
21579
|
+
workflowStep: step.id,
|
|
21580
|
+
workflowStepIndex: index + 1,
|
|
21581
|
+
workflowStepReason: step.reason,
|
|
21582
|
+
workflowContinueOn: step.continueOn ?? []
|
|
21583
|
+
}
|
|
21584
|
+
});
|
|
21585
|
+
mergeWorkflowResults(state, result.capabilityResults);
|
|
21586
|
+
const outcome = workflowOutcome(result);
|
|
21587
|
+
const prUrl = result.prUrl ?? result.taskState?.core.prUrl ?? (typeof chainData.workflowPrUrl === "string" ? chainData.workflowPrUrl : void 0);
|
|
21588
|
+
chainData = {
|
|
21589
|
+
...workflowChainData(parent, capability, base, state),
|
|
21590
|
+
...result.taskState ? { taskState: result.taskState } : {},
|
|
21591
|
+
...outcome ? { workflowLastOutcome: outcome } : {},
|
|
21592
|
+
...result.capabilityResults?.at(-1) ? { workflowLastResult: result.capabilityResults.at(-1) } : {},
|
|
21593
|
+
...prUrl ? { workflowPrUrl: prUrl } : {},
|
|
21594
|
+
...parsePrNumber5(prUrl) ? { workflowPrNumber: parsePrNumber5(prUrl) } : {}
|
|
21595
|
+
};
|
|
21596
|
+
if (!state.completedStepIds.includes(step.id)) state.completedStepIds.push(step.id);
|
|
21597
|
+
if (result.exitCode !== 0 && !canContinueWorkflow(step, outcome)) {
|
|
21598
|
+
state.status = "failed";
|
|
21599
|
+
state.blocker = result.reason ?? `workflow step ${step.id} failed`;
|
|
21600
|
+
checkpoint?.(state);
|
|
21601
|
+
return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
|
|
21602
|
+
}
|
|
21603
|
+
if (!step.next || step.next.length === 0) {
|
|
21604
|
+
state.status = "done";
|
|
21605
|
+
delete state.currentStepId;
|
|
21606
|
+
delete state.blocker;
|
|
21607
|
+
checkpoint?.(state);
|
|
21608
|
+
return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
|
|
21609
|
+
}
|
|
21610
|
+
const transition = selectWorkflowTransition(step, chainData, state.transitionCounts);
|
|
21611
|
+
if (!transition) {
|
|
21612
|
+
const reason = `workflow step ${step.id} has no available connection`;
|
|
21613
|
+
state.status = "blocked";
|
|
21614
|
+
state.blocker = reason;
|
|
21615
|
+
checkpoint?.(state);
|
|
21616
|
+
return { ...result, exitCode: 64, reason, workflowState: state };
|
|
21617
|
+
}
|
|
21618
|
+
if (transition.maxIterations !== void 0) {
|
|
21619
|
+
const key = `${step.id}->${transition.to}`;
|
|
21620
|
+
state.transitionCounts[key] = (state.transitionCounts[key] ?? 0) + 1;
|
|
21621
|
+
}
|
|
21622
|
+
state.currentStepId = transition.to;
|
|
21623
|
+
state.status = "running";
|
|
21624
|
+
delete state.blocker;
|
|
21625
|
+
checkpoint?.(state);
|
|
21626
|
+
}
|
|
21627
|
+
state.status = "done";
|
|
21628
|
+
checkpoint?.(state);
|
|
21629
|
+
return withWorkflowBoundaryEval(capability, { ...result, workflowState: state });
|
|
21630
|
+
}
|
|
21631
|
+
function mergeWorkflowResults(state, results) {
|
|
21632
|
+
for (const result of results ?? []) {
|
|
21633
|
+
Object.assign(state.facts, result.facts);
|
|
21634
|
+
Object.assign(state.evidence, result.evidence ?? {});
|
|
21635
|
+
for (const artifact of result.artifacts) {
|
|
21636
|
+
if (!state.artifacts.some(
|
|
21637
|
+
(existing) => existing.label === artifact.label && existing.url === artifact.url && existing.path === artifact.path
|
|
21638
|
+
)) {
|
|
21639
|
+
state.artifacts.push({ ...artifact });
|
|
21640
|
+
}
|
|
21641
|
+
}
|
|
21642
|
+
}
|
|
21643
|
+
}
|
|
21644
|
+
function selectWorkflowTransition(step, data, counts) {
|
|
21645
|
+
let fallback = null;
|
|
21646
|
+
for (const transition of step.next ?? []) {
|
|
21647
|
+
const key = `${step.id}->${transition.to}`;
|
|
21648
|
+
if (transition.maxIterations !== void 0 && (counts[key] ?? 0) >= transition.maxIterations) continue;
|
|
21649
|
+
if (transition.default === true) {
|
|
21650
|
+
fallback ??= transition;
|
|
21651
|
+
continue;
|
|
21652
|
+
}
|
|
21653
|
+
if (!transition.when || conditionMatches(transition.when, workflowConditionContext(data))) return transition;
|
|
21654
|
+
}
|
|
21655
|
+
return fallback;
|
|
21656
|
+
}
|
|
21657
|
+
function conditionMatches(condition, context) {
|
|
21658
|
+
return Object.entries(condition).every(([path52, expected]) => valueMatches(resolveDottedPath2(context, path52), expected));
|
|
21659
|
+
}
|
|
20852
21660
|
function withWorkflowBoundaryEval(capability, result) {
|
|
20853
21661
|
const capabilityKind = capability.config.capabilityKind;
|
|
20854
21662
|
if (!capabilityKind) return result;
|
|
@@ -20869,8 +21677,18 @@ function withWorkflowBoundaryEval(capability, result) {
|
|
|
20869
21677
|
}
|
|
20870
21678
|
function workflowStepToJob(step, parent, chainData) {
|
|
20871
21679
|
const action = step.action ?? step.capability;
|
|
21680
|
+
const mappedArgs = {};
|
|
21681
|
+
const conditionContext = workflowConditionContext(chainData);
|
|
21682
|
+
for (const [name, mapping] of Object.entries(step.inputs ?? {})) {
|
|
21683
|
+
const value = resolveDottedPath2(conditionContext, mapping.from);
|
|
21684
|
+
if (value === void 0) {
|
|
21685
|
+
throw new InvalidJobError(`workflow step ${step.id ?? action} needs missing input ${mapping.from}`);
|
|
21686
|
+
}
|
|
21687
|
+
mappedArgs[name] = value;
|
|
21688
|
+
}
|
|
20872
21689
|
const rawArgs = {
|
|
20873
21690
|
...parent.cliArgs,
|
|
21691
|
+
...mappedArgs,
|
|
20874
21692
|
...step.cliArgs ?? {}
|
|
20875
21693
|
};
|
|
20876
21694
|
const targetNumber = workflowStepTargetNumber(step, parent, chainData);
|
|
@@ -20904,9 +21722,7 @@ function workflowStepToJob(step, parent, chainData) {
|
|
|
20904
21722
|
function shouldRunWorkflowStep(step, data) {
|
|
20905
21723
|
if (!step.runWhen) return true;
|
|
20906
21724
|
const context = workflowConditionContext(data);
|
|
20907
|
-
return
|
|
20908
|
-
([path51, expected]) => valueMatches(resolveDottedPath2(context, path51), expected)
|
|
20909
|
-
);
|
|
21725
|
+
return conditionMatches(step.runWhen, context);
|
|
20910
21726
|
}
|
|
20911
21727
|
function canContinueWorkflow(step, outcome) {
|
|
20912
21728
|
if (!outcome || !step.continueOn || step.continueOn.length === 0) return false;
|
|
@@ -20917,10 +21733,16 @@ function workflowOutcome(result) {
|
|
|
20917
21733
|
}
|
|
20918
21734
|
function workflowConditionContext(data) {
|
|
20919
21735
|
const lastOutcome = data.workflowLastOutcome;
|
|
21736
|
+
const lastResult = data.workflowLastResult;
|
|
20920
21737
|
return {
|
|
20921
21738
|
...data,
|
|
21739
|
+
facts: data.workflowFacts ?? {},
|
|
21740
|
+
evidence: data.workflowEvidence ?? {},
|
|
21741
|
+
artifacts: data.workflowArtifacts ?? [],
|
|
21742
|
+
result: lastResult,
|
|
20922
21743
|
workflow: {
|
|
20923
21744
|
lastOutcome,
|
|
21745
|
+
lastResult,
|
|
20924
21746
|
issueNumber: data.workflowIssueNumber,
|
|
20925
21747
|
prNumber: data.workflowPrNumber,
|
|
20926
21748
|
prUrl: data.workflowPrUrl
|
|
@@ -20988,7 +21810,7 @@ function composeStepWhy(parentWhy, step) {
|
|
|
20988
21810
|
}
|
|
20989
21811
|
function loadCapabilityContext(slug, cwd) {
|
|
20990
21812
|
if (!slug) return null;
|
|
20991
|
-
return resolveCapabilityFolder(slug,
|
|
21813
|
+
return resolveCapabilityFolder(slug, path46.join(cwd, ".kody", "capabilities"));
|
|
20992
21814
|
}
|
|
20993
21815
|
function loadWorkflowContext(slug, base) {
|
|
20994
21816
|
if (!slug || !base.config || !isWorkflowDefinitionId(slug)) return null;
|
|
@@ -21027,6 +21849,8 @@ var init_job = __esm({
|
|
|
21027
21849
|
init_executor();
|
|
21028
21850
|
init_registry();
|
|
21029
21851
|
init_workflowDefinitions();
|
|
21852
|
+
init_workflowRunState();
|
|
21853
|
+
init_workflowValidation();
|
|
21030
21854
|
init_jobIdentity();
|
|
21031
21855
|
init_jobIdentity();
|
|
21032
21856
|
DEFAULT_INSTANT_AGENT = "kody";
|
|
@@ -21149,7 +21973,7 @@ function translateOpenAISseToBrain(opts) {
|
|
|
21149
21973
|
// src/servers/brain-serve.ts
|
|
21150
21974
|
import * as fs49 from "fs";
|
|
21151
21975
|
import { createServer as createServer2 } from "http";
|
|
21152
|
-
import * as
|
|
21976
|
+
import * as path49 from "path";
|
|
21153
21977
|
|
|
21154
21978
|
// src/chat/loop.ts
|
|
21155
21979
|
init_agent();
|
|
@@ -21679,8 +22503,8 @@ async function runOpenAIChatTurn(args) {
|
|
|
21679
22503
|
})
|
|
21680
22504
|
});
|
|
21681
22505
|
if (!response.ok) {
|
|
21682
|
-
const
|
|
21683
|
-
const error = `OpenAI-compatible model request failed ${response.status}${
|
|
22506
|
+
const text2 = await response.text().catch(() => "");
|
|
22507
|
+
const error = `OpenAI-compatible model request failed ${response.status}${text2 ? `: ${text2.slice(0, 500)}` : ""}`;
|
|
21684
22508
|
await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
|
|
21685
22509
|
return { exitCode: 99, error };
|
|
21686
22510
|
}
|
|
@@ -21718,8 +22542,8 @@ function extractOpenAIReply(payload) {
|
|
|
21718
22542
|
return content.map((part) => {
|
|
21719
22543
|
if (typeof part === "string") return part;
|
|
21720
22544
|
if (part && typeof part === "object" && "text" in part) {
|
|
21721
|
-
const
|
|
21722
|
-
return typeof
|
|
22545
|
+
const text2 = part.text;
|
|
22546
|
+
return typeof text2 === "string" ? text2 : "";
|
|
21723
22547
|
}
|
|
21724
22548
|
return "";
|
|
21725
22549
|
}).join("");
|
|
@@ -21837,7 +22661,7 @@ init_config();
|
|
|
21837
22661
|
// src/kody-cli.ts
|
|
21838
22662
|
import { execFileSync as execFileSync26 } from "child_process";
|
|
21839
22663
|
import * as fs47 from "fs";
|
|
21840
|
-
import * as
|
|
22664
|
+
import * as path47 from "path";
|
|
21841
22665
|
|
|
21842
22666
|
// src/app-auth.ts
|
|
21843
22667
|
import { createSign } from "crypto";
|
|
@@ -22108,7 +22932,7 @@ function autoDispatch(opts) {
|
|
|
22108
22932
|
}
|
|
22109
22933
|
if (eventName !== "issue_comment") return null;
|
|
22110
22934
|
const comment = objectValue(event.comment);
|
|
22111
|
-
const
|
|
22935
|
+
const issue2 = objectValue(event.issue);
|
|
22112
22936
|
const user = objectValue(comment?.user);
|
|
22113
22937
|
const rawBody = String(comment?.body ?? "");
|
|
22114
22938
|
const authorLogin = String(user?.login ?? "");
|
|
@@ -22116,9 +22940,9 @@ function autoDispatch(opts) {
|
|
|
22116
22940
|
if (!hasKodyMention(rawBody)) return null;
|
|
22117
22941
|
const isBotAuthor = authorLogin === "kody-bot" || authorType === "Bot";
|
|
22118
22942
|
if (!associationAllowed(event, opts?.config)) return null;
|
|
22119
|
-
const body = rawBody
|
|
22120
|
-
const targetNum = Number(
|
|
22121
|
-
const isPr = !!
|
|
22943
|
+
const body = rawBody;
|
|
22944
|
+
const targetNum = Number(issue2?.number ?? 0);
|
|
22945
|
+
const isPr = !!issue2?.pull_request;
|
|
22122
22946
|
if (!targetNum) return null;
|
|
22123
22947
|
const afterTag = extractAfterTag(body);
|
|
22124
22948
|
const firstTokenRaw = extractSubcommand(afterTag);
|
|
@@ -22202,7 +23026,7 @@ function autoDispatchTyped(opts) {
|
|
|
22202
23026
|
return { kind: "silent", reason: "GHA event payload unreadable" };
|
|
22203
23027
|
}
|
|
22204
23028
|
const comment = objectValue(event.comment);
|
|
22205
|
-
const
|
|
23029
|
+
const issue2 = objectValue(event.issue);
|
|
22206
23030
|
const user = objectValue(comment?.user);
|
|
22207
23031
|
const rawBody = String(comment?.body ?? "");
|
|
22208
23032
|
const authorLogin = String(user?.login ?? "");
|
|
@@ -22210,12 +23034,12 @@ function autoDispatchTyped(opts) {
|
|
|
22210
23034
|
if (!hasKodyMention(rawBody)) {
|
|
22211
23035
|
return { kind: "silent", reason: "comment does not mention @kody" };
|
|
22212
23036
|
}
|
|
22213
|
-
const targetNum = Number(
|
|
22214
|
-
const isPr = !!
|
|
23037
|
+
const targetNum = Number(issue2?.number ?? 0);
|
|
23038
|
+
const isPr = !!issue2?.pull_request;
|
|
22215
23039
|
if (!targetNum) {
|
|
22216
23040
|
return { kind: "silent", reason: "comment has no associated issue/PR number" };
|
|
22217
23041
|
}
|
|
22218
|
-
const afterTag = extractAfterTag(rawBody
|
|
23042
|
+
const afterTag = extractAfterTag(rawBody);
|
|
22219
23043
|
const tokenRaw = extractSubcommand(afterTag) ?? "";
|
|
22220
23044
|
if ((authorLogin === "kody-bot" || authorType === "Bot") && tokenRaw && !POLITE_WORDS.has(tokenRaw)) {
|
|
22221
23045
|
return {
|
|
@@ -22315,12 +23139,12 @@ function hasKodyMention(body) {
|
|
|
22315
23139
|
function extractAfterTag(body) {
|
|
22316
23140
|
const m = body.match(KODY_MENTION_RE);
|
|
22317
23141
|
if (!m || m.index === void 0) return "";
|
|
22318
|
-
const at = body.indexOf("@kody", m.index);
|
|
23142
|
+
const at = body.toLowerCase().indexOf("@kody", m.index);
|
|
22319
23143
|
return body.slice(at + "@kody".length).trim();
|
|
22320
23144
|
}
|
|
22321
23145
|
function extractSubcommand(afterTag) {
|
|
22322
|
-
const match = afterTag.match(/^([a-
|
|
22323
|
-
return match ? match[1] : null;
|
|
23146
|
+
const match = afterTag.match(/^([a-zA-Z][a-zA-Z0-9-]{1,40})\b/);
|
|
23147
|
+
return match ? match[1].toLowerCase() : null;
|
|
22324
23148
|
}
|
|
22325
23149
|
function extractCommentRest(afterTag, consumedToken) {
|
|
22326
23150
|
let rest = afterTag;
|
|
@@ -22339,7 +23163,7 @@ function parseCommentArgs(rest, inputs) {
|
|
|
22339
23163
|
const t = tokens[i];
|
|
22340
23164
|
if (t.startsWith("--")) {
|
|
22341
23165
|
const eq = t.indexOf("=");
|
|
22342
|
-
const key = eq >= 0 ? t.slice(2, eq) : t.slice(2);
|
|
23166
|
+
const key = (eq >= 0 ? t.slice(2, eq) : t.slice(2)).toLowerCase();
|
|
22343
23167
|
const inlineValue = eq >= 0 ? t.slice(eq + 1) : void 0;
|
|
22344
23168
|
const spec = findInputByFlag(inputs, key);
|
|
22345
23169
|
if (!spec) {
|
|
@@ -22359,9 +23183,12 @@ function parseCommentArgs(rest, inputs) {
|
|
|
22359
23183
|
if (inlineValue === void 0) i++;
|
|
22360
23184
|
continue;
|
|
22361
23185
|
}
|
|
22362
|
-
const
|
|
23186
|
+
const tLower = t.toLowerCase();
|
|
23187
|
+
const enumHit = inputs.find(
|
|
23188
|
+
(s) => s.type === "enum" && s.values?.some((v) => v.toLowerCase() === tLower) && args[s.name] === void 0
|
|
23189
|
+
);
|
|
22363
23190
|
if (enumHit) {
|
|
22364
|
-
args[enumHit.name] =
|
|
23191
|
+
args[enumHit.name] = enumHit.values.find((v) => v.toLowerCase() === tLower);
|
|
22365
23192
|
continue;
|
|
22366
23193
|
}
|
|
22367
23194
|
if (/^-?\d+$/.test(t)) {
|
|
@@ -22371,7 +23198,7 @@ function parseCommentArgs(rest, inputs) {
|
|
|
22371
23198
|
continue;
|
|
22372
23199
|
}
|
|
22373
23200
|
}
|
|
22374
|
-
const boolHit = inputs.find((s) => s.type === "bool" && s.flag === `--${
|
|
23201
|
+
const boolHit = inputs.find((s) => s.type === "bool" && s.flag === `--${tLower}` && args[s.name] === void 0);
|
|
22375
23202
|
if (boolHit) {
|
|
22376
23203
|
args[boolHit.name] = true;
|
|
22377
23204
|
continue;
|
|
@@ -22552,7 +23379,8 @@ function routeRunRequest(request) {
|
|
|
22552
23379
|
if (intent !== "run" && intent !== "tick") {
|
|
22553
23380
|
return { kind: "error", error: `workflow target does not support intent '${intent}'` };
|
|
22554
23381
|
}
|
|
22555
|
-
|
|
23382
|
+
const workflowRunId = typeof request.input?.runId === "string" && /^[a-z0-9][a-z0-9_-]{0,79}$/.test(request.input.runId) ? request.input.runId : void 0;
|
|
23383
|
+
return { kind: "action", action: target.id, cliArgs: {}, ...workflowRunId ? { workflowRunId } : {} };
|
|
22556
23384
|
}
|
|
22557
23385
|
return { kind: "error", error: "unsupported run request target" };
|
|
22558
23386
|
}
|
|
@@ -22631,9 +23459,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
22631
23459
|
return void 0;
|
|
22632
23460
|
}
|
|
22633
23461
|
function detectPackageManager2(cwd) {
|
|
22634
|
-
if (fs47.existsSync(
|
|
22635
|
-
if (fs47.existsSync(
|
|
22636
|
-
if (fs47.existsSync(
|
|
23462
|
+
if (fs47.existsSync(path47.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
23463
|
+
if (fs47.existsSync(path47.join(cwd, "yarn.lock"))) return "yarn";
|
|
23464
|
+
if (fs47.existsSync(path47.join(cwd, "bun.lockb"))) return "bun";
|
|
22637
23465
|
return "npm";
|
|
22638
23466
|
}
|
|
22639
23467
|
function shouldChainScheduledWatch(match) {
|
|
@@ -22756,7 +23584,7 @@ async function runCi(argv) {
|
|
|
22756
23584
|
return 0;
|
|
22757
23585
|
}
|
|
22758
23586
|
const args = parseCiArgs(argv);
|
|
22759
|
-
const cwd = args.cwd ?
|
|
23587
|
+
const cwd = args.cwd ? path47.resolve(args.cwd) : process.cwd();
|
|
22760
23588
|
try {
|
|
22761
23589
|
const n = unpackAllSecrets();
|
|
22762
23590
|
if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
|
|
@@ -22780,6 +23608,7 @@ async function runCi(argv) {
|
|
|
22780
23608
|
let manualWorkflowDispatch = false;
|
|
22781
23609
|
let forceRunAction = null;
|
|
22782
23610
|
let forceRunCliArgs = {};
|
|
23611
|
+
let forceWorkflowRunId;
|
|
22783
23612
|
let runRequestFanOut = false;
|
|
22784
23613
|
let runRequestFanOutForce = false;
|
|
22785
23614
|
const parsedRunRequest = readRunRequestFromEnv();
|
|
@@ -22804,6 +23633,7 @@ async function runCi(argv) {
|
|
|
22804
23633
|
} else if (route.kind === "action") {
|
|
22805
23634
|
forceRunAction = route.action;
|
|
22806
23635
|
forceRunCliArgs = route.cliArgs;
|
|
23636
|
+
forceWorkflowRunId = route.workflowRunId;
|
|
22807
23637
|
}
|
|
22808
23638
|
}
|
|
22809
23639
|
const envForceAction = (process.env.KODY_FORCE_ACTION ?? "").trim();
|
|
@@ -22901,6 +23731,7 @@ async function runCi(argv) {
|
|
|
22901
23731
|
capability: route.capability,
|
|
22902
23732
|
workflow: route.workflow,
|
|
22903
23733
|
implementation: route.implementation,
|
|
23734
|
+
workflowRunId: forceWorkflowRunId,
|
|
22904
23735
|
cliArgs: { ...route.cliArgs, ...forceRunCliArgs },
|
|
22905
23736
|
flavor: "instant",
|
|
22906
23737
|
force: true
|
|
@@ -23190,7 +24021,7 @@ init_repoWorkspace();
|
|
|
23190
24021
|
// src/scripts/brainTurnLog.ts
|
|
23191
24022
|
init_runtimePaths();
|
|
23192
24023
|
import * as fs48 from "fs";
|
|
23193
|
-
import * as
|
|
24024
|
+
import * as path48 from "path";
|
|
23194
24025
|
import posixPath4 from "path/posix";
|
|
23195
24026
|
var live = /* @__PURE__ */ new Map();
|
|
23196
24027
|
function brainEventsFilePath(dir, chatId) {
|
|
@@ -23240,7 +24071,7 @@ function beginTurn(dir, chatId) {
|
|
|
23240
24071
|
};
|
|
23241
24072
|
live.set(chatId, state);
|
|
23242
24073
|
const p = brainEventsFilePath(dir, chatId);
|
|
23243
|
-
fs48.mkdirSync(
|
|
24074
|
+
fs48.mkdirSync(path48.dirname(p), { recursive: true });
|
|
23244
24075
|
return (event) => {
|
|
23245
24076
|
state.seq += 1;
|
|
23246
24077
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
@@ -23618,7 +24449,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
23618
24449
|
);
|
|
23619
24450
|
}
|
|
23620
24451
|
}
|
|
23621
|
-
fs49.mkdirSync(
|
|
24452
|
+
fs49.mkdirSync(path49.dirname(sessionFile), { recursive: true });
|
|
23622
24453
|
appendTurn(sessionFile, {
|
|
23623
24454
|
role: "user",
|
|
23624
24455
|
content: message,
|
|
@@ -23693,7 +24524,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
23693
24524
|
function buildServer(opts) {
|
|
23694
24525
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
23695
24526
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
23696
|
-
const reposRoot = opts.reposRoot ??
|
|
24527
|
+
const reposRoot = opts.reposRoot ?? path49.join(path49.dirname(path49.resolve(opts.cwd)), "repos");
|
|
23697
24528
|
return createServer2(async (req, res) => {
|
|
23698
24529
|
if (!req.method || !req.url) {
|
|
23699
24530
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -24297,7 +25128,7 @@ async function loadConfigSafe() {
|
|
|
24297
25128
|
|
|
24298
25129
|
// src/chat-cli.ts
|
|
24299
25130
|
import * as fs51 from "fs";
|
|
24300
|
-
import * as
|
|
25131
|
+
import * as path51 from "path";
|
|
24301
25132
|
|
|
24302
25133
|
// src/chat/inbox.ts
|
|
24303
25134
|
import { execFileSync as execFileSync27 } from "child_process";
|
|
@@ -24370,9 +25201,9 @@ function currentBranch(cwd) {
|
|
|
24370
25201
|
// src/chat/state-sync.ts
|
|
24371
25202
|
init_stateRepo();
|
|
24372
25203
|
import * as fs50 from "fs";
|
|
24373
|
-
import * as
|
|
24374
|
-
function jsonlLines2(
|
|
24375
|
-
return
|
|
25204
|
+
import * as path50 from "path";
|
|
25205
|
+
function jsonlLines2(text2) {
|
|
25206
|
+
return text2.split("\n").filter((line) => line.length > 0);
|
|
24376
25207
|
}
|
|
24377
25208
|
function renderJsonl2(lines) {
|
|
24378
25209
|
return lines.length > 0 ? `${lines.join("\n")}
|
|
@@ -24390,7 +25221,7 @@ function syncJsonlFileFromState(opts) {
|
|
|
24390
25221
|
const local = fs50.existsSync(opts.localPath) ? fs50.readFileSync(opts.localPath, "utf-8") : "";
|
|
24391
25222
|
const next = mergeJsonl2(local, remote.content);
|
|
24392
25223
|
if (next === local) return;
|
|
24393
|
-
fs50.mkdirSync(
|
|
25224
|
+
fs50.mkdirSync(path50.dirname(opts.localPath), { recursive: true });
|
|
24394
25225
|
fs50.writeFileSync(opts.localPath, next);
|
|
24395
25226
|
}
|
|
24396
25227
|
function persistJsonlFileToState(opts) {
|
|
@@ -24660,7 +25491,7 @@ async function runChat(argv) {
|
|
|
24660
25491
|
${CHAT_HELP}`);
|
|
24661
25492
|
return 64;
|
|
24662
25493
|
}
|
|
24663
|
-
const cwd = args.cwd ?
|
|
25494
|
+
const cwd = args.cwd ? path51.resolve(args.cwd) : process.cwd();
|
|
24664
25495
|
const sessionId = args.sessionId;
|
|
24665
25496
|
const runRequest = readRunRequestFromEnv();
|
|
24666
25497
|
if (runRequest && "request" in runRequest) {
|
|
@@ -24840,8 +25671,8 @@ var FlyClient = class {
|
|
|
24840
25671
|
get fetch() {
|
|
24841
25672
|
return this.opts.fetchImpl ?? fetch;
|
|
24842
25673
|
}
|
|
24843
|
-
async call(
|
|
24844
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
25674
|
+
async call(path52, init = {}) {
|
|
25675
|
+
const res = await this.fetch(`${FLY_API_BASE}${path52}`, {
|
|
24845
25676
|
method: init.method ?? "GET",
|
|
24846
25677
|
headers: {
|
|
24847
25678
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -24851,8 +25682,8 @@ var FlyClient = class {
|
|
|
24851
25682
|
});
|
|
24852
25683
|
if (res.status === 404 && init.allow404) return null;
|
|
24853
25684
|
if (!res.ok) {
|
|
24854
|
-
const
|
|
24855
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
25685
|
+
const text2 = await res.text().catch(() => "");
|
|
25686
|
+
throw new Error(`Fly API ${res.status} on ${path52}: ${text2.slice(0, 200) || res.statusText}`);
|
|
24856
25687
|
}
|
|
24857
25688
|
if (res.status === 204) return null;
|
|
24858
25689
|
const raw = await res.text();
|
|
@@ -26199,11 +27030,11 @@ function envRunMode(env = process.env) {
|
|
|
26199
27030
|
return { ...result, command: "ci", ciArgv: [] };
|
|
26200
27031
|
}
|
|
26201
27032
|
if (mode === "issue") {
|
|
26202
|
-
const
|
|
26203
|
-
if (!
|
|
27033
|
+
const issue2 = (env.ISSUE_NUMBER ?? "").trim();
|
|
27034
|
+
if (!issue2) {
|
|
26204
27035
|
return { ...result, errors: ["KODY_RUN_MODE=issue requires ISSUE_NUMBER"] };
|
|
26205
27036
|
}
|
|
26206
|
-
return { ...result, command: "ci", ciArgv: ["--issue",
|
|
27037
|
+
return { ...result, command: "ci", ciArgv: ["--issue", issue2] };
|
|
26207
27038
|
}
|
|
26208
27039
|
return { ...result, errors: [`unknown KODY_RUN_MODE: ${mode}`] };
|
|
26209
27040
|
}
|