@kody-ade/kody-engine 0.4.150 → 0.4.152

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.150",
1064
+ version: "0.4.152",
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 });
1861
+ }
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 });
1852
1889
  }
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") {
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,
@@ -7850,6 +7898,8 @@ var FAILED = {
7850
7898
  description: "kody: flow failed"
7851
7899
  };
7852
7900
  var finalizeTerminal = async (ctx) => {
7901
+ const flow = ctx.data.taskState?.flow;
7902
+ if (flow?.issueNumber) return;
7853
7903
  const target = ctx.data.commentTargetType ?? "issue";
7854
7904
  const issueNumber = ctx.args.issue;
7855
7905
  const targetNumber = ctx.data.commentTargetNumber ?? issueNumber;
@@ -12047,6 +12097,11 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
12047
12097
  // src/scripts/syncFlow.ts
12048
12098
  import { execFileSync as execFileSync25 } from "child_process";
12049
12099
  init_issue();
12100
+ var DONE2 = {
12101
+ label: "kody:done",
12102
+ color: "0e8a16",
12103
+ description: "kody: PR ready for human review/merge"
12104
+ };
12050
12105
  var syncFlow = async (ctx, _profile, args) => {
12051
12106
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
12052
12107
  const prNumber = ctx.args.pr;
@@ -12084,6 +12139,7 @@ var syncFlow = async (ctx, _profile, args) => {
12084
12139
  if (announceOnSuccess) {
12085
12140
  ctx.output.exitCode = 0;
12086
12141
  ctx.output.reason = `already up to date with origin/${baseBranch}`;
12142
+ restoreDone(prNumber, ctx.cwd);
12087
12143
  }
12088
12144
  return;
12089
12145
  }
@@ -12098,8 +12154,15 @@ var syncFlow = async (ctx, _profile, args) => {
12098
12154
  if (announceOnSuccess) {
12099
12155
  ctx.output.exitCode = 0;
12100
12156
  ctx.output.reason = `merged origin/${baseBranch} into ${ctx.data.branch}`;
12157
+ restoreDone(prNumber, ctx.cwd);
12101
12158
  }
12102
12159
  };
12160
+ function restoreDone(prNumber, cwd) {
12161
+ try {
12162
+ setKodyLabel(prNumber, DONE2, cwd);
12163
+ } catch {
12164
+ }
12165
+ }
12103
12166
  function bail2(ctx, prNumber, reason) {
12104
12167
  ctx.skipAgent = true;
12105
12168
  ctx.output.exitCode = 1;
@@ -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": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.4.150",
3
+ "version": "0.4.152",
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",