@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
package/dist/agent.js CHANGED
@@ -262,13 +262,16 @@ function nodeWritableToWebWritable(nodeStream) {
262
262
  }
263
263
 
264
264
  // src/adapters/claude/claude-agent.ts
265
- import * as fs2 from "fs";
266
- import * as os3 from "os";
267
- import * as path3 from "path";
265
+ import { randomUUID } from "crypto";
266
+ import * as fs3 from "fs";
267
+ import * as os4 from "os";
268
+ import * as path4 from "path";
268
269
  import {
269
270
  RequestError as RequestError2
270
271
  } from "@agentclientprotocol/sdk";
271
272
  import {
273
+ getSessionMessages,
274
+ listSessions,
272
275
  query
273
276
  } from "@anthropic-ai/claude-agent-sdk";
274
277
  import { v7 as uuidv7 } from "uuid";
@@ -276,7 +279,7 @@ import { v7 as uuidv7 } from "uuid";
276
279
  // package.json
277
280
  var package_default = {
278
281
  name: "@posthog/agent",
279
- version: "2.1.131",
282
+ version: "2.1.138",
280
283
  repository: "https://github.com/PostHog/twig",
281
284
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
282
285
  exports: {
@@ -316,6 +319,10 @@ var package_default = {
316
319
  types: "./dist/adapters/claude/conversion/tool-use-to-acp.d.ts",
317
320
  import: "./dist/adapters/claude/conversion/tool-use-to-acp.js"
318
321
  },
322
+ "./adapters/claude/session/jsonl-hydration": {
323
+ types: "./dist/adapters/claude/session/jsonl-hydration.d.ts",
324
+ import: "./dist/adapters/claude/session/jsonl-hydration.js"
325
+ },
319
326
  "./server": {
320
327
  types: "./dist/server/agent-server.d.ts",
321
328
  import: "./dist/server/agent-server.js"
@@ -352,7 +359,6 @@ var package_default = {
352
359
  "@twig/git": "workspace:*",
353
360
  "@types/bun": "latest",
354
361
  "@types/tar": "^6.1.13",
355
- minimatch: "^10.0.3",
356
362
  msw: "^2.12.7",
357
363
  tsup: "^8.5.1",
358
364
  tsx: "^4.20.6",
@@ -373,6 +379,7 @@ var package_default = {
373
379
  commander: "^14.0.2",
374
380
  hono: "^4.11.7",
375
381
  jsonwebtoken: "^9.0.2",
382
+ minimatch: "^10.0.3",
376
383
  tar: "^7.5.0",
377
384
  uuid: "13.0.0",
378
385
  "yoga-wasm-web": "^0.3.3",
@@ -406,7 +413,7 @@ function unreachable(value, logger) {
406
413
  try {
407
414
  valueAsString = JSON.stringify(value);
408
415
  } catch {
409
- valueAsString = value;
416
+ valueAsString = String(value);
410
417
  }
411
418
  logger.error(`Unexpected case: ${valueAsString}`);
412
419
  }
@@ -514,19 +521,20 @@ var BaseAcpAgent = class {
514
521
  }
515
522
  async cancel(params) {
516
523
  if (this.sessionId !== params.sessionId) {
517
- throw new Error("Session not found");
524
+ throw new Error("Session ID mismatch");
518
525
  }
519
526
  this.session.cancelled = true;
520
527
  const meta = params._meta;
521
528
  if (meta?.interruptReason) {
522
529
  this.session.interruptReason = meta.interruptReason;
523
530
  }
524
- await this.interruptSession();
531
+ await this.interrupt();
525
532
  }
526
533
  async closeSession() {
527
534
  try {
528
535
  this.session.abortController.abort();
529
536
  await this.cancel({ sessionId: this.sessionId });
537
+ this.session.settingsManager.dispose();
530
538
  this.logger.info("Closed session", { sessionId: this.sessionId });
531
539
  } catch (err) {
532
540
  this.logger.warn("Failed to close session", {
@@ -701,8 +709,8 @@ var ToolContentBuilder = class {
701
709
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
702
710
  return this;
703
711
  }
704
- diff(path5, oldText, newText) {
705
- this.items.push({ type: "diff", path: path5, oldText, newText });
712
+ diff(path6, oldText, newText) {
713
+ this.items.push({ type: "diff", path: path6, oldText, newText });
706
714
  return this;
707
715
  }
708
716
  build() {
@@ -722,7 +730,7 @@ var registerHookCallback = (toolUseID, {
722
730
  onPostToolUseHook
723
731
  };
724
732
  };
725
- var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
733
+ var createPostToolUseHook = ({ onModeChange, logger }) => async (input, toolUseID) => {
726
734
  if (input.hook_event_name === "PostToolUse") {
727
735
  const toolName = input.tool_name;
728
736
  if (onModeChange && toolName === "EnterPlanMode") {
@@ -737,11 +745,54 @@ var createPostToolUseHook = ({ onModeChange }) => async (input, toolUseID) => {
737
745
  input.tool_response
738
746
  );
739
747
  delete toolUseCallbacks[toolUseID];
748
+ } else {
749
+ logger?.error(
750
+ `No onPostToolUseHook found for tool use ID: ${toolUseID}`
751
+ );
752
+ delete toolUseCallbacks[toolUseID];
740
753
  }
741
754
  }
742
755
  }
743
756
  return { continue: true };
744
757
  };
758
+ var createPreToolUseHook = (settingsManager, logger) => async (input, _toolUseID) => {
759
+ if (input.hook_event_name !== "PreToolUse") {
760
+ return { continue: true };
761
+ }
762
+ const toolName = input.tool_name;
763
+ const toolInput = input.tool_input;
764
+ const permissionCheck = settingsManager.checkPermission(
765
+ toolName,
766
+ toolInput
767
+ );
768
+ if (permissionCheck.decision !== "ask") {
769
+ logger.info(
770
+ `[PreToolUseHook] Tool: ${toolName}, Decision: ${permissionCheck.decision}, Rule: ${permissionCheck.rule}`
771
+ );
772
+ }
773
+ switch (permissionCheck.decision) {
774
+ case "allow":
775
+ return {
776
+ continue: true,
777
+ hookSpecificOutput: {
778
+ hookEventName: "PreToolUse",
779
+ permissionDecision: "allow",
780
+ permissionDecisionReason: `Allowed by settings rule: ${permissionCheck.rule}`
781
+ }
782
+ };
783
+ case "deny":
784
+ return {
785
+ continue: true,
786
+ hookSpecificOutput: {
787
+ hookEventName: "PreToolUse",
788
+ permissionDecision: "deny",
789
+ permissionDecisionReason: `Denied by settings rule: ${permissionCheck.rule}`
790
+ }
791
+ };
792
+ default:
793
+ return { continue: true };
794
+ }
795
+ };
745
796
 
746
797
  // src/adapters/claude/mcp/tool-metadata.ts
747
798
  var mcpToolMetadataCache = /* @__PURE__ */ new Map();
@@ -818,85 +869,14 @@ var SYSTEM_REMINDER = `
818
869
  <system-reminder>
819
870
  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.
820
871
  </system-reminder>`;
821
- function replaceAndCalculateLocation(fileContent, edits) {
822
- let currentContent = fileContent;
823
- const randomHex = Array.from(crypto.getRandomValues(new Uint8Array(5))).map((b) => b.toString(16).padStart(2, "0")).join("");
824
- const markerPrefix = `__REPLACE_MARKER_${randomHex}_`;
825
- let markerCounter = 0;
826
- const markers = [];
827
- for (const edit of edits) {
828
- if (edit.oldText === "") {
829
- throw new Error(
830
- `The provided \`old_string\` is empty.
831
-
832
- No edits were applied.`
833
- );
834
- }
835
- if (edit.replaceAll) {
836
- const parts = [];
837
- let lastIndex = 0;
838
- let searchIndex = 0;
839
- while (true) {
840
- const index = currentContent.indexOf(edit.oldText, searchIndex);
841
- if (index === -1) {
842
- if (searchIndex === 0) {
843
- throw new Error(
844
- `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
845
-
846
- No edits were applied.`
847
- );
848
- }
849
- break;
850
- }
851
- parts.push(currentContent.substring(lastIndex, index));
852
- const marker = `${markerPrefix}${markerCounter++}__`;
853
- markers.push(marker);
854
- parts.push(marker + edit.newText);
855
- lastIndex = index + edit.oldText.length;
856
- searchIndex = lastIndex;
857
- }
858
- parts.push(currentContent.substring(lastIndex));
859
- currentContent = parts.join("");
860
- } else {
861
- const index = currentContent.indexOf(edit.oldText);
862
- if (index === -1) {
863
- throw new Error(
864
- `The provided \`old_string\` does not appear in the file: "${edit.oldText}".
865
-
866
- No edits were applied.`
867
- );
868
- } else {
869
- const marker = `${markerPrefix}${markerCounter++}__`;
870
- markers.push(marker);
871
- currentContent = currentContent.substring(0, index) + marker + edit.newText + currentContent.substring(index + edit.oldText.length);
872
- }
873
- }
874
- }
875
- const lineNumbers = [];
876
- for (const marker of markers) {
877
- const index = currentContent.indexOf(marker);
878
- if (index !== -1) {
879
- const lineNumber = Math.max(
880
- 0,
881
- currentContent.substring(0, index).split(/\r\n|\r|\n/).length - 1
882
- );
883
- lineNumbers.push(lineNumber);
884
- }
885
- }
886
- let finalContent = currentContent;
887
- for (const marker of markers) {
888
- finalContent = finalContent.replace(marker, "");
889
- }
890
- const uniqueLineNumbers = [...new Set(lineNumbers)].sort();
891
- return { newContent: finalContent, lineNumbers: uniqueLineNumbers };
892
- }
893
- function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ debug: false, prefix: "[ClaudeTools]" })) {
872
+ function toolInfoFromToolUse(toolUse, options) {
894
873
  const name = toolUse.name;
895
874
  const input = toolUse.input;
896
875
  switch (name) {
897
876
  case "Task":
877
+ case "Agent":
898
878
  return {
899
- title: input?.description ? String(input.description) : "Task",
879
+ title: input?.description ? String(input.description) : name,
900
880
  kind: "think",
901
881
  content: input?.prompt ? toolContent().text(String(input.prompt)).build() : []
902
882
  };
@@ -915,6 +895,13 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
915
895
  locations: input?.notebook_path ? [{ path: String(input.notebook_path) }] : []
916
896
  };
917
897
  case "Bash":
898
+ if (options?.supportsTerminalOutput && options?.toolUseId) {
899
+ return {
900
+ title: input?.description ? String(input.description) : "Execute command",
901
+ kind: "execute",
902
+ content: [{ type: "terminal", terminalId: options.toolUseId }]
903
+ };
904
+ }
918
905
  return {
919
906
  title: input?.description ? String(input.description) : "Execute command",
920
907
  kind: "execute",
@@ -935,11 +922,11 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
935
922
  case "Read": {
936
923
  let limit = "";
937
924
  const inputLimit = input?.limit;
938
- const inputOffset = input?.offset ?? 0;
925
+ const inputOffset = input?.offset ?? 1;
939
926
  if (inputLimit) {
940
- limit = ` (${inputOffset + 1} - ${inputOffset + inputLimit})`;
941
- } else if (inputOffset) {
942
- limit = ` (from line ${inputOffset + 1})`;
927
+ limit = ` (${inputOffset} - ${inputOffset + inputLimit - 1})`;
928
+ } else if (inputOffset > 1) {
929
+ limit = ` (from line ${inputOffset})`;
943
930
  }
944
931
  return {
945
932
  title: `Read ${input?.file_path ? String(input.file_path) : "File"}${limit}`,
@@ -961,39 +948,21 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
961
948
  locations: []
962
949
  };
963
950
  case "Edit": {
964
- const path5 = input?.file_path ? String(input.file_path) : void 0;
965
- let oldText = input?.old_string ? String(input.old_string) : null;
966
- let newText = input?.new_string ? String(input.new_string) : "";
967
- let affectedLines = [];
968
- if (path5 && oldText) {
969
- try {
970
- const oldContent = cachedFileContent[path5] || "";
971
- const newContent = replaceAndCalculateLocation(oldContent, [
972
- {
973
- oldText,
974
- newText,
975
- replaceAll: false
976
- }
977
- ]);
978
- oldText = oldContent;
979
- newText = newContent.newContent;
980
- affectedLines = newContent.lineNumbers;
981
- } catch (e) {
982
- logger.error("Failed to edit file", e);
983
- }
984
- }
951
+ const path6 = input?.file_path ? String(input.file_path) : void 0;
952
+ const oldText = input?.old_string ? String(input.old_string) : null;
953
+ const newText = input?.new_string ? String(input.new_string) : "";
985
954
  return {
986
- title: path5 ? `Edit \`${path5}\`` : "Edit",
955
+ title: path6 ? `Edit \`${path6}\`` : "Edit",
987
956
  kind: "edit",
988
- content: input && path5 ? [
957
+ content: input && path6 ? [
989
958
  {
990
959
  type: "diff",
991
- path: path5,
960
+ path: path6,
992
961
  oldText,
993
962
  newText
994
963
  }
995
964
  ] : [],
996
- locations: path5 ? affectedLines.length > 0 ? affectedLines.map((line) => ({ line, path: path5 })) : [{ path: path5 }] : []
965
+ locations: path6 ? [{ path: path6 }] : []
997
966
  };
998
967
  }
999
968
  case "Write": {
@@ -1047,10 +1016,10 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1047
1016
  }
1048
1017
  if (input?.output_mode) {
1049
1018
  switch (input.output_mode) {
1050
- case "FilesWithMatches":
1019
+ case "files_with_matches":
1051
1020
  label += " -l";
1052
1021
  break;
1053
- case "Count":
1022
+ case "count":
1054
1023
  label += " -c";
1055
1024
  break;
1056
1025
  default:
@@ -1069,7 +1038,9 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1069
1038
  if (input?.multiline) {
1070
1039
  label += " -P";
1071
1040
  }
1072
- label += ` "${input?.pattern ? String(input.pattern) : ""}"`;
1041
+ if (input?.pattern) {
1042
+ label += ` "${String(input.pattern)}"`;
1043
+ }
1073
1044
  if (input?.path) {
1074
1045
  label += ` ${String(input.path)}`;
1075
1046
  }
@@ -1163,7 +1134,49 @@ function mcpToolInfo(name, _input) {
1163
1134
  content: []
1164
1135
  };
1165
1136
  }
1166
- function toolUpdateFromToolResult(toolResult, toolUse) {
1137
+ function toolUpdateFromEditToolResponse(toolResponse) {
1138
+ if (!toolResponse || typeof toolResponse !== "object") return null;
1139
+ const response = toolResponse;
1140
+ const patches = response.structuredPatch;
1141
+ if (!Array.isArray(patches) || patches.length === 0) return null;
1142
+ const content = [];
1143
+ const locations = [];
1144
+ for (const patch of patches) {
1145
+ if (!patch.hunks || patch.hunks.length === 0) continue;
1146
+ const filePath = patch.newFileName || patch.oldFileName;
1147
+ const oldLines = [];
1148
+ const newLines = [];
1149
+ for (const hunk of patch.hunks) {
1150
+ for (const line of hunk.lines) {
1151
+ if (line.startsWith("-")) {
1152
+ oldLines.push(line.slice(1));
1153
+ } else if (line.startsWith("+")) {
1154
+ newLines.push(line.slice(1));
1155
+ } else if (line.startsWith(" ")) {
1156
+ oldLines.push(line.slice(1));
1157
+ newLines.push(line.slice(1));
1158
+ }
1159
+ }
1160
+ }
1161
+ content.push({
1162
+ type: "diff",
1163
+ path: filePath,
1164
+ oldText: oldLines.join("\n"),
1165
+ newText: newLines.join("\n")
1166
+ });
1167
+ const firstHunk = patch.hunks[0];
1168
+ locations.push({
1169
+ path: filePath,
1170
+ line: firstHunk.newStart
1171
+ });
1172
+ }
1173
+ if (content.length === 0) return null;
1174
+ return { content, locations };
1175
+ }
1176
+ function toolUpdateFromToolResult(toolResult, toolUse, options) {
1177
+ if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
1178
+ return toAcpContentUpdate(toolResult.content, true);
1179
+ }
1167
1180
  switch (toolUse?.name) {
1168
1181
  case "Read":
1169
1182
  if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
@@ -1180,6 +1193,16 @@ function toolUpdateFromToolResult(toolResult, toolUse) {
1180
1193
  )
1181
1194
  };
1182
1195
  }
1196
+ if (itemObj.type === "image" && itemObj.source) {
1197
+ return {
1198
+ type: "content",
1199
+ content: {
1200
+ type: "image",
1201
+ data: itemObj.source.data ?? "",
1202
+ mimeType: itemObj.source.media_type ?? "image/png"
1203
+ }
1204
+ };
1205
+ }
1183
1206
  return {
1184
1207
  type: "content",
1185
1208
  content: item
@@ -1195,18 +1218,51 @@ function toolUpdateFromToolResult(toolResult, toolUse) {
1195
1218
  }
1196
1219
  return {};
1197
1220
  case "Bash": {
1198
- return toAcpContentUpdate(
1199
- toolResult.content,
1200
- "is_error" in toolResult ? toolResult.is_error : false
1201
- );
1202
- }
1203
- case "Edit":
1204
- case "Write": {
1205
- if ("is_error" in toolResult && toolResult.is_error && toolResult.content && toolResult.content.length > 0) {
1206
- return toAcpContentUpdate(toolResult.content, true);
1221
+ const result = toolResult.content;
1222
+ const terminalId = "tool_use_id" in toolResult ? String(toolResult.tool_use_id) : "";
1223
+ const isError = "is_error" in toolResult && toolResult.is_error;
1224
+ let output = "";
1225
+ let exitCode = isError ? 1 : 0;
1226
+ if (result && typeof result === "object" && "type" in result && result.type === "bash_code_execution_result") {
1227
+ const bashResult = result;
1228
+ output = [bashResult.stdout, bashResult.stderr].filter(Boolean).join("\n");
1229
+ exitCode = bashResult.return_code;
1230
+ } else if (typeof result === "string") {
1231
+ output = result;
1232
+ } else if (Array.isArray(result) && result.length > 0 && "text" in result[0] && typeof result[0].text === "string") {
1233
+ output = result.map((c) => c.text ?? "").join("\n");
1234
+ }
1235
+ if (options?.supportsTerminalOutput) {
1236
+ return {
1237
+ content: [{ type: "terminal", terminalId }],
1238
+ _meta: {
1239
+ terminal_info: {
1240
+ terminal_id: terminalId
1241
+ },
1242
+ terminal_output: {
1243
+ terminal_id: terminalId,
1244
+ data: output
1245
+ },
1246
+ terminal_exit: {
1247
+ terminal_id: terminalId,
1248
+ exit_code: exitCode,
1249
+ signal: null
1250
+ }
1251
+ }
1252
+ };
1253
+ }
1254
+ if (output.trim()) {
1255
+ return {
1256
+ content: toolContent().text(`\`\`\`console
1257
+ ${output.trimEnd()}
1258
+ \`\`\``).build()
1259
+ };
1207
1260
  }
1208
1261
  return {};
1209
1262
  }
1263
+ case "Edit":
1264
+ case "Write":
1265
+ return {};
1210
1266
  case "ExitPlanMode": {
1211
1267
  return { title: "Exited Plan Mode" };
1212
1268
  }
@@ -1366,6 +1422,7 @@ function handleThinkingChunk(chunk, parentToolCallId) {
1366
1422
  return update;
1367
1423
  }
1368
1424
  function handleToolUseChunk(chunk, ctx) {
1425
+ const alreadyCached = chunk.id in ctx.toolUseCache;
1369
1426
  ctx.toolUseCache[chunk.id] = chunk;
1370
1427
  if (chunk.name === "TodoWrite") {
1371
1428
  const input = chunk.input;
@@ -1377,37 +1434,60 @@ function handleToolUseChunk(chunk, ctx) {
1377
1434
  }
1378
1435
  return null;
1379
1436
  }
1380
- registerHookCallback(chunk.id, {
1381
- onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
1382
- const toolUse = ctx.toolUseCache[toolUseId];
1383
- if (toolUse) {
1384
- await ctx.client.sessionUpdate({
1385
- sessionId: ctx.sessionId,
1386
- update: {
1387
- _meta: toolMeta(toolUse.name, toolResponse, ctx.parentToolCallId),
1388
- toolCallId: toolUseId,
1389
- sessionUpdate: "tool_call_update"
1390
- }
1391
- });
1392
- } else {
1393
- ctx.logger.error(
1394
- `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
1395
- );
1437
+ if (!alreadyCached && ctx.registerHooks !== false) {
1438
+ registerHookCallback(chunk.id, {
1439
+ onPostToolUseHook: async (toolUseId, _toolInput, toolResponse) => {
1440
+ const toolUse = ctx.toolUseCache[toolUseId];
1441
+ if (toolUse) {
1442
+ const editUpdate = toolUse.name === "Edit" ? toolUpdateFromEditToolResponse(toolResponse) : null;
1443
+ await ctx.client.sessionUpdate({
1444
+ sessionId: ctx.sessionId,
1445
+ update: {
1446
+ _meta: toolMeta(toolUse.name, toolResponse, ctx.parentToolCallId),
1447
+ toolCallId: toolUseId,
1448
+ sessionUpdate: "tool_call_update",
1449
+ ...editUpdate ? editUpdate : {}
1450
+ }
1451
+ });
1452
+ } else {
1453
+ ctx.logger.error(
1454
+ `Got a tool response for tool use that wasn't tracked: ${toolUseId}`
1455
+ );
1456
+ }
1396
1457
  }
1397
- }
1398
- });
1458
+ });
1459
+ }
1399
1460
  let rawInput;
1400
1461
  try {
1401
1462
  rawInput = JSON.parse(JSON.stringify(chunk.input));
1402
1463
  } catch {
1403
1464
  }
1465
+ const toolInfo = toolInfoFromToolUse(chunk, {
1466
+ supportsTerminalOutput: ctx.supportsTerminalOutput,
1467
+ toolUseId: chunk.id
1468
+ });
1469
+ const meta = {
1470
+ ...toolMeta(chunk.name, void 0, ctx.parentToolCallId)
1471
+ };
1472
+ if (chunk.name === "Bash" && ctx.supportsTerminalOutput && !alreadyCached) {
1473
+ meta.terminal_info = { terminal_id: chunk.id };
1474
+ }
1475
+ if (alreadyCached) {
1476
+ return {
1477
+ _meta: meta,
1478
+ toolCallId: chunk.id,
1479
+ sessionUpdate: "tool_call_update",
1480
+ rawInput,
1481
+ ...toolInfo
1482
+ };
1483
+ }
1404
1484
  return {
1405
- _meta: toolMeta(chunk.name, void 0, ctx.parentToolCallId),
1485
+ _meta: meta,
1406
1486
  toolCallId: chunk.id,
1407
1487
  sessionUpdate: "tool_call",
1408
1488
  rawInput,
1409
1489
  status: "pending",
1410
- ...toolInfoFromToolUse(chunk, ctx.fileContentCache, ctx.logger)
1490
+ ...toolInfo
1411
1491
  };
1412
1492
  }
1413
1493
  function handleToolResultChunk(chunk, ctx) {
@@ -1416,36 +1496,71 @@ function handleToolResultChunk(chunk, ctx) {
1416
1496
  ctx.logger.error(
1417
1497
  `Got a tool result for tool use that wasn't tracked: ${chunk.tool_use_id}`
1418
1498
  );
1419
- return null;
1499
+ return [];
1420
1500
  }
1421
1501
  if (toolUse.name === "TodoWrite") {
1422
- return null;
1502
+ return [];
1423
1503
  }
1424
- return {
1425
- _meta: toolMeta(toolUse.name, void 0, ctx.parentToolCallId),
1504
+ const { _meta: resultMeta, ...toolUpdate } = toolUpdateFromToolResult(
1505
+ chunk,
1506
+ toolUse,
1507
+ {
1508
+ supportsTerminalOutput: ctx.supportsTerminalOutput,
1509
+ toolUseId: chunk.tool_use_id
1510
+ }
1511
+ );
1512
+ const updates = [];
1513
+ if (resultMeta?.terminal_output) {
1514
+ const terminalOutputMeta = {
1515
+ terminal_output: resultMeta.terminal_output
1516
+ };
1517
+ if (ctx.parentToolCallId) {
1518
+ terminalOutputMeta.claudeCode = {
1519
+ parentToolCallId: ctx.parentToolCallId
1520
+ };
1521
+ }
1522
+ updates.push({
1523
+ _meta: terminalOutputMeta,
1524
+ toolCallId: chunk.tool_use_id,
1525
+ sessionUpdate: "tool_call_update"
1526
+ });
1527
+ }
1528
+ const meta = {
1529
+ ...toolMeta(toolUse.name, void 0, ctx.parentToolCallId),
1530
+ ...resultMeta?.terminal_exit ? { terminal_exit: resultMeta.terminal_exit } : {}
1531
+ };
1532
+ updates.push({
1533
+ _meta: meta,
1426
1534
  toolCallId: chunk.tool_use_id,
1427
1535
  sessionUpdate: "tool_call_update",
1428
1536
  status: chunk.is_error ? "failed" : "completed",
1429
- ...toolUpdateFromToolResult(
1430
- chunk,
1431
- toolUse
1432
- )
1433
- };
1537
+ rawOutput: chunk.content,
1538
+ ...toolUpdate
1539
+ });
1540
+ return updates;
1434
1541
  }
1435
1542
  function processContentChunk(chunk, role, ctx) {
1436
1543
  switch (chunk.type) {
1437
1544
  case "text":
1438
- case "text_delta":
1439
- return handleTextChunk(chunk, role, ctx.parentToolCallId);
1440
- case "image":
1441
- return handleImageChunk(chunk, role);
1545
+ case "text_delta": {
1546
+ const update = handleTextChunk(chunk, role, ctx.parentToolCallId);
1547
+ return update ? [update] : [];
1548
+ }
1549
+ case "image": {
1550
+ const update = handleImageChunk(chunk, role);
1551
+ return update ? [update] : [];
1552
+ }
1442
1553
  case "thinking":
1443
- case "thinking_delta":
1444
- return handleThinkingChunk(chunk, ctx.parentToolCallId);
1554
+ case "thinking_delta": {
1555
+ const update = handleThinkingChunk(chunk, ctx.parentToolCallId);
1556
+ return update ? [update] : [];
1557
+ }
1445
1558
  case "tool_use":
1446
1559
  case "server_tool_use":
1447
- case "mcp_tool_use":
1448
- return handleToolUseChunk(chunk, ctx);
1560
+ case "mcp_tool_use": {
1561
+ const update = handleToolUseChunk(chunk, ctx);
1562
+ return update ? [update] : [];
1563
+ }
1449
1564
  case "tool_result":
1450
1565
  case "tool_search_tool_result":
1451
1566
  case "web_fetch_tool_result":
@@ -1467,13 +1582,13 @@ function processContentChunk(chunk, role, ctx) {
1467
1582
  case "container_upload":
1468
1583
  case "compaction":
1469
1584
  case "compaction_delta":
1470
- return null;
1585
+ return [];
1471
1586
  default:
1472
1587
  unreachable(chunk, ctx.logger);
1473
- return null;
1588
+ return [];
1474
1589
  }
1475
1590
  }
1476
- function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId) {
1591
+ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput) {
1477
1592
  if (typeof content === "string") {
1478
1593
  const update = {
1479
1594
  sessionUpdate: messageUpdateType(role),
@@ -1494,18 +1609,19 @@ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentC
1494
1609
  fileContentCache,
1495
1610
  client,
1496
1611
  logger,
1497
- parentToolCallId
1612
+ parentToolCallId,
1613
+ registerHooks,
1614
+ supportsTerminalOutput
1498
1615
  };
1499
1616
  const output = [];
1500
1617
  for (const chunk of content) {
1501
- const update = processContentChunk(chunk, role, ctx);
1502
- if (update) {
1618
+ for (const update of processContentChunk(chunk, role, ctx)) {
1503
1619
  output.push({ sessionId, update });
1504
1620
  }
1505
1621
  }
1506
1622
  return output;
1507
1623
  }
1508
- function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId) {
1624
+ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput) {
1509
1625
  const event = message.event;
1510
1626
  switch (event.type) {
1511
1627
  case "content_block_start":
@@ -1517,7 +1633,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
1517
1633
  fileContentCache,
1518
1634
  client,
1519
1635
  logger,
1520
- parentToolCallId
1636
+ parentToolCallId,
1637
+ registerHooks,
1638
+ supportsTerminalOutput
1521
1639
  );
1522
1640
  case "content_block_delta":
1523
1641
  return toAcpNotifications(
@@ -1528,7 +1646,9 @@ function streamEventToAcpNotifications(message, sessionId, toolUseCache, fileCon
1528
1646
  fileContentCache,
1529
1647
  client,
1530
1648
  logger,
1531
- parentToolCallId
1649
+ parentToolCallId,
1650
+ registerHooks,
1651
+ supportsTerminalOutput
1532
1652
  );
1533
1653
  case "message_start":
1534
1654
  case "message_delta":
@@ -1587,29 +1707,25 @@ async function handleSystemMessage(message, context) {
1587
1707
  break;
1588
1708
  }
1589
1709
  }
1590
- function handleResultMessage(message, context) {
1591
- const { session } = context;
1592
- if (session.cancelled) {
1593
- return {
1594
- shouldStop: true,
1595
- stopReason: "cancelled"
1596
- };
1597
- }
1710
+ function handleResultMessage(message) {
1711
+ const usage = extractUsageFromResult(message);
1598
1712
  switch (message.subtype) {
1599
1713
  case "success": {
1600
1714
  if (message.result.includes("Please run /login")) {
1601
1715
  return {
1602
1716
  shouldStop: true,
1603
- error: RequestError.authRequired()
1717
+ error: RequestError.authRequired(),
1718
+ usage
1604
1719
  };
1605
1720
  }
1606
1721
  if (message.is_error) {
1607
1722
  return {
1608
1723
  shouldStop: true,
1609
- error: RequestError.internalError(void 0, message.result)
1724
+ error: RequestError.internalError(void 0, message.result),
1725
+ usage
1610
1726
  };
1611
1727
  }
1612
- return { shouldStop: true, stopReason: "end_turn" };
1728
+ return { shouldStop: true, stopReason: "end_turn", usage };
1613
1729
  }
1614
1730
  case "error_during_execution":
1615
1731
  if (message.is_error) {
@@ -1618,10 +1734,11 @@ function handleResultMessage(message, context) {
1618
1734
  error: RequestError.internalError(
1619
1735
  void 0,
1620
1736
  message.errors.join(", ") || message.subtype
1621
- )
1737
+ ),
1738
+ usage
1622
1739
  };
1623
1740
  }
1624
- return { shouldStop: true, stopReason: "end_turn" };
1741
+ return { shouldStop: true, stopReason: "end_turn", usage };
1625
1742
  case "error_max_budget_usd":
1626
1743
  case "error_max_turns":
1627
1744
  case "error_max_structured_output_retries":
@@ -1631,13 +1748,37 @@ function handleResultMessage(message, context) {
1631
1748
  error: RequestError.internalError(
1632
1749
  void 0,
1633
1750
  message.errors.join(", ") || message.subtype
1634
- )
1751
+ ),
1752
+ usage
1635
1753
  };
1636
1754
  }
1637
- return { shouldStop: true, stopReason: "max_turn_requests" };
1755
+ return { shouldStop: true, stopReason: "max_turn_requests", usage };
1638
1756
  default:
1639
- return { shouldStop: false };
1757
+ return { shouldStop: false, usage };
1758
+ }
1759
+ }
1760
+ function extractUsageFromResult(message) {
1761
+ const msg = message;
1762
+ const msgUsage = msg.usage;
1763
+ if (!msgUsage) return void 0;
1764
+ const modelUsage = msg.modelUsage;
1765
+ let contextWindowSize;
1766
+ if (modelUsage) {
1767
+ const contextWindows = Object.values(modelUsage).map(
1768
+ (m) => m.contextWindow
1769
+ );
1770
+ if (contextWindows.length > 0) {
1771
+ contextWindowSize = Math.min(...contextWindows);
1772
+ }
1640
1773
  }
1774
+ return {
1775
+ inputTokens: msgUsage.input_tokens ?? 0,
1776
+ outputTokens: msgUsage.output_tokens ?? 0,
1777
+ cachedReadTokens: msgUsage.cache_read_input_tokens ?? 0,
1778
+ cachedWriteTokens: msgUsage.cache_creation_input_tokens ?? 0,
1779
+ costUsd: typeof msg.total_cost_usd === "number" ? msg.total_cost_usd : void 0,
1780
+ contextWindowSize
1781
+ };
1641
1782
  }
1642
1783
  async function handleStreamEvent(message, context) {
1643
1784
  const { sessionId, client, toolUseCache, fileContentCache, logger } = context;
@@ -1649,7 +1790,9 @@ async function handleStreamEvent(message, context) {
1649
1790
  fileContentCache,
1650
1791
  client,
1651
1792
  logger,
1652
- parentToolCallId
1793
+ parentToolCallId,
1794
+ context.registerHooks,
1795
+ context.supportsTerminalOutput
1653
1796
  )) {
1654
1797
  await client.sessionUpdate(notification);
1655
1798
  context.session.notificationHistory.push(notification);
@@ -1661,14 +1804,15 @@ function hasLocalCommandStdout(content) {
1661
1804
  function hasLocalCommandStderr(content) {
1662
1805
  return typeof content === "string" && content.includes("<local-command-stderr>");
1663
1806
  }
1664
- function isSimpleUserMessage(message) {
1665
- 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");
1666
- }
1667
1807
  function isLoginRequiredMessage(message) {
1668
1808
  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;
1669
1809
  }
1810
+ function isPlainTextUserMessage(message) {
1811
+ const content = message.message.content;
1812
+ return message.type === "user" && (typeof content === "string" || Array.isArray(content) && content.length === 1 && content[0].type === "text");
1813
+ }
1670
1814
  function shouldSkipUserAssistantMessage(message) {
1671
- return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isSimpleUserMessage(message) || isLoginRequiredMessage(message);
1815
+ return hasLocalCommandStdout(message.message.content) || hasLocalCommandStderr(message.message.content) || isLoginRequiredMessage(message);
1672
1816
  }
1673
1817
  function logSpecialMessages(message, logger) {
1674
1818
  const content = message.message.content;
@@ -1689,18 +1833,33 @@ function filterMessageContent(content) {
1689
1833
  }
1690
1834
  async function handleUserAssistantMessage(message, context) {
1691
1835
  const { session, sessionId, client, toolUseCache, fileContentCache, logger } = context;
1692
- if (session.cancelled) {
1693
- return {};
1694
- }
1695
1836
  if (shouldSkipUserAssistantMessage(message)) {
1837
+ const content2 = message.message.content;
1838
+ if (typeof content2 === "string" && hasLocalCommandStdout(content2) && content2.includes("Context Usage")) {
1839
+ const stripped = content2.replace("<local-command-stdout>", "").replace("</local-command-stdout>", "");
1840
+ for (const notification of toAcpNotifications(
1841
+ stripped,
1842
+ "assistant",
1843
+ sessionId,
1844
+ toolUseCache,
1845
+ fileContentCache,
1846
+ client,
1847
+ logger
1848
+ )) {
1849
+ await client.sessionUpdate(notification);
1850
+ }
1851
+ }
1696
1852
  logSpecialMessages(message, logger);
1697
1853
  if (isLoginRequiredMessage(message)) {
1698
1854
  return { shouldStop: true, error: RequestError.authRequired() };
1699
1855
  }
1700
1856
  return {};
1701
1857
  }
1858
+ if (isPlainTextUserMessage(message)) {
1859
+ return {};
1860
+ }
1702
1861
  const content = message.message.content;
1703
- const contentToProcess = filterMessageContent(content);
1862
+ const contentToProcess = message.type === "assistant" ? filterMessageContent(content) : content;
1704
1863
  const parentToolCallId = "parent_tool_use_id" in message ? message.parent_tool_use_id ?? void 0 : void 0;
1705
1864
  for (const notification of toAcpNotifications(
1706
1865
  contentToProcess,
@@ -1710,7 +1869,9 @@ async function handleUserAssistantMessage(message, context) {
1710
1869
  fileContentCache,
1711
1870
  client,
1712
1871
  logger,
1713
- parentToolCallId
1872
+ parentToolCallId,
1873
+ context.registerHooks,
1874
+ context.supportsTerminalOutput
1714
1875
  )) {
1715
1876
  await client.sessionUpdate(notification);
1716
1877
  session.notificationHistory.push(notification);
@@ -1796,36 +1957,45 @@ function normalizeAskUserQuestionInput(input) {
1796
1957
  }
1797
1958
 
1798
1959
  // src/execution-mode.ts
1799
- var MODES = [
1960
+ var ALLOW_BYPASS = !IS_ROOT;
1961
+ var availableModes = [
1800
1962
  {
1801
1963
  id: "default",
1802
- name: "Always Ask",
1803
- description: "Prompts for permission on first use of each tool"
1964
+ name: "Default",
1965
+ description: "Standard behavior, prompts for dangerous operations"
1804
1966
  },
1805
1967
  {
1806
1968
  id: "acceptEdits",
1807
1969
  name: "Accept Edits",
1808
- description: "Automatically accepts file edit permissions for the session"
1970
+ description: "Auto-accept file edit operations"
1809
1971
  },
1810
1972
  {
1811
1973
  id: "plan",
1812
1974
  name: "Plan Mode",
1813
- description: "Claude can analyze but not modify files or execute commands"
1814
- },
1815
- {
1816
- id: "bypassPermissions",
1817
- name: "Bypass Permissions",
1818
- description: "Skips all permission prompts"
1975
+ description: "Planning mode, no actual tool execution"
1819
1976
  }
1977
+ // {
1978
+ // id: "dontAsk",
1979
+ // name: "Don't Ask",
1980
+ // description: "Don't prompt for permissions, deny if not pre-approved",
1981
+ // },
1820
1982
  ];
1983
+ if (ALLOW_BYPASS) {
1984
+ availableModes.push({
1985
+ id: "bypassPermissions",
1986
+ name: "Bypass Permissions",
1987
+ description: "Bypass all permission checks"
1988
+ });
1989
+ }
1821
1990
  var TWIG_EXECUTION_MODES = [
1822
1991
  "default",
1823
1992
  "acceptEdits",
1824
1993
  "plan",
1994
+ // "dontAsk",
1825
1995
  "bypassPermissions"
1826
1996
  ];
1827
1997
  function getAvailableModes() {
1828
- return IS_ROOT ? MODES.filter((m) => m.id !== "bypassPermissions") : MODES;
1998
+ return IS_ROOT ? availableModes.filter((m) => m.id !== "bypassPermissions") : availableModes;
1829
1999
  }
1830
2000
 
1831
2001
  // src/adapters/claude/tools.ts
@@ -1853,6 +2023,7 @@ var AUTO_ALLOWED_TOOLS = {
1853
2023
  default: new Set(BASE_ALLOWED_TOOLS),
1854
2024
  acceptEdits: /* @__PURE__ */ new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),
1855
2025
  plan: new Set(BASE_ALLOWED_TOOLS)
2026
+ // dontAsk: new Set(BASE_ALLOWED_TOOLS),
1856
2027
  };
1857
2028
  function isToolAllowedForMode(toolName, mode) {
1858
2029
  if (mode === "bypassPermissions") {
@@ -2000,12 +2171,11 @@ async function validatePlanContent(planText, context) {
2000
2171
  return { valid: true };
2001
2172
  }
2002
2173
  async function requestPlanApproval(context, updatedInput) {
2003
- const { client, sessionId, toolUseID, fileContentCache } = context;
2004
- const toolInfo = toolInfoFromToolUse(
2005
- { name: context.toolName, input: updatedInput },
2006
- fileContentCache,
2007
- context.logger
2008
- );
2174
+ const { client, sessionId, toolUseID } = context;
2175
+ const toolInfo = toolInfoFromToolUse({
2176
+ name: context.toolName,
2177
+ input: updatedInput
2178
+ });
2009
2179
  return await client.requestPermission({
2010
2180
  options: buildExitPlanModePermissionOptions(),
2011
2181
  sessionId,
@@ -2024,7 +2194,14 @@ async function applyPlanApproval(response, context, updatedInput) {
2024
2194
  if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "default" || response.outcome.optionId === "acceptEdits")) {
2025
2195
  session.permissionMode = response.outcome.optionId;
2026
2196
  await session.query.setPermissionMode(response.outcome.optionId);
2027
- await context.emitConfigOptionsUpdate();
2197
+ await context.client.sessionUpdate({
2198
+ sessionId: context.sessionId,
2199
+ update: {
2200
+ sessionUpdate: "current_mode_update",
2201
+ currentModeId: response.outcome.optionId
2202
+ }
2203
+ });
2204
+ await context.updateConfigOption("mode", response.outcome.optionId);
2028
2205
  return {
2029
2206
  behavior: "allow",
2030
2207
  updatedInput,
@@ -2045,7 +2222,7 @@ async function handleEnterPlanModeTool(context) {
2045
2222
  const { session, toolInput } = context;
2046
2223
  session.permissionMode = "plan";
2047
2224
  await session.query.setPermissionMode("plan");
2048
- await context.emitConfigOptionsUpdate();
2225
+ await context.updateConfigOption("mode", "plan");
2049
2226
  return {
2050
2227
  behavior: "allow",
2051
2228
  updatedInput: toolInput
@@ -2063,6 +2240,9 @@ async function handleExitPlanModeTool(context) {
2063
2240
  return validationResult.error;
2064
2241
  }
2065
2242
  const response = await requestPlanApproval(context, updatedInput);
2243
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
2244
+ throw new Error("Tool use aborted");
2245
+ }
2066
2246
  return await applyPlanApproval(response, context, updatedInput);
2067
2247
  }
2068
2248
  function buildQuestionOptions(question) {
@@ -2086,14 +2266,13 @@ async function handleAskUserQuestionTool(context) {
2086
2266
  interrupt: true
2087
2267
  };
2088
2268
  }
2089
- const { client, sessionId, toolUseID, toolInput, fileContentCache } = context;
2269
+ const { client, sessionId, toolUseID, toolInput } = context;
2090
2270
  const firstQuestion = questions[0];
2091
2271
  const options = buildQuestionOptions(firstQuestion);
2092
- const toolInfo = toolInfoFromToolUse(
2093
- { name: context.toolName, input: toolInput },
2094
- fileContentCache,
2095
- context.logger
2096
- );
2272
+ const toolInfo = toolInfoFromToolUse({
2273
+ name: context.toolName,
2274
+ input: toolInput
2275
+ });
2097
2276
  const response = await client.requestPermission({
2098
2277
  options,
2099
2278
  sessionId,
@@ -2108,6 +2287,9 @@ async function handleAskUserQuestionTool(context) {
2108
2287
  }
2109
2288
  }
2110
2289
  });
2290
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
2291
+ throw new Error("Tool use aborted");
2292
+ }
2111
2293
  if (response.outcome?.outcome !== "selected") {
2112
2294
  const customMessage = response._meta?.message;
2113
2295
  return {
@@ -2140,14 +2322,9 @@ async function handleDefaultPermissionFlow(context) {
2140
2322
  toolUseID,
2141
2323
  client,
2142
2324
  sessionId,
2143
- fileContentCache,
2144
2325
  suggestions
2145
2326
  } = context;
2146
- const toolInfo = toolInfoFromToolUse(
2147
- { name: toolName, input: toolInput },
2148
- fileContentCache,
2149
- context.logger
2150
- );
2327
+ const toolInfo = toolInfoFromToolUse({ name: toolName, input: toolInput });
2151
2328
  const options = buildPermissionOptions(
2152
2329
  toolName,
2153
2330
  toolInput,
@@ -2166,6 +2343,9 @@ async function handleDefaultPermissionFlow(context) {
2166
2343
  rawInput: toolInput
2167
2344
  }
2168
2345
  });
2346
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
2347
+ throw new Error("Tool use aborted");
2348
+ }
2169
2349
  if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "allow" || response.outcome.optionId === "allow_always")) {
2170
2350
  if (response.outcome.optionId === "allow_always") {
2171
2351
  return {
@@ -2242,16 +2422,18 @@ async function canUseTool(context) {
2242
2422
  var UNSUPPORTED_COMMANDS = [
2243
2423
  "context",
2244
2424
  "cost",
2425
+ "keybindings-help",
2245
2426
  "login",
2246
2427
  "logout",
2247
2428
  "output-style:new",
2248
2429
  "release-notes",
2249
2430
  "todos"
2250
2431
  ];
2251
- async function getAvailableSlashCommands(q) {
2252
- const commands = await q.supportedCommands();
2432
+ function getAvailableSlashCommands(commands) {
2253
2433
  return commands.map((command) => {
2254
- const input = command.argumentHint ? { hint: command.argumentHint } : null;
2434
+ const input = command.argumentHint != null ? {
2435
+ hint: Array.isArray(command.argumentHint) ? command.argumentHint.join(" ") : command.argumentHint
2436
+ } : null;
2255
2437
  let name = command.name;
2256
2438
  if (command.name.endsWith(" (MCP)")) {
2257
2439
  name = `mcp:${name.replace(" (MCP)", "")}`;
@@ -2349,13 +2531,19 @@ function buildEnvironment() {
2349
2531
  ENABLE_TOOL_SEARCH: "auto:0"
2350
2532
  };
2351
2533
  }
2352
- function buildHooks(userHooks, onModeChange) {
2534
+ function buildHooks(userHooks, onModeChange, settingsManager, logger) {
2353
2535
  return {
2354
2536
  ...userHooks,
2355
2537
  PostToolUse: [
2356
2538
  ...userHooks?.PostToolUse || [],
2357
2539
  {
2358
- hooks: [createPostToolUseHook({ onModeChange })]
2540
+ hooks: [createPostToolUseHook({ onModeChange, logger })]
2541
+ }
2542
+ ],
2543
+ PreToolUse: [
2544
+ ...userHooks?.PreToolUse || [],
2545
+ {
2546
+ hooks: [createPreToolUseHook(settingsManager, logger)]
2359
2547
  }
2360
2548
  ]
2361
2549
  };
@@ -2449,12 +2637,22 @@ function buildSessionOptions(params) {
2449
2637
  permissionMode: params.permissionMode,
2450
2638
  canUseTool: params.canUseTool,
2451
2639
  executable: "node",
2640
+ tools: { type: "preset", preset: "claude_code" },
2641
+ extraArgs: {
2642
+ ...params.userProvidedOptions?.extraArgs,
2643
+ "replay-user-messages": ""
2644
+ },
2452
2645
  mcpServers: buildMcpServers(
2453
2646
  params.userProvidedOptions?.mcpServers,
2454
2647
  params.mcpServers
2455
2648
  ),
2456
2649
  env: buildEnvironment(),
2457
- hooks: buildHooks(params.userProvidedOptions?.hooks, params.onModeChange),
2650
+ hooks: buildHooks(
2651
+ params.userProvidedOptions?.hooks,
2652
+ params.onModeChange,
2653
+ params.settingsManager,
2654
+ params.logger
2655
+ ),
2458
2656
  abortController: getAbortController(
2459
2657
  params.userProvidedOptions?.abortController
2460
2658
  ),
@@ -2471,13 +2669,36 @@ function buildSessionOptions(params) {
2471
2669
  }
2472
2670
  if (params.isResume) {
2473
2671
  options.resume = params.sessionId;
2474
- options.forkSession = false;
2672
+ options.forkSession = params.forkSession ?? false;
2475
2673
  } else {
2476
2674
  options.sessionId = params.sessionId;
2675
+ options.model = DEFAULT_MODEL;
2477
2676
  }
2478
2677
  if (params.additionalDirectories) {
2479
2678
  options.additionalDirectories = params.additionalDirectories;
2480
2679
  }
2680
+ if (params.disableBuiltInTools) {
2681
+ const builtInTools = [
2682
+ "Read",
2683
+ "Write",
2684
+ "Edit",
2685
+ "Bash",
2686
+ "Glob",
2687
+ "Grep",
2688
+ "Task",
2689
+ "TodoWrite",
2690
+ "ExitPlanMode",
2691
+ "WebSearch",
2692
+ "WebFetch",
2693
+ "SlashCommand",
2694
+ "Skill",
2695
+ "NotebookEdit"
2696
+ ];
2697
+ options.disallowedTools = [
2698
+ ...options.disallowedTools ?? [],
2699
+ ...builtInTools
2700
+ ];
2701
+ }
2481
2702
  clearStatsigCache();
2482
2703
  return options;
2483
2704
  }
@@ -2490,154 +2711,698 @@ function clearStatsigCache() {
2490
2711
  });
2491
2712
  }
2492
2713
 
2493
- // src/adapters/claude/claude-agent.ts
2494
- var SESSION_VALIDATION_TIMEOUT_MS = 1e4;
2495
- var ClaudeAcpAgent = class extends BaseAcpAgent {
2496
- adapterName = "claude";
2497
- toolUseCache;
2498
- backgroundTerminals = {};
2499
- clientCapabilities;
2500
- options;
2501
- lastSentConfigOptions;
2502
- constructor(client, options) {
2503
- super(client);
2504
- this.options = options;
2505
- this.toolUseCache = {};
2506
- this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
2507
- }
2508
- async initialize(request) {
2509
- this.clientCapabilities = request.clientCapabilities;
2714
+ // src/adapters/claude/session/settings.ts
2715
+ import * as fs2 from "fs";
2716
+ import * as os3 from "os";
2717
+ import * as path3 from "path";
2718
+ import { minimatch } from "minimatch";
2719
+ var ACP_TOOL_NAME_PREFIX = "mcp__acp__";
2720
+ var acpToolNames = {
2721
+ read: `${ACP_TOOL_NAME_PREFIX}Read`,
2722
+ edit: `${ACP_TOOL_NAME_PREFIX}Edit`,
2723
+ write: `${ACP_TOOL_NAME_PREFIX}Write`,
2724
+ bash: `${ACP_TOOL_NAME_PREFIX}Bash`
2725
+ };
2726
+ var SHELL_OPERATORS = ["&&", "||", ";", "|", "$(", "`", "\n"];
2727
+ function containsShellOperator(str) {
2728
+ return SHELL_OPERATORS.some((op) => str.includes(op));
2729
+ }
2730
+ var FILE_EDITING_TOOLS = [acpToolNames.edit, acpToolNames.write];
2731
+ var FILE_READING_TOOLS = [acpToolNames.read];
2732
+ var TOOL_ARG_ACCESSORS = {
2733
+ [acpToolNames.read]: (input) => input?.file_path,
2734
+ [acpToolNames.edit]: (input) => input?.file_path,
2735
+ [acpToolNames.write]: (input) => input?.file_path,
2736
+ [acpToolNames.bash]: (input) => input?.command
2737
+ };
2738
+ function parseRule(rule) {
2739
+ const match = rule.match(/^(\w+)(?:\((.+)\))?$/);
2740
+ if (!match) {
2741
+ return { toolName: rule };
2742
+ }
2743
+ const toolName = match[1] ?? rule;
2744
+ const argument = match[2];
2745
+ if (argument?.endsWith(":*")) {
2510
2746
  return {
2511
- protocolVersion: 1,
2512
- agentCapabilities: {
2513
- promptCapabilities: {
2514
- image: true,
2515
- embeddedContext: true
2516
- },
2517
- mcpCapabilities: {
2518
- http: true,
2519
- sse: true
2520
- },
2521
- loadSession: true,
2522
- _meta: {
2523
- posthog: {
2524
- resumeSession: true
2525
- }
2526
- }
2527
- },
2528
- agentInfo: {
2529
- name: package_default.name,
2530
- title: "Claude Code",
2531
- version: package_default.version
2532
- },
2533
- authMethods: [
2534
- {
2535
- id: "claude-login",
2536
- name: "Log in with Claude Code",
2537
- description: "Run `claude /login` in the terminal"
2538
- }
2539
- ]
2747
+ toolName,
2748
+ argument: argument.slice(0, -2),
2749
+ isWildcard: true
2540
2750
  };
2541
2751
  }
2542
- async authenticate(_params) {
2543
- throw new Error("Method not implemented.");
2752
+ return { toolName, argument };
2753
+ }
2754
+ function normalizePath(filePath, cwd) {
2755
+ let resolved = filePath;
2756
+ if (resolved.startsWith("~/")) {
2757
+ resolved = path3.join(os3.homedir(), resolved.slice(2));
2758
+ } else if (resolved.startsWith("./")) {
2759
+ resolved = path3.join(cwd, resolved.slice(2));
2760
+ } else if (!path3.isAbsolute(resolved)) {
2761
+ resolved = path3.join(cwd, resolved);
2762
+ }
2763
+ return path3.normalize(resolved).replace(/\\/g, "/");
2764
+ }
2765
+ function matchesGlob(pattern, filePath, cwd) {
2766
+ const normalizedPattern = normalizePath(pattern, cwd);
2767
+ const normalizedPath = normalizePath(filePath, cwd);
2768
+ return minimatch(normalizedPath, normalizedPattern, {
2769
+ dot: true,
2770
+ matchBase: false,
2771
+ nocase: process.platform === "win32"
2772
+ });
2773
+ }
2774
+ function matchesRule(rule, toolName, toolInput, cwd) {
2775
+ const ruleAppliesToTool = rule.toolName === "Bash" && toolName === acpToolNames.bash || rule.toolName === "Edit" && FILE_EDITING_TOOLS.includes(toolName) || rule.toolName === "Read" && FILE_READING_TOOLS.includes(toolName);
2776
+ if (!ruleAppliesToTool) {
2777
+ return false;
2544
2778
  }
2545
- async newSession(params) {
2546
- this.checkAuthStatus();
2547
- const meta = params._meta;
2548
- const taskId = meta?.persistence?.taskId;
2549
- const sessionId = uuidv7();
2550
- this.logger.info("Creating new session", {
2551
- sessionId,
2552
- taskId,
2553
- taskRunId: meta?.taskRunId,
2554
- cwd: params.cwd
2555
- });
2556
- const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
2557
- const mcpServers = parseMcpServers(params);
2558
- const options = buildSessionOptions({
2559
- cwd: params.cwd,
2560
- mcpServers,
2561
- permissionMode,
2562
- canUseTool: this.createCanUseTool(sessionId),
2563
- logger: this.logger,
2564
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
2565
- userProvidedOptions: meta?.claudeCode?.options,
2566
- sessionId,
2567
- isResume: false,
2568
- onModeChange: this.createOnModeChange(sessionId),
2569
- onProcessSpawned: this.options?.onProcessSpawned,
2570
- onProcessExited: this.options?.onProcessExited
2779
+ if (!rule.argument) {
2780
+ return true;
2781
+ }
2782
+ const argAccessor = TOOL_ARG_ACCESSORS[toolName];
2783
+ if (!argAccessor) {
2784
+ return true;
2785
+ }
2786
+ const actualArg = argAccessor(toolInput);
2787
+ if (!actualArg) {
2788
+ return false;
2789
+ }
2790
+ if (toolName === acpToolNames.bash) {
2791
+ if (rule.isWildcard) {
2792
+ if (!actualArg.startsWith(rule.argument)) {
2793
+ return false;
2794
+ }
2795
+ const remainder = actualArg.slice(rule.argument.length);
2796
+ if (containsShellOperator(remainder)) {
2797
+ return false;
2798
+ }
2799
+ return true;
2800
+ }
2801
+ return actualArg === rule.argument;
2802
+ }
2803
+ return matchesGlob(rule.argument, actualArg, cwd);
2804
+ }
2805
+ async function loadSettingsFile(filePath) {
2806
+ if (!filePath) {
2807
+ return {};
2808
+ }
2809
+ try {
2810
+ const content = await fs2.promises.readFile(filePath, "utf-8");
2811
+ return JSON.parse(content);
2812
+ } catch {
2813
+ return {};
2814
+ }
2815
+ }
2816
+ function getManagedSettingsPath() {
2817
+ switch (process.platform) {
2818
+ case "darwin":
2819
+ return "/Library/Application Support/ClaudeCode/managed-settings.json";
2820
+ case "linux":
2821
+ return "/etc/claude-code/managed-settings.json";
2822
+ case "win32":
2823
+ return "C:\\Program Files\\ClaudeCode\\managed-settings.json";
2824
+ default:
2825
+ return "/etc/claude-code/managed-settings.json";
2826
+ }
2827
+ }
2828
+ var SettingsManager = class {
2829
+ cwd;
2830
+ userSettings = {};
2831
+ projectSettings = {};
2832
+ localSettings = {};
2833
+ enterpriseSettings = {};
2834
+ mergedSettings = {};
2835
+ initialized = false;
2836
+ constructor(cwd) {
2837
+ this.cwd = cwd;
2838
+ }
2839
+ async initialize() {
2840
+ if (this.initialized) {
2841
+ return;
2842
+ }
2843
+ await this.loadAllSettings();
2844
+ this.initialized = true;
2845
+ }
2846
+ getUserSettingsPath() {
2847
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path3.join(os3.homedir(), ".claude");
2848
+ return path3.join(configDir, "settings.json");
2849
+ }
2850
+ getProjectSettingsPath() {
2851
+ return path3.join(this.cwd, ".claude", "settings.json");
2852
+ }
2853
+ getLocalSettingsPath() {
2854
+ return path3.join(this.cwd, ".claude", "settings.local.json");
2855
+ }
2856
+ async loadAllSettings() {
2857
+ const [userSettings, projectSettings, localSettings, enterpriseSettings] = await Promise.all([
2858
+ loadSettingsFile(this.getUserSettingsPath()),
2859
+ loadSettingsFile(this.getProjectSettingsPath()),
2860
+ loadSettingsFile(this.getLocalSettingsPath()),
2861
+ loadSettingsFile(getManagedSettingsPath())
2862
+ ]);
2863
+ this.userSettings = userSettings;
2864
+ this.projectSettings = projectSettings;
2865
+ this.localSettings = localSettings;
2866
+ this.enterpriseSettings = enterpriseSettings;
2867
+ this.mergeAllSettings();
2868
+ }
2869
+ mergeAllSettings() {
2870
+ const allSettings = [
2871
+ this.userSettings,
2872
+ this.projectSettings,
2873
+ this.localSettings,
2874
+ this.enterpriseSettings
2875
+ ];
2876
+ const permissions = {
2877
+ allow: [],
2878
+ deny: [],
2879
+ ask: []
2880
+ };
2881
+ const merged = { permissions };
2882
+ for (const settings of allSettings) {
2883
+ if (settings.permissions) {
2884
+ if (settings.permissions.allow) {
2885
+ permissions.allow?.push(...settings.permissions.allow);
2886
+ }
2887
+ if (settings.permissions.deny) {
2888
+ permissions.deny?.push(...settings.permissions.deny);
2889
+ }
2890
+ if (settings.permissions.ask) {
2891
+ permissions.ask?.push(...settings.permissions.ask);
2892
+ }
2893
+ if (settings.permissions.additionalDirectories) {
2894
+ permissions.additionalDirectories = [
2895
+ ...permissions.additionalDirectories || [],
2896
+ ...settings.permissions.additionalDirectories
2897
+ ];
2898
+ }
2899
+ if (settings.permissions.defaultMode) {
2900
+ permissions.defaultMode = settings.permissions.defaultMode;
2901
+ }
2902
+ }
2903
+ if (settings.env) {
2904
+ merged.env = { ...merged.env, ...settings.env };
2905
+ }
2906
+ if (settings.model) {
2907
+ merged.model = settings.model;
2908
+ }
2909
+ }
2910
+ this.mergedSettings = merged;
2911
+ }
2912
+ checkPermission(toolName, toolInput) {
2913
+ if (!toolName.startsWith(ACP_TOOL_NAME_PREFIX)) {
2914
+ return { decision: "ask" };
2915
+ }
2916
+ const permissions = this.mergedSettings.permissions;
2917
+ if (!permissions) {
2918
+ return { decision: "ask" };
2919
+ }
2920
+ for (const rule of permissions.deny || []) {
2921
+ const parsed = parseRule(rule);
2922
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
2923
+ return { decision: "deny", rule, source: "deny" };
2924
+ }
2925
+ }
2926
+ for (const rule of permissions.allow || []) {
2927
+ const parsed = parseRule(rule);
2928
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
2929
+ return { decision: "allow", rule, source: "allow" };
2930
+ }
2931
+ }
2932
+ for (const rule of permissions.ask || []) {
2933
+ const parsed = parseRule(rule);
2934
+ if (matchesRule(parsed, toolName, toolInput, this.cwd)) {
2935
+ return { decision: "ask", rule, source: "ask" };
2936
+ }
2937
+ }
2938
+ return { decision: "ask" };
2939
+ }
2940
+ getSettings() {
2941
+ return this.mergedSettings;
2942
+ }
2943
+ getCwd() {
2944
+ return this.cwd;
2945
+ }
2946
+ async setCwd(cwd) {
2947
+ if (this.cwd === cwd) {
2948
+ return;
2949
+ }
2950
+ this.dispose();
2951
+ this.cwd = cwd;
2952
+ this.initialized = false;
2953
+ await this.initialize();
2954
+ }
2955
+ dispose() {
2956
+ this.initialized = false;
2957
+ }
2958
+ };
2959
+
2960
+ // src/adapters/claude/claude-agent.ts
2961
+ var SESSION_VALIDATION_TIMEOUT_MS = 1e4;
2962
+ var MAX_TITLE_LENGTH = 256;
2963
+ function sanitizeTitle(text2) {
2964
+ const sanitized = text2.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
2965
+ if (sanitized.length <= MAX_TITLE_LENGTH) {
2966
+ return sanitized;
2967
+ }
2968
+ return `${sanitized.slice(0, MAX_TITLE_LENGTH - 1)}\u2026`;
2969
+ }
2970
+ var ClaudeAcpAgent = class extends BaseAcpAgent {
2971
+ adapterName = "claude";
2972
+ toolUseCache;
2973
+ backgroundTerminals = {};
2974
+ clientCapabilities;
2975
+ options;
2976
+ constructor(client, options) {
2977
+ super(client);
2978
+ this.options = options;
2979
+ this.toolUseCache = {};
2980
+ this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
2981
+ }
2982
+ async initialize(request) {
2983
+ this.clientCapabilities = request.clientCapabilities;
2984
+ return {
2985
+ protocolVersion: 1,
2986
+ agentCapabilities: {
2987
+ promptCapabilities: {
2988
+ image: true,
2989
+ embeddedContext: true
2990
+ },
2991
+ mcpCapabilities: {
2992
+ http: true,
2993
+ sse: true
2994
+ },
2995
+ loadSession: true,
2996
+ sessionCapabilities: {
2997
+ list: {},
2998
+ fork: {},
2999
+ resume: {}
3000
+ },
3001
+ _meta: {
3002
+ posthog: {
3003
+ resumeSession: true
3004
+ },
3005
+ claudeCode: {
3006
+ promptQueueing: true
3007
+ }
3008
+ }
3009
+ },
3010
+ agentInfo: {
3011
+ name: package_default.name,
3012
+ title: "Claude Agent",
3013
+ version: package_default.version
3014
+ },
3015
+ authMethods: []
3016
+ };
3017
+ }
3018
+ async newSession(params) {
3019
+ if (fs3.existsSync(path4.resolve(os4.homedir(), ".claude.json.backup")) && !fs3.existsSync(path4.resolve(os4.homedir(), ".claude.json"))) {
3020
+ throw RequestError2.authRequired();
3021
+ }
3022
+ const response = await this.createSession(params, {
3023
+ // Revisit these meta values once we support resume
3024
+ resume: params._meta?.claudeCode?.options?.resume
2571
3025
  });
2572
- const input = new Pushable();
2573
- options.model = DEFAULT_MODEL;
2574
- const q = query({ prompt: input, options });
2575
- const session = this.createSession(
2576
- sessionId,
2577
- q,
2578
- input,
2579
- permissionMode,
2580
- params.cwd,
2581
- options.abortController
3026
+ return response;
3027
+ }
3028
+ async unstable_forkSession(params) {
3029
+ return this.createSession(
3030
+ {
3031
+ cwd: params.cwd,
3032
+ mcpServers: params.mcpServers ?? [],
3033
+ _meta: params._meta
3034
+ },
3035
+ { resume: params.sessionId, forkSession: true }
2582
3036
  );
2583
- session.taskRunId = meta?.taskRunId;
2584
- if (meta?.taskRunId) {
2585
- await this.client.extNotification("_posthog/sdk_session", {
2586
- taskRunId: meta.taskRunId,
2587
- sessionId,
2588
- adapter: "claude"
3037
+ }
3038
+ async unstable_resumeSession(params) {
3039
+ const response = await this.createSession(
3040
+ {
3041
+ cwd: params.cwd,
3042
+ mcpServers: params.mcpServers ?? [],
3043
+ _meta: params._meta
3044
+ },
3045
+ {
3046
+ resume: params.sessionId
3047
+ }
3048
+ );
3049
+ return response;
3050
+ }
3051
+ async loadSession(params) {
3052
+ const response = await this.createSession(
3053
+ {
3054
+ cwd: params.cwd,
3055
+ mcpServers: params.mcpServers ?? [],
3056
+ _meta: params._meta
3057
+ },
3058
+ { resume: params.sessionId, skipBackgroundFetches: true }
3059
+ );
3060
+ await this.replaySessionHistory(params.sessionId);
3061
+ this.deferBackgroundFetches(this.session.query);
3062
+ return {
3063
+ modes: response.modes,
3064
+ models: response.models,
3065
+ configOptions: response.configOptions
3066
+ };
3067
+ }
3068
+ async unstable_listSessions(params) {
3069
+ const sdkSessions = await listSessions({ dir: params.cwd ?? void 0 });
3070
+ const sessions = [];
3071
+ for (const session of sdkSessions) {
3072
+ if (!session.cwd) continue;
3073
+ sessions.push({
3074
+ sessionId: session.sessionId,
3075
+ cwd: session.cwd,
3076
+ title: sanitizeTitle(session.customTitle || session.summary || ""),
3077
+ updatedAt: new Date(session.lastModified).toISOString()
2589
3078
  });
2590
3079
  }
2591
- const modelOptions = await this.getModelConfigOptions();
2592
- this.deferBackgroundFetches(q, sessionId);
2593
- session.modelId = modelOptions.currentModelId;
2594
- const resolvedSdkModel = toSdkModelId(modelOptions.currentModelId);
2595
- if (resolvedSdkModel !== DEFAULT_MODEL) {
2596
- await this.trySetModel(q, modelOptions.currentModelId);
2597
- }
2598
- const configOptions = await this.buildConfigOptions(modelOptions);
2599
3080
  return {
2600
- sessionId,
2601
- configOptions
3081
+ sessions
2602
3082
  };
2603
3083
  }
2604
- async loadSession(params) {
2605
- return this.resumeSession(params);
3084
+ async prompt(params) {
3085
+ this.session.cancelled = false;
3086
+ this.session.interruptReason = void 0;
3087
+ this.session.accumulatedUsage = {
3088
+ inputTokens: 0,
3089
+ outputTokens: 0,
3090
+ cachedReadTokens: 0,
3091
+ cachedWriteTokens: 0
3092
+ };
3093
+ const userMessage = promptToClaude(params);
3094
+ if (this.session.promptRunning) {
3095
+ const uuid = randomUUID();
3096
+ userMessage.uuid = uuid;
3097
+ this.session.input.push(userMessage);
3098
+ const order = this.session.nextPendingOrder++;
3099
+ const cancelled = await new Promise((resolve3) => {
3100
+ this.session.pendingMessages.set(uuid, { resolve: resolve3, order });
3101
+ });
3102
+ if (cancelled) {
3103
+ return { stopReason: "cancelled" };
3104
+ }
3105
+ } else {
3106
+ this.session.input.push(userMessage);
3107
+ }
3108
+ await this.broadcastUserMessage(params);
3109
+ this.session.promptRunning = true;
3110
+ let handedOff = false;
3111
+ let lastAssistantTotalUsage = null;
3112
+ const supportsTerminalOutput = this.clientCapabilities?._meta?.terminal_output === true;
3113
+ const context = {
3114
+ session: this.session,
3115
+ sessionId: params.sessionId,
3116
+ client: this.client,
3117
+ toolUseCache: this.toolUseCache,
3118
+ fileContentCache: this.fileContentCache,
3119
+ logger: this.logger,
3120
+ supportsTerminalOutput
3121
+ };
3122
+ try {
3123
+ while (true) {
3124
+ const { value: message, done } = await this.session.query.next();
3125
+ if (done || !message) {
3126
+ if (this.session.cancelled) {
3127
+ return {
3128
+ stopReason: "cancelled",
3129
+ _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
3130
+ };
3131
+ }
3132
+ break;
3133
+ }
3134
+ switch (message.type) {
3135
+ case "system":
3136
+ if (message.subtype === "compact_boundary") {
3137
+ lastAssistantTotalUsage = 0;
3138
+ }
3139
+ await handleSystemMessage(message, context);
3140
+ break;
3141
+ case "result": {
3142
+ if (this.session.cancelled) {
3143
+ return { stopReason: "cancelled" };
3144
+ }
3145
+ this.session.accumulatedUsage.inputTokens += message.usage.input_tokens;
3146
+ this.session.accumulatedUsage.outputTokens += message.usage.output_tokens;
3147
+ this.session.accumulatedUsage.cachedReadTokens += message.usage.cache_read_input_tokens;
3148
+ this.session.accumulatedUsage.cachedWriteTokens += message.usage.cache_creation_input_tokens;
3149
+ const contextWindows = Object.values(message.modelUsage).map(
3150
+ (m) => m.contextWindow
3151
+ );
3152
+ const contextWindowSize = contextWindows.length > 0 ? Math.min(...contextWindows) : 2e5;
3153
+ if (lastAssistantTotalUsage !== null) {
3154
+ await this.client.sessionUpdate({
3155
+ sessionId: params.sessionId,
3156
+ update: {
3157
+ sessionUpdate: "usage_update",
3158
+ used: lastAssistantTotalUsage,
3159
+ size: contextWindowSize,
3160
+ cost: {
3161
+ amount: message.total_cost_usd,
3162
+ currency: "USD"
3163
+ }
3164
+ }
3165
+ });
3166
+ }
3167
+ await this.client.extNotification("_posthog/usage_update", {
3168
+ sessionId: params.sessionId,
3169
+ used: {
3170
+ inputTokens: message.usage.input_tokens,
3171
+ outputTokens: message.usage.output_tokens,
3172
+ cachedReadTokens: message.usage.cache_read_input_tokens,
3173
+ cachedWriteTokens: message.usage.cache_creation_input_tokens
3174
+ },
3175
+ cost: message.total_cost_usd
3176
+ });
3177
+ const usage = {
3178
+ inputTokens: this.session.accumulatedUsage.inputTokens,
3179
+ outputTokens: this.session.accumulatedUsage.outputTokens,
3180
+ cachedReadTokens: this.session.accumulatedUsage.cachedReadTokens,
3181
+ cachedWriteTokens: this.session.accumulatedUsage.cachedWriteTokens,
3182
+ totalTokens: this.session.accumulatedUsage.inputTokens + this.session.accumulatedUsage.outputTokens + this.session.accumulatedUsage.cachedReadTokens + this.session.accumulatedUsage.cachedWriteTokens
3183
+ };
3184
+ const result = handleResultMessage(message);
3185
+ if (result.error) throw result.error;
3186
+ switch (message.subtype) {
3187
+ case "error_max_budget_usd":
3188
+ case "error_max_turns":
3189
+ case "error_max_structured_output_retries":
3190
+ return { stopReason: "max_turn_requests", usage };
3191
+ default:
3192
+ return { stopReason: "end_turn", usage };
3193
+ }
3194
+ }
3195
+ case "stream_event":
3196
+ await handleStreamEvent(message, context);
3197
+ break;
3198
+ case "user":
3199
+ case "assistant": {
3200
+ if (this.session.cancelled) {
3201
+ break;
3202
+ }
3203
+ if (message.type === "user" && "uuid" in message && message.uuid) {
3204
+ const pending = this.session.pendingMessages.get(
3205
+ message.uuid
3206
+ );
3207
+ if (pending) {
3208
+ pending.resolve(false);
3209
+ this.session.pendingMessages.delete(message.uuid);
3210
+ handedOff = true;
3211
+ return { stopReason: "end_turn" };
3212
+ }
3213
+ }
3214
+ if ("usage" in message.message && message.parent_tool_use_id === null) {
3215
+ const usage = message.message.usage;
3216
+ lastAssistantTotalUsage = usage.input_tokens + usage.output_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens;
3217
+ }
3218
+ const result = await handleUserAssistantMessage(message, context);
3219
+ if (result.error) throw result.error;
3220
+ if (result.shouldStop) {
3221
+ return { stopReason: "end_turn" };
3222
+ }
3223
+ break;
3224
+ }
3225
+ case "tool_progress":
3226
+ case "auth_status":
3227
+ case "tool_use_summary":
3228
+ break;
3229
+ default:
3230
+ unreachable(message, this.logger);
3231
+ break;
3232
+ }
3233
+ }
3234
+ throw new Error("Session did not end in result");
3235
+ } finally {
3236
+ if (!handedOff) {
3237
+ this.session.promptRunning = false;
3238
+ for (const [key, pending] of this.session.pendingMessages) {
3239
+ pending.resolve(true);
3240
+ this.session.pendingMessages.delete(key);
3241
+ }
3242
+ }
3243
+ }
3244
+ }
3245
+ // Called by BaseAcpAgent#cancel() to interrupt the session
3246
+ async interrupt() {
3247
+ this.session.cancelled = true;
3248
+ for (const [, pending] of this.session.pendingMessages) {
3249
+ pending.resolve(true);
3250
+ }
3251
+ this.session.pendingMessages.clear();
3252
+ await this.session.query.interrupt();
3253
+ }
3254
+ async unstable_setSessionModel(params) {
3255
+ const sdkModelId = toSdkModelId(params.modelId);
3256
+ await this.session.query.setModel(sdkModelId);
3257
+ this.session.modelId = params.modelId;
3258
+ await this.updateConfigOption("model", params.modelId);
3259
+ return {};
3260
+ }
3261
+ async setSessionMode(params) {
3262
+ await this.applySessionMode(params.modeId);
3263
+ await this.updateConfigOption("mode", params.modeId);
3264
+ return {};
3265
+ }
3266
+ async setSessionConfigOption(params) {
3267
+ const option = this.session.configOptions.find(
3268
+ (o) => o.id === params.configId
3269
+ );
3270
+ if (!option) {
3271
+ throw new Error(`Unknown config option: ${params.configId}`);
3272
+ }
3273
+ const allValues = "options" in option && Array.isArray(option.options) ? option.options.flatMap(
3274
+ (o) => "options" in o && Array.isArray(o.options) ? o.options : [o]
3275
+ ) : [];
3276
+ const validValue = allValues.find((o) => o.value === params.value);
3277
+ if (!validValue) {
3278
+ throw new Error(
3279
+ `Invalid value for config option ${params.configId}: ${params.value}`
3280
+ );
3281
+ }
3282
+ if (params.configId === "mode") {
3283
+ await this.applySessionMode(params.value);
3284
+ await this.client.sessionUpdate({
3285
+ sessionId: this.sessionId,
3286
+ update: {
3287
+ sessionUpdate: "current_mode_update",
3288
+ currentModeId: params.value
3289
+ }
3290
+ });
3291
+ } else if (params.configId === "model") {
3292
+ const sdkModelId = toSdkModelId(params.value);
3293
+ await this.session.query.setModel(sdkModelId);
3294
+ this.session.modelId = params.value;
3295
+ }
3296
+ this.session.configOptions = this.session.configOptions.map(
3297
+ (o) => o.id === params.configId ? { ...o, currentValue: params.value } : o
3298
+ );
3299
+ return { configOptions: this.session.configOptions };
2606
3300
  }
2607
- async resumeSession(params) {
3301
+ async updateConfigOption(configId, value) {
3302
+ this.session.configOptions = this.session.configOptions.map(
3303
+ (o) => o.id === configId ? { ...o, currentValue: value } : o
3304
+ );
3305
+ await this.client.sessionUpdate({
3306
+ sessionId: this.sessionId,
3307
+ update: {
3308
+ sessionUpdate: "config_option_update",
3309
+ configOptions: this.session.configOptions
3310
+ }
3311
+ });
3312
+ }
3313
+ async applySessionMode(modeId) {
3314
+ if (!TWIG_EXECUTION_MODES.includes(modeId)) {
3315
+ throw new Error("Invalid Mode");
3316
+ }
3317
+ const previousMode = this.session.permissionMode;
3318
+ this.session.permissionMode = modeId;
3319
+ try {
3320
+ await this.session.query.setPermissionMode(modeId);
3321
+ } catch (error) {
3322
+ this.session.permissionMode = previousMode;
3323
+ if (error instanceof Error) {
3324
+ if (!error.message) {
3325
+ error.message = "Invalid Mode";
3326
+ }
3327
+ throw error;
3328
+ }
3329
+ throw new Error("Invalid Mode");
3330
+ }
3331
+ }
3332
+ async createSession(params, creationOpts = {}) {
3333
+ const { cwd } = params;
3334
+ const { resume, forkSession } = creationOpts;
3335
+ const isResume = !!resume;
2608
3336
  const meta = params._meta;
2609
3337
  const taskId = meta?.persistence?.taskId;
2610
- const sessionId = meta?.sessionId;
2611
- if (!sessionId) {
2612
- throw new Error("Cannot resume session without sessionId");
2613
- }
2614
- if (this.sessionId === sessionId) {
2615
- return {};
3338
+ let sessionId;
3339
+ if (forkSession) {
3340
+ sessionId = uuidv7();
3341
+ } else if (isResume) {
3342
+ sessionId = resume;
3343
+ } else {
3344
+ sessionId = uuidv7();
2616
3345
  }
2617
- this.logger.info("Resuming session", {
3346
+ const input = new Pushable();
3347
+ const settingsManager = new SettingsManager(cwd);
3348
+ await settingsManager.initialize();
3349
+ const mcpServers = parseMcpServers(params);
3350
+ const systemPrompt = buildSystemPrompt(meta?.systemPrompt);
3351
+ this.logger.info(isResume ? "Resuming session" : "Creating new session", {
2618
3352
  sessionId,
2619
3353
  taskId,
2620
3354
  taskRunId: meta?.taskRunId,
2621
- cwd: params.cwd
3355
+ cwd
2622
3356
  });
2623
- const mcpServers = parseMcpServers(params);
2624
3357
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
2625
- const { query: q, session } = await this.initializeQuery({
2626
- cwd: params.cwd,
2627
- permissionMode,
3358
+ const options = buildSessionOptions({
3359
+ cwd,
2628
3360
  mcpServers,
2629
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3361
+ permissionMode,
3362
+ canUseTool: this.createCanUseTool(sessionId),
3363
+ logger: this.logger,
3364
+ systemPrompt,
2630
3365
  userProvidedOptions: meta?.claudeCode?.options,
2631
3366
  sessionId,
2632
- isResume: true,
2633
- additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3367
+ isResume,
3368
+ forkSession,
3369
+ additionalDirectories: meta?.claudeCode?.options?.additionalDirectories,
3370
+ disableBuiltInTools: meta?.disableBuiltInTools,
3371
+ settingsManager,
3372
+ onModeChange: this.createOnModeChange(),
3373
+ onProcessSpawned: this.options?.onProcessSpawned,
3374
+ onProcessExited: this.options?.onProcessExited
2634
3375
  });
2635
- this.logger.info("Session query initialized, awaiting resumption", {
2636
- sessionId,
2637
- taskId,
3376
+ const abortController = options.abortController;
3377
+ const q = query({ prompt: input, options });
3378
+ const session = {
3379
+ query: q,
3380
+ input,
3381
+ cancelled: false,
3382
+ settingsManager,
3383
+ permissionMode,
3384
+ abortController,
3385
+ accumulatedUsage: {
3386
+ inputTokens: 0,
3387
+ outputTokens: 0,
3388
+ cachedReadTokens: 0,
3389
+ cachedWriteTokens: 0
3390
+ },
3391
+ configOptions: [],
3392
+ promptRunning: false,
3393
+ pendingMessages: /* @__PURE__ */ new Map(),
3394
+ nextPendingOrder: 0,
3395
+ // Custom properties
3396
+ cwd,
3397
+ notificationHistory: [],
2638
3398
  taskRunId: meta?.taskRunId
2639
- });
2640
- session.taskRunId = meta?.taskRunId;
3399
+ };
3400
+ this.session = session;
3401
+ this.sessionId = sessionId;
3402
+ this.logger.info(
3403
+ isResume ? "Session query initialized, awaiting resumption" : "Session query initialized, awaiting initialization",
3404
+ { sessionId, taskId, taskRunId: meta?.taskRunId }
3405
+ );
2641
3406
  try {
2642
3407
  const result = await withTimeout(
2643
3408
  q.initializationResult(),
@@ -2645,231 +3410,181 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
2645
3410
  );
2646
3411
  if (result.result === "timeout") {
2647
3412
  throw new Error(
2648
- `Session resumption timed out for sessionId=${sessionId}`
3413
+ `Session ${isResume ? forkSession ? "fork" : "resumption" : "initialization"} timed out for sessionId=${sessionId}`
2649
3414
  );
2650
3415
  }
2651
3416
  } catch (err) {
2652
- this.logger.error("Session resumption failed", {
2653
- sessionId,
2654
- taskId,
2655
- taskRunId: meta?.taskRunId,
2656
- error: err instanceof Error ? err.message : String(err)
2657
- });
3417
+ settingsManager.dispose();
3418
+ this.logger.error(
3419
+ isResume ? forkSession ? "Session fork failed" : "Session resumption failed" : "Session initialization failed",
3420
+ {
3421
+ sessionId,
3422
+ taskId,
3423
+ taskRunId: meta?.taskRunId,
3424
+ error: err instanceof Error ? err.message : String(err)
3425
+ }
3426
+ );
2658
3427
  throw err;
2659
3428
  }
2660
- this.logger.info("Session resumed successfully", {
2661
- sessionId,
2662
- taskId,
2663
- taskRunId: meta?.taskRunId
2664
- });
2665
- this.deferBackgroundFetches(q, sessionId);
2666
- const configOptions = await this.buildConfigOptions();
2667
- return { configOptions };
2668
- }
2669
- async prompt(params) {
2670
- this.session.cancelled = false;
2671
- this.session.interruptReason = void 0;
2672
- await this.broadcastUserMessage(params);
2673
- this.session.input.push(promptToClaude(params));
2674
- return this.processMessages(params.sessionId);
2675
- }
2676
- async setSessionConfigOption(params) {
2677
- const configId = params.configId;
2678
- const value = params.value;
2679
- if (configId === "mode") {
2680
- const modeId = value;
2681
- if (!TWIG_EXECUTION_MODES.includes(modeId)) {
2682
- throw new Error("Invalid Mode");
2683
- }
2684
- this.session.permissionMode = modeId;
2685
- await this.session.query.setPermissionMode(modeId);
2686
- } else if (configId === "model") {
2687
- await this.setModelWithFallback(this.session.query, value);
2688
- this.session.modelId = value;
2689
- } else {
2690
- throw new Error("Unsupported config option");
3429
+ if (meta?.taskRunId) {
3430
+ await this.client.extNotification("_posthog/sdk_session", {
3431
+ taskRunId: meta.taskRunId,
3432
+ sessionId,
3433
+ adapter: "claude"
3434
+ });
2691
3435
  }
2692
- await this.emitConfigOptionsUpdate();
2693
- return { configOptions: await this.buildConfigOptions() };
2694
- }
2695
- async interruptSession() {
2696
- await this.session.query.interrupt();
2697
- }
2698
- async extMethod(method, params) {
2699
- if (method === "_posthog/session/resume") {
2700
- const result = await this.resumeSession(
2701
- params
2702
- );
2703
- return {
2704
- _meta: {
2705
- configOptions: result.configOptions
2706
- }
2707
- };
3436
+ const settingsModel = settingsManager.getSettings().model;
3437
+ const modelOptions = await this.getModelConfigOptions();
3438
+ const resolvedModelId = settingsModel || modelOptions.currentModelId;
3439
+ session.modelId = resolvedModelId;
3440
+ if (!isResume) {
3441
+ const resolvedSdkModel = toSdkModelId(resolvedModelId);
3442
+ if (resolvedSdkModel !== DEFAULT_MODEL) {
3443
+ await this.session.query.setModel(resolvedSdkModel);
3444
+ }
2708
3445
  }
2709
- throw RequestError2.methodNotFound(method);
2710
- }
2711
- createSession(sessionId, q, input, permissionMode, cwd, abortController) {
2712
- const session = {
2713
- query: q,
2714
- input,
2715
- cancelled: false,
2716
- permissionMode,
2717
- cwd,
2718
- notificationHistory: [],
2719
- abortController
3446
+ const availableModes2 = getAvailableModes();
3447
+ const modes = {
3448
+ currentModeId: permissionMode,
3449
+ availableModes: availableModes2.map((mode) => ({
3450
+ id: mode.id,
3451
+ name: mode.name,
3452
+ description: mode.description ?? void 0
3453
+ }))
2720
3454
  };
2721
- this.session = session;
2722
- this.sessionId = sessionId;
2723
- return session;
2724
- }
2725
- async initializeQuery(config) {
2726
- const input = new Pushable();
2727
- const options = buildSessionOptions({
2728
- cwd: config.cwd,
2729
- mcpServers: config.mcpServers,
2730
- permissionMode: config.permissionMode,
2731
- canUseTool: this.createCanUseTool(config.sessionId),
2732
- logger: this.logger,
2733
- systemPrompt: config.systemPrompt,
2734
- userProvidedOptions: config.userProvidedOptions,
2735
- sessionId: config.sessionId,
2736
- isResume: config.isResume,
2737
- additionalDirectories: config.additionalDirectories,
2738
- onModeChange: this.createOnModeChange(config.sessionId),
2739
- onProcessSpawned: this.options?.onProcessSpawned,
2740
- onProcessExited: this.options?.onProcessExited
2741
- });
2742
- const q = query({ prompt: input, options });
2743
- const abortController = options.abortController;
2744
- const session = this.createSession(
2745
- config.sessionId,
2746
- q,
2747
- input,
2748
- config.permissionMode,
2749
- config.cwd,
2750
- abortController
3455
+ const models = {
3456
+ currentModelId: resolvedModelId,
3457
+ availableModels: modelOptions.options.map(
3458
+ (opt) => ({
3459
+ modelId: opt.value,
3460
+ name: opt.name,
3461
+ description: opt.description
3462
+ })
3463
+ )
3464
+ };
3465
+ const configOptions = this.buildConfigOptions(permissionMode, modelOptions);
3466
+ session.configOptions = configOptions;
3467
+ if (!creationOpts.skipBackgroundFetches) {
3468
+ this.deferBackgroundFetches(q);
3469
+ }
3470
+ this.logger.info(
3471
+ isResume ? "Session resumed successfully" : "Session created successfully",
3472
+ {
3473
+ sessionId,
3474
+ taskId,
3475
+ taskRunId: meta?.taskRunId
3476
+ }
2751
3477
  );
2752
- return { query: q, input, session };
3478
+ return { sessionId, modes, models, configOptions };
2753
3479
  }
2754
3480
  createCanUseTool(sessionId) {
2755
- return async (toolName, toolInput, { suggestions, toolUseID }) => canUseTool({
3481
+ return async (toolName, toolInput, { suggestions, toolUseID, signal }) => canUseTool({
2756
3482
  session: this.session,
2757
3483
  toolName,
2758
3484
  toolInput,
2759
3485
  toolUseID,
2760
3486
  suggestions,
3487
+ signal,
2761
3488
  client: this.client,
2762
3489
  sessionId,
2763
3490
  fileContentCache: this.fileContentCache,
2764
3491
  logger: this.logger,
2765
- emitConfigOptionsUpdate: () => this.emitConfigOptionsUpdate(sessionId)
3492
+ updateConfigOption: (configId, value) => this.updateConfigOption(configId, value)
2766
3493
  });
2767
3494
  }
2768
- createOnModeChange(sessionId) {
3495
+ createOnModeChange() {
2769
3496
  return async (newMode) => {
2770
3497
  if (this.session) {
2771
3498
  this.session.permissionMode = newMode;
2772
3499
  }
2773
- await this.emitConfigOptionsUpdate(sessionId);
3500
+ await this.updateConfigOption("mode", newMode);
2774
3501
  };
2775
3502
  }
2776
- async buildConfigOptions(modelOptionsOverride) {
2777
- const options = [];
3503
+ buildConfigOptions(currentModeId, modelOptions) {
2778
3504
  const modeOptions = getAvailableModes().map((mode) => ({
2779
3505
  value: mode.id,
2780
3506
  name: mode.name,
2781
3507
  description: mode.description ?? void 0
2782
3508
  }));
2783
- options.push({
2784
- id: "mode",
2785
- name: "Approval Preset",
2786
- type: "select",
2787
- currentValue: this.session.permissionMode,
2788
- options: modeOptions,
2789
- category: "mode",
2790
- description: "Choose an approval and sandboxing preset for your session"
2791
- });
2792
- const modelOptions = modelOptionsOverride ?? await this.getModelConfigOptions(this.session.modelId);
2793
- this.session.modelId = modelOptions.currentModelId;
2794
- options.push({
2795
- id: "model",
2796
- name: "Model",
2797
- type: "select",
2798
- currentValue: modelOptions.currentModelId,
2799
- options: modelOptions.options,
2800
- category: "model",
2801
- description: "Choose which model Claude should use"
2802
- });
2803
- return options;
3509
+ return [
3510
+ {
3511
+ id: "mode",
3512
+ name: "Approval Preset",
3513
+ type: "select",
3514
+ currentValue: currentModeId,
3515
+ options: modeOptions,
3516
+ category: "mode",
3517
+ description: "Choose an approval and sandboxing preset for your session"
3518
+ },
3519
+ {
3520
+ id: "model",
3521
+ name: "Model",
3522
+ type: "select",
3523
+ currentValue: modelOptions.currentModelId,
3524
+ options: modelOptions.options,
3525
+ category: "model",
3526
+ description: "Choose which model Claude should use"
3527
+ }
3528
+ ];
2804
3529
  }
2805
- async emitConfigOptionsUpdate(sessionId) {
2806
- const configOptions = await this.buildConfigOptions();
2807
- const serialized = JSON.stringify(configOptions);
2808
- if (this.lastSentConfigOptions && JSON.stringify(this.lastSentConfigOptions) === serialized) {
2809
- return;
2810
- }
2811
- this.lastSentConfigOptions = configOptions;
3530
+ async sendAvailableCommandsUpdate() {
3531
+ const commands = await this.session.query.supportedCommands();
2812
3532
  await this.client.sessionUpdate({
2813
- sessionId: sessionId ?? this.sessionId,
3533
+ sessionId: this.sessionId,
2814
3534
  update: {
2815
- sessionUpdate: "config_option_update",
2816
- configOptions
3535
+ sessionUpdate: "available_commands_update",
3536
+ availableCommands: getAvailableSlashCommands(commands)
2817
3537
  }
2818
3538
  });
2819
3539
  }
2820
- checkAuthStatus() {
2821
- const backupExists = fs2.existsSync(
2822
- path3.resolve(os3.homedir(), ".claude.json.backup")
2823
- );
2824
- const configExists = fs2.existsSync(
2825
- path3.resolve(os3.homedir(), ".claude.json")
2826
- );
2827
- if (backupExists && !configExists) {
2828
- throw RequestError2.authRequired();
2829
- }
2830
- }
2831
- async trySetModel(q, modelId) {
2832
- try {
2833
- await this.setModelWithFallback(q, modelId);
2834
- } catch (err) {
2835
- this.logger.warn("Failed to set model", { modelId, error: err });
2836
- }
2837
- }
2838
- async setModelWithFallback(q, modelId) {
2839
- const sdkModelId = toSdkModelId(modelId);
3540
+ async replaySessionHistory(sessionId) {
2840
3541
  try {
2841
- await q.setModel(sdkModelId);
2842
- } catch (err) {
2843
- if (sdkModelId === modelId) {
2844
- throw err;
3542
+ const messages = await getSessionMessages(sessionId, {
3543
+ dir: this.session.cwd
3544
+ });
3545
+ const replayContext = {
3546
+ session: this.session,
3547
+ sessionId,
3548
+ client: this.client,
3549
+ toolUseCache: this.toolUseCache,
3550
+ fileContentCache: this.fileContentCache,
3551
+ logger: this.logger,
3552
+ registerHooks: false
3553
+ };
3554
+ for (const msg of messages) {
3555
+ const sdkMessage = {
3556
+ type: msg.type,
3557
+ message: msg.message,
3558
+ parent_tool_use_id: msg.parent_tool_use_id
3559
+ };
3560
+ await handleUserAssistantMessage(
3561
+ sdkMessage,
3562
+ replayContext
3563
+ );
2845
3564
  }
2846
- await q.setModel(modelId);
3565
+ } catch (err) {
3566
+ this.logger.warn("Failed to replay session history", {
3567
+ sessionId,
3568
+ error: err instanceof Error ? err.message : String(err)
3569
+ });
2847
3570
  }
2848
3571
  }
3572
+ // ================================
3573
+ // EXTENSION METHODS
3574
+ // ================================
2849
3575
  /**
2850
3576
  * Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
2851
3577
  * Both populate caches used later — neither is needed to return configOptions.
2852
3578
  */
2853
- deferBackgroundFetches(q, sessionId) {
3579
+ deferBackgroundFetches(q) {
2854
3580
  Promise.all([
2855
- getAvailableSlashCommands(q),
3581
+ new Promise((resolve3) => setTimeout(resolve3, 10)).then(
3582
+ () => this.sendAvailableCommandsUpdate()
3583
+ ),
2856
3584
  fetchMcpToolMetadata(q, this.logger)
2857
- ]).then(([slashCommands]) => {
2858
- this.sendAvailableCommandsUpdate(sessionId, slashCommands);
2859
- }).catch((err) => {
2860
- this.logger.warn("Failed to fetch deferred session data", { err });
2861
- });
2862
- }
2863
- sendAvailableCommandsUpdate(sessionId, availableCommands) {
2864
- setTimeout(() => {
2865
- this.client.sessionUpdate({
2866
- sessionId,
2867
- update: {
2868
- sessionUpdate: "available_commands_update",
2869
- availableCommands
2870
- }
2871
- });
2872
- }, 0);
3585
+ ]).catch(
3586
+ (err) => this.logger.error("Background fetch failed", { error: err })
3587
+ );
2873
3588
  }
2874
3589
  async broadcastUserMessage(params) {
2875
3590
  for (const chunk of params.prompt) {
@@ -2884,71 +3599,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
2884
3599
  this.appendNotification(params.sessionId, notification);
2885
3600
  }
2886
3601
  }
2887
- async processMessages(sessionId) {
2888
- const context = {
2889
- session: this.session,
2890
- sessionId,
2891
- client: this.client,
2892
- toolUseCache: this.toolUseCache,
2893
- fileContentCache: this.fileContentCache,
2894
- logger: this.logger
2895
- };
2896
- while (true) {
2897
- const { value: message, done } = await this.session.query.next();
2898
- if (done || !message) {
2899
- return this.handleSessionEnd();
2900
- }
2901
- const response = await this.handleMessage(message, context);
2902
- if (response) {
2903
- return response;
2904
- }
2905
- }
2906
- }
2907
- handleSessionEnd() {
2908
- if (this.session.cancelled) {
2909
- return {
2910
- stopReason: "cancelled",
2911
- _meta: this.session.interruptReason ? { interruptReason: this.session.interruptReason } : void 0
2912
- };
2913
- }
2914
- throw new Error("Session did not end in result");
2915
- }
2916
- async handleMessage(message, context) {
2917
- switch (message.type) {
2918
- case "system":
2919
- await handleSystemMessage(message, context);
2920
- return null;
2921
- case "result": {
2922
- const result = handleResultMessage(message, context);
2923
- if (result.error) throw result.error;
2924
- if (result.shouldStop) {
2925
- return {
2926
- stopReason: result.stopReason
2927
- };
2928
- }
2929
- return null;
2930
- }
2931
- case "stream_event":
2932
- await handleStreamEvent(message, context);
2933
- return null;
2934
- case "user":
2935
- case "assistant": {
2936
- const result = await handleUserAssistantMessage(message, context);
2937
- if (result.error) throw result.error;
2938
- if (result.shouldStop) {
2939
- return { stopReason: "end_turn" };
2940
- }
2941
- return null;
2942
- }
2943
- case "tool_progress":
2944
- case "auth_status":
2945
- case "tool_use_summary":
2946
- return null;
2947
- default:
2948
- unreachable(message, this.logger);
2949
- return null;
2950
- }
2951
- }
2952
3602
  };
2953
3603
 
2954
3604
  // src/adapters/codex/spawn.ts
@@ -3013,7 +3663,7 @@ function spawnCodexProcess(options) {
3013
3663
  detached: process.platform !== "win32"
3014
3664
  });
3015
3665
  child.stderr?.on("data", (data) => {
3016
- logger.error("codex-acp stderr:", data.toString());
3666
+ logger.debug("codex-acp stderr:", data.toString());
3017
3667
  });
3018
3668
  child.on("error", (err) => {
3019
3669
  logger.error("codex-acp process error:", err);
@@ -3575,8 +4225,8 @@ var PostHogAPIClient = class {
3575
4225
  };
3576
4226
 
3577
4227
  // src/session-log-writer.ts
3578
- import fs3 from "fs";
3579
- import path4 from "path";
4228
+ import fs4 from "fs";
4229
+ import path5 from "path";
3580
4230
  var SessionLogWriter = class _SessionLogWriter {
3581
4231
  static FLUSH_DEBOUNCE_MS = 500;
3582
4232
  static FLUSH_MAX_INTERVAL_MS = 5e3;
@@ -3614,13 +4264,13 @@ var SessionLogWriter = class _SessionLogWriter {
3614
4264
  this.sessions.set(sessionId, { context });
3615
4265
  this.lastFlushAttemptTime.set(sessionId, Date.now());
3616
4266
  if (this.localCachePath) {
3617
- const sessionDir = path4.join(
4267
+ const sessionDir = path5.join(
3618
4268
  this.localCachePath,
3619
4269
  "sessions",
3620
4270
  context.runId
3621
4271
  );
3622
4272
  try {
3623
- fs3.mkdirSync(sessionDir, { recursive: true });
4273
+ fs4.mkdirSync(sessionDir, { recursive: true });
3624
4274
  } catch (error) {
3625
4275
  this.logger.warn("Failed to create local cache directory", {
3626
4276
  sessionDir,
@@ -3817,14 +4467,14 @@ var SessionLogWriter = class _SessionLogWriter {
3817
4467
  if (!this.localCachePath) return;
3818
4468
  const session = this.sessions.get(sessionId);
3819
4469
  if (!session) return;
3820
- const logPath = path4.join(
4470
+ const logPath = path5.join(
3821
4471
  this.localCachePath,
3822
4472
  "sessions",
3823
4473
  session.context.runId,
3824
4474
  "logs.ndjson"
3825
4475
  );
3826
4476
  try {
3827
- fs3.appendFileSync(logPath, `${JSON.stringify(entry)}
4477
+ fs4.appendFileSync(logPath, `${JSON.stringify(entry)}
3828
4478
  `);
3829
4479
  } catch (error) {
3830
4480
  this.logger.warn("Failed to write to local cache", {
@@ -3946,6 +4596,9 @@ var Agent = class {
3946
4596
  prUrl
3947
4597
  });
3948
4598
  }
4599
+ getPosthogAPI() {
4600
+ return this.posthogAPI;
4601
+ }
3949
4602
  async flushAllLogs() {
3950
4603
  await this.sessionLogWriter?.flushAll();
3951
4604
  }