@posthog/agent 2.1.131 → 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 (32) 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 -606
  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 +1300 -655
  13. package/dist/server/agent-server.js.map +1 -1
  14. package/dist/server/bin.cjs +1278 -635
  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/utils/common.ts +1 -1
@@ -509,7 +509,7 @@ var require_has_flag = __commonJS({
509
509
  var require_supports_color = __commonJS({
510
510
  "../../node_modules/supports-color/index.js"(exports2, module2) {
511
511
  "use strict";
512
- var os4 = require("os");
512
+ var os5 = require("os");
513
513
  var tty = require("tty");
514
514
  var hasFlag = require_has_flag();
515
515
  var { env } = process;
@@ -557,7 +557,7 @@ var require_supports_color = __commonJS({
557
557
  return min;
558
558
  }
559
559
  if (process.platform === "win32") {
560
- const osRelease = os4.release().split(".");
560
+ const osRelease = os5.release().split(".");
561
561
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
562
562
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
563
563
  }
@@ -805,10 +805,10 @@ var require_src2 = __commonJS({
805
805
  var fs_1 = require("fs");
806
806
  var debug_1 = __importDefault(require_src());
807
807
  var log = debug_1.default("@kwsites/file-exists");
808
- function check(path8, isFile, isDirectory) {
809
- log(`checking %s`, path8);
808
+ function check(path9, isFile, isDirectory) {
809
+ log(`checking %s`, path9);
810
810
  try {
811
- const stat = fs_1.statSync(path8);
811
+ const stat = fs_1.statSync(path9);
812
812
  if (stat.isFile() && isFile) {
813
813
  log(`[OK] path represents a file`);
814
814
  return true;
@@ -828,8 +828,8 @@ var require_src2 = __commonJS({
828
828
  throw e;
829
829
  }
830
830
  }
831
- function exists2(path8, type = exports2.READABLE) {
832
- return check(path8, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
831
+ function exists2(path9, type = exports2.READABLE) {
832
+ return check(path9, (type & exports2.FILE) > 0, (type & exports2.FOLDER) > 0);
833
833
  }
834
834
  exports2.exists = exists2;
835
835
  exports2.FILE = 1;
@@ -904,7 +904,7 @@ var import_hono = require("hono");
904
904
  // package.json
905
905
  var package_default = {
906
906
  name: "@posthog/agent",
907
- version: "2.1.131",
907
+ version: "2.1.137",
908
908
  repository: "https://github.com/PostHog/twig",
909
909
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
910
910
  exports: {
@@ -980,7 +980,6 @@ var package_default = {
980
980
  "@twig/git": "workspace:*",
981
981
  "@types/bun": "latest",
982
982
  "@types/tar": "^6.1.13",
983
- minimatch: "^10.0.3",
984
983
  msw: "^2.12.7",
985
984
  tsup: "^8.5.1",
986
985
  tsx: "^4.20.6",
@@ -1001,6 +1000,7 @@ var package_default = {
1001
1000
  commander: "^14.0.2",
1002
1001
  hono: "^4.11.7",
1003
1002
  jsonwebtoken: "^9.0.2",
1003
+ minimatch: "^10.0.3",
1004
1004
  tar: "^7.5.0",
1005
1005
  uuid: "13.0.0",
1006
1006
  "yoga-wasm-web": "^0.3.3",
@@ -1281,9 +1281,10 @@ function nodeWritableToWebWritable(nodeStream) {
1281
1281
  }
1282
1282
 
1283
1283
  // src/adapters/claude/claude-agent.ts
1284
- var fs2 = __toESM(require("fs"), 1);
1285
- var os3 = __toESM(require("os"), 1);
1286
- var path3 = __toESM(require("path"), 1);
1284
+ var import_node_crypto = require("crypto");
1285
+ var fs3 = __toESM(require("fs"), 1);
1286
+ var os4 = __toESM(require("os"), 1);
1287
+ var path4 = __toESM(require("path"), 1);
1287
1288
  var import_sdk2 = require("@agentclientprotocol/sdk");
1288
1289
  var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
1289
1290
  var import_uuid = require("uuid");
@@ -1305,7 +1306,7 @@ function unreachable(value, logger) {
1305
1306
  try {
1306
1307
  valueAsString = JSON.stringify(value);
1307
1308
  } catch {
1308
- valueAsString = value;
1309
+ valueAsString = String(value);
1309
1310
  }
1310
1311
  logger.error(`Unexpected case: ${valueAsString}`);
1311
1312
  }
@@ -1377,19 +1378,20 @@ var BaseAcpAgent = class {
1377
1378
  }
1378
1379
  async cancel(params) {
1379
1380
  if (this.sessionId !== params.sessionId) {
1380
- throw new Error("Session not found");
1381
+ throw new Error("Session ID mismatch");
1381
1382
  }
1382
1383
  this.session.cancelled = true;
1383
1384
  const meta = params._meta;
1384
1385
  if (meta?.interruptReason) {
1385
1386
  this.session.interruptReason = meta.interruptReason;
1386
1387
  }
1387
- await this.interruptSession();
1388
+ await this.interrupt();
1388
1389
  }
1389
1390
  async closeSession() {
1390
1391
  try {
1391
1392
  this.session.abortController.abort();
1392
1393
  await this.cancel({ sessionId: this.sessionId });
1394
+ this.session.settingsManager.dispose();
1393
1395
  this.logger.info("Closed session", { sessionId: this.sessionId });
1394
1396
  } catch (err) {
1395
1397
  this.logger.warn("Failed to close session", {
@@ -1564,8 +1566,8 @@ var ToolContentBuilder = class {
1564
1566
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
1565
1567
  return this;
1566
1568
  }
1567
- diff(path8, oldText, newText) {
1568
- this.items.push({ type: "diff", path: path8, oldText, newText });
1569
+ diff(path9, oldText, newText) {
1570
+ this.items.push({ type: "diff", path: path9, oldText, newText });
1569
1571
  return this;
1570
1572
  }
1571
1573
  build() {
@@ -1585,7 +1587,7 @@ var registerHookCallback = (toolUseID, {
1585
1587
  onPostToolUseHook
1586
1588
  };
1587
1589
  };
1588
- var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
1590
+ var createPostToolUseHook = ({ onModeChange, logger }) => async (input, toolUseID) => {
1589
1591
  if (input.hook_event_name === "PostToolUse") {
1590
1592
  const toolName = input.tool_name;
1591
1593
  if (onModeChange && toolName === "EnterPlanMode") {
@@ -1600,11 +1602,54 @@ var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
1600
1602
  input.tool_response
1601
1603
  );
1602
1604
  delete toolUseCallbacks[toolUseID];
1605
+ } else {
1606
+ logger?.error(
1607
+ `No onPostToolUseHook found for tool use ID: ${toolUseID}`
1608
+ );
1609
+ delete toolUseCallbacks[toolUseID];
1603
1610
  }
1604
1611
  }
1605
1612
  }
1606
1613
  return { continue: true };
1607
1614
  };
1615
+ var createPreToolUseHook = (settingsManager, logger) => async (input, _toolUseID) => {
1616
+ if (input.hook_event_name !== "PreToolUse") {
1617
+ return { continue: true };
1618
+ }
1619
+ const toolName = input.tool_name;
1620
+ const toolInput = input.tool_input;
1621
+ const permissionCheck = settingsManager.checkPermission(
1622
+ toolName,
1623
+ toolInput
1624
+ );
1625
+ if (permissionCheck.decision !== "ask") {
1626
+ logger.info(
1627
+ `[PreToolUseHook] Tool: ${toolName}, Decision: ${permissionCheck.decision}, Rule: ${permissionCheck.rule}`
1628
+ );
1629
+ }
1630
+ switch (permissionCheck.decision) {
1631
+ case "allow":
1632
+ return {
1633
+ continue: true,
1634
+ hookSpecificOutput: {
1635
+ hookEventName: "PreToolUse",
1636
+ permissionDecision: "allow",
1637
+ permissionDecisionReason: `Allowed by settings rule: ${permissionCheck.rule}`
1638
+ }
1639
+ };
1640
+ case "deny":
1641
+ return {
1642
+ continue: true,
1643
+ hookSpecificOutput: {
1644
+ hookEventName: "PreToolUse",
1645
+ permissionDecision: "deny",
1646
+ permissionDecisionReason: `Denied by settings rule: ${permissionCheck.rule}`
1647
+ }
1648
+ };
1649
+ default:
1650
+ return { continue: true };
1651
+ }
1652
+ };
1608
1653
 
1609
1654
  // src/adapters/claude/mcp/tool-metadata.ts
1610
1655
  var mcpToolMetadataCache = /* @__PURE__ */ new Map();
@@ -1681,79 +1726,7 @@ var SYSTEM_REMINDER = `
1681
1726
  <system-reminder>
1682
1727
  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.
1683
1728
  </system-reminder>`;
1684
- function replaceAndCalculateLocation(fileContent, edits) {
1685
- let currentContent = fileContent;
1686
- const randomHex = Array.from(crypto.getRandomValues(new Uint8Array(5))).map((b) => b.toString(16).padStart(2, "0")).join("");
1687
- const markerPrefix = `__REPLACE_MARKER_${randomHex}_`;
1688
- let markerCounter = 0;
1689
- const markers = [];
1690
- for (const edit of edits) {
1691
- if (edit.oldText === "") {
1692
- throw new Error(
1693
- `The provided \`old_string\` is empty.
1694
-
1695
- No edits were applied.`
1696
- );
1697
- }
1698
- if (edit.replaceAll) {
1699
- const parts = [];
1700
- let lastIndex = 0;
1701
- let searchIndex = 0;
1702
- while (true) {
1703
- const index = currentContent.indexOf(edit.oldText, searchIndex);
1704
- if (index === -1) {
1705
- if (searchIndex === 0) {
1706
- throw new Error(
1707
- `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
1708
-
1709
- No edits were applied.`
1710
- );
1711
- }
1712
- break;
1713
- }
1714
- parts.push(currentContent.substring(lastIndex, index));
1715
- const marker = `${markerPrefix}${markerCounter++}__`;
1716
- markers.push(marker);
1717
- parts.push(marker + edit.newText);
1718
- lastIndex = index + edit.oldText.length;
1719
- searchIndex = lastIndex;
1720
- }
1721
- parts.push(currentContent.substring(lastIndex));
1722
- currentContent = parts.join("");
1723
- } else {
1724
- const index = currentContent.indexOf(edit.oldText);
1725
- if (index === -1) {
1726
- throw new Error(
1727
- `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
1728
-
1729
- No edits were applied.`
1730
- );
1731
- } else {
1732
- const marker = `${markerPrefix}${markerCounter++}__`;
1733
- markers.push(marker);
1734
- currentContent = currentContent.substring(0, index) + marker + edit.newText + currentContent.substring(index + edit.oldText.length);
1735
- }
1736
- }
1737
- }
1738
- const lineNumbers = [];
1739
- for (const marker of markers) {
1740
- const index = currentContent.indexOf(marker);
1741
- if (index !== -1) {
1742
- const lineNumber = Math.max(
1743
- 0,
1744
- currentContent.substring(0, index).split(/\r\n|\r|\n/).length - 1
1745
- );
1746
- lineNumbers.push(lineNumber);
1747
- }
1748
- }
1749
- let finalContent = currentContent;
1750
- for (const marker of markers) {
1751
- finalContent = finalContent.replace(marker, "");
1752
- }
1753
- const uniqueLineNumbers = [...new Set(lineNumbers)].sort();
1754
- return { newContent: finalContent, lineNumbers: uniqueLineNumbers };
1755
- }
1756
- function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ debug: false, prefix: "[ClaudeTools]" })) {
1729
+ function toolInfoFromToolUse(toolUse, options) {
1757
1730
  const name = toolUse.name;
1758
1731
  const input = toolUse.input;
1759
1732
  switch (name) {
@@ -1778,6 +1751,13 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1778
1751
  locations: input?.notebook_path ? [{ path: String(input.notebook_path) }] : []
1779
1752
  };
1780
1753
  case "Bash":
1754
+ if (options?.supportsTerminalOutput && options?.toolUseId) {
1755
+ return {
1756
+ title: input?.description ? String(input.description) : "Execute command",
1757
+ kind: "execute",
1758
+ content: [{ type: "terminal", terminalId: options.toolUseId }]
1759
+ };
1760
+ }
1781
1761
  return {
1782
1762
  title: input?.description ? String(input.description) : "Execute command",
1783
1763
  kind: "execute",
@@ -1798,11 +1778,11 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1798
1778
  case "Read": {
1799
1779
  let limit = "";
1800
1780
  const inputLimit = input?.limit;
1801
- const inputOffset = input?.offset ?? 0;
1781
+ const inputOffset = input?.offset ?? 1;
1802
1782
  if (inputLimit) {
1803
- limit = ` (${inputOffset + 1} - ${inputOffset + inputLimit})`;
1804
- } else if (inputOffset) {
1805
- limit = ` (from line ${inputOffset + 1})`;
1783
+ limit = ` (${inputOffset} - ${inputOffset + inputLimit - 1})`;
1784
+ } else if (inputOffset > 1) {
1785
+ limit = ` (from line ${inputOffset})`;
1806
1786
  }
1807
1787
  return {
1808
1788
  title: `Read ${input?.file_path ? String(input.file_path) : "File"}${limit}`,
@@ -1824,39 +1804,21 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1824
1804
  locations: []
1825
1805
  };
1826
1806
  case "Edit": {
1827
- const path8 = input?.file_path ? String(input.file_path) : void 0;
1828
- let oldText = input?.old_string ? String(input.old_string) : null;
1829
- let newText = input?.new_string ? String(input.new_string) : "";
1830
- let affectedLines = [];
1831
- if (path8 && oldText) {
1832
- try {
1833
- const oldContent = cachedFileContent[path8] || "";
1834
- const newContent = replaceAndCalculateLocation(oldContent, [
1835
- {
1836
- oldText,
1837
- newText,
1838
- replaceAll: false
1839
- }
1840
- ]);
1841
- oldText = oldContent;
1842
- newText = newContent.newContent;
1843
- affectedLines = newContent.lineNumbers;
1844
- } catch (e) {
1845
- logger.error("Failed to edit file", e);
1846
- }
1847
- }
1807
+ const path9 = input?.file_path ? String(input.file_path) : void 0;
1808
+ const oldText = input?.old_string ? String(input.old_string) : null;
1809
+ const newText = input?.new_string ? String(input.new_string) : "";
1848
1810
  return {
1849
- title: path8 ? `Edit \`${path8}\`` : "Edit",
1811
+ title: path9 ? `Edit \`${path9}\`` : "Edit",
1850
1812
  kind: "edit",
1851
- content: input && path8 ? [
1813
+ content: input && path9 ? [
1852
1814
  {
1853
1815
  type: "diff",
1854
- path: path8,
1816
+ path: path9,
1855
1817
  oldText,
1856
1818
  newText
1857
1819
  }
1858
1820
  ] : [],
1859
- locations: path8 ? affectedLines.length > 0 ? affectedLines.map((line) => ({ line, path: path8 })) : [{ path: path8 }] : []
1821
+ locations: path9 ? [{ path: path9 }] : []
1860
1822
  };
1861
1823
  }
1862
1824
  case "Write": {
@@ -1910,10 +1872,10 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1910
1872
  }
1911
1873
  if (input?.output_mode) {
1912
1874
  switch (input.output_mode) {
1913
- case "FilesWithMatches":
1875
+ case "files_with_matches":
1914
1876
  label += " -l";
1915
1877
  break;
1916
- case "Count":
1878
+ case "count":
1917
1879
  label += " -c";
1918
1880
  break;
1919
1881
  default:
@@ -1932,7 +1894,9 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1932
1894
  if (input?.multiline) {
1933
1895
  label += " -P";
1934
1896
  }
1935
- label += ` "${input?.pattern ? String(input.pattern) : ""}"`;
1897
+ if (input?.pattern) {
1898
+ label += ` "${String(input.pattern)}"`;
1899
+ }
1936
1900
  if (input?.path) {
1937
1901
  label += ` ${String(input.path)}`;
1938
1902
  }
@@ -2026,7 +1990,49 @@ function mcpToolInfo(name, _input) {
2026
1990
  content: []
2027
1991
  };
2028
1992
  }
2029
- function toolUpdateFromToolResult(toolResult, toolUse) {
1993
+ function toolUpdateFromEditToolResponse(toolResponse) {
1994
+ if (!toolResponse || typeof toolResponse !== "object") return null;
1995
+ const response = toolResponse;
1996
+ const patches = response.structuredPatch;
1997
+ if (!Array.isArray(patches) || patches.length === 0) return null;
1998
+ const content = [];
1999
+ const locations = [];
2000
+ for (const patch of patches) {
2001
+ if (!patch.hunks || patch.hunks.length === 0) continue;
2002
+ const filePath = patch.newFileName || patch.oldFileName;
2003
+ const oldLines = [];
2004
+ const newLines = [];
2005
+ for (const hunk of patch.hunks) {
2006
+ for (const line of hunk.lines) {
2007
+ if (line.startsWith("-")) {
2008
+ oldLines.push(line.slice(1));
2009
+ } else if (line.startsWith("+")) {
2010
+ newLines.push(line.slice(1));
2011
+ } else if (line.startsWith(" ")) {
2012
+ oldLines.push(line.slice(1));
2013
+ newLines.push(line.slice(1));
2014
+ }
2015
+ }
2016
+ }
2017
+ content.push({
2018
+ type: "diff",
2019
+ path: filePath,
2020
+ oldText: oldLines.join("\n"),
2021
+ newText: newLines.join("\n")
2022
+ });
2023
+ const firstHunk = patch.hunks[0];
2024
+ locations.push({
2025
+ path: filePath,
2026
+ line: firstHunk.newStart
2027
+ });
2028
+ }
2029
+ if (content.length === 0) return null;
2030
+ return { content, locations };
2031
+ }
2032
+ function toolUpdateFromToolResult(toolResult, toolUse, options) {
2033
+ if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
2034
+ return toAcpContentUpdate(toolResult.content, true);
2035
+ }
2030
2036
  switch (toolUse?.name) {
2031
2037
  case "Read":
2032
2038
  if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
@@ -2043,6 +2049,16 @@ function toolUpdateFromToolResult(toolResult, toolUse) {
2043
2049
  )
2044
2050
  };
2045
2051
  }
2052
+ if (itemObj.type === "image" && itemObj.source) {
2053
+ return {
2054
+ type: "content",
2055
+ content: {
2056
+ type: "image",
2057
+ data: itemObj.source.data ?? "",
2058
+ mimeType: itemObj.source.media_type ?? "image/png"
2059
+ }
2060
+ };
2061
+ }
2046
2062
  return {
2047
2063
  type: "content",
2048
2064
  content: item
@@ -2058,18 +2074,51 @@ function toolUpdateFromToolResult(toolResult, toolUse) {
2058
2074
  }
2059
2075
  return {};
2060
2076
  case "Bash": {
2061
- return toAcpContentUpdate(
2062
- toolResult.content,
2063
- "is_error" in toolResult ? toolResult.is_error : false
2064
- );
2065
- }
2066
- case "Edit":
2067
- case "Write": {
2068
- if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
2069
- return toAcpContentUpdate(toolResult.content, true);
2077
+ const result = toolResult.content;
2078
+ const terminalId = "tool_use_id" in toolResult ? String(toolResult.tool_use_id) : "";
2079
+ const isError = "is_error" in toolResult && toolResult.is_error;
2080
+ let output = "";
2081
+ let exitCode = isError ? 1 : 0;
2082
+ if (result && typeof result === "object" && "type" in result && result.type === "bash_code_execution_result") {
2083
+ const bashResult = result;
2084
+ output = [bashResult.stdout, bashResult.stderr].filter(Boolean).join("\n");
2085
+ exitCode = bashResult.return_code;
2086
+ } else if (typeof result === "string") {
2087
+ output = result;
2088
+ } else if (Array.isArray(result) && result.length > 0 && "text" in result[0] && typeof result[0].text === "string") {
2089
+ output = result.map((c) => c.text ?? "").join("\n");
2090
+ }
2091
+ if (options?.supportsTerminalOutput) {
2092
+ return {
2093
+ content: [{ type: "terminal", terminalId }],
2094
+ _meta: {
2095
+ terminal_info: {
2096
+ terminal_id: terminalId
2097
+ },
2098
+ terminal_output: {
2099
+ terminal_id: terminalId,
2100
+ data: output
2101
+ },
2102
+ terminal_exit: {
2103
+ terminal_id: terminalId,
2104
+ exit_code: exitCode,
2105
+ signal: null
2106
+ }
2107
+ }
2108
+ };
2109
+ }
2110
+ if (output.trim()) {
2111
+ return {
2112
+ content: toolContent().text(`\`\`\`console
2113
+ ${output.trimEnd()}
2114
+ \`\`\``).build()
2115
+ };
2070
2116
  }
2071
2117
  return {};
2072
2118
  }
2119
+ case "Edit":
2120
+ case "Write":
2121
+ return {};
2073
2122
  case "ExitPlanMode": {
2074
2123
  return { title: "Exited Plan Mode" };
2075
2124
  }
@@ -2229,6 +2278,7 @@ function handleThinkingChunk(chunk, parentToolCallId) {
2229
2278
  return update;
2230
2279
  }
2231
2280
  function handleToolUseChunk(chunk, ctx) {
2281
+ const alreadyCached = chunk.id in ctx.toolUseCache;
2232
2282
  ctx.toolUseCache[chunk.id] = chunk;
2233
2283
  if (chunk.name === "TodoWrite") {
2234
2284
  const input = chunk.input;
@@ -2240,37 +2290,60 @@ function handleToolUseChunk(chunk, ctx) {
2240
2290
  }
2241
2291
  return null;
2242
2292
  }
2243
- registerHookCallback(chunk.id, {
2244
- onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
2245
- const toolUse = ctx.toolUseCache[toolUseId];
2246
- if (toolUse) {
2247
- await ctx.client.sessionUpdate({
2248
- sessionId: ctx.sessionId,
2249
- update: {
2250
- _meta: toolMeta(toolUse.name, toolResponse, ctx.parentToolCallId),
2251
- toolCallId: toolUseId,
2252
- sessionUpdate: "tool_call_update"
2253
- }
2254
- });
2255
- } else {
2256
- ctx.logger.error(
2257
- `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
2258
- );
2293
+ if (!alreadyCached && ctx.registerHooks !== false) {
2294
+ registerHookCallback(chunk.id, {
2295
+ onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
2296
+ const toolUse = ctx.toolUseCache[toolUseId];
2297
+ if (toolUse) {
2298
+ const editUpdate = toolUse.name === "Edit" ? toolUpdateFromEditToolResponse(toolResponse) : null;
2299
+ await ctx.client.sessionUpdate({
2300
+ sessionId: ctx.sessionId,
2301
+ update: {
2302
+ _meta: toolMeta(toolUse.name, toolResponse, ctx.parentToolCallId),
2303
+ toolCallId: toolUseId,
2304
+ sessionUpdate: "tool_call_update",
2305
+ ...editUpdate ? editUpdate : {}
2306
+ }
2307
+ });
2308
+ } else {
2309
+ ctx.logger.error(
2310
+ `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
2311
+ );
2312
+ }
2259
2313
  }
2260
- }
2261
- });
2314
+ });
2315
+ }
2262
2316
  let rawInput;
2263
2317
  try {
2264
2318
  rawInput = JSON.parse(JSON.stringify(chunk.input));
2265
2319
  } catch {
2266
2320
  }
2321
+ const toolInfo = toolInfoFromToolUse(chunk, {
2322
+ supportsTerminalOutput: ctx.supportsTerminalOutput,
2323
+ toolUseId: chunk.id
2324
+ });
2325
+ const meta = {
2326
+ ...toolMeta(chunk.name, void 0, ctx.parentToolCallId)
2327
+ };
2328
+ if (chunk.name === "Bash" && ctx.supportsTerminalOutput && !alreadyCached) {
2329
+ meta.terminal_info = { terminal_id: chunk.id };
2330
+ }
2331
+ if (alreadyCached) {
2332
+ return {
2333
+ _meta: meta,
2334
+ toolCallId: chunk.id,
2335
+ sessionUpdate: "tool_call_update",
2336
+ rawInput,
2337
+ ...toolInfo
2338
+ };
2339
+ }
2267
2340
  return {
2268
- _meta: toolMeta(chunk.name, void 0, ctx.parentToolCallId),
2341
+ _meta: meta,
2269
2342
  toolCallId: chunk.id,
2270
2343
  sessionUpdate: "tool_call",
2271
2344
  rawInput,
2272
2345
  status: "pending",
2273
- ...toolInfoFromToolUse(chunk, ctx.fileContentCache, ctx.logger)
2346
+ ...toolInfo
2274
2347
  };
2275
2348
  }
2276
2349
  function handleToolResultChunk(chunk, ctx) {
@@ -2279,36 +2352,71 @@ function handleToolResultChunk(chunk, ctx) {
2279
2352
  ctx.logger.error(
2280
2353
  `Got a tool result for tool use that wasn't tracked: ${chunk.tool_use_id}`
2281
2354
  );
2282
- return null;
2355
+ return [];
2283
2356
  }
2284
2357
  if (toolUse.name === "TodoWrite") {
2285
- return null;
2358
+ return [];
2286
2359
  }
2287
- return {
2288
- _meta: toolMeta(toolUse.name, void 0, ctx.parentToolCallId),
2360
+ const { _meta: resultMeta, ...toolUpdate } = toolUpdateFromToolResult(
2361
+ chunk,
2362
+ toolUse,
2363
+ {
2364
+ supportsTerminalOutput: ctx.supportsTerminalOutput,
2365
+ toolUseId: chunk.tool_use_id
2366
+ }
2367
+ );
2368
+ const updates = [];
2369
+ if (resultMeta?.terminal_output) {
2370
+ const terminalOutputMeta = {
2371
+ terminal_output: resultMeta.terminal_output
2372
+ };
2373
+ if (ctx.parentToolCallId) {
2374
+ terminalOutputMeta.claudeCode = {
2375
+ parentToolCallId: ctx.parentToolCallId
2376
+ };
2377
+ }
2378
+ updates.push({
2379
+ _meta: terminalOutputMeta,
2380
+ toolCallId: chunk.tool_use_id,
2381
+ sessionUpdate: "tool_call_update"
2382
+ });
2383
+ }
2384
+ const meta = {
2385
+ ...toolMeta(toolUse.name, void 0, ctx.parentToolCallId),
2386
+ ...resultMeta?.terminal_exit ? { terminal_exit: resultMeta.terminal_exit } : {}
2387
+ };
2388
+ updates.push({
2389
+ _meta: meta,
2289
2390
  toolCallId: chunk.tool_use_id,
2290
2391
  sessionUpdate: "tool_call_update",
2291
2392
  status: chunk.is_error ? "failed" : "completed",
2292
- ...toolUpdateFromToolResult(
2293
- chunk,
2294
- toolUse
2295
- )
2296
- };
2393
+ rawOutput: chunk.content,
2394
+ ...toolUpdate
2395
+ });
2396
+ return updates;
2297
2397
  }
2298
2398
  function processContentChunk(chunk, role, ctx) {
2299
2399
  switch (chunk.type) {
2300
2400
  case "text":
2301
- case "text_delta":
2302
- return handleTextChunk(chunk, role, ctx.parentToolCallId);
2303
- case "image":
2304
- return handleImageChunk(chunk, role);
2401
+ case "text_delta": {
2402
+ const update = handleTextChunk(chunk, role, ctx.parentToolCallId);
2403
+ return update ? [update] : [];
2404
+ }
2405
+ case "image": {
2406
+ const update = handleImageChunk(chunk, role);
2407
+ return update ? [update] : [];
2408
+ }
2305
2409
  case "thinking":
2306
- case "thinking_delta":
2307
- return handleThinkingChunk(chunk, ctx.parentToolCallId);
2410
+ case "thinking_delta": {
2411
+ const update = handleThinkingChunk(chunk, ctx.parentToolCallId);
2412
+ return update ? [update] : [];
2413
+ }
2308
2414
  case "tool_use":
2309
2415
  case "server_tool_use":
2310
- case "mcp_tool_use":
2311
- return handleToolUseChunk(chunk, ctx);
2416
+ case "mcp_tool_use": {
2417
+ const update = handleToolUseChunk(chunk, ctx);
2418
+ return update ? [update] : [];
2419
+ }
2312
2420
  case "tool_result":
2313
2421
  case "tool_search_tool_result":
2314
2422
  case "web_fetch_tool_result":
@@ -2330,13 +2438,13 @@ function processContentChunk(chunk, role, ctx) {
2330
2438
  case "container_upload":
2331
2439
  case "compaction":
2332
2440
  case "compaction_delta":
2333
- return null;
2441
+ return [];
2334
2442
  default:
2335
2443
  unreachable(chunk, ctx.logger);
2336
- return null;
2444
+ return [];
2337
2445
  }
2338
2446
  }
2339
- function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId) {
2447
+ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput) {
2340
2448
  if (typeof content === "string") {
2341
2449
  const update = {
2342
2450
  sessionUpdate: messageUpdateType(role),
@@ -2357,18 +2465,19 @@ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentC
2357
2465
  fileContentCache,
2358
2466
  client,
2359
2467
  logger,
2360
- parentToolCallId
2468
+ parentToolCallId,
2469
+ registerHooks,
2470
+ supportsTerminalOutput
2361
2471
  };
2362
2472
  const output = [];
2363
2473
  for (const chunk of content) {
2364
- const update = processContentChunk(chunk, role, ctx);
2365
- if (update) {
2474
+ for (const update of processContentChunk(chunk, role, ctx)) {
2366
2475
  output.push({ sessionId, update });
2367
2476
  }
2368
2477
  }
2369
2478
  return output;
2370
2479
  }
2371
- function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId) {
2480
+ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput) {
2372
2481
  const event = message.event;
2373
2482
  switch (event.type) {
2374
2483
  case "content_block_start":
@@ -2380,7 +2489,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
2380
2489
  fileContentCache,
2381
2490
  client,
2382
2491
  logger,
2383
- parentToolCallId
2492
+ parentToolCallId,
2493
+ registerHooks,
2494
+ supportsTerminalOutput
2384
2495
  );
2385
2496
  case "content_block_delta":
2386
2497
  return toAcpNotifications(
@@ -2391,7 +2502,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
2391
2502
  fileContentCache,
2392
2503
  client,
2393
2504
  logger,
2394
- parentToolCallId
2505
+ parentToolCallId,
2506
+ registerHooks,
2507
+ supportsTerminalOutput
2395
2508
  );
2396
2509
  case "message_start":
2397
2510
  case "message_delta":
@@ -2450,29 +2563,25 @@ async function handleSystemMessage(message, context) {
2450
2563
  break;
2451
2564
  }
2452
2565
  }
2453
- function handleResultMessage(message, context) {
2454
- const { session } = context;
2455
- if (session.cancelled) {
2456
- return {
2457
- shouldStop: true,
2458
- stopReason: "cancelled"
2459
- };
2460
- }
2566
+ function handleResultMessage(message) {
2567
+ const usage = extractUsageFromResult(message);
2461
2568
  switch (message.subtype) {
2462
2569
  case "success": {
2463
2570
  if (message.result.includes("Please run /login")) {
2464
2571
  return {
2465
2572
  shouldStop: true,
2466
- error: import_sdk.RequestError.authRequired()
2573
+ error: import_sdk.RequestError.authRequired(),
2574
+ usage
2467
2575
  };
2468
2576
  }
2469
2577
  if (message.is_error) {
2470
2578
  return {
2471
2579
  shouldStop: true,
2472
- error: import_sdk.RequestError.internalError(void 0, message.result)
2580
+ error: import_sdk.RequestError.internalError(void 0, message.result),
2581
+ usage
2473
2582
  };
2474
2583
  }
2475
- return { shouldStop: true, stopReason: "end_turn" };
2584
+ return { shouldStop: true, stopReason: "end_turn", usage };
2476
2585
  }
2477
2586
  case "error_during_execution":
2478
2587
  if (message.is_error) {
@@ -2481,10 +2590,11 @@ function handleResultMessage(message, context) {
2481
2590
  error: import_sdk.RequestError.internalError(
2482
2591
  void 0,
2483
2592
  message.errors.join(", ") || message.subtype
2484
- )
2593
+ ),
2594
+ usage
2485
2595
  };
2486
2596
  }
2487
- return { shouldStop: true, stopReason: "end_turn" };
2597
+ return { shouldStop: true, stopReason: "end_turn", usage };
2488
2598
  case "error_max_budget_usd":
2489
2599
  case "error_max_turns":
2490
2600
  case "error_max_structured_output_retries":
@@ -2494,13 +2604,37 @@ function handleResultMessage(message, context) {
2494
2604
  error: import_sdk.RequestError.internalError(
2495
2605
  void 0,
2496
2606
  message.errors.join(", ") || message.subtype
2497
- )
2607
+ ),
2608
+ usage
2498
2609
  };
2499
2610
  }
2500
- return { shouldStop: true, stopReason: "max_turn_requests" };
2611
+ return { shouldStop: true, stopReason: "max_turn_requests", usage };
2501
2612
  default:
2502
- return { shouldStop: false };
2613
+ return { shouldStop: false, usage };
2614
+ }
2615
+ }
2616
+ function extractUsageFromResult(message) {
2617
+ const msg = message;
2618
+ const msgUsage = msg.usage;
2619
+ if (!msgUsage) return void 0;
2620
+ const modelUsage = msg.modelUsage;
2621
+ let contextWindowSize;
2622
+ if (modelUsage) {
2623
+ const contextWindows = Object.values(modelUsage).map(
2624
+ (m) => m.contextWindow
2625
+ );
2626
+ if (contextWindows.length > 0) {
2627
+ contextWindowSize = Math.min(...contextWindows);
2628
+ }
2503
2629
  }
2630
+ return {
2631
+ inputTokens: msgUsage.input_tokens ?? 0,
2632
+ outputTokens: msgUsage.output_tokens ?? 0,
2633
+ cachedReadTokens: msgUsage.cache_read_input_tokens ?? 0,
2634
+ cachedWriteTokens: msgUsage.cache_creation_input_tokens ?? 0,
2635
+ costUsd: typeof msg.total_cost_usd === "number" ? msg.total_cost_usd : void 0,
2636
+ contextWindowSize
2637
+ };
2504
2638
  }
2505
2639
  async function handleStreamEvent(message, context) {
2506
2640
  const { sessionId, client, toolUseCache, fileContentCache, logger } = context;
@@ -2512,7 +2646,9 @@ async function handleStreamEvent(message, context) {
2512
2646
  fileContentCache,
2513
2647
  client,
2514
2648
  logger,
2515
- parentToolCallId
2649
+ parentToolCallId,
2650
+ context.registerHooks,
2651
+ context.supportsTerminalOutput
2516
2652
  )) {
2517
2653
  await client.sessionUpdate(notification);
2518
2654
  context.session.notificationHistory.push(notification);
@@ -2524,14 +2660,15 @@ function hasLocalCommandStdout(content) {
2524
2660
  function hasLocalCommandStderr(content) {
2525
2661
  return typeof content === "string" && content.includes("<local-command-stderr>");
2526
2662
  }
2527
- function isSimpleUserMessage(message) {
2528
- 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");
2529
- }
2530
2663
  function isLoginRequiredMessage(message) {
2531
2664
  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;
2532
2665
  }
2666
+ function isPlainTextUserMessage(message) {
2667
+ const content = message.message.content;
2668
+ return message.type === "user" && (typeof content === "string" || Array.isArray(content) && content.length === 1 && content[0].type === "text");
2669
+ }
2533
2670
  function shouldSkipUserAssistantMessage(message) {
2534
- return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isSimpleUserMessage(message) || isLoginRequiredMessage(message);
2671
+ return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isLoginRequiredMessage(message);
2535
2672
  }
2536
2673
  function logSpecialMessages(message, logger) {
2537
2674
  const content = message.message.content;
@@ -2552,18 +2689,33 @@ function filterMessageContent(content) {
2552
2689
  }
2553
2690
  async function handleUserAssistantMessage(message, context) {
2554
2691
  const { session, sessionId, client, toolUseCache, fileContentCache, logger } = context;
2555
- if (session.cancelled) {
2556
- return {};
2557
- }
2558
2692
  if (shouldSkipUserAssistantMessage(message)) {
2693
+ const content2 = message.message.content;
2694
+ if (typeof content2 === "string" && hasLocalCommandStdout(content2) && content2.includes("Context Usage")) {
2695
+ const stripped = content2.replace("<local-command-stdout>", "").replace("</local-command-stdout>", "");
2696
+ for (const notification of toAcpNotifications(
2697
+ stripped,
2698
+ "assistant",
2699
+ sessionId,
2700
+ toolUseCache,
2701
+ fileContentCache,
2702
+ client,
2703
+ logger
2704
+ )) {
2705
+ await client.sessionUpdate(notification);
2706
+ }
2707
+ }
2559
2708
  logSpecialMessages(message, logger);
2560
2709
  if (isLoginRequiredMessage(message)) {
2561
2710
  return { shouldStop: true, error: import_sdk.RequestError.authRequired() };
2562
2711
  }
2563
2712
  return {};
2564
2713
  }
2714
+ if (isPlainTextUserMessage(message)) {
2715
+ return {};
2716
+ }
2565
2717
  const content = message.message.content;
2566
- const contentToProcess = filterMessageContent(content);
2718
+ const contentToProcess = message.type === "assistant" ? filterMessageContent(content) : content;
2567
2719
  const parentToolCallId = "parent_tool_use_id" in message ? message.parent_tool_use_id ?? void 0 : void 0;
2568
2720
  for (const notification of toAcpNotifications(
2569
2721
  contentToProcess,
@@ -2573,7 +2725,9 @@ async function handleUserAssistantMessage(message, context) {
2573
2725
  fileContentCache,
2574
2726
  client,
2575
2727
  logger,
2576
- parentToolCallId
2728
+ parentToolCallId,
2729
+ context.registerHooks,
2730
+ context.supportsTerminalOutput
2577
2731
  )) {
2578
2732
  await client.sessionUpdate(notification);
2579
2733
  session.notificationHistory.push(notification);
@@ -2659,36 +2813,45 @@ function normalizeAskUserQuestionInput(input) {
2659
2813
  }
2660
2814
 
2661
2815
  // src/execution-mode.ts
2662
- var MODES = [
2816
+ var ALLOW_BYPASS = !IS_ROOT;
2817
+ var availableModes = [
2663
2818
  {
2664
2819
  id: "default",
2665
- name: "Always Ask",
2666
- description: "Prompts for permission on first use of each tool"
2820
+ name: "Default",
2821
+ description: "Standard behavior, prompts for dangerous operations"
2667
2822
  },
2668
2823
  {
2669
2824
  id: "acceptEdits",
2670
2825
  name: "Accept Edits",
2671
- description: "Automatically accepts file edit permissions for the session"
2826
+ description: "Auto-accept file edit operations"
2672
2827
  },
2673
2828
  {
2674
2829
  id: "plan",
2675
2830
  name: "Plan Mode",
2676
- description: "Claude can analyze but not modify files or execute commands"
2677
- },
2678
- {
2679
- id: "bypassPermissions",
2680
- name: "Bypass Permissions",
2681
- description: "Skips all permission prompts"
2831
+ description: "Planning mode, no actual tool execution"
2682
2832
  }
2833
+ // {
2834
+ // id: "dontAsk",
2835
+ // name: "Don't Ask",
2836
+ // description: "Don't prompt for permissions, deny if not pre-approved",
2837
+ // },
2683
2838
  ];
2839
+ if (ALLOW_BYPASS) {
2840
+ availableModes.push({
2841
+ id: "bypassPermissions",
2842
+ name: "Bypass Permissions",
2843
+ description: "Bypass all permission checks"
2844
+ });
2845
+ }
2684
2846
  var TWIG_EXECUTION_MODES = [
2685
2847
  "default",
2686
2848
  "acceptEdits",
2687
2849
  "plan",
2850
+ // "dontAsk",
2688
2851
  "bypassPermissions"
2689
2852
  ];
2690
2853
  function getAvailableModes() {
2691
- return IS_ROOT ? MODES.filter((m) => m.id !== "bypassPermissions") : MODES;
2854
+ return IS_ROOT ? availableModes.filter((m) => m.id !== "bypassPermissions") : availableModes;
2692
2855
  }
2693
2856
 
2694
2857
  // src/adapters/claude/tools.ts
@@ -2716,6 +2879,7 @@ var AUTO_ALLOWED_TOOLS = {
2716
2879
  default: new Set(BASE_ALLOWED_TOOLS),
2717
2880
  acceptEdits: /* @__PURE__ */ new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),
2718
2881
  plan: new Set(BASE_ALLOWED_TOOLS)
2882
+ // dontAsk: new Set(BASE_ALLOWED_TOOLS),
2719
2883
  };
2720
2884
  function isToolAllowedForMode(toolName, mode) {
2721
2885
  if (mode === "bypassPermissions") {
@@ -2863,12 +3027,11 @@ async function validatePlanContent(planText, context) {
2863
3027
  return { valid: true };
2864
3028
  }
2865
3029
  async function requestPlanApproval(context, updatedInput) {
2866
- const { client, sessionId, toolUseID, fileContentCache } = context;
2867
- const toolInfo = toolInfoFromToolUse(
2868
- { name: context.toolName, input: updatedInput },
2869
- fileContentCache,
2870
- context.logger
2871
- );
3030
+ const { client, sessionId, toolUseID } = context;
3031
+ const toolInfo = toolInfoFromToolUse({
3032
+ name: context.toolName,
3033
+ input: updatedInput
3034
+ });
2872
3035
  return await client.requestPermission({
2873
3036
  options: buildExitPlanModePermissionOptions(),
2874
3037
  sessionId,
@@ -2887,7 +3050,14 @@ async function applyPlanApproval(response, context, updatedInput) {
2887
3050
  if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "default" || response.outcome.optionId === "acceptEdits")) {
2888
3051
  session.permissionMode = response.outcome.optionId;
2889
3052
  await session.query.setPermissionMode(response.outcome.optionId);
2890
- await context.emitConfigOptionsUpdate();
3053
+ await context.client.sessionUpdate({
3054
+ sessionId: context.sessionId,
3055
+ update: {
3056
+ sessionUpdate: "current_mode_update",
3057
+ currentModeId: response.outcome.optionId
3058
+ }
3059
+ });
3060
+ await context.updateConfigOption("mode", response.outcome.optionId);
2891
3061
  return {
2892
3062
  behavior: "allow",
2893
3063
  updatedInput,
@@ -2908,7 +3078,7 @@ async function handleEnterPlanModeTool(context) {
2908
3078
  const { session, toolInput } = context;
2909
3079
  session.permissionMode = "plan";
2910
3080
  await session.query.setPermissionMode("plan");
2911
- await context.emitConfigOptionsUpdate();
3081
+ await context.updateConfigOption("mode", "plan");
2912
3082
  return {
2913
3083
  behavior: "allow",
2914
3084
  updatedInput: toolInput
@@ -2926,6 +3096,9 @@ async function handleExitPlanModeTool(context) {
2926
3096
  return validationResult.error;
2927
3097
  }
2928
3098
  const response = await requestPlanApproval(context, updatedInput);
3099
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3100
+ throw new Error("Tool use aborted");
3101
+ }
2929
3102
  return await applyPlanApproval(response, context, updatedInput);
2930
3103
  }
2931
3104
  function buildQuestionOptions(question) {
@@ -2949,14 +3122,13 @@ async function handleAskUserQuestionTool(context) {
2949
3122
  interrupt: true
2950
3123
  };
2951
3124
  }
2952
- const { client, sessionId, toolUseID, toolInput, fileContentCache } = context;
3125
+ const { client, sessionId, toolUseID, toolInput } = context;
2953
3126
  const firstQuestion = questions[0];
2954
3127
  const options = buildQuestionOptions(firstQuestion);
2955
- const toolInfo = toolInfoFromToolUse(
2956
- { name: context.toolName, input: toolInput },
2957
- fileContentCache,
2958
- context.logger
2959
- );
3128
+ const toolInfo = toolInfoFromToolUse({
3129
+ name: context.toolName,
3130
+ input: toolInput
3131
+ });
2960
3132
  const response = await client.requestPermission({
2961
3133
  options,
2962
3134
  sessionId,
@@ -2971,6 +3143,9 @@ async function handleAskUserQuestionTool(context) {
2971
3143
  }
2972
3144
  }
2973
3145
  });
3146
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3147
+ throw new Error("Tool use aborted");
3148
+ }
2974
3149
  if (response.outcome?.outcome !== "selected") {
2975
3150
  const customMessage = response._meta?.message;
2976
3151
  return {
@@ -3003,14 +3178,9 @@ async function handleDefaultPermissionFlow(context) {
3003
3178
  toolUseID,
3004
3179
  client,
3005
3180
  sessionId,
3006
- fileContentCache,
3007
3181
  suggestions
3008
3182
  } = context;
3009
- const toolInfo = toolInfoFromToolUse(
3010
- { name: toolName, input: toolInput },
3011
- fileContentCache,
3012
- context.logger
3013
- );
3183
+ const toolInfo = toolInfoFromToolUse({ name: toolName, input: toolInput });
3014
3184
  const options = buildPermissionOptions(
3015
3185
  toolName,
3016
3186
  toolInput,
@@ -3029,6 +3199,9 @@ async function handleDefaultPermissionFlow(context) {
3029
3199
  rawInput: toolInput
3030
3200
  }
3031
3201
  });
3202
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3203
+ throw new Error("Tool use aborted");
3204
+ }
3032
3205
  if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "allow" || response.outcome.optionId === "allow_always")) {
3033
3206
  if (response.outcome.optionId === "allow_always") {
3034
3207
  return {
@@ -3105,16 +3278,18 @@ async function canUseTool(context) {
3105
3278
  var UNSUPPORTED_COMMANDS = [
3106
3279
  "context",
3107
3280
  "cost",
3281
+ "keybindings-help",
3108
3282
  "login",
3109
3283
  "logout",
3110
3284
  "output-style:new",
3111
3285
  "release-notes",
3112
3286
  "todos"
3113
3287
  ];
3114
- async function getAvailableSlashCommands(q) {
3115
- const commands = await q.supportedCommands();
3288
+ function getAvailableSlashCommands(commands) {
3116
3289
  return commands.map((command) => {
3117
- const input = command.argumentHint ? { hint: command.argumentHint } : null;
3290
+ const input = command.argumentHint != null ? {
3291
+ hint: Array.isArray(command.argumentHint) ? command.argumentHint.join(" ") : command.argumentHint
3292
+ } : null;
3118
3293
  let name = command.name;
3119
3294
  if (command.name.endsWith(" (MCP)")) {
3120
3295
  name = `mcp:${name.replace(" (MCP)", "")}`;
@@ -3212,13 +3387,19 @@ function buildEnvironment() {
3212
3387
  ENABLE_TOOL_SEARCH: "auto:0"
3213
3388
  };
3214
3389
  }
3215
- function buildHooks(userHooks, onModeChange) {
3390
+ function buildHooks(userHooks, onModeChange, settingsManager, logger) {
3216
3391
  return {
3217
3392
  ...userHooks,
3218
3393
  PostToolUse: [
3219
3394
  ...userHooks?.PostToolUse || [],
3220
3395
  {
3221
- hooks: [createPostToolUseHook({ onModeChange })]
3396
+ hooks: [createPostToolUseHook({ onModeChange, logger })]
3397
+ }
3398
+ ],
3399
+ PreToolUse: [
3400
+ ...userHooks?.PreToolUse || [],
3401
+ {
3402
+ hooks: [createPreToolUseHook(settingsManager, logger)]
3222
3403
  }
3223
3404
  ]
3224
3405
  };
@@ -3312,12 +3493,22 @@ function buildSessionOptions(params) {
3312
3493
  permissionMode: params.permissionMode,
3313
3494
  canUseTool: params.canUseTool,
3314
3495
  executable: "node",
3496
+ tools: { type: "preset", preset: "claude_code" },
3497
+ extraArgs: {
3498
+ ...params.userProvidedOptions?.extraArgs,
3499
+ "replay-user-messages": ""
3500
+ },
3315
3501
  mcpServers: buildMcpServers(
3316
3502
  params.userProvidedOptions?.mcpServers,
3317
3503
  params.mcpServers
3318
3504
  ),
3319
3505
  env: buildEnvironment(),
3320
- hooks: buildHooks(params.userProvidedOptions?.hooks, params.onModeChange),
3506
+ hooks: buildHooks(
3507
+ params.userProvidedOptions?.hooks,
3508
+ params.onModeChange,
3509
+ params.settingsManager,
3510
+ params.logger
3511
+ ),
3321
3512
  abortController: getAbortController(
3322
3513
  params.userProvidedOptions?.abortController
3323
3514
  ),
@@ -3334,13 +3525,36 @@ function buildSessionOptions(params) {
3334
3525
  }
3335
3526
  if (params.isResume) {
3336
3527
  options.resume = params.sessionId;
3337
- options.forkSession = false;
3528
+ options.forkSession = params.forkSession ?? false;
3338
3529
  } else {
3339
3530
  options.sessionId = params.sessionId;
3531
+ options.model = DEFAULT_MODEL;
3340
3532
  }
3341
3533
  if (params.additionalDirectories) {
3342
3534
  options.additionalDirectories = params.additionalDirectories;
3343
3535
  }
3536
+ if (params.disableBuiltInTools) {
3537
+ const builtInTools = [
3538
+ "Read",
3539
+ "Write",
3540
+ "Edit",
3541
+ "Bash",
3542
+ "Glob",
3543
+ "Grep",
3544
+ "Task",
3545
+ "TodoWrite",
3546
+ "ExitPlanMode",
3547
+ "WebSearch",
3548
+ "WebFetch",
3549
+ "SlashCommand",
3550
+ "Skill",
3551
+ "NotebookEdit"
3552
+ ];
3553
+ options.disallowedTools = [
3554
+ ...options.disallowedTools ?? [],
3555
+ ...builtInTools
3556
+ ];
3557
+ }
3344
3558
  clearStatsigCache();
3345
3559
  return options;
3346
3560
  }
@@ -3353,15 +3567,268 @@ function clearStatsigCache() {
3353
3567
  });
3354
3568
  }
3355
3569
 
3570
+ // src/adapters/claude/session/settings.ts
3571
+ var fs2 = __toESM(require("fs"), 1);
3572
+ var os3 = __toESM(require("os"), 1);
3573
+ var path3 = __toESM(require("path"), 1);
3574
+ var import_minimatch = require("minimatch");
3575
+ var ACP_TOOL_NAME_PREFIX = "mcp__acp__";
3576
+ var acpToolNames = {
3577
+ read: `${ACP_TOOL_NAME_PREFIX}Read`,
3578
+ edit: `${ACP_TOOL_NAME_PREFIX}Edit`,
3579
+ write: `${ACP_TOOL_NAME_PREFIX}Write`,
3580
+ bash: `${ACP_TOOL_NAME_PREFIX}Bash`
3581
+ };
3582
+ var SHELL_OPERATORS = ["&&", "||", ";", "|", "$(", "`", "\n"];
3583
+ function containsShellOperator(str) {
3584
+ return SHELL_OPERATORS.some((op) => str.includes(op));
3585
+ }
3586
+ var FILE_EDITING_TOOLS = [acpToolNames.edit, acpToolNames.write];
3587
+ var FILE_READING_TOOLS = [acpToolNames.read];
3588
+ var TOOL_ARG_ACCESSORS = {
3589
+ [acpToolNames.read]: (input) => input?.file_path,
3590
+ [acpToolNames.edit]: (input) => input?.file_path,
3591
+ [acpToolNames.write]: (input) => input?.file_path,
3592
+ [acpToolNames.bash]: (input) => input?.command
3593
+ };
3594
+ function parseRule(rule) {
3595
+ const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
3596
+ if (!match) {
3597
+ return { toolName: rule };
3598
+ }
3599
+ const toolName = match[1] ?? rule;
3600
+ const argument = match[2];
3601
+ if (argument?.endsWith(":*")) {
3602
+ return {
3603
+ toolName,
3604
+ argument: argument.slice(0, -2),
3605
+ isWildcard: true
3606
+ };
3607
+ }
3608
+ return { toolName, argument };
3609
+ }
3610
+ function normalizePath(filePath, cwd) {
3611
+ let resolved = filePath;
3612
+ if (resolved.startsWith("~/")) {
3613
+ resolved = path3.join(os3.homedir(), resolved.slice(2));
3614
+ } else if (resolved.startsWith("./")) {
3615
+ resolved = path3.join(cwd, resolved.slice(2));
3616
+ } else if (!path3.isAbsolute(resolved)) {
3617
+ resolved = path3.join(cwd, resolved);
3618
+ }
3619
+ return path3.normalize(resolved).replace(/\\/g, "/");
3620
+ }
3621
+ function matchesGlob(pattern, filePath, cwd) {
3622
+ const normalizedPattern = normalizePath(pattern, cwd);
3623
+ const normalizedPath = normalizePath(filePath, cwd);
3624
+ return (0, import_minimatch.minimatch)(normalizedPath, normalizedPattern, {
3625
+ dot: true,
3626
+ matchBase: false,
3627
+ nocase: process.platform === "win32"
3628
+ });
3629
+ }
3630
+ function matchesRule(rule, toolName, toolInput, cwd) {
3631
+ const ruleAppliesToTool = rule.toolName === "Bash" && toolName === acpToolNames.bash || rule.toolName === "Edit" && FILE_EDITING_TOOLS.includes(toolName) || rule.toolName === "Read" && FILE_READING_TOOLS.includes(toolName);
3632
+ if (!ruleAppliesToTool) {
3633
+ return false;
3634
+ }
3635
+ if (!rule.argument) {
3636
+ return true;
3637
+ }
3638
+ const argAccessor = TOOL_ARG_ACCESSORS[toolName];
3639
+ if (!argAccessor) {
3640
+ return true;
3641
+ }
3642
+ const actualArg = argAccessor(toolInput);
3643
+ if (!actualArg) {
3644
+ return false;
3645
+ }
3646
+ if (toolName === acpToolNames.bash) {
3647
+ if (rule.isWildcard) {
3648
+ if (!actualArg.startsWith(rule.argument)) {
3649
+ return false;
3650
+ }
3651
+ const remainder = actualArg.slice(rule.argument.length);
3652
+ if (containsShellOperator(remainder)) {
3653
+ return false;
3654
+ }
3655
+ return true;
3656
+ }
3657
+ return actualArg === rule.argument;
3658
+ }
3659
+ return matchesGlob(rule.argument, actualArg, cwd);
3660
+ }
3661
+ async function loadSettingsFile(filePath) {
3662
+ if (!filePath) {
3663
+ return {};
3664
+ }
3665
+ try {
3666
+ const content = await fs2.promises.readFile(filePath, "utf-8");
3667
+ return JSON.parse(content);
3668
+ } catch {
3669
+ return {};
3670
+ }
3671
+ }
3672
+ function getManagedSettingsPath() {
3673
+ switch (process.platform) {
3674
+ case "darwin":
3675
+ return "/Library/Application Support/ClaudeCode/managed-settings.json";
3676
+ case "linux":
3677
+ return "/etc/claude-code/managed-settings.json";
3678
+ case "win32":
3679
+ return "C:\\Program Files\\ClaudeCode\\managed-settings.json";
3680
+ default:
3681
+ return "/etc/claude-code/managed-settings.json";
3682
+ }
3683
+ }
3684
+ var SettingsManager = class {
3685
+ cwd;
3686
+ userSettings = {};
3687
+ projectSettings = {};
3688
+ localSettings = {};
3689
+ enterpriseSettings = {};
3690
+ mergedSettings = {};
3691
+ initialized = false;
3692
+ constructor(cwd) {
3693
+ this.cwd = cwd;
3694
+ }
3695
+ async initialize() {
3696
+ if (this.initialized) {
3697
+ return;
3698
+ }
3699
+ await this.loadAllSettings();
3700
+ this.initialized = true;
3701
+ }
3702
+ getUserSettingsPath() {
3703
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path3.join(os3.homedir(), ".claude");
3704
+ return path3.join(configDir, "settings.json");
3705
+ }
3706
+ getProjectSettingsPath() {
3707
+ return path3.join(this.cwd, ".claude", "settings.json");
3708
+ }
3709
+ getLocalSettingsPath() {
3710
+ return path3.join(this.cwd, ".claude", "settings.local.json");
3711
+ }
3712
+ async loadAllSettings() {
3713
+ const [userSettings, projectSettings, localSettings, enterpriseSettings] = await Promise.all([
3714
+ loadSettingsFile(this.getUserSettingsPath()),
3715
+ loadSettingsFile(this.getProjectSettingsPath()),
3716
+ loadSettingsFile(this.getLocalSettingsPath()),
3717
+ loadSettingsFile(getManagedSettingsPath())
3718
+ ]);
3719
+ this.userSettings = userSettings;
3720
+ this.projectSettings = projectSettings;
3721
+ this.localSettings = localSettings;
3722
+ this.enterpriseSettings = enterpriseSettings;
3723
+ this.mergeAllSettings();
3724
+ }
3725
+ mergeAllSettings() {
3726
+ const allSettings = [
3727
+ this.userSettings,
3728
+ this.projectSettings,
3729
+ this.localSettings,
3730
+ this.enterpriseSettings
3731
+ ];
3732
+ const permissions = {
3733
+ allow: [],
3734
+ deny: [],
3735
+ ask: []
3736
+ };
3737
+ const merged = { permissions };
3738
+ for (const settings of allSettings) {
3739
+ if (settings.permissions) {
3740
+ if (settings.permissions.allow) {
3741
+ permissions.allow?.push(...settings.permissions.allow);
3742
+ }
3743
+ if (settings.permissions.deny) {
3744
+ permissions.deny?.push(...settings.permissions.deny);
3745
+ }
3746
+ if (settings.permissions.ask) {
3747
+ permissions.ask?.push(...settings.permissions.ask);
3748
+ }
3749
+ if (settings.permissions.additionalDirectories) {
3750
+ permissions.additionalDirectories = [
3751
+ ...permissions.additionalDirectories || [],
3752
+ ...settings.permissions.additionalDirectories
3753
+ ];
3754
+ }
3755
+ if (settings.permissions.defaultMode) {
3756
+ permissions.defaultMode = settings.permissions.defaultMode;
3757
+ }
3758
+ }
3759
+ if (settings.env) {
3760
+ merged.env = { ...merged.env, ...settings.env };
3761
+ }
3762
+ if (settings.model) {
3763
+ merged.model = settings.model;
3764
+ }
3765
+ }
3766
+ this.mergedSettings = merged;
3767
+ }
3768
+ checkPermission(toolName, toolInput) {
3769
+ if (!toolName.startsWith(ACP_TOOL_NAME_PREFIX)) {
3770
+ return { decision: "ask" };
3771
+ }
3772
+ const permissions = this.mergedSettings.permissions;
3773
+ if (!permissions) {
3774
+ return { decision: "ask" };
3775
+ }
3776
+ for (const rule of permissions.deny || []) {
3777
+ const parsed = parseRule(rule);
3778
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3779
+ return { decision: "deny", rule, source: "deny" };
3780
+ }
3781
+ }
3782
+ for (const rule of permissions.allow || []) {
3783
+ const parsed = parseRule(rule);
3784
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3785
+ return { decision: "allow", rule, source: "allow" };
3786
+ }
3787
+ }
3788
+ for (const rule of permissions.ask || []) {
3789
+ const parsed = parseRule(rule);
3790
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3791
+ return { decision: "ask", rule, source: "ask" };
3792
+ }
3793
+ }
3794
+ return { decision: "ask" };
3795
+ }
3796
+ getSettings() {
3797
+ return this.mergedSettings;
3798
+ }
3799
+ getCwd() {
3800
+ return this.cwd;
3801
+ }
3802
+ async setCwd(cwd) {
3803
+ if (this.cwd === cwd) {
3804
+ return;
3805
+ }
3806
+ this.dispose();
3807
+ this.cwd = cwd;
3808
+ this.initialized = false;
3809
+ await this.initialize();
3810
+ }
3811
+ dispose() {
3812
+ this.initialized = false;
3813
+ }
3814
+ };
3815
+
3356
3816
  // src/adapters/claude/claude-agent.ts
3357
3817
  var SESSION_VALIDATION_TIMEOUT_MS = 1e4;
3818
+ var MAX_TITLE_LENGTH = 256;
3819
+ function sanitizeTitle(text2) {
3820
+ const sanitized = text2.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
3821
+ if (sanitized.length <= MAX_TITLE_LENGTH) {
3822
+ return sanitized;
3823
+ }
3824
+ return `${sanitized.slice(0, MAX_TITLE_LENGTH - 1)}\u2026`;
3825
+ }
3358
3826
  var ClaudeAcpAgent = class extends BaseAcpAgent {
3359
3827
  adapterName = "claude";
3360
3828
  toolUseCache;
3361
3829
  backgroundTerminals = {};
3362
3830
  clientCapabilities;
3363
3831
  options;
3364
- lastSentConfigOptions;
3365
3832
  constructor(client, options) {
3366
3833
  super(client);
3367
3834
  this.options = options;
@@ -3382,125 +3849,416 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3382
3849
  sse: true
3383
3850
  },
3384
3851
  loadSession: true,
3852
+ sessionCapabilities: {
3853
+ list: {},
3854
+ fork: {},
3855
+ resume: {}
3856
+ },
3385
3857
  _meta: {
3386
3858
  posthog: {
3387
3859
  resumeSession: true
3860
+ },
3861
+ claudeCode: {
3862
+ promptQueueing: true
3863
+ }
3864
+ }
3865
+ },
3866
+ agentInfo: {
3867
+ name: package_default.name,
3868
+ title: "Claude Agent",
3869
+ version: package_default.version
3870
+ },
3871
+ authMethods: []
3872
+ };
3873
+ }
3874
+ async newSession(params) {
3875
+ if (fs3.existsSync(path4.resolve(os4.homedir(), ".claude.json.backup")) && !fs3.existsSync(path4.resolve(os4.homedir(), ".claude.json"))) {
3876
+ throw import_sdk2.RequestError.authRequired();
3877
+ }
3878
+ const response = await this.createSession(params, {
3879
+ // Revisit these meta values once we support resume
3880
+ resume: params._meta?.claudeCode?.options?.resume
3881
+ });
3882
+ return response;
3883
+ }
3884
+ async unstable_forkSession(params) {
3885
+ return this.createSession(
3886
+ {
3887
+ cwd: params.cwd,
3888
+ mcpServers: params.mcpServers ?? [],
3889
+ _meta: params._meta
3890
+ },
3891
+ { resume: params.sessionId, forkSession: true }
3892
+ );
3893
+ }
3894
+ async unstable_resumeSession(params) {
3895
+ const response = await this.createSession(
3896
+ {
3897
+ cwd: params.cwd,
3898
+ mcpServers: params.mcpServers ?? [],
3899
+ _meta: params._meta
3900
+ },
3901
+ {
3902
+ resume: params.sessionId
3903
+ }
3904
+ );
3905
+ return response;
3906
+ }
3907
+ async loadSession(params) {
3908
+ const response = await this.createSession(
3909
+ {
3910
+ cwd: params.cwd,
3911
+ mcpServers: params.mcpServers ?? [],
3912
+ _meta: params._meta
3913
+ },
3914
+ { resume: params.sessionId, skipBackgroundFetches: true }
3915
+ );
3916
+ await this.replaySessionHistory(params.sessionId);
3917
+ this.deferBackgroundFetches(this.session.query);
3918
+ return {
3919
+ modes: response.modes,
3920
+ models: response.models,
3921
+ configOptions: response.configOptions
3922
+ };
3923
+ }
3924
+ async unstable_listSessions(params) {
3925
+ const sdkSessions = await (0, import_claude_agent_sdk.listSessions)({ dir: params.cwd ?? void 0 });
3926
+ const sessions = [];
3927
+ for (const session of sdkSessions) {
3928
+ if (!session.cwd) continue;
3929
+ sessions.push({
3930
+ sessionId: session.sessionId,
3931
+ cwd: session.cwd,
3932
+ title: sanitizeTitle(session.customTitle || session.summary || ""),
3933
+ updatedAt: new Date(session.lastModified).toISOString()
3934
+ });
3935
+ }
3936
+ return {
3937
+ sessions
3938
+ };
3939
+ }
3940
+ async prompt(params) {
3941
+ this.session.cancelled = false;
3942
+ this.session.interruptReason = void 0;
3943
+ this.session.accumulatedUsage = {
3944
+ inputTokens: 0,
3945
+ outputTokens: 0,
3946
+ cachedReadTokens: 0,
3947
+ cachedWriteTokens: 0
3948
+ };
3949
+ const userMessage = promptToClaude(params);
3950
+ if (this.session.promptRunning) {
3951
+ const uuid = (0, import_node_crypto.randomUUID)();
3952
+ userMessage.uuid = uuid;
3953
+ this.session.input.push(userMessage);
3954
+ const order = this.session.nextPendingOrder++;
3955
+ const cancelled = await new Promise((resolve4) => {
3956
+ this.session.pendingMessages.set(uuid, { resolve: resolve4, order });
3957
+ });
3958
+ if (cancelled) {
3959
+ return { stopReason: "cancelled" };
3960
+ }
3961
+ } else {
3962
+ this.session.input.push(userMessage);
3963
+ }
3964
+ await this.broadcastUserMessage(params);
3965
+ this.session.promptRunning = true;
3966
+ let handedOff = false;
3967
+ let lastAssistantTotalUsage = null;
3968
+ const supportsTerminalOutput = this.clientCapabilities?._meta?.terminal_output === true;
3969
+ const context = {
3970
+ session: this.session,
3971
+ sessionId: params.sessionId,
3972
+ client: this.client,
3973
+ toolUseCache: this.toolUseCache,
3974
+ fileContentCache: this.fileContentCache,
3975
+ logger: this.logger,
3976
+ supportsTerminalOutput
3977
+ };
3978
+ try {
3979
+ while (true) {
3980
+ const { value: message, done } = await this.session.query.next();
3981
+ if (done || !message) {
3982
+ if (this.session.cancelled) {
3983
+ return {
3984
+ stopReason: "cancelled",
3985
+ _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
3986
+ };
3987
+ }
3988
+ break;
3989
+ }
3990
+ switch (message.type) {
3991
+ case "system":
3992
+ if (message.subtype === "compact_boundary") {
3993
+ lastAssistantTotalUsage = 0;
3994
+ }
3995
+ await handleSystemMessage(message, context);
3996
+ break;
3997
+ case "result": {
3998
+ if (this.session.cancelled) {
3999
+ return { stopReason: "cancelled" };
4000
+ }
4001
+ this.session.accumulatedUsage.inputTokens += message.usage.input_tokens;
4002
+ this.session.accumulatedUsage.outputTokens += message.usage.output_tokens;
4003
+ this.session.accumulatedUsage.cachedReadTokens += message.usage.cache_read_input_tokens;
4004
+ this.session.accumulatedUsage.cachedWriteTokens += message.usage.cache_creation_input_tokens;
4005
+ const contextWindows = Object.values(message.modelUsage).map(
4006
+ (m) => m.contextWindow
4007
+ );
4008
+ const contextWindowSize = contextWindows.length > 0 ? Math.min(...contextWindows) : 2e5;
4009
+ if (lastAssistantTotalUsage !== null) {
4010
+ await this.client.sessionUpdate({
4011
+ sessionId: params.sessionId,
4012
+ update: {
4013
+ sessionUpdate: "usage_update",
4014
+ used: lastAssistantTotalUsage,
4015
+ size: contextWindowSize,
4016
+ cost: {
4017
+ amount: message.total_cost_usd,
4018
+ currency: "USD"
4019
+ }
4020
+ }
4021
+ });
4022
+ }
4023
+ await this.client.extNotification("_posthog/usage_update", {
4024
+ sessionId: params.sessionId,
4025
+ used: {
4026
+ inputTokens: message.usage.input_tokens,
4027
+ outputTokens: message.usage.output_tokens,
4028
+ cachedReadTokens: message.usage.cache_read_input_tokens,
4029
+ cachedWriteTokens: message.usage.cache_creation_input_tokens
4030
+ },
4031
+ cost: message.total_cost_usd
4032
+ });
4033
+ const usage = {
4034
+ inputTokens: this.session.accumulatedUsage.inputTokens,
4035
+ outputTokens: this.session.accumulatedUsage.outputTokens,
4036
+ cachedReadTokens: this.session.accumulatedUsage.cachedReadTokens,
4037
+ cachedWriteTokens: this.session.accumulatedUsage.cachedWriteTokens,
4038
+ totalTokens: this.session.accumulatedUsage.inputTokens + this.session.accumulatedUsage.outputTokens + this.session.accumulatedUsage.cachedReadTokens + this.session.accumulatedUsage.cachedWriteTokens
4039
+ };
4040
+ const result = handleResultMessage(message);
4041
+ if (result.error) throw result.error;
4042
+ switch (message.subtype) {
4043
+ case "error_max_budget_usd":
4044
+ case "error_max_turns":
4045
+ case "error_max_structured_output_retries":
4046
+ return { stopReason: "max_turn_requests", usage };
4047
+ default:
4048
+ return { stopReason: "end_turn", usage };
4049
+ }
4050
+ }
4051
+ case "stream_event":
4052
+ await handleStreamEvent(message, context);
4053
+ break;
4054
+ case "user":
4055
+ case "assistant": {
4056
+ if (this.session.cancelled) {
4057
+ break;
4058
+ }
4059
+ if (message.type === "user" && "uuid" in message && message.uuid) {
4060
+ const pending = this.session.pendingMessages.get(
4061
+ message.uuid
4062
+ );
4063
+ if (pending) {
4064
+ pending.resolve(false);
4065
+ this.session.pendingMessages.delete(message.uuid);
4066
+ handedOff = true;
4067
+ return { stopReason: "end_turn" };
4068
+ }
4069
+ }
4070
+ if ("usage" in message.message && message.parent_tool_use_id === null) {
4071
+ const usage = message.message.usage;
4072
+ lastAssistantTotalUsage = usage.input_tokens + usage.output_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens;
4073
+ }
4074
+ const result = await handleUserAssistantMessage(message, context);
4075
+ if (result.error) throw result.error;
4076
+ if (result.shouldStop) {
4077
+ return { stopReason: "end_turn" };
4078
+ }
4079
+ break;
3388
4080
  }
4081
+ case "tool_progress":
4082
+ case "auth_status":
4083
+ case "tool_use_summary":
4084
+ break;
4085
+ default:
4086
+ unreachable(message, this.logger);
4087
+ break;
3389
4088
  }
3390
- },
3391
- agentInfo: {
3392
- name: package_default.name,
3393
- title: "Claude Code",
3394
- version: package_default.version
3395
- },
3396
- authMethods: [
3397
- {
3398
- id: "claude-login",
3399
- name: "Log in with Claude Code",
3400
- description: "Run `claude /login` in the terminal"
4089
+ }
4090
+ throw new Error("Session did not end in result");
4091
+ } finally {
4092
+ if (!handedOff) {
4093
+ this.session.promptRunning = false;
4094
+ for (const [key, pending] of this.session.pendingMessages) {
4095
+ pending.resolve(true);
4096
+ this.session.pendingMessages.delete(key);
3401
4097
  }
3402
- ]
3403
- };
4098
+ }
4099
+ }
3404
4100
  }
3405
- async authenticate(_params) {
3406
- throw new Error("Method not implemented.");
4101
+ // Called by BaseAcpAgent#cancel() to interrupt the session
4102
+ async interrupt() {
4103
+ this.session.cancelled = true;
4104
+ for (const [, pending] of this.session.pendingMessages) {
4105
+ pending.resolve(true);
4106
+ }
4107
+ this.session.pendingMessages.clear();
4108
+ await this.session.query.interrupt();
3407
4109
  }
3408
- async newSession(params) {
3409
- this.checkAuthStatus();
4110
+ async unstable_setSessionModel(params) {
4111
+ const sdkModelId = toSdkModelId(params.modelId);
4112
+ await this.session.query.setModel(sdkModelId);
4113
+ this.session.modelId = params.modelId;
4114
+ await this.updateConfigOption("model", params.modelId);
4115
+ return {};
4116
+ }
4117
+ async setSessionMode(params) {
4118
+ await this.applySessionMode(params.modeId);
4119
+ await this.updateConfigOption("mode", params.modeId);
4120
+ return {};
4121
+ }
4122
+ async setSessionConfigOption(params) {
4123
+ const option = this.session.configOptions.find(
4124
+ (o) => o.id === params.configId
4125
+ );
4126
+ if (!option) {
4127
+ throw new Error(`Unknown config option: ${params.configId}`);
4128
+ }
4129
+ const allValues = "options" in option && Array.isArray(option.options) ? option.options.flatMap(
4130
+ (o) => "options" in o && Array.isArray(o.options) ? o.options : [o]
4131
+ ) : [];
4132
+ const validValue = allValues.find((o) => o.value === params.value);
4133
+ if (!validValue) {
4134
+ throw new Error(
4135
+ `Invalid value for config option ${params.configId}: ${params.value}`
4136
+ );
4137
+ }
4138
+ if (params.configId === "mode") {
4139
+ await this.applySessionMode(params.value);
4140
+ await this.client.sessionUpdate({
4141
+ sessionId: this.sessionId,
4142
+ update: {
4143
+ sessionUpdate: "current_mode_update",
4144
+ currentModeId: params.value
4145
+ }
4146
+ });
4147
+ } else if (params.configId === "model") {
4148
+ const sdkModelId = toSdkModelId(params.value);
4149
+ await this.session.query.setModel(sdkModelId);
4150
+ this.session.modelId = params.value;
4151
+ }
4152
+ this.session.configOptions = this.session.configOptions.map(
4153
+ (o) => o.id === params.configId ? { ...o, currentValue: params.value } : o
4154
+ );
4155
+ return { configOptions: this.session.configOptions };
4156
+ }
4157
+ async updateConfigOption(configId, value) {
4158
+ this.session.configOptions = this.session.configOptions.map(
4159
+ (o) => o.id === configId ? { ...o, currentValue: value } : o
4160
+ );
4161
+ await this.client.sessionUpdate({
4162
+ sessionId: this.sessionId,
4163
+ update: {
4164
+ sessionUpdate: "config_option_update",
4165
+ configOptions: this.session.configOptions
4166
+ }
4167
+ });
4168
+ }
4169
+ async applySessionMode(modeId) {
4170
+ if (!TWIG_EXECUTION_MODES.includes(modeId)) {
4171
+ throw new Error("Invalid Mode");
4172
+ }
4173
+ const previousMode = this.session.permissionMode;
4174
+ this.session.permissionMode = modeId;
4175
+ try {
4176
+ await this.session.query.setPermissionMode(modeId);
4177
+ } catch (error) {
4178
+ this.session.permissionMode = previousMode;
4179
+ if (error instanceof Error) {
4180
+ if (!error.message) {
4181
+ error.message = "Invalid Mode";
4182
+ }
4183
+ throw error;
4184
+ }
4185
+ throw new Error("Invalid Mode");
4186
+ }
4187
+ }
4188
+ async createSession(params, creationOpts = {}) {
4189
+ const { cwd } = params;
4190
+ const { resume, forkSession } = creationOpts;
4191
+ const isResume = !!resume;
3410
4192
  const meta = params._meta;
3411
4193
  const taskId = meta?.persistence?.taskId;
3412
- const sessionId = (0, import_uuid.v7)();
3413
- this.logger.info("Creating new session", {
4194
+ let sessionId;
4195
+ if (forkSession) {
4196
+ sessionId = (0, import_uuid.v7)();
4197
+ } else if (isResume) {
4198
+ sessionId = resume;
4199
+ } else {
4200
+ sessionId = (0, import_uuid.v7)();
4201
+ }
4202
+ const input = new Pushable();
4203
+ const settingsManager = new SettingsManager(cwd);
4204
+ await settingsManager.initialize();
4205
+ const mcpServers = parseMcpServers(params);
4206
+ const systemPrompt = buildSystemPrompt(meta?.systemPrompt);
4207
+ this.logger.info(isResume ? "Resuming session" : "Creating new session", {
3414
4208
  sessionId,
3415
4209
  taskId,
3416
4210
  taskRunId: meta?.taskRunId,
3417
- cwd: params.cwd
4211
+ cwd
3418
4212
  });
3419
4213
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3420
- const mcpServers = parseMcpServers(params);
3421
4214
  const options = buildSessionOptions({
3422
- cwd: params.cwd,
4215
+ cwd,
3423
4216
  mcpServers,
3424
4217
  permissionMode,
3425
4218
  canUseTool: this.createCanUseTool(sessionId),
3426
4219
  logger: this.logger,
3427
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
4220
+ systemPrompt,
3428
4221
  userProvidedOptions: meta?.claudeCode?.options,
3429
4222
  sessionId,
3430
- isResume: false,
3431
- onModeChange: this.createOnModeChange(sessionId),
4223
+ isResume,
4224
+ forkSession,
4225
+ additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
4226
+ disableBuiltInTools: meta?.disableBuiltInTools,
4227
+ settingsManager,
4228
+ onModeChange: this.createOnModeChange(),
3432
4229
  onProcessSpawned: this.options?.onProcessSpawned,
3433
4230
  onProcessExited: this.options?.onProcessExited
3434
4231
  });
3435
- const input = new Pushable();
3436
- options.model = DEFAULT_MODEL;
4232
+ const abortController = options.abortController;
3437
4233
  const q = (0, import_claude_agent_sdk.query)({ prompt: input, options });
3438
- const session = this.createSession(
3439
- sessionId,
3440
- q,
4234
+ const session = {
4235
+ query: q,
3441
4236
  input,
4237
+ cancelled: false,
4238
+ settingsManager,
3442
4239
  permissionMode,
3443
- params.cwd,
3444
- options.abortController
3445
- );
3446
- session.taskRunId = meta?.taskRunId;
3447
- if (meta?.taskRunId) {
3448
- await this.client.extNotification("_posthog/sdk_session", {
3449
- taskRunId: meta.taskRunId,
3450
- sessionId,
3451
- adapter: "claude"
3452
- });
3453
- }
3454
- const modelOptions = await this.getModelConfigOptions();
3455
- this.deferBackgroundFetches(q, sessionId);
3456
- session.modelId = modelOptions.currentModelId;
3457
- const resolvedSdkModel = toSdkModelId(modelOptions.currentModelId);
3458
- if (resolvedSdkModel !== DEFAULT_MODEL) {
3459
- await this.trySetModel(q, modelOptions.currentModelId);
3460
- }
3461
- const configOptions = await this.buildConfigOptions(modelOptions);
3462
- return {
3463
- sessionId,
3464
- configOptions
3465
- };
3466
- }
3467
- async loadSession(params) {
3468
- return this.resumeSession(params);
3469
- }
3470
- async resumeSession(params) {
3471
- const meta = params._meta;
3472
- const taskId = meta?.persistence?.taskId;
3473
- const sessionId = meta?.sessionId;
3474
- if (!sessionId) {
3475
- throw new Error("Cannot resume session without sessionId");
3476
- }
3477
- if (this.sessionId === sessionId) {
3478
- return {};
3479
- }
3480
- this.logger.info("Resuming session", {
3481
- sessionId,
3482
- taskId,
3483
- taskRunId: meta?.taskRunId,
3484
- cwd: params.cwd
3485
- });
3486
- const mcpServers = parseMcpServers(params);
3487
- const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3488
- const { query: q, session } = await this.initializeQuery({
3489
- cwd: params.cwd,
3490
- permissionMode,
3491
- mcpServers,
3492
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3493
- userProvidedOptions: meta?.claudeCode?.options,
3494
- sessionId,
3495
- isResume: true,
3496
- additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3497
- });
3498
- this.logger.info("Session query initialized, awaiting resumption", {
3499
- sessionId,
3500
- taskId,
4240
+ abortController,
4241
+ accumulatedUsage: {
4242
+ inputTokens: 0,
4243
+ outputTokens: 0,
4244
+ cachedReadTokens: 0,
4245
+ cachedWriteTokens: 0
4246
+ },
4247
+ configOptions: [],
4248
+ promptRunning: false,
4249
+ pendingMessages: /* @__PURE__ */ new Map(),
4250
+ nextPendingOrder: 0,
4251
+ // Custom properties
4252
+ cwd,
4253
+ notificationHistory: [],
3501
4254
  taskRunId: meta?.taskRunId
3502
- });
3503
- session.taskRunId = meta?.taskRunId;
4255
+ };
4256
+ this.session = session;
4257
+ this.sessionId = sessionId;
4258
+ this.logger.info(
4259
+ isResume ? "Session query initialized, awaiting resumption" : "Session query initialized, awaiting initialization",
4260
+ { sessionId, taskId, taskRunId: meta?.taskRunId }
4261
+ );
3504
4262
  try {
3505
4263
  const result = await withTimeout(
3506
4264
  q.initializationResult(),
@@ -3508,231 +4266,181 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3508
4266
  );
3509
4267
  if (result.result === "timeout") {
3510
4268
  throw new Error(
3511
- `Session resumption timed out for sessionId=${sessionId}`
4269
+ `Session ${isResume ? forkSession ? "fork" : "resumption" : "initialization"} timed out for sessionId=${sessionId}`
3512
4270
  );
3513
4271
  }
3514
4272
  } catch (err) {
3515
- this.logger.error("Session resumption failed", {
3516
- sessionId,
3517
- taskId,
3518
- taskRunId: meta?.taskRunId,
3519
- error: err instanceof Error ? err.message : String(err)
3520
- });
4273
+ settingsManager.dispose();
4274
+ this.logger.error(
4275
+ isResume ? forkSession ? "Session fork failed" : "Session resumption failed" : "Session initialization failed",
4276
+ {
4277
+ sessionId,
4278
+ taskId,
4279
+ taskRunId: meta?.taskRunId,
4280
+ error: err instanceof Error ? err.message : String(err)
4281
+ }
4282
+ );
3521
4283
  throw err;
3522
4284
  }
3523
- this.logger.info("Session resumed successfully", {
3524
- sessionId,
3525
- taskId,
3526
- taskRunId: meta?.taskRunId
3527
- });
3528
- this.deferBackgroundFetches(q, sessionId);
3529
- const configOptions = await this.buildConfigOptions();
3530
- return { configOptions };
3531
- }
3532
- async prompt(params) {
3533
- this.session.cancelled = false;
3534
- this.session.interruptReason = void 0;
3535
- await this.broadcastUserMessage(params);
3536
- this.session.input.push(promptToClaude(params));
3537
- return this.processMessages(params.sessionId);
3538
- }
3539
- async setSessionConfigOption(params) {
3540
- const configId = params.configId;
3541
- const value = params.value;
3542
- if (configId === "mode") {
3543
- const modeId = value;
3544
- if (!TWIG_EXECUTION_MODES.includes(modeId)) {
3545
- throw new Error("Invalid Mode");
3546
- }
3547
- this.session.permissionMode = modeId;
3548
- await this.session.query.setPermissionMode(modeId);
3549
- } else if (configId === "model") {
3550
- await this.setModelWithFallback(this.session.query, value);
3551
- this.session.modelId = value;
3552
- } else {
3553
- throw new Error("Unsupported config option");
3554
- }
3555
- await this.emitConfigOptionsUpdate();
3556
- return { configOptions: await this.buildConfigOptions() };
3557
- }
3558
- async interruptSession() {
3559
- await this.session.query.interrupt();
3560
- }
3561
- async extMethod(method, params) {
3562
- if (method === "_posthog/session/resume") {
3563
- const result = await this.resumeSession(
3564
- params
3565
- );
3566
- return {
3567
- _meta: {
3568
- configOptions: result.configOptions
3569
- }
3570
- };
4285
+ if (meta?.taskRunId) {
4286
+ await this.client.extNotification("_posthog/sdk_session", {
4287
+ taskRunId: meta.taskRunId,
4288
+ sessionId,
4289
+ adapter: "claude"
4290
+ });
3571
4291
  }
3572
- throw import_sdk2.RequestError.methodNotFound(method);
3573
- }
3574
- createSession(sessionId, q, input, permissionMode, cwd, abortController) {
3575
- const session = {
3576
- query: q,
3577
- input,
3578
- cancelled: false,
3579
- permissionMode,
3580
- cwd,
3581
- notificationHistory: [],
3582
- abortController
4292
+ const settingsModel = settingsManager.getSettings().model;
4293
+ const modelOptions = await this.getModelConfigOptions();
4294
+ const resolvedModelId = settingsModel || modelOptions.currentModelId;
4295
+ session.modelId = resolvedModelId;
4296
+ if (!isResume) {
4297
+ const resolvedSdkModel = toSdkModelId(resolvedModelId);
4298
+ if (resolvedSdkModel !== DEFAULT_MODEL) {
4299
+ await this.session.query.setModel(resolvedSdkModel);
4300
+ }
4301
+ }
4302
+ const availableModes2 = getAvailableModes();
4303
+ const modes = {
4304
+ currentModeId: permissionMode,
4305
+ availableModes: availableModes2.map((mode) => ({
4306
+ id: mode.id,
4307
+ name: mode.name,
4308
+ description: mode.description ?? void 0
4309
+ }))
4310
+ };
4311
+ const models = {
4312
+ currentModelId: resolvedModelId,
4313
+ availableModels: modelOptions.options.map(
4314
+ (opt) => ({
4315
+ modelId: opt.value,
4316
+ name: opt.name,
4317
+ description: opt.description
4318
+ })
4319
+ )
3583
4320
  };
3584
- this.session = session;
3585
- this.sessionId = sessionId;
3586
- return session;
3587
- }
3588
- async initializeQuery(config) {
3589
- const input = new Pushable();
3590
- const options = buildSessionOptions({
3591
- cwd: config.cwd,
3592
- mcpServers: config.mcpServers,
3593
- permissionMode: config.permissionMode,
3594
- canUseTool: this.createCanUseTool(config.sessionId),
3595
- logger: this.logger,
3596
- systemPrompt: config.systemPrompt,
3597
- userProvidedOptions: config.userProvidedOptions,
3598
- sessionId: config.sessionId,
3599
- isResume: config.isResume,
3600
- additionalDirectories: config.additionalDirectories,
3601
- onModeChange: this.createOnModeChange(config.sessionId),
3602
- onProcessSpawned: this.options?.onProcessSpawned,
3603
- onProcessExited: this.options?.onProcessExited
3604
- });
3605
- const q = (0, import_claude_agent_sdk.query)({ prompt: input, options });
3606
- const abortController = options.abortController;
3607
- const session = this.createSession(
3608
- config.sessionId,
3609
- q,
3610
- input,
3611
- config.permissionMode,
3612
- config.cwd,
3613
- abortController
4321
+ const configOptions = this.buildConfigOptions(permissionMode, modelOptions);
4322
+ session.configOptions = configOptions;
4323
+ if (!creationOpts.skipBackgroundFetches) {
4324
+ this.deferBackgroundFetches(q);
4325
+ }
4326
+ this.logger.info(
4327
+ isResume ? "Session resumed successfully" : "Session created successfully",
4328
+ {
4329
+ sessionId,
4330
+ taskId,
4331
+ taskRunId: meta?.taskRunId
4332
+ }
3614
4333
  );
3615
- return { query: q, input, session };
4334
+ return { sessionId, modes, models, configOptions };
3616
4335
  }
3617
4336
  createCanUseTool(sessionId) {
3618
- return async (toolName, toolInput, { suggestions, toolUseID }) => canUseTool({
4337
+ return async (toolName, toolInput, { suggestions, toolUseID, signal }) => canUseTool({
3619
4338
  session: this.session,
3620
4339
  toolName,
3621
4340
  toolInput,
3622
4341
  toolUseID,
3623
4342
  suggestions,
4343
+ signal,
3624
4344
  client: this.client,
3625
4345
  sessionId,
3626
4346
  fileContentCache: this.fileContentCache,
3627
4347
  logger: this.logger,
3628
- emitConfigOptionsUpdate: () => this.emitConfigOptionsUpdate(sessionId)
4348
+ updateConfigOption: (configId, value) => this.updateConfigOption(configId, value)
3629
4349
  });
3630
4350
  }
3631
- createOnModeChange(sessionId) {
4351
+ createOnModeChange() {
3632
4352
  return async (newMode) => {
3633
4353
  if (this.session) {
3634
4354
  this.session.permissionMode = newMode;
3635
4355
  }
3636
- await this.emitConfigOptionsUpdate(sessionId);
4356
+ await this.updateConfigOption("mode", newMode);
3637
4357
  };
3638
4358
  }
3639
- async buildConfigOptions(modelOptionsOverride) {
3640
- const options = [];
4359
+ buildConfigOptions(currentModeId, modelOptions) {
3641
4360
  const modeOptions = getAvailableModes().map((mode) => ({
3642
4361
  value: mode.id,
3643
4362
  name: mode.name,
3644
4363
  description: mode.description ?? void 0
3645
4364
  }));
3646
- options.push({
3647
- id: "mode",
3648
- name: "Approval Preset",
3649
- type: "select",
3650
- currentValue: this.session.permissionMode,
3651
- options: modeOptions,
3652
- category: "mode",
3653
- description: "Choose an approval and sandboxing preset for your session"
3654
- });
3655
- const modelOptions = modelOptionsOverride ?? await this.getModelConfigOptions(this.session.modelId);
3656
- this.session.modelId = modelOptions.currentModelId;
3657
- options.push({
3658
- id: "model",
3659
- name: "Model",
3660
- type: "select",
3661
- currentValue: modelOptions.currentModelId,
3662
- options: modelOptions.options,
3663
- category: "model",
3664
- description: "Choose which model Claude should use"
3665
- });
3666
- return options;
4365
+ return [
4366
+ {
4367
+ id: "mode",
4368
+ name: "Approval Preset",
4369
+ type: "select",
4370
+ currentValue: currentModeId,
4371
+ options: modeOptions,
4372
+ category: "mode",
4373
+ description: "Choose an approval and sandboxing preset for your session"
4374
+ },
4375
+ {
4376
+ id: "model",
4377
+ name: "Model",
4378
+ type: "select",
4379
+ currentValue: modelOptions.currentModelId,
4380
+ options: modelOptions.options,
4381
+ category: "model",
4382
+ description: "Choose which model Claude should use"
4383
+ }
4384
+ ];
3667
4385
  }
3668
- async emitConfigOptionsUpdate(sessionId) {
3669
- const configOptions = await this.buildConfigOptions();
3670
- const serialized = JSON.stringify(configOptions);
3671
- if (this.lastSentConfigOptions && JSON.stringify(this.lastSentConfigOptions) === serialized) {
3672
- return;
3673
- }
3674
- this.lastSentConfigOptions = configOptions;
4386
+ async sendAvailableCommandsUpdate() {
4387
+ const commands = await this.session.query.supportedCommands();
3675
4388
  await this.client.sessionUpdate({
3676
- sessionId: sessionId ?? this.sessionId,
4389
+ sessionId: this.sessionId,
3677
4390
  update: {
3678
- sessionUpdate: "config_option_update",
3679
- configOptions
4391
+ sessionUpdate: "available_commands_update",
4392
+ availableCommands: getAvailableSlashCommands(commands)
3680
4393
  }
3681
4394
  });
3682
4395
  }
3683
- checkAuthStatus() {
3684
- const backupExists = fs2.existsSync(
3685
- path3.resolve(os3.homedir(), ".claude.json.backup")
3686
- );
3687
- const configExists = fs2.existsSync(
3688
- path3.resolve(os3.homedir(), ".claude.json")
3689
- );
3690
- if (backupExists && !configExists) {
3691
- throw import_sdk2.RequestError.authRequired();
3692
- }
3693
- }
3694
- async trySetModel(q, modelId) {
4396
+ async replaySessionHistory(sessionId) {
3695
4397
  try {
3696
- await this.setModelWithFallback(q, modelId);
3697
- } catch (err) {
3698
- this.logger.warn("Failed to set model", { modelId, error: err });
3699
- }
3700
- }
3701
- async setModelWithFallback(q, modelId) {
3702
- const sdkModelId = toSdkModelId(modelId);
3703
- try {
3704
- await q.setModel(sdkModelId);
3705
- } catch (err) {
3706
- if (sdkModelId === modelId) {
3707
- throw err;
4398
+ const messages = await (0, import_claude_agent_sdk.getSessionMessages)(sessionId, {
4399
+ dir: this.session.cwd
4400
+ });
4401
+ const replayContext = {
4402
+ session: this.session,
4403
+ sessionId,
4404
+ client: this.client,
4405
+ toolUseCache: this.toolUseCache,
4406
+ fileContentCache: this.fileContentCache,
4407
+ logger: this.logger,
4408
+ registerHooks: false
4409
+ };
4410
+ for (const msg of messages) {
4411
+ const sdkMessage = {
4412
+ type: msg.type,
4413
+ message: msg.message,
4414
+ parent_tool_use_id: msg.parent_tool_use_id
4415
+ };
4416
+ await handleUserAssistantMessage(
4417
+ sdkMessage,
4418
+ replayContext
4419
+ );
3708
4420
  }
3709
- await q.setModel(modelId);
4421
+ } catch (err) {
4422
+ this.logger.warn("Failed to replay session history", {
4423
+ sessionId,
4424
+ error: err instanceof Error ? err.message : String(err)
4425
+ });
3710
4426
  }
3711
4427
  }
4428
+ // ================================
4429
+ // EXTENSION METHODS
4430
+ // ================================
3712
4431
  /**
3713
4432
  * Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
3714
4433
  * Both populate caches used later — neither is needed to return configOptions.
3715
4434
  */
3716
- deferBackgroundFetches(q, sessionId) {
4435
+ deferBackgroundFetches(q) {
3717
4436
  Promise.all([
3718
- getAvailableSlashCommands(q),
4437
+ new Promise((resolve4) => setTimeout(resolve4, 10)).then(
4438
+ () => this.sendAvailableCommandsUpdate()
4439
+ ),
3719
4440
  fetchMcpToolMetadata(q, this.logger)
3720
- ]).then(([slashCommands]) => {
3721
- this.sendAvailableCommandsUpdate(sessionId, slashCommands);
3722
- }).catch((err) => {
3723
- this.logger.warn("Failed to fetch deferred session data", { err });
3724
- });
3725
- }
3726
- sendAvailableCommandsUpdate(sessionId, availableCommands) {
3727
- setTimeout(() => {
3728
- this.client.sessionUpdate({
3729
- sessionId,
3730
- update: {
3731
- sessionUpdate: "available_commands_update",
3732
- availableCommands
3733
- }
3734
- });
3735
- }, 0);
4441
+ ]).catch(
4442
+ (err) => this.logger.error("Background fetch failed", { error: err })
4443
+ );
3736
4444
  }
3737
4445
  async broadcastUserMessage(params) {
3738
4446
  for (const chunk of params.prompt) {
@@ -3747,71 +4455,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3747
4455
  this.appendNotification(params.sessionId, notification);
3748
4456
  }
3749
4457
  }
3750
- async processMessages(sessionId) {
3751
- const context = {
3752
- session: this.session,
3753
- sessionId,
3754
- client: this.client,
3755
- toolUseCache: this.toolUseCache,
3756
- fileContentCache: this.fileContentCache,
3757
- logger: this.logger
3758
- };
3759
- while (true) {
3760
- const { value: message, done } = await this.session.query.next();
3761
- if (done || !message) {
3762
- return this.handleSessionEnd();
3763
- }
3764
- const response = await this.handleMessage(message, context);
3765
- if (response) {
3766
- return response;
3767
- }
3768
- }
3769
- }
3770
- handleSessionEnd() {
3771
- if (this.session.cancelled) {
3772
- return {
3773
- stopReason: "cancelled",
3774
- _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
3775
- };
3776
- }
3777
- throw new Error("Session did not end in result");
3778
- }
3779
- async handleMessage(message, context) {
3780
- switch (message.type) {
3781
- case "system":
3782
- await handleSystemMessage(message, context);
3783
- return null;
3784
- case "result": {
3785
- const result = handleResultMessage(message, context);
3786
- if (result.error) throw result.error;
3787
- if (result.shouldStop) {
3788
- return {
3789
- stopReason: result.stopReason
3790
- };
3791
- }
3792
- return null;
3793
- }
3794
- case "stream_event":
3795
- await handleStreamEvent(message, context);
3796
- return null;
3797
- case "user":
3798
- case "assistant": {
3799
- const result = await handleUserAssistantMessage(message, context);
3800
- if (result.error) throw result.error;
3801
- if (result.shouldStop) {
3802
- return { stopReason: "end_turn" };
3803
- }
3804
- return null;
3805
- }
3806
- case "tool_progress":
3807
- case "auth_status":
3808
- case "tool_use_summary":
3809
- return null;
3810
- default:
3811
- unreachable(message, this.logger);
3812
- return null;
3813
- }
3814
- }
3815
4458
  };
3816
4459
 
3817
4460
  // src/adapters/codex/spawn.ts
@@ -4701,8 +5344,8 @@ var SessionLogWriter = class _SessionLogWriter {
4701
5344
  };
4702
5345
 
4703
5346
  // ../git/dist/queries.js
4704
- var fs5 = __toESM(require("fs/promises"), 1);
4705
- var path6 = __toESM(require("path"), 1);
5347
+ var fs6 = __toESM(require("fs/promises"), 1);
5348
+ var path7 = __toESM(require("path"), 1);
4706
5349
 
4707
5350
  // ../../node_modules/simple-git/dist/esm/index.js
4708
5351
  var import_node_buffer = require("buffer");
@@ -4741,8 +5384,8 @@ function pathspec(...paths) {
4741
5384
  cache.set(key, paths);
4742
5385
  return key;
4743
5386
  }
4744
- function isPathSpec(path8) {
4745
- return path8 instanceof String && cache.has(path8);
5387
+ function isPathSpec(path9) {
5388
+ return path9 instanceof String && cache.has(path9);
4746
5389
  }
4747
5390
  function toPaths(pathSpec) {
4748
5391
  return cache.get(pathSpec) || [];
@@ -4831,8 +5474,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
4831
5474
  function forEachLineWithContent(input, callback) {
4832
5475
  return toLinesWithContent(input, true).map((line) => callback(line));
4833
5476
  }
4834
- function folderExists(path8) {
4835
- return (0, import_file_exists.exists)(path8, import_file_exists.FOLDER);
5477
+ function folderExists(path9) {
5478
+ return (0, import_file_exists.exists)(path9, import_file_exists.FOLDER);
4836
5479
  }
4837
5480
  function append(target, item) {
4838
5481
  if (Array.isArray(target)) {
@@ -5236,8 +5879,8 @@ function checkIsRepoRootTask() {
5236
5879
  commands,
5237
5880
  format: "utf-8",
5238
5881
  onError,
5239
- parser(path8) {
5240
- return /^\.(git)?$/.test(path8.trim());
5882
+ parser(path9) {
5883
+ return /^\.(git)?$/.test(path9.trim());
5241
5884
  }
5242
5885
  };
5243
5886
  }
@@ -5671,11 +6314,11 @@ function parseGrep(grep) {
5671
6314
  const paths = /* @__PURE__ */ new Set();
5672
6315
  const results = {};
5673
6316
  forEachLineWithContent(grep, (input) => {
5674
- const [path8, line, preview] = input.split(NULL);
5675
- paths.add(path8);
5676
- (results[path8] = results[path8] || []).push({
6317
+ const [path9, line, preview] = input.split(NULL);
6318
+ paths.add(path9);
6319
+ (results[path9] = results[path9] || []).push({
5677
6320
  line: asNumber(line),
5678
- path: path8,
6321
+ path: path9,
5679
6322
  preview
5680
6323
  });
5681
6324
  });
@@ -6440,14 +7083,14 @@ var init_hash_object = __esm({
6440
7083
  init_task();
6441
7084
  }
6442
7085
  });
6443
- function parseInit(bare, path8, text2) {
7086
+ function parseInit(bare, path9, text2) {
6444
7087
  const response = String(text2).trim();
6445
7088
  let result;
6446
7089
  if (result = initResponseRegex.exec(response)) {
6447
- return new InitSummary(bare, path8, false, result[1]);
7090
+ return new InitSummary(bare, path9, false, result[1]);
6448
7091
  }
6449
7092
  if (result = reInitResponseRegex.exec(response)) {
6450
- return new InitSummary(bare, path8, true, result[1]);
7093
+ return new InitSummary(bare, path9, true, result[1]);
6451
7094
  }
6452
7095
  let gitDir = "";
6453
7096
  const tokens = response.split(" ");
@@ -6458,7 +7101,7 @@ function parseInit(bare, path8, text2) {
6458
7101
  break;
6459
7102
  }
6460
7103
  }
6461
- return new InitSummary(bare, path8, /^re/i.test(response), gitDir);
7104
+ return new InitSummary(bare, path9, /^re/i.test(response), gitDir);
6462
7105
  }
6463
7106
  var InitSummary;
6464
7107
  var initResponseRegex;
@@ -6467,9 +7110,9 @@ var init_InitSummary = __esm({
6467
7110
  "src/lib/responses/InitSummary.ts"() {
6468
7111
  "use strict";
6469
7112
  InitSummary = class {
6470
- constructor(bare, path8, existing, gitDir) {
7113
+ constructor(bare, path9, existing, gitDir) {
6471
7114
  this.bare = bare;
6472
- this.path = path8;
7115
+ this.path = path9;
6473
7116
  this.existing = existing;
6474
7117
  this.gitDir = gitDir;
6475
7118
  }
@@ -6481,7 +7124,7 @@ var init_InitSummary = __esm({
6481
7124
  function hasBareCommand(command) {
6482
7125
  return command.includes(bareCommand);
6483
7126
  }
6484
- function initTask(bare = false, path8, customArgs) {
7127
+ function initTask(bare = false, path9, customArgs) {
6485
7128
  const commands = ["init", ...customArgs];
6486
7129
  if (bare && !hasBareCommand(commands)) {
6487
7130
  commands.splice(1, 0, bareCommand);
@@ -6490,7 +7133,7 @@ function initTask(bare = false, path8, customArgs) {
6490
7133
  commands,
6491
7134
  format: "utf-8",
6492
7135
  parser(text2) {
6493
- return parseInit(commands.includes("--bare"), path8, text2);
7136
+ return parseInit(commands.includes("--bare"), path9, text2);
6494
7137
  }
6495
7138
  };
6496
7139
  }
@@ -7306,12 +7949,12 @@ var init_FileStatusSummary = __esm({
7306
7949
  "use strict";
7307
7950
  fromPathRegex = /^(.+)\0(.+)$/;
7308
7951
  FileStatusSummary = class {
7309
- constructor(path8, index, working_dir) {
7310
- this.path = path8;
7952
+ constructor(path9, index, working_dir) {
7953
+ this.path = path9;
7311
7954
  this.index = index;
7312
7955
  this.working_dir = working_dir;
7313
7956
  if (index === "R" || working_dir === "R") {
7314
- const detail = fromPathRegex.exec(path8) || [null, path8, path8];
7957
+ const detail = fromPathRegex.exec(path9) || [null, path9, path9];
7315
7958
  this.from = detail[2] || "";
7316
7959
  this.path = detail[1] || "";
7317
7960
  }
@@ -7342,14 +7985,14 @@ function splitLine(result, lineStr) {
7342
7985
  default:
7343
7986
  return;
7344
7987
  }
7345
- function data(index, workingDir, path8) {
7988
+ function data(index, workingDir, path9) {
7346
7989
  const raw = `${index}${workingDir}`;
7347
7990
  const handler = parsers6.get(raw);
7348
7991
  if (handler) {
7349
- handler(result, path8);
7992
+ handler(result, path9);
7350
7993
  }
7351
7994
  if (raw !== "##" && raw !== "!!") {
7352
- result.files.push(new FileStatusSummary(path8, index, workingDir));
7995
+ result.files.push(new FileStatusSummary(path9, index, workingDir));
7353
7996
  }
7354
7997
  }
7355
7998
  }
@@ -7662,9 +8305,9 @@ var init_simple_git_api = __esm({
7662
8305
  next
7663
8306
  );
7664
8307
  }
7665
- hashObject(path8, write) {
8308
+ hashObject(path9, write) {
7666
8309
  return this._runTask(
7667
- hashObjectTask(path8, write === true),
8310
+ hashObjectTask(path9, write === true),
7668
8311
  trailingFunctionArgument(arguments)
7669
8312
  );
7670
8313
  }
@@ -8017,8 +8660,8 @@ var init_branch = __esm({
8017
8660
  }
8018
8661
  });
8019
8662
  function toPath(input) {
8020
- const path8 = input.trim().replace(/^["']|["']$/g, "");
8021
- return path8 && (0, import_node_path3.normalize)(path8);
8663
+ const path9 = input.trim().replace(/^["']|["']$/g, "");
8664
+ return path9 && (0, import_node_path3.normalize)(path9);
8022
8665
  }
8023
8666
  var parseCheckIgnore;
8024
8667
  var init_CheckIgnore = __esm({
@@ -8332,8 +8975,8 @@ __export(sub_module_exports, {
8332
8975
  subModuleTask: () => subModuleTask,
8333
8976
  updateSubModuleTask: () => updateSubModuleTask
8334
8977
  });
8335
- function addSubModuleTask(repo, path8) {
8336
- return subModuleTask(["add", repo, path8]);
8978
+ function addSubModuleTask(repo, path9) {
8979
+ return subModuleTask(["add", repo, path9]);
8337
8980
  }
8338
8981
  function initSubModuleTask(customArgs) {
8339
8982
  return subModuleTask(["init", ...customArgs]);
@@ -8663,8 +9306,8 @@ var require_git = __commonJS2({
8663
9306
  }
8664
9307
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
8665
9308
  };
8666
- Git2.prototype.submoduleAdd = function(repo, path8, then) {
8667
- return this._runTask(addSubModuleTask2(repo, path8), trailingFunctionArgument2(arguments));
9309
+ Git2.prototype.submoduleAdd = function(repo, path9, then) {
9310
+ return this._runTask(addSubModuleTask2(repo, path9), trailingFunctionArgument2(arguments));
8668
9311
  };
8669
9312
  Git2.prototype.submoduleUpdate = function(args, then) {
8670
9313
  return this._runTask(
@@ -9589,8 +10232,8 @@ var Saga = class {
9589
10232
 
9590
10233
  // ../git/dist/sagas/tree.js
9591
10234
  var import_node_fs3 = require("fs");
9592
- var fs6 = __toESM(require("fs/promises"), 1);
9593
- var path7 = __toESM(require("path"), 1);
10235
+ var fs7 = __toESM(require("fs/promises"), 1);
10236
+ var path8 = __toESM(require("path"), 1);
9594
10237
  var tar = __toESM(require("tar"), 1);
9595
10238
 
9596
10239
  // ../git/dist/git-saga.js
@@ -9616,14 +10259,14 @@ var CaptureTreeSaga = class extends GitSaga {
9616
10259
  tempIndexPath = null;
9617
10260
  async executeGitOperations(input) {
9618
10261
  const { baseDir, lastTreeHash, archivePath, signal } = input;
9619
- const tmpDir = path7.join(baseDir, ".git", "twig-tmp");
10262
+ const tmpDir = path8.join(baseDir, ".git", "twig-tmp");
9620
10263
  await this.step({
9621
10264
  name: "create_tmp_dir",
9622
- execute: () => fs6.mkdir(tmpDir, { recursive: true }),
10265
+ execute: () => fs7.mkdir(tmpDir, { recursive: true }),
9623
10266
  rollback: async () => {
9624
10267
  }
9625
10268
  });
9626
- this.tempIndexPath = path7.join(tmpDir, `index-${Date.now()}`);
10269
+ this.tempIndexPath = path8.join(tmpDir, `index-${Date.now()}`);
9627
10270
  const tempIndexGit = this.git.env({
9628
10271
  ...process.env,
9629
10272
  GIT_INDEX_FILE: this.tempIndexPath
@@ -9633,7 +10276,7 @@ var CaptureTreeSaga = class extends GitSaga {
9633
10276
  execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
9634
10277
  rollback: async () => {
9635
10278
  if (this.tempIndexPath) {
9636
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10279
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9637
10280
  });
9638
10281
  }
9639
10282
  }
@@ -9642,7 +10285,7 @@ var CaptureTreeSaga = class extends GitSaga {
9642
10285
  const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
9643
10286
  if (lastTreeHash && treeHash === lastTreeHash) {
9644
10287
  this.log.debug("No changes since last capture", { treeHash });
9645
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10288
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9646
10289
  });
9647
10290
  return { snapshot: null, changed: false };
9648
10291
  }
@@ -9654,7 +10297,7 @@ var CaptureTreeSaga = class extends GitSaga {
9654
10297
  }
9655
10298
  });
9656
10299
  const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
9657
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10300
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9658
10301
  });
9659
10302
  const snapshot = {
9660
10303
  treeHash,
@@ -9678,15 +10321,15 @@ var CaptureTreeSaga = class extends GitSaga {
9678
10321
  if (filesToArchive.length === 0) {
9679
10322
  return void 0;
9680
10323
  }
9681
- const existingFiles = filesToArchive.filter((f) => (0, import_node_fs3.existsSync)(path7.join(baseDir, f)));
10324
+ const existingFiles = filesToArchive.filter((f) => (0, import_node_fs3.existsSync)(path8.join(baseDir, f)));
9682
10325
  if (existingFiles.length === 0) {
9683
10326
  return void 0;
9684
10327
  }
9685
10328
  await this.step({
9686
10329
  name: "create_archive",
9687
10330
  execute: async () => {
9688
- const archiveDir = path7.dirname(archivePath);
9689
- await fs6.mkdir(archiveDir, { recursive: true });
10331
+ const archiveDir = path8.dirname(archivePath);
10332
+ await fs7.mkdir(archiveDir, { recursive: true });
9690
10333
  await tar.create({
9691
10334
  gzip: true,
9692
10335
  file: archivePath,
@@ -9694,7 +10337,7 @@ var CaptureTreeSaga = class extends GitSaga {
9694
10337
  }, existingFiles);
9695
10338
  },
9696
10339
  rollback: async () => {
9697
- await fs6.rm(archivePath, { force: true }).catch(() => {
10340
+ await fs7.rm(archivePath, { force: true }).catch(() => {
9698
10341
  });
9699
10342
  }
9700
10343
  });
@@ -9793,9 +10436,9 @@ var ApplyTreeSaga = class extends GitSaga {
9793
10436
  const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
9794
10437
  await this.readOnlyStep("backup_existing_files", async () => {
9795
10438
  for (const filePath of filesToExtract) {
9796
- const fullPath = path7.join(baseDir, filePath);
10439
+ const fullPath = path8.join(baseDir, filePath);
9797
10440
  try {
9798
- const content = await fs6.readFile(fullPath);
10441
+ const content = await fs7.readFile(fullPath);
9799
10442
  this.fileBackups.set(filePath, content);
9800
10443
  } catch {
9801
10444
  }
@@ -9812,16 +10455,16 @@ var ApplyTreeSaga = class extends GitSaga {
9812
10455
  },
9813
10456
  rollback: async () => {
9814
10457
  for (const filePath of this.extractedFiles) {
9815
- const fullPath = path7.join(baseDir, filePath);
10458
+ const fullPath = path8.join(baseDir, filePath);
9816
10459
  const backup = this.fileBackups.get(filePath);
9817
10460
  if (backup) {
9818
- const dir = path7.dirname(fullPath);
9819
- await fs6.mkdir(dir, { recursive: true }).catch(() => {
10461
+ const dir = path8.dirname(fullPath);
10462
+ await fs7.mkdir(dir, { recursive: true }).catch(() => {
9820
10463
  });
9821
- await fs6.writeFile(fullPath, backup).catch(() => {
10464
+ await fs7.writeFile(fullPath, backup).catch(() => {
9822
10465
  });
9823
10466
  } else {
9824
- await fs6.rm(fullPath, { force: true }).catch(() => {
10467
+ await fs7.rm(fullPath, { force: true }).catch(() => {
9825
10468
  });
9826
10469
  }
9827
10470
  }
@@ -9829,10 +10472,10 @@ var ApplyTreeSaga = class extends GitSaga {
9829
10472
  });
9830
10473
  }
9831
10474
  for (const change of changes.filter((c) => c.status === "D")) {
9832
- const fullPath = path7.join(baseDir, change.path);
10475
+ const fullPath = path8.join(baseDir, change.path);
9833
10476
  const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
9834
10477
  try {
9835
- return await fs6.readFile(fullPath);
10478
+ return await fs7.readFile(fullPath);
9836
10479
  } catch {
9837
10480
  return null;
9838
10481
  }
@@ -9840,15 +10483,15 @@ var ApplyTreeSaga = class extends GitSaga {
9840
10483
  await this.step({
9841
10484
  name: `delete_${change.path}`,
9842
10485
  execute: async () => {
9843
- await fs6.rm(fullPath, { force: true });
10486
+ await fs7.rm(fullPath, { force: true });
9844
10487
  this.log.debug(`Deleted file: ${change.path}`);
9845
10488
  },
9846
10489
  rollback: async () => {
9847
10490
  if (backupContent) {
9848
- const dir = path7.dirname(fullPath);
9849
- await fs6.mkdir(dir, { recursive: true }).catch(() => {
10491
+ const dir = path8.dirname(fullPath);
10492
+ await fs7.mkdir(dir, { recursive: true }).catch(() => {
9850
10493
  });
9851
- await fs6.writeFile(fullPath, backupContent).catch(() => {
10494
+ await fs7.writeFile(fullPath, backupContent).catch(() => {
9852
10495
  });
9853
10496
  }
9854
10497
  }