@posthog/agent 2.1.131 → 2.1.137

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