@kody-ade/kody-engine 0.4.149 → 0.4.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js
CHANGED
|
@@ -1061,7 +1061,7 @@ var init_loadPriorArt = __esm({
|
|
|
1061
1061
|
// package.json
|
|
1062
1062
|
var package_default = {
|
|
1063
1063
|
name: "@kody-ade/kody-engine",
|
|
1064
|
-
version: "0.4.
|
|
1064
|
+
version: "0.4.151",
|
|
1065
1065
|
description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
|
|
1066
1066
|
license: "MIT",
|
|
1067
1067
|
type: "module",
|
|
@@ -1662,11 +1662,27 @@ function resolveTurnTimeoutMs(opts) {
|
|
|
1662
1662
|
if (Number.isFinite(envSec) && envSec <= 0) return 0;
|
|
1663
1663
|
return DEFAULT_TURN_TIMEOUT_MS;
|
|
1664
1664
|
}
|
|
1665
|
+
var MAX_CONNECTION_RETRIES = 2;
|
|
1666
|
+
var CONNECTION_RETRY_BASE_MS = 2e3;
|
|
1667
|
+
function isTransientConnectionError(msg) {
|
|
1668
|
+
if (!msg) return false;
|
|
1669
|
+
return /ConnectionRefused|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EHOSTUNREACH|ENOTFOUND|socket hang up|Unable to connect to API|fetch failed/i.test(
|
|
1670
|
+
msg
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
var MUTATING_FILE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
1674
|
+
var BASH_WRITE_VERB = /\b(git\s+(commit|push|merge|rebase|tag|reset|cherry-pick)|gh\s+(pr|issue|release)\s+(create|comment|edit|close|merge|review|reopen)|gh\s+api\b[^|&]*-X\s*(POST|PUT|PATCH|DELETE)|npm\s+publish)\b/i;
|
|
1675
|
+
function toolMayMutate(name, input) {
|
|
1676
|
+
if (!name) return false;
|
|
1677
|
+
if (MUTATING_FILE_TOOLS.has(name)) return true;
|
|
1678
|
+
if (name.startsWith("mcp__kody-submit__")) return true;
|
|
1679
|
+
if (name === "Bash") return BASH_WRITE_VERB.test(String(input?.command ?? ""));
|
|
1680
|
+
return false;
|
|
1681
|
+
}
|
|
1665
1682
|
async function runAgent(opts) {
|
|
1666
1683
|
const ndjsonDir = opts.ndjsonDir ?? path7.join(opts.cwd, ".kody");
|
|
1667
1684
|
fs7.mkdirSync(ndjsonDir, { recursive: true });
|
|
1668
1685
|
const ndjsonPath = path7.join(ndjsonDir, "last-run.jsonl");
|
|
1669
|
-
const fullLog = fs7.createWriteStream(ndjsonPath, { flags: "w" });
|
|
1670
1686
|
const env = {
|
|
1671
1687
|
...process.env,
|
|
1672
1688
|
SKIP_HOOKS: "1",
|
|
@@ -1685,227 +1701,259 @@ async function runAgent(opts) {
|
|
|
1685
1701
|
env.ANTHROPIC_BASE_URL = opts.litellmUrl;
|
|
1686
1702
|
env.ANTHROPIC_API_KEY = getAnthropicApiKeyOrDummy();
|
|
1687
1703
|
}
|
|
1688
|
-
const
|
|
1704
|
+
const startedAt = Date.now();
|
|
1705
|
+
const turnTimeoutMs = resolveTurnTimeoutMs(opts);
|
|
1689
1706
|
let outcome = "failed";
|
|
1690
1707
|
let outcomeKind = "generic_failed";
|
|
1691
1708
|
let errorMessage;
|
|
1692
|
-
|
|
1709
|
+
let tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
|
|
1693
1710
|
let messageCount = 0;
|
|
1694
|
-
|
|
1695
|
-
const turnTimeoutMs = resolveTurnTimeoutMs(opts);
|
|
1696
|
-
let ndjsonWriteFailed = false;
|
|
1697
|
-
let ndjsonWriteError;
|
|
1711
|
+
let finalText = "";
|
|
1698
1712
|
let getSubmitted;
|
|
1699
|
-
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
if (s.args) cfg.args = s.args;
|
|
1714
|
-
if (s.env) cfg.env = s.env;
|
|
1715
|
-
mcpEntries.push([s.name, cfg]);
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
if (opts.enableVerifyTool && opts.verifyConfig) {
|
|
1719
|
-
const { buildVerifyMcpServer: buildVerifyMcpServer2 } = await Promise.resolve().then(() => (init_verifyMcp(), verifyMcp_exports));
|
|
1720
|
-
const verifyServer = buildVerifyMcpServer2({
|
|
1721
|
-
config: opts.verifyConfig,
|
|
1713
|
+
for (let attempt = 0; ; attempt++) {
|
|
1714
|
+
const fullLog = fs7.createWriteStream(ndjsonPath, { flags: "w" });
|
|
1715
|
+
const resultTexts = [];
|
|
1716
|
+
outcome = "failed";
|
|
1717
|
+
outcomeKind = "generic_failed";
|
|
1718
|
+
errorMessage = void 0;
|
|
1719
|
+
tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
|
|
1720
|
+
messageCount = 0;
|
|
1721
|
+
let ndjsonWriteFailed = false;
|
|
1722
|
+
let ndjsonWriteError;
|
|
1723
|
+
let sawMutatingTool = false;
|
|
1724
|
+
try {
|
|
1725
|
+
const queryOptions = {
|
|
1726
|
+
model: opts.model.model,
|
|
1722
1727
|
cwd: opts.cwd,
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
if (opts.enableSubmitTool) {
|
|
1729
|
-
const { buildSubmitMcpServer: buildSubmitMcpServer2 } = await Promise.resolve().then(() => (init_submitMcp(), submitMcp_exports));
|
|
1730
|
-
const submitHandle = buildSubmitMcpServer2();
|
|
1731
|
-
getSubmitted = submitHandle.getSubmitted;
|
|
1732
|
-
mcpEntries.push(["kody-submit", submitHandle.server]);
|
|
1733
|
-
}
|
|
1734
|
-
if (opts.enableFetchRepoTool && opts.reposRoot) {
|
|
1735
|
-
const { buildFetchRepoMcpServer: buildFetchRepoMcpServer2 } = await Promise.resolve().then(() => (init_fetchRepoMcp(), fetchRepoMcp_exports));
|
|
1736
|
-
const fetchServer = buildFetchRepoMcpServer2({
|
|
1737
|
-
reposRoot: opts.reposRoot,
|
|
1738
|
-
repoToken: opts.repoToken
|
|
1739
|
-
});
|
|
1740
|
-
mcpEntries.push(["kody-fetch-repo", fetchServer]);
|
|
1741
|
-
queryOptions.allowedTools.push("mcp__kody-fetch-repo__fetch_repo");
|
|
1742
|
-
queryOptions.additionalDirectories = [opts.reposRoot];
|
|
1743
|
-
}
|
|
1744
|
-
if (mcpEntries.length > 0) {
|
|
1745
|
-
queryOptions.mcpServers = Object.fromEntries(mcpEntries);
|
|
1746
|
-
}
|
|
1747
|
-
if (opts.pluginPaths && opts.pluginPaths.length > 0) {
|
|
1748
|
-
queryOptions.plugins = opts.pluginPaths.map((p) => ({ type: "local", path: p }));
|
|
1749
|
-
}
|
|
1750
|
-
if (opts.agents && Object.keys(opts.agents).length > 0) {
|
|
1751
|
-
queryOptions.agents = opts.agents;
|
|
1752
|
-
}
|
|
1753
|
-
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
1754
|
-
queryOptions.maxTurns = opts.maxTurns;
|
|
1755
|
-
}
|
|
1756
|
-
if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
|
|
1757
|
-
queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
|
|
1758
|
-
}
|
|
1759
|
-
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
1760
|
-
const systemPrompt = {
|
|
1761
|
-
type: "preset",
|
|
1762
|
-
preset: "claude_code",
|
|
1763
|
-
append: opts.systemPromptAppend
|
|
1728
|
+
// Fresh array (never mutate the shared DEFAULT_ALLOWED_TOOLS const) so
|
|
1729
|
+
// opt-in tools like fetch_repo can be appended below.
|
|
1730
|
+
allowedTools: [...opts.allowedToolsOverride ?? DEFAULT_ALLOWED_TOOLS],
|
|
1731
|
+
permissionMode: opts.permissionModeOverride ?? "acceptEdits",
|
|
1732
|
+
env
|
|
1764
1733
|
};
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
// biome-ignore lint/suspicious/noExplicitAny: SDK options type is narrow; mcpServers is runtime-passthrough.
|
|
1782
|
-
options: queryOptions
|
|
1783
|
-
});
|
|
1784
|
-
const iterator = typeof result[Symbol.asyncIterator] === "function" ? result[Symbol.asyncIterator]() : result;
|
|
1785
|
-
while (true) {
|
|
1786
|
-
const nextPromise = iterator.next();
|
|
1787
|
-
let timedOut = false;
|
|
1788
|
-
let timer;
|
|
1789
|
-
let next;
|
|
1790
|
-
if (turnTimeoutMs > 0) {
|
|
1791
|
-
const timeoutPromise = new Promise((resolve6) => {
|
|
1792
|
-
timer = setTimeout(() => {
|
|
1793
|
-
timedOut = true;
|
|
1794
|
-
resolve6({ done: true, value: void 0 });
|
|
1795
|
-
}, turnTimeoutMs);
|
|
1734
|
+
const mcpEntries = [];
|
|
1735
|
+
if (opts.mcpServers && opts.mcpServers.length > 0) {
|
|
1736
|
+
for (const s of opts.mcpServers) {
|
|
1737
|
+
const cfg = { command: s.command };
|
|
1738
|
+
if (s.args) cfg.args = s.args;
|
|
1739
|
+
if (s.env) cfg.env = s.env;
|
|
1740
|
+
mcpEntries.push([s.name, cfg]);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (opts.enableVerifyTool && opts.verifyConfig) {
|
|
1744
|
+
const { buildVerifyMcpServer: buildVerifyMcpServer2 } = await Promise.resolve().then(() => (init_verifyMcp(), verifyMcp_exports));
|
|
1745
|
+
const verifyServer = buildVerifyMcpServer2({
|
|
1746
|
+
config: opts.verifyConfig,
|
|
1747
|
+
cwd: opts.cwd,
|
|
1748
|
+
executable: opts.executableName ?? "agent",
|
|
1749
|
+
maxAttempts: typeof opts.verifyToolMaxAttempts === "number" && opts.verifyToolMaxAttempts > 0 ? opts.verifyToolMaxAttempts : void 0
|
|
1796
1750
|
});
|
|
1797
|
-
|
|
1798
|
-
if (timer) clearTimeout(timer);
|
|
1799
|
-
} else {
|
|
1800
|
-
next = await nextPromise;
|
|
1751
|
+
mcpEntries.push(["kody-verify", verifyServer]);
|
|
1801
1752
|
}
|
|
1802
|
-
if (
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1753
|
+
if (opts.enableSubmitTool) {
|
|
1754
|
+
const { buildSubmitMcpServer: buildSubmitMcpServer2 } = await Promise.resolve().then(() => (init_submitMcp(), submitMcp_exports));
|
|
1755
|
+
const submitHandle = buildSubmitMcpServer2();
|
|
1756
|
+
getSubmitted = submitHandle.getSubmitted;
|
|
1757
|
+
mcpEntries.push(["kody-submit", submitHandle.server]);
|
|
1758
|
+
}
|
|
1759
|
+
if (opts.enableFetchRepoTool && opts.reposRoot) {
|
|
1760
|
+
const { buildFetchRepoMcpServer: buildFetchRepoMcpServer2 } = await Promise.resolve().then(() => (init_fetchRepoMcp(), fetchRepoMcp_exports));
|
|
1761
|
+
const fetchServer = buildFetchRepoMcpServer2({
|
|
1762
|
+
reposRoot: opts.reposRoot,
|
|
1763
|
+
repoToken: opts.repoToken
|
|
1764
|
+
});
|
|
1765
|
+
mcpEntries.push(["kody-fetch-repo", fetchServer]);
|
|
1766
|
+
queryOptions.allowedTools.push("mcp__kody-fetch-repo__fetch_repo");
|
|
1767
|
+
queryOptions.additionalDirectories = [opts.reposRoot];
|
|
1768
|
+
}
|
|
1769
|
+
if (mcpEntries.length > 0) {
|
|
1770
|
+
queryOptions.mcpServers = Object.fromEntries(mcpEntries);
|
|
1771
|
+
}
|
|
1772
|
+
if (opts.pluginPaths && opts.pluginPaths.length > 0) {
|
|
1773
|
+
queryOptions.plugins = opts.pluginPaths.map((p) => ({ type: "local", path: p }));
|
|
1774
|
+
}
|
|
1775
|
+
if (opts.agents && Object.keys(opts.agents).length > 0) {
|
|
1776
|
+
queryOptions.agents = opts.agents;
|
|
1777
|
+
}
|
|
1778
|
+
if (typeof opts.maxTurns === "number" && opts.maxTurns > 0) {
|
|
1779
|
+
queryOptions.maxTurns = opts.maxTurns;
|
|
1780
|
+
}
|
|
1781
|
+
if (typeof opts.maxThinkingTokens === "number" && opts.maxThinkingTokens > 0) {
|
|
1782
|
+
queryOptions.maxThinkingTokens = opts.maxThinkingTokens;
|
|
1783
|
+
}
|
|
1784
|
+
if (typeof opts.systemPromptAppend === "string" && opts.systemPromptAppend.length > 0) {
|
|
1785
|
+
const systemPrompt = {
|
|
1786
|
+
type: "preset",
|
|
1787
|
+
preset: "claude_code",
|
|
1788
|
+
append: opts.systemPromptAppend
|
|
1789
|
+
};
|
|
1790
|
+
if (opts.cacheable) systemPrompt.excludeDynamicSections = true;
|
|
1791
|
+
queryOptions.systemPrompt = systemPrompt;
|
|
1792
|
+
} else if (opts.cacheable) {
|
|
1793
|
+
queryOptions.systemPrompt = {
|
|
1794
|
+
type: "preset",
|
|
1795
|
+
preset: "claude_code",
|
|
1796
|
+
excludeDynamicSections: true
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
queryOptions.settingSources = opts.settingSources ?? ["project", "local"];
|
|
1800
|
+
const stableBinary = ensureStableClaudeBinary();
|
|
1801
|
+
if (stableBinary) {
|
|
1802
|
+
queryOptions.pathToClaudeCodeExecutable = stableBinary;
|
|
1803
|
+
}
|
|
1804
|
+
const result = query({
|
|
1805
|
+
prompt: opts.prompt,
|
|
1806
|
+
// biome-ignore lint/suspicious/noExplicitAny: SDK options type is narrow; mcpServers is runtime-passthrough.
|
|
1807
|
+
options: queryOptions
|
|
1808
|
+
});
|
|
1809
|
+
const iterator = typeof result[Symbol.asyncIterator] === "function" ? result[Symbol.asyncIterator]() : result;
|
|
1810
|
+
while (true) {
|
|
1811
|
+
const nextPromise = iterator.next();
|
|
1812
|
+
let timedOut = false;
|
|
1813
|
+
let timer;
|
|
1814
|
+
let next;
|
|
1815
|
+
if (turnTimeoutMs > 0) {
|
|
1816
|
+
const timeoutPromise = new Promise((resolve6) => {
|
|
1817
|
+
timer = setTimeout(() => {
|
|
1818
|
+
timedOut = true;
|
|
1819
|
+
resolve6({ done: true, value: void 0 });
|
|
1820
|
+
}, turnTimeoutMs);
|
|
1821
|
+
});
|
|
1822
|
+
next = await Promise.race([nextPromise, timeoutPromise]);
|
|
1823
|
+
if (timer) clearTimeout(timer);
|
|
1824
|
+
} else {
|
|
1825
|
+
next = await nextPromise;
|
|
1826
|
+
}
|
|
1827
|
+
if (timedOut) {
|
|
1828
|
+
outcome = "failed";
|
|
1829
|
+
outcomeKind = "stalled";
|
|
1830
|
+
errorMessage = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
|
|
1831
|
+
if (typeof iterator.return === "function") {
|
|
1832
|
+
try {
|
|
1833
|
+
await iterator.return(void 0);
|
|
1834
|
+
} catch {
|
|
1835
|
+
}
|
|
1810
1836
|
}
|
|
1837
|
+
break;
|
|
1811
1838
|
}
|
|
1812
|
-
break;
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
try {
|
|
1818
|
-
fullLog.write(`${JSON.stringify(msg)}
|
|
1839
|
+
if (next.done) break;
|
|
1840
|
+
const msg = next.value;
|
|
1841
|
+
messageCount++;
|
|
1842
|
+
try {
|
|
1843
|
+
fullLog.write(`${JSON.stringify(msg)}
|
|
1819
1844
|
`);
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1845
|
+
} catch (e) {
|
|
1846
|
+
ndjsonWriteFailed = true;
|
|
1847
|
+
ndjsonWriteError = e instanceof Error ? e.message : String(e);
|
|
1848
|
+
}
|
|
1849
|
+
const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
|
|
1850
|
+
if (line) process.stdout.write(`${line}
|
|
1826
1851
|
`);
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
}
|
|
1837
|
-
} else if (block.type === "tool_use") {
|
|
1838
|
-
const b = block;
|
|
1839
|
-
await opts.onProgress({
|
|
1840
|
-
kind: "tool_use",
|
|
1841
|
-
name: b.name ?? "tool",
|
|
1842
|
-
input: b.input,
|
|
1843
|
-
id: b.id
|
|
1844
|
-
});
|
|
1845
|
-
} else if (block.type === "tool_result") {
|
|
1846
|
-
const b = block;
|
|
1847
|
-
const content = typeof b.content === "string" ? b.content : (() => {
|
|
1848
|
-
try {
|
|
1849
|
-
return JSON.stringify(b.content);
|
|
1850
|
-
} catch {
|
|
1851
|
-
return "";
|
|
1852
|
+
const m = msg;
|
|
1853
|
+
if (opts.onProgress) {
|
|
1854
|
+
const blocks = m.message?.content ?? [];
|
|
1855
|
+
for (const block of blocks) {
|
|
1856
|
+
try {
|
|
1857
|
+
if (block.type === "thinking") {
|
|
1858
|
+
const t = block.thinking;
|
|
1859
|
+
if (typeof t === "string" && t.length > 0) {
|
|
1860
|
+
await opts.onProgress({ kind: "thinking", thinking: t });
|
|
1852
1861
|
}
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1862
|
+
} else if (block.type === "tool_use") {
|
|
1863
|
+
const b = block;
|
|
1864
|
+
await opts.onProgress({
|
|
1865
|
+
kind: "tool_use",
|
|
1866
|
+
name: b.name ?? "tool",
|
|
1867
|
+
input: b.input,
|
|
1868
|
+
id: b.id
|
|
1869
|
+
});
|
|
1870
|
+
} else if (block.type === "tool_result") {
|
|
1871
|
+
const b = block;
|
|
1872
|
+
const content = typeof b.content === "string" ? b.content : (() => {
|
|
1873
|
+
try {
|
|
1874
|
+
return JSON.stringify(b.content);
|
|
1875
|
+
} catch {
|
|
1876
|
+
return "";
|
|
1877
|
+
}
|
|
1878
|
+
})();
|
|
1879
|
+
await opts.onProgress({
|
|
1880
|
+
kind: "tool_result",
|
|
1881
|
+
toolUseId: b.tool_use_id,
|
|
1882
|
+
content,
|
|
1883
|
+
isError: b.is_error
|
|
1884
|
+
});
|
|
1885
|
+
} else if (block.type === "text") {
|
|
1886
|
+
const b = block;
|
|
1887
|
+
if (typeof b.text === "string" && b.text.length > 0) {
|
|
1888
|
+
await opts.onProgress({ kind: "text", text: b.text });
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
} catch {
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
const usage = m.usage;
|
|
1896
|
+
if (usage && typeof usage === "object") {
|
|
1897
|
+
const i = Number(usage.input_tokens ?? 0);
|
|
1898
|
+
const o = Number(usage.output_tokens ?? 0);
|
|
1899
|
+
const cr = Number(usage.cache_read_input_tokens ?? 0);
|
|
1900
|
+
const cc = Number(usage.cache_creation_input_tokens ?? 0);
|
|
1901
|
+
if (Number.isFinite(i)) tokens.input += i;
|
|
1902
|
+
if (Number.isFinite(o)) tokens.output += o;
|
|
1903
|
+
if (Number.isFinite(cr)) tokens.cacheRead += cr;
|
|
1904
|
+
if (Number.isFinite(cc)) tokens.cacheCreate += cc;
|
|
1905
|
+
}
|
|
1906
|
+
if (!sawMutatingTool) {
|
|
1907
|
+
const blocks = m.message?.content ?? [];
|
|
1908
|
+
for (const block of blocks) {
|
|
1909
|
+
if (block.type === "tool_use") {
|
|
1861
1910
|
const b = block;
|
|
1862
|
-
if (
|
|
1863
|
-
|
|
1911
|
+
if (toolMayMutate(b.name, b.input)) {
|
|
1912
|
+
sawMutatingTool = true;
|
|
1913
|
+
break;
|
|
1864
1914
|
}
|
|
1865
1915
|
}
|
|
1866
|
-
} catch {
|
|
1867
1916
|
}
|
|
1868
1917
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
}
|
|
1881
|
-
if (m.type === "result") {
|
|
1882
|
-
if (m.subtype === "success") {
|
|
1883
|
-
outcome = "completed";
|
|
1884
|
-
outcomeKind = "ok";
|
|
1885
|
-
const text = (typeof m.result === "string" ? m.result : "").trim();
|
|
1886
|
-
if (text) resultTexts.push(text);
|
|
1887
|
-
} else {
|
|
1888
|
-
outcome = "failed";
|
|
1889
|
-
outcomeKind = classifySubtype(m.subtype);
|
|
1890
|
-
errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
|
|
1918
|
+
if (m.type === "result") {
|
|
1919
|
+
if (m.subtype === "success") {
|
|
1920
|
+
outcome = "completed";
|
|
1921
|
+
outcomeKind = "ok";
|
|
1922
|
+
const text = (typeof m.result === "string" ? m.result : "").trim();
|
|
1923
|
+
if (text) resultTexts.push(text);
|
|
1924
|
+
} else {
|
|
1925
|
+
outcome = "failed";
|
|
1926
|
+
outcomeKind = classifySubtype(m.subtype);
|
|
1927
|
+
errorMessage = `result subtype: ${m.subtype ?? "unknown"}`;
|
|
1928
|
+
}
|
|
1891
1929
|
}
|
|
1892
1930
|
}
|
|
1931
|
+
} catch (e) {
|
|
1932
|
+
outcome = "failed";
|
|
1933
|
+
outcomeKind = "model_error";
|
|
1934
|
+
errorMessage = e instanceof Error ? e.message : String(e);
|
|
1935
|
+
} finally {
|
|
1936
|
+
try {
|
|
1937
|
+
fullLog.end();
|
|
1938
|
+
} catch {
|
|
1939
|
+
}
|
|
1893
1940
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
try {
|
|
1900
|
-
fullLog.end();
|
|
1901
|
-
} catch {
|
|
1941
|
+
if (ndjsonWriteFailed) {
|
|
1942
|
+
process.stderr.write(
|
|
1943
|
+
`[kody agent] NDJSON write failed (post-mortem may be incomplete): ${ndjsonWriteError ?? "unknown error"}
|
|
1944
|
+
`
|
|
1945
|
+
);
|
|
1902
1946
|
}
|
|
1947
|
+
finalText = resultTexts.join("\n\n---\n\n");
|
|
1948
|
+
const shouldRetry = outcome === "failed" && attempt < MAX_CONNECTION_RETRIES && !sawMutatingTool && isTransientConnectionError(errorMessage);
|
|
1949
|
+
if (!shouldRetry) break;
|
|
1950
|
+
const delayMs = CONNECTION_RETRY_BASE_MS * 2 ** attempt;
|
|
1951
|
+
process.stderr.write(
|
|
1952
|
+
`[kody agent] transient connection error (attempt ${attempt + 1}/${MAX_CONNECTION_RETRIES + 1}); retrying in ${Math.round(delayMs / 1e3)}s: ${errorMessage}
|
|
1953
|
+
`
|
|
1954
|
+
);
|
|
1955
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
1903
1956
|
}
|
|
1904
|
-
if (ndjsonWriteFailed) {
|
|
1905
|
-
process.stderr.write(`[kody agent] NDJSON write failed (post-mortem may be incomplete): ${ndjsonWriteError ?? "unknown error"}
|
|
1906
|
-
`);
|
|
1907
|
-
}
|
|
1908
|
-
const finalText = resultTexts.join("\n\n---\n\n");
|
|
1909
1957
|
const submittedState = getSubmitted?.();
|
|
1910
1958
|
return {
|
|
1911
1959
|
outcome,
|
|
@@ -2703,6 +2751,76 @@ import { execFileSync as execFileSync29 } from "child_process";
|
|
|
2703
2751
|
import * as fs41 from "fs";
|
|
2704
2752
|
import * as path37 from "path";
|
|
2705
2753
|
|
|
2754
|
+
// src/app-auth.ts
|
|
2755
|
+
import { createSign } from "crypto";
|
|
2756
|
+
var GH_API = process.env.GITHUB_API_URL || "https://api.github.com";
|
|
2757
|
+
function b64url(input) {
|
|
2758
|
+
return Buffer.from(input).toString("base64url");
|
|
2759
|
+
}
|
|
2760
|
+
function normalizePem(key) {
|
|
2761
|
+
const trimmed = key.trim();
|
|
2762
|
+
if (trimmed.includes("BEGIN")) return trimmed;
|
|
2763
|
+
try {
|
|
2764
|
+
const decoded = Buffer.from(trimmed, "base64").toString("utf8");
|
|
2765
|
+
if (decoded.includes("BEGIN")) return decoded;
|
|
2766
|
+
} catch {
|
|
2767
|
+
}
|
|
2768
|
+
return trimmed;
|
|
2769
|
+
}
|
|
2770
|
+
function buildAppJwt(appId, privateKeyPem) {
|
|
2771
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2772
|
+
const header = { alg: "RS256", typ: "JWT" };
|
|
2773
|
+
const payload = { iat: now - 60, exp: now + 9 * 60, iss: appId };
|
|
2774
|
+
const signingInput = `${b64url(JSON.stringify(header))}.${b64url(JSON.stringify(payload))}`;
|
|
2775
|
+
const signer = createSign("RSA-SHA256");
|
|
2776
|
+
signer.update(signingInput);
|
|
2777
|
+
signer.end();
|
|
2778
|
+
const signature = signer.sign(normalizePem(privateKeyPem)).toString("base64url");
|
|
2779
|
+
return `${signingInput}.${signature}`;
|
|
2780
|
+
}
|
|
2781
|
+
async function ghApp(jwt, apiPath, method = "GET") {
|
|
2782
|
+
const res = await fetch(`${GH_API}${apiPath}`, {
|
|
2783
|
+
method,
|
|
2784
|
+
headers: {
|
|
2785
|
+
Authorization: `Bearer ${jwt}`,
|
|
2786
|
+
Accept: "application/vnd.github+json",
|
|
2787
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
2788
|
+
"User-Agent": "kody-engine"
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2791
|
+
if (!res.ok) {
|
|
2792
|
+
const body = await res.text().catch(() => "");
|
|
2793
|
+
throw new Error(
|
|
2794
|
+
`GitHub App API ${method} ${apiPath} \u2192 ${res.status} ${res.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
return await res.json();
|
|
2798
|
+
}
|
|
2799
|
+
function readAppCreds(env = process.env) {
|
|
2800
|
+
const appId = env.KODY_APP_ID?.trim();
|
|
2801
|
+
const privateKey = env.KODY_APP_PRIVATE_KEY;
|
|
2802
|
+
if (!appId || !privateKey) return null;
|
|
2803
|
+
return {
|
|
2804
|
+
appId,
|
|
2805
|
+
privateKey,
|
|
2806
|
+
installationId: env.KODY_APP_INSTALLATION_ID?.trim() || void 0,
|
|
2807
|
+
repo: env.GITHUB_REPOSITORY?.trim() || void 0
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
async function mintAppInstallationToken(creds) {
|
|
2811
|
+
const jwt = buildAppJwt(creds.appId, creds.privateKey);
|
|
2812
|
+
let installationId = creds.installationId;
|
|
2813
|
+
if (!installationId) {
|
|
2814
|
+
if (!creds.repo) {
|
|
2815
|
+
throw new Error("cannot resolve App installation: no KODY_APP_INSTALLATION_ID and no GITHUB_REPOSITORY");
|
|
2816
|
+
}
|
|
2817
|
+
const inst = await ghApp(jwt, `/repos/${creds.repo}/installation`);
|
|
2818
|
+
installationId = String(inst.id);
|
|
2819
|
+
}
|
|
2820
|
+
const tok = await ghApp(jwt, `/app/installations/${installationId}/access_tokens`, "POST");
|
|
2821
|
+
return tok.token;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2706
2824
|
// src/dispatch.ts
|
|
2707
2825
|
import * as fs12 from "fs";
|
|
2708
2826
|
|
|
@@ -3029,9 +3147,6 @@ function coerceBare(spec, value) {
|
|
|
3029
3147
|
return value;
|
|
3030
3148
|
}
|
|
3031
3149
|
|
|
3032
|
-
// src/kody-cli.ts
|
|
3033
|
-
init_issue();
|
|
3034
|
-
|
|
3035
3150
|
// src/executor.ts
|
|
3036
3151
|
import { execFileSync as execFileSync28, spawn as spawn9 } from "child_process";
|
|
3037
3152
|
import * as fs40 from "fs";
|
|
@@ -7783,6 +7898,8 @@ var FAILED = {
|
|
|
7783
7898
|
description: "kody: flow failed"
|
|
7784
7899
|
};
|
|
7785
7900
|
var finalizeTerminal = async (ctx) => {
|
|
7901
|
+
const flow = ctx.data.taskState?.flow;
|
|
7902
|
+
if (flow?.issueNumber) return;
|
|
7786
7903
|
const target = ctx.data.commentTargetType ?? "issue";
|
|
7787
7904
|
const issueNumber = ctx.args.issue;
|
|
7788
7905
|
const targetNumber = ctx.data.commentTargetNumber ?? issueNumber;
|
|
@@ -13821,7 +13938,7 @@ function unpackAllSecrets(env = process.env) {
|
|
|
13821
13938
|
}
|
|
13822
13939
|
return count;
|
|
13823
13940
|
}
|
|
13824
|
-
function resolveAuthToken(env = process.env) {
|
|
13941
|
+
async function resolveAuthToken(env = process.env) {
|
|
13825
13942
|
const sources = [
|
|
13826
13943
|
["KODY_TOKEN", env.KODY_TOKEN],
|
|
13827
13944
|
["GH_TOKEN", env.GH_TOKEN],
|
|
@@ -13834,10 +13951,24 @@ function resolveAuthToken(env = process.env) {
|
|
|
13834
13951
|
if (token) {
|
|
13835
13952
|
process.stdout.write(`\u2192 kody: GH_TOKEN sourced from env.${picked[0]}
|
|
13836
13953
|
`);
|
|
13837
|
-
|
|
13838
|
-
|
|
13954
|
+
return token;
|
|
13955
|
+
}
|
|
13956
|
+
const creds = readAppCreds(env);
|
|
13957
|
+
if (creds) {
|
|
13958
|
+
try {
|
|
13959
|
+
const minted = await mintAppInstallationToken(creds);
|
|
13960
|
+
env.GH_TOKEN = minted;
|
|
13961
|
+
process.stdout.write("\u2192 kody: GH_TOKEN minted from GitHub App (KODY_APP_ID/KODY_APP_PRIVATE_KEY)\n");
|
|
13962
|
+
return minted;
|
|
13963
|
+
} catch (err) {
|
|
13964
|
+
process.stdout.write(`\u2192 kody: WARNING GitHub App token mint failed: ${err.message}
|
|
13965
|
+
`);
|
|
13966
|
+
}
|
|
13839
13967
|
}
|
|
13840
|
-
|
|
13968
|
+
process.stdout.write(
|
|
13969
|
+
"\u2192 kody: WARNING no auth token found (KODY_TOKEN/GH_TOKEN/GITHUB_TOKEN/GH_PAT/GitHub App all empty)\n"
|
|
13970
|
+
);
|
|
13971
|
+
return void 0;
|
|
13841
13972
|
}
|
|
13842
13973
|
function detectPackageManager2(cwd) {
|
|
13843
13974
|
if (fs41.existsSync(path37.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
@@ -13986,7 +14117,7 @@ async function runCi(argv) {
|
|
|
13986
14117
|
if (outcome.kind === "unrecognized") {
|
|
13987
14118
|
try {
|
|
13988
14119
|
unpackAllSecrets();
|
|
13989
|
-
resolveAuthToken();
|
|
14120
|
+
await resolveAuthToken();
|
|
13990
14121
|
} catch {
|
|
13991
14122
|
}
|
|
13992
14123
|
const tokenLabel = outcome.token ? `\`${outcome.token}\`` : "an empty subcommand";
|
|
@@ -14003,8 +14134,10 @@ async function runCi(argv) {
|
|
|
14003
14134
|
if (outcome.isPr) postPrReviewComment(outcome.target, body, cwd);
|
|
14004
14135
|
else postIssueComment(outcome.target, body, cwd);
|
|
14005
14136
|
} catch (err) {
|
|
14006
|
-
process.stderr.write(
|
|
14007
|
-
`)
|
|
14137
|
+
process.stderr.write(
|
|
14138
|
+
`[kody] dispatch: failed to post unrecognized-token feedback: ${err instanceof Error ? err.message : String(err)}
|
|
14139
|
+
`
|
|
14140
|
+
);
|
|
14008
14141
|
}
|
|
14009
14142
|
process.stdout.write(
|
|
14010
14143
|
`\u2192 kody: unrecognized subcommand "${outcome.token}" on #${outcome.target} \u2014 feedback comment attempt finished, exiting cleanly
|
|
@@ -14039,7 +14172,7 @@ ${CI_HELP}`);
|
|
|
14039
14172
|
const n = unpackAllSecrets();
|
|
14040
14173
|
if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
|
|
14041
14174
|
`);
|
|
14042
|
-
resolveAuthToken();
|
|
14175
|
+
await resolveAuthToken();
|
|
14043
14176
|
reactToTriggerComment(cwd);
|
|
14044
14177
|
const pm = args.packageManager ?? detectPackageManager2(cwd);
|
|
14045
14178
|
process.stdout.write(`\u2192 kody: package manager = ${pm}
|
|
@@ -14112,7 +14245,7 @@ async function runScheduledFanOut(cwd, args, opts) {
|
|
|
14112
14245
|
const n = unpackAllSecrets();
|
|
14113
14246
|
if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s) from ALL_SECRETS
|
|
14114
14247
|
`);
|
|
14115
|
-
resolveAuthToken();
|
|
14248
|
+
await resolveAuthToken();
|
|
14116
14249
|
const pm = args.packageManager ?? detectPackageManager2(cwd);
|
|
14117
14250
|
process.stdout.write(`\u2192 kody: package manager = ${pm}
|
|
14118
14251
|
`);
|
|
@@ -14285,7 +14418,7 @@ ${CHAT_HELP}`);
|
|
|
14285
14418
|
process.stdout.write(`\u2192 kody: unpacked ${unpackedSecrets} secret(s) from ALL_SECRETS
|
|
14286
14419
|
`);
|
|
14287
14420
|
}
|
|
14288
|
-
resolveAuthToken();
|
|
14421
|
+
await resolveAuthToken();
|
|
14289
14422
|
configureGitIdentity(cwd);
|
|
14290
14423
|
const config = tryLoadConfig(cwd);
|
|
14291
14424
|
const modelSpec = args.model ?? config?.agent.model ?? DEFAULT_MODEL;
|
|
@@ -27,14 +27,6 @@
|
|
|
27
27
|
"cliTools": [],
|
|
28
28
|
"scripts": {
|
|
29
29
|
"preflight": [
|
|
30
|
-
{
|
|
31
|
-
"script": "setLifecycleLabel",
|
|
32
|
-
"with": {
|
|
33
|
-
"label": "kody:syncing",
|
|
34
|
-
"color": "c5def5",
|
|
35
|
-
"description": "kody: syncing PR with base"
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
30
|
{
|
|
39
31
|
"script": "syncFlow",
|
|
40
32
|
"with": { "announceOnSuccess": true }
|
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.151",
|
|
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",
|