@posthog/agent 2.1.131 → 2.1.138

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 (40) 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 +118 -165
  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/session/jsonl-hydration.d.ts +45 -0
  7. package/dist/adapters/claude/session/jsonl-hydration.js +444 -0
  8. package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -0
  9. package/dist/adapters/claude/tools.js +21 -11
  10. package/dist/adapters/claude/tools.js.map +1 -1
  11. package/dist/agent.d.ts +2 -0
  12. package/dist/agent.js +1261 -608
  13. package/dist/agent.js.map +1 -1
  14. package/dist/posthog-api.js +6 -2
  15. package/dist/posthog-api.js.map +1 -1
  16. package/dist/server/agent-server.js +1307 -657
  17. package/dist/server/agent-server.js.map +1 -1
  18. package/dist/server/bin.cjs +1285 -637
  19. package/dist/server/bin.cjs.map +1 -1
  20. package/package.json +8 -4
  21. package/src/adapters/base-acp-agent.ts +6 -3
  22. package/src/adapters/claude/UPSTREAM.md +63 -0
  23. package/src/adapters/claude/claude-agent.ts +682 -421
  24. package/src/adapters/claude/conversion/sdk-to-acp.ts +249 -85
  25. package/src/adapters/claude/conversion/tool-use-to-acp.ts +176 -150
  26. package/src/adapters/claude/hooks.ts +53 -1
  27. package/src/adapters/claude/permissions/permission-handlers.ts +39 -21
  28. package/src/adapters/claude/session/commands.ts +13 -9
  29. package/src/adapters/claude/session/jsonl-hydration.test.ts +903 -0
  30. package/src/adapters/claude/session/jsonl-hydration.ts +581 -0
  31. package/src/adapters/claude/session/mcp-config.ts +2 -5
  32. package/src/adapters/claude/session/options.ts +58 -6
  33. package/src/adapters/claude/session/settings.ts +326 -0
  34. package/src/adapters/claude/tools.ts +1 -0
  35. package/src/adapters/claude/types.ts +38 -0
  36. package/src/adapters/codex/spawn.ts +1 -1
  37. package/src/agent.ts +4 -0
  38. package/src/execution-mode.ts +26 -10
  39. package/src/server/agent-server.test.ts +41 -1
  40. package/src/utils/common.ts +1 -1
@@ -513,7 +513,7 @@ var require_has_flag = __commonJS({
513
513
  var require_supports_color = __commonJS({
514
514
  "../../node_modules/supports-color/index.js"(exports, module) {
515
515
  "use strict";
516
- var os4 = __require("os");
516
+ var os5 = __require("os");
517
517
  var tty = __require("tty");
518
518
  var hasFlag = require_has_flag();
519
519
  var { env } = process;
@@ -561,7 +561,7 @@ var require_supports_color = __commonJS({
561
561
  return min;
562
562
  }
563
563
  if (process.platform === "win32") {
564
- const osRelease = os4.release().split(".");
564
+ const osRelease = os5.release().split(".");
565
565
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
566
566
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
567
567
  }
@@ -809,10 +809,10 @@ var require_src2 = __commonJS({
809
809
  var fs_1 = __require("fs");
810
810
  var debug_1 = __importDefault(require_src());
811
811
  var log = debug_1.default("@kwsites/file-exists");
812
- function check(path8, isFile, isDirectory) {
813
- log(`checking %s`, path8);
812
+ function check(path9, isFile, isDirectory) {
813
+ log(`checking %s`, path9);
814
814
  try {
815
- const stat = fs_1.statSync(path8);
815
+ const stat = fs_1.statSync(path9);
816
816
  if (stat.isFile() && isFile) {
817
817
  log(`[OK] path represents a file`);
818
818
  return true;
@@ -832,8 +832,8 @@ var require_src2 = __commonJS({
832
832
  throw e;
833
833
  }
834
834
  }
835
- function exists2(path8, type = exports.READABLE) {
836
- return check(path8, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
835
+ function exists2(path9, type = exports.READABLE) {
836
+ return check(path9, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
837
837
  }
838
838
  exports.exists = exists2;
839
839
  exports.FILE = 1;
@@ -908,7 +908,7 @@ import { Hono } from "hono";
908
908
  // package.json
909
909
  var package_default = {
910
910
  name: "@posthog/agent",
911
- version: "2.1.131",
911
+ version: "2.1.138",
912
912
  repository: "https://github.com/PostHog/twig",
913
913
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
914
914
  exports: {
@@ -948,6 +948,10 @@ var package_default = {
948
948
  types: "./dist/adapters/claude/conversion/tool-use-to-acp.d.ts",
949
949
  import: "./dist/adapters/claude/conversion/tool-use-to-acp.js"
950
950
  },
951
+ "./adapters/claude/session/jsonl-hydration": {
952
+ types: "./dist/adapters/claude/session/jsonl-hydration.d.ts",
953
+ import: "./dist/adapters/claude/session/jsonl-hydration.js"
954
+ },
951
955
  "./server": {
952
956
  types: "./dist/server/agent-server.d.ts",
953
957
  import: "./dist/server/agent-server.js"
@@ -984,7 +988,6 @@ var package_default = {
984
988
  "@twig/git": "workspace:*",
985
989
  "@types/bun": "latest",
986
990
  "@types/tar": "^6.1.13",
987
- minimatch: "^10.0.3",
988
991
  msw: "^2.12.7",
989
992
  tsup: "^8.5.1",
990
993
  tsx: "^4.20.6",
@@ -1005,6 +1008,7 @@ var package_default = {
1005
1008
  commander: "^14.0.2",
1006
1009
  hono: "^4.11.7",
1007
1010
  jsonwebtoken: "^9.0.2",
1011
+ minimatch: "^10.0.3",
1008
1012
  tar: "^7.5.0",
1009
1013
  uuid: "13.0.0",
1010
1014
  "yoga-wasm-web": "^0.3.3",
@@ -1285,13 +1289,16 @@ function nodeWritableToWebWritable(nodeStream) {
1285
1289
  }
1286
1290
 
1287
1291
  // src/adapters/claude/claude-agent.ts
1288
- import * as fs2 from "fs";
1289
- import * as os3 from "os";
1290
- import * as path3 from "path";
1292
+ import { randomUUID } from "crypto";
1293
+ import * as fs3 from "fs";
1294
+ import * as os4 from "os";
1295
+ import * as path4 from "path";
1291
1296
  import {
1292
1297
  RequestError as RequestError2
1293
1298
  } from "@agentclientprotocol/sdk";
1294
1299
  import {
1300
+ getSessionMessages,
1301
+ listSessions,
1295
1302
  query
1296
1303
  } from "@anthropic-ai/claude-agent-sdk";
1297
1304
  import { v7 as uuidv7 } from "uuid";
@@ -1313,7 +1320,7 @@ function unreachable(value, logger) {
1313
1320
  try {
1314
1321
  valueAsString = JSON.stringify(value);
1315
1322
  } catch {
1316
- valueAsString = value;
1323
+ valueAsString = String(value);
1317
1324
  }
1318
1325
  logger.error(`Unexpected case: ${valueAsString}`);
1319
1326
  }
@@ -1385,19 +1392,20 @@ var BaseAcpAgent = class {
1385
1392
  }
1386
1393
  async cancel(params) {
1387
1394
  if (this.sessionId !== params.sessionId) {
1388
- throw new Error("Session not found");
1395
+ throw new Error("Session ID mismatch");
1389
1396
  }
1390
1397
  this.session.cancelled = true;
1391
1398
  const meta = params._meta;
1392
1399
  if (meta?.interruptReason) {
1393
1400
  this.session.interruptReason = meta.interruptReason;
1394
1401
  }
1395
- await this.interruptSession();
1402
+ await this.interrupt();
1396
1403
  }
1397
1404
  async closeSession() {
1398
1405
  try {
1399
1406
  this.session.abortController.abort();
1400
1407
  await this.cancel({ sessionId: this.sessionId });
1408
+ this.session.settingsManager.dispose();
1401
1409
  this.logger.info("Closed session", { sessionId: this.sessionId });
1402
1410
  } catch (err) {
1403
1411
  this.logger.warn("Failed to close session", {
@@ -1572,8 +1580,8 @@ var ToolContentBuilder = class {
1572
1580
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
1573
1581
  return this;
1574
1582
  }
1575
- diff(path8, oldText, newText) {
1576
- this.items.push({ type: "diff", path: path8, oldText, newText });
1583
+ diff(path9, oldText, newText) {
1584
+ this.items.push({ type: "diff", path: path9, oldText, newText });
1577
1585
  return this;
1578
1586
  }
1579
1587
  build() {
@@ -1593,7 +1601,7 @@ var registerHookCallback = (toolUseID, {
1593
1601
  onPostToolUseHook
1594
1602
  };
1595
1603
  };
1596
- var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
1604
+ var createPostToolUseHook = ({ onModeChange, logger }) => async (input, toolUseID) => {
1597
1605
  if (input.hook_event_name === "PostToolUse") {
1598
1606
  const toolName = input.tool_name;
1599
1607
  if (onModeChange && toolName === "EnterPlanMode") {
@@ -1608,11 +1616,54 @@ var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
1608
1616
  input.tool_response
1609
1617
  );
1610
1618
  delete toolUseCallbacks[toolUseID];
1619
+ } else {
1620
+ logger?.error(
1621
+ `No onPostToolUseHook found for tool use ID: ${toolUseID}`
1622
+ );
1623
+ delete toolUseCallbacks[toolUseID];
1611
1624
  }
1612
1625
  }
1613
1626
  }
1614
1627
  return { continue: true };
1615
1628
  };
1629
+ var createPreToolUseHook = (settingsManager, logger) => async (input, _toolUseID) => {
1630
+ if (input.hook_event_name !== "PreToolUse") {
1631
+ return { continue: true };
1632
+ }
1633
+ const toolName = input.tool_name;
1634
+ const toolInput = input.tool_input;
1635
+ const permissionCheck = settingsManager.checkPermission(
1636
+ toolName,
1637
+ toolInput
1638
+ );
1639
+ if (permissionCheck.decision !== "ask") {
1640
+ logger.info(
1641
+ `[PreToolUseHook] Tool: ${toolName}, Decision: ${permissionCheck.decision}, Rule: ${permissionCheck.rule}`
1642
+ );
1643
+ }
1644
+ switch (permissionCheck.decision) {
1645
+ case "allow":
1646
+ return {
1647
+ continue: true,
1648
+ hookSpecificOutput: {
1649
+ hookEventName: "PreToolUse",
1650
+ permissionDecision: "allow",
1651
+ permissionDecisionReason: `Allowed by settings rule: ${permissionCheck.rule}`
1652
+ }
1653
+ };
1654
+ case "deny":
1655
+ return {
1656
+ continue: true,
1657
+ hookSpecificOutput: {
1658
+ hookEventName: "PreToolUse",
1659
+ permissionDecision: "deny",
1660
+ permissionDecisionReason: `Denied by settings rule: ${permissionCheck.rule}`
1661
+ }
1662
+ };
1663
+ default:
1664
+ return { continue: true };
1665
+ }
1666
+ };
1616
1667
 
1617
1668
  // src/adapters/claude/mcp/tool-metadata.ts
1618
1669
  var mcpToolMetadataCache = /* @__PURE__ */ new Map();
@@ -1689,85 +1740,14 @@ var SYSTEM_REMINDER = `
1689
1740
  <system-reminder>
1690
1741
  Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
1691
1742
  </system-reminder>`;
1692
- function replaceAndCalculateLocation(fileContent, edits) {
1693
- let currentContent = fileContent;
1694
- const randomHex = Array.from(crypto.getRandomValues(new Uint8Array(5))).map((b) => b.toString(16).padStart(2, "0")).join("");
1695
- const markerPrefix = `__REPLACE_MARKER_${randomHex}_`;
1696
- let markerCounter = 0;
1697
- const markers = [];
1698
- for (const edit of edits) {
1699
- if (edit.oldText === "") {
1700
- throw new Error(
1701
- `The provided \`old_string\` is empty.
1702
-
1703
- No edits were applied.`
1704
- );
1705
- }
1706
- if (edit.replaceAll) {
1707
- const parts = [];
1708
- let lastIndex = 0;
1709
- let searchIndex = 0;
1710
- while (true) {
1711
- const index = currentContent.indexOf(edit.oldText, searchIndex);
1712
- if (index === -1) {
1713
- if (searchIndex === 0) {
1714
- throw new Error(
1715
- `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
1716
-
1717
- No edits were applied.`
1718
- );
1719
- }
1720
- break;
1721
- }
1722
- parts.push(currentContent.substring(lastIndex, index));
1723
- const marker = `${markerPrefix}${markerCounter++}__`;
1724
- markers.push(marker);
1725
- parts.push(marker + edit.newText);
1726
- lastIndex = index + edit.oldText.length;
1727
- searchIndex = lastIndex;
1728
- }
1729
- parts.push(currentContent.substring(lastIndex));
1730
- currentContent = parts.join("");
1731
- } else {
1732
- const index = currentContent.indexOf(edit.oldText);
1733
- if (index === -1) {
1734
- throw new Error(
1735
- `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
1736
-
1737
- No edits were applied.`
1738
- );
1739
- } else {
1740
- const marker = `${markerPrefix}${markerCounter++}__`;
1741
- markers.push(marker);
1742
- currentContent = currentContent.substring(0, index) + marker + edit.newText + currentContent.substring(index + edit.oldText.length);
1743
- }
1744
- }
1745
- }
1746
- const lineNumbers = [];
1747
- for (const marker of markers) {
1748
- const index = currentContent.indexOf(marker);
1749
- if (index !== -1) {
1750
- const lineNumber = Math.max(
1751
- 0,
1752
- currentContent.substring(0, index).split(/\r\n|\r|\n/).length - 1
1753
- );
1754
- lineNumbers.push(lineNumber);
1755
- }
1756
- }
1757
- let finalContent = currentContent;
1758
- for (const marker of markers) {
1759
- finalContent = finalContent.replace(marker, "");
1760
- }
1761
- const uniqueLineNumbers = [...new Set(lineNumbers)].sort();
1762
- return { newContent: finalContent, lineNumbers: uniqueLineNumbers };
1763
- }
1764
- function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ debug: false, prefix: "[ClaudeTools]" })) {
1743
+ function toolInfoFromToolUse(toolUse, options) {
1765
1744
  const name = toolUse.name;
1766
1745
  const input = toolUse.input;
1767
1746
  switch (name) {
1768
1747
  case "Task":
1748
+ case "Agent":
1769
1749
  return {
1770
- title: input?.description ? String(input.description) : "Task",
1750
+ title: input?.description ? String(input.description) : name,
1771
1751
  kind: "think",
1772
1752
  content: input?.prompt ? toolContent().text(String(input.prompt)).build() : []
1773
1753
  };
@@ -1786,6 +1766,13 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1786
1766
  locations: input?.notebook_path ? [{ path: String(input.notebook_path) }] : []
1787
1767
  };
1788
1768
  case "Bash":
1769
+ if (options?.supportsTerminalOutput && options?.toolUseId) {
1770
+ return {
1771
+ title: input?.description ? String(input.description) : "Execute command",
1772
+ kind: "execute",
1773
+ content: [{ type: "terminal", terminalId: options.toolUseId }]
1774
+ };
1775
+ }
1789
1776
  return {
1790
1777
  title: input?.description ? String(input.description) : "Execute command",
1791
1778
  kind: "execute",
@@ -1806,11 +1793,11 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1806
1793
  case "Read": {
1807
1794
  let limit = "";
1808
1795
  const inputLimit = input?.limit;
1809
- const inputOffset = input?.offset ?? 0;
1796
+ const inputOffset = input?.offset ?? 1;
1810
1797
  if (inputLimit) {
1811
- limit = ` (${inputOffset + 1} - ${inputOffset + inputLimit})`;
1812
- } else if (inputOffset) {
1813
- limit = ` (from line ${inputOffset + 1})`;
1798
+ limit = ` (${inputOffset} - ${inputOffset + inputLimit - 1})`;
1799
+ } else if (inputOffset > 1) {
1800
+ limit = ` (from line ${inputOffset})`;
1814
1801
  }
1815
1802
  return {
1816
1803
  title: `Read ${input?.file_path ? String(input.file_path) : "File"}${limit}`,
@@ -1832,39 +1819,21 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1832
1819
  locations: []
1833
1820
  };
1834
1821
  case "Edit": {
1835
- const path8 = input?.file_path ? String(input.file_path) : void 0;
1836
- let oldText = input?.old_string ? String(input.old_string) : null;
1837
- let newText = input?.new_string ? String(input.new_string) : "";
1838
- let affectedLines = [];
1839
- if (path8 && oldText) {
1840
- try {
1841
- const oldContent = cachedFileContent[path8] || "";
1842
- const newContent = replaceAndCalculateLocation(oldContent, [
1843
- {
1844
- oldText,
1845
- newText,
1846
- replaceAll: false
1847
- }
1848
- ]);
1849
- oldText = oldContent;
1850
- newText = newContent.newContent;
1851
- affectedLines = newContent.lineNumbers;
1852
- } catch (e) {
1853
- logger.error("Failed to edit file", e);
1854
- }
1855
- }
1822
+ const path9 = input?.file_path ? String(input.file_path) : void 0;
1823
+ const oldText = input?.old_string ? String(input.old_string) : null;
1824
+ const newText = input?.new_string ? String(input.new_string) : "";
1856
1825
  return {
1857
- title: path8 ? `Edit \`${path8}\`` : "Edit",
1826
+ title: path9 ? `Edit \`${path9}\`` : "Edit",
1858
1827
  kind: "edit",
1859
- content: input && path8 ? [
1828
+ content: input && path9 ? [
1860
1829
  {
1861
1830
  type: "diff",
1862
- path: path8,
1831
+ path: path9,
1863
1832
  oldText,
1864
1833
  newText
1865
1834
  }
1866
1835
  ] : [],
1867
- locations: path8 ? affectedLines.length > 0 ? affectedLines.map((line) => ({ line, path: path8 })) : [{ path: path8 }] : []
1836
+ locations: path9 ? [{ path: path9 }] : []
1868
1837
  };
1869
1838
  }
1870
1839
  case "Write": {
@@ -1918,10 +1887,10 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1918
1887
  }
1919
1888
  if (input?.output_mode) {
1920
1889
  switch (input.output_mode) {
1921
- case "FilesWithMatches":
1890
+ case "files_with_matches":
1922
1891
  label += " -l";
1923
1892
  break;
1924
- case "Count":
1893
+ case "count":
1925
1894
  label += " -c";
1926
1895
  break;
1927
1896
  default:
@@ -1940,7 +1909,9 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1940
1909
  if (input?.multiline) {
1941
1910
  label += " -P";
1942
1911
  }
1943
- label += ` "${input?.pattern ? String(input.pattern) : ""}"`;
1912
+ if (input?.pattern) {
1913
+ label += ` "${String(input.pattern)}"`;
1914
+ }
1944
1915
  if (input?.path) {
1945
1916
  label += ` ${String(input.path)}`;
1946
1917
  }
@@ -2034,7 +2005,49 @@ function mcpToolInfo(name, _input) {
2034
2005
  content: []
2035
2006
  };
2036
2007
  }
2037
- function toolUpdateFromToolResult(toolResult, toolUse) {
2008
+ function toolUpdateFromEditToolResponse(toolResponse) {
2009
+ if (!toolResponse || typeof toolResponse !== "object") return null;
2010
+ const response = toolResponse;
2011
+ const patches = response.structuredPatch;
2012
+ if (!Array.isArray(patches) || patches.length === 0) return null;
2013
+ const content = [];
2014
+ const locations = [];
2015
+ for (const patch of patches) {
2016
+ if (!patch.hunks || patch.hunks.length === 0) continue;
2017
+ const filePath = patch.newFileName || patch.oldFileName;
2018
+ const oldLines = [];
2019
+ const newLines = [];
2020
+ for (const hunk of patch.hunks) {
2021
+ for (const line of hunk.lines) {
2022
+ if (line.startsWith("-")) {
2023
+ oldLines.push(line.slice(1));
2024
+ } else if (line.startsWith("+")) {
2025
+ newLines.push(line.slice(1));
2026
+ } else if (line.startsWith(" ")) {
2027
+ oldLines.push(line.slice(1));
2028
+ newLines.push(line.slice(1));
2029
+ }
2030
+ }
2031
+ }
2032
+ content.push({
2033
+ type: "diff",
2034
+ path: filePath,
2035
+ oldText: oldLines.join("\n"),
2036
+ newText: newLines.join("\n")
2037
+ });
2038
+ const firstHunk = patch.hunks[0];
2039
+ locations.push({
2040
+ path: filePath,
2041
+ line: firstHunk.newStart
2042
+ });
2043
+ }
2044
+ if (content.length === 0) return null;
2045
+ return { content, locations };
2046
+ }
2047
+ function toolUpdateFromToolResult(toolResult, toolUse, options) {
2048
+ if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
2049
+ return toAcpContentUpdate(toolResult.content, true);
2050
+ }
2038
2051
  switch (toolUse?.name) {
2039
2052
  case "Read":
2040
2053
  if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
@@ -2051,6 +2064,16 @@ function toolUpdateFromToolResult(toolResult, toolUse) {
2051
2064
  )
2052
2065
  };
2053
2066
  }
2067
+ if (itemObj.type === "image" && itemObj.source) {
2068
+ return {
2069
+ type: "content",
2070
+ content: {
2071
+ type: "image",
2072
+ data: itemObj.source.data ?? "",
2073
+ mimeType: itemObj.source.media_type ?? "image/png"
2074
+ }
2075
+ };
2076
+ }
2054
2077
  return {
2055
2078
  type: "content",
2056
2079
  content: item
@@ -2066,18 +2089,51 @@ function toolUpdateFromToolResult(toolResult, toolUse) {
2066
2089
  }
2067
2090
  return {};
2068
2091
  case "Bash": {
2069
- return toAcpContentUpdate(
2070
- toolResult.content,
2071
- "is_error" in toolResult ? toolResult.is_error : false
2072
- );
2073
- }
2074
- case "Edit":
2075
- case "Write": {
2076
- if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
2077
- return toAcpContentUpdate(toolResult.content, true);
2092
+ const result = toolResult.content;
2093
+ const terminalId = "tool_use_id" in toolResult ? String(toolResult.tool_use_id) : "";
2094
+ const isError = "is_error" in toolResult && toolResult.is_error;
2095
+ let output = "";
2096
+ let exitCode = isError ? 1 : 0;
2097
+ if (result && typeof result === "object" && "type" in result && result.type === "bash_code_execution_result") {
2098
+ const bashResult = result;
2099
+ output = [bashResult.stdout, bashResult.stderr].filter(Boolean).join("\n");
2100
+ exitCode = bashResult.return_code;
2101
+ } else if (typeof result === "string") {
2102
+ output = result;
2103
+ } else if (Array.isArray(result) && result.length > 0 && "text" in result[0] && typeof result[0].text === "string") {
2104
+ output = result.map((c) => c.text ?? "").join("\n");
2105
+ }
2106
+ if (options?.supportsTerminalOutput) {
2107
+ return {
2108
+ content: [{ type: "terminal", terminalId }],
2109
+ _meta: {
2110
+ terminal_info: {
2111
+ terminal_id: terminalId
2112
+ },
2113
+ terminal_output: {
2114
+ terminal_id: terminalId,
2115
+ data: output
2116
+ },
2117
+ terminal_exit: {
2118
+ terminal_id: terminalId,
2119
+ exit_code: exitCode,
2120
+ signal: null
2121
+ }
2122
+ }
2123
+ };
2124
+ }
2125
+ if (output.trim()) {
2126
+ return {
2127
+ content: toolContent().text(`\`\`\`console
2128
+ ${output.trimEnd()}
2129
+ \`\`\``).build()
2130
+ };
2078
2131
  }
2079
2132
  return {};
2080
2133
  }
2134
+ case "Edit":
2135
+ case "Write":
2136
+ return {};
2081
2137
  case "ExitPlanMode": {
2082
2138
  return { title: "Exited Plan Mode" };
2083
2139
  }
@@ -2237,6 +2293,7 @@ function handleThinkingChunk(chunk, parentToolCallId) {
2237
2293
  return update;
2238
2294
  }
2239
2295
  function handleToolUseChunk(chunk, ctx) {
2296
+ const alreadyCached = chunk.id in ctx.toolUseCache;
2240
2297
  ctx.toolUseCache[chunk.id] = chunk;
2241
2298
  if (chunk.name === "TodoWrite") {
2242
2299
  const input = chunk.input;
@@ -2248,37 +2305,60 @@ function handleToolUseChunk(chunk, ctx) {
2248
2305
  }
2249
2306
  return null;
2250
2307
  }
2251
- registerHookCallback(chunk.id, {
2252
- onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
2253
- const toolUse = ctx.toolUseCache[toolUseId];
2254
- if (toolUse) {
2255
- await ctx.client.sessionUpdate({
2256
- sessionId: ctx.sessionId,
2257
- update: {
2258
- _meta: toolMeta(toolUse.name, toolResponse, ctx.parentToolCallId),
2259
- toolCallId: toolUseId,
2260
- sessionUpdate: "tool_call_update"
2261
- }
2262
- });
2263
- } else {
2264
- ctx.logger.error(
2265
- `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
2266
- );
2308
+ if (!alreadyCached && ctx.registerHooks !== false) {
2309
+ registerHookCallback(chunk.id, {
2310
+ onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
2311
+ const toolUse = ctx.toolUseCache[toolUseId];
2312
+ if (toolUse) {
2313
+ const editUpdate = toolUse.name === "Edit" ? toolUpdateFromEditToolResponse(toolResponse) : null;
2314
+ await ctx.client.sessionUpdate({
2315
+ sessionId: ctx.sessionId,
2316
+ update: {
2317
+ _meta: toolMeta(toolUse.name, toolResponse, ctx.parentToolCallId),
2318
+ toolCallId: toolUseId,
2319
+ sessionUpdate: "tool_call_update",
2320
+ ...editUpdate ? editUpdate : {}
2321
+ }
2322
+ });
2323
+ } else {
2324
+ ctx.logger.error(
2325
+ `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
2326
+ );
2327
+ }
2267
2328
  }
2268
- }
2269
- });
2329
+ });
2330
+ }
2270
2331
  let rawInput;
2271
2332
  try {
2272
2333
  rawInput = JSON.parse(JSON.stringify(chunk.input));
2273
2334
  } catch {
2274
2335
  }
2336
+ const toolInfo = toolInfoFromToolUse(chunk, {
2337
+ supportsTerminalOutput: ctx.supportsTerminalOutput,
2338
+ toolUseId: chunk.id
2339
+ });
2340
+ const meta = {
2341
+ ...toolMeta(chunk.name, void 0, ctx.parentToolCallId)
2342
+ };
2343
+ if (chunk.name === "Bash" && ctx.supportsTerminalOutput && !alreadyCached) {
2344
+ meta.terminal_info = { terminal_id: chunk.id };
2345
+ }
2346
+ if (alreadyCached) {
2347
+ return {
2348
+ _meta: meta,
2349
+ toolCallId: chunk.id,
2350
+ sessionUpdate: "tool_call_update",
2351
+ rawInput,
2352
+ ...toolInfo
2353
+ };
2354
+ }
2275
2355
  return {
2276
- _meta: toolMeta(chunk.name, void 0, ctx.parentToolCallId),
2356
+ _meta: meta,
2277
2357
  toolCallId: chunk.id,
2278
2358
  sessionUpdate: "tool_call",
2279
2359
  rawInput,
2280
2360
  status: "pending",
2281
- ...toolInfoFromToolUse(chunk, ctx.fileContentCache, ctx.logger)
2361
+ ...toolInfo
2282
2362
  };
2283
2363
  }
2284
2364
  function handleToolResultChunk(chunk, ctx) {
@@ -2287,36 +2367,71 @@ function handleToolResultChunk(chunk, ctx) {
2287
2367
  ctx.logger.error(
2288
2368
  `Got a tool result for tool use that wasn't tracked: ${chunk.tool_use_id}`
2289
2369
  );
2290
- return null;
2370
+ return [];
2291
2371
  }
2292
2372
  if (toolUse.name === "TodoWrite") {
2293
- return null;
2373
+ return [];
2294
2374
  }
2295
- return {
2296
- _meta: toolMeta(toolUse.name, void 0, ctx.parentToolCallId),
2375
+ const { _meta: resultMeta, ...toolUpdate } = toolUpdateFromToolResult(
2376
+ chunk,
2377
+ toolUse,
2378
+ {
2379
+ supportsTerminalOutput: ctx.supportsTerminalOutput,
2380
+ toolUseId: chunk.tool_use_id
2381
+ }
2382
+ );
2383
+ const updates = [];
2384
+ if (resultMeta?.terminal_output) {
2385
+ const terminalOutputMeta = {
2386
+ terminal_output: resultMeta.terminal_output
2387
+ };
2388
+ if (ctx.parentToolCallId) {
2389
+ terminalOutputMeta.claudeCode = {
2390
+ parentToolCallId: ctx.parentToolCallId
2391
+ };
2392
+ }
2393
+ updates.push({
2394
+ _meta: terminalOutputMeta,
2395
+ toolCallId: chunk.tool_use_id,
2396
+ sessionUpdate: "tool_call_update"
2397
+ });
2398
+ }
2399
+ const meta = {
2400
+ ...toolMeta(toolUse.name, void 0, ctx.parentToolCallId),
2401
+ ...resultMeta?.terminal_exit ? { terminal_exit: resultMeta.terminal_exit } : {}
2402
+ };
2403
+ updates.push({
2404
+ _meta: meta,
2297
2405
  toolCallId: chunk.tool_use_id,
2298
2406
  sessionUpdate: "tool_call_update",
2299
2407
  status: chunk.is_error ? "failed" : "completed",
2300
- ...toolUpdateFromToolResult(
2301
- chunk,
2302
- toolUse
2303
- )
2304
- };
2408
+ rawOutput: chunk.content,
2409
+ ...toolUpdate
2410
+ });
2411
+ return updates;
2305
2412
  }
2306
2413
  function processContentChunk(chunk, role, ctx) {
2307
2414
  switch (chunk.type) {
2308
2415
  case "text":
2309
- case "text_delta":
2310
- return handleTextChunk(chunk, role, ctx.parentToolCallId);
2311
- case "image":
2312
- return handleImageChunk(chunk, role);
2416
+ case "text_delta": {
2417
+ const update = handleTextChunk(chunk, role, ctx.parentToolCallId);
2418
+ return update ? [update] : [];
2419
+ }
2420
+ case "image": {
2421
+ const update = handleImageChunk(chunk, role);
2422
+ return update ? [update] : [];
2423
+ }
2313
2424
  case "thinking":
2314
- case "thinking_delta":
2315
- return handleThinkingChunk(chunk, ctx.parentToolCallId);
2425
+ case "thinking_delta": {
2426
+ const update = handleThinkingChunk(chunk, ctx.parentToolCallId);
2427
+ return update ? [update] : [];
2428
+ }
2316
2429
  case "tool_use":
2317
2430
  case "server_tool_use":
2318
- case "mcp_tool_use":
2319
- return handleToolUseChunk(chunk, ctx);
2431
+ case "mcp_tool_use": {
2432
+ const update = handleToolUseChunk(chunk, ctx);
2433
+ return update ? [update] : [];
2434
+ }
2320
2435
  case "tool_result":
2321
2436
  case "tool_search_tool_result":
2322
2437
  case "web_fetch_tool_result":
@@ -2338,13 +2453,13 @@ function processContentChunk(chunk, role, ctx) {
2338
2453
  case "container_upload":
2339
2454
  case "compaction":
2340
2455
  case "compaction_delta":
2341
- return null;
2456
+ return [];
2342
2457
  default:
2343
2458
  unreachable(chunk, ctx.logger);
2344
- return null;
2459
+ return [];
2345
2460
  }
2346
2461
  }
2347
- function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId) {
2462
+ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput) {
2348
2463
  if (typeof content === "string") {
2349
2464
  const update = {
2350
2465
  sessionUpdate: messageUpdateType(role),
@@ -2365,18 +2480,19 @@ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentC
2365
2480
  fileContentCache,
2366
2481
  client,
2367
2482
  logger,
2368
- parentToolCallId
2483
+ parentToolCallId,
2484
+ registerHooks,
2485
+ supportsTerminalOutput
2369
2486
  };
2370
2487
  const output = [];
2371
2488
  for (const chunk of content) {
2372
- const update = processContentChunk(chunk, role, ctx);
2373
- if (update) {
2489
+ for (const update of processContentChunk(chunk, role, ctx)) {
2374
2490
  output.push({ sessionId, update });
2375
2491
  }
2376
2492
  }
2377
2493
  return output;
2378
2494
  }
2379
- function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId) {
2495
+ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput) {
2380
2496
  const event = message.event;
2381
2497
  switch (event.type) {
2382
2498
  case "content_block_start":
@@ -2388,7 +2504,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
2388
2504
  fileContentCache,
2389
2505
  client,
2390
2506
  logger,
2391
- parentToolCallId
2507
+ parentToolCallId,
2508
+ registerHooks,
2509
+ supportsTerminalOutput
2392
2510
  );
2393
2511
  case "content_block_delta":
2394
2512
  return toAcpNotifications(
@@ -2399,7 +2517,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
2399
2517
  fileContentCache,
2400
2518
  client,
2401
2519
  logger,
2402
- parentToolCallId
2520
+ parentToolCallId,
2521
+ registerHooks,
2522
+ supportsTerminalOutput
2403
2523
  );
2404
2524
  case "message_start":
2405
2525
  case "message_delta":
@@ -2458,29 +2578,25 @@ async function handleSystemMessage(message, context) {
2458
2578
  break;
2459
2579
  }
2460
2580
  }
2461
- function handleResultMessage(message, context) {
2462
- const { session } = context;
2463
- if (session.cancelled) {
2464
- return {
2465
- shouldStop: true,
2466
- stopReason: "cancelled"
2467
- };
2468
- }
2581
+ function handleResultMessage(message) {
2582
+ const usage = extractUsageFromResult(message);
2469
2583
  switch (message.subtype) {
2470
2584
  case "success": {
2471
2585
  if (message.result.includes("Please run /login")) {
2472
2586
  return {
2473
2587
  shouldStop: true,
2474
- error: RequestError.authRequired()
2588
+ error: RequestError.authRequired(),
2589
+ usage
2475
2590
  };
2476
2591
  }
2477
2592
  if (message.is_error) {
2478
2593
  return {
2479
2594
  shouldStop: true,
2480
- error: RequestError.internalError(void 0, message.result)
2595
+ error: RequestError.internalError(void 0, message.result),
2596
+ usage
2481
2597
  };
2482
2598
  }
2483
- return { shouldStop: true, stopReason: "end_turn" };
2599
+ return { shouldStop: true, stopReason: "end_turn", usage };
2484
2600
  }
2485
2601
  case "error_during_execution":
2486
2602
  if (message.is_error) {
@@ -2489,10 +2605,11 @@ function handleResultMessage(message, context) {
2489
2605
  error: RequestError.internalError(
2490
2606
  void 0,
2491
2607
  message.errors.join(", ") || message.subtype
2492
- )
2608
+ ),
2609
+ usage
2493
2610
  };
2494
2611
  }
2495
- return { shouldStop: true, stopReason: "end_turn" };
2612
+ return { shouldStop: true, stopReason: "end_turn", usage };
2496
2613
  case "error_max_budget_usd":
2497
2614
  case "error_max_turns":
2498
2615
  case "error_max_structured_output_retries":
@@ -2502,13 +2619,37 @@ function handleResultMessage(message, context) {
2502
2619
  error: RequestError.internalError(
2503
2620
  void 0,
2504
2621
  message.errors.join(", ") || message.subtype
2505
- )
2622
+ ),
2623
+ usage
2506
2624
  };
2507
2625
  }
2508
- return { shouldStop: true, stopReason: "max_turn_requests" };
2626
+ return { shouldStop: true, stopReason: "max_turn_requests", usage };
2509
2627
  default:
2510
- return { shouldStop: false };
2628
+ return { shouldStop: false, usage };
2629
+ }
2630
+ }
2631
+ function extractUsageFromResult(message) {
2632
+ const msg = message;
2633
+ const msgUsage = msg.usage;
2634
+ if (!msgUsage) return void 0;
2635
+ const modelUsage = msg.modelUsage;
2636
+ let contextWindowSize;
2637
+ if (modelUsage) {
2638
+ const contextWindows = Object.values(modelUsage).map(
2639
+ (m) => m.contextWindow
2640
+ );
2641
+ if (contextWindows.length > 0) {
2642
+ contextWindowSize = Math.min(...contextWindows);
2643
+ }
2511
2644
  }
2645
+ return {
2646
+ inputTokens: msgUsage.input_tokens ?? 0,
2647
+ outputTokens: msgUsage.output_tokens ?? 0,
2648
+ cachedReadTokens: msgUsage.cache_read_input_tokens ?? 0,
2649
+ cachedWriteTokens: msgUsage.cache_creation_input_tokens ?? 0,
2650
+ costUsd: typeof msg.total_cost_usd === "number" ? msg.total_cost_usd : void 0,
2651
+ contextWindowSize
2652
+ };
2512
2653
  }
2513
2654
  async function handleStreamEvent(message, context) {
2514
2655
  const { sessionId, client, toolUseCache, fileContentCache, logger } = context;
@@ -2520,7 +2661,9 @@ async function handleStreamEvent(message, context) {
2520
2661
  fileContentCache,
2521
2662
  client,
2522
2663
  logger,
2523
- parentToolCallId
2664
+ parentToolCallId,
2665
+ context.registerHooks,
2666
+ context.supportsTerminalOutput
2524
2667
  )) {
2525
2668
  await client.sessionUpdate(notification);
2526
2669
  context.session.notificationHistory.push(notification);
@@ -2532,14 +2675,15 @@ function hasLocalCommandStdout(content) {
2532
2675
  function hasLocalCommandStderr(content) {
2533
2676
  return typeof content === "string" && content.includes("<local-command-stderr>");
2534
2677
  }
2535
- function isSimpleUserMessage(message) {
2536
- return message.type === "user" && (typeof message.message.content === "string" || Array.isArray(message.message.content) && message.message.content.length === 1 && message.message.content[0].type === "text");
2537
- }
2538
2678
  function isLoginRequiredMessage(message) {
2539
2679
  return message.type === "assistant" && message.message.model === "<synthetic>" && Array.isArray(message.message.content) && message.message.content.length === 1 && message.message.content[0].type === "text" && message.message.content[0].text?.includes("Please run /login") === true;
2540
2680
  }
2681
+ function isPlainTextUserMessage(message) {
2682
+ const content = message.message.content;
2683
+ return message.type === "user" && (typeof content === "string" || Array.isArray(content) && content.length === 1 && content[0].type === "text");
2684
+ }
2541
2685
  function shouldSkipUserAssistantMessage(message) {
2542
- return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isSimpleUserMessage(message) || isLoginRequiredMessage(message);
2686
+ return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isLoginRequiredMessage(message);
2543
2687
  }
2544
2688
  function logSpecialMessages(message, logger) {
2545
2689
  const content = message.message.content;
@@ -2560,18 +2704,33 @@ function filterMessageContent(content) {
2560
2704
  }
2561
2705
  async function handleUserAssistantMessage(message, context) {
2562
2706
  const { session, sessionId, client, toolUseCache, fileContentCache, logger } = context;
2563
- if (session.cancelled) {
2564
- return {};
2565
- }
2566
2707
  if (shouldSkipUserAssistantMessage(message)) {
2708
+ const content2 = message.message.content;
2709
+ if (typeof content2 === "string" && hasLocalCommandStdout(content2) && content2.includes("Context Usage")) {
2710
+ const stripped = content2.replace("<local-command-stdout>", "").replace("</local-command-stdout>", "");
2711
+ for (const notification of toAcpNotifications(
2712
+ stripped,
2713
+ "assistant",
2714
+ sessionId,
2715
+ toolUseCache,
2716
+ fileContentCache,
2717
+ client,
2718
+ logger
2719
+ )) {
2720
+ await client.sessionUpdate(notification);
2721
+ }
2722
+ }
2567
2723
  logSpecialMessages(message, logger);
2568
2724
  if (isLoginRequiredMessage(message)) {
2569
2725
  return { shouldStop: true, error: RequestError.authRequired() };
2570
2726
  }
2571
2727
  return {};
2572
2728
  }
2729
+ if (isPlainTextUserMessage(message)) {
2730
+ return {};
2731
+ }
2573
2732
  const content = message.message.content;
2574
- const contentToProcess = filterMessageContent(content);
2733
+ const contentToProcess = message.type === "assistant" ? filterMessageContent(content) : content;
2575
2734
  const parentToolCallId = "parent_tool_use_id" in message ? message.parent_tool_use_id ?? void 0 : void 0;
2576
2735
  for (const notification of toAcpNotifications(
2577
2736
  contentToProcess,
@@ -2581,7 +2740,9 @@ async function handleUserAssistantMessage(message, context) {
2581
2740
  fileContentCache,
2582
2741
  client,
2583
2742
  logger,
2584
- parentToolCallId
2743
+ parentToolCallId,
2744
+ context.registerHooks,
2745
+ context.supportsTerminalOutput
2585
2746
  )) {
2586
2747
  await client.sessionUpdate(notification);
2587
2748
  session.notificationHistory.push(notification);
@@ -2667,36 +2828,45 @@ function normalizeAskUserQuestionInput(input) {
2667
2828
  }
2668
2829
 
2669
2830
  // src/execution-mode.ts
2670
- var MODES = [
2831
+ var ALLOW_BYPASS = !IS_ROOT;
2832
+ var availableModes = [
2671
2833
  {
2672
2834
  id: "default",
2673
- name: "Always Ask",
2674
- description: "Prompts for permission on first use of each tool"
2835
+ name: "Default",
2836
+ description: "Standard behavior, prompts for dangerous operations"
2675
2837
  },
2676
2838
  {
2677
2839
  id: "acceptEdits",
2678
2840
  name: "Accept Edits",
2679
- description: "Automatically accepts file edit permissions for the session"
2841
+ description: "Auto-accept file edit operations"
2680
2842
  },
2681
2843
  {
2682
2844
  id: "plan",
2683
2845
  name: "Plan Mode",
2684
- description: "Claude can analyze but not modify files or execute commands"
2685
- },
2686
- {
2687
- id: "bypassPermissions",
2688
- name: "Bypass Permissions",
2689
- description: "Skips all permission prompts"
2846
+ description: "Planning mode, no actual tool execution"
2690
2847
  }
2848
+ // {
2849
+ // id: "dontAsk",
2850
+ // name: "Don't Ask",
2851
+ // description: "Don't prompt for permissions, deny if not pre-approved",
2852
+ // },
2691
2853
  ];
2854
+ if (ALLOW_BYPASS) {
2855
+ availableModes.push({
2856
+ id: "bypassPermissions",
2857
+ name: "Bypass Permissions",
2858
+ description: "Bypass all permission checks"
2859
+ });
2860
+ }
2692
2861
  var TWIG_EXECUTION_MODES = [
2693
2862
  "default",
2694
2863
  "acceptEdits",
2695
2864
  "plan",
2865
+ // "dontAsk",
2696
2866
  "bypassPermissions"
2697
2867
  ];
2698
2868
  function getAvailableModes() {
2699
- return IS_ROOT ? MODES.filter((m) => m.id !== "bypassPermissions") : MODES;
2869
+ return IS_ROOT ? availableModes.filter((m) => m.id !== "bypassPermissions") : availableModes;
2700
2870
  }
2701
2871
 
2702
2872
  // src/adapters/claude/tools.ts
@@ -2724,6 +2894,7 @@ var AUTO_ALLOWED_TOOLS = {
2724
2894
  default: new Set(BASE_ALLOWED_TOOLS),
2725
2895
  acceptEdits: /* @__PURE__ */ new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),
2726
2896
  plan: new Set(BASE_ALLOWED_TOOLS)
2897
+ // dontAsk: new Set(BASE_ALLOWED_TOOLS),
2727
2898
  };
2728
2899
  function isToolAllowedForMode(toolName, mode) {
2729
2900
  if (mode === "bypassPermissions") {
@@ -2871,12 +3042,11 @@ async function validatePlanContent(planText, context) {
2871
3042
  return { valid: true };
2872
3043
  }
2873
3044
  async function requestPlanApproval(context, updatedInput) {
2874
- const { client, sessionId, toolUseID, fileContentCache } = context;
2875
- const toolInfo = toolInfoFromToolUse(
2876
- { name: context.toolName, input: updatedInput },
2877
- fileContentCache,
2878
- context.logger
2879
- );
3045
+ const { client, sessionId, toolUseID } = context;
3046
+ const toolInfo = toolInfoFromToolUse({
3047
+ name: context.toolName,
3048
+ input: updatedInput
3049
+ });
2880
3050
  return await client.requestPermission({
2881
3051
  options: buildExitPlanModePermissionOptions(),
2882
3052
  sessionId,
@@ -2895,7 +3065,14 @@ async function applyPlanApproval(response, context, updatedInput) {
2895
3065
  if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "default" || response.outcome.optionId === "acceptEdits")) {
2896
3066
  session.permissionMode = response.outcome.optionId;
2897
3067
  await session.query.setPermissionMode(response.outcome.optionId);
2898
- await context.emitConfigOptionsUpdate();
3068
+ await context.client.sessionUpdate({
3069
+ sessionId: context.sessionId,
3070
+ update: {
3071
+ sessionUpdate: "current_mode_update",
3072
+ currentModeId: response.outcome.optionId
3073
+ }
3074
+ });
3075
+ await context.updateConfigOption("mode", response.outcome.optionId);
2899
3076
  return {
2900
3077
  behavior: "allow",
2901
3078
  updatedInput,
@@ -2916,7 +3093,7 @@ async function handleEnterPlanModeTool(context) {
2916
3093
  const { session, toolInput } = context;
2917
3094
  session.permissionMode = "plan";
2918
3095
  await session.query.setPermissionMode("plan");
2919
- await context.emitConfigOptionsUpdate();
3096
+ await context.updateConfigOption("mode", "plan");
2920
3097
  return {
2921
3098
  behavior: "allow",
2922
3099
  updatedInput: toolInput
@@ -2934,6 +3111,9 @@ async function handleExitPlanModeTool(context) {
2934
3111
  return validationResult.error;
2935
3112
  }
2936
3113
  const response = await requestPlanApproval(context, updatedInput);
3114
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3115
+ throw new Error("Tool use aborted");
3116
+ }
2937
3117
  return await applyPlanApproval(response, context, updatedInput);
2938
3118
  }
2939
3119
  function buildQuestionOptions(question) {
@@ -2957,14 +3137,13 @@ async function handleAskUserQuestionTool(context) {
2957
3137
  interrupt: true
2958
3138
  };
2959
3139
  }
2960
- const { client, sessionId, toolUseID, toolInput, fileContentCache } = context;
3140
+ const { client, sessionId, toolUseID, toolInput } = context;
2961
3141
  const firstQuestion = questions[0];
2962
3142
  const options = buildQuestionOptions(firstQuestion);
2963
- const toolInfo = toolInfoFromToolUse(
2964
- { name: context.toolName, input: toolInput },
2965
- fileContentCache,
2966
- context.logger
2967
- );
3143
+ const toolInfo = toolInfoFromToolUse({
3144
+ name: context.toolName,
3145
+ input: toolInput
3146
+ });
2968
3147
  const response = await client.requestPermission({
2969
3148
  options,
2970
3149
  sessionId,
@@ -2979,6 +3158,9 @@ async function handleAskUserQuestionTool(context) {
2979
3158
  }
2980
3159
  }
2981
3160
  });
3161
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3162
+ throw new Error("Tool use aborted");
3163
+ }
2982
3164
  if (response.outcome?.outcome !== "selected") {
2983
3165
  const customMessage = response._meta?.message;
2984
3166
  return {
@@ -3011,14 +3193,9 @@ async function handleDefaultPermissionFlow(context) {
3011
3193
  toolUseID,
3012
3194
  client,
3013
3195
  sessionId,
3014
- fileContentCache,
3015
3196
  suggestions
3016
3197
  } = context;
3017
- const toolInfo = toolInfoFromToolUse(
3018
- { name: toolName, input: toolInput },
3019
- fileContentCache,
3020
- context.logger
3021
- );
3198
+ const toolInfo = toolInfoFromToolUse({ name: toolName, input: toolInput });
3022
3199
  const options = buildPermissionOptions(
3023
3200
  toolName,
3024
3201
  toolInput,
@@ -3037,6 +3214,9 @@ async function handleDefaultPermissionFlow(context) {
3037
3214
  rawInput: toolInput
3038
3215
  }
3039
3216
  });
3217
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
3218
+ throw new Error("Tool use aborted");
3219
+ }
3040
3220
  if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "allow" || response.outcome.optionId === "allow_always")) {
3041
3221
  if (response.outcome.optionId === "allow_always") {
3042
3222
  return {
@@ -3113,16 +3293,18 @@ async function canUseTool(context) {
3113
3293
  var UNSUPPORTED_COMMANDS = [
3114
3294
  "context",
3115
3295
  "cost",
3296
+ "keybindings-help",
3116
3297
  "login",
3117
3298
  "logout",
3118
3299
  "output-style:new",
3119
3300
  "release-notes",
3120
3301
  "todos"
3121
3302
  ];
3122
- async function getAvailableSlashCommands(q) {
3123
- const commands = await q.supportedCommands();
3303
+ function getAvailableSlashCommands(commands) {
3124
3304
  return commands.map((command) => {
3125
- const input = command.argumentHint ? { hint: command.argumentHint } : null;
3305
+ const input = command.argumentHint != null ? {
3306
+ hint: Array.isArray(command.argumentHint) ? command.argumentHint.join(" ") : command.argumentHint
3307
+ } : null;
3126
3308
  let name = command.name;
3127
3309
  if (command.name.endsWith(" (MCP)")) {
3128
3310
  name = `mcp:${name.replace(" (MCP)", "")}`;
@@ -3220,13 +3402,19 @@ function buildEnvironment() {
3220
3402
  ENABLE_TOOL_SEARCH: "auto:0"
3221
3403
  };
3222
3404
  }
3223
- function buildHooks(userHooks, onModeChange) {
3405
+ function buildHooks(userHooks, onModeChange, settingsManager, logger) {
3224
3406
  return {
3225
3407
  ...userHooks,
3226
3408
  PostToolUse: [
3227
3409
  ...userHooks?.PostToolUse || [],
3228
3410
  {
3229
- hooks: [createPostToolUseHook({ onModeChange })]
3411
+ hooks: [createPostToolUseHook({ onModeChange, logger })]
3412
+ }
3413
+ ],
3414
+ PreToolUse: [
3415
+ ...userHooks?.PreToolUse || [],
3416
+ {
3417
+ hooks: [createPreToolUseHook(settingsManager, logger)]
3230
3418
  }
3231
3419
  ]
3232
3420
  };
@@ -3320,12 +3508,22 @@ function buildSessionOptions(params) {
3320
3508
  permissionMode: params.permissionMode,
3321
3509
  canUseTool: params.canUseTool,
3322
3510
  executable: "node",
3511
+ tools: { type: "preset", preset: "claude_code" },
3512
+ extraArgs: {
3513
+ ...params.userProvidedOptions?.extraArgs,
3514
+ "replay-user-messages": ""
3515
+ },
3323
3516
  mcpServers: buildMcpServers(
3324
3517
  params.userProvidedOptions?.mcpServers,
3325
3518
  params.mcpServers
3326
3519
  ),
3327
3520
  env: buildEnvironment(),
3328
- hooks: buildHooks(params.userProvidedOptions?.hooks, params.onModeChange),
3521
+ hooks: buildHooks(
3522
+ params.userProvidedOptions?.hooks,
3523
+ params.onModeChange,
3524
+ params.settingsManager,
3525
+ params.logger
3526
+ ),
3329
3527
  abortController: getAbortController(
3330
3528
  params.userProvidedOptions?.abortController
3331
3529
  ),
@@ -3342,13 +3540,36 @@ function buildSessionOptions(params) {
3342
3540
  }
3343
3541
  if (params.isResume) {
3344
3542
  options.resume = params.sessionId;
3345
- options.forkSession = false;
3543
+ options.forkSession = params.forkSession ?? false;
3346
3544
  } else {
3347
3545
  options.sessionId = params.sessionId;
3546
+ options.model = DEFAULT_MODEL;
3348
3547
  }
3349
3548
  if (params.additionalDirectories) {
3350
3549
  options.additionalDirectories = params.additionalDirectories;
3351
3550
  }
3551
+ if (params.disableBuiltInTools) {
3552
+ const builtInTools = [
3553
+ "Read",
3554
+ "Write",
3555
+ "Edit",
3556
+ "Bash",
3557
+ "Glob",
3558
+ "Grep",
3559
+ "Task",
3560
+ "TodoWrite",
3561
+ "ExitPlanMode",
3562
+ "WebSearch",
3563
+ "WebFetch",
3564
+ "SlashCommand",
3565
+ "Skill",
3566
+ "NotebookEdit"
3567
+ ];
3568
+ options.disallowedTools = [
3569
+ ...options.disallowedTools ?? [],
3570
+ ...builtInTools
3571
+ ];
3572
+ }
3352
3573
  clearStatsigCache();
3353
3574
  return options;
3354
3575
  }
@@ -3361,15 +3582,268 @@ function clearStatsigCache() {
3361
3582
  });
3362
3583
  }
3363
3584
 
3585
+ // src/adapters/claude/session/settings.ts
3586
+ import * as fs2 from "fs";
3587
+ import * as os3 from "os";
3588
+ import * as path3 from "path";
3589
+ import { minimatch } from "minimatch";
3590
+ var ACP_TOOL_NAME_PREFIX = "mcp__acp__";
3591
+ var acpToolNames = {
3592
+ read: `${ACP_TOOL_NAME_PREFIX}Read`,
3593
+ edit: `${ACP_TOOL_NAME_PREFIX}Edit`,
3594
+ write: `${ACP_TOOL_NAME_PREFIX}Write`,
3595
+ bash: `${ACP_TOOL_NAME_PREFIX}Bash`
3596
+ };
3597
+ var SHELL_OPERATORS = ["&&", "||", ";", "|", "$(", "`", "\n"];
3598
+ function containsShellOperator(str) {
3599
+ return SHELL_OPERATORS.some((op) => str.includes(op));
3600
+ }
3601
+ var FILE_EDITING_TOOLS = [acpToolNames.edit, acpToolNames.write];
3602
+ var FILE_READING_TOOLS = [acpToolNames.read];
3603
+ var TOOL_ARG_ACCESSORS = {
3604
+ [acpToolNames.read]: (input) => input?.file_path,
3605
+ [acpToolNames.edit]: (input) => input?.file_path,
3606
+ [acpToolNames.write]: (input) => input?.file_path,
3607
+ [acpToolNames.bash]: (input) => input?.command
3608
+ };
3609
+ function parseRule(rule) {
3610
+ const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
3611
+ if (!match) {
3612
+ return { toolName: rule };
3613
+ }
3614
+ const toolName = match[1] ?? rule;
3615
+ const argument = match[2];
3616
+ if (argument?.endsWith(":*")) {
3617
+ return {
3618
+ toolName,
3619
+ argument: argument.slice(0, -2),
3620
+ isWildcard: true
3621
+ };
3622
+ }
3623
+ return { toolName, argument };
3624
+ }
3625
+ function normalizePath(filePath, cwd) {
3626
+ let resolved = filePath;
3627
+ if (resolved.startsWith("~/")) {
3628
+ resolved = path3.join(os3.homedir(), resolved.slice(2));
3629
+ } else if (resolved.startsWith("./")) {
3630
+ resolved = path3.join(cwd, resolved.slice(2));
3631
+ } else if (!path3.isAbsolute(resolved)) {
3632
+ resolved = path3.join(cwd, resolved);
3633
+ }
3634
+ return path3.normalize(resolved).replace(/\\/g, "/");
3635
+ }
3636
+ function matchesGlob(pattern, filePath, cwd) {
3637
+ const normalizedPattern = normalizePath(pattern, cwd);
3638
+ const normalizedPath = normalizePath(filePath, cwd);
3639
+ return minimatch(normalizedPath, normalizedPattern, {
3640
+ dot: true,
3641
+ matchBase: false,
3642
+ nocase: process.platform === "win32"
3643
+ });
3644
+ }
3645
+ function matchesRule(rule, toolName, toolInput, cwd) {
3646
+ const ruleAppliesToTool = rule.toolName === "Bash" && toolName === acpToolNames.bash || rule.toolName === "Edit" && FILE_EDITING_TOOLS.includes(toolName) || rule.toolName === "Read" && FILE_READING_TOOLS.includes(toolName);
3647
+ if (!ruleAppliesToTool) {
3648
+ return false;
3649
+ }
3650
+ if (!rule.argument) {
3651
+ return true;
3652
+ }
3653
+ const argAccessor = TOOL_ARG_ACCESSORS[toolName];
3654
+ if (!argAccessor) {
3655
+ return true;
3656
+ }
3657
+ const actualArg = argAccessor(toolInput);
3658
+ if (!actualArg) {
3659
+ return false;
3660
+ }
3661
+ if (toolName === acpToolNames.bash) {
3662
+ if (rule.isWildcard) {
3663
+ if (!actualArg.startsWith(rule.argument)) {
3664
+ return false;
3665
+ }
3666
+ const remainder = actualArg.slice(rule.argument.length);
3667
+ if (containsShellOperator(remainder)) {
3668
+ return false;
3669
+ }
3670
+ return true;
3671
+ }
3672
+ return actualArg === rule.argument;
3673
+ }
3674
+ return matchesGlob(rule.argument, actualArg, cwd);
3675
+ }
3676
+ async function loadSettingsFile(filePath) {
3677
+ if (!filePath) {
3678
+ return {};
3679
+ }
3680
+ try {
3681
+ const content = await fs2.promises.readFile(filePath, "utf-8");
3682
+ return JSON.parse(content);
3683
+ } catch {
3684
+ return {};
3685
+ }
3686
+ }
3687
+ function getManagedSettingsPath() {
3688
+ switch (process.platform) {
3689
+ case "darwin":
3690
+ return "/Library/Application Support/ClaudeCode/managed-settings.json";
3691
+ case "linux":
3692
+ return "/etc/claude-code/managed-settings.json";
3693
+ case "win32":
3694
+ return "C:\\Program Files\\ClaudeCode\\managed-settings.json";
3695
+ default:
3696
+ return "/etc/claude-code/managed-settings.json";
3697
+ }
3698
+ }
3699
+ var SettingsManager = class {
3700
+ cwd;
3701
+ userSettings = {};
3702
+ projectSettings = {};
3703
+ localSettings = {};
3704
+ enterpriseSettings = {};
3705
+ mergedSettings = {};
3706
+ initialized = false;
3707
+ constructor(cwd) {
3708
+ this.cwd = cwd;
3709
+ }
3710
+ async initialize() {
3711
+ if (this.initialized) {
3712
+ return;
3713
+ }
3714
+ await this.loadAllSettings();
3715
+ this.initialized = true;
3716
+ }
3717
+ getUserSettingsPath() {
3718
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path3.join(os3.homedir(), ".claude");
3719
+ return path3.join(configDir, "settings.json");
3720
+ }
3721
+ getProjectSettingsPath() {
3722
+ return path3.join(this.cwd, ".claude", "settings.json");
3723
+ }
3724
+ getLocalSettingsPath() {
3725
+ return path3.join(this.cwd, ".claude", "settings.local.json");
3726
+ }
3727
+ async loadAllSettings() {
3728
+ const [userSettings, projectSettings, localSettings, enterpriseSettings] = await Promise.all([
3729
+ loadSettingsFile(this.getUserSettingsPath()),
3730
+ loadSettingsFile(this.getProjectSettingsPath()),
3731
+ loadSettingsFile(this.getLocalSettingsPath()),
3732
+ loadSettingsFile(getManagedSettingsPath())
3733
+ ]);
3734
+ this.userSettings = userSettings;
3735
+ this.projectSettings = projectSettings;
3736
+ this.localSettings = localSettings;
3737
+ this.enterpriseSettings = enterpriseSettings;
3738
+ this.mergeAllSettings();
3739
+ }
3740
+ mergeAllSettings() {
3741
+ const allSettings = [
3742
+ this.userSettings,
3743
+ this.projectSettings,
3744
+ this.localSettings,
3745
+ this.enterpriseSettings
3746
+ ];
3747
+ const permissions = {
3748
+ allow: [],
3749
+ deny: [],
3750
+ ask: []
3751
+ };
3752
+ const merged = { permissions };
3753
+ for (const settings of allSettings) {
3754
+ if (settings.permissions) {
3755
+ if (settings.permissions.allow) {
3756
+ permissions.allow?.push(...settings.permissions.allow);
3757
+ }
3758
+ if (settings.permissions.deny) {
3759
+ permissions.deny?.push(...settings.permissions.deny);
3760
+ }
3761
+ if (settings.permissions.ask) {
3762
+ permissions.ask?.push(...settings.permissions.ask);
3763
+ }
3764
+ if (settings.permissions.additionalDirectories) {
3765
+ permissions.additionalDirectories = [
3766
+ ...permissions.additionalDirectories || [],
3767
+ ...settings.permissions.additionalDirectories
3768
+ ];
3769
+ }
3770
+ if (settings.permissions.defaultMode) {
3771
+ permissions.defaultMode = settings.permissions.defaultMode;
3772
+ }
3773
+ }
3774
+ if (settings.env) {
3775
+ merged.env = { ...merged.env, ...settings.env };
3776
+ }
3777
+ if (settings.model) {
3778
+ merged.model = settings.model;
3779
+ }
3780
+ }
3781
+ this.mergedSettings = merged;
3782
+ }
3783
+ checkPermission(toolName, toolInput) {
3784
+ if (!toolName.startsWith(ACP_TOOL_NAME_PREFIX)) {
3785
+ return { decision: "ask" };
3786
+ }
3787
+ const permissions = this.mergedSettings.permissions;
3788
+ if (!permissions) {
3789
+ return { decision: "ask" };
3790
+ }
3791
+ for (const rule of permissions.deny || []) {
3792
+ const parsed = parseRule(rule);
3793
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3794
+ return { decision: "deny", rule, source: "deny" };
3795
+ }
3796
+ }
3797
+ for (const rule of permissions.allow || []) {
3798
+ const parsed = parseRule(rule);
3799
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3800
+ return { decision: "allow", rule, source: "allow" };
3801
+ }
3802
+ }
3803
+ for (const rule of permissions.ask || []) {
3804
+ const parsed = parseRule(rule);
3805
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
3806
+ return { decision: "ask", rule, source: "ask" };
3807
+ }
3808
+ }
3809
+ return { decision: "ask" };
3810
+ }
3811
+ getSettings() {
3812
+ return this.mergedSettings;
3813
+ }
3814
+ getCwd() {
3815
+ return this.cwd;
3816
+ }
3817
+ async setCwd(cwd) {
3818
+ if (this.cwd === cwd) {
3819
+ return;
3820
+ }
3821
+ this.dispose();
3822
+ this.cwd = cwd;
3823
+ this.initialized = false;
3824
+ await this.initialize();
3825
+ }
3826
+ dispose() {
3827
+ this.initialized = false;
3828
+ }
3829
+ };
3830
+
3364
3831
  // src/adapters/claude/claude-agent.ts
3365
3832
  var SESSION_VALIDATION_TIMEOUT_MS = 1e4;
3833
+ var MAX_TITLE_LENGTH = 256;
3834
+ function sanitizeTitle(text2) {
3835
+ const sanitized = text2.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
3836
+ if (sanitized.length <= MAX_TITLE_LENGTH) {
3837
+ return sanitized;
3838
+ }
3839
+ return `${sanitized.slice(0, MAX_TITLE_LENGTH - 1)}\u2026`;
3840
+ }
3366
3841
  var ClaudeAcpAgent = class extends BaseAcpAgent {
3367
3842
  adapterName = "claude";
3368
3843
  toolUseCache;
3369
3844
  backgroundTerminals = {};
3370
3845
  clientCapabilities;
3371
3846
  options;
3372
- lastSentConfigOptions;
3373
3847
  constructor(client, options) {
3374
3848
  super(client);
3375
3849
  this.options = options;
@@ -3390,125 +3864,416 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3390
3864
  sse: true
3391
3865
  },
3392
3866
  loadSession: true,
3867
+ sessionCapabilities: {
3868
+ list: {},
3869
+ fork: {},
3870
+ resume: {}
3871
+ },
3393
3872
  _meta: {
3394
3873
  posthog: {
3395
3874
  resumeSession: true
3875
+ },
3876
+ claudeCode: {
3877
+ promptQueueing: true
3878
+ }
3879
+ }
3880
+ },
3881
+ agentInfo: {
3882
+ name: package_default.name,
3883
+ title: "Claude Agent",
3884
+ version: package_default.version
3885
+ },
3886
+ authMethods: []
3887
+ };
3888
+ }
3889
+ async newSession(params) {
3890
+ if (fs3.existsSync(path4.resolve(os4.homedir(), ".claude.json.backup")) && !fs3.existsSync(path4.resolve(os4.homedir(), ".claude.json"))) {
3891
+ throw RequestError2.authRequired();
3892
+ }
3893
+ const response = await this.createSession(params, {
3894
+ // Revisit these meta values once we support resume
3895
+ resume: params._meta?.claudeCode?.options?.resume
3896
+ });
3897
+ return response;
3898
+ }
3899
+ async unstable_forkSession(params) {
3900
+ return this.createSession(
3901
+ {
3902
+ cwd: params.cwd,
3903
+ mcpServers: params.mcpServers ?? [],
3904
+ _meta: params._meta
3905
+ },
3906
+ { resume: params.sessionId, forkSession: true }
3907
+ );
3908
+ }
3909
+ async unstable_resumeSession(params) {
3910
+ const response = await this.createSession(
3911
+ {
3912
+ cwd: params.cwd,
3913
+ mcpServers: params.mcpServers ?? [],
3914
+ _meta: params._meta
3915
+ },
3916
+ {
3917
+ resume: params.sessionId
3918
+ }
3919
+ );
3920
+ return response;
3921
+ }
3922
+ async loadSession(params) {
3923
+ const response = await this.createSession(
3924
+ {
3925
+ cwd: params.cwd,
3926
+ mcpServers: params.mcpServers ?? [],
3927
+ _meta: params._meta
3928
+ },
3929
+ { resume: params.sessionId, skipBackgroundFetches: true }
3930
+ );
3931
+ await this.replaySessionHistory(params.sessionId);
3932
+ this.deferBackgroundFetches(this.session.query);
3933
+ return {
3934
+ modes: response.modes,
3935
+ models: response.models,
3936
+ configOptions: response.configOptions
3937
+ };
3938
+ }
3939
+ async unstable_listSessions(params) {
3940
+ const sdkSessions = await listSessions({ dir: params.cwd ?? void 0 });
3941
+ const sessions = [];
3942
+ for (const session of sdkSessions) {
3943
+ if (!session.cwd) continue;
3944
+ sessions.push({
3945
+ sessionId: session.sessionId,
3946
+ cwd: session.cwd,
3947
+ title: sanitizeTitle(session.customTitle || session.summary || ""),
3948
+ updatedAt: new Date(session.lastModified).toISOString()
3949
+ });
3950
+ }
3951
+ return {
3952
+ sessions
3953
+ };
3954
+ }
3955
+ async prompt(params) {
3956
+ this.session.cancelled = false;
3957
+ this.session.interruptReason = void 0;
3958
+ this.session.accumulatedUsage = {
3959
+ inputTokens: 0,
3960
+ outputTokens: 0,
3961
+ cachedReadTokens: 0,
3962
+ cachedWriteTokens: 0
3963
+ };
3964
+ const userMessage = promptToClaude(params);
3965
+ if (this.session.promptRunning) {
3966
+ const uuid = randomUUID();
3967
+ userMessage.uuid = uuid;
3968
+ this.session.input.push(userMessage);
3969
+ const order = this.session.nextPendingOrder++;
3970
+ const cancelled = await new Promise((resolve4) => {
3971
+ this.session.pendingMessages.set(uuid, { resolve: resolve4, order });
3972
+ });
3973
+ if (cancelled) {
3974
+ return { stopReason: "cancelled" };
3975
+ }
3976
+ } else {
3977
+ this.session.input.push(userMessage);
3978
+ }
3979
+ await this.broadcastUserMessage(params);
3980
+ this.session.promptRunning = true;
3981
+ let handedOff = false;
3982
+ let lastAssistantTotalUsage = null;
3983
+ const supportsTerminalOutput = this.clientCapabilities?._meta?.terminal_output === true;
3984
+ const context = {
3985
+ session: this.session,
3986
+ sessionId: params.sessionId,
3987
+ client: this.client,
3988
+ toolUseCache: this.toolUseCache,
3989
+ fileContentCache: this.fileContentCache,
3990
+ logger: this.logger,
3991
+ supportsTerminalOutput
3992
+ };
3993
+ try {
3994
+ while (true) {
3995
+ const { value: message, done } = await this.session.query.next();
3996
+ if (done || !message) {
3997
+ if (this.session.cancelled) {
3998
+ return {
3999
+ stopReason: "cancelled",
4000
+ _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
4001
+ };
4002
+ }
4003
+ break;
4004
+ }
4005
+ switch (message.type) {
4006
+ case "system":
4007
+ if (message.subtype === "compact_boundary") {
4008
+ lastAssistantTotalUsage = 0;
4009
+ }
4010
+ await handleSystemMessage(message, context);
4011
+ break;
4012
+ case "result": {
4013
+ if (this.session.cancelled) {
4014
+ return { stopReason: "cancelled" };
4015
+ }
4016
+ this.session.accumulatedUsage.inputTokens += message.usage.input_tokens;
4017
+ this.session.accumulatedUsage.outputTokens += message.usage.output_tokens;
4018
+ this.session.accumulatedUsage.cachedReadTokens += message.usage.cache_read_input_tokens;
4019
+ this.session.accumulatedUsage.cachedWriteTokens += message.usage.cache_creation_input_tokens;
4020
+ const contextWindows = Object.values(message.modelUsage).map(
4021
+ (m) => m.contextWindow
4022
+ );
4023
+ const contextWindowSize = contextWindows.length > 0 ? Math.min(...contextWindows) : 2e5;
4024
+ if (lastAssistantTotalUsage !== null) {
4025
+ await this.client.sessionUpdate({
4026
+ sessionId: params.sessionId,
4027
+ update: {
4028
+ sessionUpdate: "usage_update",
4029
+ used: lastAssistantTotalUsage,
4030
+ size: contextWindowSize,
4031
+ cost: {
4032
+ amount: message.total_cost_usd,
4033
+ currency: "USD"
4034
+ }
4035
+ }
4036
+ });
4037
+ }
4038
+ await this.client.extNotification("_posthog/usage_update", {
4039
+ sessionId: params.sessionId,
4040
+ used: {
4041
+ inputTokens: message.usage.input_tokens,
4042
+ outputTokens: message.usage.output_tokens,
4043
+ cachedReadTokens: message.usage.cache_read_input_tokens,
4044
+ cachedWriteTokens: message.usage.cache_creation_input_tokens
4045
+ },
4046
+ cost: message.total_cost_usd
4047
+ });
4048
+ const usage = {
4049
+ inputTokens: this.session.accumulatedUsage.inputTokens,
4050
+ outputTokens: this.session.accumulatedUsage.outputTokens,
4051
+ cachedReadTokens: this.session.accumulatedUsage.cachedReadTokens,
4052
+ cachedWriteTokens: this.session.accumulatedUsage.cachedWriteTokens,
4053
+ totalTokens: this.session.accumulatedUsage.inputTokens + this.session.accumulatedUsage.outputTokens + this.session.accumulatedUsage.cachedReadTokens + this.session.accumulatedUsage.cachedWriteTokens
4054
+ };
4055
+ const result = handleResultMessage(message);
4056
+ if (result.error) throw result.error;
4057
+ switch (message.subtype) {
4058
+ case "error_max_budget_usd":
4059
+ case "error_max_turns":
4060
+ case "error_max_structured_output_retries":
4061
+ return { stopReason: "max_turn_requests", usage };
4062
+ default:
4063
+ return { stopReason: "end_turn", usage };
4064
+ }
4065
+ }
4066
+ case "stream_event":
4067
+ await handleStreamEvent(message, context);
4068
+ break;
4069
+ case "user":
4070
+ case "assistant": {
4071
+ if (this.session.cancelled) {
4072
+ break;
4073
+ }
4074
+ if (message.type === "user" && "uuid" in message && message.uuid) {
4075
+ const pending = this.session.pendingMessages.get(
4076
+ message.uuid
4077
+ );
4078
+ if (pending) {
4079
+ pending.resolve(false);
4080
+ this.session.pendingMessages.delete(message.uuid);
4081
+ handedOff = true;
4082
+ return { stopReason: "end_turn" };
4083
+ }
4084
+ }
4085
+ if ("usage" in message.message && message.parent_tool_use_id === null) {
4086
+ const usage = message.message.usage;
4087
+ lastAssistantTotalUsage = usage.input_tokens + usage.output_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens;
4088
+ }
4089
+ const result = await handleUserAssistantMessage(message, context);
4090
+ if (result.error) throw result.error;
4091
+ if (result.shouldStop) {
4092
+ return { stopReason: "end_turn" };
4093
+ }
4094
+ break;
3396
4095
  }
4096
+ case "tool_progress":
4097
+ case "auth_status":
4098
+ case "tool_use_summary":
4099
+ break;
4100
+ default:
4101
+ unreachable(message, this.logger);
4102
+ break;
3397
4103
  }
3398
- },
3399
- agentInfo: {
3400
- name: package_default.name,
3401
- title: "Claude Code",
3402
- version: package_default.version
3403
- },
3404
- authMethods: [
3405
- {
3406
- id: "claude-login",
3407
- name: "Log in with Claude Code",
3408
- description: "Run `claude /login` in the terminal"
4104
+ }
4105
+ throw new Error("Session did not end in result");
4106
+ } finally {
4107
+ if (!handedOff) {
4108
+ this.session.promptRunning = false;
4109
+ for (const [key, pending] of this.session.pendingMessages) {
4110
+ pending.resolve(true);
4111
+ this.session.pendingMessages.delete(key);
3409
4112
  }
3410
- ]
3411
- };
4113
+ }
4114
+ }
3412
4115
  }
3413
- async authenticate(_params) {
3414
- throw new Error("Method not implemented.");
4116
+ // Called by BaseAcpAgent#cancel() to interrupt the session
4117
+ async interrupt() {
4118
+ this.session.cancelled = true;
4119
+ for (const [, pending] of this.session.pendingMessages) {
4120
+ pending.resolve(true);
4121
+ }
4122
+ this.session.pendingMessages.clear();
4123
+ await this.session.query.interrupt();
3415
4124
  }
3416
- async newSession(params) {
3417
- this.checkAuthStatus();
4125
+ async unstable_setSessionModel(params) {
4126
+ const sdkModelId = toSdkModelId(params.modelId);
4127
+ await this.session.query.setModel(sdkModelId);
4128
+ this.session.modelId = params.modelId;
4129
+ await this.updateConfigOption("model", params.modelId);
4130
+ return {};
4131
+ }
4132
+ async setSessionMode(params) {
4133
+ await this.applySessionMode(params.modeId);
4134
+ await this.updateConfigOption("mode", params.modeId);
4135
+ return {};
4136
+ }
4137
+ async setSessionConfigOption(params) {
4138
+ const option = this.session.configOptions.find(
4139
+ (o) => o.id === params.configId
4140
+ );
4141
+ if (!option) {
4142
+ throw new Error(`Unknown config option: ${params.configId}`);
4143
+ }
4144
+ const allValues = "options" in option && Array.isArray(option.options) ? option.options.flatMap(
4145
+ (o) => "options" in o && Array.isArray(o.options) ? o.options : [o]
4146
+ ) : [];
4147
+ const validValue = allValues.find((o) => o.value === params.value);
4148
+ if (!validValue) {
4149
+ throw new Error(
4150
+ `Invalid value for config option ${params.configId}: ${params.value}`
4151
+ );
4152
+ }
4153
+ if (params.configId === "mode") {
4154
+ await this.applySessionMode(params.value);
4155
+ await this.client.sessionUpdate({
4156
+ sessionId: this.sessionId,
4157
+ update: {
4158
+ sessionUpdate: "current_mode_update",
4159
+ currentModeId: params.value
4160
+ }
4161
+ });
4162
+ } else if (params.configId === "model") {
4163
+ const sdkModelId = toSdkModelId(params.value);
4164
+ await this.session.query.setModel(sdkModelId);
4165
+ this.session.modelId = params.value;
4166
+ }
4167
+ this.session.configOptions = this.session.configOptions.map(
4168
+ (o) => o.id === params.configId ? { ...o, currentValue: params.value } : o
4169
+ );
4170
+ return { configOptions: this.session.configOptions };
4171
+ }
4172
+ async updateConfigOption(configId, value) {
4173
+ this.session.configOptions = this.session.configOptions.map(
4174
+ (o) => o.id === configId ? { ...o, currentValue: value } : o
4175
+ );
4176
+ await this.client.sessionUpdate({
4177
+ sessionId: this.sessionId,
4178
+ update: {
4179
+ sessionUpdate: "config_option_update",
4180
+ configOptions: this.session.configOptions
4181
+ }
4182
+ });
4183
+ }
4184
+ async applySessionMode(modeId) {
4185
+ if (!TWIG_EXECUTION_MODES.includes(modeId)) {
4186
+ throw new Error("Invalid Mode");
4187
+ }
4188
+ const previousMode = this.session.permissionMode;
4189
+ this.session.permissionMode = modeId;
4190
+ try {
4191
+ await this.session.query.setPermissionMode(modeId);
4192
+ } catch (error) {
4193
+ this.session.permissionMode = previousMode;
4194
+ if (error instanceof Error) {
4195
+ if (!error.message) {
4196
+ error.message = "Invalid Mode";
4197
+ }
4198
+ throw error;
4199
+ }
4200
+ throw new Error("Invalid Mode");
4201
+ }
4202
+ }
4203
+ async createSession(params, creationOpts = {}) {
4204
+ const { cwd } = params;
4205
+ const { resume, forkSession } = creationOpts;
4206
+ const isResume = !!resume;
3418
4207
  const meta = params._meta;
3419
4208
  const taskId = meta?.persistence?.taskId;
3420
- const sessionId = uuidv7();
3421
- this.logger.info("Creating new session", {
4209
+ let sessionId;
4210
+ if (forkSession) {
4211
+ sessionId = uuidv7();
4212
+ } else if (isResume) {
4213
+ sessionId = resume;
4214
+ } else {
4215
+ sessionId = uuidv7();
4216
+ }
4217
+ const input = new Pushable();
4218
+ const settingsManager = new SettingsManager(cwd);
4219
+ await settingsManager.initialize();
4220
+ const mcpServers = parseMcpServers(params);
4221
+ const systemPrompt = buildSystemPrompt(meta?.systemPrompt);
4222
+ this.logger.info(isResume ? "Resuming session" : "Creating new session", {
3422
4223
  sessionId,
3423
4224
  taskId,
3424
4225
  taskRunId: meta?.taskRunId,
3425
- cwd: params.cwd
4226
+ cwd
3426
4227
  });
3427
4228
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3428
- const mcpServers = parseMcpServers(params);
3429
4229
  const options = buildSessionOptions({
3430
- cwd: params.cwd,
4230
+ cwd,
3431
4231
  mcpServers,
3432
4232
  permissionMode,
3433
4233
  canUseTool: this.createCanUseTool(sessionId),
3434
4234
  logger: this.logger,
3435
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
4235
+ systemPrompt,
3436
4236
  userProvidedOptions: meta?.claudeCode?.options,
3437
4237
  sessionId,
3438
- isResume: false,
3439
- onModeChange: this.createOnModeChange(sessionId),
4238
+ isResume,
4239
+ forkSession,
4240
+ additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
4241
+ disableBuiltInTools: meta?.disableBuiltInTools,
4242
+ settingsManager,
4243
+ onModeChange: this.createOnModeChange(),
3440
4244
  onProcessSpawned: this.options?.onProcessSpawned,
3441
4245
  onProcessExited: this.options?.onProcessExited
3442
4246
  });
3443
- const input = new Pushable();
3444
- options.model = DEFAULT_MODEL;
4247
+ const abortController = options.abortController;
3445
4248
  const q = query({ prompt: input, options });
3446
- const session = this.createSession(
3447
- sessionId,
3448
- q,
4249
+ const session = {
4250
+ query: q,
3449
4251
  input,
4252
+ cancelled: false,
4253
+ settingsManager,
3450
4254
  permissionMode,
3451
- params.cwd,
3452
- options.abortController
3453
- );
3454
- session.taskRunId = meta?.taskRunId;
3455
- if (meta?.taskRunId) {
3456
- await this.client.extNotification("_posthog/sdk_session", {
3457
- taskRunId: meta.taskRunId,
3458
- sessionId,
3459
- adapter: "claude"
3460
- });
3461
- }
3462
- const modelOptions = await this.getModelConfigOptions();
3463
- this.deferBackgroundFetches(q, sessionId);
3464
- session.modelId = modelOptions.currentModelId;
3465
- const resolvedSdkModel = toSdkModelId(modelOptions.currentModelId);
3466
- if (resolvedSdkModel !== DEFAULT_MODEL) {
3467
- await this.trySetModel(q, modelOptions.currentModelId);
3468
- }
3469
- const configOptions = await this.buildConfigOptions(modelOptions);
3470
- return {
3471
- sessionId,
3472
- configOptions
3473
- };
3474
- }
3475
- async loadSession(params) {
3476
- return this.resumeSession(params);
3477
- }
3478
- async resumeSession(params) {
3479
- const meta = params._meta;
3480
- const taskId = meta?.persistence?.taskId;
3481
- const sessionId = meta?.sessionId;
3482
- if (!sessionId) {
3483
- throw new Error("Cannot resume session without sessionId");
3484
- }
3485
- if (this.sessionId === sessionId) {
3486
- return {};
3487
- }
3488
- this.logger.info("Resuming session", {
3489
- sessionId,
3490
- taskId,
3491
- taskRunId: meta?.taskRunId,
3492
- cwd: params.cwd
3493
- });
3494
- const mcpServers = parseMcpServers(params);
3495
- const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3496
- const { query: q, session } = await this.initializeQuery({
3497
- cwd: params.cwd,
3498
- permissionMode,
3499
- mcpServers,
3500
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3501
- userProvidedOptions: meta?.claudeCode?.options,
3502
- sessionId,
3503
- isResume: true,
3504
- additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3505
- });
3506
- this.logger.info("Session query initialized, awaiting resumption", {
3507
- sessionId,
3508
- taskId,
4255
+ abortController,
4256
+ accumulatedUsage: {
4257
+ inputTokens: 0,
4258
+ outputTokens: 0,
4259
+ cachedReadTokens: 0,
4260
+ cachedWriteTokens: 0
4261
+ },
4262
+ configOptions: [],
4263
+ promptRunning: false,
4264
+ pendingMessages: /* @__PURE__ */ new Map(),
4265
+ nextPendingOrder: 0,
4266
+ // Custom properties
4267
+ cwd,
4268
+ notificationHistory: [],
3509
4269
  taskRunId: meta?.taskRunId
3510
- });
3511
- session.taskRunId = meta?.taskRunId;
4270
+ };
4271
+ this.session = session;
4272
+ this.sessionId = sessionId;
4273
+ this.logger.info(
4274
+ isResume ? "Session query initialized, awaiting resumption" : "Session query initialized, awaiting initialization",
4275
+ { sessionId, taskId, taskRunId: meta?.taskRunId }
4276
+ );
3512
4277
  try {
3513
4278
  const result = await withTimeout(
3514
4279
  q.initializationResult(),
@@ -3516,231 +4281,181 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3516
4281
  );
3517
4282
  if (result.result === "timeout") {
3518
4283
  throw new Error(
3519
- `Session resumption timed out for sessionId=${sessionId}`
4284
+ `Session ${isResume ? forkSession ? "fork" : "resumption" : "initialization"} timed out for sessionId=${sessionId}`
3520
4285
  );
3521
4286
  }
3522
4287
  } catch (err) {
3523
- this.logger.error("Session resumption failed", {
3524
- sessionId,
3525
- taskId,
3526
- taskRunId: meta?.taskRunId,
3527
- error: err instanceof Error ? err.message : String(err)
3528
- });
4288
+ settingsManager.dispose();
4289
+ this.logger.error(
4290
+ isResume ? forkSession ? "Session fork failed" : "Session resumption failed" : "Session initialization failed",
4291
+ {
4292
+ sessionId,
4293
+ taskId,
4294
+ taskRunId: meta?.taskRunId,
4295
+ error: err instanceof Error ? err.message : String(err)
4296
+ }
4297
+ );
3529
4298
  throw err;
3530
4299
  }
3531
- this.logger.info("Session resumed successfully", {
3532
- sessionId,
3533
- taskId,
3534
- taskRunId: meta?.taskRunId
3535
- });
3536
- this.deferBackgroundFetches(q, sessionId);
3537
- const configOptions = await this.buildConfigOptions();
3538
- return { configOptions };
3539
- }
3540
- async prompt(params) {
3541
- this.session.cancelled = false;
3542
- this.session.interruptReason = void 0;
3543
- await this.broadcastUserMessage(params);
3544
- this.session.input.push(promptToClaude(params));
3545
- return this.processMessages(params.sessionId);
3546
- }
3547
- async setSessionConfigOption(params) {
3548
- const configId = params.configId;
3549
- const value = params.value;
3550
- if (configId === "mode") {
3551
- const modeId = value;
3552
- if (!TWIG_EXECUTION_MODES.includes(modeId)) {
3553
- throw new Error("Invalid Mode");
3554
- }
3555
- this.session.permissionMode = modeId;
3556
- await this.session.query.setPermissionMode(modeId);
3557
- } else if (configId === "model") {
3558
- await this.setModelWithFallback(this.session.query, value);
3559
- this.session.modelId = value;
3560
- } else {
3561
- throw new Error("Unsupported config option");
3562
- }
3563
- await this.emitConfigOptionsUpdate();
3564
- return { configOptions: await this.buildConfigOptions() };
3565
- }
3566
- async interruptSession() {
3567
- await this.session.query.interrupt();
3568
- }
3569
- async extMethod(method, params) {
3570
- if (method === "_posthog/session/resume") {
3571
- const result = await this.resumeSession(
3572
- params
3573
- );
3574
- return {
3575
- _meta: {
3576
- configOptions: result.configOptions
3577
- }
3578
- };
4300
+ if (meta?.taskRunId) {
4301
+ await this.client.extNotification("_posthog/sdk_session", {
4302
+ taskRunId: meta.taskRunId,
4303
+ sessionId,
4304
+ adapter: "claude"
4305
+ });
3579
4306
  }
3580
- throw RequestError2.methodNotFound(method);
3581
- }
3582
- createSession(sessionId, q, input, permissionMode, cwd, abortController) {
3583
- const session = {
3584
- query: q,
3585
- input,
3586
- cancelled: false,
3587
- permissionMode,
3588
- cwd,
3589
- notificationHistory: [],
3590
- abortController
4307
+ const settingsModel = settingsManager.getSettings().model;
4308
+ const modelOptions = await this.getModelConfigOptions();
4309
+ const resolvedModelId = settingsModel || modelOptions.currentModelId;
4310
+ session.modelId = resolvedModelId;
4311
+ if (!isResume) {
4312
+ const resolvedSdkModel = toSdkModelId(resolvedModelId);
4313
+ if (resolvedSdkModel !== DEFAULT_MODEL) {
4314
+ await this.session.query.setModel(resolvedSdkModel);
4315
+ }
4316
+ }
4317
+ const availableModes2 = getAvailableModes();
4318
+ const modes = {
4319
+ currentModeId: permissionMode,
4320
+ availableModes: availableModes2.map((mode) => ({
4321
+ id: mode.id,
4322
+ name: mode.name,
4323
+ description: mode.description ?? void 0
4324
+ }))
4325
+ };
4326
+ const models = {
4327
+ currentModelId: resolvedModelId,
4328
+ availableModels: modelOptions.options.map(
4329
+ (opt) => ({
4330
+ modelId: opt.value,
4331
+ name: opt.name,
4332
+ description: opt.description
4333
+ })
4334
+ )
3591
4335
  };
3592
- this.session = session;
3593
- this.sessionId = sessionId;
3594
- return session;
3595
- }
3596
- async initializeQuery(config) {
3597
- const input = new Pushable();
3598
- const options = buildSessionOptions({
3599
- cwd: config.cwd,
3600
- mcpServers: config.mcpServers,
3601
- permissionMode: config.permissionMode,
3602
- canUseTool: this.createCanUseTool(config.sessionId),
3603
- logger: this.logger,
3604
- systemPrompt: config.systemPrompt,
3605
- userProvidedOptions: config.userProvidedOptions,
3606
- sessionId: config.sessionId,
3607
- isResume: config.isResume,
3608
- additionalDirectories: config.additionalDirectories,
3609
- onModeChange: this.createOnModeChange(config.sessionId),
3610
- onProcessSpawned: this.options?.onProcessSpawned,
3611
- onProcessExited: this.options?.onProcessExited
3612
- });
3613
- const q = query({ prompt: input, options });
3614
- const abortController = options.abortController;
3615
- const session = this.createSession(
3616
- config.sessionId,
3617
- q,
3618
- input,
3619
- config.permissionMode,
3620
- config.cwd,
3621
- abortController
4336
+ const configOptions = this.buildConfigOptions(permissionMode, modelOptions);
4337
+ session.configOptions = configOptions;
4338
+ if (!creationOpts.skipBackgroundFetches) {
4339
+ this.deferBackgroundFetches(q);
4340
+ }
4341
+ this.logger.info(
4342
+ isResume ? "Session resumed successfully" : "Session created successfully",
4343
+ {
4344
+ sessionId,
4345
+ taskId,
4346
+ taskRunId: meta?.taskRunId
4347
+ }
3622
4348
  );
3623
- return { query: q, input, session };
4349
+ return { sessionId, modes, models, configOptions };
3624
4350
  }
3625
4351
  createCanUseTool(sessionId) {
3626
- return async (toolName, toolInput, { suggestions, toolUseID }) => canUseTool({
4352
+ return async (toolName, toolInput, { suggestions, toolUseID, signal }) => canUseTool({
3627
4353
  session: this.session,
3628
4354
  toolName,
3629
4355
  toolInput,
3630
4356
  toolUseID,
3631
4357
  suggestions,
4358
+ signal,
3632
4359
  client: this.client,
3633
4360
  sessionId,
3634
4361
  fileContentCache: this.fileContentCache,
3635
4362
  logger: this.logger,
3636
- emitConfigOptionsUpdate: () => this.emitConfigOptionsUpdate(sessionId)
4363
+ updateConfigOption: (configId, value) => this.updateConfigOption(configId, value)
3637
4364
  });
3638
4365
  }
3639
- createOnModeChange(sessionId) {
4366
+ createOnModeChange() {
3640
4367
  return async (newMode) => {
3641
4368
  if (this.session) {
3642
4369
  this.session.permissionMode = newMode;
3643
4370
  }
3644
- await this.emitConfigOptionsUpdate(sessionId);
4371
+ await this.updateConfigOption("mode", newMode);
3645
4372
  };
3646
4373
  }
3647
- async buildConfigOptions(modelOptionsOverride) {
3648
- const options = [];
4374
+ buildConfigOptions(currentModeId, modelOptions) {
3649
4375
  const modeOptions = getAvailableModes().map((mode) => ({
3650
4376
  value: mode.id,
3651
4377
  name: mode.name,
3652
4378
  description: mode.description ?? void 0
3653
4379
  }));
3654
- options.push({
3655
- id: "mode",
3656
- name: "Approval Preset",
3657
- type: "select",
3658
- currentValue: this.session.permissionMode,
3659
- options: modeOptions,
3660
- category: "mode",
3661
- description: "Choose an approval and sandboxing preset for your session"
3662
- });
3663
- const modelOptions = modelOptionsOverride ?? await this.getModelConfigOptions(this.session.modelId);
3664
- this.session.modelId = modelOptions.currentModelId;
3665
- options.push({
3666
- id: "model",
3667
- name: "Model",
3668
- type: "select",
3669
- currentValue: modelOptions.currentModelId,
3670
- options: modelOptions.options,
3671
- category: "model",
3672
- description: "Choose which model Claude should use"
3673
- });
3674
- return options;
4380
+ return [
4381
+ {
4382
+ id: "mode",
4383
+ name: "Approval Preset",
4384
+ type: "select",
4385
+ currentValue: currentModeId,
4386
+ options: modeOptions,
4387
+ category: "mode",
4388
+ description: "Choose an approval and sandboxing preset for your session"
4389
+ },
4390
+ {
4391
+ id: "model",
4392
+ name: "Model",
4393
+ type: "select",
4394
+ currentValue: modelOptions.currentModelId,
4395
+ options: modelOptions.options,
4396
+ category: "model",
4397
+ description: "Choose which model Claude should use"
4398
+ }
4399
+ ];
3675
4400
  }
3676
- async emitConfigOptionsUpdate(sessionId) {
3677
- const configOptions = await this.buildConfigOptions();
3678
- const serialized = JSON.stringify(configOptions);
3679
- if (this.lastSentConfigOptions && JSON.stringify(this.lastSentConfigOptions) === serialized) {
3680
- return;
3681
- }
3682
- this.lastSentConfigOptions = configOptions;
4401
+ async sendAvailableCommandsUpdate() {
4402
+ const commands = await this.session.query.supportedCommands();
3683
4403
  await this.client.sessionUpdate({
3684
- sessionId: sessionId ?? this.sessionId,
4404
+ sessionId: this.sessionId,
3685
4405
  update: {
3686
- sessionUpdate: "config_option_update",
3687
- configOptions
4406
+ sessionUpdate: "available_commands_update",
4407
+ availableCommands: getAvailableSlashCommands(commands)
3688
4408
  }
3689
4409
  });
3690
4410
  }
3691
- checkAuthStatus() {
3692
- const backupExists = fs2.existsSync(
3693
- path3.resolve(os3.homedir(), ".claude.json.backup")
3694
- );
3695
- const configExists = fs2.existsSync(
3696
- path3.resolve(os3.homedir(), ".claude.json")
3697
- );
3698
- if (backupExists && !configExists) {
3699
- throw RequestError2.authRequired();
3700
- }
3701
- }
3702
- async trySetModel(q, modelId) {
3703
- try {
3704
- await this.setModelWithFallback(q, modelId);
3705
- } catch (err) {
3706
- this.logger.warn("Failed to set model", { modelId, error: err });
3707
- }
3708
- }
3709
- async setModelWithFallback(q, modelId) {
3710
- const sdkModelId = toSdkModelId(modelId);
4411
+ async replaySessionHistory(sessionId) {
3711
4412
  try {
3712
- await q.setModel(sdkModelId);
3713
- } catch (err) {
3714
- if (sdkModelId === modelId) {
3715
- throw err;
4413
+ const messages = await getSessionMessages(sessionId, {
4414
+ dir: this.session.cwd
4415
+ });
4416
+ const replayContext = {
4417
+ session: this.session,
4418
+ sessionId,
4419
+ client: this.client,
4420
+ toolUseCache: this.toolUseCache,
4421
+ fileContentCache: this.fileContentCache,
4422
+ logger: this.logger,
4423
+ registerHooks: false
4424
+ };
4425
+ for (const msg of messages) {
4426
+ const sdkMessage = {
4427
+ type: msg.type,
4428
+ message: msg.message,
4429
+ parent_tool_use_id: msg.parent_tool_use_id
4430
+ };
4431
+ await handleUserAssistantMessage(
4432
+ sdkMessage,
4433
+ replayContext
4434
+ );
3716
4435
  }
3717
- await q.setModel(modelId);
4436
+ } catch (err) {
4437
+ this.logger.warn("Failed to replay session history", {
4438
+ sessionId,
4439
+ error: err instanceof Error ? err.message : String(err)
4440
+ });
3718
4441
  }
3719
4442
  }
4443
+ // ================================
4444
+ // EXTENSION METHODS
4445
+ // ================================
3720
4446
  /**
3721
4447
  * Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
3722
4448
  * Both populate caches used later — neither is needed to return configOptions.
3723
4449
  */
3724
- deferBackgroundFetches(q, sessionId) {
4450
+ deferBackgroundFetches(q) {
3725
4451
  Promise.all([
3726
- getAvailableSlashCommands(q),
4452
+ new Promise((resolve4) => setTimeout(resolve4, 10)).then(
4453
+ () => this.sendAvailableCommandsUpdate()
4454
+ ),
3727
4455
  fetchMcpToolMetadata(q, this.logger)
3728
- ]).then(([slashCommands]) => {
3729
- this.sendAvailableCommandsUpdate(sessionId, slashCommands);
3730
- }).catch((err) => {
3731
- this.logger.warn("Failed to fetch deferred session data", { err });
3732
- });
3733
- }
3734
- sendAvailableCommandsUpdate(sessionId, availableCommands) {
3735
- setTimeout(() => {
3736
- this.client.sessionUpdate({
3737
- sessionId,
3738
- update: {
3739
- sessionUpdate: "available_commands_update",
3740
- availableCommands
3741
- }
3742
- });
3743
- }, 0);
4456
+ ]).catch(
4457
+ (err) => this.logger.error("Background fetch failed", { error: err })
4458
+ );
3744
4459
  }
3745
4460
  async broadcastUserMessage(params) {
3746
4461
  for (const chunk of params.prompt) {
@@ -3755,71 +4470,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3755
4470
  this.appendNotification(params.sessionId, notification);
3756
4471
  }
3757
4472
  }
3758
- async processMessages(sessionId) {
3759
- const context = {
3760
- session: this.session,
3761
- sessionId,
3762
- client: this.client,
3763
- toolUseCache: this.toolUseCache,
3764
- fileContentCache: this.fileContentCache,
3765
- logger: this.logger
3766
- };
3767
- while (true) {
3768
- const { value: message, done } = await this.session.query.next();
3769
- if (done || !message) {
3770
- return this.handleSessionEnd();
3771
- }
3772
- const response = await this.handleMessage(message, context);
3773
- if (response) {
3774
- return response;
3775
- }
3776
- }
3777
- }
3778
- handleSessionEnd() {
3779
- if (this.session.cancelled) {
3780
- return {
3781
- stopReason: "cancelled",
3782
- _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
3783
- };
3784
- }
3785
- throw new Error("Session did not end in result");
3786
- }
3787
- async handleMessage(message, context) {
3788
- switch (message.type) {
3789
- case "system":
3790
- await handleSystemMessage(message, context);
3791
- return null;
3792
- case "result": {
3793
- const result = handleResultMessage(message, context);
3794
- if (result.error) throw result.error;
3795
- if (result.shouldStop) {
3796
- return {
3797
- stopReason: result.stopReason
3798
- };
3799
- }
3800
- return null;
3801
- }
3802
- case "stream_event":
3803
- await handleStreamEvent(message, context);
3804
- return null;
3805
- case "user":
3806
- case "assistant": {
3807
- const result = await handleUserAssistantMessage(message, context);
3808
- if (result.error) throw result.error;
3809
- if (result.shouldStop) {
3810
- return { stopReason: "end_turn" };
3811
- }
3812
- return null;
3813
- }
3814
- case "tool_progress":
3815
- case "auth_status":
3816
- case "tool_use_summary":
3817
- return null;
3818
- default:
3819
- unreachable(message, this.logger);
3820
- return null;
3821
- }
3822
- }
3823
4473
  };
3824
4474
 
3825
4475
  // src/adapters/codex/spawn.ts
@@ -3884,7 +4534,7 @@ function spawnCodexProcess(options) {
3884
4534
  detached: process.platform !== "win32"
3885
4535
  });
3886
4536
  child.stderr?.on("data", (data) => {
3887
- logger.error("codex-acp stderr:", data.toString());
4537
+ logger.debug("codex-acp stderr:", data.toString());
3888
4538
  });
3889
4539
  child.on("error", (err) => {
3890
4540
  logger.error("codex-acp process error:", err);
@@ -4446,8 +5096,8 @@ var PostHogAPIClient = class {
4446
5096
  };
4447
5097
 
4448
5098
  // src/session-log-writer.ts
4449
- import fs3 from "fs";
4450
- import path4 from "path";
5099
+ import fs4 from "fs";
5100
+ import path5 from "path";
4451
5101
  var SessionLogWriter = class _SessionLogWriter {
4452
5102
  static FLUSH_DEBOUNCE_MS = 500;
4453
5103
  static FLUSH_MAX_INTERVAL_MS = 5e3;
@@ -4485,13 +5135,13 @@ var SessionLogWriter = class _SessionLogWriter {
4485
5135
  this.sessions.set(sessionId, { context });
4486
5136
  this.lastFlushAttemptTime.set(sessionId, Date.now());
4487
5137
  if (this.localCachePath) {
4488
- const sessionDir = path4.join(
5138
+ const sessionDir = path5.join(
4489
5139
  this.localCachePath,
4490
5140
  "sessions",
4491
5141
  context.runId
4492
5142
  );
4493
5143
  try {
4494
- fs3.mkdirSync(sessionDir, { recursive: true });
5144
+ fs4.mkdirSync(sessionDir, { recursive: true });
4495
5145
  } catch (error) {
4496
5146
  this.logger.warn("Failed to create local cache directory", {
4497
5147
  sessionDir,
@@ -4688,14 +5338,14 @@ var SessionLogWriter = class _SessionLogWriter {
4688
5338
  if (!this.localCachePath) return;
4689
5339
  const session = this.sessions.get(sessionId);
4690
5340
  if (!session) return;
4691
- const logPath = path4.join(
5341
+ const logPath = path5.join(
4692
5342
  this.localCachePath,
4693
5343
  "sessions",
4694
5344
  session.context.runId,
4695
5345
  "logs.ndjson"
4696
5346
  );
4697
5347
  try {
4698
- fs3.appendFileSync(logPath, `${JSON.stringify(entry)}
5348
+ fs4.appendFileSync(logPath, `${JSON.stringify(entry)}
4699
5349
  `);
4700
5350
  } catch (error) {
4701
5351
  this.logger.warn("Failed to write to local cache", {
@@ -4709,8 +5359,8 @@ var SessionLogWriter = class _SessionLogWriter {
4709
5359
  };
4710
5360
 
4711
5361
  // ../git/dist/queries.js
4712
- import * as fs5 from "fs/promises";
4713
- import * as path6 from "path";
5362
+ import * as fs6 from "fs/promises";
5363
+ import * as path7 from "path";
4714
5364
 
4715
5365
  // ../../node_modules/simple-git/dist/esm/index.js
4716
5366
  var import_file_exists = __toESM(require_dist(), 1);
@@ -4719,7 +5369,7 @@ var import_promise_deferred = __toESM(require_dist2(), 1);
4719
5369
  var import_promise_deferred2 = __toESM(require_dist2(), 1);
4720
5370
  import { Buffer as Buffer2 } from "buffer";
4721
5371
  import { spawn as spawn3 } from "child_process";
4722
- import { normalize } from "path";
5372
+ import { normalize as normalize2 } from "path";
4723
5373
  import { EventEmitter } from "events";
4724
5374
  var __defProp2 = Object.defineProperty;
4725
5375
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -4749,8 +5399,8 @@ function pathspec(...paths) {
4749
5399
  cache.set(key, paths);
4750
5400
  return key;
4751
5401
  }
4752
- function isPathSpec(path8) {
4753
- return path8 instanceof String && cache.has(path8);
5402
+ function isPathSpec(path9) {
5403
+ return path9 instanceof String && cache.has(path9);
4754
5404
  }
4755
5405
  function toPaths(pathSpec) {
4756
5406
  return cache.get(pathSpec) || [];
@@ -4839,8 +5489,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
4839
5489
  function forEachLineWithContent(input, callback) {
4840
5490
  return toLinesWithContent(input, true).map((line) => callback(line));
4841
5491
  }
4842
- function folderExists(path8) {
4843
- return (0, import_file_exists.exists)(path8, import_file_exists.FOLDER);
5492
+ function folderExists(path9) {
5493
+ return (0, import_file_exists.exists)(path9, import_file_exists.FOLDER);
4844
5494
  }
4845
5495
  function append(target, item) {
4846
5496
  if (Array.isArray(target)) {
@@ -5244,8 +5894,8 @@ function checkIsRepoRootTask() {
5244
5894
  commands,
5245
5895
  format: "utf-8",
5246
5896
  onError,
5247
- parser(path8) {
5248
- return /^\.(git)?$/.test(path8.trim());
5897
+ parser(path9) {
5898
+ return /^\.(git)?$/.test(path9.trim());
5249
5899
  }
5250
5900
  };
5251
5901
  }
@@ -5679,11 +6329,11 @@ function parseGrep(grep) {
5679
6329
  const paths = /* @__PURE__ */ new Set();
5680
6330
  const results = {};
5681
6331
  forEachLineWithContent(grep, (input) => {
5682
- const [path8, line, preview] = input.split(NULL);
5683
- paths.add(path8);
5684
- (results[path8] = results[path8] || []).push({
6332
+ const [path9, line, preview] = input.split(NULL);
6333
+ paths.add(path9);
6334
+ (results[path9] = results[path9] || []).push({
5685
6335
  line: asNumber(line),
5686
- path: path8,
6336
+ path: path9,
5687
6337
  preview
5688
6338
  });
5689
6339
  });
@@ -6448,14 +7098,14 @@ var init_hash_object = __esm({
6448
7098
  init_task();
6449
7099
  }
6450
7100
  });
6451
- function parseInit(bare, path8, text2) {
7101
+ function parseInit(bare, path9, text2) {
6452
7102
  const response = String(text2).trim();
6453
7103
  let result;
6454
7104
  if (result = initResponseRegex.exec(response)) {
6455
- return new InitSummary(bare, path8, false, result[1]);
7105
+ return new InitSummary(bare, path9, false, result[1]);
6456
7106
  }
6457
7107
  if (result = reInitResponseRegex.exec(response)) {
6458
- return new InitSummary(bare, path8, true, result[1]);
7108
+ return new InitSummary(bare, path9, true, result[1]);
6459
7109
  }
6460
7110
  let gitDir = "";
6461
7111
  const tokens = response.split(" ");
@@ -6466,7 +7116,7 @@ function parseInit(bare, path8, text2) {
6466
7116
  break;
6467
7117
  }
6468
7118
  }
6469
- return new InitSummary(bare, path8, /^re/i.test(response), gitDir);
7119
+ return new InitSummary(bare, path9, /^re/i.test(response), gitDir);
6470
7120
  }
6471
7121
  var InitSummary;
6472
7122
  var initResponseRegex;
@@ -6475,9 +7125,9 @@ var init_InitSummary = __esm({
6475
7125
  "src/lib/responses/InitSummary.ts"() {
6476
7126
  "use strict";
6477
7127
  InitSummary = class {
6478
- constructor(bare, path8, existing, gitDir) {
7128
+ constructor(bare, path9, existing, gitDir) {
6479
7129
  this.bare = bare;
6480
- this.path = path8;
7130
+ this.path = path9;
6481
7131
  this.existing = existing;
6482
7132
  this.gitDir = gitDir;
6483
7133
  }
@@ -6489,7 +7139,7 @@ var init_InitSummary = __esm({
6489
7139
  function hasBareCommand(command) {
6490
7140
  return command.includes(bareCommand);
6491
7141
  }
6492
- function initTask(bare = false, path8, customArgs) {
7142
+ function initTask(bare = false, path9, customArgs) {
6493
7143
  const commands = ["init", ...customArgs];
6494
7144
  if (bare && !hasBareCommand(commands)) {
6495
7145
  commands.splice(1, 0, bareCommand);
@@ -6498,7 +7148,7 @@ function initTask(bare = false, path8, customArgs) {
6498
7148
  commands,
6499
7149
  format: "utf-8",
6500
7150
  parser(text2) {
6501
- return parseInit(commands.includes("--bare"), path8, text2);
7151
+ return parseInit(commands.includes("--bare"), path9, text2);
6502
7152
  }
6503
7153
  };
6504
7154
  }
@@ -7314,12 +7964,12 @@ var init_FileStatusSummary = __esm({
7314
7964
  "use strict";
7315
7965
  fromPathRegex = /^(.+)\0(.+)$/;
7316
7966
  FileStatusSummary = class {
7317
- constructor(path8, index, working_dir) {
7318
- this.path = path8;
7967
+ constructor(path9, index, working_dir) {
7968
+ this.path = path9;
7319
7969
  this.index = index;
7320
7970
  this.working_dir = working_dir;
7321
7971
  if (index === "R" || working_dir === "R") {
7322
- const detail = fromPathRegex.exec(path8) || [null, path8, path8];
7972
+ const detail = fromPathRegex.exec(path9) || [null, path9, path9];
7323
7973
  this.from = detail[2] || "";
7324
7974
  this.path = detail[1] || "";
7325
7975
  }
@@ -7350,14 +8000,14 @@ function splitLine(result, lineStr) {
7350
8000
  default:
7351
8001
  return;
7352
8002
  }
7353
- function data(index, workingDir, path8) {
8003
+ function data(index, workingDir, path9) {
7354
8004
  const raw = `${index}${workingDir}`;
7355
8005
  const handler = parsers6.get(raw);
7356
8006
  if (handler) {
7357
- handler(result, path8);
8007
+ handler(result, path9);
7358
8008
  }
7359
8009
  if (raw !== "##" && raw !== "!!") {
7360
- result.files.push(new FileStatusSummary(path8, index, workingDir));
8010
+ result.files.push(new FileStatusSummary(path9, index, workingDir));
7361
8011
  }
7362
8012
  }
7363
8013
  }
@@ -7670,9 +8320,9 @@ var init_simple_git_api = __esm({
7670
8320
  next
7671
8321
  );
7672
8322
  }
7673
- hashObject(path8, write) {
8323
+ hashObject(path9, write) {
7674
8324
  return this._runTask(
7675
- hashObjectTask(path8, write === true),
8325
+ hashObjectTask(path9, write === true),
7676
8326
  trailingFunctionArgument(arguments)
7677
8327
  );
7678
8328
  }
@@ -8025,8 +8675,8 @@ var init_branch = __esm({
8025
8675
  }
8026
8676
  });
8027
8677
  function toPath(input) {
8028
- const path8 = input.trim().replace(/^["']|["']$/g, "");
8029
- return path8 && normalize(path8);
8678
+ const path9 = input.trim().replace(/^["']|["']$/g, "");
8679
+ return path9 && normalize2(path9);
8030
8680
  }
8031
8681
  var parseCheckIgnore;
8032
8682
  var init_CheckIgnore = __esm({
@@ -8340,8 +8990,8 @@ __export(sub_module_exports, {
8340
8990
  subModuleTask: () => subModuleTask,
8341
8991
  updateSubModuleTask: () => updateSubModuleTask
8342
8992
  });
8343
- function addSubModuleTask(repo, path8) {
8344
- return subModuleTask(["add", repo, path8]);
8993
+ function addSubModuleTask(repo, path9) {
8994
+ return subModuleTask(["add", repo, path9]);
8345
8995
  }
8346
8996
  function initSubModuleTask(customArgs) {
8347
8997
  return subModuleTask(["init", ...customArgs]);
@@ -8671,8 +9321,8 @@ var require_git = __commonJS2({
8671
9321
  }
8672
9322
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
8673
9323
  };
8674
- Git2.prototype.submoduleAdd = function(repo, path8, then) {
8675
- return this._runTask(addSubModuleTask2(repo, path8), trailingFunctionArgument2(arguments));
9324
+ Git2.prototype.submoduleAdd = function(repo, path9, then) {
9325
+ return this._runTask(addSubModuleTask2(repo, path9), trailingFunctionArgument2(arguments));
8676
9326
  };
8677
9327
  Git2.prototype.submoduleUpdate = function(args, then) {
8678
9328
  return this._runTask(
@@ -9273,22 +9923,22 @@ function createGitClient(baseDir, options) {
9273
9923
 
9274
9924
  // ../git/dist/lock-detector.js
9275
9925
  import { execFile } from "child_process";
9276
- import fs4 from "fs/promises";
9277
- import path5 from "path";
9926
+ import fs5 from "fs/promises";
9927
+ import path6 from "path";
9278
9928
  import { promisify } from "util";
9279
9929
  var execFileAsync = promisify(execFile);
9280
9930
  async function getIndexLockPath(repoPath) {
9281
9931
  try {
9282
9932
  const { stdout } = await execFileAsync("git", ["rev-parse", "--git-path", "index.lock"], { cwd: repoPath });
9283
- return path5.resolve(repoPath, stdout.trim());
9933
+ return path6.resolve(repoPath, stdout.trim());
9284
9934
  } catch {
9285
- return path5.join(repoPath, ".git", "index.lock");
9935
+ return path6.join(repoPath, ".git", "index.lock");
9286
9936
  }
9287
9937
  }
9288
9938
  async function getLockInfo(repoPath) {
9289
9939
  const lockPath = await getIndexLockPath(repoPath);
9290
9940
  try {
9291
- const stat = await fs4.stat(lockPath);
9941
+ const stat = await fs5.stat(lockPath);
9292
9942
  return {
9293
9943
  path: lockPath,
9294
9944
  ageMs: Date.now() - stat.mtimeMs
@@ -9299,7 +9949,7 @@ async function getLockInfo(repoPath) {
9299
9949
  }
9300
9950
  async function removeLock(repoPath) {
9301
9951
  const lockPath = await getIndexLockPath(repoPath);
9302
- await fs4.rm(lockPath, { force: true });
9952
+ await fs5.rm(lockPath, { force: true });
9303
9953
  }
9304
9954
  async function isLocked(repoPath) {
9305
9955
  return await getLockInfo(repoPath) !== null;
@@ -9466,7 +10116,7 @@ async function getHeadSha(baseDir, options) {
9466
10116
 
9467
10117
  // src/sagas/apply-snapshot-saga.ts
9468
10118
  import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
9469
- import { join as join5 } from "path";
10119
+ import { join as join6 } from "path";
9470
10120
 
9471
10121
  // ../shared/dist/index.js
9472
10122
  var consoleLogger = {
@@ -9597,8 +10247,8 @@ var Saga = class {
9597
10247
 
9598
10248
  // ../git/dist/sagas/tree.js
9599
10249
  import { existsSync as existsSync4 } from "fs";
9600
- import * as fs6 from "fs/promises";
9601
- import * as path7 from "path";
10250
+ import * as fs7 from "fs/promises";
10251
+ import * as path8 from "path";
9602
10252
  import * as tar from "tar";
9603
10253
 
9604
10254
  // ../git/dist/git-saga.js
@@ -9624,14 +10274,14 @@ var CaptureTreeSaga = class extends GitSaga {
9624
10274
  tempIndexPath = null;
9625
10275
  async executeGitOperations(input) {
9626
10276
  const { baseDir, lastTreeHash, archivePath, signal } = input;
9627
- const tmpDir = path7.join(baseDir, ".git", "twig-tmp");
10277
+ const tmpDir = path8.join(baseDir, ".git", "twig-tmp");
9628
10278
  await this.step({
9629
10279
  name: "create_tmp_dir",
9630
- execute: () => fs6.mkdir(tmpDir, { recursive: true }),
10280
+ execute: () => fs7.mkdir(tmpDir, { recursive: true }),
9631
10281
  rollback: async () => {
9632
10282
  }
9633
10283
  });
9634
- this.tempIndexPath = path7.join(tmpDir, `index-${Date.now()}`);
10284
+ this.tempIndexPath = path8.join(tmpDir, `index-${Date.now()}`);
9635
10285
  const tempIndexGit = this.git.env({
9636
10286
  ...process.env,
9637
10287
  GIT_INDEX_FILE: this.tempIndexPath
@@ -9641,7 +10291,7 @@ var CaptureTreeSaga = class extends GitSaga {
9641
10291
  execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
9642
10292
  rollback: async () => {
9643
10293
  if (this.tempIndexPath) {
9644
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10294
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9645
10295
  });
9646
10296
  }
9647
10297
  }
@@ -9650,7 +10300,7 @@ var CaptureTreeSaga = class extends GitSaga {
9650
10300
  const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
9651
10301
  if (lastTreeHash && treeHash === lastTreeHash) {
9652
10302
  this.log.debug("No changes since last capture", { treeHash });
9653
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10303
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9654
10304
  });
9655
10305
  return { snapshot: null, changed: false };
9656
10306
  }
@@ -9662,7 +10312,7 @@ var CaptureTreeSaga = class extends GitSaga {
9662
10312
  }
9663
10313
  });
9664
10314
  const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
9665
- await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
10315
+ await fs7.rm(this.tempIndexPath, { force: true }).catch(() => {
9666
10316
  });
9667
10317
  const snapshot = {
9668
10318
  treeHash,
@@ -9686,15 +10336,15 @@ var CaptureTreeSaga = class extends GitSaga {
9686
10336
  if (filesToArchive.length === 0) {
9687
10337
  return void 0;
9688
10338
  }
9689
- const existingFiles = filesToArchive.filter((f) => existsSync4(path7.join(baseDir, f)));
10339
+ const existingFiles = filesToArchive.filter((f) => existsSync4(path8.join(baseDir, f)));
9690
10340
  if (existingFiles.length === 0) {
9691
10341
  return void 0;
9692
10342
  }
9693
10343
  await this.step({
9694
10344
  name: "create_archive",
9695
10345
  execute: async () => {
9696
- const archiveDir = path7.dirname(archivePath);
9697
- await fs6.mkdir(archiveDir, { recursive: true });
10346
+ const archiveDir = path8.dirname(archivePath);
10347
+ await fs7.mkdir(archiveDir, { recursive: true });
9698
10348
  await tar.create({
9699
10349
  gzip: true,
9700
10350
  file: archivePath,
@@ -9702,7 +10352,7 @@ var CaptureTreeSaga = class extends GitSaga {
9702
10352
  }, existingFiles);
9703
10353
  },
9704
10354
  rollback: async () => {
9705
- await fs6.rm(archivePath, { force: true }).catch(() => {
10355
+ await fs7.rm(archivePath, { force: true }).catch(() => {
9706
10356
  });
9707
10357
  }
9708
10358
  });
@@ -9801,9 +10451,9 @@ var ApplyTreeSaga = class extends GitSaga {
9801
10451
  const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
9802
10452
  await this.readOnlyStep("backup_existing_files", async () => {
9803
10453
  for (const filePath of filesToExtract) {
9804
- const fullPath = path7.join(baseDir, filePath);
10454
+ const fullPath = path8.join(baseDir, filePath);
9805
10455
  try {
9806
- const content = await fs6.readFile(fullPath);
10456
+ const content = await fs7.readFile(fullPath);
9807
10457
  this.fileBackups.set(filePath, content);
9808
10458
  } catch {
9809
10459
  }
@@ -9820,16 +10470,16 @@ var ApplyTreeSaga = class extends GitSaga {
9820
10470
  },
9821
10471
  rollback: async () => {
9822
10472
  for (const filePath of this.extractedFiles) {
9823
- const fullPath = path7.join(baseDir, filePath);
10473
+ const fullPath = path8.join(baseDir, filePath);
9824
10474
  const backup = this.fileBackups.get(filePath);
9825
10475
  if (backup) {
9826
- const dir = path7.dirname(fullPath);
9827
- await fs6.mkdir(dir, { recursive: true }).catch(() => {
10476
+ const dir = path8.dirname(fullPath);
10477
+ await fs7.mkdir(dir, { recursive: true }).catch(() => {
9828
10478
  });
9829
- await fs6.writeFile(fullPath, backup).catch(() => {
10479
+ await fs7.writeFile(fullPath, backup).catch(() => {
9830
10480
  });
9831
10481
  } else {
9832
- await fs6.rm(fullPath, { force: true }).catch(() => {
10482
+ await fs7.rm(fullPath, { force: true }).catch(() => {
9833
10483
  });
9834
10484
  }
9835
10485
  }
@@ -9837,10 +10487,10 @@ var ApplyTreeSaga = class extends GitSaga {
9837
10487
  });
9838
10488
  }
9839
10489
  for (const change of changes.filter((c) => c.status === "D")) {
9840
- const fullPath = path7.join(baseDir, change.path);
10490
+ const fullPath = path8.join(baseDir, change.path);
9841
10491
  const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
9842
10492
  try {
9843
- return await fs6.readFile(fullPath);
10493
+ return await fs7.readFile(fullPath);
9844
10494
  } catch {
9845
10495
  return null;
9846
10496
  }
@@ -9848,15 +10498,15 @@ var ApplyTreeSaga = class extends GitSaga {
9848
10498
  await this.step({
9849
10499
  name: `delete_${change.path}`,
9850
10500
  execute: async () => {
9851
- await fs6.rm(fullPath, { force: true });
10501
+ await fs7.rm(fullPath, { force: true });
9852
10502
  this.log.debug(`Deleted file: ${change.path}`);
9853
10503
  },
9854
10504
  rollback: async () => {
9855
10505
  if (backupContent) {
9856
- const dir = path7.dirname(fullPath);
9857
- await fs6.mkdir(dir, { recursive: true }).catch(() => {
10506
+ const dir = path8.dirname(fullPath);
10507
+ await fs7.mkdir(dir, { recursive: true }).catch(() => {
9858
10508
  });
9859
- await fs6.writeFile(fullPath, backupContent).catch(() => {
10509
+ await fs7.writeFile(fullPath, backupContent).catch(() => {
9860
10510
  });
9861
10511
  }
9862
10512
  }
@@ -9878,7 +10528,7 @@ var ApplySnapshotSaga = class extends Saga {
9878
10528
  archivePath = null;
9879
10529
  async execute(input) {
9880
10530
  const { snapshot, repositoryPath, apiClient, taskId, runId } = input;
9881
- const tmpDir = join5(repositoryPath, ".posthog", "tmp");
10531
+ const tmpDir = join6(repositoryPath, ".posthog", "tmp");
9882
10532
  if (!snapshot.archiveUrl) {
9883
10533
  throw new Error("Cannot apply snapshot: no archive URL");
9884
10534
  }
@@ -9889,7 +10539,7 @@ var ApplySnapshotSaga = class extends Saga {
9889
10539
  rollback: async () => {
9890
10540
  }
9891
10541
  });
9892
- const archivePath = join5(tmpDir, `${snapshot.treeHash}.tar.gz`);
10542
+ const archivePath = join6(tmpDir, `${snapshot.treeHash}.tar.gz`);
9893
10543
  this.archivePath = archivePath;
9894
10544
  await this.step({
9895
10545
  name: "download_archive",
@@ -9938,7 +10588,7 @@ var ApplySnapshotSaga = class extends Saga {
9938
10588
  // src/sagas/capture-tree-saga.ts
9939
10589
  import { existsSync as existsSync5 } from "fs";
9940
10590
  import { readFile as readFile3, rm as rm4 } from "fs/promises";
9941
- import { join as join6 } from "path";
10591
+ import { join as join7 } from "path";
9942
10592
  var CaptureTreeSaga2 = class extends Saga {
9943
10593
  async execute(input) {
9944
10594
  const {
@@ -9949,14 +10599,14 @@ var CaptureTreeSaga2 = class extends Saga {
9949
10599
  taskId,
9950
10600
  runId
9951
10601
  } = input;
9952
- const tmpDir = join6(repositoryPath, ".posthog", "tmp");
9953
- if (existsSync5(join6(repositoryPath, ".gitmodules"))) {
10602
+ const tmpDir = join7(repositoryPath, ".posthog", "tmp");
10603
+ if (existsSync5(join7(repositoryPath, ".gitmodules"))) {
9954
10604
  this.log.warn(
9955
10605
  "Repository has submodules - snapshot may not capture submodule state"
9956
10606
  );
9957
10607
  }
9958
10608
  const shouldArchive = !!apiClient;
9959
- const archivePath = shouldArchive ? join6(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
10609
+ const archivePath = shouldArchive ? join7(tmpDir, `tree-${Date.now()}.tar.gz`) : void 0;
9960
10610
  const gitCaptureSaga = new CaptureTreeSaga(this.log);
9961
10611
  const captureResult = await gitCaptureSaga.run({
9962
10612
  baseDir: repositoryPath,