bashkit 0.3.1 → 0.4.0

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,19 +615,197 @@ 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
- description: z.string().optional().describe("Explanation of what this option means or its implications.")
798
+ description: z.string().nullable().default(null).describe("Explanation of what this option means or its implications.")
621
799
  });
622
800
  var structuredQuestionSchema = z.object({
623
- header: z.string().optional().describe("Very short label displayed as a chip/tag (max 12 chars). Examples: 'Auth method', 'Library', 'Approach'."),
801
+ header: z.string().nullable().default(null).describe("Very short label displayed as a chip/tag (max 12 chars). Examples: 'Auth method', 'Library', 'Approach'."),
624
802
  question: z.string().describe("The complete question to ask the user. Should be clear and specific."),
625
- options: z.array(questionOptionSchema).min(2).max(4).optional().describe("Available choices for this question. 2-4 options. An 'Other' option is automatically available to users."),
626
- multiSelect: z.boolean().optional().describe("Set to true to allow the user to select multiple options instead of just one.")
803
+ options: z.array(questionOptionSchema).min(2).max(4).nullable().default(null).describe("Available choices for this question. 2-4 options. An 'Other' option is automatically available to users."),
804
+ multiSelect: z.boolean().nullable().default(null).describe("Set to true to allow the user to select multiple options instead of just one.")
627
805
  });
628
806
  var askUserInputSchema = z.object({
629
- question: z.string().optional().describe("Simple question string (for backward compatibility). Use 'questions' for structured multi-choice."),
630
- questions: z.array(structuredQuestionSchema).min(1).max(4).optional().describe("Structured questions with options (1-4 questions).")
807
+ question: z.string().nullable().default(null).describe("Simple question string (for backward compatibility). Use 'questions' for structured multi-choice."),
808
+ questions: z.array(structuredQuestionSchema).min(1).max(4).nullable().default(null).describe("Structured questions with options (1-4 questions).")
631
809
  });
632
810
  var ASK_USER_DESCRIPTION = `Use this tool when you need to ask the user questions during execution.
633
811
 
@@ -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
  });
@@ -710,9 +930,9 @@ import { tool as tool2, zodSchema as zodSchema2 } from "ai";
710
930
  import { z as z2 } from "zod";
711
931
  var bashInputSchema = z2.object({
712
932
  command: z2.string().describe("The command to execute"),
713
- timeout: z2.number().optional().describe("Optional timeout in milliseconds (max 600000)"),
714
- description: z2.string().optional().describe("Clear, concise description of what this command does in 5-10 words"),
715
- run_in_background: z2.boolean().optional().describe("Set to true to run this command in the background")
933
+ timeout: z2.number().nullable().default(null).describe("Optional timeout in milliseconds (max 600000)"),
934
+ description: z2.string().nullable().default(null).describe("Clear, concise description of what this command does in 5-10 words"),
935
+ run_in_background: z2.boolean().nullable().default(null).describe("Set to true to run this command in the background")
716
936
  });
717
937
  var BASH_DESCRIPTION = `Executes a bash command in a persistent shell session with optional timeout.
718
938
 
@@ -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
  });
@@ -810,7 +1049,7 @@ var editInputSchema = z3.object({
810
1049
  file_path: z3.string().describe("The absolute path to the file to modify"),
811
1050
  old_string: z3.string().describe("The text to replace"),
812
1051
  new_string: z3.string().describe("The text to replace it with (must be different from old_string)"),
813
- replace_all: z3.boolean().optional().describe("Replace all occurrences of old_string (default false)")
1052
+ replace_all: z3.boolean().nullable().default(null).describe("Replace all occurrences of old_string (default false)")
814
1053
  });
815
1054
  var EDIT_DESCRIPTION = `Performs exact string replacements in files.
816
1055
 
@@ -840,31 +1079,51 @@ function createEditTool(sandbox, config) {
840
1079
  file_path,
841
1080
  old_string,
842
1081
  new_string,
843
- replace_all = false
1082
+ replace_all: rawReplaceAll
844
1083
  }) => {
1084
+ const replace_all = rawReplaceAll ?? false;
1085
+ const startTime = performance.now();
1086
+ const debugId = isDebugEnabled() ? debugStart("edit", {
1087
+ file_path,
1088
+ old_string: old_string.length > 100 ? `${old_string.slice(0, 100)}...` : old_string,
1089
+ replace_all
1090
+ }) : "";
845
1091
  if (old_string === new_string) {
846
- return { error: "old_string and new_string must be different" };
1092
+ const error = "old_string and new_string must be different";
1093
+ if (debugId)
1094
+ debugError(debugId, "edit", error);
1095
+ return { error };
847
1096
  }
848
1097
  if (config?.allowedPaths) {
849
1098
  const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
850
1099
  if (!isAllowed) {
851
- return { error: `Path not allowed: ${file_path}` };
1100
+ const error = `Path not allowed: ${file_path}`;
1101
+ if (debugId)
1102
+ debugError(debugId, "edit", error);
1103
+ return { error };
852
1104
  }
853
1105
  }
854
1106
  try {
855
1107
  const exists = await sandbox.fileExists(file_path);
856
1108
  if (!exists) {
857
- return { error: `File not found: ${file_path}` };
1109
+ const error = `File not found: ${file_path}`;
1110
+ if (debugId)
1111
+ debugError(debugId, "edit", error);
1112
+ return { error };
858
1113
  }
859
1114
  const content = await sandbox.readFile(file_path);
860
1115
  const occurrences = content.split(old_string).length - 1;
861
1116
  if (occurrences === 0) {
862
- return { error: `String not found in file: "${old_string}"` };
1117
+ const error = `String not found in file: "${old_string}"`;
1118
+ if (debugId)
1119
+ debugError(debugId, "edit", error);
1120
+ return { error };
863
1121
  }
864
1122
  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
- };
1123
+ const error = `String appears ${occurrences} times in file. Use replace_all=true to replace all, or provide a more unique string.`;
1124
+ if (debugId)
1125
+ debugError(debugId, "edit", error);
1126
+ return { error };
868
1127
  }
869
1128
  let newContent;
870
1129
  let replacements;
@@ -876,15 +1135,23 @@ function createEditTool(sandbox, config) {
876
1135
  replacements = 1;
877
1136
  }
878
1137
  await sandbox.writeFile(file_path, newContent);
1138
+ const durationMs = Math.round(performance.now() - startTime);
1139
+ if (debugId) {
1140
+ debugEnd(debugId, "edit", {
1141
+ summary: { replacements },
1142
+ duration_ms: durationMs
1143
+ });
1144
+ }
879
1145
  return {
880
1146
  message: `Successfully edited ${file_path}`,
881
1147
  file_path,
882
1148
  replacements
883
1149
  };
884
1150
  } catch (error) {
885
- return {
886
- error: error instanceof Error ? error.message : "Unknown error"
887
- };
1151
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1152
+ if (debugId)
1153
+ debugError(debugId, "edit", errorMessage);
1154
+ return { error: errorMessage };
888
1155
  }
889
1156
  }
890
1157
  });
@@ -951,33 +1218,44 @@ In plan mode, you'll:
951
1218
  - This tool REQUIRES user approval - they must consent to entering plan mode
952
1219
  - If unsure whether to use it, err on the side of planning - it's better to get alignment upfront than to redo work
953
1220
  - Users appreciate being consulted before significant changes`;
954
- function createEnterPlanModeTool(state, onEnter) {
1221
+ function createEnterPlanModeTool(state2, onEnter) {
955
1222
  return tool4({
956
1223
  description: ENTER_PLAN_MODE_DESCRIPTION,
957
1224
  inputSchema: zodSchema4(enterPlanModeInputSchema),
958
1225
  execute: async ({
959
1226
  reason
960
1227
  }) => {
1228
+ const startTime = performance.now();
1229
+ const debugId = isDebugEnabled() ? debugStart("enter-plan-mode", { reason }) : "";
961
1230
  try {
962
- if (state.isActive) {
963
- return {
964
- error: "Already in planning mode. Use ExitPlanMode to exit."
965
- };
1231
+ if (state2.isActive) {
1232
+ const error = "Already in planning mode. Use ExitPlanMode to exit.";
1233
+ if (debugId)
1234
+ debugError(debugId, "enter-plan-mode", error);
1235
+ return { error };
966
1236
  }
967
- state.isActive = true;
968
- state.enteredAt = new Date;
969
- state.reason = reason;
1237
+ state2.isActive = true;
1238
+ state2.enteredAt = new Date;
1239
+ state2.reason = reason;
970
1240
  if (onEnter) {
971
1241
  await onEnter(reason);
972
1242
  }
1243
+ const durationMs = Math.round(performance.now() - startTime);
1244
+ if (debugId) {
1245
+ debugEnd(debugId, "enter-plan-mode", {
1246
+ summary: { mode: "planning" },
1247
+ duration_ms: durationMs
1248
+ });
1249
+ }
973
1250
  return {
974
1251
  message: `Entered planning mode: ${reason}. Use Read, Grep, and Glob to explore. Call ExitPlanMode when ready with a plan.`,
975
1252
  mode: "planning"
976
1253
  };
977
1254
  } catch (error) {
978
- return {
979
- error: error instanceof Error ? error.message : "Unknown error"
980
- };
1255
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1256
+ if (debugId)
1257
+ debugError(debugId, "enter-plan-mode", errorMessage);
1258
+ return { error: errorMessage };
981
1259
  }
982
1260
  }
983
1261
  });
@@ -1017,19 +1295,32 @@ function createExitPlanModeTool(onPlanSubmit) {
1017
1295
  execute: async ({
1018
1296
  plan
1019
1297
  }) => {
1298
+ const startTime = performance.now();
1299
+ const debugId = isDebugEnabled() ? debugStart("exit-plan-mode", {
1300
+ planLength: plan.length,
1301
+ planPreview: plan.length > 200 ? `${plan.slice(0, 200)}...` : plan
1302
+ }) : "";
1020
1303
  try {
1021
1304
  let approved;
1022
1305
  if (onPlanSubmit) {
1023
1306
  approved = await onPlanSubmit(plan);
1024
1307
  }
1308
+ const durationMs = Math.round(performance.now() - startTime);
1309
+ if (debugId) {
1310
+ debugEnd(debugId, "exit-plan-mode", {
1311
+ summary: { approved },
1312
+ duration_ms: durationMs
1313
+ });
1314
+ }
1025
1315
  return {
1026
1316
  message: approved ? "Plan approved, proceeding with execution" : "Plan submitted for review",
1027
1317
  approved
1028
1318
  };
1029
1319
  } catch (error) {
1030
- return {
1031
- error: error instanceof Error ? error.message : "Unknown error"
1032
- };
1320
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1321
+ if (debugId)
1322
+ debugError(debugId, "exit-plan-mode", errorMessage);
1323
+ return { error: errorMessage };
1033
1324
  }
1034
1325
  }
1035
1326
  });
@@ -1040,7 +1331,7 @@ import { tool as tool6, zodSchema as zodSchema6 } from "ai";
1040
1331
  import { z as z6 } from "zod";
1041
1332
  var globInputSchema = z6.object({
1042
1333
  pattern: z6.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js", "*.md")'),
1043
- path: z6.string().optional().describe("Directory to search in (defaults to working directory)")
1334
+ path: z6.string().nullable().default(null).describe("Directory to search in (defaults to working directory)")
1044
1335
  });
1045
1336
  var GLOB_DESCRIPTION = `
1046
1337
  - Fast file pattern matching tool that works with any codebase size
@@ -1061,29 +1352,48 @@ function createGlobTool(sandbox, config) {
1061
1352
  pattern,
1062
1353
  path
1063
1354
  }) => {
1064
- const searchPath = path || ".";
1355
+ const searchPath = path ?? ".";
1356
+ const startTime = performance.now();
1357
+ const debugId = isDebugEnabled() ? debugStart("glob", { pattern, path: searchPath }) : "";
1065
1358
  if (config?.allowedPaths) {
1066
1359
  const isAllowed = config.allowedPaths.some((allowed) => searchPath.startsWith(allowed));
1067
1360
  if (!isAllowed) {
1068
- return { error: `Path not allowed: ${searchPath}` };
1361
+ const error = `Path not allowed: ${searchPath}`;
1362
+ if (debugId)
1363
+ debugError(debugId, "glob", error);
1364
+ return { error };
1069
1365
  }
1070
1366
  }
1071
1367
  try {
1072
- const result = await sandbox.exec(`find ${searchPath} -type f -name "${pattern}" 2>/dev/null | head -1000`, { timeout: config?.timeout });
1368
+ const findFlag = pattern.includes("/") ? "-path" : "-name";
1369
+ const findPattern = pattern.includes("/") && !pattern.startsWith("*") ? `*/${pattern}` : pattern;
1370
+ const result = await sandbox.exec(`find ${searchPath} -type f ${findFlag} "${findPattern}" 2>/dev/null | head -1000`, { timeout: config?.timeout });
1073
1371
  if (result.exitCode !== 0 && result.stderr) {
1074
- return { error: result.stderr };
1372
+ const error = result.stderr;
1373
+ if (debugId)
1374
+ debugError(debugId, "glob", error);
1375
+ return { error };
1075
1376
  }
1076
1377
  const matches = result.stdout.split(`
1077
1378
  `).filter(Boolean).map((p) => p.trim());
1379
+ const durationMs = Math.round(performance.now() - startTime);
1380
+ if (debugId) {
1381
+ debugEnd(debugId, "glob", {
1382
+ summary: { count: matches.length },
1383
+ output: matches.slice(0, 10),
1384
+ duration_ms: durationMs
1385
+ });
1386
+ }
1078
1387
  return {
1079
1388
  matches,
1080
1389
  count: matches.length,
1081
1390
  search_path: searchPath
1082
1391
  };
1083
1392
  } catch (error) {
1084
- return {
1085
- error: error instanceof Error ? error.message : "Unknown error"
1086
- };
1393
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1394
+ if (debugId)
1395
+ debugError(debugId, "glob", errorMessage);
1396
+ return { error: errorMessage };
1087
1397
  }
1088
1398
  }
1089
1399
  });
@@ -1094,18 +1404,18 @@ import { tool as tool7, zodSchema as zodSchema7 } from "ai";
1094
1404
  import { z as z7 } from "zod";
1095
1405
  var grepInputSchema = z7.object({
1096
1406
  pattern: z7.string().describe("The regular expression pattern to search for in file contents"),
1097
- path: z7.string().optional().describe("File or directory to search in (defaults to cwd)"),
1098
- glob: z7.string().optional().describe('Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}")'),
1099
- type: z7.string().optional().describe('File type to search (e.g. "js", "py", "rust")'),
1100
- output_mode: z7.enum(["content", "files_with_matches", "count"]).optional().describe('Output mode: "content" shows matching lines, "files_with_matches" shows file paths (default), "count" shows match counts'),
1101
- "-i": z7.boolean().optional().describe("Case insensitive search"),
1102
- "-n": z7.boolean().optional().describe("Show line numbers in output. Requires output_mode: 'content'. Defaults to true."),
1103
- "-B": z7.number().optional().describe("Number of lines to show before each match. Requires output_mode: 'content'."),
1104
- "-A": z7.number().optional().describe("Number of lines to show after each match. Requires output_mode: 'content'."),
1105
- "-C": z7.number().optional().describe("Number of lines to show before and after each match. Requires output_mode: 'content'."),
1106
- head_limit: z7.number().optional().describe("Limit output to first N lines/entries. Works across all output modes. Defaults to 0 (unlimited)."),
1107
- offset: z7.number().optional().describe("Skip first N lines/entries before applying head_limit. Works across all output modes. Defaults to 0."),
1108
- multiline: z7.boolean().optional().describe("Enable multiline mode where patterns can span lines. Default: false.")
1407
+ path: z7.string().nullable().default(null).describe("File or directory to search in (defaults to cwd)"),
1408
+ glob: z7.string().nullable().default(null).describe('Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}")'),
1409
+ type: z7.string().nullable().default(null).describe('File type to search (e.g. "js", "py", "rust")'),
1410
+ output_mode: z7.enum(["content", "files_with_matches", "count"]).nullable().default(null).describe('Output mode: "content" shows matching lines, "files_with_matches" shows file paths (default), "count" shows match counts'),
1411
+ "-i": z7.boolean().nullable().default(null).describe("Case insensitive search"),
1412
+ "-n": z7.boolean().nullable().default(null).describe("Show line numbers in output. Requires output_mode: 'content'. Defaults to true."),
1413
+ "-B": z7.number().nullable().default(null).describe("Number of lines to show before each match. Requires output_mode: 'content'."),
1414
+ "-A": z7.number().nullable().default(null).describe("Number of lines to show after each match. Requires output_mode: 'content'."),
1415
+ "-C": z7.number().nullable().default(null).describe("Number of lines to show before and after each match. Requires output_mode: 'content'."),
1416
+ head_limit: z7.number().nullable().default(null).describe("Limit output to first N lines/entries. Works across all output modes. Defaults to 0 (unlimited)."),
1417
+ offset: z7.number().nullable().default(null).describe("Skip first N lines/entries before applying head_limit. Works across all output modes. Defaults to 0."),
1418
+ multiline: z7.boolean().nullable().default(null).describe("Enable multiline mode where patterns can span lines. Default: false.")
1109
1419
  });
1110
1420
  var GREP_DESCRIPTION = `A powerful content search tool built on ripgrep with regex support.
1111
1421
 
@@ -1140,27 +1450,43 @@ function createGrepTool(sandbox, config) {
1140
1450
  path,
1141
1451
  glob,
1142
1452
  type,
1143
- output_mode = "files_with_matches",
1453
+ output_mode: rawOutputMode,
1144
1454
  "-i": caseInsensitive,
1145
1455
  "-B": beforeContext,
1146
1456
  "-A": afterContext,
1147
1457
  "-C": context,
1148
1458
  head_limit,
1149
- offset = 0,
1459
+ offset: rawOffset,
1150
1460
  multiline
1151
1461
  } = input;
1462
+ const output_mode = rawOutputMode ?? "files_with_matches";
1463
+ const offset = rawOffset ?? 0;
1152
1464
  const searchPath = path || ".";
1465
+ const startTime = performance.now();
1466
+ const debugId = isDebugEnabled() ? debugStart("grep", {
1467
+ pattern,
1468
+ path: searchPath,
1469
+ output_mode,
1470
+ glob,
1471
+ type,
1472
+ caseInsensitive,
1473
+ multiline
1474
+ }) : "";
1153
1475
  if (config?.allowedPaths) {
1154
1476
  const isAllowed = config.allowedPaths.some((allowed) => searchPath.startsWith(allowed));
1155
1477
  if (!isAllowed) {
1156
- return { error: `Path not allowed: ${searchPath}` };
1478
+ const error = `Path not allowed: ${searchPath}`;
1479
+ if (debugId)
1480
+ debugError(debugId, "grep", error);
1481
+ return { error };
1157
1482
  }
1158
1483
  }
1159
1484
  try {
1160
1485
  if (!sandbox.rgPath) {
1161
- return {
1162
- error: "Ripgrep not available. Call ensureSandboxTools(sandbox) before using Grep with remote sandboxes."
1163
- };
1486
+ const error = "Ripgrep not available. Call ensureSandboxTools(sandbox) before using Grep with remote sandboxes.";
1487
+ if (debugId)
1488
+ debugError(debugId, "grep", error);
1489
+ return { error };
1164
1490
  }
1165
1491
  const cmd = buildRipgrepCommand({
1166
1492
  rgPath: sandbox.rgPath,
@@ -1176,17 +1502,48 @@ function createGrepTool(sandbox, config) {
1176
1502
  multiline
1177
1503
  });
1178
1504
  const result = await sandbox.exec(cmd, { timeout: config?.timeout });
1505
+ const durationMs = Math.round(performance.now() - startTime);
1506
+ let output;
1179
1507
  if (output_mode === "files_with_matches") {
1180
- return parseFilesOutput(result.stdout);
1508
+ output = parseFilesOutput(result.stdout);
1509
+ if (debugId) {
1510
+ debugEnd(debugId, "grep", {
1511
+ summary: {
1512
+ fileCount: output.count,
1513
+ exitCode: result.exitCode
1514
+ },
1515
+ duration_ms: durationMs
1516
+ });
1517
+ }
1181
1518
  } else if (output_mode === "count") {
1182
- return parseCountOutput(result.stdout);
1519
+ output = parseCountOutput(result.stdout);
1520
+ if (debugId) {
1521
+ debugEnd(debugId, "grep", {
1522
+ summary: {
1523
+ total: output.total,
1524
+ exitCode: result.exitCode
1525
+ },
1526
+ duration_ms: durationMs
1527
+ });
1528
+ }
1183
1529
  } else {
1184
- return parseContentOutput(result.stdout, head_limit, offset);
1530
+ output = parseContentOutput(result.stdout, head_limit, offset);
1531
+ if (debugId) {
1532
+ debugEnd(debugId, "grep", {
1533
+ summary: {
1534
+ matchCount: output.total_matches,
1535
+ exitCode: result.exitCode
1536
+ },
1537
+ duration_ms: durationMs
1538
+ });
1539
+ }
1185
1540
  }
1541
+ return output;
1186
1542
  } catch (error) {
1187
- return {
1188
- error: error instanceof Error ? error.message : "Unknown error"
1189
- };
1543
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1544
+ if (debugId)
1545
+ debugError(debugId, "grep", errorMessage);
1546
+ return { error: errorMessage };
1190
1547
  }
1191
1548
  }
1192
1549
  });
@@ -1253,7 +1610,7 @@ function parseCountOutput(stdout) {
1253
1610
  total
1254
1611
  };
1255
1612
  }
1256
- function parseContentOutput(stdout, head_limit, offset = 0) {
1613
+ function parseContentOutput(stdout, head_limit, offset) {
1257
1614
  const fileData = new Map;
1258
1615
  for (const line of stdout.split(`
1259
1616
  `).filter(Boolean)) {
@@ -1350,8 +1707,8 @@ import { tool as tool8, zodSchema as zodSchema8 } from "ai";
1350
1707
  import { z as z8 } from "zod";
1351
1708
  var readInputSchema = z8.object({
1352
1709
  file_path: z8.string().describe("Absolute path to file or directory"),
1353
- offset: z8.number().optional().describe("Line number to start reading from (1-indexed)"),
1354
- limit: z8.number().optional().describe("Maximum number of lines to read")
1710
+ offset: z8.number().nullable().default(null).describe("Line number to start reading from (1-indexed)"),
1711
+ limit: z8.number().nullable().default(null).describe("Maximum number of lines to read")
1355
1712
  });
1356
1713
  var READ_DESCRIPTION = `Reads a file from the local filesystem. You can access any file directly by using this tool.
1357
1714
  Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
@@ -1377,20 +1734,35 @@ function createReadTool(sandbox, config) {
1377
1734
  offset,
1378
1735
  limit
1379
1736
  }) => {
1737
+ const startTime = performance.now();
1738
+ const debugId = isDebugEnabled() ? debugStart("read", { file_path, offset, limit }) : "";
1380
1739
  if (config?.allowedPaths) {
1381
1740
  const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
1382
1741
  if (!isAllowed) {
1383
- return { error: `Path not allowed: ${file_path}` };
1742
+ const error = `Path not allowed: ${file_path}`;
1743
+ if (debugId)
1744
+ debugError(debugId, "read", error);
1745
+ return { error };
1384
1746
  }
1385
1747
  }
1386
1748
  try {
1387
1749
  const exists = await sandbox.fileExists(file_path);
1388
1750
  if (!exists) {
1389
- return { error: `Path not found: ${file_path}` };
1751
+ const error = `Path not found: ${file_path}`;
1752
+ if (debugId)
1753
+ debugError(debugId, "read", error);
1754
+ return { error };
1390
1755
  }
1391
1756
  const isDir = await sandbox.isDirectory(file_path);
1392
1757
  if (isDir) {
1393
1758
  const entries = await sandbox.readDir(file_path);
1759
+ const durationMs2 = Math.round(performance.now() - startTime);
1760
+ if (debugId) {
1761
+ debugEnd(debugId, "read", {
1762
+ summary: { type: "directory", count: entries.length },
1763
+ duration_ms: durationMs2
1764
+ });
1765
+ }
1394
1766
  return {
1395
1767
  type: "directory",
1396
1768
  entries,
@@ -1416,9 +1788,10 @@ function createReadTool(sandbox, config) {
1416
1788
  "dylib"
1417
1789
  ];
1418
1790
  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
- };
1791
+ 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).`;
1792
+ if (debugId)
1793
+ debugError(debugId, "read", error);
1794
+ return { error };
1422
1795
  }
1423
1796
  }
1424
1797
  const allLines = content.split(`
@@ -1426,9 +1799,10 @@ function createReadTool(sandbox, config) {
1426
1799
  const totalLines = allLines.length;
1427
1800
  const maxLinesWithoutLimit = config?.maxFileSize || 500;
1428
1801
  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
- };
1802
+ const error = `File is large (${totalLines} lines). Use 'offset' and 'limit' to read in chunks. Example: offset=1, limit=100 for first 100 lines.`;
1803
+ if (debugId)
1804
+ debugError(debugId, "read", error);
1805
+ return { error };
1432
1806
  }
1433
1807
  const startLine = offset ? offset - 1 : 0;
1434
1808
  const endLine = limit ? startLine + limit : allLines.length;
@@ -1437,6 +1811,18 @@ function createReadTool(sandbox, config) {
1437
1811
  line_number: startLine + i + 1,
1438
1812
  content: line
1439
1813
  }));
1814
+ const durationMs = Math.round(performance.now() - startTime);
1815
+ if (debugId) {
1816
+ debugEnd(debugId, "read", {
1817
+ summary: {
1818
+ type: "text",
1819
+ totalLines,
1820
+ returnedLines: lines.length,
1821
+ bytes: content.length
1822
+ },
1823
+ duration_ms: durationMs
1824
+ });
1825
+ }
1440
1826
  return {
1441
1827
  type: "text",
1442
1828
  content: selectedLines.join(`
@@ -1445,9 +1831,10 @@ function createReadTool(sandbox, config) {
1445
1831
  total_lines: totalLines
1446
1832
  };
1447
1833
  } catch (error) {
1448
- return {
1449
- error: error instanceof Error ? error.message : "Unknown error"
1450
- };
1834
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1835
+ if (debugId)
1836
+ debugError(debugId, "read", errorMessage);
1837
+ return { error: errorMessage };
1451
1838
  }
1452
1839
  }
1453
1840
  });
@@ -1481,22 +1868,29 @@ function createSkillTool(config) {
1481
1868
  execute: async ({
1482
1869
  name
1483
1870
  }) => {
1871
+ const startTime = performance.now();
1872
+ const debugId = isDebugEnabled() ? debugStart("skill", { name, availableSkills: Object.keys(skills) }) : "";
1484
1873
  try {
1485
1874
  const skill = skills[name];
1486
1875
  if (!skill) {
1487
1876
  const available = Object.keys(skills);
1488
1877
  if (available.length === 0) {
1489
- return { error: "No skills are available." };
1878
+ const error2 = "No skills are available.";
1879
+ if (debugId)
1880
+ debugError(debugId, "skill", error2);
1881
+ return { error: error2 };
1490
1882
  }
1491
- return {
1492
- error: `Skill '${name}' not found. Available skills: ${available.join(", ")}`
1493
- };
1883
+ const error = `Skill '${name}' not found. Available skills: ${available.join(", ")}`;
1884
+ if (debugId)
1885
+ debugError(debugId, "skill", error);
1886
+ return { error };
1494
1887
  }
1495
1888
  let instructions;
1496
1889
  if (!sandbox) {
1497
- return {
1498
- error: `Cannot load skill '${name}': no sandbox provided to read ${skill.path}`
1499
- };
1890
+ const error = `Cannot load skill '${name}': no sandbox provided to read ${skill.path}`;
1891
+ if (debugId)
1892
+ debugError(debugId, "skill", error);
1893
+ return { error };
1500
1894
  }
1501
1895
  const content = await sandbox.readFile(skill.path);
1502
1896
  const frontmatterEnd = content.indexOf(`
@@ -1509,6 +1903,17 @@ function createSkillTool(config) {
1509
1903
  if (onActivate) {
1510
1904
  await onActivate(skill, instructions);
1511
1905
  }
1906
+ const durationMs = Math.round(performance.now() - startTime);
1907
+ if (debugId) {
1908
+ debugEnd(debugId, "skill", {
1909
+ summary: {
1910
+ skillName: skill.name,
1911
+ instructionLength: instructions.length,
1912
+ hasAllowedTools: !!skill.allowedTools
1913
+ },
1914
+ duration_ms: durationMs
1915
+ });
1916
+ }
1512
1917
  return {
1513
1918
  name: skill.name,
1514
1919
  instructions,
@@ -1516,9 +1921,10 @@ function createSkillTool(config) {
1516
1921
  message: skill.allowedTools ? `Skill '${name}' activated. Restricted to tools: ${skill.allowedTools.join(", ")}` : `Skill '${name}' activated. Follow the instructions below.`
1517
1922
  };
1518
1923
  } catch (error) {
1519
- return {
1520
- error: error instanceof Error ? error.message : "Unknown error"
1521
- };
1924
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1925
+ if (debugId)
1926
+ debugError(debugId, "skill", errorMessage);
1927
+ return { error: errorMessage };
1522
1928
  }
1523
1929
  }
1524
1930
  });
@@ -1613,6 +2019,11 @@ function createWebFetchTool(config) {
1613
2019
  providerOptions,
1614
2020
  execute: async (input) => {
1615
2021
  const { url, prompt } = input;
2022
+ const startTime = performance.now();
2023
+ const debugId = isDebugEnabled() ? debugStart("web-fetch", {
2024
+ url,
2025
+ prompt: prompt.length > 200 ? `${prompt.slice(0, 200)}...` : prompt
2026
+ }) : "";
1616
2027
  try {
1617
2028
  const { content, finalUrl } = await fetchContent(url, apiKey, provider);
1618
2029
  const result = await generateText({
@@ -1623,6 +2034,16 @@ Content from ${url}:
1623
2034
 
1624
2035
  ${content}`
1625
2036
  });
2037
+ const durationMs = Math.round(performance.now() - startTime);
2038
+ if (debugId) {
2039
+ debugEnd(debugId, "web-fetch", {
2040
+ summary: {
2041
+ contentLength: content.length,
2042
+ responseLength: result.text.length
2043
+ },
2044
+ duration_ms: durationMs
2045
+ });
2046
+ }
1626
2047
  return {
1627
2048
  response: result.text,
1628
2049
  url,
@@ -1632,15 +2053,18 @@ ${content}`
1632
2053
  if (error && typeof error === "object" && "status" in error) {
1633
2054
  const statusCode = error.status;
1634
2055
  const message = error.message || "API request failed";
2056
+ if (debugId)
2057
+ debugError(debugId, "web-fetch", `${message} (status: ${statusCode})`);
1635
2058
  return {
1636
2059
  error: message,
1637
2060
  status_code: statusCode,
1638
2061
  retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
1639
2062
  };
1640
2063
  }
1641
- return {
1642
- error: error instanceof Error ? error.message : "Unknown error"
1643
- };
2064
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2065
+ if (debugId)
2066
+ debugError(debugId, "web-fetch", errorMessage);
2067
+ return { error: errorMessage };
1644
2068
  }
1645
2069
  }
1646
2070
  });
@@ -1697,8 +2121,8 @@ async function searchContent(apiKey, provider, options) {
1697
2121
  }
1698
2122
  var webSearchInputSchema = z11.object({
1699
2123
  query: z11.string().describe("The search query to use"),
1700
- allowed_domains: z11.array(z11.string()).optional().describe("Only include results from these domains"),
1701
- blocked_domains: z11.array(z11.string()).optional().describe("Never include results from these domains")
2124
+ allowed_domains: z11.array(z11.string()).nullable().default(null).describe("Only include results from these domains"),
2125
+ blocked_domains: z11.array(z11.string()).nullable().default(null).describe("Never include results from these domains")
1702
2126
  });
1703
2127
  var WEB_SEARCH_DESCRIPTION = `Searches the web and returns results with links. Use this for accessing up-to-date information beyond your knowledge cutoff.
1704
2128
 
@@ -1736,12 +2160,22 @@ function createWebSearchTool(config) {
1736
2160
  providerOptions,
1737
2161
  execute: async (input) => {
1738
2162
  const { query, allowed_domains, blocked_domains } = input;
2163
+ const startTime = performance.now();
2164
+ const debugId = isDebugEnabled() ? debugStart("web-search", { query, allowed_domains, blocked_domains }) : "";
1739
2165
  try {
1740
2166
  const results = await searchContent(apiKey, provider, {
1741
2167
  query,
1742
2168
  allowedDomains: allowed_domains,
1743
2169
  blockedDomains: blocked_domains
1744
2170
  });
2171
+ const durationMs = Math.round(performance.now() - startTime);
2172
+ if (debugId) {
2173
+ debugEnd(debugId, "web-search", {
2174
+ summary: { resultCount: results.length },
2175
+ output: results.slice(0, 5).map((r) => ({ title: r.title, url: r.url })),
2176
+ duration_ms: durationMs
2177
+ });
2178
+ }
1745
2179
  return {
1746
2180
  results,
1747
2181
  total_results: results.length,
@@ -1751,15 +2185,18 @@ function createWebSearchTool(config) {
1751
2185
  if (error && typeof error === "object" && "status" in error) {
1752
2186
  const statusCode = error.status;
1753
2187
  const message = error.message || "API request failed";
2188
+ if (debugId)
2189
+ debugError(debugId, "web-search", `${message} (status: ${statusCode})`);
1754
2190
  return {
1755
2191
  error: message,
1756
2192
  status_code: statusCode,
1757
2193
  retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
1758
2194
  };
1759
2195
  }
1760
- return {
1761
- error: error instanceof Error ? error.message : "Unknown error"
1762
- };
2196
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2197
+ if (debugId)
2198
+ debugError(debugId, "web-search", errorMessage);
2199
+ return { error: errorMessage };
1763
2200
  }
1764
2201
  }
1765
2202
  });
@@ -1795,29 +2232,43 @@ function createWriteTool(sandbox, config) {
1795
2232
  file_path,
1796
2233
  content
1797
2234
  }) => {
2235
+ const startTime = performance.now();
1798
2236
  const byteLength = Buffer.byteLength(content, "utf-8");
2237
+ const debugId = isDebugEnabled() ? debugStart("write", { file_path, contentLength: byteLength }) : "";
1799
2238
  if (config?.maxFileSize && byteLength > config.maxFileSize) {
1800
- return {
1801
- error: `File content exceeds maximum size of ${config.maxFileSize} bytes (got ${byteLength})`
1802
- };
2239
+ const error = `File content exceeds maximum size of ${config.maxFileSize} bytes (got ${byteLength})`;
2240
+ if (debugId)
2241
+ debugError(debugId, "write", error);
2242
+ return { error };
1803
2243
  }
1804
2244
  if (config?.allowedPaths) {
1805
2245
  const isAllowed = config.allowedPaths.some((allowed) => file_path.startsWith(allowed));
1806
2246
  if (!isAllowed) {
1807
- return { error: `Path not allowed: ${file_path}` };
2247
+ const error = `Path not allowed: ${file_path}`;
2248
+ if (debugId)
2249
+ debugError(debugId, "write", error);
2250
+ return { error };
1808
2251
  }
1809
2252
  }
1810
2253
  try {
1811
2254
  await sandbox.writeFile(file_path, content);
2255
+ const durationMs = Math.round(performance.now() - startTime);
2256
+ if (debugId) {
2257
+ debugEnd(debugId, "write", {
2258
+ summary: { bytes_written: byteLength },
2259
+ duration_ms: durationMs
2260
+ });
2261
+ }
1812
2262
  return {
1813
2263
  message: `Successfully wrote to ${file_path}`,
1814
2264
  bytes_written: byteLength,
1815
2265
  file_path
1816
2266
  };
1817
2267
  } catch (error) {
1818
- return {
1819
- error: error instanceof Error ? error.message : "Unknown error"
1820
- };
2268
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2269
+ if (debugId)
2270
+ debugError(debugId, "write", errorMessage);
2271
+ return { error: errorMessage };
1821
2272
  }
1822
2273
  }
1823
2274
  });
@@ -1835,8 +2286,8 @@ var taskInputSchema = z13.object({
1835
2286
  description: z13.string().describe("A short (3-5 word) description of the task"),
1836
2287
  prompt: z13.string().describe("The task for the agent to perform"),
1837
2288
  subagent_type: z13.string().describe("The type of specialized agent to use for this task"),
1838
- system_prompt: z13.string().optional().describe("Optional custom system prompt for this agent. If provided, overrides the default system prompt for the subagent type. Use this to create dynamic, specialized agents on the fly."),
1839
- tools: z13.array(z13.string()).optional().describe("Optional list of tool names this agent can use (e.g., ['Read', 'Grep', 'WebSearch']). If provided, overrides the default tools for the subagent type. Use this to restrict or expand the agent's capabilities.")
2289
+ system_prompt: z13.string().nullable().default(null).describe("Optional custom system prompt for this agent. If provided, overrides the default system prompt for the subagent type. Use this to create dynamic, specialized agents on the fly."),
2290
+ tools: z13.array(z13.string()).nullable().default(null).describe("Optional list of tool names this agent can use (e.g., ['Read', 'Grep', 'WebSearch']). If provided, overrides the default tools for the subagent type. Use this to restrict or expand the agent's capabilities.")
1840
2291
  });
1841
2292
  var TASK_DESCRIPTION = `Launch a new agent to handle complex, multi-step tasks autonomously.
1842
2293
 
@@ -1864,16 +2315,22 @@ var eventCounter = 0;
1864
2315
  function generateEventId() {
1865
2316
  return `subagent-${Date.now()}-${++eventCounter}`;
1866
2317
  }
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];
2318
+ function filterTools(allTools, allowedTools, additionalTools) {
2319
+ let result;
2320
+ if (allowedTools) {
2321
+ result = {};
2322
+ for (const name of allowedTools) {
2323
+ if (allTools[name]) {
2324
+ result[name] = allTools[name];
2325
+ }
1874
2326
  }
2327
+ } else {
2328
+ result = allTools;
2329
+ }
2330
+ if (additionalTools) {
2331
+ result = { ...result, ...additionalTools };
1875
2332
  }
1876
- return filtered;
2333
+ return result;
1877
2334
  }
1878
2335
  function createTaskTool(config) {
1879
2336
  const {
@@ -1895,10 +2352,20 @@ function createTaskTool(config) {
1895
2352
  tools: customTools
1896
2353
  }) => {
1897
2354
  const startTime = performance.now();
2355
+ const typeConfig = subagentTypes[subagent_type] || {};
2356
+ const debugId = isDebugEnabled() ? debugStart("task", {
2357
+ subagent_type,
2358
+ description,
2359
+ tools: [
2360
+ ...customTools ?? typeConfig.tools ?? Object.keys(allTools),
2361
+ ...Object.keys(typeConfig.additionalTools ?? {})
2362
+ ]
2363
+ }) : "";
2364
+ if (debugId)
2365
+ pushParent(debugId);
1898
2366
  try {
1899
- const typeConfig = subagentTypes[subagent_type] || {};
1900
2367
  const model = typeConfig.model || defaultModel;
1901
- const tools = filterTools(allTools, customTools ?? typeConfig.tools);
2368
+ const tools = filterTools(allTools, customTools ?? typeConfig.tools, typeConfig.additionalTools);
1902
2369
  const systemPrompt = system_prompt ?? typeConfig.systemPrompt;
1903
2370
  const commonOptions = {
1904
2371
  model,
@@ -1969,6 +2436,19 @@ function createTaskTool(config) {
1969
2436
  }
1970
2437
  });
1971
2438
  const durationMs2 = Math.round(performance.now() - startTime);
2439
+ if (debugId) {
2440
+ popParent();
2441
+ debugEnd(debugId, "task", {
2442
+ summary: {
2443
+ tokens: {
2444
+ input: usage2.inputTokens,
2445
+ output: usage2.outputTokens
2446
+ },
2447
+ steps: response.messages?.length
2448
+ },
2449
+ duration_ms: durationMs2
2450
+ });
2451
+ }
1972
2452
  return {
1973
2453
  result: text,
1974
2454
  usage: usage2.inputTokens !== undefined && usage2.outputTokens !== undefined ? {
@@ -1996,6 +2476,19 @@ function createTaskTool(config) {
1996
2476
  input_tokens: result.usage.inputTokens,
1997
2477
  output_tokens: result.usage.outputTokens
1998
2478
  } : undefined;
2479
+ if (debugId) {
2480
+ popParent();
2481
+ debugEnd(debugId, "task", {
2482
+ summary: {
2483
+ tokens: {
2484
+ input: result.usage.inputTokens,
2485
+ output: result.usage.outputTokens
2486
+ },
2487
+ steps: result.steps?.length
2488
+ },
2489
+ duration_ms: durationMs
2490
+ });
2491
+ }
1999
2492
  return {
2000
2493
  result: result.text,
2001
2494
  usage,
@@ -2005,6 +2498,10 @@ function createTaskTool(config) {
2005
2498
  };
2006
2499
  } catch (error) {
2007
2500
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
2501
+ if (debugId) {
2502
+ popParent();
2503
+ debugError(debugId, "task", errorMessage);
2504
+ }
2008
2505
  return { error: errorMessage };
2009
2506
  }
2010
2507
  }
@@ -2052,15 +2549,22 @@ var TODO_WRITE_DESCRIPTION = `Use this tool to create and manage a structured ta
2052
2549
  - Keep exactly ONE task in_progress at any time
2053
2550
  - ONLY mark completed when FULLY accomplished
2054
2551
  - If blocked/errors, keep in_progress and create new task for the blocker`;
2055
- function createTodoWriteTool(state, onUpdate) {
2552
+ function createTodoWriteTool(state2, onUpdate) {
2056
2553
  return tool14({
2057
2554
  description: TODO_WRITE_DESCRIPTION,
2058
2555
  inputSchema: zodSchema14(todoWriteInputSchema),
2059
2556
  execute: async ({
2060
2557
  todos
2061
2558
  }) => {
2559
+ const startTime = performance.now();
2560
+ const debugId = isDebugEnabled() ? debugStart("todo-write", {
2561
+ todoCount: todos.length,
2562
+ pending: todos.filter((t) => t.status === "pending").length,
2563
+ in_progress: todos.filter((t) => t.status === "in_progress").length,
2564
+ completed: todos.filter((t) => t.status === "completed").length
2565
+ }) : "";
2062
2566
  try {
2063
- state.todos = todos;
2567
+ state2.todos = todos;
2064
2568
  if (onUpdate) {
2065
2569
  onUpdate(todos);
2066
2570
  }
@@ -2070,14 +2574,22 @@ function createTodoWriteTool(state, onUpdate) {
2070
2574
  in_progress: todos.filter((t) => t.status === "in_progress").length,
2071
2575
  completed: todos.filter((t) => t.status === "completed").length
2072
2576
  };
2577
+ const durationMs = Math.round(performance.now() - startTime);
2578
+ if (debugId) {
2579
+ debugEnd(debugId, "todo-write", {
2580
+ summary: stats,
2581
+ duration_ms: durationMs
2582
+ });
2583
+ }
2073
2584
  return {
2074
2585
  message: "Todo list updated successfully",
2075
2586
  stats
2076
2587
  };
2077
2588
  } catch (error) {
2078
- return {
2079
- error: error instanceof Error ? error.message : "Unknown error"
2080
- };
2589
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2590
+ if (debugId)
2591
+ debugError(debugId, "todo-write", errorMessage);
2592
+ return { error: errorMessage };
2081
2593
  }
2082
2594
  }
2083
2595
  });
@@ -2326,20 +2838,20 @@ function pruneMessagesByTokens(messages, config) {
2326
2838
  }
2327
2839
 
2328
2840
  // src/utils/compact-conversation.ts
2329
- async function compactConversation(messages, config, state = { conversationSummary: "" }) {
2841
+ async function compactConversation(messages, config, state2 = { conversationSummary: "" }) {
2330
2842
  const currentTokens = estimateMessagesTokens(messages);
2331
2843
  const threshold = config.compactionThreshold ?? 0.85;
2332
2844
  const limit = config.maxTokens * threshold;
2333
2845
  if (currentTokens < limit) {
2334
- return { messages, state, didCompact: false };
2846
+ return { messages, state: state2, didCompact: false };
2335
2847
  }
2336
2848
  const protectCount = config.protectRecentMessages ?? 10;
2337
2849
  const recentMessages = messages.slice(-protectCount);
2338
2850
  const oldMessages = messages.slice(0, -protectCount);
2339
2851
  if (oldMessages.length === 0) {
2340
- return { messages, state, didCompact: false };
2852
+ return { messages, state: state2, didCompact: false };
2341
2853
  }
2342
- const newSummary = await summarizeMessages(oldMessages, config.summarizerModel, config.taskContext, state.conversationSummary);
2854
+ const newSummary = await summarizeMessages(oldMessages, config.summarizerModel, config.taskContext, state2.conversationSummary);
2343
2855
  const compactedMessages = [
2344
2856
  {
2345
2857
  role: "user",
@@ -2874,10 +3386,13 @@ async function createDirectory(sandbox, path) {
2874
3386
  export {
2875
3387
  skillsToXml,
2876
3388
  setupAgentEnvironment,
3389
+ reinitDebugMode,
2877
3390
  pruneMessagesByTokens,
2878
3391
  parseSkillMetadata,
2879
3392
  loadSkillBundles,
2880
3393
  loadSkillBundle,
3394
+ isDebugEnabled,
3395
+ getDebugLogs,
2881
3396
  getContextStatus,
2882
3397
  fetchSkills,
2883
3398
  fetchSkill,
@@ -2909,6 +3424,7 @@ export {
2909
3424
  contextNeedsCompaction,
2910
3425
  contextNeedsAttention,
2911
3426
  compactConversation,
3427
+ clearDebugLogs,
2912
3428
  cached,
2913
3429
  anthropicPromptCacheMiddlewareV2,
2914
3430
  anthropicPromptCacheMiddleware,