@posthog/agent 2.1.125 → 2.1.137

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.
Files changed (33) hide show
  1. package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +14 -28
  2. package/dist/adapters/claude/conversion/tool-use-to-acp.js +116 -164
  3. package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
  4. package/dist/adapters/claude/permissions/permission-options.js +33 -0
  5. package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
  6. package/dist/adapters/claude/tools.js +21 -11
  7. package/dist/adapters/claude/tools.js.map +1 -1
  8. package/dist/agent.js +1251 -640
  9. package/dist/agent.js.map +1 -1
  10. package/dist/posthog-api.js +2 -2
  11. package/dist/posthog-api.js.map +1 -1
  12. package/dist/server/agent-server.js +1295 -684
  13. package/dist/server/agent-server.js.map +1 -1
  14. package/dist/server/bin.cjs +1278 -669
  15. package/dist/server/bin.cjs.map +1 -1
  16. package/package.json +2 -2
  17. package/src/adapters/base-acp-agent.ts +6 -3
  18. package/src/adapters/claude/UPSTREAM.md +63 -0
  19. package/src/adapters/claude/claude-agent.ts +682 -421
  20. package/src/adapters/claude/conversion/sdk-to-acp.ts +249 -85
  21. package/src/adapters/claude/conversion/tool-use-to-acp.ts +174 -149
  22. package/src/adapters/claude/hooks.ts +53 -1
  23. package/src/adapters/claude/permissions/permission-handlers.ts +39 -21
  24. package/src/adapters/claude/session/commands.ts +13 -9
  25. package/src/adapters/claude/session/mcp-config.ts +2 -5
  26. package/src/adapters/claude/session/options.ts +58 -6
  27. package/src/adapters/claude/session/settings.ts +326 -0
  28. package/src/adapters/claude/tools.ts +1 -0
  29. package/src/adapters/claude/types.ts +38 -0
  30. package/src/execution-mode.ts +26 -10
  31. package/src/server/agent-server.test.ts +41 -1
  32. package/src/session-log-writer.ts +1 -36
  33. package/src/utils/common.ts +1 -1
@@ -513,7 +513,7 @@ var require_has_flag = __commonJS({
513
513
  var require_supports_color = __commonJS({
514
514
  "../../node_modules/supports-color/index.js"(exports, module) {
515
515
  "use strict";
516
- var os4 = __require("os");
516
+ var os5 = __require("os");
517
517
  var tty = __require("tty");
518
518
  var hasFlag = require_has_flag();
519
519
  var { env } = process;
@@ -561,7 +561,7 @@ var require_supports_color = __commonJS({
561
561
  return min;
562
562
  }
563
563
  if (process.platform === "win32") {
564
- const osRelease = os4.release().split(".");
564
+ const osRelease = os5.release().split(".");
565
565
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
566
566
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
567
567
  }
@@ -809,10 +809,10 @@ var require_src2 = __commonJS({
809
809
  var fs_1 = __require("fs");
810
810
  var debug_1 = __importDefault(require_src());
811
811
  var log = debug_1.default("@kwsites/file-exists");
812
- function check(path8, isFile, isDirectory) {
813
- log(`checking %s`, path8);
812
+ function check(path9, isFile, isDirectory) {
813
+ log(`checking %s`, path9);
814
814
  try {
815
- const stat = fs_1.statSync(path8);
815
+ const stat = fs_1.statSync(path9);
816
816
  if (stat.isFile() && isFile) {
817
817
  log(`[OK] path represents a file`);
818
818
  return true;
@@ -832,8 +832,8 @@ var require_src2 = __commonJS({
832
832
  throw e;
833
833
  }
834
834
  }
835
- function exists2(path8, type = exports.READABLE) {
836
- return check(path8, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
835
+ function exists2(path9, type = exports.READABLE) {
836
+ return check(path9, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
837
837
  }
838
838
  exports.exists = exists2;
839
839
  exports.FILE = 1;
@@ -908,7 +908,7 @@ import { Hono } from "hono";
908
908
  // package.json
909
909
  var package_default = {
910
910
  name: "@posthog/agent",
911
- version: "2.1.125",
911
+ version: "2.1.137",
912
912
  repository: "https://github.com/PostHog/twig",
913
913
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
914
914
  exports: {
@@ -984,7 +984,6 @@ var package_default = {
984
984
  "@twig/git": "workspace:*",
985
985
  "@types/bun": "latest",
986
986
  "@types/tar": "^6.1.13",
987
- minimatch: "^10.0.3",
988
987
  msw: "^2.12.7",
989
988
  tsup: "^8.5.1",
990
989
  tsx: "^4.20.6",
@@ -1005,6 +1004,7 @@ var package_default = {
1005
1004
  commander: "^14.0.2",
1006
1005
  hono: "^4.11.7",
1007
1006
  jsonwebtoken: "^9.0.2",
1007
+ minimatch: "^10.0.3",
1008
1008
  tar: "^7.5.0",
1009
1009
  uuid: "13.0.0",
1010
1010
  "yoga-wasm-web": "^0.3.3",
@@ -1285,13 +1285,16 @@ function nodeWritableToWebWritable(nodeStream) {
1285
1285
  }
1286
1286
 
1287
1287
  // src/adapters/claude/claude-agent.ts
1288
- import * as fs2 from "fs";
1289
- import * as os3 from "os";
1290
- import * as path3 from "path";
1288
+ import { randomUUID } from "crypto";
1289
+ import * as fs3 from "fs";
1290
+ import * as os4 from "os";
1291
+ import * as path4 from "path";
1291
1292
  import {
1292
1293
  RequestError as RequestError2
1293
1294
  } from "@agentclientprotocol/sdk";
1294
1295
  import {
1296
+ getSessionMessages,
1297
+ listSessions,
1295
1298
  query
1296
1299
  } from "@anthropic-ai/claude-agent-sdk";
1297
1300
  import { v7 as uuidv7 } from "uuid";
@@ -1313,7 +1316,7 @@ function unreachable(value, logger) {
1313
1316
  try {
1314
1317
  valueAsString = JSON.stringify(value);
1315
1318
  } catch {
1316
- valueAsString = value;
1319
+ valueAsString = String(value);
1317
1320
  }
1318
1321
  logger.error(`Unexpected case: ${valueAsString}`);
1319
1322
  }
@@ -1385,19 +1388,20 @@ var BaseAcpAgent = class {
1385
1388
  }
1386
1389
  async cancel(params) {
1387
1390
  if (this.sessionId !== params.sessionId) {
1388
- throw new Error("Session not found");
1391
+ throw new Error("Session ID mismatch");
1389
1392
  }
1390
1393
  this.session.cancelled = true;
1391
1394
  const meta = params._meta;
1392
1395
  if (meta?.interruptReason) {
1393
1396
  this.session.interruptReason = meta.interruptReason;
1394
1397
  }
1395
- await this.interruptSession();
1398
+ await this.interrupt();
1396
1399
  }
1397
1400
  async closeSession() {
1398
1401
  try {
1399
1402
  this.session.abortController.abort();
1400
1403
  await this.cancel({ sessionId: this.sessionId });
1404
+ this.session.settingsManager.dispose();
1401
1405
  this.logger.info("Closed session", { sessionId: this.sessionId });
1402
1406
  } catch (err) {
1403
1407
  this.logger.warn("Failed to close session", {
@@ -1572,8 +1576,8 @@ var ToolContentBuilder = class {
1572
1576
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
1573
1577
  return this;
1574
1578
  }
1575
- diff(path8, oldText, newText) {
1576
- this.items.push({ type: "diff", path: path8, oldText, newText });
1579
+ diff(path9, oldText, newText) {
1580
+ this.items.push({ type: "diff", path: path9, oldText, newText });
1577
1581
  return this;
1578
1582
  }
1579
1583
  build() {
@@ -1593,7 +1597,7 @@ var registerHookCallback = (toolUseID, {
1593
1597
  onPostToolUseHook
1594
1598
  };
1595
1599
  };
1596
- var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
1600
+ var createPostToolUseHook = ({ onModeChange, logger }) => async (input, toolUseID) => {
1597
1601
  if (input.hook_event_name === "PostToolUse") {
1598
1602
  const toolName = input.tool_name;
1599
1603
  if (onModeChange && toolName === "EnterPlanMode") {
@@ -1608,11 +1612,54 @@ var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
1608
1612
  input.tool_response
1609
1613
  );
1610
1614
  delete toolUseCallbacks[toolUseID];
1615
+ } else {
1616
+ logger?.error(
1617
+ `No onPostToolUseHook found for tool use ID: ${toolUseID}`
1618
+ );
1619
+ delete toolUseCallbacks[toolUseID];
1611
1620
  }
1612
1621
  }
1613
1622
  }
1614
1623
  return { continue: true };
1615
1624
  };
1625
+ var createPreToolUseHook = (settingsManager, logger) => async (input, _toolUseID) => {
1626
+ if (input.hook_event_name !== "PreToolUse") {
1627
+ return { continue: true };
1628
+ }
1629
+ const toolName = input.tool_name;
1630
+ const toolInput = input.tool_input;
1631
+ const permissionCheck = settingsManager.checkPermission(
1632
+ toolName,
1633
+ toolInput
1634
+ );
1635
+ if (permissionCheck.decision !== "ask") {
1636
+ logger.info(
1637
+ `[PreToolUseHook] Tool: ${toolName}, Decision: ${permissionCheck.decision}, Rule: ${permissionCheck.rule}`
1638
+ );
1639
+ }
1640
+ switch (permissionCheck.decision) {
1641
+ case "allow":
1642
+ return {
1643
+ continue: true,
1644
+ hookSpecificOutput: {
1645
+ hookEventName: "PreToolUse",
1646
+ permissionDecision: "allow",
1647
+ permissionDecisionReason: `Allowed by settings rule: ${permissionCheck.rule}`
1648
+ }
1649
+ };
1650
+ case "deny":
1651
+ return {
1652
+ continue: true,
1653
+ hookSpecificOutput: {
1654
+ hookEventName: "PreToolUse",
1655
+ permissionDecision: "deny",
1656
+ permissionDecisionReason: `Denied by settings rule: ${permissionCheck.rule}`
1657
+ }
1658
+ };
1659
+ default:
1660
+ return { continue: true };
1661
+ }
1662
+ };
1616
1663
 
1617
1664
  // src/adapters/claude/mcp/tool-metadata.ts
1618
1665
  var mcpToolMetadataCache = /* @__PURE__ */ new Map();
@@ -1689,79 +1736,7 @@ var SYSTEM_REMINDER = `
1689
1736
  <system-reminder>
1690
1737
  Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
1691
1738
  </system-reminder>`;
1692
- function replaceAndCalculateLocation(fileContent, edits) {
1693
- let currentContent = fileContent;
1694
- const randomHex = Array.from(crypto.getRandomValues(new Uint8Array(5))).map((b) => b.toString(16).padStart(2, "0")).join("");
1695
- const markerPrefix = `__REPLACE_MARKER_${randomHex}_`;
1696
- let markerCounter = 0;
1697
- const markers = [];
1698
- for (const edit of edits) {
1699
- if (edit.oldText === "") {
1700
- throw new Error(
1701
- `The provided \`old_string\` is empty.
1702
-
1703
- No edits were applied.`
1704
- );
1705
- }
1706
- if (edit.replaceAll) {
1707
- const parts = [];
1708
- let lastIndex = 0;
1709
- let searchIndex = 0;
1710
- while (true) {
1711
- const index = currentContent.indexOf(edit.oldText, searchIndex);
1712
- if (index === -1) {
1713
- if (searchIndex === 0) {
1714
- throw new Error(
1715
- `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
1716
-
1717
- No edits were applied.`
1718
- );
1719
- }
1720
- break;
1721
- }
1722
- parts.push(currentContent.substring(lastIndex, index));
1723
- const marker = `${markerPrefix}${markerCounter++}__`;
1724
- markers.push(marker);
1725
- parts.push(marker + edit.newText);
1726
- lastIndex = index + edit.oldText.length;
1727
- searchIndex = lastIndex;
1728
- }
1729
- parts.push(currentContent.substring(lastIndex));
1730
- currentContent = parts.join("");
1731
- } else {
1732
- const index = currentContent.indexOf(edit.oldText);
1733
- if (index === -1) {
1734
- throw new Error(
1735
- `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
1736
-
1737
- No edits were applied.`
1738
- );
1739
- } else {
1740
- const marker = `${markerPrefix}${markerCounter++}__`;
1741
- markers.push(marker);
1742
- currentContent = currentContent.substring(0, index) + marker + edit.newText + currentContent.substring(index + edit.oldText.length);
1743
- }
1744
- }
1745
- }
1746
- const lineNumbers = [];
1747
- for (const marker of markers) {
1748
- const index = currentContent.indexOf(marker);
1749
- if (index !== -1) {
1750
- const lineNumber = Math.max(
1751
- 0,
1752
- currentContent.substring(0, index).split(/\r\n|\r|\n/).length - 1
1753
- );
1754
- lineNumbers.push(lineNumber);
1755
- }
1756
- }
1757
- let finalContent = currentContent;
1758
- for (const marker of markers) {
1759
- finalContent = finalContent.replace(marker, "");
1760
- }
1761
- const uniqueLineNumbers = [...new Set(lineNumbers)].sort();
1762
- return { newContent: finalContent, lineNumbers: uniqueLineNumbers };
1763
- }
1764
- function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ debug: false, prefix: "[ClaudeTools]" })) {
1739
+ function toolInfoFromToolUse(toolUse, options) {
1765
1740
  const name = toolUse.name;
1766
1741
  const input = toolUse.input;
1767
1742
  switch (name) {
@@ -1786,6 +1761,13 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1786
1761
  locations: input?.notebook_path ? [{ path: String(input.notebook_path) }] : []
1787
1762
  };
1788
1763
  case "Bash":
1764
+ if (options?.supportsTerminalOutput && options?.toolUseId) {
1765
+ return {
1766
+ title: input?.description ? String(input.description) : "Execute command",
1767
+ kind: "execute",
1768
+ content: [{ type: "terminal", terminalId: options.toolUseId }]
1769
+ };
1770
+ }
1789
1771
  return {
1790
1772
  title: input?.description ? String(input.description) : "Execute command",
1791
1773
  kind: "execute",
@@ -1806,11 +1788,11 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1806
1788
  case "Read": {
1807
1789
  let limit = "";
1808
1790
  const inputLimit = input?.limit;
1809
- const inputOffset = input?.offset ?? 0;
1791
+ const inputOffset = input?.offset ?? 1;
1810
1792
  if (inputLimit) {
1811
- limit = ` (${inputOffset + 1} - ${inputOffset + inputLimit})`;
1812
- } else if (inputOffset) {
1813
- limit = ` (from line ${inputOffset + 1})`;
1793
+ limit = ` (${inputOffset} - ${inputOffset + inputLimit - 1})`;
1794
+ } else if (inputOffset > 1) {
1795
+ limit = ` (from line ${inputOffset})`;
1814
1796
  }
1815
1797
  return {
1816
1798
  title: `Read ${input?.file_path ? String(input.file_path) : "File"}${limit}`,
@@ -1832,39 +1814,21 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1832
1814
  locations: []
1833
1815
  };
1834
1816
  case "Edit": {
1835
- const path8 = input?.file_path ? String(input.file_path) : void 0;
1836
- let oldText = input?.old_string ? String(input.old_string) : null;
1837
- let newText = input?.new_string ? String(input.new_string) : "";
1838
- let affectedLines = [];
1839
- if (path8 && oldText) {
1840
- try {
1841
- const oldContent = cachedFileContent[path8] || "";
1842
- const newContent = replaceAndCalculateLocation(oldContent, [
1843
- {
1844
- oldText,
1845
- newText,
1846
- replaceAll: false
1847
- }
1848
- ]);
1849
- oldText = oldContent;
1850
- newText = newContent.newContent;
1851
- affectedLines = newContent.lineNumbers;
1852
- } catch (e) {
1853
- logger.error("Failed to edit file", e);
1854
- }
1855
- }
1817
+ const path9 = input?.file_path ? String(input.file_path) : void 0;
1818
+ const oldText = input?.old_string ? String(input.old_string) : null;
1819
+ const newText = input?.new_string ? String(input.new_string) : "";
1856
1820
  return {
1857
- title: path8 ? `Edit \`${path8}\`` : "Edit",
1821
+ title: path9 ? `Edit \`${path9}\`` : "Edit",
1858
1822
  kind: "edit",
1859
- content: input && path8 ? [
1823
+ content: input && path9 ? [
1860
1824
  {
1861
1825
  type: "diff",
1862
- path: path8,
1826
+ path: path9,
1863
1827
  oldText,
1864
1828
  newText
1865
1829
  }
1866
1830
  ] : [],
1867
- locations: path8 ? affectedLines.length > 0 ? affectedLines.map((line) => ({ line, path: path8 })) : [{ path: path8 }] : []
1831
+ locations: path9 ? [{ path: path9 }] : []
1868
1832
  };
1869
1833
  }
1870
1834
  case "Write": {
@@ -1918,10 +1882,10 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1918
1882
  }
1919
1883
  if (input?.output_mode) {
1920
1884
  switch (input.output_mode) {
1921
- case "FilesWithMatches":
1885
+ case "files_with_matches":
1922
1886
  label += " -l";
1923
1887
  break;
1924
- case "Count":
1888
+ case "count":
1925
1889
  label += " -c";
1926
1890
  break;
1927
1891
  default:
@@ -1940,7 +1904,9 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1940
1904
  if (input?.multiline) {
1941
1905
  label += " -P";
1942
1906
  }
1943
- label += ` "${input?.pattern ? String(input.pattern) : ""}"`;
1907
+ if (input?.pattern) {
1908
+ label += ` "${String(input.pattern)}"`;
1909
+ }
1944
1910
  if (input?.path) {
1945
1911
  label += ` ${String(input.path)}`;
1946
1912
  }
@@ -2034,7 +2000,49 @@ function mcpToolInfo(name, _input) {
2034
2000
  content: []
2035
2001
  };
2036
2002
  }
2037
- function toolUpdateFromToolResult(toolResult, toolUse) {
2003
+ function toolUpdateFromEditToolResponse(toolResponse) {
2004
+ if (!toolResponse || typeof toolResponse !== "object") return null;
2005
+ const response = toolResponse;
2006
+ const patches = response.structuredPatch;
2007
+ if (!Array.isArray(patches) || patches.length === 0) return null;
2008
+ const content = [];
2009
+ const locations = [];
2010
+ for (const patch of patches) {
2011
+ if (!patch.hunks || patch.hunks.length === 0) continue;
2012
+ const filePath = patch.newFileName || patch.oldFileName;
2013
+ const oldLines = [];
2014
+ const newLines = [];
2015
+ for (const hunk of patch.hunks) {
2016
+ for (const line of hunk.lines) {
2017
+ if (line.startsWith("-")) {
2018
+ oldLines.push(line.slice(1));
2019
+ } else if (line.startsWith("+")) {
2020
+ newLines.push(line.slice(1));
2021
+ } else if (line.startsWith(" ")) {
2022
+ oldLines.push(line.slice(1));
2023
+ newLines.push(line.slice(1));
2024
+ }
2025
+ }
2026
+ }
2027
+ content.push({
2028
+ type: "diff",
2029
+ path: filePath,
2030
+ oldText: oldLines.join("\n"),
2031
+ newText: newLines.join("\n")
2032
+ });
2033
+ const firstHunk = patch.hunks[0];
2034
+ locations.push({
2035
+ path: filePath,
2036
+ line: firstHunk.newStart
2037
+ });
2038
+ }
2039
+ if (content.length === 0) return null;
2040
+ return { content, locations };
2041
+ }
2042
+ function toolUpdateFromToolResult(toolResult, toolUse, options) {
2043
+ if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
2044
+ return toAcpContentUpdate(toolResult.content, true);
2045
+ }
2038
2046
  switch (toolUse?.name) {
2039
2047
  case "Read":
2040
2048
  if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
@@ -2051,6 +2059,16 @@ function toolUpdateFromToolResult(toolResult, toolUse) {
2051
2059
  )
2052
2060
  };
2053
2061
  }
2062
+ if (itemObj.type === "image" && itemObj.source) {
2063
+ return {
2064
+ type: "content",
2065
+ content: {
2066
+ type: "image",
2067
+ data: itemObj.source.data ?? "",
2068
+ mimeType: itemObj.source.media_type ?? "image/png"
2069
+ }
2070
+ };
2071
+ }
2054
2072
  return {
2055
2073
  type: "content",
2056
2074
  content: item
@@ -2066,18 +2084,51 @@ function toolUpdateFromToolResult(toolResult, toolUse) {
2066
2084
  }
2067
2085
  return {};
2068
2086
  case "Bash": {
2069
- return toAcpContentUpdate(
2070
- toolResult.content,
2071
- "is_error" in toolResult ? toolResult.is_error : false
2072
- );
2073
- }
2074
- case "Edit":
2075
- case "Write": {
2076
- if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
2077
- return toAcpContentUpdate(toolResult.content, true);
2087
+ const result = toolResult.content;
2088
+ const terminalId = "tool_use_id" in toolResult ? String(toolResult.tool_use_id) : "";
2089
+ const isError = "is_error" in toolResult && toolResult.is_error;
2090
+ let output = "";
2091
+ let exitCode = isError ? 1 : 0;
2092
+ if (result && typeof result === "object" && "type" in result && result.type === "bash_code_execution_result") {
2093
+ const bashResult = result;
2094
+ output = [bashResult.stdout, bashResult.stderr].filter(Boolean).join("\n");
2095
+ exitCode = bashResult.return_code;
2096
+ } else if (typeof result === "string") {
2097
+ output = result;
2098
+ } else if (Array.isArray(result) && result.length > 0 && "text" in result[0] && typeof result[0].text === "string") {
2099
+ output = result.map((c) => c.text ?? "").join("\n");
2100
+ }
2101
+ if (options?.supportsTerminalOutput) {
2102
+ return {
2103
+ content: [{ type: "terminal", terminalId }],
2104
+ _meta: {
2105
+ terminal_info: {
2106
+ terminal_id: terminalId
2107
+ },
2108
+ terminal_output: {
2109
+ terminal_id: terminalId,
2110
+ data: output
2111
+ },
2112
+ terminal_exit: {
2113
+ terminal_id: terminalId,
2114
+ exit_code: exitCode,
2115
+ signal: null
2116
+ }
2117
+ }
2118
+ };
2119
+ }
2120
+ if (output.trim()) {
2121
+ return {
2122
+ content: toolContent().text(`\`\`\`console
2123
+ ${output.trimEnd()}
2124
+ \`\`\``).build()
2125
+ };
2078
2126
  }
2079
2127
  return {};
2080
2128
  }
2129
+ case "Edit":
2130
+ case "Write":
2131
+ return {};
2081
2132
  case "ExitPlanMode": {
2082
2133
  return { title: "Exited Plan Mode" };
2083
2134
  }
@@ -2237,6 +2288,7 @@ function handleThinkingChunk(chunk, parentToolCallId) {
2237
2288
  return update;
2238
2289
  }
2239
2290
  function handleToolUseChunk(chunk, ctx) {
2291
+ const alreadyCached = chunk.id in ctx.toolUseCache;
2240
2292
  ctx.toolUseCache[chunk.id] = chunk;
2241
2293
  if (chunk.name === "TodoWrite") {
2242
2294
  const input = chunk.input;
@@ -2248,37 +2300,60 @@ function handleToolUseChunk(chunk, ctx) {
2248
2300
  }
2249
2301
  return null;
2250
2302
  }
2251
- registerHookCallback(chunk.id, {
2252
- onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
2253
- const toolUse = ctx.toolUseCache[toolUseId];
2254
- if (toolUse) {
2255
- await ctx.client.sessionUpdate({
2256
- sessionId: ctx.sessionId,
2257
- update: {
2258
- _meta: toolMeta(toolUse.name, toolResponse, ctx.parentToolCallId),
2259
- toolCallId: toolUseId,
2260
- sessionUpdate: "tool_call_update"
2261
- }
2262
- });
2263
- } else {
2264
- ctx.logger.error(
2265
- `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
2266
- );
2303
+ if (!alreadyCached && ctx.registerHooks !== false) {
2304
+ registerHookCallback(chunk.id, {
2305
+ onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
2306
+ const toolUse = ctx.toolUseCache[toolUseId];
2307
+ if (toolUse) {
2308
+ const editUpdate = toolUse.name === "Edit" ? toolUpdateFromEditToolResponse(toolResponse) : null;
2309
+ await ctx.client.sessionUpdate({
2310
+ sessionId: ctx.sessionId,
2311
+ update: {
2312
+ _meta: toolMeta(toolUse.name, toolResponse, ctx.parentToolCallId),
2313
+ toolCallId: toolUseId,
2314
+ sessionUpdate: "tool_call_update",
2315
+ ...editUpdate ? editUpdate : {}
2316
+ }
2317
+ });
2318
+ } else {
2319
+ ctx.logger.error(
2320
+ `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
2321
+ );
2322
+ }
2267
2323
  }
2268
- }
2269
- });
2324
+ });
2325
+ }
2270
2326
  let rawInput;
2271
2327
  try {
2272
2328
  rawInput = JSON.parse(JSON.stringify(chunk.input));
2273
2329
  } catch {
2274
2330
  }
2331
+ const toolInfo = toolInfoFromToolUse(chunk, {
2332
+ supportsTerminalOutput: ctx.supportsTerminalOutput,
2333
+ toolUseId: chunk.id
2334
+ });
2335
+ const meta = {
2336
+ ...toolMeta(chunk.name, void 0, ctx.parentToolCallId)
2337
+ };
2338
+ if (chunk.name === "Bash" && ctx.supportsTerminalOutput && !alreadyCached) {
2339
+ meta.terminal_info = { terminal_id: chunk.id };
2340
+ }
2341
+ if (alreadyCached) {
2342
+ return {
2343
+ _meta: meta,
2344
+ toolCallId: chunk.id,
2345
+ sessionUpdate: "tool_call_update",
2346
+ rawInput,
2347
+ ...toolInfo
2348
+ };
2349
+ }
2275
2350
  return {
2276
- _meta: toolMeta(chunk.name, void 0, ctx.parentToolCallId),
2351
+ _meta: meta,
2277
2352
  toolCallId: chunk.id,
2278
2353
  sessionUpdate: "tool_call",
2279
2354
  rawInput,
2280
2355
  status: "pending",
2281
- ...toolInfoFromToolUse(chunk, ctx.fileContentCache, ctx.logger)
2356
+ ...toolInfo
2282
2357
  };
2283
2358
  }
2284
2359
  function handleToolResultChunk(chunk, ctx) {
@@ -2287,36 +2362,71 @@ function handleToolResultChunk(chunk, ctx) {
2287
2362
  ctx.logger.error(
2288
2363
  `Got a tool result for tool use that wasn't tracked: ${chunk.tool_use_id}`
2289
2364
  );
2290
- return null;
2365
+ return [];
2291
2366
  }
2292
2367
  if (toolUse.name === "TodoWrite") {
2293
- return null;
2368
+ return [];
2294
2369
  }
2295
- return {
2296
- _meta: toolMeta(toolUse.name, void 0, ctx.parentToolCallId),
2370
+ const { _meta: resultMeta, ...toolUpdate } = toolUpdateFromToolResult(
2371
+ chunk,
2372
+ toolUse,
2373
+ {
2374
+ supportsTerminalOutput: ctx.supportsTerminalOutput,
2375
+ toolUseId: chunk.tool_use_id
2376
+ }
2377
+ );
2378
+ const updates = [];
2379
+ if (resultMeta?.terminal_output) {
2380
+ const terminalOutputMeta = {
2381
+ terminal_output: resultMeta.terminal_output
2382
+ };
2383
+ if (ctx.parentToolCallId) {
2384
+ terminalOutputMeta.claudeCode = {
2385
+ parentToolCallId: ctx.parentToolCallId
2386
+ };
2387
+ }
2388
+ updates.push({
2389
+ _meta: terminalOutputMeta,
2390
+ toolCallId: chunk.tool_use_id,
2391
+ sessionUpdate: "tool_call_update"
2392
+ });
2393
+ }
2394
+ const meta = {
2395
+ ...toolMeta(toolUse.name, void 0, ctx.parentToolCallId),
2396
+ ...resultMeta?.terminal_exit ? { terminal_exit: resultMeta.terminal_exit } : {}
2397
+ };
2398
+ updates.push({
2399
+ _meta: meta,
2297
2400
  toolCallId: chunk.tool_use_id,
2298
2401
  sessionUpdate: "tool_call_update",
2299
2402
  status: chunk.is_error ? "failed" : "completed",
2300
- ...toolUpdateFromToolResult(
2301
- chunk,
2302
- toolUse
2303
- )
2304
- };
2403
+ rawOutput: chunk.content,
2404
+ ...toolUpdate
2405
+ });
2406
+ return updates;
2305
2407
  }
2306
2408
  function processContentChunk(chunk, role, ctx) {
2307
2409
  switch (chunk.type) {
2308
2410
  case "text":
2309
- case "text_delta":
2310
- return handleTextChunk(chunk, role, ctx.parentToolCallId);
2311
- case "image":
2312
- return handleImageChunk(chunk, role);
2411
+ case "text_delta": {
2412
+ const update = handleTextChunk(chunk, role, ctx.parentToolCallId);
2413
+ return update ? [update] : [];
2414
+ }
2415
+ case "image": {
2416
+ const update = handleImageChunk(chunk, role);
2417
+ return update ? [update] : [];
2418
+ }
2313
2419
  case "thinking":
2314
- case "thinking_delta":
2315
- return handleThinkingChunk(chunk, ctx.parentToolCallId);
2420
+ case "thinking_delta": {
2421
+ const update = handleThinkingChunk(chunk, ctx.parentToolCallId);
2422
+ return update ? [update] : [];
2423
+ }
2316
2424
  case "tool_use":
2317
2425
  case "server_tool_use":
2318
- case "mcp_tool_use":
2319
- return handleToolUseChunk(chunk, ctx);
2426
+ case "mcp_tool_use": {
2427
+ const update = handleToolUseChunk(chunk, ctx);
2428
+ return update ? [update] : [];
2429
+ }
2320
2430
  case "tool_result":
2321
2431
  case "tool_search_tool_result":
2322
2432
  case "web_fetch_tool_result":
@@ -2338,13 +2448,13 @@ function processContentChunk(chunk, role, ctx) {
2338
2448
  case "container_upload":
2339
2449
  case "compaction":
2340
2450
  case "compaction_delta":
2341
- return null;
2451
+ return [];
2342
2452
  default:
2343
2453
  unreachable(chunk, ctx.logger);
2344
- return null;
2454
+ return [];
2345
2455
  }
2346
2456
  }
2347
- function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId) {
2457
+ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput) {
2348
2458
  if (typeof content === "string") {
2349
2459
  const update = {
2350
2460
  sessionUpdate: messageUpdateType(role),
@@ -2365,18 +2475,19 @@ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentC
2365
2475
  fileContentCache,
2366
2476
  client,
2367
2477
  logger,
2368
- parentToolCallId
2478
+ parentToolCallId,
2479
+ registerHooks,
2480
+ supportsTerminalOutput
2369
2481
  };
2370
2482
  const output = [];
2371
2483
  for (const chunk of content) {
2372
- const update = processContentChunk(chunk, role, ctx);
2373
- if (update) {
2484
+ for (const update of processContentChunk(chunk, role, ctx)) {
2374
2485
  output.push({ sessionId, update });
2375
2486
  }
2376
2487
  }
2377
2488
  return output;
2378
2489
  }
2379
- function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId) {
2490
+ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput) {
2380
2491
  const event = message.event;
2381
2492
  switch (event.type) {
2382
2493
  case "content_block_start":
@@ -2388,7 +2499,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
2388
2499
  fileContentCache,
2389
2500
  client,
2390
2501
  logger,
2391
- parentToolCallId
2502
+ parentToolCallId,
2503
+ registerHooks,
2504
+ supportsTerminalOutput
2392
2505
  );
2393
2506
  case "content_block_delta":
2394
2507
  return toAcpNotifications(
@@ -2399,7 +2512,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
2399
2512
  fileContentCache,
2400
2513
  client,
2401
2514
  logger,
2402
- parentToolCallId
2515
+ parentToolCallId,
2516
+ registerHooks,
2517
+ supportsTerminalOutput
2403
2518
  );
2404
2519
  case "message_start":
2405
2520
  case "message_delta":
@@ -2458,29 +2573,25 @@ async function handleSystemMessage(message, context) {
2458
2573
  break;
2459
2574
  }
2460
2575
  }
2461
- function handleResultMessage(message, context) {
2462
- const { session } = context;
2463
- if (session.cancelled) {
2464
- return {
2465
- shouldStop: true,
2466
- stopReason: "cancelled"
2467
- };
2468
- }
2576
+ function handleResultMessage(message) {
2577
+ const usage = extractUsageFromResult(message);
2469
2578
  switch (message.subtype) {
2470
2579
  case "success": {
2471
2580
  if (message.result.includes("Please run /login")) {
2472
2581
  return {
2473
2582
  shouldStop: true,
2474
- error: RequestError.authRequired()
2583
+ error: RequestError.authRequired(),
2584
+ usage
2475
2585
  };
2476
2586
  }
2477
2587
  if (message.is_error) {
2478
2588
  return {
2479
2589
  shouldStop: true,
2480
- error: RequestError.internalError(void 0, message.result)
2590
+ error: RequestError.internalError(void 0, message.result),
2591
+ usage
2481
2592
  };
2482
2593
  }
2483
- return { shouldStop: true, stopReason: "end_turn" };
2594
+ return { shouldStop: true, stopReason: "end_turn", usage };
2484
2595
  }
2485
2596
  case "error_during_execution":
2486
2597
  if (message.is_error) {
@@ -2489,10 +2600,11 @@ function handleResultMessage(message, context) {
2489
2600
  error: RequestError.internalError(
2490
2601
  void 0,
2491
2602
  message.errors.join(", ") || message.subtype
2492
- )
2603
+ ),
2604
+ usage
2493
2605
  };
2494
2606
  }
2495
- return { shouldStop: true, stopReason: "end_turn" };
2607
+ return { shouldStop: true, stopReason: "end_turn", usage };
2496
2608
  case "error_max_budget_usd":
2497
2609
  case "error_max_turns":
2498
2610
  case "error_max_structured_output_retries":
@@ -2502,13 +2614,37 @@ function handleResultMessage(message, context) {
2502
2614
  error: RequestError.internalError(
2503
2615
  void 0,
2504
2616
  message.errors.join(", ") || message.subtype
2505
- )
2617
+ ),
2618
+ usage
2506
2619
  };
2507
2620
  }
2508
- return { shouldStop: true, stopReason: "max_turn_requests" };
2621
+ return { shouldStop: true, stopReason: "max_turn_requests", usage };
2509
2622
  default:
2510
- return { shouldStop: false };
2623
+ return { shouldStop: false, usage };
2624
+ }
2625
+ }
2626
+ function extractUsageFromResult(message) {
2627
+ const msg = message;
2628
+ const msgUsage = msg.usage;
2629
+ if (!msgUsage) return void 0;
2630
+ const modelUsage = msg.modelUsage;
2631
+ let contextWindowSize;
2632
+ if (modelUsage) {
2633
+ const contextWindows = Object.values(modelUsage).map(
2634
+ (m) => m.contextWindow
2635
+ );
2636
+ if (contextWindows.length > 0) {
2637
+ contextWindowSize = Math.min(...contextWindows);
2638
+ }
2511
2639
  }
2640
+ return {
2641
+ inputTokens: msgUsage.input_tokens ?? 0,
2642
+ outputTokens: msgUsage.output_tokens ?? 0,
2643
+ cachedReadTokens: msgUsage.cache_read_input_tokens ?? 0,
2644
+ cachedWriteTokens: msgUsage.cache_creation_input_tokens ?? 0,
2645
+ costUsd: typeof msg.total_cost_usd === "number" ? msg.total_cost_usd : void 0,
2646
+ contextWindowSize
2647
+ };
2512
2648
  }
2513
2649
  async function handleStreamEvent(message, context) {
2514
2650
  const { sessionId, client, toolUseCache, fileContentCache, logger } = context;
@@ -2520,7 +2656,9 @@ async function handleStreamEvent(message, context) {
2520
2656
  fileContentCache,
2521
2657
  client,
2522
2658
  logger,
2523
- parentToolCallId
2659
+ parentToolCallId,
2660
+ context.registerHooks,
2661
+ context.supportsTerminalOutput
2524
2662
  )) {
2525
2663
  await client.sessionUpdate(notification);
2526
2664
  context.session.notificationHistory.push(notification);
@@ -2532,14 +2670,15 @@ function hasLocalCommandStdout(content) {
2532
2670
  function hasLocalCommandStderr(content) {
2533
2671
  return typeof content === "string" && content.includes("<local-command-stderr>");
2534
2672
  }
2535
- function isSimpleUserMessage(message) {
2536
- return message.type === "user" && (typeof message.message.content === "string" || Array.isArray(message.message.content) && message.message.content.length === 1 && message.message.content[0].type === "text");
2537
- }
2538
2673
  function isLoginRequiredMessage(message) {
2539
2674
  return message.type === "assistant" && message.message.model === "<synthetic>" && Array.isArray(message.message.content) && message.message.content.length === 1 && message.message.content[0].type === "text" && message.message.content[0].text?.includes("Please run /login") === true;
2540
2675
  }
2676
+ function isPlainTextUserMessage(message) {
2677
+ const content = message.message.content;
2678
+ return message.type === "user" && (typeof content === "string" || Array.isArray(content) && content.length === 1 && content[0].type === "text");
2679
+ }
2541
2680
  function shouldSkipUserAssistantMessage(message) {
2542
- return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isSimpleUserMessage(message) || isLoginRequiredMessage(message);
2681
+ return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isLoginRequiredMessage(message);
2543
2682
  }
2544
2683
  function logSpecialMessages(message, logger) {
2545
2684
  const content = message.message.content;
@@ -2560,18 +2699,33 @@ function filterMessageContent(content) {
2560
2699
  }
2561
2700
  async function handleUserAssistantMessage(message, context) {
2562
2701
  const { session, sessionId, client, toolUseCache, fileContentCache, logger } = context;
2563
- if (session.cancelled) {
2564
- return {};
2565
- }
2566
2702
  if (shouldSkipUserAssistantMessage(message)) {
2703
+ const content2 = message.message.content;
2704
+ if (typeof content2 === "string" && hasLocalCommandStdout(content2) && content2.includes("Context Usage")) {
2705
+ const stripped = content2.replace("<local-command-stdout>", "").replace("</local-command-stdout>", "");
2706
+ for (const notification of toAcpNotifications(
2707
+ stripped,
2708
+ "assistant",
2709
+ sessionId,
2710
+ toolUseCache,
2711
+ fileContentCache,
2712
+ client,
2713
+ logger
2714
+ )) {
2715
+ await client.sessionUpdate(notification);
2716
+ }
2717
+ }
2567
2718
  logSpecialMessages(message, logger);
2568
2719
  if (isLoginRequiredMessage(message)) {
2569
2720
  return { shouldStop: true, error: RequestError.authRequired() };
2570
2721
  }
2571
2722
  return {};
2572
2723
  }
2724
+ if (isPlainTextUserMessage(message)) {
2725
+ return {};
2726
+ }
2573
2727
  const content = message.message.content;
2574
- const contentToProcess = filterMessageContent(content);
2728
+ const contentToProcess = message.type === "assistant" ? filterMessageContent(content) : content;
2575
2729
  const parentToolCallId = "parent_tool_use_id" in message ? message.parent_tool_use_id ?? void 0 : void 0;
2576
2730
  for (const notification of toAcpNotifications(
2577
2731
  contentToProcess,
@@ -2581,7 +2735,9 @@ async function handleUserAssistantMessage(message, context) {
2581
2735
  fileContentCache,
2582
2736
  client,
2583
2737
  logger,
2584
- parentToolCallId
2738
+ parentToolCallId,
2739
+ context.registerHooks,
2740
+ context.supportsTerminalOutput
2585
2741
  )) {
2586
2742
  await client.sessionUpdate(notification);
2587
2743
  session.notificationHistory.push(notification);
@@ -2667,36 +2823,45 @@ function normalizeAskUserQuestionInput(input) {
2667
2823
  }
2668
2824
 
2669
2825
  // src/execution-mode.ts
2670
- var MODES = [
2826
+ var ALLOW_BYPASS = !IS_ROOT;
2827
+ var availableModes = [
2671
2828
  {
2672
2829
  id: "default",
2673
- name: "Always Ask",
2674
- description: "Prompts for permission on first use of each tool"
2830
+ name: "Default",
2831
+ description: "Standard behavior, prompts for dangerous operations"
2675
2832
  },
2676
2833
  {
2677
2834
  id: "acceptEdits",
2678
2835
  name: "Accept Edits",
2679
- description: "Automatically accepts file edit permissions for the session"
2836
+ description: "Auto-accept file edit operations"
2680
2837
  },
2681
2838
  {
2682
2839
  id: "plan",
2683
2840
  name: "Plan Mode",
2684
- description: "Claude can analyze but not modify files or execute commands"
2685
- },
2686
- {
2687
- id: "bypassPermissions",
2688
- name: "Bypass Permissions",
2689
- description: "Skips all permission prompts"
2841
+ description: "Planning mode, no actual tool execution"
2690
2842
  }
2843
+ // {
2844
+ // id: "dontAsk",
2845
+ // name: "Don't Ask",
2846
+ // description: "Don't prompt for permissions, deny if not pre-approved",
2847
+ // },
2691
2848
  ];
2849
+ if (ALLOW_BYPASS) {
2850
+ availableModes.push({
2851
+ id: "bypassPermissions",
2852
+ name: "Bypass Permissions",
2853
+ description: "Bypass all permission checks"
2854
+ });
2855
+ }
2692
2856
  var TWIG_EXECUTION_MODES = [
2693
2857
  "default",
2694
2858
  "acceptEdits",
2695
2859
  "plan",
2860
+ // "dontAsk",
2696
2861
  "bypassPermissions"
2697
2862
  ];
2698
2863
  function getAvailableModes() {
2699
- return IS_ROOT ? MODES.filter((m) => m.id !== "bypassPermissions") : MODES;
2864
+ return IS_ROOT ? availableModes.filter((m) => m.id !== "bypassPermissions") : availableModes;
2700
2865
  }
2701
2866
 
2702
2867
  // src/adapters/claude/tools.ts
@@ -2724,6 +2889,7 @@ var AUTO_ALLOWED_TOOLS = {
2724
2889
  default: new Set(BASE_ALLOWED_TOOLS),
2725
2890
  acceptEdits: /* @__PURE__ */ new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),
2726
2891
  plan: new Set(BASE_ALLOWED_TOOLS)
2892
+ // dontAsk: new Set(BASE_ALLOWED_TOOLS),
2727
2893
  };
2728
2894
  function isToolAllowedForMode(toolName, mode) {
2729
2895
  if (mode === "bypassPermissions") {
@@ -2871,12 +3037,11 @@ async function validatePlanContent(planText, context) {
2871
3037
  return { valid: true };
2872
3038
  }
2873
3039
  async function requestPlanApproval(context, updatedInput) {
2874
- const { client, sessionId, toolUseID, fileContentCache } = context;
2875
- const toolInfo = toolInfoFromToolUse(
2876
- { name: context.toolName, input: updatedInput },
2877
- fileContentCache,
2878
- context.logger
2879
- );
3040
+ const { client, sessionId, toolUseID } = context;
3041
+ const toolInfo = toolInfoFromToolUse({
3042
+ name: context.toolName,
3043
+ input: updatedInput
3044
+ });
2880
3045
  return await client.requestPermission({
2881
3046
  options: buildExitPlanModePermissionOptions(),
2882
3047
  sessionId,
@@ -2895,7 +3060,14 @@ async function applyPlanApproval(response, context, updatedInput) {
2895
3060
  if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "default" || response.outcome.optionId === "acceptEdits")) {
2896
3061
  session.permissionMode = response.outcome.optionId;
2897
3062
  await session.query.setPermissionMode(response.outcome.optionId);
2898
- await context.emitConfigOptionsUpdate();
3063
+ await context.client.sessionUpdate({
3064
+ sessionId: context.sessionId,
3065
+ update: {
3066
+ sessionUpdate: "current_mode_update",
3067
+ currentModeId: response.outcome.optionId
3068
+ }
3069
+ });
3070
+ await context.updateConfigOption("mode", response.outcome.optionId);
2899
3071
  return {
2900
3072
  behavior: "allow",
2901
3073
  updatedInput,
@@ -2916,7 +3088,7 @@ async function handleEnterPlanModeTool(context) {
2916
3088
  const { session, toolInput } = context;
2917
3089
  session.permissionMode = "plan";
2918
3090
  await session.query.setPermissionMode("plan");
2919
- await context.emitConfigOptionsUpdate();
3091
+ await context.updateConfigOption("mode", "plan");
2920
3092
  return {
2921
3093
  behavior: "allow",
2922
3094
  updatedInput: toolInput
@@ -2934,6 +3106,9 @@ async function handleExitPlanModeTool(context) {
2934
3106
  return validationResult.error;
2935
3107
  }
2936
3108
  const response = await requestPlanApproval(context, updatedInput);
3109
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3110
+ throw new Error("Tool use aborted");
3111
+ }
2937
3112
  return await applyPlanApproval(response, context, updatedInput);
2938
3113
  }
2939
3114
  function buildQuestionOptions(question) {
@@ -2957,14 +3132,13 @@ async function handleAskUserQuestionTool(context) {
2957
3132
  interrupt: true
2958
3133
  };
2959
3134
  }
2960
- const { client, sessionId, toolUseID, toolInput, fileContentCache } = context;
3135
+ const { client, sessionId, toolUseID, toolInput } = context;
2961
3136
  const firstQuestion = questions[0];
2962
3137
  const options = buildQuestionOptions(firstQuestion);
2963
- const toolInfo = toolInfoFromToolUse(
2964
- { name: context.toolName, input: toolInput },
2965
- fileContentCache,
2966
- context.logger
2967
- );
3138
+ const toolInfo = toolInfoFromToolUse({
3139
+ name: context.toolName,
3140
+ input: toolInput
3141
+ });
2968
3142
  const response = await client.requestPermission({
2969
3143
  options,
2970
3144
  sessionId,
@@ -2979,6 +3153,9 @@ async function handleAskUserQuestionTool(context) {
2979
3153
  }
2980
3154
  }
2981
3155
  });
3156
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3157
+ throw new Error("Tool use aborted");
3158
+ }
2982
3159
  if (response.outcome?.outcome !== "selected") {
2983
3160
  const customMessage = response._meta?.message;
2984
3161
  return {
@@ -3011,14 +3188,9 @@ async function handleDefaultPermissionFlow(context) {
3011
3188
  toolUseID,
3012
3189
  client,
3013
3190
  sessionId,
3014
- fileContentCache,
3015
3191
  suggestions
3016
3192
  } = context;
3017
- const toolInfo = toolInfoFromToolUse(
3018
- { name: toolName, input: toolInput },
3019
- fileContentCache,
3020
- context.logger
3021
- );
3193
+ const toolInfo = toolInfoFromToolUse({ name: toolName, input: toolInput });
3022
3194
  const options = buildPermissionOptions(
3023
3195
  toolName,
3024
3196
  toolInput,
@@ -3037,6 +3209,9 @@ async function handleDefaultPermissionFlow(context) {
3037
3209
  rawInput: toolInput
3038
3210
  }
3039
3211
  });
3212
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3213
+ throw new Error("Tool use aborted");
3214
+ }
3040
3215
  if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "allow" || response.outcome.optionId === "allow_always")) {
3041
3216
  if (response.outcome.optionId === "allow_always") {
3042
3217
  return {
@@ -3113,16 +3288,18 @@ async function canUseTool(context) {
3113
3288
  var UNSUPPORTED_COMMANDS = [
3114
3289
  "context",
3115
3290
  "cost",
3291
+ "keybindings-help",
3116
3292
  "login",
3117
3293
  "logout",
3118
3294
  "output-style:new",
3119
3295
  "release-notes",
3120
3296
  "todos"
3121
3297
  ];
3122
- async function getAvailableSlashCommands(q) {
3123
- const commands = await q.supportedCommands();
3298
+ function getAvailableSlashCommands(commands) {
3124
3299
  return commands.map((command) => {
3125
- const input = command.argumentHint ? { hint: command.argumentHint } : null;
3300
+ const input = command.argumentHint != null ? {
3301
+ hint: Array.isArray(command.argumentHint) ? command.argumentHint.join(" ") : command.argumentHint
3302
+ } : null;
3126
3303
  let name = command.name;
3127
3304
  if (command.name.endsWith(" (MCP)")) {
3128
3305
  name = `mcp:${name.replace(" (MCP)", "")}`;
@@ -3220,13 +3397,19 @@ function buildEnvironment() {
3220
3397
  ENABLE_TOOL_SEARCH: "auto:0"
3221
3398
  };
3222
3399
  }
3223
- function buildHooks(userHooks, onModeChange) {
3400
+ function buildHooks(userHooks, onModeChange, settingsManager, logger) {
3224
3401
  return {
3225
3402
  ...userHooks,
3226
3403
  PostToolUse: [
3227
3404
  ...userHooks?.PostToolUse || [],
3228
3405
  {
3229
- hooks: [createPostToolUseHook({ onModeChange })]
3406
+ hooks: [createPostToolUseHook({ onModeChange, logger })]
3407
+ }
3408
+ ],
3409
+ PreToolUse: [
3410
+ ...userHooks?.PreToolUse || [],
3411
+ {
3412
+ hooks: [createPreToolUseHook(settingsManager, logger)]
3230
3413
  }
3231
3414
  ]
3232
3415
  };
@@ -3320,12 +3503,22 @@ function buildSessionOptions(params) {
3320
3503
  permissionMode: params.permissionMode,
3321
3504
  canUseTool: params.canUseTool,
3322
3505
  executable: "node",
3506
+ tools: { type: "preset", preset: "claude_code" },
3507
+ extraArgs: {
3508
+ ...params.userProvidedOptions?.extraArgs,
3509
+ "replay-user-messages": ""
3510
+ },
3323
3511
  mcpServers: buildMcpServers(
3324
3512
  params.userProvidedOptions?.mcpServers,
3325
3513
  params.mcpServers
3326
3514
  ),
3327
3515
  env: buildEnvironment(),
3328
- hooks: buildHooks(params.userProvidedOptions?.hooks, params.onModeChange),
3516
+ hooks: buildHooks(
3517
+ params.userProvidedOptions?.hooks,
3518
+ params.onModeChange,
3519
+ params.settingsManager,
3520
+ params.logger
3521
+ ),
3329
3522
  abortController: getAbortController(
3330
3523
  params.userProvidedOptions?.abortController
3331
3524
  ),
@@ -3342,13 +3535,36 @@ function buildSessionOptions(params) {
3342
3535
  }
3343
3536
  if (params.isResume) {
3344
3537
  options.resume = params.sessionId;
3345
- options.forkSession = false;
3538
+ options.forkSession = params.forkSession ?? false;
3346
3539
  } else {
3347
3540
  options.sessionId = params.sessionId;
3541
+ options.model = DEFAULT_MODEL;
3348
3542
  }
3349
3543
  if (params.additionalDirectories) {
3350
3544
  options.additionalDirectories = params.additionalDirectories;
3351
3545
  }
3546
+ if (params.disableBuiltInTools) {
3547
+ const builtInTools = [
3548
+ "Read",
3549
+ "Write",
3550
+ "Edit",
3551
+ "Bash",
3552
+ "Glob",
3553
+ "Grep",
3554
+ "Task",
3555
+ "TodoWrite",
3556
+ "ExitPlanMode",
3557
+ "WebSearch",
3558
+ "WebFetch",
3559
+ "SlashCommand",
3560
+ "Skill",
3561
+ "NotebookEdit"
3562
+ ];
3563
+ options.disallowedTools = [
3564
+ ...options.disallowedTools ?? [],
3565
+ ...builtInTools
3566
+ ];
3567
+ }
3352
3568
  clearStatsigCache();
3353
3569
  return options;
3354
3570
  }
@@ -3361,15 +3577,268 @@ function clearStatsigCache() {
3361
3577
  });
3362
3578
  }
3363
3579
 
3580
+ // src/adapters/claude/session/settings.ts
3581
+ import * as fs2 from "fs";
3582
+ import * as os3 from "os";
3583
+ import * as path3 from "path";
3584
+ import { minimatch } from "minimatch";
3585
+ var ACP_TOOL_NAME_PREFIX = "mcp__acp__";
3586
+ var acpToolNames = {
3587
+ read: `${ACP_TOOL_NAME_PREFIX}Read`,
3588
+ edit: `${ACP_TOOL_NAME_PREFIX}Edit`,
3589
+ write: `${ACP_TOOL_NAME_PREFIX}Write`,
3590
+ bash: `${ACP_TOOL_NAME_PREFIX}Bash`
3591
+ };
3592
+ var SHELL_OPERATORS = ["&&", "||", ";", "|", "$(", "`", "\n"];
3593
+ function containsShellOperator(str) {
3594
+ return SHELL_OPERATORS.some((op) => str.includes(op));
3595
+ }
3596
+ var FILE_EDITING_TOOLS = [acpToolNames.edit, acpToolNames.write];
3597
+ var FILE_READING_TOOLS = [acpToolNames.read];
3598
+ var TOOL_ARG_ACCESSORS = {
3599
+ [acpToolNames.read]: (input) => input?.file_path,
3600
+ [acpToolNames.edit]: (input) => input?.file_path,
3601
+ [acpToolNames.write]: (input) => input?.file_path,
3602
+ [acpToolNames.bash]: (input) => input?.command
3603
+ };
3604
+ function parseRule(rule) {
3605
+ const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
3606
+ if (!match) {
3607
+ return { toolName: rule };
3608
+ }
3609
+ const toolName = match[1] ?? rule;
3610
+ const argument = match[2];
3611
+ if (argument?.endsWith(":*")) {
3612
+ return {
3613
+ toolName,
3614
+ argument: argument.slice(0, -2),
3615
+ isWildcard: true
3616
+ };
3617
+ }
3618
+ return { toolName, argument };
3619
+ }
3620
+ function normalizePath(filePath, cwd) {
3621
+ let resolved = filePath;
3622
+ if (resolved.startsWith("~/")) {
3623
+ resolved = path3.join(os3.homedir(), resolved.slice(2));
3624
+ } else if (resolved.startsWith("./")) {
3625
+ resolved = path3.join(cwd, resolved.slice(2));
3626
+ } else if (!path3.isAbsolute(resolved)) {
3627
+ resolved = path3.join(cwd, resolved);
3628
+ }
3629
+ return path3.normalize(resolved).replace(/\\/g, "/");
3630
+ }
3631
+ function matchesGlob(pattern, filePath, cwd) {
3632
+ const normalizedPattern = normalizePath(pattern, cwd);
3633
+ const normalizedPath = normalizePath(filePath, cwd);
3634
+ return minimatch(normalizedPath, normalizedPattern, {
3635
+ dot: true,
3636
+ matchBase: false,
3637
+ nocase: process.platform === "win32"
3638
+ });
3639
+ }
3640
+ function matchesRule(rule, toolName, toolInput, cwd) {
3641
+ const ruleAppliesToTool = rule.toolName === "Bash" && toolName === acpToolNames.bash || rule.toolName === "Edit" && FILE_EDITING_TOOLS.includes(toolName) || rule.toolName === "Read" && FILE_READING_TOOLS.includes(toolName);
3642
+ if (!ruleAppliesToTool) {
3643
+ return false;
3644
+ }
3645
+ if (!rule.argument) {
3646
+ return true;
3647
+ }
3648
+ const argAccessor = TOOL_ARG_ACCESSORS[toolName];
3649
+ if (!argAccessor) {
3650
+ return true;
3651
+ }
3652
+ const actualArg = argAccessor(toolInput);
3653
+ if (!actualArg) {
3654
+ return false;
3655
+ }
3656
+ if (toolName === acpToolNames.bash) {
3657
+ if (rule.isWildcard) {
3658
+ if (!actualArg.startsWith(rule.argument)) {
3659
+ return false;
3660
+ }
3661
+ const remainder = actualArg.slice(rule.argument.length);
3662
+ if (containsShellOperator(remainder)) {
3663
+ return false;
3664
+ }
3665
+ return true;
3666
+ }
3667
+ return actualArg === rule.argument;
3668
+ }
3669
+ return matchesGlob(rule.argument, actualArg, cwd);
3670
+ }
3671
+ async function loadSettingsFile(filePath) {
3672
+ if (!filePath) {
3673
+ return {};
3674
+ }
3675
+ try {
3676
+ const content = await fs2.promises.readFile(filePath, "utf-8");
3677
+ return JSON.parse(content);
3678
+ } catch {
3679
+ return {};
3680
+ }
3681
+ }
3682
+ function getManagedSettingsPath() {
3683
+ switch (process.platform) {
3684
+ case "darwin":
3685
+ return "/Library/Application Support/ClaudeCode/managed-settings.json";
3686
+ case "linux":
3687
+ return "/etc/claude-code/managed-settings.json";
3688
+ case "win32":
3689
+ return "C:\\Program Files\\ClaudeCode\\managed-settings.json";
3690
+ default:
3691
+ return "/etc/claude-code/managed-settings.json";
3692
+ }
3693
+ }
3694
+ var SettingsManager = class {
3695
+ cwd;
3696
+ userSettings = {};
3697
+ projectSettings = {};
3698
+ localSettings = {};
3699
+ enterpriseSettings = {};
3700
+ mergedSettings = {};
3701
+ initialized = false;
3702
+ constructor(cwd) {
3703
+ this.cwd = cwd;
3704
+ }
3705
+ async initialize() {
3706
+ if (this.initialized) {
3707
+ return;
3708
+ }
3709
+ await this.loadAllSettings();
3710
+ this.initialized = true;
3711
+ }
3712
+ getUserSettingsPath() {
3713
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path3.join(os3.homedir(), ".claude");
3714
+ return path3.join(configDir, "settings.json");
3715
+ }
3716
+ getProjectSettingsPath() {
3717
+ return path3.join(this.cwd, ".claude", "settings.json");
3718
+ }
3719
+ getLocalSettingsPath() {
3720
+ return path3.join(this.cwd, ".claude", "settings.local.json");
3721
+ }
3722
+ async loadAllSettings() {
3723
+ const [userSettings, projectSettings, localSettings, enterpriseSettings] = await Promise.all([
3724
+ loadSettingsFile(this.getUserSettingsPath()),
3725
+ loadSettingsFile(this.getProjectSettingsPath()),
3726
+ loadSettingsFile(this.getLocalSettingsPath()),
3727
+ loadSettingsFile(getManagedSettingsPath())
3728
+ ]);
3729
+ this.userSettings = userSettings;
3730
+ this.projectSettings = projectSettings;
3731
+ this.localSettings = localSettings;
3732
+ this.enterpriseSettings = enterpriseSettings;
3733
+ this.mergeAllSettings();
3734
+ }
3735
+ mergeAllSettings() {
3736
+ const allSettings = [
3737
+ this.userSettings,
3738
+ this.projectSettings,
3739
+ this.localSettings,
3740
+ this.enterpriseSettings
3741
+ ];
3742
+ const permissions = {
3743
+ allow: [],
3744
+ deny: [],
3745
+ ask: []
3746
+ };
3747
+ const merged = { permissions };
3748
+ for (const settings of allSettings) {
3749
+ if (settings.permissions) {
3750
+ if (settings.permissions.allow) {
3751
+ permissions.allow?.push(...settings.permissions.allow);
3752
+ }
3753
+ if (settings.permissions.deny) {
3754
+ permissions.deny?.push(...settings.permissions.deny);
3755
+ }
3756
+ if (settings.permissions.ask) {
3757
+ permissions.ask?.push(...settings.permissions.ask);
3758
+ }
3759
+ if (settings.permissions.additionalDirectories) {
3760
+ permissions.additionalDirectories = [
3761
+ ...permissions.additionalDirectories || [],
3762
+ ...settings.permissions.additionalDirectories
3763
+ ];
3764
+ }
3765
+ if (settings.permissions.defaultMode) {
3766
+ permissions.defaultMode = settings.permissions.defaultMode;
3767
+ }
3768
+ }
3769
+ if (settings.env) {
3770
+ merged.env = { ...merged.env, ...settings.env };
3771
+ }
3772
+ if (settings.model) {
3773
+ merged.model = settings.model;
3774
+ }
3775
+ }
3776
+ this.mergedSettings = merged;
3777
+ }
3778
+ checkPermission(toolName, toolInput) {
3779
+ if (!toolName.startsWith(ACP_TOOL_NAME_PREFIX)) {
3780
+ return { decision: "ask" };
3781
+ }
3782
+ const permissions = this.mergedSettings.permissions;
3783
+ if (!permissions) {
3784
+ return { decision: "ask" };
3785
+ }
3786
+ for (const rule of permissions.deny || []) {
3787
+ const parsed = parseRule(rule);
3788
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3789
+ return { decision: "deny", rule, source: "deny" };
3790
+ }
3791
+ }
3792
+ for (const rule of permissions.allow || []) {
3793
+ const parsed = parseRule(rule);
3794
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3795
+ return { decision: "allow", rule, source: "allow" };
3796
+ }
3797
+ }
3798
+ for (const rule of permissions.ask || []) {
3799
+ const parsed = parseRule(rule);
3800
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3801
+ return { decision: "ask", rule, source: "ask" };
3802
+ }
3803
+ }
3804
+ return { decision: "ask" };
3805
+ }
3806
+ getSettings() {
3807
+ return this.mergedSettings;
3808
+ }
3809
+ getCwd() {
3810
+ return this.cwd;
3811
+ }
3812
+ async setCwd(cwd) {
3813
+ if (this.cwd === cwd) {
3814
+ return;
3815
+ }
3816
+ this.dispose();
3817
+ this.cwd = cwd;
3818
+ this.initialized = false;
3819
+ await this.initialize();
3820
+ }
3821
+ dispose() {
3822
+ this.initialized = false;
3823
+ }
3824
+ };
3825
+
3364
3826
  // src/adapters/claude/claude-agent.ts
3365
3827
  var SESSION_VALIDATION_TIMEOUT_MS = 1e4;
3828
+ var MAX_TITLE_LENGTH = 256;
3829
+ function sanitizeTitle(text2) {
3830
+ const sanitized = text2.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
3831
+ if (sanitized.length <= MAX_TITLE_LENGTH) {
3832
+ return sanitized;
3833
+ }
3834
+ return `${sanitized.slice(0, MAX_TITLE_LENGTH - 1)}\u2026`;
3835
+ }
3366
3836
  var ClaudeAcpAgent = class extends BaseAcpAgent {
3367
3837
  adapterName = "claude";
3368
3838
  toolUseCache;
3369
3839
  backgroundTerminals = {};
3370
3840
  clientCapabilities;
3371
3841
  options;
3372
- lastSentConfigOptions;
3373
3842
  constructor(client, options) {
3374
3843
  super(client);
3375
3844
  this.options = options;
@@ -3390,125 +3859,416 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3390
3859
  sse: true
3391
3860
  },
3392
3861
  loadSession: true,
3862
+ sessionCapabilities: {
3863
+ list: {},
3864
+ fork: {},
3865
+ resume: {}
3866
+ },
3393
3867
  _meta: {
3394
3868
  posthog: {
3395
3869
  resumeSession: true
3870
+ },
3871
+ claudeCode: {
3872
+ promptQueueing: true
3396
3873
  }
3397
3874
  }
3398
3875
  },
3399
3876
  agentInfo: {
3400
3877
  name: package_default.name,
3401
- title: "Claude Code",
3878
+ title: "Claude Agent",
3402
3879
  version: package_default.version
3403
3880
  },
3404
- authMethods: [
3405
- {
3406
- id: "claude-login",
3407
- name: "Log in with Claude Code",
3408
- description: "Run `claude /login` in the terminal"
3881
+ authMethods: []
3882
+ };
3883
+ }
3884
+ async newSession(params) {
3885
+ if (fs3.existsSync(path4.resolve(os4.homedir(), ".claude.json.backup")) && !fs3.existsSync(path4.resolve(os4.homedir(), ".claude.json"))) {
3886
+ throw RequestError2.authRequired();
3887
+ }
3888
+ const response = await this.createSession(params, {
3889
+ // Revisit these meta values once we support resume
3890
+ resume: params._meta?.claudeCode?.options?.resume
3891
+ });
3892
+ return response;
3893
+ }
3894
+ async unstable_forkSession(params) {
3895
+ return this.createSession(
3896
+ {
3897
+ cwd: params.cwd,
3898
+ mcpServers: params.mcpServers ?? [],
3899
+ _meta: params._meta
3900
+ },
3901
+ { resume: params.sessionId, forkSession: true }
3902
+ );
3903
+ }
3904
+ async unstable_resumeSession(params) {
3905
+ const response = await this.createSession(
3906
+ {
3907
+ cwd: params.cwd,
3908
+ mcpServers: params.mcpServers ?? [],
3909
+ _meta: params._meta
3910
+ },
3911
+ {
3912
+ resume: params.sessionId
3913
+ }
3914
+ );
3915
+ return response;
3916
+ }
3917
+ async loadSession(params) {
3918
+ const response = await this.createSession(
3919
+ {
3920
+ cwd: params.cwd,
3921
+ mcpServers: params.mcpServers ?? [],
3922
+ _meta: params._meta
3923
+ },
3924
+ { resume: params.sessionId, skipBackgroundFetches: true }
3925
+ );
3926
+ await this.replaySessionHistory(params.sessionId);
3927
+ this.deferBackgroundFetches(this.session.query);
3928
+ return {
3929
+ modes: response.modes,
3930
+ models: response.models,
3931
+ configOptions: response.configOptions
3932
+ };
3933
+ }
3934
+ async unstable_listSessions(params) {
3935
+ const sdkSessions = await listSessions({ dir: params.cwd ?? void 0 });
3936
+ const sessions = [];
3937
+ for (const session of sdkSessions) {
3938
+ if (!session.cwd) continue;
3939
+ sessions.push({
3940
+ sessionId: session.sessionId,
3941
+ cwd: session.cwd,
3942
+ title: sanitizeTitle(session.customTitle || session.summary || ""),
3943
+ updatedAt: new Date(session.lastModified).toISOString()
3944
+ });
3945
+ }
3946
+ return {
3947
+ sessions
3948
+ };
3949
+ }
3950
+ async prompt(params) {
3951
+ this.session.cancelled = false;
3952
+ this.session.interruptReason = void 0;
3953
+ this.session.accumulatedUsage = {
3954
+ inputTokens: 0,
3955
+ outputTokens: 0,
3956
+ cachedReadTokens: 0,
3957
+ cachedWriteTokens: 0
3958
+ };
3959
+ const userMessage = promptToClaude(params);
3960
+ if (this.session.promptRunning) {
3961
+ const uuid = randomUUID();
3962
+ userMessage.uuid = uuid;
3963
+ this.session.input.push(userMessage);
3964
+ const order = this.session.nextPendingOrder++;
3965
+ const cancelled = await new Promise((resolve4) => {
3966
+ this.session.pendingMessages.set(uuid, { resolve: resolve4, order });
3967
+ });
3968
+ if (cancelled) {
3969
+ return { stopReason: "cancelled" };
3970
+ }
3971
+ } else {
3972
+ this.session.input.push(userMessage);
3973
+ }
3974
+ await this.broadcastUserMessage(params);
3975
+ this.session.promptRunning = true;
3976
+ let handedOff = false;
3977
+ let lastAssistantTotalUsage = null;
3978
+ const supportsTerminalOutput = this.clientCapabilities?._meta?.terminal_output === true;
3979
+ const context = {
3980
+ session: this.session,
3981
+ sessionId: params.sessionId,
3982
+ client: this.client,
3983
+ toolUseCache: this.toolUseCache,
3984
+ fileContentCache: this.fileContentCache,
3985
+ logger: this.logger,
3986
+ supportsTerminalOutput
3987
+ };
3988
+ try {
3989
+ while (true) {
3990
+ const { value: message, done } = await this.session.query.next();
3991
+ if (done || !message) {
3992
+ if (this.session.cancelled) {
3993
+ return {
3994
+ stopReason: "cancelled",
3995
+ _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
3996
+ };
3997
+ }
3998
+ break;
3999
+ }
4000
+ switch (message.type) {
4001
+ case "system":
4002
+ if (message.subtype === "compact_boundary") {
4003
+ lastAssistantTotalUsage = 0;
4004
+ }
4005
+ await handleSystemMessage(message, context);
4006
+ break;
4007
+ case "result": {
4008
+ if (this.session.cancelled) {
4009
+ return { stopReason: "cancelled" };
4010
+ }
4011
+ this.session.accumulatedUsage.inputTokens += message.usage.input_tokens;
4012
+ this.session.accumulatedUsage.outputTokens += message.usage.output_tokens;
4013
+ this.session.accumulatedUsage.cachedReadTokens += message.usage.cache_read_input_tokens;
4014
+ this.session.accumulatedUsage.cachedWriteTokens += message.usage.cache_creation_input_tokens;
4015
+ const contextWindows = Object.values(message.modelUsage).map(
4016
+ (m) => m.contextWindow
4017
+ );
4018
+ const contextWindowSize = contextWindows.length > 0 ? Math.min(...contextWindows) : 2e5;
4019
+ if (lastAssistantTotalUsage !== null) {
4020
+ await this.client.sessionUpdate({
4021
+ sessionId: params.sessionId,
4022
+ update: {
4023
+ sessionUpdate: "usage_update",
4024
+ used: lastAssistantTotalUsage,
4025
+ size: contextWindowSize,
4026
+ cost: {
4027
+ amount: message.total_cost_usd,
4028
+ currency: "USD"
4029
+ }
4030
+ }
4031
+ });
4032
+ }
4033
+ await this.client.extNotification("_posthog/usage_update", {
4034
+ sessionId: params.sessionId,
4035
+ used: {
4036
+ inputTokens: message.usage.input_tokens,
4037
+ outputTokens: message.usage.output_tokens,
4038
+ cachedReadTokens: message.usage.cache_read_input_tokens,
4039
+ cachedWriteTokens: message.usage.cache_creation_input_tokens
4040
+ },
4041
+ cost: message.total_cost_usd
4042
+ });
4043
+ const usage = {
4044
+ inputTokens: this.session.accumulatedUsage.inputTokens,
4045
+ outputTokens: this.session.accumulatedUsage.outputTokens,
4046
+ cachedReadTokens: this.session.accumulatedUsage.cachedReadTokens,
4047
+ cachedWriteTokens: this.session.accumulatedUsage.cachedWriteTokens,
4048
+ totalTokens: this.session.accumulatedUsage.inputTokens + this.session.accumulatedUsage.outputTokens + this.session.accumulatedUsage.cachedReadTokens + this.session.accumulatedUsage.cachedWriteTokens
4049
+ };
4050
+ const result = handleResultMessage(message);
4051
+ if (result.error) throw result.error;
4052
+ switch (message.subtype) {
4053
+ case "error_max_budget_usd":
4054
+ case "error_max_turns":
4055
+ case "error_max_structured_output_retries":
4056
+ return { stopReason: "max_turn_requests", usage };
4057
+ default:
4058
+ return { stopReason: "end_turn", usage };
4059
+ }
4060
+ }
4061
+ case "stream_event":
4062
+ await handleStreamEvent(message, context);
4063
+ break;
4064
+ case "user":
4065
+ case "assistant": {
4066
+ if (this.session.cancelled) {
4067
+ break;
4068
+ }
4069
+ if (message.type === "user" && "uuid" in message && message.uuid) {
4070
+ const pending = this.session.pendingMessages.get(
4071
+ message.uuid
4072
+ );
4073
+ if (pending) {
4074
+ pending.resolve(false);
4075
+ this.session.pendingMessages.delete(message.uuid);
4076
+ handedOff = true;
4077
+ return { stopReason: "end_turn" };
4078
+ }
4079
+ }
4080
+ if ("usage" in message.message && message.parent_tool_use_id === null) {
4081
+ const usage = message.message.usage;
4082
+ lastAssistantTotalUsage = usage.input_tokens + usage.output_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens;
4083
+ }
4084
+ const result = await handleUserAssistantMessage(message, context);
4085
+ if (result.error) throw result.error;
4086
+ if (result.shouldStop) {
4087
+ return { stopReason: "end_turn" };
4088
+ }
4089
+ break;
4090
+ }
4091
+ case "tool_progress":
4092
+ case "auth_status":
4093
+ case "tool_use_summary":
4094
+ break;
4095
+ default:
4096
+ unreachable(message, this.logger);
4097
+ break;
3409
4098
  }
3410
- ]
3411
- };
4099
+ }
4100
+ throw new Error("Session did not end in result");
4101
+ } finally {
4102
+ if (!handedOff) {
4103
+ this.session.promptRunning = false;
4104
+ for (const [key, pending] of this.session.pendingMessages) {
4105
+ pending.resolve(true);
4106
+ this.session.pendingMessages.delete(key);
4107
+ }
4108
+ }
4109
+ }
3412
4110
  }
3413
- async authenticate(_params) {
3414
- throw new Error("Method not implemented.");
4111
+ // Called by BaseAcpAgent#cancel() to interrupt the session
4112
+ async interrupt() {
4113
+ this.session.cancelled = true;
4114
+ for (const [, pending] of this.session.pendingMessages) {
4115
+ pending.resolve(true);
4116
+ }
4117
+ this.session.pendingMessages.clear();
4118
+ await this.session.query.interrupt();
3415
4119
  }
3416
- async newSession(params) {
3417
- this.checkAuthStatus();
4120
+ async unstable_setSessionModel(params) {
4121
+ const sdkModelId = toSdkModelId(params.modelId);
4122
+ await this.session.query.setModel(sdkModelId);
4123
+ this.session.modelId = params.modelId;
4124
+ await this.updateConfigOption("model", params.modelId);
4125
+ return {};
4126
+ }
4127
+ async setSessionMode(params) {
4128
+ await this.applySessionMode(params.modeId);
4129
+ await this.updateConfigOption("mode", params.modeId);
4130
+ return {};
4131
+ }
4132
+ async setSessionConfigOption(params) {
4133
+ const option = this.session.configOptions.find(
4134
+ (o) => o.id === params.configId
4135
+ );
4136
+ if (!option) {
4137
+ throw new Error(`Unknown config option: ${params.configId}`);
4138
+ }
4139
+ const allValues = "options" in option && Array.isArray(option.options) ? option.options.flatMap(
4140
+ (o) => "options" in o && Array.isArray(o.options) ? o.options : [o]
4141
+ ) : [];
4142
+ const validValue = allValues.find((o) => o.value === params.value);
4143
+ if (!validValue) {
4144
+ throw new Error(
4145
+ `Invalid value for config option ${params.configId}: ${params.value}`
4146
+ );
4147
+ }
4148
+ if (params.configId === "mode") {
4149
+ await this.applySessionMode(params.value);
4150
+ await this.client.sessionUpdate({
4151
+ sessionId: this.sessionId,
4152
+ update: {
4153
+ sessionUpdate: "current_mode_update",
4154
+ currentModeId: params.value
4155
+ }
4156
+ });
4157
+ } else if (params.configId === "model") {
4158
+ const sdkModelId = toSdkModelId(params.value);
4159
+ await this.session.query.setModel(sdkModelId);
4160
+ this.session.modelId = params.value;
4161
+ }
4162
+ this.session.configOptions = this.session.configOptions.map(
4163
+ (o) => o.id === params.configId ? { ...o, currentValue: params.value } : o
4164
+ );
4165
+ return { configOptions: this.session.configOptions };
4166
+ }
4167
+ async updateConfigOption(configId, value) {
4168
+ this.session.configOptions = this.session.configOptions.map(
4169
+ (o) => o.id === configId ? { ...o, currentValue: value } : o
4170
+ );
4171
+ await this.client.sessionUpdate({
4172
+ sessionId: this.sessionId,
4173
+ update: {
4174
+ sessionUpdate: "config_option_update",
4175
+ configOptions: this.session.configOptions
4176
+ }
4177
+ });
4178
+ }
4179
+ async applySessionMode(modeId) {
4180
+ if (!TWIG_EXECUTION_MODES.includes(modeId)) {
4181
+ throw new Error("Invalid Mode");
4182
+ }
4183
+ const previousMode = this.session.permissionMode;
4184
+ this.session.permissionMode = modeId;
4185
+ try {
4186
+ await this.session.query.setPermissionMode(modeId);
4187
+ } catch (error) {
4188
+ this.session.permissionMode = previousMode;
4189
+ if (error instanceof Error) {
4190
+ if (!error.message) {
4191
+ error.message = "Invalid Mode";
4192
+ }
4193
+ throw error;
4194
+ }
4195
+ throw new Error("Invalid Mode");
4196
+ }
4197
+ }
4198
+ async createSession(params, creationOpts = {}) {
4199
+ const { cwd } = params;
4200
+ const { resume, forkSession } = creationOpts;
4201
+ const isResume = !!resume;
3418
4202
  const meta = params._meta;
3419
4203
  const taskId = meta?.persistence?.taskId;
3420
- const sessionId = uuidv7();
3421
- this.logger.info("Creating new session", {
4204
+ let sessionId;
4205
+ if (forkSession) {
4206
+ sessionId = uuidv7();
4207
+ } else if (isResume) {
4208
+ sessionId = resume;
4209
+ } else {
4210
+ sessionId = uuidv7();
4211
+ }
4212
+ const input = new Pushable();
4213
+ const settingsManager = new SettingsManager(cwd);
4214
+ await settingsManager.initialize();
4215
+ const mcpServers = parseMcpServers(params);
4216
+ const systemPrompt = buildSystemPrompt(meta?.systemPrompt);
4217
+ this.logger.info(isResume ? "Resuming session" : "Creating new session", {
3422
4218
  sessionId,
3423
4219
  taskId,
3424
4220
  taskRunId: meta?.taskRunId,
3425
- cwd: params.cwd
4221
+ cwd
3426
4222
  });
3427
4223
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3428
- const mcpServers = parseMcpServers(params);
3429
4224
  const options = buildSessionOptions({
3430
- cwd: params.cwd,
4225
+ cwd,
3431
4226
  mcpServers,
3432
4227
  permissionMode,
3433
4228
  canUseTool: this.createCanUseTool(sessionId),
3434
4229
  logger: this.logger,
3435
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
4230
+ systemPrompt,
3436
4231
  userProvidedOptions: meta?.claudeCode?.options,
3437
4232
  sessionId,
3438
- isResume: false,
3439
- onModeChange: this.createOnModeChange(sessionId),
4233
+ isResume,
4234
+ forkSession,
4235
+ additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
4236
+ disableBuiltInTools: meta?.disableBuiltInTools,
4237
+ settingsManager,
4238
+ onModeChange: this.createOnModeChange(),
3440
4239
  onProcessSpawned: this.options?.onProcessSpawned,
3441
4240
  onProcessExited: this.options?.onProcessExited
3442
4241
  });
3443
- const input = new Pushable();
3444
- options.model = DEFAULT_MODEL;
4242
+ const abortController = options.abortController;
3445
4243
  const q = query({ prompt: input, options });
3446
- const session = this.createSession(
3447
- sessionId,
3448
- q,
4244
+ const session = {
4245
+ query: q,
3449
4246
  input,
4247
+ cancelled: false,
4248
+ settingsManager,
3450
4249
  permissionMode,
3451
- params.cwd,
3452
- options.abortController
3453
- );
3454
- session.taskRunId = meta?.taskRunId;
3455
- if (meta?.taskRunId) {
3456
- await this.client.extNotification("_posthog/sdk_session", {
3457
- taskRunId: meta.taskRunId,
3458
- sessionId,
3459
- adapter: "claude"
3460
- });
3461
- }
3462
- const modelOptions = await this.getModelConfigOptions();
3463
- this.deferBackgroundFetches(q, sessionId);
3464
- session.modelId = modelOptions.currentModelId;
3465
- const resolvedSdkModel = toSdkModelId(modelOptions.currentModelId);
3466
- if (resolvedSdkModel !== DEFAULT_MODEL) {
3467
- await this.trySetModel(q, modelOptions.currentModelId);
3468
- }
3469
- const configOptions = await this.buildConfigOptions(modelOptions);
3470
- return {
3471
- sessionId,
3472
- configOptions
3473
- };
3474
- }
3475
- async loadSession(params) {
3476
- return this.resumeSession(params);
3477
- }
3478
- async resumeSession(params) {
3479
- const meta = params._meta;
3480
- const taskId = meta?.persistence?.taskId;
3481
- const sessionId = meta?.sessionId;
3482
- if (!sessionId) {
3483
- throw new Error("Cannot resume session without sessionId");
3484
- }
3485
- if (this.sessionId === sessionId) {
3486
- return {};
3487
- }
3488
- this.logger.info("Resuming session", {
3489
- sessionId,
3490
- taskId,
3491
- taskRunId: meta?.taskRunId,
3492
- cwd: params.cwd
3493
- });
3494
- const mcpServers = parseMcpServers(params);
3495
- const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3496
- const { query: q, session } = await this.initializeQuery({
3497
- cwd: params.cwd,
3498
- permissionMode,
3499
- mcpServers,
3500
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3501
- userProvidedOptions: meta?.claudeCode?.options,
3502
- sessionId,
3503
- isResume: true,
3504
- additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3505
- });
3506
- this.logger.info("Session query initialized, awaiting resumption", {
3507
- sessionId,
3508
- taskId,
4250
+ abortController,
4251
+ accumulatedUsage: {
4252
+ inputTokens: 0,
4253
+ outputTokens: 0,
4254
+ cachedReadTokens: 0,
4255
+ cachedWriteTokens: 0
4256
+ },
4257
+ configOptions: [],
4258
+ promptRunning: false,
4259
+ pendingMessages: /* @__PURE__ */ new Map(),
4260
+ nextPendingOrder: 0,
4261
+ // Custom properties
4262
+ cwd,
4263
+ notificationHistory: [],
3509
4264
  taskRunId: meta?.taskRunId
3510
- });
3511
- session.taskRunId = meta?.taskRunId;
4265
+ };
4266
+ this.session = session;
4267
+ this.sessionId = sessionId;
4268
+ this.logger.info(
4269
+ isResume ? "Session query initialized, awaiting resumption" : "Session query initialized, awaiting initialization",
4270
+ { sessionId, taskId, taskRunId: meta?.taskRunId }
4271
+ );
3512
4272
  try {
3513
4273
  const result = await withTimeout(
3514
4274
  q.initializationResult(),
@@ -3516,231 +4276,181 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3516
4276
  );
3517
4277
  if (result.result === "timeout") {
3518
4278
  throw new Error(
3519
- `Session resumption timed out for sessionId=${sessionId}`
4279
+ `Session ${isResume ? forkSession ? "fork" : "resumption" : "initialization"} timed out for sessionId=${sessionId}`
3520
4280
  );
3521
4281
  }
3522
4282
  } catch (err) {
3523
- this.logger.error("Session resumption failed", {
3524
- sessionId,
3525
- taskId,
3526
- taskRunId: meta?.taskRunId,
3527
- error: err instanceof Error ? err.message : String(err)
3528
- });
4283
+ settingsManager.dispose();
4284
+ this.logger.error(
4285
+ isResume ? forkSession ? "Session fork failed" : "Session resumption failed" : "Session initialization failed",
4286
+ {
4287
+ sessionId,
4288
+ taskId,
4289
+ taskRunId: meta?.taskRunId,
4290
+ error: err instanceof Error ? err.message : String(err)
4291
+ }
4292
+ );
3529
4293
  throw err;
3530
4294
  }
3531
- this.logger.info("Session resumed successfully", {
3532
- sessionId,
3533
- taskId,
3534
- taskRunId: meta?.taskRunId
3535
- });
3536
- this.deferBackgroundFetches(q, sessionId);
3537
- const configOptions = await this.buildConfigOptions();
3538
- return { configOptions };
3539
- }
3540
- async prompt(params) {
3541
- this.session.cancelled = false;
3542
- this.session.interruptReason = void 0;
3543
- await this.broadcastUserMessage(params);
3544
- this.session.input.push(promptToClaude(params));
3545
- return this.processMessages(params.sessionId);
3546
- }
3547
- async setSessionConfigOption(params) {
3548
- const configId = params.configId;
3549
- const value = params.value;
3550
- if (configId === "mode") {
3551
- const modeId = value;
3552
- if (!TWIG_EXECUTION_MODES.includes(modeId)) {
3553
- throw new Error("Invalid Mode");
3554
- }
3555
- this.session.permissionMode = modeId;
3556
- await this.session.query.setPermissionMode(modeId);
3557
- } else if (configId === "model") {
3558
- await this.setModelWithFallback(this.session.query, value);
3559
- this.session.modelId = value;
3560
- } else {
3561
- throw new Error("Unsupported config option");
3562
- }
3563
- await this.emitConfigOptionsUpdate();
3564
- return { configOptions: await this.buildConfigOptions() };
3565
- }
3566
- async interruptSession() {
3567
- await this.session.query.interrupt();
3568
- }
3569
- async extMethod(method, params) {
3570
- if (method === "_posthog/session/resume") {
3571
- const result = await this.resumeSession(
3572
- params
3573
- );
3574
- return {
3575
- _meta: {
3576
- configOptions: result.configOptions
3577
- }
3578
- };
4295
+ if (meta?.taskRunId) {
4296
+ await this.client.extNotification("_posthog/sdk_session", {
4297
+ taskRunId: meta.taskRunId,
4298
+ sessionId,
4299
+ adapter: "claude"
4300
+ });
3579
4301
  }
3580
- throw RequestError2.methodNotFound(method);
3581
- }
3582
- createSession(sessionId, q, input, permissionMode, cwd, abortController) {
3583
- const session = {
3584
- query: q,
3585
- input,
3586
- cancelled: false,
3587
- permissionMode,
3588
- cwd,
3589
- notificationHistory: [],
3590
- abortController
4302
+ const settingsModel = settingsManager.getSettings().model;
4303
+ const modelOptions = await this.getModelConfigOptions();
4304
+ const resolvedModelId = settingsModel || modelOptions.currentModelId;
4305
+ session.modelId = resolvedModelId;
4306
+ if (!isResume) {
4307
+ const resolvedSdkModel = toSdkModelId(resolvedModelId);
4308
+ if (resolvedSdkModel !== DEFAULT_MODEL) {
4309
+ await this.session.query.setModel(resolvedSdkModel);
4310
+ }
4311
+ }
4312
+ const availableModes2 = getAvailableModes();
4313
+ const modes = {
4314
+ currentModeId: permissionMode,
4315
+ availableModes: availableModes2.map((mode) => ({
4316
+ id: mode.id,
4317
+ name: mode.name,
4318
+ description: mode.description ?? void 0
4319
+ }))
4320
+ };
4321
+ const models = {
4322
+ currentModelId: resolvedModelId,
4323
+ availableModels: modelOptions.options.map(
4324
+ (opt) => ({
4325
+ modelId: opt.value,
4326
+ name: opt.name,
4327
+ description: opt.description
4328
+ })
4329
+ )
3591
4330
  };
3592
- this.session = session;
3593
- this.sessionId = sessionId;
3594
- return session;
3595
- }
3596
- async initializeQuery(config) {
3597
- const input = new Pushable();
3598
- const options = buildSessionOptions({
3599
- cwd: config.cwd,
3600
- mcpServers: config.mcpServers,
3601
- permissionMode: config.permissionMode,
3602
- canUseTool: this.createCanUseTool(config.sessionId),
3603
- logger: this.logger,
3604
- systemPrompt: config.systemPrompt,
3605
- userProvidedOptions: config.userProvidedOptions,
3606
- sessionId: config.sessionId,
3607
- isResume: config.isResume,
3608
- additionalDirectories: config.additionalDirectories,
3609
- onModeChange: this.createOnModeChange(config.sessionId),
3610
- onProcessSpawned: this.options?.onProcessSpawned,
3611
- onProcessExited: this.options?.onProcessExited
3612
- });
3613
- const q = query({ prompt: input, options });
3614
- const abortController = options.abortController;
3615
- const session = this.createSession(
3616
- config.sessionId,
3617
- q,
3618
- input,
3619
- config.permissionMode,
3620
- config.cwd,
3621
- abortController
4331
+ const configOptions = this.buildConfigOptions(permissionMode, modelOptions);
4332
+ session.configOptions = configOptions;
4333
+ if (!creationOpts.skipBackgroundFetches) {
4334
+ this.deferBackgroundFetches(q);
4335
+ }
4336
+ this.logger.info(
4337
+ isResume ? "Session resumed successfully" : "Session created successfully",
4338
+ {
4339
+ sessionId,
4340
+ taskId,
4341
+ taskRunId: meta?.taskRunId
4342
+ }
3622
4343
  );
3623
- return { query: q, input, session };
4344
+ return { sessionId, modes, models, configOptions };
3624
4345
  }
3625
4346
  createCanUseTool(sessionId) {
3626
- return async (toolName, toolInput, { suggestions, toolUseID }) => canUseTool({
4347
+ return async (toolName, toolInput, { suggestions, toolUseID, signal }) => canUseTool({
3627
4348
  session: this.session,
3628
4349
  toolName,
3629
4350
  toolInput,
3630
4351
  toolUseID,
3631
4352
  suggestions,
4353
+ signal,
3632
4354
  client: this.client,
3633
4355
  sessionId,
3634
4356
  fileContentCache: this.fileContentCache,
3635
4357
  logger: this.logger,
3636
- emitConfigOptionsUpdate: () => this.emitConfigOptionsUpdate(sessionId)
4358
+ updateConfigOption: (configId, value) => this.updateConfigOption(configId, value)
3637
4359
  });
3638
4360
  }
3639
- createOnModeChange(sessionId) {
4361
+ createOnModeChange() {
3640
4362
  return async (newMode) => {
3641
4363
  if (this.session) {
3642
4364
  this.session.permissionMode = newMode;
3643
4365
  }
3644
- await this.emitConfigOptionsUpdate(sessionId);
4366
+ await this.updateConfigOption("mode", newMode);
3645
4367
  };
3646
4368
  }
3647
- async buildConfigOptions(modelOptionsOverride) {
3648
- const options = [];
4369
+ buildConfigOptions(currentModeId, modelOptions) {
3649
4370
  const modeOptions = getAvailableModes().map((mode) => ({
3650
4371
  value: mode.id,
3651
4372
  name: mode.name,
3652
4373
  description: mode.description ?? void 0
3653
4374
  }));
3654
- options.push({
3655
- id: "mode",
3656
- name: "Approval Preset",
3657
- type: "select",
3658
- currentValue: this.session.permissionMode,
3659
- options: modeOptions,
3660
- category: "mode",
3661
- description: "Choose an approval and sandboxing preset for your session"
3662
- });
3663
- const modelOptions = modelOptionsOverride ?? await this.getModelConfigOptions(this.session.modelId);
3664
- this.session.modelId = modelOptions.currentModelId;
3665
- options.push({
3666
- id: "model",
3667
- name: "Model",
3668
- type: "select",
3669
- currentValue: modelOptions.currentModelId,
3670
- options: modelOptions.options,
3671
- category: "model",
3672
- description: "Choose which model Claude should use"
3673
- });
3674
- return options;
4375
+ return [
4376
+ {
4377
+ id: "mode",
4378
+ name: "Approval Preset",
4379
+ type: "select",
4380
+ currentValue: currentModeId,
4381
+ options: modeOptions,
4382
+ category: "mode",
4383
+ description: "Choose an approval and sandboxing preset for your session"
4384
+ },
4385
+ {
4386
+ id: "model",
4387
+ name: "Model",
4388
+ type: "select",
4389
+ currentValue: modelOptions.currentModelId,
4390
+ options: modelOptions.options,
4391
+ category: "model",
4392
+ description: "Choose which model Claude should use"
4393
+ }
4394
+ ];
3675
4395
  }
3676
- async emitConfigOptionsUpdate(sessionId) {
3677
- const configOptions = await this.buildConfigOptions();
3678
- const serialized = JSON.stringify(configOptions);
3679
- if (this.lastSentConfigOptions && JSON.stringify(this.lastSentConfigOptions) === serialized) {
3680
- return;
3681
- }
3682
- this.lastSentConfigOptions = configOptions;
4396
+ async sendAvailableCommandsUpdate() {
4397
+ const commands = await this.session.query.supportedCommands();
3683
4398
  await this.client.sessionUpdate({
3684
- sessionId: sessionId ?? this.sessionId,
4399
+ sessionId: this.sessionId,
3685
4400
  update: {
3686
- sessionUpdate: "config_option_update",
3687
- configOptions
4401
+ sessionUpdate: "available_commands_update",
4402
+ availableCommands: getAvailableSlashCommands(commands)
3688
4403
  }
3689
4404
  });
3690
4405
  }
3691
- checkAuthStatus() {
3692
- const backupExists = fs2.existsSync(
3693
- path3.resolve(os3.homedir(), ".claude.json.backup")
3694
- );
3695
- const configExists = fs2.existsSync(
3696
- path3.resolve(os3.homedir(), ".claude.json")
3697
- );
3698
- if (backupExists && !configExists) {
3699
- throw RequestError2.authRequired();
3700
- }
3701
- }
3702
- async trySetModel(q, modelId) {
3703
- try {
3704
- await this.setModelWithFallback(q, modelId);
3705
- } catch (err) {
3706
- this.logger.warn("Failed to set model", { modelId, error: err });
3707
- }
3708
- }
3709
- async setModelWithFallback(q, modelId) {
3710
- const sdkModelId = toSdkModelId(modelId);
4406
+ async replaySessionHistory(sessionId) {
3711
4407
  try {
3712
- await q.setModel(sdkModelId);
3713
- } catch (err) {
3714
- if (sdkModelId === modelId) {
3715
- throw err;
4408
+ const messages = await getSessionMessages(sessionId, {
4409
+ dir: this.session.cwd
4410
+ });
4411
+ const replayContext = {
4412
+ session: this.session,
4413
+ sessionId,
4414
+ client: this.client,
4415
+ toolUseCache: this.toolUseCache,
4416
+ fileContentCache: this.fileContentCache,
4417
+ logger: this.logger,
4418
+ registerHooks: false
4419
+ };
4420
+ for (const msg of messages) {
4421
+ const sdkMessage = {
4422
+ type: msg.type,
4423
+ message: msg.message,
4424
+ parent_tool_use_id: msg.parent_tool_use_id
4425
+ };
4426
+ await handleUserAssistantMessage(
4427
+ sdkMessage,
4428
+ replayContext
4429
+ );
3716
4430
  }
3717
- await q.setModel(modelId);
4431
+ } catch (err) {
4432
+ this.logger.warn("Failed to replay session history", {
4433
+ sessionId,
4434
+ error: err instanceof Error ? err.message : String(err)
4435
+ });
3718
4436
  }
3719
4437
  }
4438
+ // ================================
4439
+ // EXTENSION METHODS
4440
+ // ================================
3720
4441
  /**
3721
4442
  * Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
3722
4443
  * Both populate caches used later — neither is needed to return configOptions.
3723
4444
  */
3724
- deferBackgroundFetches(q, sessionId) {
4445
+ deferBackgroundFetches(q) {
3725
4446
  Promise.all([
3726
- getAvailableSlashCommands(q),
4447
+ new Promise((resolve4) => setTimeout(resolve4, 10)).then(
4448
+ () => this.sendAvailableCommandsUpdate()
4449
+ ),
3727
4450
  fetchMcpToolMetadata(q, this.logger)
3728
- ]).then(([slashCommands]) => {
3729
- this.sendAvailableCommandsUpdate(sessionId, slashCommands);
3730
- }).catch((err) => {
3731
- this.logger.warn("Failed to fetch deferred session data", { err });
3732
- });
3733
- }
3734
- sendAvailableCommandsUpdate(sessionId, availableCommands) {
3735
- setTimeout(() => {
3736
- this.client.sessionUpdate({
3737
- sessionId,
3738
- update: {
3739
- sessionUpdate: "available_commands_update",
3740
- availableCommands
3741
- }
3742
- });
3743
- }, 0);
4451
+ ]).catch(
4452
+ (err) => this.logger.error("Background fetch failed", { error: err })
4453
+ );
3744
4454
  }
3745
4455
  async broadcastUserMessage(params) {
3746
4456
  for (const chunk of params.prompt) {
@@ -3755,71 +4465,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3755
4465
  this.appendNotification(params.sessionId, notification);
3756
4466
  }
3757
4467
  }
3758
- async processMessages(sessionId) {
3759
- const context = {
3760
- session: this.session,
3761
- sessionId,
3762
- client: this.client,
3763
- toolUseCache: this.toolUseCache,
3764
- fileContentCache: this.fileContentCache,
3765
- logger: this.logger
3766
- };
3767
- while (true) {
3768
- const { value: message, done } = await this.session.query.next();
3769
- if (done || !message) {
3770
- return this.handleSessionEnd();
3771
- }
3772
- const response = await this.handleMessage(message, context);
3773
- if (response) {
3774
- return response;
3775
- }
3776
- }
3777
- }
3778
- handleSessionEnd() {
3779
- if (this.session.cancelled) {
3780
- return {
3781
- stopReason: "cancelled",
3782
- _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
3783
- };
3784
- }
3785
- throw new Error("Session did not end in result");
3786
- }
3787
- async handleMessage(message, context) {
3788
- switch (message.type) {
3789
- case "system":
3790
- await handleSystemMessage(message, context);
3791
- return null;
3792
- case "result": {
3793
- const result = handleResultMessage(message, context);
3794
- if (result.error) throw result.error;
3795
- if (result.shouldStop) {
3796
- return {
3797
- stopReason: result.stopReason
3798
- };
3799
- }
3800
- return null;
3801
- }
3802
- case "stream_event":
3803
- await handleStreamEvent(message, context);
3804
- return null;
3805
- case "user":
3806
- case "assistant": {
3807
- const result = await handleUserAssistantMessage(message, context);
3808
- if (result.error) throw result.error;
3809
- if (result.shouldStop) {
3810
- return { stopReason: "end_turn" };
3811
- }
3812
- return null;
3813
- }
3814
- case "tool_progress":
3815
- case "auth_status":
3816
- case "tool_use_summary":
3817
- return null;
3818
- default:
3819
- unreachable(message, this.logger);
3820
- return null;
3821
- }
3822
- }
3823
4468
  };
3824
4469
 
3825
4470
  // src/adapters/codex/spawn.ts
@@ -4446,8 +5091,8 @@ var PostHogAPIClient = class {
4446
5091
  };
4447
5092
 
4448
5093
  // src/session-log-writer.ts
4449
- import fs3 from "fs";
4450
- import path4 from "path";
5094
+ import fs4 from "fs";
5095
+ import path5 from "path";
4451
5096
  var SessionLogWriter = class _SessionLogWriter {
4452
5097
  static FLUSH_DEBOUNCE_MS = 500;
4453
5098
  static FLUSH_MAX_INTERVAL_MS = 5e3;
@@ -4459,7 +5104,6 @@ var SessionLogWriter = class _SessionLogWriter {
4459
5104
  lastFlushAttemptTime = /* @__PURE__ */ new Map();
4460
5105
  retryCounts = /* @__PURE__ */ new Map();
4461
5106
  sessions = /* @__PURE__ */ new Map();
4462
- messageCounts = /* @__PURE__ */ new Map();
4463
5107
  logger;
4464
5108
  localCachePath;
4465
5109
  constructor(options = {}) {
@@ -4469,19 +5113,6 @@ var SessionLogWriter = class _SessionLogWriter {
4469
5113
  }
4470
5114
  async flushAll() {
4471
5115
  const sessionIds = [...this.sessions.keys()];
4472
- const pendingCounts = sessionIds.map((id) => {
4473
- const session = this.sessions.get(id);
4474
- return {
4475
- taskId: session?.context.taskId,
4476
- runId: session?.context.runId,
4477
- pending: this.pendingEntries.get(id)?.length ?? 0,
4478
- messages: this.messageCounts.get(id) ?? 0
4479
- };
4480
- });
4481
- this.logger.info("flushAll called", {
4482
- sessions: sessionIds.length,
4483
- pending: pendingCounts
4484
- });
4485
5116
  const flushPromises = [];
4486
5117
  for (const sessionId of sessionIds) {
4487
5118
  flushPromises.push(this.flush(sessionId));
@@ -4499,13 +5130,13 @@ var SessionLogWriter = class _SessionLogWriter {
4499
5130
  this.sessions.set(sessionId, { context });
4500
5131
  this.lastFlushAttemptTime.set(sessionId, Date.now());
4501
5132
  if (this.localCachePath) {
4502
- const sessionDir = path4.join(
5133
+ const sessionDir = path5.join(
4503
5134
  this.localCachePath,
4504
5135
  "sessions",
4505
5136
  context.runId
4506
5137
  );
4507
5138
  try {
4508
- fs3.mkdirSync(sessionDir, { recursive: true });
5139
+ fs4.mkdirSync(sessionDir, { recursive: true });
4509
5140
  } catch (error) {
4510
5141
  this.logger.warn("Failed to create local cache directory", {
4511
5142
  sessionDir,
@@ -4525,15 +5156,6 @@ var SessionLogWriter = class _SessionLogWriter {
4525
5156
  });
4526
5157
  return;
4527
5158
  }
4528
- const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
4529
- this.messageCounts.set(sessionId, count);
4530
- if (count % 10 === 1) {
4531
- this.logger.info("Messages received", {
4532
- count,
4533
- taskId: session.context.taskId,
4534
- runId: session.context.runId
4535
- });
4536
- }
4537
5159
  try {
4538
5160
  const message = JSON.parse(line);
4539
5161
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -4582,12 +5204,6 @@ var SessionLogWriter = class _SessionLogWriter {
4582
5204
  this.emitCoalescedMessage(sessionId, session);
4583
5205
  const pending = this.pendingEntries.get(sessionId);
4584
5206
  if (!this.posthogAPI || !pending?.length) {
4585
- this.logger.info("flush: nothing to persist", {
4586
- taskId: session.context.taskId,
4587
- runId: session.context.runId,
4588
- hasPosthogAPI: !!this.posthogAPI,
4589
- pendingCount: pending?.length ?? 0
4590
- });
4591
5207
  return;
4592
5208
  }
4593
5209
  this.pendingEntries.delete(sessionId);
@@ -4604,11 +5220,6 @@ var SessionLogWriter = class _SessionLogWriter {
4604
5220
  pending
4605
5221
  );
4606
5222
  this.retryCounts.set(sessionId, 0);
4607
- this.logger.info("Flushed session logs", {
4608
- taskId: session.context.taskId,
4609
- runId: session.context.runId,
4610
- entryCount: pending.length
4611
- });
4612
5223
  } catch (error) {
4613
5224
  const retryCount = (this.retryCounts.get(sessionId) ?? 0) + 1;
4614
5225
  this.retryCounts.set(sessionId, retryCount);
@@ -4722,14 +5333,14 @@ var SessionLogWriter = class _SessionLogWriter {
4722
5333
  if (!this.localCachePath) return;
4723
5334
  const session = this.sessions.get(sessionId);
4724
5335
  if (!session) return;
4725
- const logPath = path4.join(
5336
+ const logPath = path5.join(
4726
5337
  this.localCachePath,
4727
5338
  "sessions",
4728
5339
  session.context.runId,
4729
5340
  "logs.ndjson"
4730
5341
  );
4731
5342
  try {
4732
- fs3.appendFileSync(logPath, `${JSON.stringify(entry)}
5343
+ fs4.appendFileSync(logPath, `${JSON.stringify(entry)}
4733
5344
  `);
4734
5345
  } catch (error) {
4735
5346
  this.logger.warn("Failed to write to local cache", {
@@ -4743,8 +5354,8 @@ var SessionLogWriter = class _SessionLogWriter {
4743
5354
  };
4744
5355
 
4745
5356
  // ../git/dist/queries.js
4746
- import * as fs5 from "fs/promises";
4747
- import * as path6 from "path";
5357
+ import * as fs6 from "fs/promises";
5358
+ import * as path7 from "path";
4748
5359
 
4749
5360
  // ../../node_modules/simple-git/dist/esm/index.js
4750
5361
  var import_file_exists = __toESM(require_dist(), 1);
@@ -4753,7 +5364,7 @@ var import_promise_deferred = __toESM(require_dist2(), 1);
4753
5364
  var import_promise_deferred2 = __toESM(require_dist2(), 1);
4754
5365
  import { Buffer as Buffer2 } from "buffer";
4755
5366
  import { spawn as spawn3 } from "child_process";
4756
- import { normalize } from "path";
5367
+ import { normalize as normalize2 } from "path";
4757
5368
  import { EventEmitter } from "events";
4758
5369
  var __defProp2 = Object.defineProperty;
4759
5370
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -4783,8 +5394,8 @@ function pathspec(...paths) {
4783
5394
  cache.set(key, paths);
4784
5395
  return key;
4785
5396
  }
4786
- function isPathSpec(path8) {
4787
- return path8 instanceof String && cache.has(path8);
5397
+ function isPathSpec(path9) {
5398
+ return path9 instanceof String && cache.has(path9);
4788
5399
  }
4789
5400
  function toPaths(pathSpec) {
4790
5401
  return cache.get(pathSpec) || [];
@@ -4873,8 +5484,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
4873
5484
  function forEachLineWithContent(input, callback) {
4874
5485
  return toLinesWithContent(input, true).map((line) => callback(line));
4875
5486
  }
4876
- function folderExists(path8) {
4877
- return (0, import_file_exists.exists)(path8, import_file_exists.FOLDER);
5487
+ function folderExists(path9) {
5488
+ return (0, import_file_exists.exists)(path9, import_file_exists.FOLDER);
4878
5489
  }
4879
5490
  function append(target, item) {
4880
5491
  if (Array.isArray(target)) {
@@ -5278,8 +5889,8 @@ function checkIsRepoRootTask() {
5278
5889
  commands,
5279
5890
  format: "utf-8",
5280
5891
  onError,
5281
- parser(path8) {
5282
- return /^\.(git)?$/.test(path8.trim());
5892
+ parser(path9) {
5893
+ return /^\.(git)?$/.test(path9.trim());
5283
5894
  }
5284
5895
  };
5285
5896
  }
@@ -5713,11 +6324,11 @@ function parseGrep(grep) {
5713
6324
  const paths = /* @__PURE__ */ new Set();
5714
6325
  const results = {};
5715
6326
  forEachLineWithContent(grep, (input) => {
5716
- const [path8, line, preview] = input.split(NULL);
5717
- paths.add(path8);
5718
- (results[path8] = results[path8] || []).push({
6327
+ const [path9, line, preview] = input.split(NULL);
6328
+ paths.add(path9);
6329
+ (results[path9] = results[path9] || []).push({
5719
6330
  line: asNumber(line),
5720
- path: path8,
6331
+ path: path9,
5721
6332
  preview
5722
6333
  });
5723
6334
  });
@@ -6482,14 +7093,14 @@ var init_hash_object = __esm({
6482
7093
  init_task();
6483
7094
  }
6484
7095
  });
6485
- function parseInit(bare, path8, text2) {
7096
+ function parseInit(bare, path9, text2) {
6486
7097
  const response = String(text2).trim();
6487
7098
  let result;
6488
7099
  if (result = initResponseRegex.exec(response)) {
6489
- return new InitSummary(bare, path8, false, result[1]);
7100
+ return new InitSummary(bare, path9, false, result[1]);
6490
7101
  }
6491
7102
  if (result = reInitResponseRegex.exec(response)) {
6492
- return new InitSummary(bare, path8, true, result[1]);
7103
+ return new InitSummary(bare, path9, true, result[1]);
6493
7104
  }
6494
7105
  let gitDir = "";
6495
7106
  const tokens = response.split(" ");
@@ -6500,7 +7111,7 @@ function parseInit(bare, path8, text2) {
6500
7111
  break;
6501
7112
  }
6502
7113
  }
6503
- return new InitSummary(bare, path8, /^re/i.test(response), gitDir);
7114
+ return new InitSummary(bare, path9, /^re/i.test(response), gitDir);
6504
7115
  }
6505
7116
  var InitSummary;
6506
7117
  var initResponseRegex;
@@ -6509,9 +7120,9 @@ var init_InitSummary = __esm({
6509
7120
  "src/lib/responses/InitSummary.ts"() {
6510
7121
  "use strict";
6511
7122
  InitSummary = class {
6512
- constructor(bare, path8, existing, gitDir) {
7123
+ constructor(bare, path9, existing, gitDir) {
6513
7124
  this.bare = bare;
6514
- this.path = path8;
7125
+ this.path = path9;
6515
7126
  this.existing = existing;
6516
7127
  this.gitDir = gitDir;
6517
7128
  }
@@ -6523,7 +7134,7 @@ var init_InitSummary = __esm({
6523
7134
  function hasBareCommand(command) {
6524
7135
  return command.includes(bareCommand);
6525
7136
  }
6526
- function initTask(bare = false, path8, customArgs) {
7137
+ function initTask(bare = false, path9, customArgs) {
6527
7138
  const commands = ["init", ...customArgs];
6528
7139
  if (bare && !hasBareCommand(commands)) {
6529
7140
  commands.splice(1, 0, bareCommand);
@@ -6532,7 +7143,7 @@ function initTask(bare = false, path8, customArgs) {
6532
7143
  commands,
6533
7144
  format: "utf-8",
6534
7145
  parser(text2) {
6535
- return parseInit(commands.includes("--bare"), path8, text2);
7146
+ return parseInit(commands.includes("--bare"), path9, text2);
6536
7147
  }
6537
7148
  };
6538
7149
  }
@@ -7348,12 +7959,12 @@ var init_FileStatusSummary = __esm({
7348
7959
  "use strict";
7349
7960
  fromPathRegex = /^(.+)\0(.+)$/;
7350
7961
  FileStatusSummary = class {
7351
- constructor(path8, index, working_dir) {
7352
- this.path = path8;
7962
+ constructor(path9, index, working_dir) {
7963
+ this.path = path9;
7353
7964
  this.index = index;
7354
7965
  this.working_dir = working_dir;
7355
7966
  if (index === "R" || working_dir === "R") {
7356
- const detail = fromPathRegex.exec(path8) || [null, path8, path8];
7967
+ const detail = fromPathRegex.exec(path9) || [null, path9, path9];
7357
7968
  this.from = detail[2] || "";
7358
7969
  this.path = detail[1] || "";
7359
7970
  }
@@ -7384,14 +7995,14 @@ function splitLine(result, lineStr) {
7384
7995
  default:
7385
7996
  return;
7386
7997
  }
7387
- function data(index, workingDir, path8) {
7998
+ function data(index, workingDir, path9) {
7388
7999
  const raw = `${index}${workingDir}`;
7389
8000
  const handler = parsers6.get(raw);
7390
8001
  if (handler) {
7391
- handler(result, path8);
8002
+ handler(result, path9);
7392
8003
  }
7393
8004
  if (raw !== "##" && raw !== "!!") {
7394
- result.files.push(new FileStatusSummary(path8, index, workingDir));
8005
+ result.files.push(new FileStatusSummary(path9, index, workingDir));
7395
8006
  }
7396
8007
  }
7397
8008
  }
@@ -7704,9 +8315,9 @@ var init_simple_git_api = __esm({
7704
8315
  next
7705
8316
  );
7706
8317
  }
7707
- hashObject(path8, write) {
8318
+ hashObject(path9, write) {
7708
8319
  return this._runTask(
7709
- hashObjectTask(path8, write === true),
8320
+ hashObjectTask(path9, write === true),
7710
8321
  trailingFunctionArgument(arguments)
7711
8322
  );
7712
8323
  }
@@ -8059,8 +8670,8 @@ var init_branch = __esm({
8059
8670
  }
8060
8671
  });
8061
8672
  function toPath(input) {
8062
- const path8 = input.trim().replace(/^["']|["']$/g, "");
8063
- return path8 && normalize(path8);
8673
+ const path9 = input.trim().replace(/^["']|["']$/g, "");
8674
+ return path9 && normalize2(path9);
8064
8675
  }
8065
8676
  var parseCheckIgnore;
8066
8677
  var init_CheckIgnore = __esm({
@@ -8374,8 +8985,8 @@ __export(sub_module_exports, {
8374
8985
  subModuleTask: () => subModuleTask,
8375
8986
  updateSubModuleTask: () => updateSubModuleTask
8376
8987
  });
8377
- function addSubModuleTask(repo, path8) {
8378
- return subModuleTask(["add", repo, path8]);
8988
+ function addSubModuleTask(repo, path9) {
8989
+ return subModuleTask(["add", repo, path9]);
8379
8990
  }
8380
8991
  function initSubModuleTask(customArgs) {
8381
8992
  return subModuleTask(["init", ...customArgs]);
@@ -8705,8 +9316,8 @@ var require_git = __commonJS2({
8705
9316
  }
8706
9317
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
8707
9318
  };
8708
- Git2.prototype.submoduleAdd = function(repo, path8, then) {
8709
- return this._runTask(addSubModuleTask2(repo, path8), trailingFunctionArgument2(arguments));
9319
+ Git2.prototype.submoduleAdd = function(repo, path9, then) {
9320
+ return this._runTask(addSubModuleTask2(repo, path9), trailingFunctionArgument2(arguments));
8710
9321
  };
8711
9322
  Git2.prototype.submoduleUpdate = function(args, then) {
8712
9323
  return this._runTask(
@@ -9307,22 +9918,22 @@ function createGitClient(baseDir, options) {
9307
9918
 
9308
9919
  // ../git/dist/lock-detector.js
9309
9920
  import { execFile } from "child_process";
9310
- import fs4 from "fs/promises";
9311
- import path5 from "path";
9921
+ import fs5 from "fs/promises";
9922
+ import path6 from "path";
9312
9923
  import { promisify } from "util";
9313
9924
  var execFileAsync = promisify(execFile);
9314
9925
  async function getIndexLockPath(repoPath) {
9315
9926
  try {
9316
9927
  const { stdout } = await execFileAsync("git", ["rev-parse", "--git-path", "index.lock"], { cwd: repoPath });
9317
- return path5.resolve(repoPath, stdout.trim());
9928
+ return path6.resolve(repoPath, stdout.trim());
9318
9929
  } catch {
9319
- return path5.join(repoPath, ".git", "index.lock");
9930
+ return path6.join(repoPath, ".git", "index.lock");
9320
9931
  }
9321
9932
  }
9322
9933
  async function getLockInfo(repoPath) {
9323
9934
  const lockPath = await getIndexLockPath(repoPath);
9324
9935
  try {
9325
- const stat = await fs4.stat(lockPath);
9936
+ const stat = await fs5.stat(lockPath);
9326
9937
  return {
9327
9938
  path: lockPath,
9328
9939
  ageMs: Date.now() - stat.mtimeMs
@@ -9333,7 +9944,7 @@ async function getLockInfo(repoPath) {
9333
9944
  }
9334
9945
  async function removeLock(repoPath) {
9335
9946
  const lockPath = await getIndexLockPath(repoPath);
9336
- await fs4.rm(lockPath, { force: true });
9947
+ await fs5.rm(lockPath, { force: true });
9337
9948
  }
9338
9949
  async function isLocked(repoPath) {
9339
9950
  return await getLockInfo(repoPath) !== null;
@@ -9500,7 +10111,7 @@ async function getHeadSha(baseDir, options) {
9500
10111
 
9501
10112
  // src/sagas/apply-snapshot-saga.ts
9502
10113
  import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
9503
- import { join as join5 } from "path";
10114
+ import { join as join6 } from "path";
9504
10115
 
9505
10116
  // ../shared/dist/index.js
9506
10117
  var consoleLogger = {
@@ -9631,8 +10242,8 @@ var Saga = class {
9631
10242
 
9632
10243
  // ../git/dist/sagas/tree.js
9633
10244
  import { existsSync as existsSync4 } from "fs";
9634
- import * as fs6 from "fs/promises";
9635
- import * as path7 from "path";
10245
+ import * as fs7 from "fs/promises";
10246
+ import * as path8 from "path";
9636
10247
  import * as tar from "tar";
9637
10248
 
9638
10249
  // ../git/dist/git-saga.js
@@ -9658,14 +10269,14 @@ var CaptureTreeSaga = class extends GitSaga {
9658
10269
  tempIndexPath = null;
9659
10270
  async executeGitOperations(input) {
9660
10271
  const { baseDir, lastTreeHash, archivePath, signal } = input;
9661
- const tmpDir = path7.join(baseDir, ".git", "twig-tmp");
10272
+ const tmpDir = path8.join(baseDir, ".git", "twig-tmp");
9662
10273
  await this.step({
9663
10274
  name: "create_tmp_dir",
9664
- execute: () => fs6.mkdir(tmpDir, { recursive: true }),
10275
+ execute: () => fs7.mkdir(tmpDir, { recursive: true }),
9665
10276
  rollback: async () => {
9666
10277
  }
9667
10278
  });
9668
- this.tempIndexPath = path7.join(tmpDir, `index-${Date.now()}`);
10279
+ this.tempIndexPath = path8.join(tmpDir, `index-${Date.now()}`);
9669
10280
  const tempIndexGit = this.git.env({
9670
10281
  ...process.env,
9671
10282
  GIT_INDEX_FILE: this.tempIndexPath
@@ -9675,7 +10286,7 @@ var CaptureTreeSaga = class extends GitSaga {
9675
10286
  execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
9676
10287
  rollback: async () => {
9677
10288
  if (this.tempIndexPath) {
9678
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10289
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9679
10290
  });
9680
10291
  }
9681
10292
  }
@@ -9684,7 +10295,7 @@ var CaptureTreeSaga = class extends GitSaga {
9684
10295
  const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
9685
10296
  if (lastTreeHash && treeHash === lastTreeHash) {
9686
10297
  this.log.debug("No changes since last capture", { treeHash });
9687
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10298
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9688
10299
  });
9689
10300
  return { snapshot: null, changed: false };
9690
10301
  }
@@ -9696,7 +10307,7 @@ var CaptureTreeSaga = class extends GitSaga {
9696
10307
  }
9697
10308
  });
9698
10309
  const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
9699
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10310
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9700
10311
  });
9701
10312
  const snapshot = {
9702
10313
  treeHash,
@@ -9720,15 +10331,15 @@ var CaptureTreeSaga = class extends GitSaga {
9720
10331
  if (filesToArchive.length === 0) {
9721
10332
  return void 0;
9722
10333
  }
9723
- const existingFiles = filesToArchive.filter((f) => existsSync4(path7.join(baseDir, f)));
10334
+ const existingFiles = filesToArchive.filter((f) => existsSync4(path8.join(baseDir, f)));
9724
10335
  if (existingFiles.length === 0) {
9725
10336
  return void 0;
9726
10337
  }
9727
10338
  await this.step({
9728
10339
  name: "create_archive",
9729
10340
  execute: async () => {
9730
- const archiveDir = path7.dirname(archivePath);
9731
- await fs6.mkdir(archiveDir, { recursive: true });
10341
+ const archiveDir = path8.dirname(archivePath);
10342
+ await fs7.mkdir(archiveDir, { recursive: true });
9732
10343
  await tar.create({
9733
10344
  gzip: true,
9734
10345
  file: archivePath,
@@ -9736,7 +10347,7 @@ var CaptureTreeSaga = class extends GitSaga {
9736
10347
  }, existingFiles);
9737
10348
  },
9738
10349
  rollback: async () => {
9739
- await fs6.rm(archivePath, { force: true }).catch(() => {
10350
+ await fs7.rm(archivePath, { force: true }).catch(() => {
9740
10351
  });
9741
10352
  }
9742
10353
  });
@@ -9835,9 +10446,9 @@ var ApplyTreeSaga = class extends GitSaga {
9835
10446
  const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
9836
10447
  await this.readOnlyStep("backup_existing_files", async () => {
9837
10448
  for (const filePath of filesToExtract) {
9838
- const fullPath = path7.join(baseDir, filePath);
10449
+ const fullPath = path8.join(baseDir, filePath);
9839
10450
  try {
9840
- const content = await fs6.readFile(fullPath);
10451
+ const content = await fs7.readFile(fullPath);
9841
10452
  this.fileBackups.set(filePath, content);
9842
10453
  } catch {
9843
10454
  }
@@ -9854,16 +10465,16 @@ var ApplyTreeSaga = class extends GitSaga {
9854
10465
  },
9855
10466
  rollback: async () => {
9856
10467
  for (const filePath of this.extractedFiles) {
9857
- const fullPath = path7.join(baseDir, filePath);
10468
+ const fullPath = path8.join(baseDir, filePath);
9858
10469
  const backup = this.fileBackups.get(filePath);
9859
10470
  if (backup) {
9860
- const dir = path7.dirname(fullPath);
9861
- await fs6.mkdir(dir, { recursive: true }).catch(() => {
10471
+ const dir = path8.dirname(fullPath);
10472
+ await fs7.mkdir(dir, { recursive: true }).catch(() => {
9862
10473
  });
9863
- await fs6.writeFile(fullPath, backup).catch(() => {
10474
+ await fs7.writeFile(fullPath, backup).catch(() => {
9864
10475
  });
9865
10476
  } else {
9866
- await fs6.rm(fullPath, { force: true }).catch(() => {
10477
+ await fs7.rm(fullPath, { force: true }).catch(() => {
9867
10478
  });
9868
10479
  }
9869
10480
  }
@@ -9871,10 +10482,10 @@ var ApplyTreeSaga = class extends GitSaga {
9871
10482
  });
9872
10483
  }
9873
10484
  for (const change of changes.filter((c) => c.status === "D")) {
9874
- const fullPath = path7.join(baseDir, change.path);
10485
+ const fullPath = path8.join(baseDir, change.path);
9875
10486
  const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
9876
10487
  try {
9877
- return await fs6.readFile(fullPath);
10488
+ return await fs7.readFile(fullPath);
9878
10489
  } catch {
9879
10490
  return null;
9880
10491
  }
@@ -9882,15 +10493,15 @@ var ApplyTreeSaga = class extends GitSaga {
9882
10493
  await this.step({
9883
10494
  name: `delete_${change.path}`,
9884
10495
  execute: async () => {
9885
- await fs6.rm(fullPath, { force: true });
10496
+ await fs7.rm(fullPath, { force: true });
9886
10497
  this.log.debug(`Deleted file: ${change.path}`);
9887
10498
  },
9888
10499
  rollback: async () => {
9889
10500
  if (backupContent) {
9890
- const dir = path7.dirname(fullPath);
9891
- await fs6.mkdir(dir, { recursive: true }).catch(() => {
10501
+ const dir = path8.dirname(fullPath);
10502
+ await fs7.mkdir(dir, { recursive: true }).catch(() => {
9892
10503
  });
9893
- await fs6.writeFile(fullPath, backupContent).catch(() => {
10504
+ await fs7.writeFile(fullPath, backupContent).catch(() => {
9894
10505
  });
9895
10506
  }
9896
10507
  }
@@ -9912,7 +10523,7 @@ var ApplySnapshotSaga = class extends Saga {
9912
10523
  archivePath = null;
9913
10524
  async execute(input) {
9914
10525
  const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
9915
- const tmpDir = join5(repositoryPath, ".posthog", "tmp");
10526
+ const tmpDir = join6(repositoryPath, ".posthog", "tmp");
9916
10527
  if (!snapshot.archiveUrl) {
9917
10528
  throw new Error("Cannot apply snapshot: no archive URL");
9918
10529
  }
@@ -9923,7 +10534,7 @@ var ApplySnapshotSaga = class extends Saga {
9923
10534
  rollback: async () => {
9924
10535
  }
9925
10536
  });
9926
- const archivePath = join5(tmpDir, `${snapshot.treeHash}.tar.gz`);
10537
+ const archivePath = join6(tmpDir, `${snapshot.treeHash}.tar.gz`);
9927
10538
  this.archivePath = archivePath;
9928
10539
  await this.step({
9929
10540
  name: "download_archive",
@@ -9972,7 +10583,7 @@ var ApplySnapshotSaga = class extends Saga {
9972
10583
  // src/sagas/capture-tree-saga.ts
9973
10584
  import { existsSync as existsSync5 } from "fs";
9974
10585
  import { readFile as readFile3, rm as rm4 } from "fs/promises";
9975
- import { join as join6 } from "path";
10586
+ import { join as join7 } from "path";
9976
10587
  var CaptureTreeSaga2 = class extends Saga {
9977
10588
  async execute(input) {
9978
10589
  const {
@@ -9983,14 +10594,14 @@ var CaptureTreeSaga2 = class extends Saga {
9983
10594
  taskId,
9984
10595
  runId
9985
10596
  } = input;
9986
- const tmpDir = join6(repositoryPath, ".posthog", "tmp");
9987
- if (existsSync5(join6(repositoryPath, ".gitmodules"))) {
10597
+ const tmpDir = join7(repositoryPath, ".posthog", "tmp");
10598
+ if (existsSync5(join7(repositoryPath, ".gitmodules"))) {
9988
10599
  this.log.warn(
9989
10600
  "Repository has submodules - snapshot may not capture submodule state"
9990
10601
  );
9991
10602
  }
9992
10603
  const shouldArchive = !!apiClient;
9993
- const archivePath = shouldArchive ? join6(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
10604
+ const archivePath = shouldArchive ? join7(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
9994
10605
  const gitCaptureSaga = new CaptureTreeSaga(this.log);
9995
10606
  const captureResult = await gitCaptureSaga.run({
9996
10607
  baseDir: repositoryPath,