@kody-ade/kody-engine 0.4.94 → 0.4.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/kody.js +240 -163
- package/dist/executables/job-tick/prompt.md +8 -2
- package/dist/executables/worker-ask/profile.json +70 -0
- package/dist/executables/worker-ask/prompt.md +60 -0
- package/package.json +1 -1
- package/dist/executables/worker-scheduler/profile.json +0 -50
- package/dist/executables/worker-tick/profile.json +0 -74
- package/dist/executables/worker-tick/prompt.md +0 -65
- package/dist/executables/worker-tick-scripted/profile.json +0 -69
package/dist/bin/kody.js
CHANGED
|
@@ -313,14 +313,14 @@ var init_verifyMcp = __esm({
|
|
|
313
313
|
});
|
|
314
314
|
|
|
315
315
|
// src/issue.ts
|
|
316
|
-
import { execFileSync
|
|
316
|
+
import { execFileSync } from "child_process";
|
|
317
317
|
function ghToken() {
|
|
318
318
|
return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
|
|
319
319
|
}
|
|
320
320
|
function gh(args, options) {
|
|
321
321
|
const token = ghToken();
|
|
322
322
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
323
|
-
return
|
|
323
|
+
return execFileSync("gh", args, {
|
|
324
324
|
encoding: "utf-8",
|
|
325
325
|
timeout: API_TIMEOUT_MS,
|
|
326
326
|
cwd: options?.cwd,
|
|
@@ -626,28 +626,28 @@ var loadMemoryContext_exports = {};
|
|
|
626
626
|
__export(loadMemoryContext_exports, {
|
|
627
627
|
loadMemoryContext: () => loadMemoryContext
|
|
628
628
|
});
|
|
629
|
-
import * as
|
|
630
|
-
import * as
|
|
629
|
+
import * as fs32 from "fs";
|
|
630
|
+
import * as path30 from "path";
|
|
631
631
|
function collectPages(memoryAbs) {
|
|
632
632
|
const out = [];
|
|
633
633
|
walkMd(memoryAbs, (file) => {
|
|
634
634
|
let stat;
|
|
635
635
|
try {
|
|
636
|
-
stat =
|
|
636
|
+
stat = fs32.statSync(file);
|
|
637
637
|
} catch {
|
|
638
638
|
return;
|
|
639
639
|
}
|
|
640
640
|
let raw;
|
|
641
641
|
try {
|
|
642
|
-
raw =
|
|
642
|
+
raw = fs32.readFileSync(file, "utf-8");
|
|
643
643
|
} catch {
|
|
644
644
|
return;
|
|
645
645
|
}
|
|
646
646
|
const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
647
|
-
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ??
|
|
647
|
+
const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path30.basename(file, ".md");
|
|
648
648
|
const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
|
|
649
649
|
out.push({
|
|
650
|
-
relPath:
|
|
650
|
+
relPath: path30.relative(memoryAbs, file),
|
|
651
651
|
title,
|
|
652
652
|
updated,
|
|
653
653
|
content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
|
|
@@ -715,16 +715,16 @@ function walkMd(root, visit) {
|
|
|
715
715
|
const dir = stack.pop();
|
|
716
716
|
let names;
|
|
717
717
|
try {
|
|
718
|
-
names =
|
|
718
|
+
names = fs32.readdirSync(dir);
|
|
719
719
|
} catch {
|
|
720
720
|
continue;
|
|
721
721
|
}
|
|
722
722
|
for (const name of names) {
|
|
723
723
|
if (name.startsWith(".")) continue;
|
|
724
|
-
const full =
|
|
724
|
+
const full = path30.join(dir, name);
|
|
725
725
|
let stat;
|
|
726
726
|
try {
|
|
727
|
-
stat =
|
|
727
|
+
stat = fs32.statSync(full);
|
|
728
728
|
} catch {
|
|
729
729
|
continue;
|
|
730
730
|
}
|
|
@@ -747,8 +747,8 @@ var init_loadMemoryContext = __esm({
|
|
|
747
747
|
TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
|
|
748
748
|
loadMemoryContext = async (ctx) => {
|
|
749
749
|
if (typeof ctx.data.memoryContext === "string") return;
|
|
750
|
-
const memoryAbs =
|
|
751
|
-
if (!
|
|
750
|
+
const memoryAbs = path30.join(ctx.cwd, MEMORY_DIR_RELATIVE);
|
|
751
|
+
if (!fs32.existsSync(memoryAbs)) {
|
|
752
752
|
ctx.data.memoryContext = "";
|
|
753
753
|
return;
|
|
754
754
|
}
|
|
@@ -877,7 +877,7 @@ var init_loadPriorArt = __esm({
|
|
|
877
877
|
// package.json
|
|
878
878
|
var package_default = {
|
|
879
879
|
name: "@kody-ade/kody-engine",
|
|
880
|
-
version: "0.4.
|
|
880
|
+
version: "0.4.96",
|
|
881
881
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
882
882
|
license: "MIT",
|
|
883
883
|
type: "module",
|
|
@@ -932,8 +932,8 @@ var package_default = {
|
|
|
932
932
|
|
|
933
933
|
// src/chat-cli.ts
|
|
934
934
|
import { execFileSync as execFileSync31 } from "child_process";
|
|
935
|
-
import * as
|
|
936
|
-
import * as
|
|
935
|
+
import * as fs38 from "fs";
|
|
936
|
+
import * as path35 from "path";
|
|
937
937
|
|
|
938
938
|
// src/chat/events.ts
|
|
939
939
|
import * as fs from "fs";
|
|
@@ -1988,13 +1988,13 @@ async function emit(sink, type, sessionId, suffix, payload) {
|
|
|
1988
1988
|
}
|
|
1989
1989
|
|
|
1990
1990
|
// src/chat/modes/interactive.ts
|
|
1991
|
-
|
|
1991
|
+
init_issue();
|
|
1992
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
1992
1993
|
import * as fs9 from "fs";
|
|
1993
|
-
import * as os2 from "os";
|
|
1994
1994
|
import * as path8 from "path";
|
|
1995
1995
|
|
|
1996
1996
|
// src/chat/inbox.ts
|
|
1997
|
-
import { execFileSync } from "child_process";
|
|
1997
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1998
1998
|
var DEFAULT_POLL_MS = 3e3;
|
|
1999
1999
|
async function waitForNextUserMessage(opts) {
|
|
2000
2000
|
const pollMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
@@ -2011,13 +2011,13 @@ async function waitForNextUserMessage(opts) {
|
|
|
2011
2011
|
try {
|
|
2012
2012
|
const branch = currentBranch(opts.cwd);
|
|
2013
2013
|
if (branch) {
|
|
2014
|
-
|
|
2015
|
-
|
|
2014
|
+
execFileSync2("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
|
|
2015
|
+
execFileSync2("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
|
|
2016
2016
|
cwd: opts.cwd,
|
|
2017
2017
|
stdio: "pipe"
|
|
2018
2018
|
});
|
|
2019
2019
|
} else {
|
|
2020
|
-
|
|
2020
|
+
execFileSync2("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
|
|
2021
2021
|
}
|
|
2022
2022
|
} catch (err) {
|
|
2023
2023
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2043,7 +2043,7 @@ function sleep(ms) {
|
|
|
2043
2043
|
}
|
|
2044
2044
|
function currentBranch(cwd) {
|
|
2045
2045
|
try {
|
|
2046
|
-
const out =
|
|
2046
|
+
const out = execFileSync2("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
2047
2047
|
cwd,
|
|
2048
2048
|
stdio: ["ignore", "pipe", "ignore"]
|
|
2049
2049
|
});
|
|
@@ -2068,8 +2068,10 @@ async function runInteractiveMode(opts) {
|
|
|
2068
2068
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
2069
2069
|
const serverUrl = process.env.GITHUB_SERVER_URL ?? "https://github.com";
|
|
2070
2070
|
const runUrl = runId && repository ? `${serverUrl}/${repository}/actions/runs/${runId}` : void 0;
|
|
2071
|
-
process.stdout.write(
|
|
2072
|
-
|
|
2071
|
+
process.stdout.write(
|
|
2072
|
+
`\u2192 kody:chat:interactive: emitting chat.ready (idleExitMs=${idleExitMs}, hardCapMs=${hardCapMs}, runUrl=${runUrl ?? "n/a"})
|
|
2073
|
+
`
|
|
2074
|
+
);
|
|
2073
2075
|
await emit2(opts.sink, "chat.ready", opts.sessionId, "ready", {
|
|
2074
2076
|
sessionId: opts.sessionId,
|
|
2075
2077
|
startedAt: new Date(startedAt).toISOString(),
|
|
@@ -2147,117 +2149,89 @@ function findNextUserTurn(turns, fromIdx) {
|
|
|
2147
2149
|
if (turns.length > 0 && turns[turns.length - 1].role === "user") return turns.length - 1;
|
|
2148
2150
|
return -1;
|
|
2149
2151
|
}
|
|
2150
|
-
function commitTurn(cwd, sessionId,
|
|
2152
|
+
function commitTurn(cwd, sessionId, _verbose) {
|
|
2151
2153
|
const sessionRel = path8.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
2152
2154
|
const eventsRel = path8.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
2153
|
-
const
|
|
2154
|
-
if (
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2155
|
+
const rels = [sessionRel, eventsRel].filter((p) => fs9.existsSync(path8.join(cwd, p)));
|
|
2156
|
+
if (rels.length === 0) return;
|
|
2157
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
2158
|
+
if (!repository) {
|
|
2159
|
+
process.stderr.write(
|
|
2160
|
+
`[kody:chat:interactive] GITHUB_REPOSITORY unset; cannot persist session/events via Contents API
|
|
2161
|
+
`
|
|
2162
|
+
);
|
|
2159
2163
|
return;
|
|
2160
2164
|
}
|
|
2161
|
-
const
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
exec(["fetch", "--quiet", "origin", eventsBranch]);
|
|
2167
|
-
exec(["worktree", "add", "--detach", "--quiet", worktreeDir, `origin/${eventsBranch}`]);
|
|
2168
|
-
worktreeAdded = true;
|
|
2169
|
-
for (const rel of paths) {
|
|
2170
|
-
const src = path8.join(cwd, rel);
|
|
2171
|
-
const dst = path8.join(worktreeDir, rel);
|
|
2172
|
-
fs9.mkdirSync(path8.dirname(dst), { recursive: true });
|
|
2173
|
-
fs9.copyFileSync(src, dst);
|
|
2174
|
-
}
|
|
2175
|
-
commitPathsAndPush(worktreeDir, paths, sessionId, verbose, `HEAD:${eventsBranch}`);
|
|
2176
|
-
} catch (err) {
|
|
2177
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2178
|
-
process.stderr.write(`[kody:chat:interactive] worktree commit failed: ${msg}
|
|
2179
|
-
`);
|
|
2180
|
-
} finally {
|
|
2181
|
-
if (worktreeAdded) {
|
|
2182
|
-
try {
|
|
2183
|
-
exec(["worktree", "remove", "--force", "--quiet", worktreeDir]);
|
|
2184
|
-
} catch {
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
try {
|
|
2188
|
-
fs9.rmSync(worktreeDir, { recursive: true, force: true });
|
|
2189
|
-
} catch {
|
|
2190
|
-
}
|
|
2165
|
+
const branch = defaultBranch(cwd) ?? "main";
|
|
2166
|
+
for (const rel of rels) {
|
|
2167
|
+
const repoPath = rel.split(path8.sep).join("/");
|
|
2168
|
+
const localText = fs9.readFileSync(path8.join(cwd, rel), "utf-8");
|
|
2169
|
+
putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
|
|
2191
2170
|
}
|
|
2192
2171
|
}
|
|
2193
|
-
function
|
|
2194
|
-
|
|
2195
|
-
|
|
2172
|
+
function jsonlLines(text) {
|
|
2173
|
+
return text.split("\n").filter((l) => l.length > 0);
|
|
2174
|
+
}
|
|
2175
|
+
function getRemoteBlob(repository, branch, repoPath, cwd) {
|
|
2196
2176
|
try {
|
|
2197
|
-
|
|
2198
|
-
|
|
2177
|
+
const raw = gh(["api", `/repos/${repository}/contents/${repoPath}?ref=${encodeURIComponent(branch)}`], { cwd });
|
|
2178
|
+
const o = JSON.parse(raw);
|
|
2179
|
+
if (o.type === "file" && o.encoding === "base64" && typeof o.content === "string" && typeof o.sha === "string") {
|
|
2180
|
+
return { sha: o.sha, lines: jsonlLines(Buffer.from(o.content, "base64").toString("utf-8")) };
|
|
2181
|
+
}
|
|
2182
|
+
return { sha: null, lines: [] };
|
|
2199
2183
|
} catch (err) {
|
|
2200
2184
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
return;
|
|
2185
|
+
if (/HTTP 404/i.test(msg) || /Not Found/i.test(msg)) return { sha: null, lines: [] };
|
|
2186
|
+
throw err;
|
|
2204
2187
|
}
|
|
2188
|
+
}
|
|
2189
|
+
function putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd) {
|
|
2205
2190
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
2191
|
+
const remote = getRemoteBlob(repository, branch, repoPath, cwd);
|
|
2192
|
+
let body = localText;
|
|
2193
|
+
if (attempt > 1 && remote.lines.length > 0) {
|
|
2194
|
+
const localLines = jsonlLines(localText);
|
|
2195
|
+
const localSet = new Set(localLines);
|
|
2196
|
+
const extra = remote.lines.filter((l) => !localSet.has(l));
|
|
2197
|
+
if (extra.length > 0) body = [...localLines, ...extra].join("\n") + "\n";
|
|
2198
|
+
}
|
|
2199
|
+
const payload = {
|
|
2200
|
+
message: `chat: interactive turn for ${sessionId}`,
|
|
2201
|
+
content: Buffer.from(body, "utf-8").toString("base64"),
|
|
2202
|
+
branch
|
|
2203
|
+
};
|
|
2204
|
+
if (remote.sha) payload.sha = remote.sha;
|
|
2206
2205
|
try {
|
|
2207
|
-
|
|
2206
|
+
gh(["api", "--method", "PUT", `/repos/${repository}/contents/${repoPath}`, "--input", "-"], {
|
|
2207
|
+
cwd,
|
|
2208
|
+
input: JSON.stringify(payload)
|
|
2209
|
+
});
|
|
2208
2210
|
return;
|
|
2209
2211
|
} catch (err) {
|
|
2210
2212
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2211
|
-
const
|
|
2212
|
-
if (!
|
|
2213
|
-
process.stderr.write(`[kody:chat:interactive]
|
|
2214
|
-
`);
|
|
2215
|
-
return;
|
|
2216
|
-
}
|
|
2217
|
-
process.stderr.write(`[kody:chat:interactive] push rejected (attempt ${attempt}); fetch+rebase+retry
|
|
2218
|
-
`);
|
|
2219
|
-
try {
|
|
2220
|
-
exec(["fetch", "--quiet", "origin"]);
|
|
2221
|
-
const upstream = pushSpec.includes(":") ? `origin/${pushSpec.split(":")[1]}` : (() => {
|
|
2222
|
-
const branch = currentBranch2(cwd);
|
|
2223
|
-
return branch ? `origin/${branch}` : null;
|
|
2224
|
-
})();
|
|
2225
|
-
if (!upstream) {
|
|
2226
|
-
process.stderr.write(`[kody:chat:interactive] cannot rebase: no upstream resolved
|
|
2227
|
-
`);
|
|
2228
|
-
return;
|
|
2229
|
-
}
|
|
2230
|
-
exec(["rebase", "--quiet", upstream]);
|
|
2231
|
-
} catch (rebaseErr) {
|
|
2232
|
-
const rmsg = rebaseErr instanceof Error ? rebaseErr.message : String(rebaseErr);
|
|
2233
|
-
process.stderr.write(`[kody:chat:interactive] rebase failed: ${rmsg}
|
|
2213
|
+
const isConflict = /HTTP 409/i.test(msg) || /HTTP 422/i.test(msg) || /does not match|but expected/i.test(msg);
|
|
2214
|
+
if (!isConflict || attempt === 3) {
|
|
2215
|
+
process.stderr.write(`[kody:chat:interactive] Contents PUT failed (${repoPath}, attempt ${attempt}): ${msg}
|
|
2234
2216
|
`);
|
|
2235
2217
|
return;
|
|
2236
2218
|
}
|
|
2219
|
+
process.stderr.write(
|
|
2220
|
+
`[kody:chat:interactive] Contents PUT conflict (${repoPath}, attempt ${attempt}); refetch+union+retry
|
|
2221
|
+
`
|
|
2222
|
+
);
|
|
2237
2223
|
}
|
|
2238
2224
|
}
|
|
2239
2225
|
}
|
|
2240
2226
|
function defaultBranch(cwd) {
|
|
2241
2227
|
try {
|
|
2242
|
-
const out =
|
|
2243
|
-
"git",
|
|
2244
|
-
["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
|
|
2245
|
-
{ cwd, stdio: ["ignore", "pipe", "ignore"] }
|
|
2246
|
-
);
|
|
2247
|
-
const symbolic = out.toString("utf-8").trim();
|
|
2248
|
-
if (symbolic.startsWith("origin/")) return symbolic.slice("origin/".length);
|
|
2249
|
-
return symbolic || null;
|
|
2250
|
-
} catch {
|
|
2251
|
-
return null;
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
function currentBranch2(cwd) {
|
|
2255
|
-
try {
|
|
2256
|
-
const out = execFileSync2("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
2228
|
+
const out = execFileSync3("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], {
|
|
2257
2229
|
cwd,
|
|
2258
2230
|
stdio: ["ignore", "pipe", "ignore"]
|
|
2259
2231
|
});
|
|
2260
|
-
|
|
2232
|
+
const symbolic = out.toString("utf-8").trim();
|
|
2233
|
+
if (symbolic.startsWith("origin/")) return symbolic.slice("origin/".length);
|
|
2234
|
+
return symbolic || null;
|
|
2261
2235
|
} catch {
|
|
2262
2236
|
return null;
|
|
2263
2237
|
}
|
|
@@ -2281,8 +2255,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
|
|
|
2281
2255
|
|
|
2282
2256
|
// src/kody-cli.ts
|
|
2283
2257
|
import { execFileSync as execFileSync30 } from "child_process";
|
|
2284
|
-
import * as
|
|
2285
|
-
import * as
|
|
2258
|
+
import * as fs37 from "fs";
|
|
2259
|
+
import * as path34 from "path";
|
|
2286
2260
|
|
|
2287
2261
|
// src/dispatch.ts
|
|
2288
2262
|
import * as fs10 from "fs";
|
|
@@ -2604,8 +2578,8 @@ init_issue();
|
|
|
2604
2578
|
|
|
2605
2579
|
// src/executor.ts
|
|
2606
2580
|
import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
|
|
2607
|
-
import * as
|
|
2608
|
-
import * as
|
|
2581
|
+
import * as fs36 from "fs";
|
|
2582
|
+
import * as path33 from "path";
|
|
2609
2583
|
init_events();
|
|
2610
2584
|
|
|
2611
2585
|
// src/lifecycleLabels.ts
|
|
@@ -3229,7 +3203,7 @@ function errMsg(err) {
|
|
|
3229
3203
|
// src/litellm.ts
|
|
3230
3204
|
import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
|
|
3231
3205
|
import * as fs12 from "fs";
|
|
3232
|
-
import * as
|
|
3206
|
+
import * as os2 from "os";
|
|
3233
3207
|
import * as path10 from "path";
|
|
3234
3208
|
async function checkLitellmHealth(url) {
|
|
3235
3209
|
try {
|
|
@@ -3277,13 +3251,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
|
|
|
3277
3251
|
throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
|
|
3278
3252
|
}
|
|
3279
3253
|
}
|
|
3280
|
-
const configPath = path10.join(
|
|
3254
|
+
const configPath = path10.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
|
|
3281
3255
|
fs12.writeFileSync(configPath, generateLitellmConfigYaml(model));
|
|
3282
3256
|
const portMatch = url.match(/:(\d+)/);
|
|
3283
3257
|
const port = portMatch ? portMatch[1] : "4000";
|
|
3284
3258
|
const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
|
|
3285
3259
|
const dotenvVars = readDotenvApiKeys(projectDir);
|
|
3286
|
-
const logPath = path10.join(
|
|
3260
|
+
const logPath = path10.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
|
|
3287
3261
|
const outFd = fs12.openSync(logPath, "w");
|
|
3288
3262
|
const child = spawn2(cmd, args, {
|
|
3289
3263
|
stdio: ["ignore", outFd, outFd],
|
|
@@ -4244,7 +4218,7 @@ var brainServe = async (ctx) => {
|
|
|
4244
4218
|
|
|
4245
4219
|
// src/scripts/buildSyntheticPlugin.ts
|
|
4246
4220
|
import * as fs16 from "fs";
|
|
4247
|
-
import * as
|
|
4221
|
+
import * as os3 from "os";
|
|
4248
4222
|
import * as path14 from "path";
|
|
4249
4223
|
function getPluginsCatalogRoot() {
|
|
4250
4224
|
const here = path14.dirname(new URL(import.meta.url).pathname);
|
|
@@ -4267,7 +4241,7 @@ var buildSyntheticPlugin = async (ctx, profile) => {
|
|
|
4267
4241
|
if (!needsSynthetic) return;
|
|
4268
4242
|
const catalog = getPluginsCatalogRoot();
|
|
4269
4243
|
const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4270
|
-
const root = path14.join(
|
|
4244
|
+
const root = path14.join(os3.tmpdir(), `kody-synth-${runId}`);
|
|
4271
4245
|
fs16.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
|
|
4272
4246
|
const resolvePart = (bucket, entry) => {
|
|
4273
4247
|
const local = path14.join(profile.dir, bucket, entry);
|
|
@@ -5490,10 +5464,10 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
|
|
|
5490
5464
|
// src/scripts/diagMcp.ts
|
|
5491
5465
|
import { execFileSync as execFileSync11 } from "child_process";
|
|
5492
5466
|
import * as fs21 from "fs";
|
|
5493
|
-
import * as
|
|
5467
|
+
import * as os4 from "os";
|
|
5494
5468
|
import * as path20 from "path";
|
|
5495
5469
|
var diagMcp = async (_ctx) => {
|
|
5496
|
-
const home =
|
|
5470
|
+
const home = os4.homedir();
|
|
5497
5471
|
const cacheDir = path20.join(home, ".cache", "ms-playwright");
|
|
5498
5472
|
let entries = [];
|
|
5499
5473
|
try {
|
|
@@ -6168,6 +6142,8 @@ function parseFlatYaml(text) {
|
|
|
6168
6142
|
const lower = value.toLowerCase();
|
|
6169
6143
|
if (lower === "true") out.disabled = true;
|
|
6170
6144
|
else if (lower === "false") out.disabled = false;
|
|
6145
|
+
} else if (key === "worker" && value.length > 0) {
|
|
6146
|
+
out.worker = value;
|
|
6171
6147
|
}
|
|
6172
6148
|
}
|
|
6173
6149
|
return out;
|
|
@@ -6554,6 +6530,14 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
|
|
|
6554
6530
|
results.push({ slug, exitCode: 0, skipped: true, reason: "disabled" });
|
|
6555
6531
|
continue;
|
|
6556
6532
|
}
|
|
6533
|
+
if (!frontmatter.worker || frontmatter.worker.trim().length === 0) {
|
|
6534
|
+
process.stderr.write(
|
|
6535
|
+
`[jobs] \u23ED skip ${slug}: no worker assigned (add 'worker: <slug>' frontmatter)
|
|
6536
|
+
`
|
|
6537
|
+
);
|
|
6538
|
+
results.push({ slug, exitCode: 0, skipped: true, reason: "no worker assigned" });
|
|
6539
|
+
continue;
|
|
6540
|
+
}
|
|
6557
6541
|
const decision = await decideShouldFire(frontmatter.every, slug, backend, now);
|
|
6558
6542
|
if (decision.skip) {
|
|
6559
6543
|
process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
|
|
@@ -7999,10 +7983,10 @@ import * as fs29 from "fs";
|
|
|
7999
7983
|
import * as path27 from "path";
|
|
8000
7984
|
var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
|
|
8001
7985
|
var GoalStateError = class extends Error {
|
|
8002
|
-
constructor(
|
|
8003
|
-
super(`Invalid goal state at ${
|
|
7986
|
+
constructor(path36, message) {
|
|
7987
|
+
super(`Invalid goal state at ${path36}:
|
|
8004
7988
|
${message}`);
|
|
8005
|
-
this.path =
|
|
7989
|
+
this.path = path36;
|
|
8006
7990
|
this.name = "GoalStateError";
|
|
8007
7991
|
}
|
|
8008
7992
|
path;
|
|
@@ -8169,6 +8153,7 @@ import * as fs30 from "fs";
|
|
|
8169
8153
|
import * as path28 from "path";
|
|
8170
8154
|
var loadJobFromFile = async (ctx, _profile, args) => {
|
|
8171
8155
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
8156
|
+
const workersDir = String(args?.workersDir ?? ".kody/workers");
|
|
8172
8157
|
const slugArg = String(args?.slugArg ?? "job");
|
|
8173
8158
|
const slug = String(ctx.args[slugArg] ?? "").trim();
|
|
8174
8159
|
if (!slug) {
|
|
@@ -8180,6 +8165,21 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8180
8165
|
}
|
|
8181
8166
|
const raw = fs30.readFileSync(absPath, "utf-8");
|
|
8182
8167
|
const { title, body } = parseJobFile(raw, slug);
|
|
8168
|
+
const workerSlug = (splitFrontmatter(raw).frontmatter.worker ?? "").trim();
|
|
8169
|
+
let workerTitle = "";
|
|
8170
|
+
let workerPersona = "";
|
|
8171
|
+
if (workerSlug) {
|
|
8172
|
+
const workerPath = path28.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8173
|
+
if (!fs30.existsSync(workerPath)) {
|
|
8174
|
+
throw new Error(
|
|
8175
|
+
`loadJobFromFile: job '${slug}' declares worker '${workerSlug}' but ${workerPath} does not exist`
|
|
8176
|
+
);
|
|
8177
|
+
}
|
|
8178
|
+
const workerRaw = fs30.readFileSync(workerPath, "utf-8");
|
|
8179
|
+
const parsed = parseJobFile(workerRaw, workerSlug);
|
|
8180
|
+
workerTitle = parsed.title;
|
|
8181
|
+
workerPersona = parsed.body;
|
|
8182
|
+
}
|
|
8183
8183
|
const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
|
|
8184
8184
|
const loaded = await backend.load(slug);
|
|
8185
8185
|
ctx.data.jobSlug = slug;
|
|
@@ -8187,6 +8187,9 @@ var loadJobFromFile = async (ctx, _profile, args) => {
|
|
|
8187
8187
|
ctx.data.jobIntent = body;
|
|
8188
8188
|
ctx.data.jobState = loaded;
|
|
8189
8189
|
ctx.data.jobStateJson = JSON.stringify(loaded.state, null, 2);
|
|
8190
|
+
ctx.data.workerSlug = workerSlug;
|
|
8191
|
+
ctx.data.workerTitle = workerTitle;
|
|
8192
|
+
ctx.data.workerPersona = workerPersona;
|
|
8190
8193
|
};
|
|
8191
8194
|
function parseJobFile(raw, slug) {
|
|
8192
8195
|
let stripped = raw;
|
|
@@ -8209,6 +8212,79 @@ function humanizeSlug(slug) {
|
|
|
8209
8212
|
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
8210
8213
|
}
|
|
8211
8214
|
|
|
8215
|
+
// src/scripts/loadWorkerAdhoc.ts
|
|
8216
|
+
import * as fs31 from "fs";
|
|
8217
|
+
import * as path29 from "path";
|
|
8218
|
+
var loadWorkerAdhoc = async (ctx, _profile, args) => {
|
|
8219
|
+
const workersDir = String(args?.workersDir ?? ".kody/workers");
|
|
8220
|
+
const workerSlug = String(ctx.args.worker ?? "").trim();
|
|
8221
|
+
if (!workerSlug) {
|
|
8222
|
+
throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
|
|
8223
|
+
}
|
|
8224
|
+
const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
|
|
8225
|
+
if (!fs31.existsSync(workerPath)) {
|
|
8226
|
+
throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
|
|
8227
|
+
}
|
|
8228
|
+
const { title, body } = parsePersona(fs31.readFileSync(workerPath, "utf-8"), workerSlug);
|
|
8229
|
+
const message = resolveMessage(ctx.args.message);
|
|
8230
|
+
if (!message) {
|
|
8231
|
+
throw new Error(
|
|
8232
|
+
"loadWorkerAdhoc: no message \u2014 neither the dispatching comment body nor ctx.args.message provided one"
|
|
8233
|
+
);
|
|
8234
|
+
}
|
|
8235
|
+
ctx.data.workerSlug = workerSlug;
|
|
8236
|
+
ctx.data.workerTitle = title;
|
|
8237
|
+
ctx.data.workerPersona = body;
|
|
8238
|
+
ctx.data.message = message;
|
|
8239
|
+
ctx.data.thread = String(ctx.args.thread ?? "").trim();
|
|
8240
|
+
};
|
|
8241
|
+
function resolveMessage(messageArg) {
|
|
8242
|
+
const fromComment = readCommentBody();
|
|
8243
|
+
if (fromComment) return stripDirective(fromComment);
|
|
8244
|
+
return String(messageArg ?? "").trim();
|
|
8245
|
+
}
|
|
8246
|
+
function readCommentBody() {
|
|
8247
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
8248
|
+
if (!eventPath || !fs31.existsSync(eventPath)) return "";
|
|
8249
|
+
try {
|
|
8250
|
+
const event = JSON.parse(fs31.readFileSync(eventPath, "utf-8"));
|
|
8251
|
+
return String(event.comment?.body ?? "");
|
|
8252
|
+
} catch {
|
|
8253
|
+
return "";
|
|
8254
|
+
}
|
|
8255
|
+
}
|
|
8256
|
+
function stripDirective(body) {
|
|
8257
|
+
const lines = body.split("\n");
|
|
8258
|
+
let start = 0;
|
|
8259
|
+
while (start < lines.length) {
|
|
8260
|
+
const line = lines[start].trim();
|
|
8261
|
+
if (line.length === 0) {
|
|
8262
|
+
start++;
|
|
8263
|
+
continue;
|
|
8264
|
+
}
|
|
8265
|
+
if (/@kody\s+worker-ask\b/i.test(line)) {
|
|
8266
|
+
start++;
|
|
8267
|
+
continue;
|
|
8268
|
+
}
|
|
8269
|
+
break;
|
|
8270
|
+
}
|
|
8271
|
+
return lines.slice(start).join("\n").trim();
|
|
8272
|
+
}
|
|
8273
|
+
function parsePersona(raw, slug) {
|
|
8274
|
+
const stripped = splitFrontmatter(raw).body;
|
|
8275
|
+
const trimmed = stripped.trim();
|
|
8276
|
+
const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
|
|
8277
|
+
const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
|
|
8278
|
+
if (h1) {
|
|
8279
|
+
const rest = trimmed.slice(firstLine2.length).replace(/^\n+/, "");
|
|
8280
|
+
return { title: h1[1].trim(), body: rest };
|
|
8281
|
+
}
|
|
8282
|
+
return { title: humanizeSlug2(slug), body: trimmed };
|
|
8283
|
+
}
|
|
8284
|
+
function humanizeSlug2(slug) {
|
|
8285
|
+
return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
|
|
8286
|
+
}
|
|
8287
|
+
|
|
8212
8288
|
// src/scripts/index.ts
|
|
8213
8289
|
init_loadMemoryContext();
|
|
8214
8290
|
init_loadPriorArt();
|
|
@@ -8217,8 +8293,8 @@ init_loadPriorArt();
|
|
|
8217
8293
|
init_events();
|
|
8218
8294
|
|
|
8219
8295
|
// src/taskContext.ts
|
|
8220
|
-
import * as
|
|
8221
|
-
import * as
|
|
8296
|
+
import * as fs33 from "fs";
|
|
8297
|
+
import * as path31 from "path";
|
|
8222
8298
|
var TASK_CONTEXT_SCHEMA_VERSION = 1;
|
|
8223
8299
|
function buildTaskContext(args) {
|
|
8224
8300
|
return {
|
|
@@ -8234,10 +8310,10 @@ function buildTaskContext(args) {
|
|
|
8234
8310
|
}
|
|
8235
8311
|
function persistTaskContext(cwd, ctx) {
|
|
8236
8312
|
try {
|
|
8237
|
-
const dir =
|
|
8238
|
-
|
|
8239
|
-
const file =
|
|
8240
|
-
|
|
8313
|
+
const dir = path31.join(cwd, ".kody", "runs", ctx.runId);
|
|
8314
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
8315
|
+
const file = path31.join(dir, "task-context.json");
|
|
8316
|
+
fs33.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
|
|
8241
8317
|
`);
|
|
8242
8318
|
return file;
|
|
8243
8319
|
} catch (err) {
|
|
@@ -9600,8 +9676,8 @@ function resolveBaseOverride(value) {
|
|
|
9600
9676
|
|
|
9601
9677
|
// src/scripts/runTickScript.ts
|
|
9602
9678
|
import { spawnSync } from "child_process";
|
|
9603
|
-
import * as
|
|
9604
|
-
import * as
|
|
9679
|
+
import * as fs34 from "fs";
|
|
9680
|
+
import * as path32 from "path";
|
|
9605
9681
|
var runTickScript = async (ctx, _profile, args) => {
|
|
9606
9682
|
ctx.skipAgent = true;
|
|
9607
9683
|
const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
|
|
@@ -9613,13 +9689,13 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
9613
9689
|
ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
|
|
9614
9690
|
return;
|
|
9615
9691
|
}
|
|
9616
|
-
const jobPath =
|
|
9617
|
-
if (!
|
|
9692
|
+
const jobPath = path32.join(ctx.cwd, jobsDir, `${slug}.md`);
|
|
9693
|
+
if (!fs34.existsSync(jobPath)) {
|
|
9618
9694
|
ctx.output.exitCode = 99;
|
|
9619
9695
|
ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
|
|
9620
9696
|
return;
|
|
9621
9697
|
}
|
|
9622
|
-
const raw =
|
|
9698
|
+
const raw = fs34.readFileSync(jobPath, "utf-8");
|
|
9623
9699
|
const { frontmatter } = splitFrontmatter(raw);
|
|
9624
9700
|
const tickScript = frontmatter.tickScript;
|
|
9625
9701
|
if (!tickScript) {
|
|
@@ -9627,8 +9703,8 @@ var runTickScript = async (ctx, _profile, args) => {
|
|
|
9627
9703
|
ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
|
|
9628
9704
|
return;
|
|
9629
9705
|
}
|
|
9630
|
-
const scriptPath =
|
|
9631
|
-
if (!
|
|
9706
|
+
const scriptPath = path32.isAbsolute(tickScript) ? tickScript : path32.join(ctx.cwd, tickScript);
|
|
9707
|
+
if (!fs34.existsSync(scriptPath)) {
|
|
9632
9708
|
ctx.output.exitCode = 99;
|
|
9633
9709
|
ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
|
|
9634
9710
|
return;
|
|
@@ -10650,7 +10726,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
|
|
|
10650
10726
|
};
|
|
10651
10727
|
|
|
10652
10728
|
// src/scripts/writeRunSummary.ts
|
|
10653
|
-
import * as
|
|
10729
|
+
import * as fs35 from "fs";
|
|
10654
10730
|
var writeRunSummary = async (ctx, profile) => {
|
|
10655
10731
|
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
10656
10732
|
if (!summaryPath) return;
|
|
@@ -10672,7 +10748,7 @@ var writeRunSummary = async (ctx, profile) => {
|
|
|
10672
10748
|
if (reason) lines.push(`- **Reason:** ${reason}`);
|
|
10673
10749
|
lines.push("");
|
|
10674
10750
|
try {
|
|
10675
|
-
|
|
10751
|
+
fs35.appendFileSync(summaryPath, `${lines.join("\n")}
|
|
10676
10752
|
`);
|
|
10677
10753
|
} catch {
|
|
10678
10754
|
}
|
|
@@ -10693,6 +10769,7 @@ var preflightScripts = {
|
|
|
10693
10769
|
loadIssueContext,
|
|
10694
10770
|
loadIssueStateComment,
|
|
10695
10771
|
loadJobFromFile,
|
|
10772
|
+
loadWorkerAdhoc,
|
|
10696
10773
|
loadConventions,
|
|
10697
10774
|
loadCoverageRules,
|
|
10698
10775
|
loadMemoryContext,
|
|
@@ -10897,9 +10974,9 @@ async function runExecutable(profileName, input) {
|
|
|
10897
10974
|
data: { ...input.preloadedData ?? {} },
|
|
10898
10975
|
output: { exitCode: 0 }
|
|
10899
10976
|
};
|
|
10900
|
-
const ndjsonDir =
|
|
10977
|
+
const ndjsonDir = path33.join(input.cwd, ".kody");
|
|
10901
10978
|
const invokeAgent = async (prompt) => {
|
|
10902
|
-
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) =>
|
|
10979
|
+
const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path33.isAbsolute(p) ? p : path33.resolve(profile.dir, p)).filter((p) => p.length > 0);
|
|
10903
10980
|
const syntheticPath = ctx.data.syntheticPluginPath;
|
|
10904
10981
|
const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
|
|
10905
10982
|
return runAgent({
|
|
@@ -11094,7 +11171,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
|
|
|
11094
11171
|
function getProfileInputsForChild(profileName, _cwd) {
|
|
11095
11172
|
try {
|
|
11096
11173
|
const profilePath = resolveProfilePath(profileName);
|
|
11097
|
-
if (!
|
|
11174
|
+
if (!fs36.existsSync(profilePath)) return null;
|
|
11098
11175
|
return loadProfile(profilePath).inputs;
|
|
11099
11176
|
} catch {
|
|
11100
11177
|
return null;
|
|
@@ -11103,17 +11180,17 @@ function getProfileInputsForChild(profileName, _cwd) {
|
|
|
11103
11180
|
function resolveProfilePath(profileName) {
|
|
11104
11181
|
const found = resolveExecutable(profileName);
|
|
11105
11182
|
if (found) return found;
|
|
11106
|
-
const here =
|
|
11183
|
+
const here = path33.dirname(new URL(import.meta.url).pathname);
|
|
11107
11184
|
const candidates = [
|
|
11108
|
-
|
|
11185
|
+
path33.join(here, "executables", profileName, "profile.json"),
|
|
11109
11186
|
// same-dir sibling (dev)
|
|
11110
|
-
|
|
11187
|
+
path33.join(here, "..", "executables", profileName, "profile.json"),
|
|
11111
11188
|
// up one (prod: dist/bin → dist/executables)
|
|
11112
|
-
|
|
11189
|
+
path33.join(here, "..", "src", "executables", profileName, "profile.json")
|
|
11113
11190
|
// fallback
|
|
11114
11191
|
];
|
|
11115
11192
|
for (const c of candidates) {
|
|
11116
|
-
if (
|
|
11193
|
+
if (fs36.existsSync(c)) return c;
|
|
11117
11194
|
}
|
|
11118
11195
|
return candidates[0];
|
|
11119
11196
|
}
|
|
@@ -11213,8 +11290,8 @@ function resolveShellTimeoutMs(entry) {
|
|
|
11213
11290
|
var SIGKILL_GRACE_MS = 5e3;
|
|
11214
11291
|
async function runShellEntry(entry, ctx, profile) {
|
|
11215
11292
|
const shellName = entry.shell;
|
|
11216
|
-
const shellPath =
|
|
11217
|
-
if (!
|
|
11293
|
+
const shellPath = path33.join(profile.dir, shellName);
|
|
11294
|
+
if (!fs36.existsSync(shellPath)) {
|
|
11218
11295
|
ctx.skipAgent = true;
|
|
11219
11296
|
ctx.output.exitCode = 99;
|
|
11220
11297
|
ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
|
|
@@ -11693,9 +11770,9 @@ function resolveAuthToken(env = process.env) {
|
|
|
11693
11770
|
return token;
|
|
11694
11771
|
}
|
|
11695
11772
|
function detectPackageManager2(cwd) {
|
|
11696
|
-
if (
|
|
11697
|
-
if (
|
|
11698
|
-
if (
|
|
11773
|
+
if (fs37.existsSync(path34.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
11774
|
+
if (fs37.existsSync(path34.join(cwd, "yarn.lock"))) return "yarn";
|
|
11775
|
+
if (fs37.existsSync(path34.join(cwd, "bun.lockb"))) return "bun";
|
|
11699
11776
|
return "npm";
|
|
11700
11777
|
}
|
|
11701
11778
|
function shellOut(cmd, args, cwd, stream = true) {
|
|
@@ -11782,11 +11859,11 @@ function configureGitIdentity(cwd) {
|
|
|
11782
11859
|
}
|
|
11783
11860
|
function postFailureTail(issueNumber, cwd, reason) {
|
|
11784
11861
|
if (!issueNumber) return;
|
|
11785
|
-
const logPath =
|
|
11862
|
+
const logPath = path34.join(cwd, ".kody", "last-run.jsonl");
|
|
11786
11863
|
let tail = "";
|
|
11787
11864
|
try {
|
|
11788
|
-
if (
|
|
11789
|
-
const content =
|
|
11865
|
+
if (fs37.existsSync(logPath)) {
|
|
11866
|
+
const content = fs37.readFileSync(logPath, "utf-8");
|
|
11790
11867
|
tail = content.slice(-3e3);
|
|
11791
11868
|
}
|
|
11792
11869
|
} catch {
|
|
@@ -11811,7 +11888,7 @@ async function runCi(argv) {
|
|
|
11811
11888
|
return 0;
|
|
11812
11889
|
}
|
|
11813
11890
|
const args = parseCiArgs(argv);
|
|
11814
|
-
const cwd = args.cwd ?
|
|
11891
|
+
const cwd = args.cwd ? path34.resolve(args.cwd) : process.cwd();
|
|
11815
11892
|
let earlyConfig;
|
|
11816
11893
|
try {
|
|
11817
11894
|
earlyConfig = loadConfig(cwd);
|
|
@@ -11821,9 +11898,9 @@ async function runCi(argv) {
|
|
|
11821
11898
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
11822
11899
|
const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
|
|
11823
11900
|
let manualWorkflowDispatch = false;
|
|
11824
|
-
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath &&
|
|
11901
|
+
if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs37.existsSync(dispatchEventPath)) {
|
|
11825
11902
|
try {
|
|
11826
|
-
const evt = JSON.parse(
|
|
11903
|
+
const evt = JSON.parse(fs37.readFileSync(dispatchEventPath, "utf-8"));
|
|
11827
11904
|
const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
|
|
11828
11905
|
const sessionInput = String(evt?.inputs?.sessionId ?? "");
|
|
11829
11906
|
manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
|
|
@@ -12082,9 +12159,9 @@ function parseChatArgs(argv, env = process.env) {
|
|
|
12082
12159
|
return result;
|
|
12083
12160
|
}
|
|
12084
12161
|
function commitChatFiles(cwd, sessionId, verbose) {
|
|
12085
|
-
const sessionFile =
|
|
12086
|
-
const eventsFile =
|
|
12087
|
-
const paths = [sessionFile, eventsFile].filter((p) =>
|
|
12162
|
+
const sessionFile = path35.relative(cwd, sessionFilePath(cwd, sessionId));
|
|
12163
|
+
const eventsFile = path35.relative(cwd, eventsFilePath(cwd, sessionId));
|
|
12164
|
+
const paths = [sessionFile, eventsFile].filter((p) => fs38.existsSync(path35.join(cwd, p)));
|
|
12088
12165
|
if (paths.length === 0) return;
|
|
12089
12166
|
const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
|
|
12090
12167
|
try {
|
|
@@ -12122,7 +12199,7 @@ async function runChat(argv) {
|
|
|
12122
12199
|
${CHAT_HELP}`);
|
|
12123
12200
|
return 64;
|
|
12124
12201
|
}
|
|
12125
|
-
const cwd = args.cwd ?
|
|
12202
|
+
const cwd = args.cwd ? path35.resolve(args.cwd) : process.cwd();
|
|
12126
12203
|
const sessionId = args.sessionId;
|
|
12127
12204
|
const unpackedSecrets = unpackAllSecrets();
|
|
12128
12205
|
if (unpackedSecrets > 0) {
|
|
@@ -12174,7 +12251,7 @@ ${CHAT_HELP}`);
|
|
|
12174
12251
|
const sink = buildSink(cwd, sessionId, args.dashboardUrl);
|
|
12175
12252
|
const meta = readMeta(sessionFile);
|
|
12176
12253
|
process.stdout.write(
|
|
12177
|
-
`\u2192 kody:chat: session file=${sessionFile} exists=${
|
|
12254
|
+
`\u2192 kody:chat: session file=${sessionFile} exists=${fs38.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
|
|
12178
12255
|
`
|
|
12179
12256
|
);
|
|
12180
12257
|
try {
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
You are **kody job-tick
|
|
1
|
+
You are **{{workerTitle}}** (worker `{{workerSlug}}`), operating through **kody job-tick** — the coordinator for one file-based job. You do **not** touch code, do **not** commit, and do **not** edit files. You coordinate by inspecting GitHub state and issuing Kody commands as PR comments.
|
|
2
|
+
|
|
3
|
+
## Who you are — worker persona (authoritative identity)
|
|
4
|
+
|
|
5
|
+
The job below assigns you, worker **`{{workerSlug}}`**, as its executor. This persona defines *who* runs the job: your authority, doctrine, voice, and hard limits. Where the persona's restrictions are stricter than the job body, **the persona wins** — a job can never grant you authority your worker persona withholds.
|
|
6
|
+
|
|
7
|
+
{{workerPersona}}
|
|
2
8
|
|
|
3
9
|
## The job
|
|
4
10
|
|
|
5
|
-
Slug **`{{jobSlug}}`** — *{{jobTitle}}
|
|
11
|
+
Slug **`{{jobSlug}}`** — *{{jobTitle}}*, assigned to worker **`{{workerSlug}}`**. The job body below is authoritative for *what* to do, *when* (cadence), allowed commands, and state schema. It is human-edited — re-read it every tick. Execute it **as** the persona above.
|
|
6
12
|
|
|
7
13
|
### Job body
|
|
8
14
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "worker-ask",
|
|
3
|
+
"role": "primitive",
|
|
4
|
+
"describe": "Ad-hoc one-shot: run a worker persona against an inline message + context (from a dashboard @worker mention). Stateless — no job file, no state, no commit. Replies into the originating thread.",
|
|
5
|
+
"kind": "oneshot",
|
|
6
|
+
"inputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "worker",
|
|
9
|
+
"flag": "--worker",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"required": true,
|
|
12
|
+
"describe": "Worker slug — basename (without .md) of the persona file under .kody/workers/."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "thread",
|
|
16
|
+
"flag": "--thread",
|
|
17
|
+
"type": "string",
|
|
18
|
+
"describe": "Where to post the reply: `discussion:<n>` (or a bare number) for a Discussion, or `issue:<n>` for an issue/PR. When set, the worker answers as a comment on that thread."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "message",
|
|
22
|
+
"flag": "--message",
|
|
23
|
+
"type": "string",
|
|
24
|
+
"bindsCommentRest": true,
|
|
25
|
+
"describe": "Fallback inline message when no triggering comment body is available (CLI/testing). In production the message is read verbatim from the dispatching comment body."
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"claudeCode": {
|
|
29
|
+
"model": "inherit",
|
|
30
|
+
"permissionMode": "default",
|
|
31
|
+
"maxTurns": 20,
|
|
32
|
+
"maxThinkingTokens": null,
|
|
33
|
+
"systemPromptAppend": null,
|
|
34
|
+
"tools": ["Bash", "Read"],
|
|
35
|
+
"hooks": [],
|
|
36
|
+
"skills": [],
|
|
37
|
+
"commands": [],
|
|
38
|
+
"subagents": [],
|
|
39
|
+
"plugins": [],
|
|
40
|
+
"mcpServers": []
|
|
41
|
+
},
|
|
42
|
+
"cliTools": [
|
|
43
|
+
{
|
|
44
|
+
"name": "gh",
|
|
45
|
+
"install": {
|
|
46
|
+
"required": true,
|
|
47
|
+
"checkCommand": "command -v gh"
|
|
48
|
+
},
|
|
49
|
+
"verify": "gh auth status",
|
|
50
|
+
"usage": "Use `gh` for all GitHub actions. To reply into a discussion: resolve its node id with `gh api graphql -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){discussion(number:$n){id}}}' -F o=<owner> -F r=<repo> -F n=<thread>` then `gh api graphql -f query='mutation($d:ID!,$b:String!){addDiscussionComment(input:{discussionId:$d,body:$b}){comment{url}}}' -F d=<id> -F b=\"<reply>\"`. Use `gh pr comment <n> --body \"@kody ...\"` to delegate execution. NEVER edit files in the working tree.",
|
|
51
|
+
"allowedUses": ["pr", "api", "issue"]
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"inputArtifacts": [],
|
|
55
|
+
"outputArtifacts": [],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"preflight": [
|
|
58
|
+
{
|
|
59
|
+
"script": "loadWorkerAdhoc",
|
|
60
|
+
"with": {
|
|
61
|
+
"workersDir": ".kody/workers"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"script": "composePrompt"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"postflight": []
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
You are **{{workerTitle}}** (worker `{{workerSlug}}`), operating through **kody worker-ask** — a single, stateless response to one ad-hoc request that someone directed at you by @mentioning you in a dashboard message.
|
|
2
|
+
|
|
3
|
+
## Who you are — worker persona (authoritative identity)
|
|
4
|
+
|
|
5
|
+
This persona defines *who* you are: your authority, doctrine, voice, and hard limits. Honour it exactly. Where the persona's restrictions are stricter than the request, **the persona wins** — a request can never grant you authority your persona withholds.
|
|
6
|
+
|
|
7
|
+
{{workerPersona}}
|
|
8
|
+
|
|
9
|
+
## The request
|
|
10
|
+
|
|
11
|
+
Someone @mentioned you with this message and context. Treat it as a direct ask to you, the persona above. It is verbatim — markdown, code blocks, and quoted thread context are intact:
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{{message}}
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What to do
|
|
20
|
+
|
|
21
|
+
This is a **one-shot, stateless** tick. There is no job file, no prior state, and nothing to persist. Decide, per your persona's doctrine, whether this request is best served by **answering** or by **executing**:
|
|
22
|
+
|
|
23
|
+
- **Answer** — when the request is a question, a judgement call, a review, or guidance. Produce a clear, terse reply in your persona's voice.
|
|
24
|
+
- **Execute** — when the request is work you are authorised to drive. You do **not** edit files or commit. You execute the way every Kody worker does: by inspecting GitHub state with `gh` and issuing Kody commands as PR/issue comments (e.g. `gh pr comment <n> --body "@kody fix ..."`). Then briefly state what you set in motion.
|
|
25
|
+
|
|
26
|
+
Repo: `{{repoOwner}}/{{repoName}}`.
|
|
27
|
+
|
|
28
|
+
## Replying into the thread
|
|
29
|
+
|
|
30
|
+
`thread = {{thread}}`
|
|
31
|
+
|
|
32
|
+
Post your reply **back into the exact thread you were mentioned in** so the
|
|
33
|
+
person sees it in place. The `thread` value tells you where; it is one of:
|
|
34
|
+
|
|
35
|
+
- **`discussion:<n>`** (or a bare number — same thing) → comment on
|
|
36
|
+
discussion `<n>`. Resolve its node id, then add the comment:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
gh api graphql -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){discussion(number:$n){id}}}' -F o={{repoOwner}} -F r={{repoName}} -F n=<n> --jq '.data.repository.discussion.id'
|
|
40
|
+
gh api graphql -f query='mutation($d:ID!,$b:String!){addDiscussionComment(input:{discussionId:$d,body:$b}){comment{url}}}' -F d=<id> -F b="<your reply, markdown>"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **`issue:<n>`** → comment on issue/PR `<n>` (the issues API serves both):
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
gh api -X POST repos/{{repoOwner}}/{{repoName}}/issues/<n>/comments -f body="<your reply, markdown>"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Sign the reply so it reads as you, e.g. lead with `**{{workerTitle}}** —`.
|
|
50
|
+
|
|
51
|
+
If `thread` is empty, just produce your reply as your final response (no
|
|
52
|
+
GitHub post).
|
|
53
|
+
|
|
54
|
+
## Rules
|
|
55
|
+
|
|
56
|
+
- Never edit, create, or delete files in the working tree. Never `git commit`/`push`.
|
|
57
|
+
- The only shell tool is `gh`. Everything goes through it.
|
|
58
|
+
- Stay inside your persona's authority and restrictions at all times.
|
|
59
|
+
- Be terse. One focused reply; do not spawn duplicate work.
|
|
60
|
+
- There is **no state output contract** — do not emit a state fenced block. When you have replied (or posted to the thread), you are done.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kody-ade/kody-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.96",
|
|
4
4
|
"description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "worker-scheduler",
|
|
3
|
-
"role": "watch",
|
|
4
|
-
"describe": "Scheduled: for every worker file under .kody/workers/, invoke worker-tick once. No agent on the scheduler itself. Parallel to job-scheduler.",
|
|
5
|
-
"kind": "scheduled",
|
|
6
|
-
"schedule": "*/5 * * * *",
|
|
7
|
-
"inputs": [],
|
|
8
|
-
"claudeCode": {
|
|
9
|
-
"model": "inherit",
|
|
10
|
-
"permissionMode": "default",
|
|
11
|
-
"maxTurns": null,
|
|
12
|
-
"maxThinkingTokens": null,
|
|
13
|
-
"systemPromptAppend": null,
|
|
14
|
-
"tools": [],
|
|
15
|
-
"hooks": [],
|
|
16
|
-
"skills": [],
|
|
17
|
-
"commands": [],
|
|
18
|
-
"subagents": [],
|
|
19
|
-
"plugins": [],
|
|
20
|
-
"mcpServers": []
|
|
21
|
-
},
|
|
22
|
-
"cliTools": [
|
|
23
|
-
{
|
|
24
|
-
"name": "gh",
|
|
25
|
-
"install": {
|
|
26
|
-
"required": true,
|
|
27
|
-
"checkCommand": "command -v gh"
|
|
28
|
-
},
|
|
29
|
-
"verify": "gh auth status",
|
|
30
|
-
"usage": "",
|
|
31
|
-
"allowedUses": ["api", "issue", "pr"]
|
|
32
|
-
}
|
|
33
|
-
],
|
|
34
|
-
"inputArtifacts": [],
|
|
35
|
-
"outputArtifacts": [],
|
|
36
|
-
"scripts": {
|
|
37
|
-
"preflight": [
|
|
38
|
-
{
|
|
39
|
-
"script": "dispatchJobFileTicks",
|
|
40
|
-
"with": {
|
|
41
|
-
"jobsDir": ".kody/workers",
|
|
42
|
-
"targetExecutable": "worker-tick",
|
|
43
|
-
"scriptedExecutable": "worker-tick-scripted",
|
|
44
|
-
"slugArg": "job"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
],
|
|
48
|
-
"postflight": []
|
|
49
|
-
}
|
|
50
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "worker-tick",
|
|
3
|
-
"role": "primitive",
|
|
4
|
-
"describe": "One classifier tick for one worker file: read intent + state, decide and execute via gh, emit next state. Parallel to job-tick but reads .kody/workers/.",
|
|
5
|
-
"kind": "oneshot",
|
|
6
|
-
"inputs": [
|
|
7
|
-
{
|
|
8
|
-
"name": "job",
|
|
9
|
-
"flag": "--job",
|
|
10
|
-
"type": "string",
|
|
11
|
-
"required": true,
|
|
12
|
-
"describe": "Worker slug — basename (without .md) of the file under .kody/workers/."
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"name": "force",
|
|
16
|
-
"flag": "--force",
|
|
17
|
-
"type": "bool",
|
|
18
|
-
"describe": "When true, the agent ignores the worker body's cadence guard and executes the work this tick. All other body rules (allowed commands, restrictions, state schema) still apply. Used for manual triggers from the dashboard's 'Run now' button."
|
|
19
|
-
}
|
|
20
|
-
],
|
|
21
|
-
"claudeCode": {
|
|
22
|
-
"model": "inherit",
|
|
23
|
-
"permissionMode": "default",
|
|
24
|
-
"maxTurns": 20,
|
|
25
|
-
"maxThinkingTokens": null,
|
|
26
|
-
"systemPromptAppend": null,
|
|
27
|
-
"tools": ["Bash", "Read"],
|
|
28
|
-
"hooks": [],
|
|
29
|
-
"skills": [],
|
|
30
|
-
"commands": [],
|
|
31
|
-
"subagents": [],
|
|
32
|
-
"plugins": [],
|
|
33
|
-
"mcpServers": []
|
|
34
|
-
},
|
|
35
|
-
"cliTools": [
|
|
36
|
-
{
|
|
37
|
-
"name": "gh",
|
|
38
|
-
"install": {
|
|
39
|
-
"required": true,
|
|
40
|
-
"checkCommand": "command -v gh"
|
|
41
|
-
},
|
|
42
|
-
"verify": "gh auth status",
|
|
43
|
-
"usage": "Use `gh` for all GitHub actions: `gh pr list ...` to enumerate candidate PRs, `gh pr comment <n> --body \"...\"` to issue a Kody command, `gh pr view <n> --json mergeable,statusCheckRollup,headRefOid` to inspect state, `gh api ...` for anything else. NEVER edit files in the working tree.",
|
|
44
|
-
"allowedUses": ["pr", "api", "issue"]
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"inputArtifacts": [],
|
|
48
|
-
"outputArtifacts": [],
|
|
49
|
-
"scripts": {
|
|
50
|
-
"preflight": [
|
|
51
|
-
{
|
|
52
|
-
"script": "loadJobFromFile",
|
|
53
|
-
"with": {
|
|
54
|
-
"jobsDir": ".kody/workers",
|
|
55
|
-
"slugArg": "job"
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"script": "composePrompt"
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
"postflight": [
|
|
63
|
-
{
|
|
64
|
-
"script": "parseJobStateFromAgentResult",
|
|
65
|
-
"with": {
|
|
66
|
-
"fenceLabel": "kody-job-next-state"
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"script": "writeJobStateFile"
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
You are **kody worker-tick**, the coordinator for one file-based worker. You do **not** touch code, do **not** commit, and do **not** edit files. You coordinate by inspecting GitHub state and issuing Kody commands as PR comments.
|
|
2
|
-
|
|
3
|
-
## The worker
|
|
4
|
-
|
|
5
|
-
Slug **`{{jobSlug}}`** — *{{jobTitle}}*. The worker body below is authoritative: it states what success looks like, allowed commands, and restrictions. The worker file is human-edited — re-read it every tick.
|
|
6
|
-
|
|
7
|
-
### Worker body
|
|
8
|
-
|
|
9
|
-
{{jobIntent}}
|
|
10
|
-
|
|
11
|
-
## Current state
|
|
12
|
-
|
|
13
|
-
This is the state you wrote at the end of the previous tick (or `null` if this is the first tick):
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{{jobStateJson}}
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
`cursor` is *your* enum — pick whatever labels map cleanly to your worker's phases. `data` is where you stash anything you need on the next tick (per-PR attempt counters, last-seen SHAs, etc). `done: true` is how you signal that the worker is permanently over — for evergreen workers this should always remain `false`.
|
|
20
|
-
|
|
21
|
-
## What to do on this tick
|
|
22
|
-
|
|
23
|
-
`forceRun = {{args.force}}` — set to `true` when an operator clicked "Run now" on the dashboard. When `forceRun` is `true`, ignore the worker body's `**Cadence guard.**` paragraph (or any equivalent "skip if last run was within X" rule) and execute the work as if the guard had passed. All other body rules — allowed commands, restrictions, state schema — still apply. Force only overrides cadence.
|
|
24
|
-
|
|
25
|
-
1. **Check `done`.** If the prior state has `done: true`, emit the same state back unchanged and exit without any action.
|
|
26
|
-
2. **Re-read the worker body.** It may have changed since the last tick.
|
|
27
|
-
3. **Execute exactly the work the body's `## Worker` section describes**, subject to its `## Allowed Commands` and `## Restrictions`. Use the `## State` section to interpret and update `data`.
|
|
28
|
-
4. **Optionally post a short narration** wherever the worker tells you to (typically a PR comment alongside the action). Keep it terse.
|
|
29
|
-
5. **Emit the new state** at the very end of your response using the fenced block below. Do not include `version` or `rev` — the postflight script manages those.
|
|
30
|
-
|
|
31
|
-
## Output contract (MANDATORY, exactly once, at the end)
|
|
32
|
-
|
|
33
|
-
End your response with a single fenced block using the `kody-job-next-state` language tag:
|
|
34
|
-
|
|
35
|
-
````
|
|
36
|
-
```kody-job-next-state
|
|
37
|
-
{
|
|
38
|
-
"cursor": "<your-next-cursor>",
|
|
39
|
-
"data": { ... },
|
|
40
|
-
"done": <true|false>
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
````
|
|
44
|
-
|
|
45
|
-
If you fail to emit this block, or the JSON is invalid, the tick fails and the gist state is NOT updated. On the next wake you'll see the same prior state and can retry.
|
|
46
|
-
|
|
47
|
-
## Rules
|
|
48
|
-
|
|
49
|
-
- Never edit, create, or delete files in the working tree.
|
|
50
|
-
- Never commit or push via `git`. The only permitted commit path is `gh api -X PUT` against the report file (see exception below).
|
|
51
|
-
- Only shell calls allowed: `gh`. Everything must go through it.
|
|
52
|
-
- Keep each tick focused: do one action per candidate per wake. The cron will call you again.
|
|
53
|
-
- If state says you're waiting on something, just check and re-emit — don't spawn a duplicate.
|
|
54
|
-
- Honour the worker body's `## Restrictions` over any inferred shortcut.
|
|
55
|
-
|
|
56
|
-
### Single permitted write: the worker's report file
|
|
57
|
-
|
|
58
|
-
A worker MAY (optionally — only if its body asks for it) write a single
|
|
59
|
-
markdown report file at the canonical path:
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
.kody/reports/{{jobSlug}}.md
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Only that exact path. Only via `gh api -X PUT /repos/<owner>/<repo>/contents/.kody/reports/{{jobSlug}}.md` (with base64 content + `sha` of the existing file when updating). All other writes — code files, other report paths, other slugs — remain forbidden. The dashboard's `/reports` page surfaces these files automatically; this is the canonical channel for a worker's diagnostic output when an issue comment isn't expressive enough.
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "worker-tick-scripted",
|
|
3
|
-
"role": "utility",
|
|
4
|
-
"describe": "Deterministic worker tick: runs the slug's `tickScript:` (declared in worker frontmatter), parses next-state from its stdout, persists. No agent. Parallel to job-tick-scripted but reads .kody/workers/.",
|
|
5
|
-
"kind": "oneshot",
|
|
6
|
-
"inputs": [
|
|
7
|
-
{
|
|
8
|
-
"name": "job",
|
|
9
|
-
"flag": "--job",
|
|
10
|
-
"type": "string",
|
|
11
|
-
"required": true,
|
|
12
|
-
"describe": "Worker slug — basename (without .md) of the file under .kody/workers/."
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"name": "force",
|
|
16
|
-
"flag": "--force",
|
|
17
|
-
"type": "bool",
|
|
18
|
-
"describe": "Accepted for parity with `worker-tick`. Scripted ticks have no agent cadence guard to bypass — the dispatcher already gated on frontmatter `every:`. Forwarded to the script via env if it cares."
|
|
19
|
-
}
|
|
20
|
-
],
|
|
21
|
-
"claudeCode": {
|
|
22
|
-
"model": "inherit",
|
|
23
|
-
"permissionMode": "default",
|
|
24
|
-
"maxTurns": null,
|
|
25
|
-
"maxThinkingTokens": null,
|
|
26
|
-
"systemPromptAppend": null,
|
|
27
|
-
"tools": [],
|
|
28
|
-
"hooks": [],
|
|
29
|
-
"skills": [],
|
|
30
|
-
"commands": [],
|
|
31
|
-
"subagents": [],
|
|
32
|
-
"plugins": [],
|
|
33
|
-
"mcpServers": []
|
|
34
|
-
},
|
|
35
|
-
"cliTools": [
|
|
36
|
-
{
|
|
37
|
-
"name": "gh",
|
|
38
|
-
"install": {
|
|
39
|
-
"required": true,
|
|
40
|
-
"checkCommand": "command -v gh"
|
|
41
|
-
},
|
|
42
|
-
"verify": "gh auth status",
|
|
43
|
-
"usage": "Available to the tickScript via PATH; this executable shells out to `bash <tickScript>`. Scripts use `gh pr list`, `gh pr comment`, etc.",
|
|
44
|
-
"allowedUses": ["pr", "api", "issue"]
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"inputArtifacts": [],
|
|
48
|
-
"outputArtifacts": [],
|
|
49
|
-
"scripts": {
|
|
50
|
-
"preflight": [
|
|
51
|
-
{
|
|
52
|
-
"script": "runTickScript",
|
|
53
|
-
"with": {
|
|
54
|
-
"jobsDir": ".kody/workers",
|
|
55
|
-
"slugArg": "job",
|
|
56
|
-
"fenceLabel": "kody-job-next-state"
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
],
|
|
60
|
-
"postflight": [
|
|
61
|
-
{
|
|
62
|
-
"script": "writeJobStateFile",
|
|
63
|
-
"with": {
|
|
64
|
-
"jobsDir": ".kody/workers"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
]
|
|
68
|
-
}
|
|
69
|
-
}
|