@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.149",
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 resultTexts = [];
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
- const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
1709
+ let tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
1693
1710
  let messageCount = 0;
1694
- const startedAt = Date.now();
1695
- const turnTimeoutMs = resolveTurnTimeoutMs(opts);
1696
- let ndjsonWriteFailed = false;
1697
- let ndjsonWriteError;
1711
+ let finalText = "";
1698
1712
  let getSubmitted;
1699
- try {
1700
- const queryOptions = {
1701
- model: opts.model.model,
1702
- cwd: opts.cwd,
1703
- // Fresh array (never mutate the shared DEFAULT_ALLOWED_TOOLS const) so
1704
- // opt-in tools like fetch_repo can be appended below.
1705
- allowedTools: [...opts.allowedToolsOverride ?? DEFAULT_ALLOWED_TOOLS],
1706
- permissionMode: opts.permissionModeOverride ?? "acceptEdits",
1707
- env
1708
- };
1709
- const mcpEntries = [];
1710
- if (opts.mcpServers && opts.mcpServers.length > 0) {
1711
- for (const s of opts.mcpServers) {
1712
- const cfg = { command: s.command };
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
- executable: opts.executableName ?? "agent",
1724
- maxAttempts: typeof opts.verifyToolMaxAttempts === "number" && opts.verifyToolMaxAttempts > 0 ? opts.verifyToolMaxAttempts : void 0
1725
- });
1726
- mcpEntries.push(["kody-verify", verifyServer]);
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
- if (opts.cacheable) systemPrompt.excludeDynamicSections = true;
1766
- queryOptions.systemPrompt = systemPrompt;
1767
- } else if (opts.cacheable) {
1768
- queryOptions.systemPrompt = {
1769
- type: "preset",
1770
- preset: "claude_code",
1771
- excludeDynamicSections: true
1772
- };
1773
- }
1774
- queryOptions.settingSources = opts.settingSources ?? ["project", "local"];
1775
- const stableBinary = ensureStableClaudeBinary();
1776
- if (stableBinary) {
1777
- queryOptions.pathToClaudeCodeExecutable = stableBinary;
1778
- }
1779
- const result = query({
1780
- prompt: opts.prompt,
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
- next = await Promise.race([nextPromise, timeoutPromise]);
1798
- if (timer) clearTimeout(timer);
1799
- } else {
1800
- next = await nextPromise;
1751
+ mcpEntries.push(["kody-verify", verifyServer]);
1801
1752
  }
1802
- if (timedOut) {
1803
- outcome = "failed";
1804
- outcomeKind = "stalled";
1805
- errorMessage = `agent stalled: no SDK message in ${Math.round(turnTimeoutMs / 1e3)}s`;
1806
- if (typeof iterator.return === "function") {
1807
- try {
1808
- await iterator.return(void 0);
1809
- } catch {
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
- if (next.done) break;
1815
- const msg = next.value;
1816
- messageCount++;
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
- } catch (e) {
1821
- ndjsonWriteFailed = true;
1822
- ndjsonWriteError = e instanceof Error ? e.message : String(e);
1823
- }
1824
- const line = renderEvent(msg, { verbose: opts.verbose, quiet: opts.quiet });
1825
- if (line) process.stdout.write(`${line}
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
- const m = msg;
1828
- if (opts.onProgress) {
1829
- const blocks = m.message?.content ?? [];
1830
- for (const block of blocks) {
1831
- try {
1832
- if (block.type === "thinking") {
1833
- const t = block.thinking;
1834
- if (typeof t === "string" && t.length > 0) {
1835
- await opts.onProgress({ kind: "thinking", thinking: t });
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
- await opts.onProgress({
1855
- kind: "tool_result",
1856
- toolUseId: b.tool_use_id,
1857
- content,
1858
- isError: b.is_error
1859
- });
1860
- } else if (block.type === "text") {
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 (typeof b.text === "string" && b.text.length > 0) {
1863
- await opts.onProgress({ kind: "text", text: b.text });
1911
+ if (toolMayMutate(b.name, b.input)) {
1912
+ sawMutatingTool = true;
1913
+ break;
1864
1914
  }
1865
1915
  }
1866
- } catch {
1867
1916
  }
1868
1917
  }
1869
- }
1870
- const usage = m.usage;
1871
- if (usage && typeof usage === "object") {
1872
- const i = Number(usage.input_tokens ?? 0);
1873
- const o = Number(usage.output_tokens ?? 0);
1874
- const cr = Number(usage.cache_read_input_tokens ?? 0);
1875
- const cc = Number(usage.cache_creation_input_tokens ?? 0);
1876
- if (Number.isFinite(i)) tokens.input += i;
1877
- if (Number.isFinite(o)) tokens.output += o;
1878
- if (Number.isFinite(cr)) tokens.cacheRead += cr;
1879
- if (Number.isFinite(cc)) tokens.cacheCreate += cc;
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
- } catch (e) {
1895
- outcome = "failed";
1896
- outcomeKind = "model_error";
1897
- errorMessage = e instanceof Error ? e.message : String(e);
1898
- } finally {
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
- } else {
13838
- process.stdout.write("\u2192 kody: WARNING no auth token found (KODY_TOKEN/GH_TOKEN/GITHUB_TOKEN/GH_PAT all empty)\n");
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
- return token;
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(`[kody] dispatch: failed to post unrecognized-token feedback: ${err instanceof Error ? err.message : String(err)}
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;
@@ -70,7 +70,8 @@
70
70
  "color": "e99695",
71
71
  "description": "kody: applying review feedback"
72
72
  },
73
- "context": "task"
73
+ "context": "task",
74
+ "finalize": true
74
75
  },
75
76
  "scripts": {
76
77
  "preflight": [
@@ -52,7 +52,8 @@
52
52
  "description": "kody: fixing CI failures"
53
53
  },
54
54
  "context": "ci-fix",
55
- "advance": false
55
+ "advance": false,
56
+ "finalize": true
56
57
  },
57
58
  "scripts": {
58
59
  "preflight": [
@@ -54,7 +54,8 @@
54
54
  "context": "task",
55
55
  "contextExtras": ["resolveArtifacts"],
56
56
  "sync": false,
57
- "mirrorState": true
57
+ "mirrorState": true,
58
+ "finalize": true
58
59
  },
59
60
  "scripts": {
60
61
  "preflight": [
@@ -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.149",
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",