@kody-ade/kody-engine 0.4.221 → 0.4.223
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/README.md +7 -5
- package/dist/bin/kody.js +767 -661
- package/dist/duties/duty-scheduler/duty.md +3 -0
- package/dist/duties/duty-scheduler/profile.json +6 -0
- package/dist/duties/duty-tick/duty.md +3 -0
- package/dist/duties/duty-tick/profile.json +6 -0
- package/dist/duties/duty-tick-scripted/duty.md +3 -0
- package/dist/duties/duty-tick-scripted/profile.json +6 -0
- package/dist/duties/goal-scheduler/duty.md +3 -0
- package/dist/duties/goal-scheduler/profile.json +6 -0
- package/dist/duties/goal-tick/duty.md +3 -0
- package/dist/duties/goal-tick/profile.json +6 -0
- package/dist/duties/job-live-verify/duty.md +3 -0
- package/dist/duties/job-live-verify/profile.json +6 -0
- package/dist/duties/plan-verify/duty.md +3 -0
- package/dist/duties/plan-verify/profile.json +6 -0
- package/dist/duties/probe-skill/duty.md +3 -0
- package/dist/duties/probe-skill/profile.json +6 -0
- package/dist/duties/qa-goal/duty.md +3 -0
- package/dist/duties/qa-goal/profile.json +6 -0
- package/dist/duties/task-job-fail-once/duty.md +3 -0
- package/dist/duties/task-job-fail-once/profile.json +6 -0
- package/dist/duties/task-job-pass-a/duty.md +3 -0
- package/dist/duties/task-job-pass-a/profile.json +6 -0
- package/dist/duties/task-job-pass-b/duty.md +3 -0
- package/dist/duties/task-job-pass-b/profile.json +6 -0
- package/dist/duties/task-jobs/duty.md +3 -0
- package/dist/duties/task-jobs/profile.json +6 -0
- package/dist/executables/preview-health/profile.json +48 -0
- package/dist/executables/preview-health/tick.sh +319 -0
- package/dist/executables/release/prepare.sh +4 -2
- package/dist/executables/types.ts +7 -10
- package/kody.config.schema.json +5 -0
- 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.223",
|
|
19
19
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
type: "module",
|
|
@@ -146,6 +146,12 @@ var init_claudeBinary = __esm({
|
|
|
146
146
|
// src/config.ts
|
|
147
147
|
import * as fs2 from "fs";
|
|
148
148
|
import * as path2 from "path";
|
|
149
|
+
function parseReasoningEffort(raw) {
|
|
150
|
+
if (!raw) return null;
|
|
151
|
+
const v = raw.trim().toLowerCase();
|
|
152
|
+
if (REASONING_EFFORTS.includes(v)) return v;
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
149
155
|
function parseProviderModel(s) {
|
|
150
156
|
const slash = s.indexOf("/");
|
|
151
157
|
if (slash <= 0 || slash === s.length - 1) {
|
|
@@ -198,7 +204,8 @@ function loadConfig(projectDir = process.cwd()) {
|
|
|
198
204
|
},
|
|
199
205
|
agent: {
|
|
200
206
|
model: String(agent.model),
|
|
201
|
-
...parsePerExecutable(agent.perExecutable)
|
|
207
|
+
...parsePerExecutable(agent.perExecutable),
|
|
208
|
+
...parseAgentReasoningEffort(agent.reasoningEffort)
|
|
202
209
|
},
|
|
203
210
|
issueContext: parseIssueContext(raw.issueContext),
|
|
204
211
|
testRequirements: parseTestRequirements(raw.testRequirements),
|
|
@@ -275,6 +282,10 @@ function parsePerExecutable(raw) {
|
|
|
275
282
|
}
|
|
276
283
|
return Object.keys(out).length > 0 ? { perExecutable: out } : {};
|
|
277
284
|
}
|
|
285
|
+
function parseAgentReasoningEffort(raw) {
|
|
286
|
+
if (typeof raw !== "string") return {};
|
|
287
|
+
return { reasoningEffort: parseReasoningEffort(raw) ?? void 0 };
|
|
288
|
+
}
|
|
278
289
|
function parseClassifyConfig(raw) {
|
|
279
290
|
if (!raw || typeof raw !== "object") return void 0;
|
|
280
291
|
const r = raw;
|
|
@@ -327,10 +338,16 @@ function parseTestRequirements(raw) {
|
|
|
327
338
|
function getAnthropicApiKeyOrDummy() {
|
|
328
339
|
return process.env.ANTHROPIC_API_KEY || `sk-ant-api03-${"0".repeat(64)}`;
|
|
329
340
|
}
|
|
330
|
-
var LITELLM_DEFAULT_PORT, LITELLM_DEFAULT_URL, GITHUB_AUTHOR_ASSOCIATIONS, DEFAULT_ALLOWED_ASSOCIATIONS, BUILTIN_ALIASES;
|
|
341
|
+
var REASONING_EFFORTS, REASONING_BUDGETS, LITELLM_DEFAULT_PORT, LITELLM_DEFAULT_URL, GITHUB_AUTHOR_ASSOCIATIONS, DEFAULT_ALLOWED_ASSOCIATIONS, BUILTIN_ALIASES;
|
|
331
342
|
var init_config = __esm({
|
|
332
343
|
"src/config.ts"() {
|
|
333
344
|
"use strict";
|
|
345
|
+
REASONING_EFFORTS = ["off", "low", "medium", "high"];
|
|
346
|
+
REASONING_BUDGETS = {
|
|
347
|
+
low: 2048,
|
|
348
|
+
medium: 1e4,
|
|
349
|
+
high: 32e3
|
|
350
|
+
};
|
|
334
351
|
LITELLM_DEFAULT_PORT = 4e3;
|
|
335
352
|
LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
|
|
336
353
|
GITHUB_AUTHOR_ASSOCIATIONS = [
|
|
@@ -1089,8 +1106,8 @@ function listRepairCandidates(repoSlug) {
|
|
|
1089
1106
|
};
|
|
1090
1107
|
});
|
|
1091
1108
|
}
|
|
1092
|
-
function dispatchVerb(workflowFile,
|
|
1093
|
-
return dispatchWorkflow(workflowFile,
|
|
1109
|
+
function dispatchVerb(workflowFile, duty, prNumber) {
|
|
1110
|
+
return dispatchWorkflow(workflowFile, duty, prNumber);
|
|
1094
1111
|
}
|
|
1095
1112
|
function postRecommendation(prNumber, mention, message, dutySlug) {
|
|
1096
1113
|
const mentioned = mention ? `${mention} ${message}` : message;
|
|
@@ -1219,17 +1236,17 @@ ${marker}` });
|
|
|
1219
1236
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
1220
1237
|
}
|
|
1221
1238
|
}
|
|
1222
|
-
function dispatchWorkflow(workflowFile,
|
|
1239
|
+
function dispatchWorkflow(workflowFile, duty, issueNumber) {
|
|
1223
1240
|
try {
|
|
1224
|
-
gh(["workflow", "run", workflowFile, "-f", `
|
|
1241
|
+
gh(["workflow", "run", workflowFile, "-f", `duty=${duty}`, "-f", `issue_number=${issueNumber}`]);
|
|
1225
1242
|
return { ok: true };
|
|
1226
1243
|
} catch (err) {
|
|
1227
1244
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1228
1245
|
}
|
|
1229
1246
|
}
|
|
1230
|
-
function isDispatchGated(
|
|
1247
|
+
function isDispatchGated(duty, mode) {
|
|
1231
1248
|
if (mode === "auto") return false;
|
|
1232
|
-
if (
|
|
1249
|
+
if (duty && GATE_EXEMPT_DUTIES.has(duty)) return false;
|
|
1233
1250
|
return true;
|
|
1234
1251
|
}
|
|
1235
1252
|
function trustRefusal(dutySlug) {
|
|
@@ -1374,19 +1391,20 @@ function dutyToolDefinitions(opts) {
|
|
|
1374
1391
|
};
|
|
1375
1392
|
const dispatchTool = {
|
|
1376
1393
|
name: "dispatch_workflow",
|
|
1377
|
-
description: "Dispatch a kody.yml workflow_dispatch run for
|
|
1394
|
+
description: "Dispatch a kody.yml workflow_dispatch run for a duty action against an issue (the cross-run bot\u2192engine path; a bot `@kody` comment would be dropped). E.g. dispatch_workflow({duty:'run', issueNumber:<n>}) opens a fix PR from a tracking issue. Returns {ok} or {ok:false,error}.",
|
|
1378
1395
|
inputSchema: {
|
|
1379
|
-
|
|
1396
|
+
duty: z2.string().min(1).optional().describe("Duty action to run (e.g. 'run')."),
|
|
1397
|
+
executable: z2.string().min(1).optional().describe("Deprecated alias for duty."),
|
|
1380
1398
|
issueNumber: z2.number().int().positive().describe("Issue (or PR) number forwarded as issue_number.")
|
|
1381
1399
|
},
|
|
1382
1400
|
handler: async (args) => {
|
|
1383
|
-
const
|
|
1401
|
+
const duty = String(args.duty ?? args.executable ?? "");
|
|
1384
1402
|
const issueNumber = Number(args.issueNumber);
|
|
1385
|
-
if (isDispatchGated(
|
|
1403
|
+
if (isDispatchGated(duty, readDutyTrustMode(opts.repoSlug, opts.dutySlug))) {
|
|
1386
1404
|
return { content: [{ type: "text", text: trustRefusal(opts.dutySlug) }] };
|
|
1387
1405
|
}
|
|
1388
|
-
const result = dispatchWorkflow(workflowFile,
|
|
1389
|
-
const text = result.ok ? `Dispatched \`${
|
|
1406
|
+
const result = dispatchWorkflow(workflowFile, duty, issueNumber);
|
|
1407
|
+
const text = result.ok ? `Dispatched duty \`${duty}\` on #${issueNumber} via workflow_dispatch.` : `Dispatch failed for duty \`${duty}\` on #${issueNumber}: ${result.error}`;
|
|
1390
1408
|
return { content: [{ type: "text", text }] };
|
|
1391
1409
|
}
|
|
1392
1410
|
};
|
|
@@ -1421,7 +1439,7 @@ function buildDutyMcpServer(opts) {
|
|
|
1421
1439
|
});
|
|
1422
1440
|
return { server };
|
|
1423
1441
|
}
|
|
1424
|
-
var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, TRUST_STATE_BRANCH, THREAD_BODY_MAX, CHECK_FAIL_CONCLUSIONS, DEFAULT_IGNORE_CHECKS, trackMarker, commentMarker,
|
|
1442
|
+
var FAIL_CONCLUSIONS, RUNNING_STATUSES, TRUST_FILE_PATH, TRUST_STATE_BRANCH, THREAD_BODY_MAX, CHECK_FAIL_CONCLUSIONS, DEFAULT_IGNORE_CHECKS, trackMarker, commentMarker, GATE_EXEMPT_DUTIES, DUTY_MCP_TOOL_NAMES;
|
|
1425
1443
|
var init_dutyMcp = __esm({
|
|
1426
1444
|
"src/dutyMcp.ts"() {
|
|
1427
1445
|
"use strict";
|
|
@@ -1435,7 +1453,7 @@ var init_dutyMcp = __esm({
|
|
|
1435
1453
|
DEFAULT_IGNORE_CHECKS = ["run", "kody", "duty-tick", "goal-tick", "worker-ask", "chat"];
|
|
1436
1454
|
trackMarker = (key) => `<!-- kody-track:${key} -->`;
|
|
1437
1455
|
commentMarker = (key) => `<!-- kody-track-comment:${key} -->`;
|
|
1438
|
-
|
|
1456
|
+
GATE_EXEMPT_DUTIES = /* @__PURE__ */ new Set(["qa-engineer", "ui-review"]);
|
|
1439
1457
|
DUTY_MCP_TOOL_NAMES = [
|
|
1440
1458
|
"list_prs_to_repair",
|
|
1441
1459
|
"sync_pr",
|
|
@@ -1663,15 +1681,19 @@ async function runAgent(opts) {
|
|
|
1663
1681
|
let finalText = "";
|
|
1664
1682
|
let getSubmitted;
|
|
1665
1683
|
for (let attempt = 0; ; attempt++) {
|
|
1684
|
+
let ndjsonWriteFailed = false;
|
|
1685
|
+
let ndjsonWriteError;
|
|
1666
1686
|
const fullLog = fs5.createWriteStream(ndjsonPath, { flags: "w" });
|
|
1687
|
+
fullLog.on("error", (err) => {
|
|
1688
|
+
ndjsonWriteFailed = true;
|
|
1689
|
+
ndjsonWriteError = err instanceof Error ? err.message : String(err);
|
|
1690
|
+
});
|
|
1667
1691
|
const resultTexts = [];
|
|
1668
1692
|
outcome = "failed";
|
|
1669
1693
|
outcomeKind = "generic_failed";
|
|
1670
1694
|
errorMessage = void 0;
|
|
1671
1695
|
tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
|
|
1672
1696
|
messageCount = 0;
|
|
1673
|
-
let ndjsonWriteFailed = false;
|
|
1674
|
-
let ndjsonWriteError;
|
|
1675
1697
|
let sawMutatingTool = false;
|
|
1676
1698
|
let sawTerminalSuccess = false;
|
|
1677
1699
|
let noWorkSuccess = false;
|
|
@@ -1746,7 +1768,13 @@ async function runAgent(opts) {
|
|
|
1746
1768
|
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
1747
1769
|
queryOptions.maxTurns = opts.maxTurns;
|
|
1748
1770
|
}
|
|
1749
|
-
if (
|
|
1771
|
+
if (opts.reasoningEffort !== void 0 && opts.reasoningEffort !== null) {
|
|
1772
|
+
if (opts.reasoningEffort === "off") {
|
|
1773
|
+
} else {
|
|
1774
|
+
const budget = REASONING_BUDGETS[opts.reasoningEffort];
|
|
1775
|
+
if (budget) queryOptions.maxThinkingTokens = budget;
|
|
1776
|
+
}
|
|
1777
|
+
} else if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
|
|
1750
1778
|
queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
|
|
1751
1779
|
}
|
|
1752
1780
|
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
@@ -1807,12 +1835,14 @@ async function runAgent(opts) {
|
|
|
1807
1835
|
if (next.done) break;
|
|
1808
1836
|
const msg = next.value;
|
|
1809
1837
|
messageCount++;
|
|
1810
|
-
|
|
1811
|
-
|
|
1838
|
+
if (!ndjsonWriteFailed) {
|
|
1839
|
+
try {
|
|
1840
|
+
fullLog.write(`${JSON.stringify(msg)}
|
|
1812
1841
|
`);
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1842
|
+
} catch (e) {
|
|
1843
|
+
ndjsonWriteFailed = true;
|
|
1844
|
+
ndjsonWriteError = e instanceof Error ? e.message : String(e);
|
|
1845
|
+
}
|
|
1816
1846
|
}
|
|
1817
1847
|
const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
|
|
1818
1848
|
if (line) process.stdout.write(`${line}
|
|
@@ -2237,9 +2267,6 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
|
|
|
2237
2267
|
}
|
|
2238
2268
|
return null;
|
|
2239
2269
|
}
|
|
2240
|
-
function hasExecutable(name, roots = getExecutableRoots()) {
|
|
2241
|
-
return resolveExecutable(name, roots) !== null;
|
|
2242
|
-
}
|
|
2243
2270
|
function listDutyActions(projectDutiesRoot = getProjectDutiesRoot()) {
|
|
2244
2271
|
const seen = /* @__PURE__ */ new Set();
|
|
2245
2272
|
const out = [];
|
|
@@ -2439,6 +2466,73 @@ var init_task_artifacts = __esm({
|
|
|
2439
2466
|
}
|
|
2440
2467
|
});
|
|
2441
2468
|
|
|
2469
|
+
// src/gha.ts
|
|
2470
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2471
|
+
import * as fs14 from "fs";
|
|
2472
|
+
function getRunUrl() {
|
|
2473
|
+
const server = process.env.GITHUB_SERVER_URL;
|
|
2474
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
2475
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
2476
|
+
if (!server || !repo || !runId) return "";
|
|
2477
|
+
return `${server}/${repo}/actions/runs/${runId}`;
|
|
2478
|
+
}
|
|
2479
|
+
function reactToTriggerComment(cwd) {
|
|
2480
|
+
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
2481
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
2482
|
+
if (!eventPath || !fs14.existsSync(eventPath)) return;
|
|
2483
|
+
let event = null;
|
|
2484
|
+
try {
|
|
2485
|
+
event = JSON.parse(fs14.readFileSync(eventPath, "utf-8"));
|
|
2486
|
+
} catch {
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
const commentId = event?.comment?.id;
|
|
2490
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
2491
|
+
if (!commentId || !repo) return;
|
|
2492
|
+
const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
2493
|
+
const args = [
|
|
2494
|
+
"api",
|
|
2495
|
+
"-X",
|
|
2496
|
+
"POST",
|
|
2497
|
+
"-H",
|
|
2498
|
+
"Accept: application/vnd.github+json",
|
|
2499
|
+
`/repos/${repo}/issues/comments/${commentId}/reactions`,
|
|
2500
|
+
"-f",
|
|
2501
|
+
"content=eyes"
|
|
2502
|
+
];
|
|
2503
|
+
const opts = {
|
|
2504
|
+
cwd,
|
|
2505
|
+
env: { ...process.env, GH_TOKEN: token ?? process.env.GH_TOKEN ?? "" },
|
|
2506
|
+
stdio: "pipe",
|
|
2507
|
+
timeout: 15e3
|
|
2508
|
+
};
|
|
2509
|
+
let lastErr = null;
|
|
2510
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2511
|
+
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
2512
|
+
try {
|
|
2513
|
+
execFileSync2("gh", args, opts);
|
|
2514
|
+
return;
|
|
2515
|
+
} catch (err) {
|
|
2516
|
+
lastErr = err;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
process.stderr.write(
|
|
2520
|
+
`[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
|
|
2521
|
+
`
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
function sleepMs(ms) {
|
|
2525
|
+
try {
|
|
2526
|
+
execFileSync2("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
2527
|
+
} catch {
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
var init_gha = __esm({
|
|
2531
|
+
"src/gha.ts"() {
|
|
2532
|
+
"use strict";
|
|
2533
|
+
}
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2442
2536
|
// src/profile-error.ts
|
|
2443
2537
|
var ProfileError;
|
|
2444
2538
|
var init_profile_error = __esm({
|
|
@@ -2599,7 +2693,7 @@ var init_lifecycles = __esm({
|
|
|
2599
2693
|
});
|
|
2600
2694
|
|
|
2601
2695
|
// src/scripts/buildSyntheticPlugin.ts
|
|
2602
|
-
import * as
|
|
2696
|
+
import * as fs15 from "fs";
|
|
2603
2697
|
import * as os2 from "os";
|
|
2604
2698
|
import * as path13 from "path";
|
|
2605
2699
|
function getPluginsCatalogRoot() {
|
|
@@ -2613,17 +2707,17 @@ function getPluginsCatalogRoot() {
|
|
|
2613
2707
|
// fallback
|
|
2614
2708
|
];
|
|
2615
2709
|
for (const c of candidates) {
|
|
2616
|
-
if (
|
|
2710
|
+
if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
|
|
2617
2711
|
}
|
|
2618
2712
|
return candidates[0];
|
|
2619
2713
|
}
|
|
2620
2714
|
function copyDir(src, dst) {
|
|
2621
|
-
|
|
2622
|
-
for (const ent of
|
|
2715
|
+
fs15.mkdirSync(dst, { recursive: true });
|
|
2716
|
+
for (const ent of fs15.readdirSync(src, { withFileTypes: true })) {
|
|
2623
2717
|
const s = path13.join(src, ent.name);
|
|
2624
2718
|
const d = path13.join(dst, ent.name);
|
|
2625
2719
|
if (ent.isDirectory()) copyDir(s, d);
|
|
2626
|
-
else if (ent.isFile())
|
|
2720
|
+
else if (ent.isFile()) fs15.copyFileSync(s, d);
|
|
2627
2721
|
}
|
|
2628
2722
|
}
|
|
2629
2723
|
var buildSyntheticPlugin;
|
|
@@ -2637,44 +2731,44 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2637
2731
|
const catalog = getPluginsCatalogRoot();
|
|
2638
2732
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2639
2733
|
const root = path13.join(os2.tmpdir(), `kody-synth-${runId}`);
|
|
2640
|
-
|
|
2734
|
+
fs15.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
|
|
2641
2735
|
const resolvePart = (bucket, entry) => {
|
|
2642
2736
|
const local = path13.join(profile.dir, bucket, entry);
|
|
2643
|
-
if (
|
|
2737
|
+
if (fs15.existsSync(local)) return local;
|
|
2644
2738
|
const central = path13.join(catalog, bucket, entry);
|
|
2645
|
-
if (
|
|
2739
|
+
if (fs15.existsSync(central)) return central;
|
|
2646
2740
|
throw new Error(
|
|
2647
2741
|
`buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
|
|
2648
2742
|
);
|
|
2649
2743
|
};
|
|
2650
2744
|
if (cc.skills.length > 0) {
|
|
2651
2745
|
const dst = path13.join(root, "skills");
|
|
2652
|
-
|
|
2746
|
+
fs15.mkdirSync(dst, { recursive: true });
|
|
2653
2747
|
for (const name of cc.skills) {
|
|
2654
2748
|
copyDir(resolvePart("skills", name), path13.join(dst, name));
|
|
2655
2749
|
}
|
|
2656
2750
|
}
|
|
2657
2751
|
if (cc.commands.length > 0) {
|
|
2658
2752
|
const dst = path13.join(root, "commands");
|
|
2659
|
-
|
|
2753
|
+
fs15.mkdirSync(dst, { recursive: true });
|
|
2660
2754
|
for (const name of cc.commands) {
|
|
2661
|
-
|
|
2755
|
+
fs15.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
|
|
2662
2756
|
}
|
|
2663
2757
|
}
|
|
2664
2758
|
if (cc.hooks.length > 0) {
|
|
2665
2759
|
const dst = path13.join(root, "hooks");
|
|
2666
|
-
|
|
2760
|
+
fs15.mkdirSync(dst, { recursive: true });
|
|
2667
2761
|
const merged = { hooks: {} };
|
|
2668
2762
|
for (const name of cc.hooks) {
|
|
2669
2763
|
const src = resolvePart("hooks", `${name}.json`);
|
|
2670
|
-
const parsed = JSON.parse(
|
|
2764
|
+
const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
|
|
2671
2765
|
for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
|
|
2672
2766
|
if (!Array.isArray(entries)) continue;
|
|
2673
2767
|
if (!merged.hooks[event]) merged.hooks[event] = [];
|
|
2674
2768
|
merged.hooks[event].push(...entries);
|
|
2675
2769
|
}
|
|
2676
2770
|
}
|
|
2677
|
-
|
|
2771
|
+
fs15.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
|
|
2678
2772
|
`);
|
|
2679
2773
|
}
|
|
2680
2774
|
const manifest = {
|
|
@@ -2684,7 +2778,7 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2684
2778
|
};
|
|
2685
2779
|
if (cc.skills.length > 0) manifest.skills = ["./skills/"];
|
|
2686
2780
|
if (cc.commands.length > 0) manifest.commands = ["./commands/"];
|
|
2687
|
-
|
|
2781
|
+
fs15.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
2688
2782
|
`);
|
|
2689
2783
|
ctx.data.syntheticPluginPath = root;
|
|
2690
2784
|
};
|
|
@@ -2692,7 +2786,7 @@ var init_buildSyntheticPlugin = __esm({
|
|
|
2692
2786
|
});
|
|
2693
2787
|
|
|
2694
2788
|
// src/subagents.ts
|
|
2695
|
-
import * as
|
|
2789
|
+
import * as fs16 from "fs";
|
|
2696
2790
|
import * as path14 from "path";
|
|
2697
2791
|
function splitFrontmatter(raw) {
|
|
2698
2792
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
|
|
@@ -2707,9 +2801,9 @@ function splitFrontmatter(raw) {
|
|
|
2707
2801
|
}
|
|
2708
2802
|
function resolveAgentFile(profileDir, name) {
|
|
2709
2803
|
const local = path14.join(profileDir, "agents", `${name}.md`);
|
|
2710
|
-
if (
|
|
2804
|
+
if (fs16.existsSync(local)) return local;
|
|
2711
2805
|
const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
|
|
2712
|
-
if (
|
|
2806
|
+
if (fs16.existsSync(central)) return central;
|
|
2713
2807
|
throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
|
|
2714
2808
|
}
|
|
2715
2809
|
function captureSubagentTemplates(profile) {
|
|
@@ -2718,7 +2812,7 @@ function captureSubagentTemplates(profile) {
|
|
|
2718
2812
|
const out = {};
|
|
2719
2813
|
for (const name of names) {
|
|
2720
2814
|
try {
|
|
2721
|
-
out[name] =
|
|
2815
|
+
out[name] = fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
2722
2816
|
} catch {
|
|
2723
2817
|
}
|
|
2724
2818
|
}
|
|
@@ -2729,7 +2823,7 @@ function loadSubagents(profile) {
|
|
|
2729
2823
|
if (!names || names.length === 0) return void 0;
|
|
2730
2824
|
const agents = {};
|
|
2731
2825
|
for (const name of names) {
|
|
2732
|
-
const raw = profile.subagentTemplates?.[name] ??
|
|
2826
|
+
const raw = profile.subagentTemplates?.[name] ?? fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
|
|
2733
2827
|
const { fm, body } = splitFrontmatter(raw);
|
|
2734
2828
|
if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
|
|
2735
2829
|
const def = {
|
|
@@ -2753,15 +2847,15 @@ var init_subagents = __esm({
|
|
|
2753
2847
|
});
|
|
2754
2848
|
|
|
2755
2849
|
// src/profile.ts
|
|
2756
|
-
import * as
|
|
2850
|
+
import * as fs17 from "fs";
|
|
2757
2851
|
import * as path15 from "path";
|
|
2758
2852
|
function loadProfile(profilePath) {
|
|
2759
|
-
if (!
|
|
2853
|
+
if (!fs17.existsSync(profilePath)) {
|
|
2760
2854
|
throw new ProfileError(profilePath, "file not found");
|
|
2761
2855
|
}
|
|
2762
2856
|
let raw;
|
|
2763
2857
|
try {
|
|
2764
|
-
raw = JSON.parse(
|
|
2858
|
+
raw = JSON.parse(fs17.readFileSync(profilePath, "utf-8"));
|
|
2765
2859
|
} catch (err) {
|
|
2766
2860
|
throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
2767
2861
|
}
|
|
@@ -2880,7 +2974,7 @@ function loadProfile(profilePath) {
|
|
|
2880
2974
|
const preNames = new Set(profile.scripts.preflight.map((e) => e.script).filter(Boolean));
|
|
2881
2975
|
const postNames = profile.scripts.postflight.map((e) => e.script).filter(Boolean);
|
|
2882
2976
|
const needsState = postNames.includes("writeJobStateFile") || postNames.includes("parseJobStateFromAgentResult");
|
|
2883
|
-
const STATE_LOADERS = ["loadDutyState", "loadJobFromFile", "runTickScript"];
|
|
2977
|
+
const STATE_LOADERS = ["loadDutyState", "loadJobFromFile", "runTickScript", "runScheduledExecutableTick"];
|
|
2884
2978
|
if (needsState && !STATE_LOADERS.some((s) => preNames.has(s))) {
|
|
2885
2979
|
throw new ProfileError(
|
|
2886
2980
|
profilePath,
|
|
@@ -2894,7 +2988,7 @@ function readPromptTemplates(dir) {
|
|
|
2894
2988
|
const out = {};
|
|
2895
2989
|
const read = (p) => {
|
|
2896
2990
|
try {
|
|
2897
|
-
out[p] =
|
|
2991
|
+
out[p] = fs17.readFileSync(p, "utf-8");
|
|
2898
2992
|
} catch {
|
|
2899
2993
|
}
|
|
2900
2994
|
};
|
|
@@ -2902,7 +2996,7 @@ function readPromptTemplates(dir) {
|
|
|
2902
2996
|
read(path15.join(dir, "duty.md"));
|
|
2903
2997
|
try {
|
|
2904
2998
|
const promptsDir = path15.join(dir, "prompts");
|
|
2905
|
-
for (const ent of
|
|
2999
|
+
for (const ent of fs17.readdirSync(promptsDir)) {
|
|
2906
3000
|
if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
|
|
2907
3001
|
}
|
|
2908
3002
|
} catch {
|
|
@@ -3210,7 +3304,7 @@ var init_profile = __esm({
|
|
|
3210
3304
|
});
|
|
3211
3305
|
|
|
3212
3306
|
// src/state.ts
|
|
3213
|
-
import { execFileSync as
|
|
3307
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
3214
3308
|
function emptyState() {
|
|
3215
3309
|
return {
|
|
3216
3310
|
schemaVersion: 1,
|
|
@@ -3233,7 +3327,7 @@ function ghToken2() {
|
|
|
3233
3327
|
function gh2(args, input, cwd) {
|
|
3234
3328
|
const token = ghToken2();
|
|
3235
3329
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
3236
|
-
return
|
|
3330
|
+
return execFileSync3("gh", args, {
|
|
3237
3331
|
encoding: "utf-8",
|
|
3238
3332
|
timeout: API_TIMEOUT_MS2,
|
|
3239
3333
|
cwd,
|
|
@@ -3580,16 +3674,16 @@ var init_state = __esm({
|
|
|
3580
3674
|
});
|
|
3581
3675
|
|
|
3582
3676
|
// src/prompt.ts
|
|
3583
|
-
import * as
|
|
3677
|
+
import * as fs18 from "fs";
|
|
3584
3678
|
import * as path16 from "path";
|
|
3585
3679
|
function loadProjectConventions(projectDir) {
|
|
3586
3680
|
const out = [];
|
|
3587
3681
|
for (const rel of CONVENTION_FILES) {
|
|
3588
3682
|
const abs = path16.join(projectDir, rel);
|
|
3589
|
-
if (!
|
|
3683
|
+
if (!fs18.existsSync(abs)) continue;
|
|
3590
3684
|
let content;
|
|
3591
3685
|
try {
|
|
3592
|
-
content =
|
|
3686
|
+
content = fs18.readFileSync(abs, "utf-8");
|
|
3593
3687
|
} catch {
|
|
3594
3688
|
continue;
|
|
3595
3689
|
}
|
|
@@ -3824,20 +3918,20 @@ var loadMemoryContext_exports = {};
|
|
|
3824
3918
|
__export(loadMemoryContext_exports, {
|
|
3825
3919
|
loadMemoryContext: () => loadMemoryContext
|
|
3826
3920
|
});
|
|
3827
|
-
import * as
|
|
3921
|
+
import * as fs19 from "fs";
|
|
3828
3922
|
import * as path17 from "path";
|
|
3829
3923
|
function collectPages(memoryAbs) {
|
|
3830
3924
|
const out = [];
|
|
3831
3925
|
walkMd(memoryAbs, (file) => {
|
|
3832
3926
|
let stat;
|
|
3833
3927
|
try {
|
|
3834
|
-
stat =
|
|
3928
|
+
stat = fs19.statSync(file);
|
|
3835
3929
|
} catch {
|
|
3836
3930
|
return;
|
|
3837
3931
|
}
|
|
3838
3932
|
let raw;
|
|
3839
3933
|
try {
|
|
3840
|
-
raw =
|
|
3934
|
+
raw = fs19.readFileSync(file, "utf-8");
|
|
3841
3935
|
} catch {
|
|
3842
3936
|
return;
|
|
3843
3937
|
}
|
|
@@ -3913,7 +4007,7 @@ function walkMd(root, visit) {
|
|
|
3913
4007
|
const dir = stack.pop();
|
|
3914
4008
|
let names;
|
|
3915
4009
|
try {
|
|
3916
|
-
names =
|
|
4010
|
+
names = fs19.readdirSync(dir);
|
|
3917
4011
|
} catch {
|
|
3918
4012
|
continue;
|
|
3919
4013
|
}
|
|
@@ -3922,7 +4016,7 @@ function walkMd(root, visit) {
|
|
|
3922
4016
|
const full = path17.join(dir, name);
|
|
3923
4017
|
let stat;
|
|
3924
4018
|
try {
|
|
3925
|
-
stat =
|
|
4019
|
+
stat = fs19.statSync(full);
|
|
3926
4020
|
} catch {
|
|
3927
4021
|
continue;
|
|
3928
4022
|
}
|
|
@@ -3946,7 +4040,7 @@ var init_loadMemoryContext = __esm({
|
|
|
3946
4040
|
loadMemoryContext = async (ctx) => {
|
|
3947
4041
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
3948
4042
|
const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
3949
|
-
if (!
|
|
4043
|
+
if (!fs19.existsSync(memoryAbs)) {
|
|
3950
4044
|
ctx.data.memoryContext = "";
|
|
3951
4045
|
return;
|
|
3952
4046
|
}
|
|
@@ -3989,12 +4083,12 @@ var init_loadCoverageRules = __esm({
|
|
|
3989
4083
|
});
|
|
3990
4084
|
|
|
3991
4085
|
// src/container.ts
|
|
3992
|
-
import { execFileSync as
|
|
3993
|
-
import * as
|
|
4086
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
4087
|
+
import * as fs20 from "fs";
|
|
3994
4088
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
3995
4089
|
try {
|
|
3996
4090
|
const profilePath = resolveProfilePath(profileName);
|
|
3997
|
-
if (!
|
|
4091
|
+
if (!fs20.existsSync(profilePath)) return null;
|
|
3998
4092
|
return loadProfile(profilePath).inputs;
|
|
3999
4093
|
} catch {
|
|
4000
4094
|
return null;
|
|
@@ -4228,7 +4322,7 @@ async function runContainerLoop(profile, ctx, input) {
|
|
|
4228
4322
|
}
|
|
4229
4323
|
function resetWorkingTree(cwd) {
|
|
4230
4324
|
try {
|
|
4231
|
-
|
|
4325
|
+
execFileSync4("git", ["reset", "--hard", "HEAD"], {
|
|
4232
4326
|
cwd,
|
|
4233
4327
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4234
4328
|
timeout: 3e4
|
|
@@ -4455,8 +4549,8 @@ var init_lifecycleLabels = __esm({
|
|
|
4455
4549
|
});
|
|
4456
4550
|
|
|
4457
4551
|
// src/litellm.ts
|
|
4458
|
-
import { execFileSync as
|
|
4459
|
-
import * as
|
|
4552
|
+
import { execFileSync as execFileSync5, spawn as spawn3 } from "child_process";
|
|
4553
|
+
import * as fs21 from "fs";
|
|
4460
4554
|
import * as os3 from "os";
|
|
4461
4555
|
import * as path18 from "path";
|
|
4462
4556
|
async function checkLitellmHealth(url) {
|
|
@@ -4488,7 +4582,7 @@ function generateLitellmConfigYaml(model) {
|
|
|
4488
4582
|
}
|
|
4489
4583
|
function litellmImportable() {
|
|
4490
4584
|
try {
|
|
4491
|
-
|
|
4585
|
+
execFileSync5("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
|
|
4492
4586
|
return true;
|
|
4493
4587
|
} catch {
|
|
4494
4588
|
return false;
|
|
@@ -4496,7 +4590,7 @@ function litellmImportable() {
|
|
|
4496
4590
|
}
|
|
4497
4591
|
function locateLitellmScript() {
|
|
4498
4592
|
try {
|
|
4499
|
-
const out =
|
|
4593
|
+
const out = execFileSync5(
|
|
4500
4594
|
"python3",
|
|
4501
4595
|
[
|
|
4502
4596
|
"-c",
|
|
@@ -4511,7 +4605,7 @@ function locateLitellmScript() {
|
|
|
4511
4605
|
}
|
|
4512
4606
|
function resolveLitellmCommand() {
|
|
4513
4607
|
try {
|
|
4514
|
-
|
|
4608
|
+
execFileSync5("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
4515
4609
|
return "litellm";
|
|
4516
4610
|
} catch {
|
|
4517
4611
|
if (!litellmImportable()) {
|
|
@@ -4519,7 +4613,7 @@ function resolveLitellmCommand() {
|
|
|
4519
4613
|
let installed = false;
|
|
4520
4614
|
for (const pip of ["pip", "pip3"]) {
|
|
4521
4615
|
try {
|
|
4522
|
-
|
|
4616
|
+
execFileSync5(pip, ["install", "litellm[proxy]"], { timeout: 3e5, stdio: "inherit" });
|
|
4523
4617
|
installed = true;
|
|
4524
4618
|
break;
|
|
4525
4619
|
} catch {
|
|
@@ -4544,12 +4638,12 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4544
4638
|
let logPath;
|
|
4545
4639
|
const spawnProxy = () => {
|
|
4546
4640
|
const configPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
|
|
4547
|
-
|
|
4641
|
+
fs21.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
4548
4642
|
const args = ["--config", configPath, "--port", port];
|
|
4549
4643
|
const nextLogPath = path18.join(os3.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
|
|
4550
|
-
const outFd =
|
|
4644
|
+
const outFd = fs21.openSync(nextLogPath, "w");
|
|
4551
4645
|
child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
|
|
4552
|
-
|
|
4646
|
+
fs21.closeSync(outFd);
|
|
4553
4647
|
logPath = nextLogPath;
|
|
4554
4648
|
};
|
|
4555
4649
|
const waitForHealth = async () => {
|
|
@@ -4563,7 +4657,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
4563
4657
|
const readLogTail = () => {
|
|
4564
4658
|
if (!logPath) return "";
|
|
4565
4659
|
try {
|
|
4566
|
-
return
|
|
4660
|
+
return fs21.readFileSync(logPath, "utf-8").slice(-2e3);
|
|
4567
4661
|
} catch {
|
|
4568
4662
|
return "";
|
|
4569
4663
|
}
|
|
@@ -4616,9 +4710,9 @@ ${tail}`
|
|
|
4616
4710
|
}
|
|
4617
4711
|
function readDotenvApiKeys(projectDir) {
|
|
4618
4712
|
const dotenvPath = path18.join(projectDir, ".env");
|
|
4619
|
-
if (!
|
|
4713
|
+
if (!fs21.existsSync(dotenvPath)) return {};
|
|
4620
4714
|
const result = {};
|
|
4621
|
-
for (const rawLine of
|
|
4715
|
+
for (const rawLine of fs21.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
4622
4716
|
const line = rawLine.trim();
|
|
4623
4717
|
if (!line || line.startsWith("#")) continue;
|
|
4624
4718
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -4650,14 +4744,14 @@ var init_litellm = __esm({
|
|
|
4650
4744
|
});
|
|
4651
4745
|
|
|
4652
4746
|
// src/pushWithRetry.ts
|
|
4653
|
-
import { execFileSync as
|
|
4747
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
4654
4748
|
function sleepSync(ms) {
|
|
4655
4749
|
if (ms <= 0) return;
|
|
4656
4750
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
4657
4751
|
}
|
|
4658
4752
|
function runGit(args, cwd) {
|
|
4659
4753
|
try {
|
|
4660
|
-
const stdout =
|
|
4754
|
+
const stdout = execFileSync6("git", args, {
|
|
4661
4755
|
cwd,
|
|
4662
4756
|
encoding: "utf-8",
|
|
4663
4757
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -4732,12 +4826,12 @@ var init_pushWithRetry = __esm({
|
|
|
4732
4826
|
});
|
|
4733
4827
|
|
|
4734
4828
|
// src/commit.ts
|
|
4735
|
-
import { execFileSync as
|
|
4736
|
-
import * as
|
|
4829
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
4830
|
+
import * as fs22 from "fs";
|
|
4737
4831
|
import * as path19 from "path";
|
|
4738
4832
|
function git(args, cwd) {
|
|
4739
4833
|
try {
|
|
4740
|
-
return
|
|
4834
|
+
return execFileSync7("git", args, {
|
|
4741
4835
|
encoding: "utf-8",
|
|
4742
4836
|
timeout: 12e4,
|
|
4743
4837
|
cwd,
|
|
@@ -4773,17 +4867,17 @@ function ensureGitIdentity(cwd) {
|
|
|
4773
4867
|
function abortUnfinishedGitOps(cwd) {
|
|
4774
4868
|
const aborted = [];
|
|
4775
4869
|
const gitDir = path19.join(cwd ?? process.cwd(), ".git");
|
|
4776
|
-
if (!
|
|
4777
|
-
if (
|
|
4870
|
+
if (!fs22.existsSync(gitDir)) return aborted;
|
|
4871
|
+
if (fs22.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
|
|
4778
4872
|
if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
|
|
4779
4873
|
}
|
|
4780
|
-
if (
|
|
4874
|
+
if (fs22.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
|
|
4781
4875
|
if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
|
|
4782
4876
|
}
|
|
4783
|
-
if (
|
|
4877
|
+
if (fs22.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
|
|
4784
4878
|
if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
|
|
4785
4879
|
}
|
|
4786
|
-
if (
|
|
4880
|
+
if (fs22.existsSync(path19.join(gitDir, "rebase-merge")) || fs22.existsSync(path19.join(gitDir, "rebase-apply"))) {
|
|
4787
4881
|
if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
|
|
4788
4882
|
}
|
|
4789
4883
|
try {
|
|
@@ -4804,7 +4898,7 @@ function isForbiddenPath(p) {
|
|
|
4804
4898
|
return false;
|
|
4805
4899
|
}
|
|
4806
4900
|
function listChangedFiles(cwd) {
|
|
4807
|
-
const raw =
|
|
4901
|
+
const raw = execFileSync7("git", ["status", "--porcelain=v1", "-z"], {
|
|
4808
4902
|
encoding: "utf-8",
|
|
4809
4903
|
cwd,
|
|
4810
4904
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -4816,7 +4910,7 @@ function listChangedFiles(cwd) {
|
|
|
4816
4910
|
}
|
|
4817
4911
|
function listFilesInCommit(ref = "HEAD", cwd) {
|
|
4818
4912
|
try {
|
|
4819
|
-
const raw =
|
|
4913
|
+
const raw = execFileSync7("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
|
|
4820
4914
|
encoding: "utf-8",
|
|
4821
4915
|
cwd,
|
|
4822
4916
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
@@ -4839,7 +4933,7 @@ function normalizeCommitMessage(raw) {
|
|
|
4839
4933
|
function commitAndPush(branch, agentMessage, cwd) {
|
|
4840
4934
|
const allChanged = listChangedFiles(cwd);
|
|
4841
4935
|
const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
|
|
4842
|
-
const mergeHeadExists =
|
|
4936
|
+
const mergeHeadExists = fs22.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
|
|
4843
4937
|
if (allowedFiles.length === 0 && !mergeHeadExists) {
|
|
4844
4938
|
return { committed: false, pushed: false, sha: "", message: "" };
|
|
4845
4939
|
}
|
|
@@ -4995,10 +5089,10 @@ var init_saveTaskState = __esm({
|
|
|
4995
5089
|
});
|
|
4996
5090
|
|
|
4997
5091
|
// src/scripts/advanceFlow.ts
|
|
4998
|
-
import { execFileSync as
|
|
5092
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
4999
5093
|
function ghComment(issueNumber, body, cwd, label) {
|
|
5000
5094
|
try {
|
|
5001
|
-
|
|
5095
|
+
execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", body], {
|
|
5002
5096
|
timeout: API_TIMEOUT_MS3,
|
|
5003
5097
|
cwd,
|
|
5004
5098
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5069,78 +5163,11 @@ var init_advanceFlow = __esm({
|
|
|
5069
5163
|
`
|
|
5070
5164
|
);
|
|
5071
5165
|
}
|
|
5072
|
-
ctx.output.nextDispatch = {
|
|
5166
|
+
ctx.output.nextDispatch = { action: flow.name, cliArgs: { issue: flow.issueNumber } };
|
|
5073
5167
|
};
|
|
5074
5168
|
}
|
|
5075
5169
|
});
|
|
5076
5170
|
|
|
5077
|
-
// src/gha.ts
|
|
5078
|
-
import { execFileSync as execFileSync8 } from "child_process";
|
|
5079
|
-
import * as fs22 from "fs";
|
|
5080
|
-
function getRunUrl() {
|
|
5081
|
-
const server = process.env.GITHUB_SERVER_URL;
|
|
5082
|
-
const repo = process.env.GITHUB_REPOSITORY;
|
|
5083
|
-
const runId = process.env.GITHUB_RUN_ID;
|
|
5084
|
-
if (!server || !repo || !runId) return "";
|
|
5085
|
-
return `${server}/${repo}/actions/runs/${runId}`;
|
|
5086
|
-
}
|
|
5087
|
-
function reactToTriggerComment(cwd) {
|
|
5088
|
-
if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
|
|
5089
|
-
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
5090
|
-
if (!eventPath || !fs22.existsSync(eventPath)) return;
|
|
5091
|
-
let event = null;
|
|
5092
|
-
try {
|
|
5093
|
-
event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
|
|
5094
|
-
} catch {
|
|
5095
|
-
return;
|
|
5096
|
-
}
|
|
5097
|
-
const commentId = event?.comment?.id;
|
|
5098
|
-
const repo = process.env.GITHUB_REPOSITORY;
|
|
5099
|
-
if (!commentId || !repo) return;
|
|
5100
|
-
const token = process.env.KODY_TOKEN?.trim() || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
5101
|
-
const args = [
|
|
5102
|
-
"api",
|
|
5103
|
-
"-X",
|
|
5104
|
-
"POST",
|
|
5105
|
-
"-H",
|
|
5106
|
-
"Accept: application/vnd.github+json",
|
|
5107
|
-
`/repos/${repo}/issues/comments/${commentId}/reactions`,
|
|
5108
|
-
"-f",
|
|
5109
|
-
"content=eyes"
|
|
5110
|
-
];
|
|
5111
|
-
const opts = {
|
|
5112
|
-
cwd,
|
|
5113
|
-
env: { ...process.env, GH_TOKEN: token ?? process.env.GH_TOKEN ?? "" },
|
|
5114
|
-
stdio: "pipe",
|
|
5115
|
-
timeout: 15e3
|
|
5116
|
-
};
|
|
5117
|
-
let lastErr = null;
|
|
5118
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
5119
|
-
if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
|
|
5120
|
-
try {
|
|
5121
|
-
execFileSync8("gh", args, opts);
|
|
5122
|
-
return;
|
|
5123
|
-
} catch (err) {
|
|
5124
|
-
lastErr = err;
|
|
5125
|
-
}
|
|
5126
|
-
}
|
|
5127
|
-
process.stderr.write(
|
|
5128
|
-
`[kody] \u{1F440} reaction failed after 3 attempts on comment ${commentId}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}
|
|
5129
|
-
`
|
|
5130
|
-
);
|
|
5131
|
-
}
|
|
5132
|
-
function sleepMs(ms) {
|
|
5133
|
-
try {
|
|
5134
|
-
execFileSync8("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
|
|
5135
|
-
} catch {
|
|
5136
|
-
}
|
|
5137
|
-
}
|
|
5138
|
-
var init_gha = __esm({
|
|
5139
|
-
"src/gha.ts"() {
|
|
5140
|
-
"use strict";
|
|
5141
|
-
}
|
|
5142
|
-
});
|
|
5143
|
-
|
|
5144
5171
|
// src/stateBranch.ts
|
|
5145
5172
|
function is404(err) {
|
|
5146
5173
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5576,10 +5603,10 @@ var init_state2 = __esm({
|
|
|
5576
5603
|
"use strict";
|
|
5577
5604
|
VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
5578
5605
|
GoalStateError = class extends Error {
|
|
5579
|
-
constructor(
|
|
5580
|
-
super(`Invalid goal state at ${
|
|
5606
|
+
constructor(path45, message) {
|
|
5607
|
+
super(`Invalid goal state at ${path45}:
|
|
5581
5608
|
${message}`);
|
|
5582
|
-
this.path =
|
|
5609
|
+
this.path = path45;
|
|
5583
5610
|
this.name = "GoalStateError";
|
|
5584
5611
|
}
|
|
5585
5612
|
path;
|
|
@@ -6460,7 +6487,7 @@ function dispatchTaskRun(issueNumber, base, cwd) {
|
|
|
6460
6487
|
"-f",
|
|
6461
6488
|
`issue_number=${issueNumber}`,
|
|
6462
6489
|
"-f",
|
|
6463
|
-
"
|
|
6490
|
+
"duty=classify",
|
|
6464
6491
|
"-f",
|
|
6465
6492
|
`base=${base}`
|
|
6466
6493
|
],
|
|
@@ -7186,7 +7213,7 @@ var init_dispatch = __esm({
|
|
|
7186
7213
|
const usePr = target === "pr" && state?.core.prUrl;
|
|
7187
7214
|
const targetNumber = usePr ? parsePr(state.core.prUrl) ?? issueNumber : issueNumber;
|
|
7188
7215
|
ctx.output.nextDispatch = {
|
|
7189
|
-
|
|
7216
|
+
action: next,
|
|
7190
7217
|
cliArgs: usePr ? { pr: targetNumber } : { issue: targetNumber }
|
|
7191
7218
|
};
|
|
7192
7219
|
};
|
|
@@ -7251,228 +7278,62 @@ ${stateBody}`;
|
|
|
7251
7278
|
if (base && getProfileInputs(classification)?.some((i) => i.name === "base")) {
|
|
7252
7279
|
cliArgs.base = base;
|
|
7253
7280
|
}
|
|
7254
|
-
ctx.output.nextDispatch = {
|
|
7281
|
+
ctx.output.nextDispatch = { action: classification, cliArgs };
|
|
7255
7282
|
};
|
|
7256
7283
|
}
|
|
7257
7284
|
});
|
|
7258
7285
|
|
|
7259
|
-
// src/
|
|
7260
|
-
function
|
|
7261
|
-
|
|
7262
|
-
const
|
|
7263
|
-
|
|
7264
|
-
const target = typeof job.target === "number" ? job.target : targetFromCliArgs(job.cliArgs);
|
|
7265
|
-
const work = duty ?? executable;
|
|
7266
|
-
return target === void 0 ? `${job.flavor}:${work}` : `${job.flavor}:${work}:${target}`;
|
|
7286
|
+
// src/scripts/issueStateComment.ts
|
|
7287
|
+
function isStateEnvelope(x) {
|
|
7288
|
+
if (x === null || typeof x !== "object") return false;
|
|
7289
|
+
const o = x;
|
|
7290
|
+
return o.version === 1 && typeof o.rev === "number" && Number.isInteger(o.rev) && o.rev >= 0 && typeof o.cursor === "string" && typeof o.done === "boolean" && o.data !== null && typeof o.data === "object" && !Array.isArray(o.data);
|
|
7267
7291
|
}
|
|
7268
|
-
function
|
|
7269
|
-
|
|
7270
|
-
for (const key of ["issue", "pr", "target", "issue_number"]) {
|
|
7271
|
-
const value = cliArgs[key];
|
|
7272
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
7273
|
-
}
|
|
7274
|
-
return void 0;
|
|
7292
|
+
function initialStateEnvelope(cursor = "seed") {
|
|
7293
|
+
return { version: 1, rev: 0, cursor, data: {}, done: false };
|
|
7275
7294
|
}
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
"use strict";
|
|
7279
|
-
}
|
|
7280
|
-
});
|
|
7295
|
+
function formatStateCommentBody(marker, state) {
|
|
7296
|
+
return `<!-- ${marker} -->
|
|
7281
7297
|
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
InvalidJobError: () => InvalidJobError,
|
|
7287
|
-
mintInstantJob: () => mintInstantJob,
|
|
7288
|
-
mintScheduledJob: () => mintScheduledJob,
|
|
7289
|
-
newJobId: () => newJobId,
|
|
7290
|
-
runJob: () => runJob,
|
|
7291
|
-
stableJobKey: () => stableJobKey,
|
|
7292
|
-
validateJob: () => validateJob
|
|
7293
|
-
});
|
|
7294
|
-
function newJobId(flavor) {
|
|
7295
|
-
localJobSeq += 1;
|
|
7296
|
-
const runId = process.env.GITHUB_RUN_ID;
|
|
7297
|
-
if (runId) return `gh-${runId}-${process.env.GITHUB_RUN_ATTEMPT ?? "1"}-${localJobSeq}`;
|
|
7298
|
-
return `${flavor}-${Date.now()}-${localJobSeq}`;
|
|
7298
|
+
\`\`\`json
|
|
7299
|
+
${JSON.stringify(state, null, 2)}
|
|
7300
|
+
\`\`\`
|
|
7301
|
+
`;
|
|
7299
7302
|
}
|
|
7300
|
-
function
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7303
|
+
function parseStateCommentBody(marker, body) {
|
|
7304
|
+
const markerLine = `<!-- ${marker} -->`;
|
|
7305
|
+
if (!body.trimStart().startsWith(markerLine)) return null;
|
|
7306
|
+
const fenceOpen = body.indexOf("```json");
|
|
7307
|
+
if (fenceOpen === -1) return null;
|
|
7308
|
+
const after = body.slice(fenceOpen + "```json".length);
|
|
7309
|
+
const fenceClose = after.indexOf("```");
|
|
7310
|
+
if (fenceClose === -1) return null;
|
|
7311
|
+
const jsonText = after.slice(0, fenceClose).trim();
|
|
7312
|
+
let parsed;
|
|
7313
|
+
try {
|
|
7314
|
+
parsed = JSON.parse(jsonText);
|
|
7315
|
+
} catch {
|
|
7316
|
+
return null;
|
|
7307
7317
|
}
|
|
7308
|
-
|
|
7309
|
-
|
|
7318
|
+
return isStateEnvelope(parsed) ? parsed : null;
|
|
7319
|
+
}
|
|
7320
|
+
function listIssueComments(owner, repo, issueNumber, cwd) {
|
|
7321
|
+
const raw = gh(["api", "--paginate", `repos/${owner}/${repo}/issues/${issueNumber}/comments`], { cwd });
|
|
7322
|
+
let parsed;
|
|
7323
|
+
try {
|
|
7324
|
+
parsed = JSON.parse(raw);
|
|
7325
|
+
} catch {
|
|
7326
|
+
return [];
|
|
7310
7327
|
}
|
|
7311
|
-
if (
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
schedule: typeof j.schedule === "string" ? j.schedule : void 0,
|
|
7321
|
-
target: typeof j.target === "number" ? j.target : void 0,
|
|
7322
|
-
cliArgs: j.cliArgs ?? {},
|
|
7323
|
-
flavor: j.flavor,
|
|
7324
|
-
force: j.force === true
|
|
7325
|
-
};
|
|
7326
|
-
}
|
|
7327
|
-
async function runJob(job, base) {
|
|
7328
|
-
const valid = validateJob(job);
|
|
7329
|
-
const action = valid.action ?? valid.duty;
|
|
7330
|
-
const resolvedDuty = action ? resolveDutyAction(action) : null;
|
|
7331
|
-
const profileName = valid.executable ?? resolvedDuty?.executable ?? valid.duty;
|
|
7332
|
-
if (!profileName) {
|
|
7333
|
-
throw new InvalidJobError("job resolves to no executable or duty");
|
|
7334
|
-
}
|
|
7335
|
-
const preloadedData = { ...base.preloadedData ?? {} };
|
|
7336
|
-
preloadedData.jobId = newJobId(valid.flavor);
|
|
7337
|
-
preloadedData.jobKey = stableJobKey(valid);
|
|
7338
|
-
preloadedData.jobFlavor = valid.flavor;
|
|
7339
|
-
if (valid.target !== void 0) preloadedData.jobTarget = valid.target;
|
|
7340
|
-
if (valid.action !== void 0 && valid.action.length > 0) preloadedData.jobAction = valid.action;
|
|
7341
|
-
const dutyIdentity = valid.duty ?? resolvedDuty?.duty;
|
|
7342
|
-
if (dutyIdentity !== void 0 && dutyIdentity.length > 0) preloadedData.jobDuty = dutyIdentity;
|
|
7343
|
-
const executableIdentity = valid.executable ?? resolvedDuty?.executable;
|
|
7344
|
-
if (executableIdentity !== void 0 && executableIdentity.length > 0)
|
|
7345
|
-
preloadedData.jobExecutable = executableIdentity;
|
|
7346
|
-
if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
|
|
7347
|
-
const dutyContext = loadDutyContext(dutyIdentity ?? valid.duty);
|
|
7348
|
-
if (dutyContext) {
|
|
7349
|
-
preloadedData.dutySlug = dutyContext.slug;
|
|
7350
|
-
preloadedData.dutyTitle = dutyContext.title;
|
|
7351
|
-
preloadedData.dutyIntent = dutyContext.body;
|
|
7352
|
-
preloadedData.jobIntent = dutyContext.body;
|
|
7353
|
-
if (preloadedData.jobDuty === void 0) preloadedData.jobDuty = dutyContext.slug;
|
|
7354
|
-
if (dutyContext.config.staff && preloadedData.jobPersona === void 0) {
|
|
7355
|
-
preloadedData.jobPersona = dutyContext.config.staff;
|
|
7356
|
-
}
|
|
7357
|
-
if (dutyContext.config.every && preloadedData.jobSchedule === void 0) {
|
|
7358
|
-
preloadedData.jobSchedule = dutyContext.config.every;
|
|
7359
|
-
}
|
|
7360
|
-
if (dutyContext.config.mentions && dutyContext.config.mentions.length > 0) {
|
|
7361
|
-
preloadedData.mentions = dutyContext.config.mentions.map((login) => `@${login}`).join(" ");
|
|
7362
|
-
}
|
|
7363
|
-
}
|
|
7364
|
-
if (valid.why !== void 0 && valid.why.length > 0) preloadedData.jobWhy = valid.why;
|
|
7365
|
-
if (valid.persona !== void 0) preloadedData.jobPersona = valid.persona;
|
|
7366
|
-
const input = {
|
|
7367
|
-
cliArgs: { ...valid.cliArgs },
|
|
7368
|
-
cwd: base.cwd,
|
|
7369
|
-
config: base.config,
|
|
7370
|
-
skipConfig: base.skipConfig,
|
|
7371
|
-
verbose: base.verbose,
|
|
7372
|
-
quiet: base.quiet,
|
|
7373
|
-
preloadedData: Object.keys(preloadedData).length > 0 ? preloadedData : void 0
|
|
7374
|
-
};
|
|
7375
|
-
input.cliArgs = resolvedDuty ? { ...resolvedDuty.cliArgs, ...input.cliArgs } : input.cliArgs;
|
|
7376
|
-
const run = base.chain === false ? runExecutable : runExecutableChain;
|
|
7377
|
-
return run(profileName, input);
|
|
7378
|
-
}
|
|
7379
|
-
function loadDutyContext(slug) {
|
|
7380
|
-
if (!slug) return null;
|
|
7381
|
-
return readDutyFolder(getProjectDutiesRoot(), slug) ?? readDutyFolder(getBuiltinDutiesRoot(), slug);
|
|
7382
|
-
}
|
|
7383
|
-
function mintInstantJob(dispatch2, opts) {
|
|
7384
|
-
return {
|
|
7385
|
-
action: dispatch2.action,
|
|
7386
|
-
executable: dispatch2.executable,
|
|
7387
|
-
duty: dispatch2.duty,
|
|
7388
|
-
why: opts?.why ?? dispatch2.why,
|
|
7389
|
-
persona: opts?.persona ?? DEFAULT_INSTANT_PERSONA,
|
|
7390
|
-
target: dispatch2.target,
|
|
7391
|
-
cliArgs: dispatch2.cliArgs,
|
|
7392
|
-
flavor: "instant"
|
|
7393
|
-
};
|
|
7394
|
-
}
|
|
7395
|
-
function mintScheduledJob(input) {
|
|
7396
|
-
return {
|
|
7397
|
-
duty: input.duty,
|
|
7398
|
-
executable: input.executable,
|
|
7399
|
-
schedule: input.schedule,
|
|
7400
|
-
persona: input.persona,
|
|
7401
|
-
cliArgs: input.cliArgs ?? {},
|
|
7402
|
-
flavor: "scheduled"
|
|
7403
|
-
};
|
|
7404
|
-
}
|
|
7405
|
-
var DEFAULT_INSTANT_PERSONA, localJobSeq, InvalidJobError;
|
|
7406
|
-
var init_job = __esm({
|
|
7407
|
-
"src/job.ts"() {
|
|
7408
|
-
"use strict";
|
|
7409
|
-
init_executor();
|
|
7410
|
-
init_dutyFolders();
|
|
7411
|
-
init_registry();
|
|
7412
|
-
init_jobIdentity();
|
|
7413
|
-
init_jobIdentity();
|
|
7414
|
-
DEFAULT_INSTANT_PERSONA = "kody";
|
|
7415
|
-
localJobSeq = 0;
|
|
7416
|
-
InvalidJobError = class extends Error {
|
|
7417
|
-
constructor(message) {
|
|
7418
|
-
super(message);
|
|
7419
|
-
this.name = "InvalidJobError";
|
|
7420
|
-
}
|
|
7421
|
-
};
|
|
7422
|
-
}
|
|
7423
|
-
});
|
|
7424
|
-
|
|
7425
|
-
// src/scripts/issueStateComment.ts
|
|
7426
|
-
function isStateEnvelope(x) {
|
|
7427
|
-
if (x === null || typeof x !== "object") return false;
|
|
7428
|
-
const o = x;
|
|
7429
|
-
return o.version === 1 && typeof o.rev === "number" && Number.isInteger(o.rev) && o.rev >= 0 && typeof o.cursor === "string" && typeof o.done === "boolean" && o.data !== null && typeof o.data === "object" && !Array.isArray(o.data);
|
|
7430
|
-
}
|
|
7431
|
-
function initialStateEnvelope(cursor = "seed") {
|
|
7432
|
-
return { version: 1, rev: 0, cursor, data: {}, done: false };
|
|
7433
|
-
}
|
|
7434
|
-
function formatStateCommentBody(marker, state) {
|
|
7435
|
-
return `<!-- ${marker} -->
|
|
7436
|
-
|
|
7437
|
-
\`\`\`json
|
|
7438
|
-
${JSON.stringify(state, null, 2)}
|
|
7439
|
-
\`\`\`
|
|
7440
|
-
`;
|
|
7441
|
-
}
|
|
7442
|
-
function parseStateCommentBody(marker, body) {
|
|
7443
|
-
const markerLine = `<!-- ${marker} -->`;
|
|
7444
|
-
if (!body.trimStart().startsWith(markerLine)) return null;
|
|
7445
|
-
const fenceOpen = body.indexOf("```json");
|
|
7446
|
-
if (fenceOpen === -1) return null;
|
|
7447
|
-
const after = body.slice(fenceOpen + "```json".length);
|
|
7448
|
-
const fenceClose = after.indexOf("```");
|
|
7449
|
-
if (fenceClose === -1) return null;
|
|
7450
|
-
const jsonText = after.slice(0, fenceClose).trim();
|
|
7451
|
-
let parsed;
|
|
7452
|
-
try {
|
|
7453
|
-
parsed = JSON.parse(jsonText);
|
|
7454
|
-
} catch {
|
|
7455
|
-
return null;
|
|
7456
|
-
}
|
|
7457
|
-
return isStateEnvelope(parsed) ? parsed : null;
|
|
7458
|
-
}
|
|
7459
|
-
function listIssueComments(owner, repo, issueNumber, cwd) {
|
|
7460
|
-
const raw = gh(["api", "--paginate", `repos/${owner}/${repo}/issues/${issueNumber}/comments`], { cwd });
|
|
7461
|
-
let parsed;
|
|
7462
|
-
try {
|
|
7463
|
-
parsed = JSON.parse(raw);
|
|
7464
|
-
} catch {
|
|
7465
|
-
return [];
|
|
7466
|
-
}
|
|
7467
|
-
if (!Array.isArray(parsed)) return [];
|
|
7468
|
-
return parsed.filter((c) => typeof c.id === "number" && typeof c.node_id === "string" && typeof c.body === "string").map((c) => ({ id: c.id, node_id: c.node_id, body: c.body }));
|
|
7469
|
-
}
|
|
7470
|
-
function findStateComment2(owner, repo, issueNumber, marker, cwd) {
|
|
7471
|
-
const comments = listIssueComments(owner, repo, issueNumber, cwd);
|
|
7472
|
-
for (const c of comments) {
|
|
7473
|
-
const state = parseStateCommentBody(marker, c.body);
|
|
7474
|
-
if (!state) continue;
|
|
7475
|
-
return { commentId: c.id, commentNodeId: c.node_id, state };
|
|
7328
|
+
if (!Array.isArray(parsed)) return [];
|
|
7329
|
+
return parsed.filter((c) => typeof c.id === "number" && typeof c.node_id === "string" && typeof c.body === "string").map((c) => ({ id: c.id, node_id: c.node_id, body: c.body }));
|
|
7330
|
+
}
|
|
7331
|
+
function findStateComment2(owner, repo, issueNumber, marker, cwd) {
|
|
7332
|
+
const comments = listIssueComments(owner, repo, issueNumber, cwd);
|
|
7333
|
+
for (const c of comments) {
|
|
7334
|
+
const state = parseStateCommentBody(marker, c.body);
|
|
7335
|
+
if (!state) continue;
|
|
7336
|
+
return { commentId: c.id, commentNodeId: c.node_id, state };
|
|
7476
7337
|
}
|
|
7477
7338
|
return null;
|
|
7478
7339
|
}
|
|
@@ -7518,9 +7379,13 @@ function isStateUnchanged(prev, next) {
|
|
|
7518
7379
|
return JSON.stringify(prev.data) === JSON.stringify(next.data);
|
|
7519
7380
|
}
|
|
7520
7381
|
function stateFilePath(jobsDir, slug) {
|
|
7521
|
-
return `${jobsDir.replace(/\/+$/, "")}/${slug}
|
|
7382
|
+
return `${jobsDir.replace(/\/+$/, "")}/${slug}/state.json`;
|
|
7522
7383
|
}
|
|
7523
7384
|
function slugFromStateFilePath(filePath) {
|
|
7385
|
+
if (/\/state\.json$/i.test(filePath)) {
|
|
7386
|
+
const parts = filePath.split("/");
|
|
7387
|
+
return parts.at(-2) ?? filePath;
|
|
7388
|
+
}
|
|
7524
7389
|
const last = filePath.split("/").pop() ?? filePath;
|
|
7525
7390
|
return last.replace(/\.state\.json$/i, "");
|
|
7526
7391
|
}
|
|
@@ -7821,6 +7686,29 @@ var init_jobState = __esm({
|
|
|
7821
7686
|
}
|
|
7822
7687
|
});
|
|
7823
7688
|
|
|
7689
|
+
// src/jobIdentity.ts
|
|
7690
|
+
function stableJobKey(job) {
|
|
7691
|
+
const duty = job.duty ?? job.action;
|
|
7692
|
+
const executable = job.executable ?? duty ?? "unknown";
|
|
7693
|
+
if (job.flavor === "scheduled" && job.duty) return `scheduled:${job.duty}:${executable}`;
|
|
7694
|
+
const target = typeof job.target === "number" ? job.target : targetFromCliArgs(job.cliArgs);
|
|
7695
|
+
const work = duty && executable && executable !== duty ? `${duty}:${executable}` : duty ?? executable;
|
|
7696
|
+
return target === void 0 ? `${job.flavor}:${work}` : `${job.flavor}:${work}:${target}`;
|
|
7697
|
+
}
|
|
7698
|
+
function targetFromCliArgs(cliArgs) {
|
|
7699
|
+
if (!cliArgs) return void 0;
|
|
7700
|
+
for (const key of ["issue", "pr", "target", "issue_number"]) {
|
|
7701
|
+
const value = cliArgs[key];
|
|
7702
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
7703
|
+
}
|
|
7704
|
+
return void 0;
|
|
7705
|
+
}
|
|
7706
|
+
var init_jobIdentity = __esm({
|
|
7707
|
+
"src/jobIdentity.ts"() {
|
|
7708
|
+
"use strict";
|
|
7709
|
+
}
|
|
7710
|
+
});
|
|
7711
|
+
|
|
7824
7712
|
// src/scripts/planTaskJobs.ts
|
|
7825
7713
|
function parseTaskJobSpecs(body) {
|
|
7826
7714
|
const match = body.match(new RegExp(`<!--\\s*${TASK_JOBS_MARKER}\\s*([\\s\\S]*?)-->`));
|
|
@@ -7838,7 +7726,7 @@ function taskJobSpecToJob(spec, issueNumber) {
|
|
|
7838
7726
|
const cliArgs = spec.cliArgs ?? { issue: issueNumber };
|
|
7839
7727
|
const target = typeof spec.target === "number" ? spec.target : targetFromCliArgs(cliArgs) ?? issueNumber;
|
|
7840
7728
|
return {
|
|
7841
|
-
duty: spec.duty,
|
|
7729
|
+
duty: spec.duty ?? spec.executable,
|
|
7842
7730
|
executable: spec.executable,
|
|
7843
7731
|
why: spec.reason,
|
|
7844
7732
|
persona: spec.persona ?? spec.staff,
|
|
@@ -7881,7 +7769,7 @@ function jobToPlannedTaskJob(job) {
|
|
|
7881
7769
|
return {
|
|
7882
7770
|
id: stableJobKey(job),
|
|
7883
7771
|
executable: job.executable ?? job.duty ?? "unknown",
|
|
7884
|
-
|
|
7772
|
+
duty: job.duty ?? job.action ?? job.executable ?? "unknown",
|
|
7885
7773
|
...job.persona ? { staff: job.persona } : {},
|
|
7886
7774
|
...job.flavor ? { flavor: job.flavor } : {},
|
|
7887
7775
|
...job.schedule ? { schedule: job.schedule } : {},
|
|
@@ -8032,9 +7920,9 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8032
7920
|
init_dutyFolders();
|
|
8033
7921
|
init_issue();
|
|
8034
7922
|
init_job();
|
|
8035
|
-
init_scheduleEvery();
|
|
8036
7923
|
init_jobState();
|
|
8037
7924
|
init_planTaskJobs();
|
|
7925
|
+
init_scheduleEvery();
|
|
8038
7926
|
dispatchDutyFileTicks = async (ctx, _profile, args) => {
|
|
8039
7927
|
ctx.skipAgent = true;
|
|
8040
7928
|
const targetExecutable = String(args?.targetExecutable ?? "");
|
|
@@ -8127,7 +8015,7 @@ var init_dispatchDutyFileTicks = __esm({
|
|
|
8127
8015
|
continue;
|
|
8128
8016
|
}
|
|
8129
8017
|
const slugTarget = config.tickScript ? scriptedExecutable : config.executable ?? targetExecutable;
|
|
8130
|
-
const cliArgs =
|
|
8018
|
+
const cliArgs = { [slugArg]: slug };
|
|
8131
8019
|
process.stdout.write(`[jobs] \u2192 tick ${slug} (${slugTarget})
|
|
8132
8020
|
`);
|
|
8133
8021
|
try {
|
|
@@ -8193,8 +8081,8 @@ var dispatchDutyTicks;
|
|
|
8193
8081
|
var init_dispatchDutyTicks = __esm({
|
|
8194
8082
|
"src/scripts/dispatchDutyTicks.ts"() {
|
|
8195
8083
|
"use strict";
|
|
8196
|
-
init_executor();
|
|
8197
8084
|
init_issue();
|
|
8085
|
+
init_job();
|
|
8198
8086
|
dispatchDutyTicks = async (ctx, _profile, args) => {
|
|
8199
8087
|
ctx.skipAgent = true;
|
|
8200
8088
|
const label = String(args?.label ?? "");
|
|
@@ -8216,13 +8104,14 @@ var init_dispatchDutyTicks = __esm({
|
|
|
8216
8104
|
process.stdout.write(`[jobs] \u2192 tick #${issue.number}: ${issue.title}
|
|
8217
8105
|
`);
|
|
8218
8106
|
try {
|
|
8219
|
-
const out = await
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8107
|
+
const out = await runJob(
|
|
8108
|
+
mintScheduledJob({
|
|
8109
|
+
duty: targetExecutable,
|
|
8110
|
+
executable: targetExecutable,
|
|
8111
|
+
cliArgs: { [issueArg]: issue.number }
|
|
8112
|
+
}),
|
|
8113
|
+
{ cwd: ctx.cwd, config: ctx.config, verbose: ctx.verbose, quiet: ctx.quiet, chain: false }
|
|
8114
|
+
);
|
|
8226
8115
|
results.push({ issue: issue.number, exitCode: out.exitCode, reason: out.reason });
|
|
8227
8116
|
if (out.exitCode !== 0) {
|
|
8228
8117
|
process.stderr.write(`[jobs] tick #${issue.number} failed (exit ${out.exitCode}): ${out.reason ?? ""}
|
|
@@ -8277,8 +8166,8 @@ var init_dispatchNextTask = __esm({
|
|
|
8277
8166
|
function taskJobToJob(job, issueArg) {
|
|
8278
8167
|
const target = typeof job.target === "number" ? job.target : typeof issueArg === "number" ? issueArg : void 0;
|
|
8279
8168
|
return {
|
|
8169
|
+
duty: job.duty ?? job.executable,
|
|
8280
8170
|
executable: job.executable,
|
|
8281
|
-
...job.duty ? { duty: job.duty } : {},
|
|
8282
8171
|
...job.reason ? { why: job.reason } : {},
|
|
8283
8172
|
...job.staff ? { persona: job.staff } : {},
|
|
8284
8173
|
...job.schedule ? { schedule: job.schedule } : {},
|
|
@@ -8289,7 +8178,7 @@ function taskJobToJob(job, issueArg) {
|
|
|
8289
8178
|
function isJob(input) {
|
|
8290
8179
|
if (!input || typeof input !== "object" || Array.isArray(input)) return false;
|
|
8291
8180
|
const job = input;
|
|
8292
|
-
return typeof job.
|
|
8181
|
+
return (typeof job.duty === "string" || typeof job.action === "string") && (job.flavor === "instant" || job.flavor === "scheduled") && (!job.cliArgs || typeof job.cliArgs === "object" && !Array.isArray(job.cliArgs));
|
|
8293
8182
|
}
|
|
8294
8183
|
var dispatchNextTaskJob;
|
|
8295
8184
|
var init_dispatchNextTaskJob = __esm({
|
|
@@ -8310,7 +8199,7 @@ var init_dispatchNextTaskJob = __esm({
|
|
|
8310
8199
|
const plannedJobs = Array.isArray(ctx.data.plannedTaskJobs) ? ctx.data.plannedTaskJobs.filter(isJob) : [];
|
|
8311
8200
|
ctx.output.nextJob = plannedJobs.find((job) => stableJobKey(job) === next.id) ?? taskJobToJob(next, ctx.args.issue);
|
|
8312
8201
|
if (typeof ctx.args.issue === "number") {
|
|
8313
|
-
ctx.output.afterNextJob = {
|
|
8202
|
+
ctx.output.afterNextJob = { action: profile.action ?? profile.name, cliArgs: { issue: ctx.args.issue } };
|
|
8314
8203
|
}
|
|
8315
8204
|
};
|
|
8316
8205
|
}
|
|
@@ -8630,6 +8519,7 @@ var init_failOnceTaskJob = __esm({
|
|
|
8630
8519
|
ctx.skipAgent = true;
|
|
8631
8520
|
const issue = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
|
|
8632
8521
|
const fallbackJob = {
|
|
8522
|
+
duty: profile.action ?? profile.name,
|
|
8633
8523
|
executable: profile.name,
|
|
8634
8524
|
flavor: "instant",
|
|
8635
8525
|
...typeof issue === "number" ? { target: issue, cliArgs: { issue } } : { cliArgs: {} }
|
|
@@ -9528,9 +9418,9 @@ function performInit(cwd, force) {
|
|
|
9528
9418
|
return { wrote, skipped, labels };
|
|
9529
9419
|
}
|
|
9530
9420
|
function renderScheduledWorkflow(name, cron) {
|
|
9531
|
-
return `# Scheduled kody
|
|
9421
|
+
return `# Scheduled kody duty: ${name}
|
|
9532
9422
|
# Generated by \`kody init\`. Regenerate with \`kody init --force\`.
|
|
9533
|
-
# Edit the cron below or the
|
|
9423
|
+
# Edit the cron below or the duty's implementation profile.json#schedule.
|
|
9534
9424
|
|
|
9535
9425
|
name: kody ${name}
|
|
9536
9426
|
|
|
@@ -12491,11 +12381,46 @@ var init_runPreviewBuild = __esm({
|
|
|
12491
12381
|
}
|
|
12492
12382
|
});
|
|
12493
12383
|
|
|
12494
|
-
// src/scripts/
|
|
12384
|
+
// src/scripts/tickShellRunner.ts
|
|
12495
12385
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12386
|
+
function runTickShellAndParse(opts) {
|
|
12387
|
+
const childEnv = buildTickChildEnv(process.env, opts.force);
|
|
12388
|
+
const result = spawnSync2("bash", [opts.scriptPath], {
|
|
12389
|
+
cwd: opts.ctx.cwd,
|
|
12390
|
+
env: childEnv,
|
|
12391
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12392
|
+
encoding: "utf-8",
|
|
12393
|
+
timeout: 5 * 60 * 1e3,
|
|
12394
|
+
maxBuffer: 16 * 1024 * 1024
|
|
12395
|
+
});
|
|
12396
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
12397
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
12398
|
+
if (result.error) {
|
|
12399
|
+
opts.ctx.output.exitCode = 99;
|
|
12400
|
+
opts.ctx.output.reason = `${opts.displayName}: spawn error: ${result.error.message}`;
|
|
12401
|
+
return;
|
|
12402
|
+
}
|
|
12403
|
+
if (result.signal) {
|
|
12404
|
+
opts.ctx.output.exitCode = 124;
|
|
12405
|
+
opts.ctx.output.reason = `${opts.displayName}: ${opts.reasonSubject ? `${opts.reasonSubject} ` : ""}killed by ${result.signal} (likely 5min timeout)`;
|
|
12406
|
+
return;
|
|
12407
|
+
}
|
|
12408
|
+
if (result.status !== 0) {
|
|
12409
|
+
opts.ctx.output.exitCode = result.status ?? 99;
|
|
12410
|
+
opts.ctx.output.reason = `${opts.displayName}: ${opts.reasonSubject ? `${opts.reasonSubject} ` : ""}exited ${result.status}`;
|
|
12411
|
+
return;
|
|
12412
|
+
}
|
|
12413
|
+
const prevRev = opts.loaded.state.rev ?? 0;
|
|
12414
|
+
const parsed = extractNextStateFromText(result.stdout ?? "", opts.fenceLabel, prevRev);
|
|
12415
|
+
if (parsed.error) {
|
|
12416
|
+
opts.ctx.data.nextStateParseError = parsed.error;
|
|
12417
|
+
opts.ctx.output.exitCode = 1;
|
|
12418
|
+
opts.ctx.output.reason = `${opts.displayName}: ${parsed.error}`;
|
|
12419
|
+
return;
|
|
12420
|
+
}
|
|
12421
|
+
opts.ctx.data.nextJobState = parsed.envelope;
|
|
12422
|
+
}
|
|
12423
|
+
function buildTickChildEnv(parent, force) {
|
|
12499
12424
|
const allow = /* @__PURE__ */ new Set([
|
|
12500
12425
|
"PATH",
|
|
12501
12426
|
"HOME",
|
|
@@ -12506,45 +12431,104 @@ function buildChildEnv(parent, force) {
|
|
|
12506
12431
|
"LANG",
|
|
12507
12432
|
"LC_ALL",
|
|
12508
12433
|
"TERM",
|
|
12509
|
-
// GitHub auth — `gh` reads these.
|
|
12510
12434
|
"GH_TOKEN",
|
|
12511
12435
|
"GH_PAT",
|
|
12512
12436
|
"GITHUB_TOKEN",
|
|
12513
|
-
// CI metadata commonly read by tick scripts (`gh repo view`,
|
|
12514
|
-
// workflow run links, etc.). All public values from GitHub Actions.
|
|
12515
|
-
"GITHUB_ACTIONS",
|
|
12516
|
-
"GITHUB_ACTOR",
|
|
12517
12437
|
"GITHUB_REPOSITORY",
|
|
12518
|
-
"GITHUB_REPOSITORY_OWNER",
|
|
12519
12438
|
"GITHUB_REF",
|
|
12520
12439
|
"GITHUB_SHA",
|
|
12521
12440
|
"GITHUB_RUN_ID",
|
|
12522
|
-
"
|
|
12441
|
+
"GITHUB_RUN_ATTEMPT",
|
|
12523
12442
|
"GITHUB_WORKFLOW",
|
|
12524
|
-
"
|
|
12525
|
-
"
|
|
12526
|
-
"
|
|
12527
|
-
"
|
|
12528
|
-
"RUNNER_OS",
|
|
12529
|
-
"RUNNER_ARCH"
|
|
12443
|
+
"GITHUB_ACTIONS",
|
|
12444
|
+
"CI",
|
|
12445
|
+
"KODY_DRY_RUN",
|
|
12446
|
+
"KODY_NO_COMMIT"
|
|
12530
12447
|
]);
|
|
12531
|
-
const
|
|
12448
|
+
const env = {};
|
|
12532
12449
|
for (const [key, value] of Object.entries(parent)) {
|
|
12533
12450
|
if (value === void 0) continue;
|
|
12534
|
-
if (allow.has(key) || key.startsWith("KODY_PUBLIC_"))
|
|
12535
|
-
out[key] = value;
|
|
12536
|
-
}
|
|
12451
|
+
if (allow.has(key) || key.startsWith("KODY_PUBLIC_")) env[key] = value;
|
|
12537
12452
|
}
|
|
12538
|
-
if (force)
|
|
12539
|
-
return
|
|
12453
|
+
if (force) env.KODY_FORCE = "1";
|
|
12454
|
+
return env;
|
|
12540
12455
|
}
|
|
12456
|
+
var init_tickShellRunner = __esm({
|
|
12457
|
+
"src/scripts/tickShellRunner.ts"() {
|
|
12458
|
+
"use strict";
|
|
12459
|
+
init_parseJobStateFromAgentResult();
|
|
12460
|
+
}
|
|
12461
|
+
});
|
|
12462
|
+
|
|
12463
|
+
// src/scripts/runScheduledExecutableTick.ts
|
|
12464
|
+
import * as fs36 from "fs";
|
|
12465
|
+
import * as path35 from "path";
|
|
12466
|
+
var runScheduledExecutableTick;
|
|
12467
|
+
var init_runScheduledExecutableTick = __esm({
|
|
12468
|
+
"src/scripts/runScheduledExecutableTick.ts"() {
|
|
12469
|
+
"use strict";
|
|
12470
|
+
init_dutyFolders();
|
|
12471
|
+
init_jobState();
|
|
12472
|
+
init_tickShellRunner();
|
|
12473
|
+
runScheduledExecutableTick = async (ctx, profile, args) => {
|
|
12474
|
+
ctx.skipAgent = true;
|
|
12475
|
+
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
12476
|
+
const slugArg = String(args?.slugArg ?? "duty");
|
|
12477
|
+
const fenceLabel = String(args?.fenceLabel ?? "kody-job-next-state");
|
|
12478
|
+
const shell = String(args?.shell ?? "tick.sh");
|
|
12479
|
+
const slug = String(ctx.args[slugArg] ?? "").trim();
|
|
12480
|
+
if (!slug) {
|
|
12481
|
+
ctx.output.exitCode = 99;
|
|
12482
|
+
ctx.output.reason = `runScheduledExecutableTick: ctx.args.${slugArg} must be non-empty duty slug`;
|
|
12483
|
+
return;
|
|
12484
|
+
}
|
|
12485
|
+
const duty = readDutyFolder(path35.join(ctx.cwd, jobsDir), slug);
|
|
12486
|
+
if (!duty) {
|
|
12487
|
+
ctx.output.exitCode = 99;
|
|
12488
|
+
ctx.output.reason = `runScheduledExecutableTick: duty folder not found or incomplete: ${path35.join(jobsDir, slug)}`;
|
|
12489
|
+
return;
|
|
12490
|
+
}
|
|
12491
|
+
const shellPath = path35.join(profile.dir, shell);
|
|
12492
|
+
if (!fs36.existsSync(shellPath)) {
|
|
12493
|
+
ctx.output.exitCode = 99;
|
|
12494
|
+
ctx.output.reason = `runScheduledExecutableTick: shell not found: ${shell} (looked in ${profile.dir})`;
|
|
12495
|
+
return;
|
|
12496
|
+
}
|
|
12497
|
+
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
12498
|
+
let loaded;
|
|
12499
|
+
try {
|
|
12500
|
+
loaded = await backend.load(slug);
|
|
12501
|
+
} catch (err) {
|
|
12502
|
+
ctx.output.exitCode = 99;
|
|
12503
|
+
ctx.output.reason = `runScheduledExecutableTick: state load failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
12504
|
+
return;
|
|
12505
|
+
}
|
|
12506
|
+
ctx.data.jobSlug = slug;
|
|
12507
|
+
ctx.data.dutySlug = slug;
|
|
12508
|
+
ctx.data.executableSlug = profile.name;
|
|
12509
|
+
ctx.data.jobState = loaded;
|
|
12510
|
+
runTickShellAndParse({
|
|
12511
|
+
ctx,
|
|
12512
|
+
loaded,
|
|
12513
|
+
scriptPath: shellPath,
|
|
12514
|
+
displayName: `runScheduledExecutableTick: ${shell}`,
|
|
12515
|
+
fenceLabel,
|
|
12516
|
+
force: Boolean(ctx.args.force)
|
|
12517
|
+
});
|
|
12518
|
+
};
|
|
12519
|
+
}
|
|
12520
|
+
});
|
|
12521
|
+
|
|
12522
|
+
// src/scripts/runTickScript.ts
|
|
12523
|
+
import * as fs37 from "fs";
|
|
12524
|
+
import * as path36 from "path";
|
|
12541
12525
|
var runTickScript;
|
|
12542
12526
|
var init_runTickScript = __esm({
|
|
12543
12527
|
"src/scripts/runTickScript.ts"() {
|
|
12544
12528
|
"use strict";
|
|
12545
12529
|
init_dutyFolders();
|
|
12546
12530
|
init_jobState();
|
|
12547
|
-
|
|
12531
|
+
init_tickShellRunner();
|
|
12548
12532
|
runTickScript = async (ctx, _profile, args) => {
|
|
12549
12533
|
ctx.skipAgent = true;
|
|
12550
12534
|
const jobsDir = String(args?.jobsDir ?? ".kody/duties");
|
|
@@ -12556,10 +12540,10 @@ var init_runTickScript = __esm({
|
|
|
12556
12540
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
12557
12541
|
return;
|
|
12558
12542
|
}
|
|
12559
|
-
const duty = readDutyFolder(
|
|
12543
|
+
const duty = readDutyFolder(path36.join(ctx.cwd, jobsDir), slug);
|
|
12560
12544
|
if (!duty) {
|
|
12561
12545
|
ctx.output.exitCode = 99;
|
|
12562
|
-
ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${
|
|
12546
|
+
ctx.output.reason = `runTickScript: duty folder not found or incomplete: ${path36.join(ctx.cwd, jobsDir, slug)}`;
|
|
12563
12547
|
return;
|
|
12564
12548
|
}
|
|
12565
12549
|
const tickScript = duty.config.tickScript;
|
|
@@ -12568,8 +12552,8 @@ var init_runTickScript = __esm({
|
|
|
12568
12552
|
ctx.output.reason = `runTickScript: duty ${slug} has no \`tickScript\` in profile.json \u2014 route via duty-tick instead`;
|
|
12569
12553
|
return;
|
|
12570
12554
|
}
|
|
12571
|
-
const scriptPath =
|
|
12572
|
-
if (!
|
|
12555
|
+
const scriptPath = path36.isAbsolute(tickScript) ? tickScript : path36.join(ctx.cwd, tickScript);
|
|
12556
|
+
if (!fs37.existsSync(scriptPath)) {
|
|
12573
12557
|
ctx.output.exitCode = 99;
|
|
12574
12558
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
12575
12559
|
return;
|
|
@@ -12585,46 +12569,15 @@ var init_runTickScript = __esm({
|
|
|
12585
12569
|
}
|
|
12586
12570
|
ctx.data.jobSlug = slug;
|
|
12587
12571
|
ctx.data.jobState = loaded;
|
|
12588
|
-
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
// busy repo (or an accidental `set -x`) can blow that and silently
|
|
12597
|
-
// truncate stdout, which is the exact "silent state drop" failure
|
|
12598
|
-
// mode this whole executable was written to prevent. 16MB is well
|
|
12599
|
-
// above any realistic tick output.
|
|
12600
|
-
maxBuffer: 16 * 1024 * 1024
|
|
12572
|
+
runTickShellAndParse({
|
|
12573
|
+
ctx,
|
|
12574
|
+
loaded,
|
|
12575
|
+
scriptPath,
|
|
12576
|
+
displayName: "runTickScript",
|
|
12577
|
+
reasonSubject: tickScript,
|
|
12578
|
+
fenceLabel,
|
|
12579
|
+
force: Boolean(ctx.args.force)
|
|
12601
12580
|
});
|
|
12602
|
-
if (result.stdout) process.stdout.write(result.stdout);
|
|
12603
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
12604
|
-
if (result.error) {
|
|
12605
|
-
ctx.output.exitCode = 99;
|
|
12606
|
-
ctx.output.reason = `runTickScript: spawn error: ${result.error.message}`;
|
|
12607
|
-
return;
|
|
12608
|
-
}
|
|
12609
|
-
if (result.signal) {
|
|
12610
|
-
ctx.output.exitCode = 124;
|
|
12611
|
-
ctx.output.reason = `runTickScript: ${tickScript} killed by ${result.signal} (likely 5min timeout)`;
|
|
12612
|
-
return;
|
|
12613
|
-
}
|
|
12614
|
-
if (result.status !== 0) {
|
|
12615
|
-
ctx.output.exitCode = result.status ?? 99;
|
|
12616
|
-
ctx.output.reason = `runTickScript: ${tickScript} exited ${result.status}`;
|
|
12617
|
-
return;
|
|
12618
|
-
}
|
|
12619
|
-
const prevRev = loaded.state.rev ?? 0;
|
|
12620
|
-
const parsed = extractNextStateFromText(result.stdout ?? "", fenceLabel, prevRev);
|
|
12621
|
-
if (parsed.error) {
|
|
12622
|
-
ctx.data.nextStateParseError = parsed.error;
|
|
12623
|
-
ctx.output.exitCode = 1;
|
|
12624
|
-
ctx.output.reason = `runTickScript: ${parsed.error}`;
|
|
12625
|
-
return;
|
|
12626
|
-
}
|
|
12627
|
-
ctx.data.nextJobState = parsed.envelope;
|
|
12628
12581
|
};
|
|
12629
12582
|
}
|
|
12630
12583
|
});
|
|
@@ -12775,7 +12728,7 @@ var init_startFlow = __esm({
|
|
|
12775
12728
|
const usePr = target === "pr" && !!state?.core.prUrl;
|
|
12776
12729
|
const targetNumber = usePr ? parsePrNumber(state.core.prUrl) ?? issueNumber : issueNumber;
|
|
12777
12730
|
ctx.output.nextDispatch = {
|
|
12778
|
-
|
|
12731
|
+
action: entry,
|
|
12779
12732
|
cliArgs: usePr ? { pr: targetNumber } : { issue: targetNumber }
|
|
12780
12733
|
};
|
|
12781
12734
|
};
|
|
@@ -13533,7 +13486,7 @@ var init_writeJobStateFile = __esm({
|
|
|
13533
13486
|
});
|
|
13534
13487
|
|
|
13535
13488
|
// src/scripts/writeRunSummary.ts
|
|
13536
|
-
import * as
|
|
13489
|
+
import * as fs38 from "fs";
|
|
13537
13490
|
var writeRunSummary;
|
|
13538
13491
|
var init_writeRunSummary = __esm({
|
|
13539
13492
|
"src/scripts/writeRunSummary.ts"() {
|
|
@@ -13559,7 +13512,7 @@ var init_writeRunSummary = __esm({
|
|
|
13559
13512
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
13560
13513
|
lines.push("");
|
|
13561
13514
|
try {
|
|
13562
|
-
|
|
13515
|
+
fs38.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
13563
13516
|
`);
|
|
13564
13517
|
} catch {
|
|
13565
13518
|
}
|
|
@@ -13646,6 +13599,7 @@ var init_scripts = __esm({
|
|
|
13646
13599
|
init_reviewFlow();
|
|
13647
13600
|
init_runFlow();
|
|
13648
13601
|
init_runPreviewBuild();
|
|
13602
|
+
init_runScheduledExecutableTick();
|
|
13649
13603
|
init_runTickScript();
|
|
13650
13604
|
init_saveGoalState();
|
|
13651
13605
|
init_saveTaskState();
|
|
@@ -13704,6 +13658,7 @@ var init_scripts = __esm({
|
|
|
13704
13658
|
dispatchDutyFileTicks,
|
|
13705
13659
|
planTaskJobs,
|
|
13706
13660
|
dispatchNextTaskJob,
|
|
13661
|
+
runScheduledExecutableTick,
|
|
13707
13662
|
runTickScript,
|
|
13708
13663
|
runPreviewBuild,
|
|
13709
13664
|
loadGoalState,
|
|
@@ -13766,8 +13721,8 @@ var init_scripts = __esm({
|
|
|
13766
13721
|
});
|
|
13767
13722
|
|
|
13768
13723
|
// src/staff.ts
|
|
13769
|
-
import * as
|
|
13770
|
-
import * as
|
|
13724
|
+
import * as fs39 from "fs";
|
|
13725
|
+
import * as path37 from "path";
|
|
13771
13726
|
function stripFrontmatter(raw) {
|
|
13772
13727
|
const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
|
|
13773
13728
|
return (match ? match[1] : raw).trim();
|
|
@@ -13775,9 +13730,9 @@ function stripFrontmatter(raw) {
|
|
|
13775
13730
|
function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
|
|
13776
13731
|
const trimmed = slug.trim();
|
|
13777
13732
|
if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
|
|
13778
|
-
const staffPath =
|
|
13779
|
-
if (
|
|
13780
|
-
const body = stripFrontmatter(
|
|
13733
|
+
const staffPath = path37.join(cwd, staffDir, `${trimmed}.md`);
|
|
13734
|
+
if (fs39.existsSync(staffPath)) {
|
|
13735
|
+
const body = stripFrontmatter(fs39.readFileSync(staffPath, "utf-8"));
|
|
13781
13736
|
if (body) return body;
|
|
13782
13737
|
const builtinForEmpty = BUILTIN_PERSONAS[trimmed];
|
|
13783
13738
|
if (builtinForEmpty) return builtinForEmpty;
|
|
@@ -13890,8 +13845,8 @@ var init_tools = __esm({
|
|
|
13890
13845
|
|
|
13891
13846
|
// src/executor.ts
|
|
13892
13847
|
import { spawn as spawn7 } from "child_process";
|
|
13893
|
-
import * as
|
|
13894
|
-
import * as
|
|
13848
|
+
import * as fs40 from "fs";
|
|
13849
|
+
import * as path38 from "path";
|
|
13895
13850
|
function isMutatingPostflight(scriptName) {
|
|
13896
13851
|
return MUTATING_POSTFLIGHTS.has(scriptName ?? "");
|
|
13897
13852
|
}
|
|
@@ -14004,17 +13959,7 @@ async function runExecutable(profileName, input) {
|
|
|
14004
13959
|
reason: `agent.model invalid: ${err instanceof Error ? err.message : String(err)}`
|
|
14005
13960
|
});
|
|
14006
13961
|
}
|
|
14007
|
-
let litellm
|
|
14008
|
-
if (profileName !== "preview-build") {
|
|
14009
|
-
try {
|
|
14010
|
-
litellm = await startLitellmIfNeeded(model, input.cwd);
|
|
14011
|
-
} catch (err) {
|
|
14012
|
-
return finishAndEnd({
|
|
14013
|
-
exitCode: 99,
|
|
14014
|
-
reason: `litellm startup failed: ${err instanceof Error ? err.message : String(err)}`
|
|
14015
|
-
});
|
|
14016
|
-
}
|
|
14017
|
-
}
|
|
13962
|
+
let litellm;
|
|
14018
13963
|
const ctx = {
|
|
14019
13964
|
args,
|
|
14020
13965
|
cwd: input.cwd,
|
|
@@ -14042,16 +13987,23 @@ async function runExecutable(profileName, input) {
|
|
|
14042
13987
|
})
|
|
14043
13988
|
};
|
|
14044
13989
|
})() : null;
|
|
14045
|
-
const ndjsonDir =
|
|
13990
|
+
const ndjsonDir = path38.join(input.cwd, ".kody");
|
|
14046
13991
|
const personaSlug = typeof profile.staff === "string" && profile.staff.length > 0 ? profile.staff : typeof ctx.data.jobPersona === "string" && ctx.data.jobPersona.length > 0 ? ctx.data.jobPersona : null;
|
|
14047
13992
|
const staffPersona = personaSlug ? framePersona(personaSlug, loadStaffPersona(input.cwd, personaSlug)) : null;
|
|
14048
13993
|
const jobWhyBlock = typeof ctx.data.jobWhy === "string" ? operatorRequestBlock(ctx.data.jobWhy) : null;
|
|
14049
13994
|
const jobRefBlock = jobReferenceBlock(profileName, profile, ctx.data);
|
|
14050
13995
|
const invokeAgent = async (prompt) => {
|
|
14051
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
13996
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path38.isAbsolute(p) ? p : path38.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
14052
13997
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
14053
13998
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
14054
13999
|
const agents = loadSubagents(profile);
|
|
14000
|
+
if (litellm === void 0) {
|
|
14001
|
+
try {
|
|
14002
|
+
litellm = await startLitellmIfNeeded(model, input.cwd);
|
|
14003
|
+
} catch (err) {
|
|
14004
|
+
throw new Error(`litellm startup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
14005
|
+
}
|
|
14006
|
+
}
|
|
14055
14007
|
const lm = litellm;
|
|
14056
14008
|
return runAgent({
|
|
14057
14009
|
prompt,
|
|
@@ -14159,7 +14111,14 @@ async function runExecutable(profileName, input) {
|
|
|
14159
14111
|
});
|
|
14160
14112
|
}
|
|
14161
14113
|
emitEvent(input.cwd, { executable: profileName, kind: "agent_start" });
|
|
14162
|
-
|
|
14114
|
+
try {
|
|
14115
|
+
agentResult = await invokeAgent(prompt);
|
|
14116
|
+
} catch (err) {
|
|
14117
|
+
return finishAndEnd({
|
|
14118
|
+
exitCode: 99,
|
|
14119
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
14120
|
+
});
|
|
14121
|
+
}
|
|
14163
14122
|
emitEvent(input.cwd, {
|
|
14164
14123
|
executable: profileName,
|
|
14165
14124
|
kind: "agent_end",
|
|
@@ -14307,8 +14266,8 @@ async function runExecutableChain(profileName, input) {
|
|
|
14307
14266
|
process.stdout.write(`\u2192 kody: in-process job hand-off \u2192 ${label} (hop ${hops}/${MAX_CHAIN_HOPS})
|
|
14308
14267
|
|
|
14309
14268
|
`);
|
|
14310
|
-
const { runJob:
|
|
14311
|
-
const childResult = await
|
|
14269
|
+
const { runJob: runJob3 } = await Promise.resolve().then(() => (init_job(), job_exports));
|
|
14270
|
+
const childResult = await runJob3(next2, {
|
|
14312
14271
|
cwd: input.cwd,
|
|
14313
14272
|
config: input.config,
|
|
14314
14273
|
verbose: input.verbose,
|
|
@@ -14320,12 +14279,24 @@ async function runExecutableChain(profileName, input) {
|
|
|
14320
14279
|
...chainData,
|
|
14321
14280
|
...childResult.taskState ? { taskState: childResult.taskState } : {}
|
|
14322
14281
|
};
|
|
14323
|
-
|
|
14282
|
+
const afterJob = handoffToJob(after);
|
|
14283
|
+
if (!afterJob) {
|
|
14284
|
+
return {
|
|
14285
|
+
exitCode: 99,
|
|
14286
|
+
reason: `in-process return missing duty/action for ${after.executable ?? "unknown"}`
|
|
14287
|
+
};
|
|
14288
|
+
}
|
|
14289
|
+
process.stdout.write(
|
|
14290
|
+
`\u2192 kody: in-process return \u2192 ${afterJob.action ?? afterJob.duty} (hop ${hops}/${MAX_CHAIN_HOPS})
|
|
14324
14291
|
|
|
14325
|
-
`
|
|
14326
|
-
|
|
14327
|
-
|
|
14328
|
-
|
|
14292
|
+
`
|
|
14293
|
+
);
|
|
14294
|
+
const { runJob: runJob4 } = await Promise.resolve().then(() => (init_job(), job_exports));
|
|
14295
|
+
result = await runJob4(afterJob, {
|
|
14296
|
+
cwd: input.cwd,
|
|
14297
|
+
config: input.config,
|
|
14298
|
+
verbose: input.verbose,
|
|
14299
|
+
quiet: input.quiet,
|
|
14329
14300
|
preloadedData: chainData
|
|
14330
14301
|
});
|
|
14331
14302
|
chainData = {
|
|
@@ -14342,10 +14313,26 @@ async function runExecutableChain(profileName, input) {
|
|
|
14342
14313
|
continue;
|
|
14343
14314
|
}
|
|
14344
14315
|
const next = result.nextDispatch;
|
|
14345
|
-
|
|
14316
|
+
const nextJob = handoffToJob(next);
|
|
14317
|
+
if (!nextJob) {
|
|
14318
|
+
return {
|
|
14319
|
+
exitCode: 99,
|
|
14320
|
+
reason: `in-process hand-off missing duty/action for ${next.executable ?? "unknown"}`
|
|
14321
|
+
};
|
|
14322
|
+
}
|
|
14323
|
+
process.stdout.write(
|
|
14324
|
+
`\u2192 kody: in-process hand-off \u2192 ${nextJob.action ?? nextJob.duty} (hop ${hops}/${MAX_CHAIN_HOPS})
|
|
14346
14325
|
|
|
14347
|
-
`
|
|
14348
|
-
|
|
14326
|
+
`
|
|
14327
|
+
);
|
|
14328
|
+
const { runJob: runJob2 } = await Promise.resolve().then(() => (init_job(), job_exports));
|
|
14329
|
+
result = await runJob2(nextJob, {
|
|
14330
|
+
cwd: input.cwd,
|
|
14331
|
+
config: input.config,
|
|
14332
|
+
verbose: input.verbose,
|
|
14333
|
+
quiet: input.quiet,
|
|
14334
|
+
preloadedData: chainData
|
|
14335
|
+
});
|
|
14349
14336
|
chainData = {
|
|
14350
14337
|
...chainData,
|
|
14351
14338
|
...result.taskState ? { taskState: result.taskState } : {}
|
|
@@ -14358,6 +14345,17 @@ async function runExecutableChain(profileName, input) {
|
|
|
14358
14345
|
}
|
|
14359
14346
|
return result;
|
|
14360
14347
|
}
|
|
14348
|
+
function handoffToJob(handoff) {
|
|
14349
|
+
const dutyOrAction = handoff.action ?? handoff.duty;
|
|
14350
|
+
if (!dutyOrAction) return null;
|
|
14351
|
+
return {
|
|
14352
|
+
action: handoff.action ?? handoff.duty,
|
|
14353
|
+
duty: handoff.duty,
|
|
14354
|
+
executable: handoff.executable,
|
|
14355
|
+
cliArgs: handoff.cliArgs,
|
|
14356
|
+
flavor: "instant"
|
|
14357
|
+
};
|
|
14358
|
+
}
|
|
14361
14359
|
function clearStampedLifecycleLabels(profile, ctx) {
|
|
14362
14360
|
const target = ctx.args.issue ?? ctx.args.pr;
|
|
14363
14361
|
if (typeof target !== "number" || !Number.isFinite(target)) return;
|
|
@@ -14374,17 +14372,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
14374
14372
|
function resolveProfilePath(profileName) {
|
|
14375
14373
|
const found = resolveExecutable(profileName);
|
|
14376
14374
|
if (found) return found;
|
|
14377
|
-
const here =
|
|
14375
|
+
const here = path38.dirname(new URL(import.meta.url).pathname);
|
|
14378
14376
|
const candidates = [
|
|
14379
|
-
|
|
14377
|
+
path38.join(here, "executables", profileName, "profile.json"),
|
|
14380
14378
|
// same-dir sibling (dev)
|
|
14381
|
-
|
|
14379
|
+
path38.join(here, "..", "executables", profileName, "profile.json"),
|
|
14382
14380
|
// up one (prod: dist/bin → dist/executables)
|
|
14383
|
-
|
|
14381
|
+
path38.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
14384
14382
|
// fallback
|
|
14385
14383
|
];
|
|
14386
14384
|
for (const c of candidates) {
|
|
14387
|
-
if (
|
|
14385
|
+
if (fs40.existsSync(c)) return c;
|
|
14388
14386
|
}
|
|
14389
14387
|
return candidates[0];
|
|
14390
14388
|
}
|
|
@@ -14482,8 +14480,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
14482
14480
|
}
|
|
14483
14481
|
async function runShellEntry(entry, ctx, profile) {
|
|
14484
14482
|
const shellName = entry.shell;
|
|
14485
|
-
const shellPath =
|
|
14486
|
-
if (!
|
|
14483
|
+
const shellPath = path38.join(profile.dir, shellName);
|
|
14484
|
+
if (!fs40.existsSync(shellPath)) {
|
|
14487
14485
|
ctx.skipAgent = true;
|
|
14488
14486
|
ctx.output.exitCode = 99;
|
|
14489
14487
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -14630,6 +14628,156 @@ var init_executor = __esm({
|
|
|
14630
14628
|
}
|
|
14631
14629
|
});
|
|
14632
14630
|
|
|
14631
|
+
// src/job.ts
|
|
14632
|
+
var job_exports = {};
|
|
14633
|
+
__export(job_exports, {
|
|
14634
|
+
DEFAULT_INSTANT_PERSONA: () => DEFAULT_INSTANT_PERSONA,
|
|
14635
|
+
InvalidJobError: () => InvalidJobError,
|
|
14636
|
+
mintInstantJob: () => mintInstantJob,
|
|
14637
|
+
mintScheduledJob: () => mintScheduledJob,
|
|
14638
|
+
newJobId: () => newJobId,
|
|
14639
|
+
runJob: () => runJob,
|
|
14640
|
+
stableJobKey: () => stableJobKey,
|
|
14641
|
+
validateJob: () => validateJob
|
|
14642
|
+
});
|
|
14643
|
+
import * as path39 from "path";
|
|
14644
|
+
function newJobId(flavor) {
|
|
14645
|
+
localJobSeq += 1;
|
|
14646
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
14647
|
+
if (runId) return `gh-${runId}-${process.env.GITHUB_RUN_ATTEMPT ?? "1"}-${localJobSeq}`;
|
|
14648
|
+
return `${flavor}-${Date.now()}-${localJobSeq}`;
|
|
14649
|
+
}
|
|
14650
|
+
function validateJob(input) {
|
|
14651
|
+
if (!input || typeof input !== "object") {
|
|
14652
|
+
throw new InvalidJobError("job must be an object");
|
|
14653
|
+
}
|
|
14654
|
+
const j = input;
|
|
14655
|
+
if (typeof j.duty !== "string" && typeof j.action !== "string") {
|
|
14656
|
+
throw new InvalidJobError("job must reference a duty action or duty");
|
|
14657
|
+
}
|
|
14658
|
+
if (j.flavor !== "instant" && j.flavor !== "scheduled") {
|
|
14659
|
+
throw new InvalidJobError(`job.flavor must be "instant" or "scheduled" (got ${String(j.flavor)})`);
|
|
14660
|
+
}
|
|
14661
|
+
if (j.cliArgs !== void 0 && (typeof j.cliArgs !== "object" || j.cliArgs === null)) {
|
|
14662
|
+
throw new InvalidJobError("job.cliArgs must be an object when present");
|
|
14663
|
+
}
|
|
14664
|
+
return {
|
|
14665
|
+
action: typeof j.action === "string" ? j.action : void 0,
|
|
14666
|
+
executable: typeof j.executable === "string" ? j.executable : void 0,
|
|
14667
|
+
duty: typeof j.duty === "string" ? j.duty : void 0,
|
|
14668
|
+
why: typeof j.why === "string" ? j.why : void 0,
|
|
14669
|
+
persona: typeof j.persona === "string" ? j.persona : void 0,
|
|
14670
|
+
schedule: typeof j.schedule === "string" ? j.schedule : void 0,
|
|
14671
|
+
target: typeof j.target === "number" ? j.target : void 0,
|
|
14672
|
+
cliArgs: j.cliArgs ?? {},
|
|
14673
|
+
flavor: j.flavor,
|
|
14674
|
+
force: j.force === true
|
|
14675
|
+
};
|
|
14676
|
+
}
|
|
14677
|
+
async function runJob(job, base) {
|
|
14678
|
+
const valid = validateJob(job);
|
|
14679
|
+
const action = valid.action ?? valid.duty;
|
|
14680
|
+
const projectDutiesRoot = path39.join(base.cwd, ".kody", "duties");
|
|
14681
|
+
const resolvedDuty = action ? resolveDutyAction(action, projectDutiesRoot) : null;
|
|
14682
|
+
const dutyIdentity = valid.duty ?? resolvedDuty?.duty;
|
|
14683
|
+
const dutyContext = loadDutyContext(dutyIdentity, base.cwd);
|
|
14684
|
+
if (!resolvedDuty && !dutyContext) {
|
|
14685
|
+
throw new InvalidJobError(`job duty not found: ${action ?? valid.duty ?? "<none>"}`);
|
|
14686
|
+
}
|
|
14687
|
+
const dutySelectedExecutable = resolvedDuty?.executable ?? dutyContext?.config.executable ?? dutyContext?.config.executables?.[0] ?? (dutyContext?.config.tickScript ? "duty-tick-scripted" : void 0);
|
|
14688
|
+
const profileName = valid.executable ?? dutySelectedExecutable;
|
|
14689
|
+
if (!profileName) {
|
|
14690
|
+
throw new InvalidJobError(`job duty resolves to no executable: ${dutyIdentity ?? action}`);
|
|
14691
|
+
}
|
|
14692
|
+
const preloadedData = { ...base.preloadedData ?? {} };
|
|
14693
|
+
preloadedData.jobId = newJobId(valid.flavor);
|
|
14694
|
+
preloadedData.jobKey = stableJobKey(valid);
|
|
14695
|
+
preloadedData.jobFlavor = valid.flavor;
|
|
14696
|
+
if (valid.target !== void 0) preloadedData.jobTarget = valid.target;
|
|
14697
|
+
if (valid.action !== void 0 && valid.action.length > 0) preloadedData.jobAction = valid.action;
|
|
14698
|
+
if (dutyIdentity !== void 0 && dutyIdentity.length > 0) preloadedData.jobDuty = dutyIdentity;
|
|
14699
|
+
const executableIdentity = profileName;
|
|
14700
|
+
if (executableIdentity !== void 0 && executableIdentity.length > 0)
|
|
14701
|
+
preloadedData.jobExecutable = executableIdentity;
|
|
14702
|
+
if (valid.schedule !== void 0 && valid.schedule.length > 0) preloadedData.jobSchedule = valid.schedule;
|
|
14703
|
+
if (dutyContext) {
|
|
14704
|
+
preloadedData.dutySlug = dutyContext.slug;
|
|
14705
|
+
preloadedData.dutyTitle = dutyContext.title;
|
|
14706
|
+
preloadedData.dutyIntent = dutyContext.body;
|
|
14707
|
+
preloadedData.jobIntent = dutyContext.body;
|
|
14708
|
+
if (preloadedData.jobDuty === void 0) preloadedData.jobDuty = dutyContext.slug;
|
|
14709
|
+
if (dutyContext.config.staff && preloadedData.jobPersona === void 0) {
|
|
14710
|
+
preloadedData.jobPersona = dutyContext.config.staff;
|
|
14711
|
+
}
|
|
14712
|
+
if (dutyContext.config.every && preloadedData.jobSchedule === void 0) {
|
|
14713
|
+
preloadedData.jobSchedule = dutyContext.config.every;
|
|
14714
|
+
}
|
|
14715
|
+
if (dutyContext.config.mentions && dutyContext.config.mentions.length > 0) {
|
|
14716
|
+
preloadedData.mentions = dutyContext.config.mentions.map((login) => `@${login}`).join(" ");
|
|
14717
|
+
}
|
|
14718
|
+
}
|
|
14719
|
+
if (valid.why !== void 0 && valid.why.length > 0) preloadedData.jobWhy = valid.why;
|
|
14720
|
+
if (valid.persona !== void 0) preloadedData.jobPersona = valid.persona;
|
|
14721
|
+
const input = {
|
|
14722
|
+
cliArgs: { ...valid.cliArgs },
|
|
14723
|
+
cwd: base.cwd,
|
|
14724
|
+
config: base.config,
|
|
14725
|
+
skipConfig: base.skipConfig,
|
|
14726
|
+
verbose: base.verbose,
|
|
14727
|
+
quiet: base.quiet,
|
|
14728
|
+
preloadedData: Object.keys(preloadedData).length > 0 ? preloadedData : void 0
|
|
14729
|
+
};
|
|
14730
|
+
input.cliArgs = resolvedDuty && profileName === resolvedDuty.executable ? { ...resolvedDuty.cliArgs, ...input.cliArgs } : input.cliArgs;
|
|
14731
|
+
const run = base.chain === false ? runExecutable : runExecutableChain;
|
|
14732
|
+
return run(profileName, input);
|
|
14733
|
+
}
|
|
14734
|
+
function loadDutyContext(slug, cwd) {
|
|
14735
|
+
if (!slug) return null;
|
|
14736
|
+
return readDutyFolder(path39.join(cwd, ".kody", "duties"), slug) ?? readDutyFolder(getProjectDutiesRoot(), slug) ?? readDutyFolder(getBuiltinDutiesRoot(), slug);
|
|
14737
|
+
}
|
|
14738
|
+
function mintInstantJob(dispatch2, opts) {
|
|
14739
|
+
return {
|
|
14740
|
+
action: dispatch2.action,
|
|
14741
|
+
executable: dispatch2.executable,
|
|
14742
|
+
duty: dispatch2.duty,
|
|
14743
|
+
why: opts?.why ?? dispatch2.why,
|
|
14744
|
+
persona: opts?.persona ?? DEFAULT_INSTANT_PERSONA,
|
|
14745
|
+
target: dispatch2.target,
|
|
14746
|
+
cliArgs: dispatch2.cliArgs,
|
|
14747
|
+
flavor: "instant"
|
|
14748
|
+
};
|
|
14749
|
+
}
|
|
14750
|
+
function mintScheduledJob(input) {
|
|
14751
|
+
return {
|
|
14752
|
+
action: input.action,
|
|
14753
|
+
duty: input.duty,
|
|
14754
|
+
executable: input.executable,
|
|
14755
|
+
schedule: input.schedule,
|
|
14756
|
+
persona: input.persona,
|
|
14757
|
+
cliArgs: input.cliArgs ?? {},
|
|
14758
|
+
flavor: "scheduled"
|
|
14759
|
+
};
|
|
14760
|
+
}
|
|
14761
|
+
var DEFAULT_INSTANT_PERSONA, localJobSeq, InvalidJobError;
|
|
14762
|
+
var init_job = __esm({
|
|
14763
|
+
"src/job.ts"() {
|
|
14764
|
+
"use strict";
|
|
14765
|
+
init_dutyFolders();
|
|
14766
|
+
init_executor();
|
|
14767
|
+
init_registry();
|
|
14768
|
+
init_jobIdentity();
|
|
14769
|
+
init_jobIdentity();
|
|
14770
|
+
DEFAULT_INSTANT_PERSONA = "kody";
|
|
14771
|
+
localJobSeq = 0;
|
|
14772
|
+
InvalidJobError = class extends Error {
|
|
14773
|
+
constructor(message) {
|
|
14774
|
+
super(message);
|
|
14775
|
+
this.name = "InvalidJobError";
|
|
14776
|
+
}
|
|
14777
|
+
};
|
|
14778
|
+
}
|
|
14779
|
+
});
|
|
14780
|
+
|
|
14633
14781
|
// src/entry.ts
|
|
14634
14782
|
init_package();
|
|
14635
14783
|
|
|
@@ -14737,9 +14885,9 @@ function translateOpenAISseToBrain(opts) {
|
|
|
14737
14885
|
}
|
|
14738
14886
|
|
|
14739
14887
|
// src/servers/brain-serve.ts
|
|
14740
|
-
import * as
|
|
14888
|
+
import * as fs43 from "fs";
|
|
14741
14889
|
import { createServer } from "http";
|
|
14742
|
-
import * as
|
|
14890
|
+
import * as path42 from "path";
|
|
14743
14891
|
|
|
14744
14892
|
// src/chat/loop.ts
|
|
14745
14893
|
init_agent();
|
|
@@ -15108,6 +15256,7 @@ async function runChatTurn(opts) {
|
|
|
15108
15256
|
verbose: opts.verbose,
|
|
15109
15257
|
quiet: opts.quiet,
|
|
15110
15258
|
systemPromptAppend: systemPrompt,
|
|
15259
|
+
...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {},
|
|
15111
15260
|
// Let the agent clone + work on OTHER repos mid-conversation (a
|
|
15112
15261
|
// repo-less Brain serves many). Enabled whenever we know where repos
|
|
15113
15262
|
// live; grants read access to that root via additionalDirectories.
|
|
@@ -15280,8 +15429,8 @@ init_config();
|
|
|
15280
15429
|
|
|
15281
15430
|
// src/kody-cli.ts
|
|
15282
15431
|
import { execFileSync as execFileSync26 } from "child_process";
|
|
15283
|
-
import * as
|
|
15284
|
-
import * as
|
|
15432
|
+
import * as fs41 from "fs";
|
|
15433
|
+
import * as path40 from "path";
|
|
15285
15434
|
|
|
15286
15435
|
// src/app-auth.ts
|
|
15287
15436
|
import { createSign } from "crypto";
|
|
@@ -15435,28 +15584,12 @@ function resolveOperatorAction(action) {
|
|
|
15435
15584
|
return resolveDutyAction(action);
|
|
15436
15585
|
}
|
|
15437
15586
|
function resolveConfiguredAction(action) {
|
|
15438
|
-
|
|
15439
|
-
if (resolved) return resolved;
|
|
15440
|
-
return compatibilityDutyAction(action);
|
|
15587
|
+
return resolveDutyAction(action);
|
|
15441
15588
|
}
|
|
15442
15589
|
function requiredRoute(action) {
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
executable: action,
|
|
15447
|
-
cliArgs: {},
|
|
15448
|
-
source: "builtin"
|
|
15449
|
-
};
|
|
15450
|
-
}
|
|
15451
|
-
function compatibilityDutyAction(action) {
|
|
15452
|
-
if (!/^[a-z][a-z0-9-]*$/.test(action)) return null;
|
|
15453
|
-
return {
|
|
15454
|
-
action,
|
|
15455
|
-
duty: action,
|
|
15456
|
-
executable: action,
|
|
15457
|
-
cliArgs: {},
|
|
15458
|
-
source: "builtin"
|
|
15459
|
-
};
|
|
15590
|
+
const route = resolveConfiguredAction(action);
|
|
15591
|
+
if (!route) throw new Error(`required duty action not found: ${action}`);
|
|
15592
|
+
return route;
|
|
15460
15593
|
}
|
|
15461
15594
|
function routeResult(route, cliArgs, target, why) {
|
|
15462
15595
|
const result = {
|
|
@@ -15487,7 +15620,7 @@ function autoDispatch(opts) {
|
|
|
15487
15620
|
const inputs2 = objectValue(event.inputs);
|
|
15488
15621
|
const n = parseInt(String(inputs2?.issue_number ?? ""), 10);
|
|
15489
15622
|
if (!Number.isNaN(n) && n > 0) {
|
|
15490
|
-
const actionName = String(inputs2?.executable ?? "").trim() || "run";
|
|
15623
|
+
const actionName = String(inputs2?.duty ?? inputs2?.executable ?? "").trim() || "run";
|
|
15491
15624
|
const route2 = resolveConfiguredAction(actionName);
|
|
15492
15625
|
if (!route2) return null;
|
|
15493
15626
|
const base = String(inputs2?.base ?? "").trim();
|
|
@@ -15628,7 +15761,7 @@ function autoDispatchTyped(opts) {
|
|
|
15628
15761
|
if (!tokenRaw || POLITE_WORDS.has(tokenRaw)) {
|
|
15629
15762
|
return {
|
|
15630
15763
|
kind: "silent",
|
|
15631
|
-
reason: tokenRaw ? `polite-word lead-in '${tokenRaw}', no default
|
|
15764
|
+
reason: tokenRaw ? `polite-word lead-in '${tokenRaw}', no default duty action configured` : "no subcommand token, no default duty action configured"
|
|
15632
15765
|
};
|
|
15633
15766
|
}
|
|
15634
15767
|
const available = listDutyActions().map((e) => e.action).filter((n) => !n.startsWith("goal-") && !n.startsWith("job-")).sort();
|
|
@@ -15668,7 +15801,15 @@ function dispatchScheduledWatches(opts) {
|
|
|
15668
15801
|
continue;
|
|
15669
15802
|
}
|
|
15670
15803
|
}
|
|
15671
|
-
|
|
15804
|
+
const route = resolveConfiguredAction(exe.name);
|
|
15805
|
+
if (!route) {
|
|
15806
|
+
process.stderr.write(
|
|
15807
|
+
`[kody] dispatchScheduledWatches: '${exe.name}' is scheduled but has no duty action; skipping
|
|
15808
|
+
`
|
|
15809
|
+
);
|
|
15810
|
+
continue;
|
|
15811
|
+
}
|
|
15812
|
+
out.push({ ...route, cliArgs: route.cliArgs, target: 0 });
|
|
15672
15813
|
}
|
|
15673
15814
|
return out;
|
|
15674
15815
|
}
|
|
@@ -15770,7 +15911,6 @@ function coerceBare(spec, value) {
|
|
|
15770
15911
|
}
|
|
15771
15912
|
|
|
15772
15913
|
// src/kody-cli.ts
|
|
15773
|
-
init_executor();
|
|
15774
15914
|
init_gha();
|
|
15775
15915
|
init_issue();
|
|
15776
15916
|
init_job();
|
|
@@ -15885,9 +16025,9 @@ async function resolveAuthToken(env = process.env) {
|
|
|
15885
16025
|
return void 0;
|
|
15886
16026
|
}
|
|
15887
16027
|
function detectPackageManager2(cwd) {
|
|
15888
|
-
if (
|
|
15889
|
-
if (
|
|
15890
|
-
if (
|
|
16028
|
+
if (fs41.existsSync(path40.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
16029
|
+
if (fs41.existsSync(path40.join(cwd, "yarn.lock"))) return "yarn";
|
|
16030
|
+
if (fs41.existsSync(path40.join(cwd, "bun.lockb"))) return "bun";
|
|
15891
16031
|
return "npm";
|
|
15892
16032
|
}
|
|
15893
16033
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -15974,11 +16114,11 @@ function configureGitIdentity(cwd) {
|
|
|
15974
16114
|
}
|
|
15975
16115
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
15976
16116
|
if (!issueNumber) return;
|
|
15977
|
-
const logPath =
|
|
16117
|
+
const logPath = path40.join(cwd, ".kody", "last-run.jsonl");
|
|
15978
16118
|
let tail = "";
|
|
15979
16119
|
try {
|
|
15980
|
-
if (
|
|
15981
|
-
const content =
|
|
16120
|
+
if (fs41.existsSync(logPath)) {
|
|
16121
|
+
const content = fs41.readFileSync(logPath, "utf-8");
|
|
15982
16122
|
tail = content.slice(-3e3);
|
|
15983
16123
|
}
|
|
15984
16124
|
} catch {
|
|
@@ -16003,7 +16143,7 @@ async function runCi(argv) {
|
|
|
16003
16143
|
return 0;
|
|
16004
16144
|
}
|
|
16005
16145
|
const args = parseCiArgs(argv);
|
|
16006
|
-
const cwd = args.cwd ?
|
|
16146
|
+
const cwd = args.cwd ? path40.resolve(args.cwd) : process.cwd();
|
|
16007
16147
|
let earlyConfig;
|
|
16008
16148
|
let earlyConfigError;
|
|
16009
16149
|
try {
|
|
@@ -16016,14 +16156,14 @@ async function runCi(argv) {
|
|
|
16016
16156
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
16017
16157
|
let manualWorkflowDispatch = false;
|
|
16018
16158
|
let forceRunAction = null;
|
|
16019
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
16159
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs41.existsSync(dispatchEventPath)) {
|
|
16020
16160
|
try {
|
|
16021
|
-
const evt = JSON.parse(
|
|
16161
|
+
const evt = JSON.parse(fs41.readFileSync(dispatchEventPath, "utf-8"));
|
|
16022
16162
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
16023
16163
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
16024
|
-
const
|
|
16164
|
+
const dutyInput = String(evt?.inputs?.duty ?? evt?.inputs?.executable ?? "").trim();
|
|
16025
16165
|
const noTarget = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
16026
|
-
if (noTarget &&
|
|
16166
|
+
if (noTarget && dutyInput) forceRunAction = dutyInput;
|
|
16027
16167
|
else manualWorkflowDispatch = noTarget;
|
|
16028
16168
|
} catch {
|
|
16029
16169
|
manualWorkflowDispatch = false;
|
|
@@ -16123,7 +16263,7 @@ async function runCi(argv) {
|
|
|
16123
16263
|
);
|
|
16124
16264
|
return 0;
|
|
16125
16265
|
}
|
|
16126
|
-
if (outcome.kind === "silent" && earlyConfigError && outcome.reason.includes("no default
|
|
16266
|
+
if (outcome.kind === "silent" && earlyConfigError && outcome.reason.includes("no default duty action configured")) {
|
|
16127
16267
|
process.stderr.write(`[kody] config error: ${earlyConfigError.message}
|
|
16128
16268
|
`);
|
|
16129
16269
|
return 64;
|
|
@@ -16143,11 +16283,14 @@ async function runCi(argv) {
|
|
|
16143
16283
|
${CI_HELP}`);
|
|
16144
16284
|
return 64;
|
|
16145
16285
|
}
|
|
16286
|
+
const runRoute = args.issueNumber ? resolveDutyAction("run") : null;
|
|
16287
|
+
if (!autoFallback && args.issueNumber && !runRoute) {
|
|
16288
|
+
process.stderr.write("[kody] required duty action 'run' not found\n");
|
|
16289
|
+
return 64;
|
|
16290
|
+
}
|
|
16146
16291
|
const dispatch2 = autoFallback ?? {
|
|
16147
|
-
|
|
16148
|
-
|
|
16149
|
-
executable: "run",
|
|
16150
|
-
cliArgs: { issue: args.issueNumber },
|
|
16292
|
+
...runRoute,
|
|
16293
|
+
cliArgs: { ...runRoute.cliArgs, issue: args.issueNumber },
|
|
16151
16294
|
target: args.issueNumber
|
|
16152
16295
|
};
|
|
16153
16296
|
const issueNumber = dispatch2.target;
|
|
@@ -16229,8 +16372,8 @@ async function runScheduledFanOut(cwd, args, opts) {
|
|
|
16229
16372
|
);
|
|
16230
16373
|
return 0;
|
|
16231
16374
|
}
|
|
16232
|
-
const names = matches.map((m) => m.executable).join(", ");
|
|
16233
|
-
process.stdout.write(`\u2192 kody: scheduled wake \u2014 firing ${matches.length} watch(
|
|
16375
|
+
const names = matches.map((m) => `${m.duty}\u2192${m.executable}`).join(", ");
|
|
16376
|
+
process.stdout.write(`\u2192 kody: scheduled wake \u2014 firing ${matches.length} watch dut(y/ies): ${names}
|
|
16234
16377
|
`);
|
|
16235
16378
|
try {
|
|
16236
16379
|
const n = unpackAllSecrets();
|
|
@@ -16267,19 +16410,21 @@ async function runScheduledFanOut(cwd, args, opts) {
|
|
|
16267
16410
|
const serial = process.env.KODY_SERIAL_WATCHES === "1";
|
|
16268
16411
|
const runWatch = async (match) => {
|
|
16269
16412
|
process.stdout.write(`
|
|
16270
|
-
\u2192 kody: running watch \`${match.executable}
|
|
16413
|
+
\u2192 kody: running watch duty \`${match.duty}\` (${match.executable})
|
|
16271
16414
|
`);
|
|
16272
16415
|
try {
|
|
16273
|
-
const result = await
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16416
|
+
const result = await runJob(
|
|
16417
|
+
mintScheduledJob({
|
|
16418
|
+
action: match.action,
|
|
16419
|
+
duty: match.duty,
|
|
16420
|
+
executable: match.executable,
|
|
16421
|
+
cliArgs: match.cliArgs
|
|
16422
|
+
}),
|
|
16423
|
+
{ cwd, config, verbose: args.verbose, quiet: args.quiet, chain: false }
|
|
16424
|
+
);
|
|
16280
16425
|
if (result.exitCode !== 0) {
|
|
16281
16426
|
process.stderr.write(
|
|
16282
|
-
`[kody] watch \`${match.
|
|
16427
|
+
`[kody] watch duty \`${match.duty}\` exited ${result.exitCode}: ${result.reason ?? "(no reason)"}
|
|
16283
16428
|
`
|
|
16284
16429
|
);
|
|
16285
16430
|
return result.exitCode;
|
|
@@ -16287,7 +16432,7 @@ async function runScheduledFanOut(cwd, args, opts) {
|
|
|
16287
16432
|
return 0;
|
|
16288
16433
|
} catch (err) {
|
|
16289
16434
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16290
|
-
process.stderr.write(`[kody] watch \`${match.
|
|
16435
|
+
process.stderr.write(`[kody] watch duty \`${match.duty}\` crashed: ${msg}
|
|
16291
16436
|
`);
|
|
16292
16437
|
return 99;
|
|
16293
16438
|
}
|
|
@@ -16313,16 +16458,16 @@ init_litellm();
|
|
|
16313
16458
|
init_repoWorkspace();
|
|
16314
16459
|
|
|
16315
16460
|
// src/scripts/brainTurnLog.ts
|
|
16316
|
-
import * as
|
|
16317
|
-
import * as
|
|
16461
|
+
import * as fs42 from "fs";
|
|
16462
|
+
import * as path41 from "path";
|
|
16318
16463
|
var live = /* @__PURE__ */ new Map();
|
|
16319
16464
|
function eventsPath(dir, chatId) {
|
|
16320
|
-
return
|
|
16465
|
+
return path41.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
|
|
16321
16466
|
}
|
|
16322
16467
|
function lastPersistedSeq(dir, chatId) {
|
|
16323
16468
|
const p = eventsPath(dir, chatId);
|
|
16324
|
-
if (!
|
|
16325
|
-
const lines =
|
|
16469
|
+
if (!fs42.existsSync(p)) return 0;
|
|
16470
|
+
const lines = fs42.readFileSync(p, "utf-8").split("\n").filter(Boolean);
|
|
16326
16471
|
if (lines.length === 0) return 0;
|
|
16327
16472
|
try {
|
|
16328
16473
|
return JSON.parse(lines[lines.length - 1]).seq || 0;
|
|
@@ -16332,9 +16477,9 @@ function lastPersistedSeq(dir, chatId) {
|
|
|
16332
16477
|
}
|
|
16333
16478
|
function readSince(dir, chatId, since) {
|
|
16334
16479
|
const p = eventsPath(dir, chatId);
|
|
16335
|
-
if (!
|
|
16480
|
+
if (!fs42.existsSync(p)) return [];
|
|
16336
16481
|
const out = [];
|
|
16337
|
-
for (const line of
|
|
16482
|
+
for (const line of fs42.readFileSync(p, "utf-8").split("\n")) {
|
|
16338
16483
|
if (!line) continue;
|
|
16339
16484
|
try {
|
|
16340
16485
|
const rec = JSON.parse(line);
|
|
@@ -16360,12 +16505,12 @@ function beginTurn(dir, chatId) {
|
|
|
16360
16505
|
};
|
|
16361
16506
|
live.set(chatId, state);
|
|
16362
16507
|
const p = eventsPath(dir, chatId);
|
|
16363
|
-
|
|
16508
|
+
fs42.mkdirSync(path41.dirname(p), { recursive: true });
|
|
16364
16509
|
return (event) => {
|
|
16365
16510
|
state.seq += 1;
|
|
16366
16511
|
const rec = { seq: state.seq, turn, ts: Date.now(), event };
|
|
16367
16512
|
try {
|
|
16368
|
-
|
|
16513
|
+
fs42.appendFileSync(p, `${JSON.stringify(rec)}
|
|
16369
16514
|
`);
|
|
16370
16515
|
} catch (err) {
|
|
16371
16516
|
process.stderr.write(
|
|
@@ -16404,7 +16549,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
|
|
|
16404
16549
|
event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
|
|
16405
16550
|
};
|
|
16406
16551
|
try {
|
|
16407
|
-
|
|
16552
|
+
fs42.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
|
|
16408
16553
|
`);
|
|
16409
16554
|
} catch {
|
|
16410
16555
|
}
|
|
@@ -16635,7 +16780,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
16635
16780
|
const repo = strField(body, "repo");
|
|
16636
16781
|
const repoToken = strField(body, "repoToken");
|
|
16637
16782
|
const sessionFile = sessionFilePath(opts.cwd, chatId);
|
|
16638
|
-
|
|
16783
|
+
fs43.mkdirSync(path42.dirname(sessionFile), { recursive: true });
|
|
16639
16784
|
appendTurn(sessionFile, {
|
|
16640
16785
|
role: "user",
|
|
16641
16786
|
content: message,
|
|
@@ -16682,7 +16827,7 @@ async function handleChatTurn(req, res, chatId, opts) {
|
|
|
16682
16827
|
function buildServer(opts) {
|
|
16683
16828
|
const runTurn = opts.runTurn ?? runChatTurn;
|
|
16684
16829
|
const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
|
|
16685
|
-
const reposRoot = opts.reposRoot ??
|
|
16830
|
+
const reposRoot = opts.reposRoot ?? path42.join(path42.dirname(path42.resolve(opts.cwd)), "repos");
|
|
16686
16831
|
return createServer(async (req, res) => {
|
|
16687
16832
|
if (!req.method || !req.url) {
|
|
16688
16833
|
sendJson(res, 400, { error: "bad request" });
|
|
@@ -17275,14 +17420,14 @@ async function loadConfigSafe() {
|
|
|
17275
17420
|
|
|
17276
17421
|
// src/chat-cli.ts
|
|
17277
17422
|
import { execFileSync as execFileSync29 } from "child_process";
|
|
17278
|
-
import * as
|
|
17279
|
-
import * as
|
|
17423
|
+
import * as fs45 from "fs";
|
|
17424
|
+
import * as path44 from "path";
|
|
17280
17425
|
|
|
17281
17426
|
// src/chat/modes/interactive.ts
|
|
17282
17427
|
init_issue();
|
|
17283
17428
|
import { execFileSync as execFileSync28 } from "child_process";
|
|
17284
|
-
import * as
|
|
17285
|
-
import * as
|
|
17429
|
+
import * as fs44 from "fs";
|
|
17430
|
+
import * as path43 from "path";
|
|
17286
17431
|
|
|
17287
17432
|
// src/chat/inbox.ts
|
|
17288
17433
|
import { execFileSync as execFileSync27 } from "child_process";
|
|
@@ -17415,7 +17560,8 @@ async function runInteractiveMode(opts) {
|
|
|
17415
17560
|
sink: opts.sink,
|
|
17416
17561
|
verbose: opts.verbose,
|
|
17417
17562
|
quiet: opts.quiet,
|
|
17418
|
-
invokeAgent: opts.invokeAgent
|
|
17563
|
+
invokeAgent: opts.invokeAgent,
|
|
17564
|
+
...opts.reasoningEffort ? { reasoningEffort: opts.reasoningEffort } : {}
|
|
17419
17565
|
});
|
|
17420
17566
|
} catch (err) {
|
|
17421
17567
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -17441,9 +17587,9 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
17441
17587
|
return -1;
|
|
17442
17588
|
}
|
|
17443
17589
|
function commitTurn(cwd, sessionId, _verbose) {
|
|
17444
|
-
const sessionRel =
|
|
17445
|
-
const eventsRel =
|
|
17446
|
-
const rels = [sessionRel, eventsRel].filter((p) =>
|
|
17590
|
+
const sessionRel = path43.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
17591
|
+
const eventsRel = path43.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
17592
|
+
const rels = [sessionRel, eventsRel].filter((p) => fs44.existsSync(path43.join(cwd, p)));
|
|
17447
17593
|
if (rels.length === 0) return;
|
|
17448
17594
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
17449
17595
|
if (!repository) {
|
|
@@ -17455,8 +17601,8 @@ function commitTurn(cwd, sessionId, _verbose) {
|
|
|
17455
17601
|
}
|
|
17456
17602
|
const branch = defaultBranch(cwd) ?? "main";
|
|
17457
17603
|
for (const rel of rels) {
|
|
17458
|
-
const repoPath = rel.split(
|
|
17459
|
-
const localText =
|
|
17604
|
+
const repoPath = rel.split(path43.sep).join("/");
|
|
17605
|
+
const localText = fs44.readFileSync(path43.join(cwd, rel), "utf-8");
|
|
17460
17606
|
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
17461
17607
|
}
|
|
17462
17608
|
}
|
|
@@ -17554,11 +17700,17 @@ var CHAT_HELP = `kody chat \u2014 dashboard-driven chat session
|
|
|
17554
17700
|
|
|
17555
17701
|
Usage:
|
|
17556
17702
|
kody chat [--session <id>] [--message <text>] [--model <provider/model>]
|
|
17703
|
+
[--reasoning-effort <off|low|medium|high>]
|
|
17557
17704
|
[--dashboard-url <url>] [--cwd <path>] [--verbose|--quiet]
|
|
17558
17705
|
|
|
17559
|
-
All inputs may also come from env: SESSION_ID, INIT_MESSAGE, MODEL, DASHBOARD_URL.
|
|
17706
|
+
All inputs may also come from env: SESSION_ID, INIT_MESSAGE, MODEL, REASONING_EFFORT, DASHBOARD_URL.
|
|
17560
17707
|
CLI flags take precedence over env. SESSION_ID is required.
|
|
17561
17708
|
|
|
17709
|
+
Thinking level maps to the Claude Agent SDK's maxThinkingTokens (Anthropic
|
|
17710
|
+
extended thinking). Default is unset (no thinking \u2014 cheapest). Set via the
|
|
17711
|
+
dashboard's chat-level thinking dropdown, the REASONING_EFFORT env var,
|
|
17712
|
+
or this flag.
|
|
17713
|
+
|
|
17562
17714
|
Exit codes:
|
|
17563
17715
|
0 reply emitted successfully
|
|
17564
17716
|
64 bad inputs (missing session, empty history)
|
|
@@ -17571,6 +17723,7 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
17571
17723
|
if (arg === "--session") result.sessionId = argv[++i];
|
|
17572
17724
|
else if (arg === "--message") result.initMessage = argv[++i];
|
|
17573
17725
|
else if (arg === "--model") result.model = argv[++i];
|
|
17726
|
+
else if (arg === "--reasoning-effort") result.reasoningEffort = parseReasoningEffort(argv[++i]) ?? void 0;
|
|
17574
17727
|
else if (arg === "--dashboard-url") result.dashboardUrl = argv[++i];
|
|
17575
17728
|
else if (arg === "--cwd") result.cwd = argv[++i];
|
|
17576
17729
|
else if (arg === "--verbose") result.verbose = true;
|
|
@@ -17583,22 +17736,23 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
17583
17736
|
result.initMessage = result.initMessage ?? env.INIT_MESSAGE ?? void 0;
|
|
17584
17737
|
result.model = result.model ?? env.MODEL ?? void 0;
|
|
17585
17738
|
result.dashboardUrl = result.dashboardUrl ?? env.DASHBOARD_URL ?? void 0;
|
|
17739
|
+
result.reasoningEffort = result.reasoningEffort ?? parseReasoningEffort(env.REASONING_EFFORT) ?? void 0;
|
|
17586
17740
|
for (const key of ["sessionId", "initMessage", "model", "dashboardUrl"]) {
|
|
17587
17741
|
const v = result[key];
|
|
17588
17742
|
if (typeof v === "string" && v.trim() === "") result[key] = void 0;
|
|
17589
17743
|
}
|
|
17590
17744
|
if (!result.sessionId && !result.errors.includes("__HELP__")) {
|
|
17591
|
-
result.errors.push("--session <id> (or SESSION_ID env) is required");
|
|
17745
|
+
result.errors.push("--session <id> (or SESSION_ID env) is required)");
|
|
17592
17746
|
}
|
|
17593
17747
|
return result;
|
|
17594
17748
|
}
|
|
17595
17749
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
17596
|
-
const sessionFile =
|
|
17597
|
-
const eventsFile =
|
|
17750
|
+
const sessionFile = path44.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
17751
|
+
const eventsFile = path44.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
17598
17752
|
const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
17599
|
-
const tasksDir =
|
|
17753
|
+
const tasksDir = path44.join(".kody", "tasks", safeSession);
|
|
17600
17754
|
const candidatePaths = [sessionFile, eventsFile, tasksDir];
|
|
17601
|
-
const paths = candidatePaths.filter((p) =>
|
|
17755
|
+
const paths = candidatePaths.filter((p) => fs45.existsSync(path44.join(cwd, p)));
|
|
17602
17756
|
if (paths.length === 0) return;
|
|
17603
17757
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
17604
17758
|
try {
|
|
@@ -17642,7 +17796,7 @@ async function runChat(argv) {
|
|
|
17642
17796
|
${CHAT_HELP}`);
|
|
17643
17797
|
return 64;
|
|
17644
17798
|
}
|
|
17645
|
-
const cwd = args.cwd ?
|
|
17799
|
+
const cwd = args.cwd ? path44.resolve(args.cwd) : process.cwd();
|
|
17646
17800
|
const sessionId = args.sessionId;
|
|
17647
17801
|
const unpackedSecrets = unpackAllSecrets();
|
|
17648
17802
|
if (unpackedSecrets > 0) {
|
|
@@ -17653,6 +17807,7 @@ ${CHAT_HELP}`);
|
|
|
17653
17807
|
configureGitIdentity(cwd);
|
|
17654
17808
|
const config = tryLoadConfig(cwd);
|
|
17655
17809
|
const modelSpec = args.model ?? config?.agent.model ?? DEFAULT_MODEL2;
|
|
17810
|
+
const reasoningEffort = args.reasoningEffort ?? config?.agent.reasoningEffort ?? void 0;
|
|
17656
17811
|
let model;
|
|
17657
17812
|
try {
|
|
17658
17813
|
model = parseProviderModel(modelSpec);
|
|
@@ -17694,7 +17849,7 @@ ${CHAT_HELP}`);
|
|
|
17694
17849
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
17695
17850
|
const meta = readMeta(sessionFile);
|
|
17696
17851
|
process.stdout.write(
|
|
17697
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
17852
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs45.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
17698
17853
|
`
|
|
17699
17854
|
);
|
|
17700
17855
|
try {
|
|
@@ -17707,7 +17862,8 @@ ${CHAT_HELP}`);
|
|
|
17707
17862
|
sink,
|
|
17708
17863
|
meta,
|
|
17709
17864
|
verbose: args.verbose,
|
|
17710
|
-
quiet: args.quiet
|
|
17865
|
+
quiet: args.quiet,
|
|
17866
|
+
...reasoningEffort ? { reasoningEffort } : {}
|
|
17711
17867
|
});
|
|
17712
17868
|
return result2.exitCode;
|
|
17713
17869
|
}
|
|
@@ -17719,7 +17875,8 @@ ${CHAT_HELP}`);
|
|
|
17719
17875
|
litellmUrl: litellm?.url ?? null,
|
|
17720
17876
|
sink,
|
|
17721
17877
|
verbose: args.verbose,
|
|
17722
|
-
quiet: args.quiet
|
|
17878
|
+
quiet: args.quiet,
|
|
17879
|
+
...reasoningEffort ? { reasoningEffort } : {}
|
|
17723
17880
|
});
|
|
17724
17881
|
commitChatFiles(cwd, sessionId, args.verbose ?? false);
|
|
17725
17882
|
return result.exitCode;
|
|
@@ -17733,7 +17890,6 @@ ${CHAT_HELP}`);
|
|
|
17733
17890
|
|
|
17734
17891
|
// src/entry.ts
|
|
17735
17892
|
init_config();
|
|
17736
|
-
init_executor();
|
|
17737
17893
|
init_job();
|
|
17738
17894
|
init_registry();
|
|
17739
17895
|
|
|
@@ -17851,8 +18007,8 @@ var FlyClient = class {
|
|
|
17851
18007
|
get fetch() {
|
|
17852
18008
|
return this.opts.fetchImpl ?? fetch;
|
|
17853
18009
|
}
|
|
17854
|
-
async call(
|
|
17855
|
-
const res = await this.fetch(`${FLY_API_BASE}${
|
|
18010
|
+
async call(path45, init = {}) {
|
|
18011
|
+
const res = await this.fetch(`${FLY_API_BASE}${path45}`, {
|
|
17856
18012
|
method: init.method ?? "GET",
|
|
17857
18013
|
headers: {
|
|
17858
18014
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -17863,7 +18019,7 @@ var FlyClient = class {
|
|
|
17863
18019
|
if (res.status === 404 && init.allow404) return null;
|
|
17864
18020
|
if (!res.ok) {
|
|
17865
18021
|
const text = await res.text().catch(() => "");
|
|
17866
|
-
throw new Error(`Fly API ${res.status} on ${
|
|
18022
|
+
throw new Error(`Fly API ${res.status} on ${path45}: ${text.slice(0, 200) || res.statusText}`);
|
|
17867
18023
|
}
|
|
17868
18024
|
if (res.status === 204) return null;
|
|
17869
18025
|
const raw = await res.text();
|
|
@@ -18089,7 +18245,9 @@ var PoolManager = class {
|
|
|
18089
18245
|
const destroyedSurplus = await this.destroySurplus(surplus);
|
|
18090
18246
|
const destroyed = destroyedTracked + destroyedSurplus;
|
|
18091
18247
|
if (pruned > 0 || adopted > 0 || destroyed > 0) {
|
|
18092
|
-
this.log(
|
|
18248
|
+
this.log(
|
|
18249
|
+
`resync: pruned ${pruned} stale, adopted ${adopted}, destroyed ${destroyed} surplus (free=${this.free.length})`
|
|
18250
|
+
);
|
|
18093
18251
|
}
|
|
18094
18252
|
await this.refill();
|
|
18095
18253
|
}
|
|
@@ -18586,7 +18744,7 @@ async function poolServe() {
|
|
|
18586
18744
|
|
|
18587
18745
|
// src/servers/runner-serve.ts
|
|
18588
18746
|
import { spawn as spawn8 } from "child_process";
|
|
18589
|
-
import * as
|
|
18747
|
+
import * as fs46 from "fs";
|
|
18590
18748
|
import { createServer as createServer5 } from "http";
|
|
18591
18749
|
var DEFAULT_PORT2 = 8080;
|
|
18592
18750
|
var DEFAULT_WORKDIR = "/workspace/repo";
|
|
@@ -18666,8 +18824,8 @@ async function defaultRunJob(job) {
|
|
|
18666
18824
|
const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
|
|
18667
18825
|
const branch = job.ref ?? "main";
|
|
18668
18826
|
const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
|
|
18669
|
-
|
|
18670
|
-
|
|
18827
|
+
fs46.rmSync(workdir, { recursive: true, force: true });
|
|
18828
|
+
fs46.mkdirSync(workdir, { recursive: true });
|
|
18671
18829
|
const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
|
|
18672
18830
|
const interactive = job.mode === "interactive";
|
|
18673
18831
|
const scheduled = job.mode === "scheduled";
|
|
@@ -19124,7 +19282,6 @@ Usage:
|
|
|
19124
19282
|
kody-engine release --issue <N> [--cwd <path>] [--verbose|--quiet]
|
|
19125
19283
|
kody-engine init [--cwd <path>] [--verbose|--quiet]
|
|
19126
19284
|
kody-engine <action> [--cwd <path>] [--verbose|--quiet]
|
|
19127
|
-
kody-engine exec <executable> [--cwd <path>] [--verbose|--quiet]
|
|
19128
19285
|
kody-engine ci [preflight flags \u2014 see: kody-engine ci --help]
|
|
19129
19286
|
kody-engine chat [chat flags \u2014 see: kody-engine chat --help]
|
|
19130
19287
|
kody-engine stats [--since 7d|--run <id>|--json|--cwd <path>]
|
|
@@ -19132,8 +19289,7 @@ Usage:
|
|
|
19132
19289
|
kody-engine version
|
|
19133
19290
|
|
|
19134
19291
|
Top-level work commands are duty actions. A duty owns the public action name
|
|
19135
|
-
and selects an implementation executable.
|
|
19136
|
-
debug path for engine internals and migration compatibility.
|
|
19292
|
+
and selects an implementation executable.
|
|
19137
19293
|
|
|
19138
19294
|
Exit codes:
|
|
19139
19295
|
0 success (PR opened, verify passed \u2014 or resolve produced a merge commit)
|
|
@@ -19174,24 +19330,6 @@ function parseArgs(argv) {
|
|
|
19174
19330
|
result.serverArgs = argv.slice(1).filter((a) => !a.startsWith("-"));
|
|
19175
19331
|
return result;
|
|
19176
19332
|
}
|
|
19177
|
-
if (cmd === "exec") {
|
|
19178
|
-
const executableName = argv[1];
|
|
19179
|
-
if (!executableName) {
|
|
19180
|
-
result.errors.push("exec requires an executable name");
|
|
19181
|
-
return result;
|
|
19182
|
-
}
|
|
19183
|
-
if (!hasExecutable(executableName)) {
|
|
19184
|
-
result.errors.push(`unknown executable: ${executableName}`);
|
|
19185
|
-
return result;
|
|
19186
|
-
}
|
|
19187
|
-
result.command = "__executable__";
|
|
19188
|
-
result.executableName = executableName;
|
|
19189
|
-
result.cliArgs = parseGenericFlags(argv.slice(2));
|
|
19190
|
-
if (typeof result.cliArgs.cwd === "string") result.cwd = result.cliArgs.cwd;
|
|
19191
|
-
if (result.cliArgs.verbose === true) result.verbose = true;
|
|
19192
|
-
if (result.cliArgs.quiet === true) result.quiet = true;
|
|
19193
|
-
return result;
|
|
19194
|
-
}
|
|
19195
19333
|
if (hasDutyAction(cmd)) {
|
|
19196
19334
|
result.command = "__duty__";
|
|
19197
19335
|
result.actionName = cmd;
|
|
@@ -19201,18 +19339,8 @@ function parseArgs(argv) {
|
|
|
19201
19339
|
if (result.cliArgs.quiet === true) result.quiet = true;
|
|
19202
19340
|
return result;
|
|
19203
19341
|
}
|
|
19204
|
-
if (hasExecutable(cmd)) {
|
|
19205
|
-
result.command = "__executable__";
|
|
19206
|
-
result.executableName = cmd;
|
|
19207
|
-
result.cliArgs = parseGenericFlags(argv.slice(1));
|
|
19208
|
-
if (typeof result.cliArgs.cwd === "string") result.cwd = result.cliArgs.cwd;
|
|
19209
|
-
if (result.cliArgs.verbose === true) result.verbose = true;
|
|
19210
|
-
if (result.cliArgs.quiet === true) result.quiet = true;
|
|
19211
|
-
return result;
|
|
19212
|
-
}
|
|
19213
19342
|
const discoveredActions = listDutyActions().map((e) => e.action);
|
|
19214
|
-
const
|
|
19215
|
-
const available = ["ci", "chat", "stats", "help", "version", ...discoveredActions, ...discoveredExecutables];
|
|
19343
|
+
const available = ["ci", "chat", "stats", "help", "version", ...discoveredActions];
|
|
19216
19344
|
result.errors.push(`unknown command: ${cmd} (available: ${available.join(", ")})`);
|
|
19217
19345
|
return result;
|
|
19218
19346
|
}
|
|
@@ -19310,7 +19438,7 @@ ${HELP_TEXT}`);
|
|
|
19310
19438
|
return 64;
|
|
19311
19439
|
}
|
|
19312
19440
|
const cliArgs = { ...route.cliArgs, ...args.cliArgs ?? {} };
|
|
19313
|
-
const
|
|
19441
|
+
const skipConfig = configlessCommands.has(route.executable);
|
|
19314
19442
|
try {
|
|
19315
19443
|
const result = await runJob(
|
|
19316
19444
|
{
|
|
@@ -19323,7 +19451,7 @@ ${HELP_TEXT}`);
|
|
|
19323
19451
|
},
|
|
19324
19452
|
{
|
|
19325
19453
|
cwd,
|
|
19326
|
-
skipConfig
|
|
19454
|
+
skipConfig,
|
|
19327
19455
|
verbose: args.verbose,
|
|
19328
19456
|
quiet: args.quiet
|
|
19329
19457
|
}
|
|
@@ -19344,30 +19472,8 @@ ${HELP_TEXT}`);
|
|
|
19344
19472
|
return 99;
|
|
19345
19473
|
}
|
|
19346
19474
|
}
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
const result = await runExecutableChain(args.executableName, {
|
|
19350
|
-
cliArgs: args.cliArgs ?? {},
|
|
19351
|
-
cwd,
|
|
19352
|
-
skipConfig,
|
|
19353
|
-
verbose: args.verbose,
|
|
19354
|
-
quiet: args.quiet
|
|
19355
|
-
});
|
|
19356
|
-
if (result.exitCode !== 0 && result.reason) {
|
|
19357
|
-
process.stderr.write(`error: ${result.reason}
|
|
19358
|
-
`);
|
|
19359
|
-
}
|
|
19360
|
-
return result.exitCode;
|
|
19361
|
-
} catch (err) {
|
|
19362
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
19363
|
-
process.stderr.write(`[kody] ${args.executableName} crashed: ${msg}
|
|
19364
|
-
`);
|
|
19365
|
-
if (err instanceof Error && err.stack) process.stderr.write(`${err.stack}
|
|
19366
|
-
`);
|
|
19367
|
-
process.stdout.write(`PR_URL=FAILED: ${args.executableName} crashed: ${msg}
|
|
19368
|
-
`);
|
|
19369
|
-
return 99;
|
|
19370
|
-
}
|
|
19475
|
+
process.stderr.write("error: command did not resolve to a duty\n");
|
|
19476
|
+
return 64;
|
|
19371
19477
|
}
|
|
19372
19478
|
function numericTarget(cliArgs) {
|
|
19373
19479
|
for (const key of ["issue", "pr"]) {
|