bashkit 0.3.1 → 0.3.2

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.
package/dist/index.js CHANGED
@@ -615,6 +615,184 @@ var DEFAULT_CONFIG = {
615
615
  // src/tools/ask-user.ts
616
616
  import { tool, zodSchema } from "ai";
617
617
  import { z } from "zod";
618
+
619
+ // src/utils/debug.ts
620
+ import { appendFileSync } from "node:fs";
621
+ var state = {
622
+ mode: "off",
623
+ logs: [],
624
+ counters: new Map,
625
+ parentStack: []
626
+ };
627
+ var MAX_STRING_LENGTH = 1000;
628
+ var MAX_ARRAY_ITEMS = 10;
629
+ function initDebugMode() {
630
+ const envValue = process.env.BASHKIT_DEBUG;
631
+ if (!envValue) {
632
+ state.mode = "off";
633
+ return;
634
+ }
635
+ if (envValue === "1" || envValue === "stderr") {
636
+ state.mode = "stderr";
637
+ } else if (envValue === "json") {
638
+ state.mode = "json";
639
+ } else if (envValue === "memory") {
640
+ state.mode = "memory";
641
+ } else if (envValue.startsWith("file:")) {
642
+ state.mode = "file";
643
+ state.filePath = envValue.slice(5);
644
+ } else {
645
+ state.mode = "stderr";
646
+ }
647
+ }
648
+ initDebugMode();
649
+ function isDebugEnabled() {
650
+ return state.mode !== "off";
651
+ }
652
+ function generateId(tool) {
653
+ const count = (state.counters.get(tool) || 0) + 1;
654
+ state.counters.set(tool, count);
655
+ return `${tool}-${count}`;
656
+ }
657
+ function truncateString(str) {
658
+ if (str.length <= MAX_STRING_LENGTH)
659
+ return str;
660
+ return `${str.slice(0, MAX_STRING_LENGTH)}... [truncated, ${str.length - MAX_STRING_LENGTH} more chars]`;
661
+ }
662
+ function summarize(data, depth = 0) {
663
+ if (depth > 5)
664
+ return "[nested object]";
665
+ if (data === null || data === undefined)
666
+ return data;
667
+ if (typeof data === "string") {
668
+ return truncateString(data);
669
+ }
670
+ if (typeof data === "number" || typeof data === "boolean") {
671
+ return data;
672
+ }
673
+ if (Array.isArray(data)) {
674
+ const truncated = data.length > MAX_ARRAY_ITEMS;
675
+ const items = data.slice(0, MAX_ARRAY_ITEMS).map((item) => summarize(item, depth + 1));
676
+ if (truncated) {
677
+ return [...items, `[${data.length - MAX_ARRAY_ITEMS} more items]`];
678
+ }
679
+ return items;
680
+ }
681
+ if (typeof data === "object") {
682
+ const result = {};
683
+ for (const [key, value] of Object.entries(data)) {
684
+ result[key] = summarize(value, depth + 1);
685
+ }
686
+ return result;
687
+ }
688
+ return String(data);
689
+ }
690
+ function emitEvent(event) {
691
+ if (state.mode === "off")
692
+ return;
693
+ switch (state.mode) {
694
+ case "memory":
695
+ state.logs.push(event);
696
+ break;
697
+ case "json":
698
+ process.stderr.write(`${JSON.stringify(event)}
699
+ `);
700
+ break;
701
+ case "file":
702
+ if (state.filePath) {
703
+ appendFileSync(state.filePath, `${JSON.stringify(event)}
704
+ `);
705
+ }
706
+ break;
707
+ case "stderr":
708
+ default:
709
+ formatHumanReadable(event);
710
+ break;
711
+ }
712
+ }
713
+ function formatHumanReadable(event) {
714
+ const indent = " ".repeat(state.parentStack.length);
715
+ if (event.event === "start") {
716
+ const inputSummary = event.input ? Object.entries(event.input).map(([k, v]) => `${k}=${JSON.stringify(v)}`).slice(0, 3).join(" ") : "";
717
+ process.stderr.write(`${indent}[bashkit:${event.tool}] → ${inputSummary}
718
+ `);
719
+ } else if (event.event === "end") {
720
+ const summaryStr = event.summary ? Object.entries(event.summary).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ") : "";
721
+ process.stderr.write(`${indent}[bashkit:${event.tool}] ← ${event.duration_ms}ms ${summaryStr}
722
+ `);
723
+ } else if (event.event === "error") {
724
+ process.stderr.write(`${indent}[bashkit:${event.tool}] ✗ ${event.error}
725
+ `);
726
+ }
727
+ }
728
+ function debugStart(tool, input) {
729
+ if (state.mode === "off")
730
+ return "";
731
+ const id = generateId(tool);
732
+ const parent = state.parentStack.length > 0 ? state.parentStack[state.parentStack.length - 1] : undefined;
733
+ const event = {
734
+ id,
735
+ ts: Date.now(),
736
+ tool,
737
+ event: "start",
738
+ input: input ? summarize(input) : undefined,
739
+ parent
740
+ };
741
+ emitEvent(event);
742
+ return id;
743
+ }
744
+ function debugEnd(id, tool, options) {
745
+ if (state.mode === "off" || !id)
746
+ return;
747
+ const event = {
748
+ id,
749
+ ts: Date.now(),
750
+ tool,
751
+ event: "end",
752
+ output: options.output ? summarize(options.output) : undefined,
753
+ summary: options.summary,
754
+ duration_ms: options.duration_ms
755
+ };
756
+ emitEvent(event);
757
+ }
758
+ function debugError(id, tool, error) {
759
+ if (state.mode === "off" || !id)
760
+ return;
761
+ const event = {
762
+ id,
763
+ ts: Date.now(),
764
+ tool,
765
+ event: "error",
766
+ error: error instanceof Error ? error.message : error
767
+ };
768
+ emitEvent(event);
769
+ }
770
+ function pushParent(id) {
771
+ if (state.mode === "off" || !id)
772
+ return;
773
+ state.parentStack.push(id);
774
+ }
775
+ function popParent() {
776
+ if (state.mode === "off")
777
+ return;
778
+ state.parentStack.pop();
779
+ }
780
+ function getDebugLogs() {
781
+ return [...state.logs];
782
+ }
783
+ function clearDebugLogs() {
784
+ state.logs = [];
785
+ state.counters.clear();
786
+ state.parentStack = [];
787
+ }
788
+ function reinitDebugMode() {
789
+ state.logs = [];
790
+ state.counters.clear();
791
+ state.parentStack = [];
792
+ initDebugMode();
793
+ }
794
+
795
+ // src/tools/ask-user.ts
618
796
  var questionOptionSchema = z.object({
619
797
  label: z.string().describe("The display text for this option. Should be concise (1-5 words). Add '(Recommended)' suffix for suggested options."),
620
798
  description: z.string().optional().describe("Explanation of what this option means or its implications.")
@@ -664,22 +842,46 @@ function createAskUserTool(config) {
664
842
  description: ASK_USER_DESCRIPTION,
665
843
  inputSchema: zodSchema(askUserInputSchema),
666
844
  execute: async (input) => {
845
+ const startTime = performance.now();
846
+ const debugId = isDebugEnabled() ? debugStart("ask-user", {
847
+ hasQuestion: !!input.question,
848
+ questionCount: input.questions?.length ?? 0,
849
+ question: input.question ? input.question.length > 100 ? `${input.question.slice(0, 100)}...` : input.question : undefined
850
+ }) : "";
667
851
  try {
668
852
  if (!input.question && !input.questions) {
669
- return {
670
- error: "Either 'question' or 'questions' must be provided"
671
- };
853
+ const error2 = "Either 'question' or 'questions' must be provided";
854
+ if (debugId)
855
+ debugError(debugId, "ask-user", error2);
856
+ return { error: error2 };
672
857
  }
673
858
  if (input.questions && input.questions.length > 0) {
674
859
  if (normalizedConfig.onStructuredQuestions) {
675
860
  const answers = await normalizedConfig.onStructuredQuestions(input.questions);
676
861
  const firstKey = Object.keys(answers)[0];
677
862
  const firstAnswer = answers[firstKey];
863
+ const durationMs2 = Math.round(performance.now() - startTime);
864
+ if (debugId) {
865
+ debugEnd(debugId, "ask-user", {
866
+ summary: {
867
+ type: "structured",
868
+ answerCount: Object.keys(answers).length
869
+ },
870
+ duration_ms: durationMs2
871
+ });
872
+ }
678
873
  return {
679
874
  answer: Array.isArray(firstAnswer) ? firstAnswer.join(", ") : firstAnswer,
680
875
  answers
681
876
  };
682
877
  }
878
+ const durationMs = Math.round(performance.now() - startTime);
879
+ if (debugId) {
880
+ debugEnd(debugId, "ask-user", {
881
+ summary: { type: "structured", awaiting: true },
882
+ duration_ms: durationMs
883
+ });
884
+ }
683
885
  return {
684
886
  questions: input.questions,
685
887
  awaiting_response: true
@@ -688,18 +890,36 @@ function createAskUserTool(config) {
688
890
  if (input.question) {
689
891
  if (normalizedConfig.onQuestion) {
690
892
  const answer = await normalizedConfig.onQuestion(input.question);
893
+ const durationMs2 = Math.round(performance.now() - startTime);
894
+ if (debugId) {
895
+ debugEnd(debugId, "ask-user", {
896
+ summary: { type: "simple", hasAnswer: true },
897
+ duration_ms: durationMs2
898
+ });
899
+ }
691
900
  return { answer };
692
901
  }
902
+ const durationMs = Math.round(performance.now() - startTime);
903
+ if (debugId) {
904
+ debugEnd(debugId, "ask-user", {
905
+ summary: { type: "simple", awaiting: true },
906
+ duration_ms: durationMs
907
+ });
908
+ }
693
909
  return {
694
910
  question: input.question,
695
911
  awaiting_response: true
696
912
  };
697
913
  }
698
- return { error: "No question provided" };
914
+ const error = "No question provided";
915
+ if (debugId)
916
+ debugError(debugId, "ask-user", error);
917
+ return { error };
699
918
  } catch (error) {
700
- return {
701
- error: error instanceof Error ? error.message : "Unknown error"
702
- };
919
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
920
+ if (debugId)
921
+ debugError(debugId, "ask-user", errorMessage);
922
+ return { error: errorMessage };
703
923
  }
704
924
  }
705
925
  });
@@ -763,12 +983,18 @@ function createBashTool(sandbox, config) {
763
983
  description: _description,
764
984
  run_in_background: _run_in_background
765
985
  }) => {
986
+ const startTime = performance.now();
987
+ const debugId = isDebugEnabled() ? debugStart("bash", {
988
+ command: command.length > 200 ? `${command.slice(0, 200)}...` : command,
989
+ timeout
990
+ }) : "";
766
991
  if (config?.blockedCommands) {
767
992
  for (const blocked of config.blockedCommands) {
768
993
  if (command.includes(blocked)) {
769
- return {
770
- error: `Command blocked: contains '${blocked}'`
771
- };
994
+ const error = `Command blocked: contains '${blocked}'`;
995
+ if (debugId)
996
+ debugError(debugId, "bash", error);
997
+ return { error };
772
998
  }
773
999
  }
774
1000
  }
@@ -787,6 +1013,18 @@ function createBashTool(sandbox, config) {
787
1013
  stderr = stderr.slice(0, maxOutputLength) + `
788
1014
  [output truncated, ${stderr.length - maxOutputLength} chars omitted]`;
789
1015
  }
1016
+ const durationMs = Math.round(performance.now() - startTime);
1017
+ if (debugId) {
1018
+ debugEnd(debugId, "bash", {
1019
+ summary: {
1020
+ exitCode: result.exitCode,
1021
+ stdoutLen: result.stdout.length,
1022
+ stderrLen: result.stderr.length,
1023
+ interrupted: result.interrupted
1024
+ },
1025
+ duration_ms: durationMs
1026
+ });
1027
+ }
790
1028
  return {
791
1029
  stdout,
792
1030
  stderr,
@@ -795,9 +1033,10 @@ function createBashTool(sandbox, config) {
795
1033
  duration_ms: result.durationMs
796
1034
  };
797
1035
  } catch (error) {
798
- return {
799
- error: error instanceof Error ? error.message : "Unknown error"
800
- };
1036
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1037
+ if (debugId)
1038
+ debugError(debugId, "bash", errorMessage);
1039
+ return { error: errorMessage };
801
1040
  }
802
1041
  }
803
1042
  });
@@ -842,29 +1081,48 @@ function createEditTool(sandbox, config) {
842
1081
  new_string,
843
1082
  replace_all = false
844
1083
  }) => {
1084
+ const startTime = performance.now();
1085
+ const debugId = isDebugEnabled() ? debugStart("edit", {
1086
+ file_path,
1087
+ old_string: old_string.length > 100 ? `${old_string.slice(0, 100)}...` : old_string,
1088
+ replace_all
1089
+ }) : "";
845
1090
  if (old_string === new_string) {
846
- return { error: "old_string and new_string must be different" };
1091
+ const error = "old_string and new_string must be different";
1092
+ if (debugId)
1093
+ debugError(debugId, "edit", error);
1094
+ return { error };
847
1095
  }
848
1096
  if (config?.allowedPaths) {
849
1097
  const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
850
1098
  if (!isAllowed) {
851
- return { error: `Path not allowed: ${file_path}` };
1099
+ const error = `Path not allowed: ${file_path}`;
1100
+ if (debugId)
1101
+ debugError(debugId, "edit", error);
1102
+ return { error };
852
1103
  }
853
1104
  }
854
1105
  try {
855
1106
  const exists = await sandbox.fileExists(file_path);
856
1107
  if (!exists) {
857
- return { error: `File not found: ${file_path}` };
1108
+ const error = `File not found: ${file_path}`;
1109
+ if (debugId)
1110
+ debugError(debugId, "edit", error);
1111
+ return { error };
858
1112
  }
859
1113
  const content = await sandbox.readFile(file_path);
860
1114
  const occurrences = content.split(old_string).length - 1;
861
1115
  if (occurrences === 0) {
862
- return { error: `String not found in file: "${old_string}"` };
1116
+ const error = `String not found in file: "${old_string}"`;
1117
+ if (debugId)
1118
+ debugError(debugId, "edit", error);
1119
+ return { error };
863
1120
  }
864
1121
  if (!replace_all && occurrences > 1) {
865
- return {
866
- error: `String appears ${occurrences} times in file. Use replace_all=true to replace all, or provide a more unique string.`
867
- };
1122
+ const error = `String appears ${occurrences} times in file. Use replace_all=true to replace all, or provide a more unique string.`;
1123
+ if (debugId)
1124
+ debugError(debugId, "edit", error);
1125
+ return { error };
868
1126
  }
869
1127
  let newContent;
870
1128
  let replacements;
@@ -876,15 +1134,23 @@ function createEditTool(sandbox, config) {
876
1134
  replacements = 1;
877
1135
  }
878
1136
  await sandbox.writeFile(file_path, newContent);
1137
+ const durationMs = Math.round(performance.now() - startTime);
1138
+ if (debugId) {
1139
+ debugEnd(debugId, "edit", {
1140
+ summary: { replacements },
1141
+ duration_ms: durationMs
1142
+ });
1143
+ }
879
1144
  return {
880
1145
  message: `Successfully edited ${file_path}`,
881
1146
  file_path,
882
1147
  replacements
883
1148
  };
884
1149
  } catch (error) {
885
- return {
886
- error: error instanceof Error ? error.message : "Unknown error"
887
- };
1150
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1151
+ if (debugId)
1152
+ debugError(debugId, "edit", errorMessage);
1153
+ return { error: errorMessage };
888
1154
  }
889
1155
  }
890
1156
  });
@@ -951,33 +1217,44 @@ In plan mode, you'll:
951
1217
  - This tool REQUIRES user approval - they must consent to entering plan mode
952
1218
  - If unsure whether to use it, err on the side of planning - it's better to get alignment upfront than to redo work
953
1219
  - Users appreciate being consulted before significant changes`;
954
- function createEnterPlanModeTool(state, onEnter) {
1220
+ function createEnterPlanModeTool(state2, onEnter) {
955
1221
  return tool4({
956
1222
  description: ENTER_PLAN_MODE_DESCRIPTION,
957
1223
  inputSchema: zodSchema4(enterPlanModeInputSchema),
958
1224
  execute: async ({
959
1225
  reason
960
1226
  }) => {
1227
+ const startTime = performance.now();
1228
+ const debugId = isDebugEnabled() ? debugStart("enter-plan-mode", { reason }) : "";
961
1229
  try {
962
- if (state.isActive) {
963
- return {
964
- error: "Already in planning mode. Use ExitPlanMode to exit."
965
- };
1230
+ if (state2.isActive) {
1231
+ const error = "Already in planning mode. Use ExitPlanMode to exit.";
1232
+ if (debugId)
1233
+ debugError(debugId, "enter-plan-mode", error);
1234
+ return { error };
966
1235
  }
967
- state.isActive = true;
968
- state.enteredAt = new Date;
969
- state.reason = reason;
1236
+ state2.isActive = true;
1237
+ state2.enteredAt = new Date;
1238
+ state2.reason = reason;
970
1239
  if (onEnter) {
971
1240
  await onEnter(reason);
972
1241
  }
1242
+ const durationMs = Math.round(performance.now() - startTime);
1243
+ if (debugId) {
1244
+ debugEnd(debugId, "enter-plan-mode", {
1245
+ summary: { mode: "planning" },
1246
+ duration_ms: durationMs
1247
+ });
1248
+ }
973
1249
  return {
974
1250
  message: `Entered planning mode: ${reason}. Use Read, Grep, and Glob to explore. Call ExitPlanMode when ready with a plan.`,
975
1251
  mode: "planning"
976
1252
  };
977
1253
  } catch (error) {
978
- return {
979
- error: error instanceof Error ? error.message : "Unknown error"
980
- };
1254
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1255
+ if (debugId)
1256
+ debugError(debugId, "enter-plan-mode", errorMessage);
1257
+ return { error: errorMessage };
981
1258
  }
982
1259
  }
983
1260
  });
@@ -1017,19 +1294,32 @@ function createExitPlanModeTool(onPlanSubmit) {
1017
1294
  execute: async ({
1018
1295
  plan
1019
1296
  }) => {
1297
+ const startTime = performance.now();
1298
+ const debugId = isDebugEnabled() ? debugStart("exit-plan-mode", {
1299
+ planLength: plan.length,
1300
+ planPreview: plan.length > 200 ? `${plan.slice(0, 200)}...` : plan
1301
+ }) : "";
1020
1302
  try {
1021
1303
  let approved;
1022
1304
  if (onPlanSubmit) {
1023
1305
  approved = await onPlanSubmit(plan);
1024
1306
  }
1307
+ const durationMs = Math.round(performance.now() - startTime);
1308
+ if (debugId) {
1309
+ debugEnd(debugId, "exit-plan-mode", {
1310
+ summary: { approved },
1311
+ duration_ms: durationMs
1312
+ });
1313
+ }
1025
1314
  return {
1026
1315
  message: approved ? "Plan approved, proceeding with execution" : "Plan submitted for review",
1027
1316
  approved
1028
1317
  };
1029
1318
  } catch (error) {
1030
- return {
1031
- error: error instanceof Error ? error.message : "Unknown error"
1032
- };
1319
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1320
+ if (debugId)
1321
+ debugError(debugId, "exit-plan-mode", errorMessage);
1322
+ return { error: errorMessage };
1033
1323
  }
1034
1324
  }
1035
1325
  });
@@ -1062,28 +1352,47 @@ function createGlobTool(sandbox, config) {
1062
1352
  path
1063
1353
  }) => {
1064
1354
  const searchPath = path || ".";
1355
+ const startTime = performance.now();
1356
+ const debugId = isDebugEnabled() ? debugStart("glob", { pattern, path: searchPath }) : "";
1065
1357
  if (config?.allowedPaths) {
1066
1358
  const isAllowed = config.allowedPaths.some((allowed) => searchPath.startsWith(allowed));
1067
1359
  if (!isAllowed) {
1068
- return { error: `Path not allowed: ${searchPath}` };
1360
+ const error = `Path not allowed: ${searchPath}`;
1361
+ if (debugId)
1362
+ debugError(debugId, "glob", error);
1363
+ return { error };
1069
1364
  }
1070
1365
  }
1071
1366
  try {
1072
- const result = await sandbox.exec(`find ${searchPath} -type f -name "${pattern}" 2>/dev/null | head -1000`, { timeout: config?.timeout });
1367
+ const findFlag = pattern.includes("/") ? "-path" : "-name";
1368
+ const findPattern = pattern.includes("/") && !pattern.startsWith("*") ? `*/${pattern}` : pattern;
1369
+ const result = await sandbox.exec(`find ${searchPath} -type f ${findFlag} "${findPattern}" 2>/dev/null | head -1000`, { timeout: config?.timeout });
1073
1370
  if (result.exitCode !== 0 && result.stderr) {
1074
- return { error: result.stderr };
1371
+ const error = result.stderr;
1372
+ if (debugId)
1373
+ debugError(debugId, "glob", error);
1374
+ return { error };
1075
1375
  }
1076
1376
  const matches = result.stdout.split(`
1077
1377
  `).filter(Boolean).map((p) => p.trim());
1378
+ const durationMs = Math.round(performance.now() - startTime);
1379
+ if (debugId) {
1380
+ debugEnd(debugId, "glob", {
1381
+ summary: { count: matches.length },
1382
+ output: matches.slice(0, 10),
1383
+ duration_ms: durationMs
1384
+ });
1385
+ }
1078
1386
  return {
1079
1387
  matches,
1080
1388
  count: matches.length,
1081
1389
  search_path: searchPath
1082
1390
  };
1083
1391
  } catch (error) {
1084
- return {
1085
- error: error instanceof Error ? error.message : "Unknown error"
1086
- };
1392
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1393
+ if (debugId)
1394
+ debugError(debugId, "glob", errorMessage);
1395
+ return { error: errorMessage };
1087
1396
  }
1088
1397
  }
1089
1398
  });
@@ -1150,17 +1459,31 @@ function createGrepTool(sandbox, config) {
1150
1459
  multiline
1151
1460
  } = input;
1152
1461
  const searchPath = path || ".";
1462
+ const startTime = performance.now();
1463
+ const debugId = isDebugEnabled() ? debugStart("grep", {
1464
+ pattern,
1465
+ path: searchPath,
1466
+ output_mode,
1467
+ glob,
1468
+ type,
1469
+ caseInsensitive,
1470
+ multiline
1471
+ }) : "";
1153
1472
  if (config?.allowedPaths) {
1154
1473
  const isAllowed = config.allowedPaths.some((allowed) => searchPath.startsWith(allowed));
1155
1474
  if (!isAllowed) {
1156
- return { error: `Path not allowed: ${searchPath}` };
1475
+ const error = `Path not allowed: ${searchPath}`;
1476
+ if (debugId)
1477
+ debugError(debugId, "grep", error);
1478
+ return { error };
1157
1479
  }
1158
1480
  }
1159
1481
  try {
1160
1482
  if (!sandbox.rgPath) {
1161
- return {
1162
- error: "Ripgrep not available. Call ensureSandboxTools(sandbox) before using Grep with remote sandboxes."
1163
- };
1483
+ const error = "Ripgrep not available. Call ensureSandboxTools(sandbox) before using Grep with remote sandboxes.";
1484
+ if (debugId)
1485
+ debugError(debugId, "grep", error);
1486
+ return { error };
1164
1487
  }
1165
1488
  const cmd = buildRipgrepCommand({
1166
1489
  rgPath: sandbox.rgPath,
@@ -1176,17 +1499,48 @@ function createGrepTool(sandbox, config) {
1176
1499
  multiline
1177
1500
  });
1178
1501
  const result = await sandbox.exec(cmd, { timeout: config?.timeout });
1502
+ const durationMs = Math.round(performance.now() - startTime);
1503
+ let output;
1179
1504
  if (output_mode === "files_with_matches") {
1180
- return parseFilesOutput(result.stdout);
1505
+ output = parseFilesOutput(result.stdout);
1506
+ if (debugId) {
1507
+ debugEnd(debugId, "grep", {
1508
+ summary: {
1509
+ fileCount: output.count,
1510
+ exitCode: result.exitCode
1511
+ },
1512
+ duration_ms: durationMs
1513
+ });
1514
+ }
1181
1515
  } else if (output_mode === "count") {
1182
- return parseCountOutput(result.stdout);
1516
+ output = parseCountOutput(result.stdout);
1517
+ if (debugId) {
1518
+ debugEnd(debugId, "grep", {
1519
+ summary: {
1520
+ total: output.total,
1521
+ exitCode: result.exitCode
1522
+ },
1523
+ duration_ms: durationMs
1524
+ });
1525
+ }
1183
1526
  } else {
1184
- return parseContentOutput(result.stdout, head_limit, offset);
1527
+ output = parseContentOutput(result.stdout, head_limit, offset);
1528
+ if (debugId) {
1529
+ debugEnd(debugId, "grep", {
1530
+ summary: {
1531
+ matchCount: output.total_matches,
1532
+ exitCode: result.exitCode
1533
+ },
1534
+ duration_ms: durationMs
1535
+ });
1536
+ }
1185
1537
  }
1538
+ return output;
1186
1539
  } catch (error) {
1187
- return {
1188
- error: error instanceof Error ? error.message : "Unknown error"
1189
- };
1540
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1541
+ if (debugId)
1542
+ debugError(debugId, "grep", errorMessage);
1543
+ return { error: errorMessage };
1190
1544
  }
1191
1545
  }
1192
1546
  });
@@ -1377,20 +1731,35 @@ function createReadTool(sandbox, config) {
1377
1731
  offset,
1378
1732
  limit
1379
1733
  }) => {
1734
+ const startTime = performance.now();
1735
+ const debugId = isDebugEnabled() ? debugStart("read", { file_path, offset, limit }) : "";
1380
1736
  if (config?.allowedPaths) {
1381
1737
  const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
1382
1738
  if (!isAllowed) {
1383
- return { error: `Path not allowed: ${file_path}` };
1739
+ const error = `Path not allowed: ${file_path}`;
1740
+ if (debugId)
1741
+ debugError(debugId, "read", error);
1742
+ return { error };
1384
1743
  }
1385
1744
  }
1386
1745
  try {
1387
1746
  const exists = await sandbox.fileExists(file_path);
1388
1747
  if (!exists) {
1389
- return { error: `Path not found: ${file_path}` };
1748
+ const error = `Path not found: ${file_path}`;
1749
+ if (debugId)
1750
+ debugError(debugId, "read", error);
1751
+ return { error };
1390
1752
  }
1391
1753
  const isDir = await sandbox.isDirectory(file_path);
1392
1754
  if (isDir) {
1393
1755
  const entries = await sandbox.readDir(file_path);
1756
+ const durationMs2 = Math.round(performance.now() - startTime);
1757
+ if (debugId) {
1758
+ debugEnd(debugId, "read", {
1759
+ summary: { type: "directory", count: entries.length },
1760
+ duration_ms: durationMs2
1761
+ });
1762
+ }
1394
1763
  return {
1395
1764
  type: "directory",
1396
1765
  entries,
@@ -1416,9 +1785,10 @@ function createReadTool(sandbox, config) {
1416
1785
  "dylib"
1417
1786
  ];
1418
1787
  if (binaryExtensions.includes(ext || "")) {
1419
- return {
1420
- error: `Cannot read binary file: ${file_path} (file exists, ${content.length} bytes). Use appropriate tools to process ${ext?.toUpperCase()} files (e.g., Python scripts for PDFs).`
1421
- };
1788
+ const error = `Cannot read binary file: ${file_path} (file exists, ${content.length} bytes). Use appropriate tools to process ${ext?.toUpperCase()} files (e.g., Python scripts for PDFs).`;
1789
+ if (debugId)
1790
+ debugError(debugId, "read", error);
1791
+ return { error };
1422
1792
  }
1423
1793
  }
1424
1794
  const allLines = content.split(`
@@ -1426,9 +1796,10 @@ function createReadTool(sandbox, config) {
1426
1796
  const totalLines = allLines.length;
1427
1797
  const maxLinesWithoutLimit = config?.maxFileSize || 500;
1428
1798
  if (!limit && totalLines > maxLinesWithoutLimit) {
1429
- return {
1430
- error: `File is large (${totalLines} lines). Use 'offset' and 'limit' to read in chunks. Example: offset=1, limit=100 for first 100 lines.`
1431
- };
1799
+ const error = `File is large (${totalLines} lines). Use 'offset' and 'limit' to read in chunks. Example: offset=1, limit=100 for first 100 lines.`;
1800
+ if (debugId)
1801
+ debugError(debugId, "read", error);
1802
+ return { error };
1432
1803
  }
1433
1804
  const startLine = offset ? offset - 1 : 0;
1434
1805
  const endLine = limit ? startLine + limit : allLines.length;
@@ -1437,6 +1808,18 @@ function createReadTool(sandbox, config) {
1437
1808
  line_number: startLine + i + 1,
1438
1809
  content: line
1439
1810
  }));
1811
+ const durationMs = Math.round(performance.now() - startTime);
1812
+ if (debugId) {
1813
+ debugEnd(debugId, "read", {
1814
+ summary: {
1815
+ type: "text",
1816
+ totalLines,
1817
+ returnedLines: lines.length,
1818
+ bytes: content.length
1819
+ },
1820
+ duration_ms: durationMs
1821
+ });
1822
+ }
1440
1823
  return {
1441
1824
  type: "text",
1442
1825
  content: selectedLines.join(`
@@ -1445,9 +1828,10 @@ function createReadTool(sandbox, config) {
1445
1828
  total_lines: totalLines
1446
1829
  };
1447
1830
  } catch (error) {
1448
- return {
1449
- error: error instanceof Error ? error.message : "Unknown error"
1450
- };
1831
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1832
+ if (debugId)
1833
+ debugError(debugId, "read", errorMessage);
1834
+ return { error: errorMessage };
1451
1835
  }
1452
1836
  }
1453
1837
  });
@@ -1481,22 +1865,29 @@ function createSkillTool(config) {
1481
1865
  execute: async ({
1482
1866
  name
1483
1867
  }) => {
1868
+ const startTime = performance.now();
1869
+ const debugId = isDebugEnabled() ? debugStart("skill", { name, availableSkills: Object.keys(skills) }) : "";
1484
1870
  try {
1485
1871
  const skill = skills[name];
1486
1872
  if (!skill) {
1487
1873
  const available = Object.keys(skills);
1488
1874
  if (available.length === 0) {
1489
- return { error: "No skills are available." };
1875
+ const error2 = "No skills are available.";
1876
+ if (debugId)
1877
+ debugError(debugId, "skill", error2);
1878
+ return { error: error2 };
1490
1879
  }
1491
- return {
1492
- error: `Skill '${name}' not found. Available skills: ${available.join(", ")}`
1493
- };
1880
+ const error = `Skill '${name}' not found. Available skills: ${available.join(", ")}`;
1881
+ if (debugId)
1882
+ debugError(debugId, "skill", error);
1883
+ return { error };
1494
1884
  }
1495
1885
  let instructions;
1496
1886
  if (!sandbox) {
1497
- return {
1498
- error: `Cannot load skill '${name}': no sandbox provided to read ${skill.path}`
1499
- };
1887
+ const error = `Cannot load skill '${name}': no sandbox provided to read ${skill.path}`;
1888
+ if (debugId)
1889
+ debugError(debugId, "skill", error);
1890
+ return { error };
1500
1891
  }
1501
1892
  const content = await sandbox.readFile(skill.path);
1502
1893
  const frontmatterEnd = content.indexOf(`
@@ -1509,6 +1900,17 @@ function createSkillTool(config) {
1509
1900
  if (onActivate) {
1510
1901
  await onActivate(skill, instructions);
1511
1902
  }
1903
+ const durationMs = Math.round(performance.now() - startTime);
1904
+ if (debugId) {
1905
+ debugEnd(debugId, "skill", {
1906
+ summary: {
1907
+ skillName: skill.name,
1908
+ instructionLength: instructions.length,
1909
+ hasAllowedTools: !!skill.allowedTools
1910
+ },
1911
+ duration_ms: durationMs
1912
+ });
1913
+ }
1512
1914
  return {
1513
1915
  name: skill.name,
1514
1916
  instructions,
@@ -1516,9 +1918,10 @@ function createSkillTool(config) {
1516
1918
  message: skill.allowedTools ? `Skill '${name}' activated. Restricted to tools: ${skill.allowedTools.join(", ")}` : `Skill '${name}' activated. Follow the instructions below.`
1517
1919
  };
1518
1920
  } catch (error) {
1519
- return {
1520
- error: error instanceof Error ? error.message : "Unknown error"
1521
- };
1921
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1922
+ if (debugId)
1923
+ debugError(debugId, "skill", errorMessage);
1924
+ return { error: errorMessage };
1522
1925
  }
1523
1926
  }
1524
1927
  });
@@ -1613,6 +2016,11 @@ function createWebFetchTool(config) {
1613
2016
  providerOptions,
1614
2017
  execute: async (input) => {
1615
2018
  const { url, prompt } = input;
2019
+ const startTime = performance.now();
2020
+ const debugId = isDebugEnabled() ? debugStart("web-fetch", {
2021
+ url,
2022
+ prompt: prompt.length > 200 ? `${prompt.slice(0, 200)}...` : prompt
2023
+ }) : "";
1616
2024
  try {
1617
2025
  const { content, finalUrl } = await fetchContent(url, apiKey, provider);
1618
2026
  const result = await generateText({
@@ -1623,6 +2031,16 @@ Content from ${url}:
1623
2031
 
1624
2032
  ${content}`
1625
2033
  });
2034
+ const durationMs = Math.round(performance.now() - startTime);
2035
+ if (debugId) {
2036
+ debugEnd(debugId, "web-fetch", {
2037
+ summary: {
2038
+ contentLength: content.length,
2039
+ responseLength: result.text.length
2040
+ },
2041
+ duration_ms: durationMs
2042
+ });
2043
+ }
1626
2044
  return {
1627
2045
  response: result.text,
1628
2046
  url,
@@ -1632,15 +2050,18 @@ ${content}`
1632
2050
  if (error && typeof error === "object" && "status" in error) {
1633
2051
  const statusCode = error.status;
1634
2052
  const message = error.message || "API request failed";
2053
+ if (debugId)
2054
+ debugError(debugId, "web-fetch", `${message} (status: ${statusCode})`);
1635
2055
  return {
1636
2056
  error: message,
1637
2057
  status_code: statusCode,
1638
2058
  retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
1639
2059
  };
1640
2060
  }
1641
- return {
1642
- error: error instanceof Error ? error.message : "Unknown error"
1643
- };
2061
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2062
+ if (debugId)
2063
+ debugError(debugId, "web-fetch", errorMessage);
2064
+ return { error: errorMessage };
1644
2065
  }
1645
2066
  }
1646
2067
  });
@@ -1736,12 +2157,22 @@ function createWebSearchTool(config) {
1736
2157
  providerOptions,
1737
2158
  execute: async (input) => {
1738
2159
  const { query, allowed_domains, blocked_domains } = input;
2160
+ const startTime = performance.now();
2161
+ const debugId = isDebugEnabled() ? debugStart("web-search", { query, allowed_domains, blocked_domains }) : "";
1739
2162
  try {
1740
2163
  const results = await searchContent(apiKey, provider, {
1741
2164
  query,
1742
2165
  allowedDomains: allowed_domains,
1743
2166
  blockedDomains: blocked_domains
1744
2167
  });
2168
+ const durationMs = Math.round(performance.now() - startTime);
2169
+ if (debugId) {
2170
+ debugEnd(debugId, "web-search", {
2171
+ summary: { resultCount: results.length },
2172
+ output: results.slice(0, 5).map((r) => ({ title: r.title, url: r.url })),
2173
+ duration_ms: durationMs
2174
+ });
2175
+ }
1745
2176
  return {
1746
2177
  results,
1747
2178
  total_results: results.length,
@@ -1751,15 +2182,18 @@ function createWebSearchTool(config) {
1751
2182
  if (error && typeof error === "object" && "status" in error) {
1752
2183
  const statusCode = error.status;
1753
2184
  const message = error.message || "API request failed";
2185
+ if (debugId)
2186
+ debugError(debugId, "web-search", `${message} (status: ${statusCode})`);
1754
2187
  return {
1755
2188
  error: message,
1756
2189
  status_code: statusCode,
1757
2190
  retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
1758
2191
  };
1759
2192
  }
1760
- return {
1761
- error: error instanceof Error ? error.message : "Unknown error"
1762
- };
2193
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2194
+ if (debugId)
2195
+ debugError(debugId, "web-search", errorMessage);
2196
+ return { error: errorMessage };
1763
2197
  }
1764
2198
  }
1765
2199
  });
@@ -1795,29 +2229,43 @@ function createWriteTool(sandbox, config) {
1795
2229
  file_path,
1796
2230
  content
1797
2231
  }) => {
2232
+ const startTime = performance.now();
1798
2233
  const byteLength = Buffer.byteLength(content, "utf-8");
2234
+ const debugId = isDebugEnabled() ? debugStart("write", { file_path, contentLength: byteLength }) : "";
1799
2235
  if (config?.maxFileSize && byteLength > config.maxFileSize) {
1800
- return {
1801
- error: `File content exceeds maximum size of ${config.maxFileSize} bytes (got ${byteLength})`
1802
- };
2236
+ const error = `File content exceeds maximum size of ${config.maxFileSize} bytes (got ${byteLength})`;
2237
+ if (debugId)
2238
+ debugError(debugId, "write", error);
2239
+ return { error };
1803
2240
  }
1804
2241
  if (config?.allowedPaths) {
1805
2242
  const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
1806
2243
  if (!isAllowed) {
1807
- return { error: `Path not allowed: ${file_path}` };
2244
+ const error = `Path not allowed: ${file_path}`;
2245
+ if (debugId)
2246
+ debugError(debugId, "write", error);
2247
+ return { error };
1808
2248
  }
1809
2249
  }
1810
2250
  try {
1811
2251
  await sandbox.writeFile(file_path, content);
2252
+ const durationMs = Math.round(performance.now() - startTime);
2253
+ if (debugId) {
2254
+ debugEnd(debugId, "write", {
2255
+ summary: { bytes_written: byteLength },
2256
+ duration_ms: durationMs
2257
+ });
2258
+ }
1812
2259
  return {
1813
2260
  message: `Successfully wrote to ${file_path}`,
1814
2261
  bytes_written: byteLength,
1815
2262
  file_path
1816
2263
  };
1817
2264
  } catch (error) {
1818
- return {
1819
- error: error instanceof Error ? error.message : "Unknown error"
1820
- };
2265
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2266
+ if (debugId)
2267
+ debugError(debugId, "write", errorMessage);
2268
+ return { error: errorMessage };
1821
2269
  }
1822
2270
  }
1823
2271
  });
@@ -1864,16 +2312,22 @@ var eventCounter = 0;
1864
2312
  function generateEventId() {
1865
2313
  return `subagent-${Date.now()}-${++eventCounter}`;
1866
2314
  }
1867
- function filterTools(allTools, allowedTools) {
1868
- if (!allowedTools)
1869
- return allTools;
1870
- const filtered = {};
1871
- for (const name of allowedTools) {
1872
- if (allTools[name]) {
1873
- filtered[name] = allTools[name];
2315
+ function filterTools(allTools, allowedTools, additionalTools) {
2316
+ let result;
2317
+ if (allowedTools) {
2318
+ result = {};
2319
+ for (const name of allowedTools) {
2320
+ if (allTools[name]) {
2321
+ result[name] = allTools[name];
2322
+ }
1874
2323
  }
2324
+ } else {
2325
+ result = allTools;
2326
+ }
2327
+ if (additionalTools) {
2328
+ result = { ...result, ...additionalTools };
1875
2329
  }
1876
- return filtered;
2330
+ return result;
1877
2331
  }
1878
2332
  function createTaskTool(config) {
1879
2333
  const {
@@ -1895,10 +2349,20 @@ function createTaskTool(config) {
1895
2349
  tools: customTools
1896
2350
  }) => {
1897
2351
  const startTime = performance.now();
2352
+ const typeConfig = subagentTypes[subagent_type] || {};
2353
+ const debugId = isDebugEnabled() ? debugStart("task", {
2354
+ subagent_type,
2355
+ description,
2356
+ tools: [
2357
+ ...customTools ?? typeConfig.tools ?? Object.keys(allTools),
2358
+ ...Object.keys(typeConfig.additionalTools ?? {})
2359
+ ]
2360
+ }) : "";
2361
+ if (debugId)
2362
+ pushParent(debugId);
1898
2363
  try {
1899
- const typeConfig = subagentTypes[subagent_type] || {};
1900
2364
  const model = typeConfig.model || defaultModel;
1901
- const tools = filterTools(allTools, customTools ?? typeConfig.tools);
2365
+ const tools = filterTools(allTools, customTools ?? typeConfig.tools, typeConfig.additionalTools);
1902
2366
  const systemPrompt = system_prompt ?? typeConfig.systemPrompt;
1903
2367
  const commonOptions = {
1904
2368
  model,
@@ -1969,6 +2433,19 @@ function createTaskTool(config) {
1969
2433
  }
1970
2434
  });
1971
2435
  const durationMs2 = Math.round(performance.now() - startTime);
2436
+ if (debugId) {
2437
+ popParent();
2438
+ debugEnd(debugId, "task", {
2439
+ summary: {
2440
+ tokens: {
2441
+ input: usage2.inputTokens,
2442
+ output: usage2.outputTokens
2443
+ },
2444
+ steps: response.messages?.length
2445
+ },
2446
+ duration_ms: durationMs2
2447
+ });
2448
+ }
1972
2449
  return {
1973
2450
  result: text,
1974
2451
  usage: usage2.inputTokens !== undefined && usage2.outputTokens !== undefined ? {
@@ -1996,6 +2473,19 @@ function createTaskTool(config) {
1996
2473
  input_tokens: result.usage.inputTokens,
1997
2474
  output_tokens: result.usage.outputTokens
1998
2475
  } : undefined;
2476
+ if (debugId) {
2477
+ popParent();
2478
+ debugEnd(debugId, "task", {
2479
+ summary: {
2480
+ tokens: {
2481
+ input: result.usage.inputTokens,
2482
+ output: result.usage.outputTokens
2483
+ },
2484
+ steps: result.steps?.length
2485
+ },
2486
+ duration_ms: durationMs
2487
+ });
2488
+ }
1999
2489
  return {
2000
2490
  result: result.text,
2001
2491
  usage,
@@ -2005,6 +2495,10 @@ function createTaskTool(config) {
2005
2495
  };
2006
2496
  } catch (error) {
2007
2497
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
2498
+ if (debugId) {
2499
+ popParent();
2500
+ debugError(debugId, "task", errorMessage);
2501
+ }
2008
2502
  return { error: errorMessage };
2009
2503
  }
2010
2504
  }
@@ -2052,15 +2546,22 @@ var TODO_WRITE_DESCRIPTION = `Use this tool to create and manage a structured ta
2052
2546
  - Keep exactly ONE task in_progress at any time
2053
2547
  - ONLY mark completed when FULLY accomplished
2054
2548
  - If blocked/errors, keep in_progress and create new task for the blocker`;
2055
- function createTodoWriteTool(state, onUpdate) {
2549
+ function createTodoWriteTool(state2, onUpdate) {
2056
2550
  return tool14({
2057
2551
  description: TODO_WRITE_DESCRIPTION,
2058
2552
  inputSchema: zodSchema14(todoWriteInputSchema),
2059
2553
  execute: async ({
2060
2554
  todos
2061
2555
  }) => {
2556
+ const startTime = performance.now();
2557
+ const debugId = isDebugEnabled() ? debugStart("todo-write", {
2558
+ todoCount: todos.length,
2559
+ pending: todos.filter((t) => t.status === "pending").length,
2560
+ in_progress: todos.filter((t) => t.status === "in_progress").length,
2561
+ completed: todos.filter((t) => t.status === "completed").length
2562
+ }) : "";
2062
2563
  try {
2063
- state.todos = todos;
2564
+ state2.todos = todos;
2064
2565
  if (onUpdate) {
2065
2566
  onUpdate(todos);
2066
2567
  }
@@ -2070,14 +2571,22 @@ function createTodoWriteTool(state, onUpdate) {
2070
2571
  in_progress: todos.filter((t) => t.status === "in_progress").length,
2071
2572
  completed: todos.filter((t) => t.status === "completed").length
2072
2573
  };
2574
+ const durationMs = Math.round(performance.now() - startTime);
2575
+ if (debugId) {
2576
+ debugEnd(debugId, "todo-write", {
2577
+ summary: stats,
2578
+ duration_ms: durationMs
2579
+ });
2580
+ }
2073
2581
  return {
2074
2582
  message: "Todo list updated successfully",
2075
2583
  stats
2076
2584
  };
2077
2585
  } catch (error) {
2078
- return {
2079
- error: error instanceof Error ? error.message : "Unknown error"
2080
- };
2586
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2587
+ if (debugId)
2588
+ debugError(debugId, "todo-write", errorMessage);
2589
+ return { error: errorMessage };
2081
2590
  }
2082
2591
  }
2083
2592
  });
@@ -2326,20 +2835,20 @@ function pruneMessagesByTokens(messages, config) {
2326
2835
  }
2327
2836
 
2328
2837
  // src/utils/compact-conversation.ts
2329
- async function compactConversation(messages, config, state = { conversationSummary: "" }) {
2838
+ async function compactConversation(messages, config, state2 = { conversationSummary: "" }) {
2330
2839
  const currentTokens = estimateMessagesTokens(messages);
2331
2840
  const threshold = config.compactionThreshold ?? 0.85;
2332
2841
  const limit = config.maxTokens * threshold;
2333
2842
  if (currentTokens < limit) {
2334
- return { messages, state, didCompact: false };
2843
+ return { messages, state: state2, didCompact: false };
2335
2844
  }
2336
2845
  const protectCount = config.protectRecentMessages ?? 10;
2337
2846
  const recentMessages = messages.slice(-protectCount);
2338
2847
  const oldMessages = messages.slice(0, -protectCount);
2339
2848
  if (oldMessages.length === 0) {
2340
- return { messages, state, didCompact: false };
2849
+ return { messages, state: state2, didCompact: false };
2341
2850
  }
2342
- const newSummary = await summarizeMessages(oldMessages, config.summarizerModel, config.taskContext, state.conversationSummary);
2851
+ const newSummary = await summarizeMessages(oldMessages, config.summarizerModel, config.taskContext, state2.conversationSummary);
2343
2852
  const compactedMessages = [
2344
2853
  {
2345
2854
  role: "user",
@@ -2874,10 +3383,13 @@ async function createDirectory(sandbox, path) {
2874
3383
  export {
2875
3384
  skillsToXml,
2876
3385
  setupAgentEnvironment,
3386
+ reinitDebugMode,
2877
3387
  pruneMessagesByTokens,
2878
3388
  parseSkillMetadata,
2879
3389
  loadSkillBundles,
2880
3390
  loadSkillBundle,
3391
+ isDebugEnabled,
3392
+ getDebugLogs,
2881
3393
  getContextStatus,
2882
3394
  fetchSkills,
2883
3395
  fetchSkill,
@@ -2909,6 +3421,7 @@ export {
2909
3421
  contextNeedsCompaction,
2910
3422
  contextNeedsAttention,
2911
3423
  compactConversation,
3424
+ clearDebugLogs,
2912
3425
  cached,
2913
3426
  anthropicPromptCacheMiddlewareV2,
2914
3427
  anthropicPromptCacheMiddleware,