bashkit 0.1.2 → 0.2.1

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
@@ -18,10 +18,8 @@ var __toESM = (mod, isNodeMode, target) => {
18
18
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
19
 
20
20
  // src/middleware/anthropic-cache.ts
21
- function ensureCacheMarker(message) {
22
- if (!message)
23
- return;
24
- if (!("content" in message))
21
+ function addCacheMarker(message) {
22
+ if (!message || !("content" in message))
25
23
  return;
26
24
  const content = message.content;
27
25
  if (!content || !Array.isArray(content))
@@ -36,21 +34,20 @@ function ensureCacheMarker(message) {
36
34
  }
37
35
  };
38
36
  }
37
+ function applyCacheMarkers(params) {
38
+ const messages = params.prompt;
39
+ if (!messages || messages.length === 0)
40
+ return params;
41
+ addCacheMarker(messages.at(-1));
42
+ addCacheMarker(messages.slice(0, -1).findLast((m) => m.role !== "assistant"));
43
+ return params;
44
+ }
45
+ var anthropicPromptCacheMiddlewareV2 = {
46
+ transformParams: async ({ params }) => applyCacheMarkers(params)
47
+ };
39
48
  var anthropicPromptCacheMiddleware = {
40
- transformParams: async ({
41
- params
42
- }) => {
43
- const messages = params.prompt;
44
- if (!messages || messages.length === 0) {
45
- return params;
46
- }
47
- ensureCacheMarker(messages.at(-1));
48
- ensureCacheMarker(messages.slice(0, -1).findLast((m) => m.role !== "assistant"));
49
- return {
50
- ...params,
51
- prompt: messages
52
- };
53
- }
49
+ specificationVersion: "v3",
50
+ transformParams: async ({ params }) => applyCacheMarkers(params)
54
51
  };
55
52
  // src/sandbox/e2b.ts
56
53
  import { Sandbox as E2BSandboxSDK } from "@e2b/code-interpreter";
@@ -349,6 +346,132 @@ function createVercelSandbox(config = {}) {
349
346
  }
350
347
  };
351
348
  }
349
+ // src/cache/lru.ts
350
+ class LRUCacheStore {
351
+ cache = new Map;
352
+ maxSize;
353
+ constructor(maxSize = 1000) {
354
+ this.maxSize = maxSize;
355
+ }
356
+ get(key) {
357
+ const entry = this.cache.get(key);
358
+ if (entry) {
359
+ this.cache.delete(key);
360
+ this.cache.set(key, entry);
361
+ }
362
+ return entry;
363
+ }
364
+ set(key, entry) {
365
+ if (this.cache.has(key)) {
366
+ this.cache.delete(key);
367
+ } else if (this.cache.size >= this.maxSize) {
368
+ const firstKey = this.cache.keys().next().value;
369
+ if (firstKey !== undefined) {
370
+ this.cache.delete(firstKey);
371
+ }
372
+ }
373
+ this.cache.set(key, entry);
374
+ }
375
+ delete(key) {
376
+ this.cache.delete(key);
377
+ }
378
+ clear() {
379
+ this.cache.clear();
380
+ }
381
+ size() {
382
+ return this.cache.size;
383
+ }
384
+ }
385
+ // src/cache/cached.ts
386
+ import { createHash } from "crypto";
387
+ function defaultKeyGenerator(toolName, params) {
388
+ const sortedKeys = params && typeof params === "object" ? Object.keys(params).sort() : undefined;
389
+ const serialized = JSON.stringify(params, sortedKeys);
390
+ const hash = createHash("sha256").update(serialized).digest("hex").slice(0, 16);
391
+ return `${toolName}:${hash}`;
392
+ }
393
+ function cached(tool, toolName, options = {}) {
394
+ const {
395
+ ttl = 5 * 60 * 1000,
396
+ store = new LRUCacheStore,
397
+ keyGenerator = defaultKeyGenerator,
398
+ debug = false,
399
+ onHit,
400
+ onMiss
401
+ } = options;
402
+ let hits = 0;
403
+ let misses = 0;
404
+ const log = debug ? console.log.bind(console) : () => {};
405
+ const cachedTool = {
406
+ ...tool,
407
+ execute: async (params, execOptions) => {
408
+ const key = keyGenerator(toolName, params);
409
+ const now = Date.now();
410
+ const entry = await store.get(key);
411
+ if (entry && now - entry.timestamp < ttl) {
412
+ hits++;
413
+ log(`[Cache] HIT ${toolName}:${key.slice(-8)}`);
414
+ onHit?.(toolName, key);
415
+ return entry.result;
416
+ }
417
+ misses++;
418
+ log(`[Cache] MISS ${toolName}:${key.slice(-8)}`);
419
+ onMiss?.(toolName, key);
420
+ if (!tool.execute) {
421
+ throw new Error(`Tool ${toolName} has no execute function`);
422
+ }
423
+ const result = await tool.execute(params, execOptions);
424
+ if (result && typeof result === "object" && !("error" in result)) {
425
+ await store.set(key, { result, timestamp: now });
426
+ log(`[Cache] STORED ${toolName}:${key.slice(-8)}`);
427
+ }
428
+ return result;
429
+ },
430
+ async getStats() {
431
+ const total = hits + misses;
432
+ const size = await store.size?.() ?? 0;
433
+ return {
434
+ hits,
435
+ misses,
436
+ hitRate: total > 0 ? hits / total : 0,
437
+ size
438
+ };
439
+ },
440
+ async clearCache(key) {
441
+ if (key) {
442
+ await store.delete(key);
443
+ } else {
444
+ await store.clear();
445
+ }
446
+ }
447
+ };
448
+ return cachedTool;
449
+ }
450
+ // src/cache/redis.ts
451
+ function createRedisCacheStore(client, options = {}) {
452
+ const { prefix = "bashkit:" } = options;
453
+ return {
454
+ async get(key) {
455
+ const data = await client.get(`${prefix}${key}`);
456
+ return data ? JSON.parse(data) : undefined;
457
+ },
458
+ async set(key, entry) {
459
+ await client.set(`${prefix}${key}`, JSON.stringify(entry));
460
+ },
461
+ async delete(key) {
462
+ await client.del(`${prefix}${key}`);
463
+ },
464
+ async clear() {
465
+ const keys = await client.keys(`${prefix}*`);
466
+ if (keys.length)
467
+ await client.del(keys);
468
+ },
469
+ async size() {
470
+ const keys = await client.keys(`${prefix}*`);
471
+ return keys.length;
472
+ }
473
+ };
474
+ }
352
475
  // src/types.ts
353
476
  var DEFAULT_CONFIG = {
354
477
  defaultTimeout: 120000,
@@ -361,10 +484,27 @@ var DEFAULT_CONFIG = {
361
484
  // src/tools/ask-user.ts
362
485
  import { tool, zodSchema } from "ai";
363
486
  import { z } from "zod";
487
+ var questionOptionSchema = z.object({
488
+ label: z.string().describe("The display text for this option. Should be concise (1-5 words). Add '(Recommended)' suffix for suggested options."),
489
+ description: z.string().optional().describe("Explanation of what this option means or its implications.")
490
+ });
491
+ var structuredQuestionSchema = z.object({
492
+ header: z.string().optional().describe("Very short label displayed as a chip/tag (max 12 chars). Examples: 'Auth method', 'Library', 'Approach'."),
493
+ question: z.string().describe("The complete question to ask the user. Should be clear and specific."),
494
+ 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."),
495
+ multiSelect: z.boolean().optional().describe("Set to true to allow the user to select multiple options instead of just one.")
496
+ });
364
497
  var askUserInputSchema = z.object({
365
- question: z.string().describe("The question to ask the user. Be specific and concise.")
498
+ question: z.string().optional().describe("Simple question string (for backward compatibility). Use 'questions' for structured multi-choice."),
499
+ questions: z.array(structuredQuestionSchema).min(1).max(4).optional().describe("Structured questions with options (1-4 questions).")
366
500
  });
367
- var ASK_USER_DESCRIPTION = `Ask the user a clarifying question when you need more information to proceed.
501
+ var ASK_USER_DESCRIPTION = `Use this tool when you need to ask the user questions during execution.
502
+
503
+ **Capabilities:**
504
+ - Gather user preferences or requirements
505
+ - Clarify ambiguous instructions
506
+ - Get decisions on implementation choices
507
+ - Offer choices about what direction to take
368
508
 
369
509
  **When to use:**
370
510
  - You need clarification on ambiguous requirements
@@ -377,23 +517,54 @@ var ASK_USER_DESCRIPTION = `Ask the user a clarifying question when you need mor
377
517
  - The question is trivial or can be inferred
378
518
  - You're just being overly cautious
379
519
 
380
- Keep questions specific and actionable. Avoid yes/no questions when you need details.`;
381
- function createAskUserTool(onQuestion) {
520
+ **Simple question format:**
521
+ Use the 'question' parameter for a single free-form question.
522
+
523
+ **Structured questions format:**
524
+ Use the 'questions' parameter for multiple-choice questions with options:
525
+ - 1-4 questions allowed
526
+ - Each question can have 2-4 options with labels and descriptions
527
+ - Use multiSelect: true to allow multiple answers
528
+ - Users can always select "Other" to provide custom text input
529
+ - Place recommended option first and add "(Recommended)" to label`;
530
+ function createAskUserTool(config) {
531
+ const normalizedConfig = typeof config === "function" ? { onQuestion: config } : config ?? {};
382
532
  return tool({
383
533
  description: ASK_USER_DESCRIPTION,
384
534
  inputSchema: zodSchema(askUserInputSchema),
385
- execute: async ({
386
- question
387
- }) => {
535
+ execute: async (input) => {
388
536
  try {
389
- if (onQuestion) {
390
- const answer = await onQuestion(question);
391
- return { answer };
537
+ if (!input.question && !input.questions) {
538
+ return {
539
+ error: "Either 'question' or 'questions' must be provided"
540
+ };
392
541
  }
393
- return {
394
- question,
395
- awaiting_response: true
396
- };
542
+ if (input.questions && input.questions.length > 0) {
543
+ if (normalizedConfig.onStructuredQuestions) {
544
+ const answers = await normalizedConfig.onStructuredQuestions(input.questions);
545
+ const firstKey = Object.keys(answers)[0];
546
+ const firstAnswer = answers[firstKey];
547
+ return {
548
+ answer: Array.isArray(firstAnswer) ? firstAnswer.join(", ") : firstAnswer,
549
+ answers
550
+ };
551
+ }
552
+ return {
553
+ questions: input.questions,
554
+ awaiting_response: true
555
+ };
556
+ }
557
+ if (input.question) {
558
+ if (normalizedConfig.onQuestion) {
559
+ const answer = await normalizedConfig.onQuestion(input.question);
560
+ return { answer };
561
+ }
562
+ return {
563
+ question: input.question,
564
+ awaiting_response: true
565
+ };
566
+ }
567
+ return { error: "No question provided" };
397
568
  } catch (error) {
398
569
  return {
399
570
  error: error instanceof Error ? error.message : "Unknown error"
@@ -412,21 +583,49 @@ var bashInputSchema = z2.object({
412
583
  description: z2.string().optional().describe("Clear, concise description of what this command does in 5-10 words"),
413
584
  run_in_background: z2.boolean().optional().describe("Set to true to run this command in the background")
414
585
  });
415
- var BASH_DESCRIPTION = `Executes bash commands in a persistent shell session with optional timeout and background execution.
586
+ var BASH_DESCRIPTION = `Executes a bash command in a persistent shell session with optional timeout.
416
587
 
417
- **Important guidelines:**
418
- - Always quote file paths containing spaces with double quotes (e.g., cd "/path/with spaces")
419
- - Avoid using search commands like \`find\` and \`grep\` - use the Glob and Grep tools instead
420
- - Avoid using \`cat\`, \`head\`, \`tail\` - use the Read tool instead
421
- - When issuing multiple commands, use \`;\` or \`&&\` to separate them (not newlines)
422
- - If output exceeds 30000 characters, it will be truncated
423
- - Default timeout is 2 minutes; maximum is 10 minutes`;
588
+ IMPORTANT: For file operations (reading, writing, editing, searching, finding files) - use the specialized tools instead of bash commands.
589
+
590
+ Before executing the command, please follow these steps:
591
+
592
+ 1. Directory Verification:
593
+ - If the command will create new directories or files, first use \`ls\` to verify the parent directory exists and is the correct location
594
+ - For example, before running "mkdir foo/bar", first use \`ls foo\` to check that "foo" exists
595
+
596
+ 2. Command Execution:
597
+ - Always quote file paths that contain spaces with double quotes (e.g., cd "/path/with spaces")
598
+ - Examples of proper quoting:
599
+ - cd "/Users/name/My Documents" (correct)
600
+ - cd /Users/name/My Documents (incorrect - will fail)
601
+ - After ensuring proper quoting, execute the command
602
+
603
+ Usage notes:
604
+ - The command argument is required
605
+ - You can specify an optional timeout in milliseconds (max 600000ms / 10 minutes). Default is 120000ms (2 minutes).
606
+ - It is very helpful if you write a clear, concise description of what this command does in 5-10 words
607
+ - If the output exceeds 30000 characters, output will be truncated
608
+ - Avoid using \`find\`, \`grep\`, \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands. Instead, use dedicated tools:
609
+ - File search: Use Glob (NOT find or ls)
610
+ - Content search: Use Grep (NOT grep or rg)
611
+ - Read files: Use Read (NOT cat/head/tail)
612
+ - Edit files: Use Edit (NOT sed/awk)
613
+ - Write files: Use Write (NOT echo >/cat <<EOF)
614
+ - When issuing multiple commands:
615
+ - If commands are independent, make multiple Bash tool calls in parallel
616
+ - If commands depend on each other, use '&&' to chain them (e.g., \`git add . && git commit -m "message"\`)
617
+ - Use ';' only when you need sequential execution but don't care if earlier commands fail
618
+ - DO NOT use newlines to separate commands
619
+ - Try to maintain your current working directory by using absolute paths and avoiding \`cd\``;
424
620
  function createBashTool(sandbox, config) {
425
621
  const maxOutputLength = config?.maxOutputLength ?? 30000;
426
622
  const defaultTimeout = config?.timeout ?? 120000;
427
623
  return tool2({
428
624
  description: BASH_DESCRIPTION,
429
625
  inputSchema: zodSchema2(bashInputSchema),
626
+ strict: config?.strict,
627
+ needsApproval: config?.needsApproval,
628
+ providerOptions: config?.providerOptions,
430
629
  execute: async ({
431
630
  command,
432
631
  timeout,
@@ -482,10 +681,30 @@ var editInputSchema = z3.object({
482
681
  new_string: z3.string().describe("The text to replace it with (must be different from old_string)"),
483
682
  replace_all: z3.boolean().optional().describe("Replace all occurrences of old_string (default false)")
484
683
  });
684
+ var EDIT_DESCRIPTION = `Performs exact string replacements in files.
685
+
686
+ **Important guidelines:**
687
+ - You MUST use the Read tool first before editing any file
688
+ - Preserve exact indentation (tabs/spaces) when replacing text
689
+ - The old_string must be unique in the file, or the edit will fail
690
+ - If old_string appears multiple times, either provide more context to make it unique, or use replace_all=true
691
+
692
+ **Parameters:**
693
+ - old_string: The exact text to find and replace (must match exactly, including whitespace)
694
+ - new_string: The replacement text (must be different from old_string)
695
+ - replace_all: Set to true to replace all occurrences (useful for renaming variables)
696
+
697
+ **When to use:**
698
+ - Making targeted changes to existing files
699
+ - Renaming variables or functions (with replace_all=true)
700
+ - Updating specific sections`;
485
701
  function createEditTool(sandbox, config) {
486
702
  return tool3({
487
- description: "Performs exact string replacements in files.",
703
+ description: EDIT_DESCRIPTION,
488
704
  inputSchema: zodSchema3(editInputSchema),
705
+ strict: config?.strict,
706
+ needsApproval: config?.needsApproval,
707
+ providerOptions: config?.providerOptions,
489
708
  execute: async ({
490
709
  file_path,
491
710
  old_string,
@@ -546,24 +765,61 @@ import { z as z4 } from "zod";
546
765
  var enterPlanModeInputSchema = z4.object({
547
766
  reason: z4.string().describe("Brief explanation of why you're entering planning mode (e.g., 'Need to explore codebase architecture before implementing feature')")
548
767
  });
549
- var ENTER_PLAN_MODE_DESCRIPTION = `Enter planning mode to explore and design implementation approaches before making changes.
768
+ var ENTER_PLAN_MODE_DESCRIPTION = `Use this tool proactively when you're about to start a non-trivial task. Getting user sign-off on your approach prevents wasted effort and ensures alignment. This tool transitions you into plan mode where you can explore and design an approach for user approval.
550
769
 
551
- **When to use:**
552
- - Complex tasks requiring research or exploration
553
- - Need to understand codebase structure before implementing
554
- - Multiple approaches possible and you need to evaluate trade-offs
555
- - User explicitly asks you to plan before executing
770
+ ## When to Use This Tool
556
771
 
557
- **In planning mode:**
558
- - Focus on reading, searching, and understanding
559
- - Avoid making file changes (use Read, Grep, Glob instead of Write, Edit)
560
- - Document your findings and proposed approach
561
- - Use ExitPlanMode when ready with a plan for user approval
772
+ **Prefer using EnterPlanMode** for tasks unless they're simple. Use it when ANY of these conditions apply:
562
773
 
563
- **When NOT to use:**
564
- - Simple, well-defined tasks
565
- - You already understand the codebase and approach
566
- - User wants immediate execution`;
774
+ 1. **New Functionality**: Adding meaningful new capabilities
775
+ - Example: "Add a new report" - what format? What data?
776
+ - Example: "Add validation" - what rules? What error messages?
777
+
778
+ 2. **Multiple Valid Approaches**: The task can be solved in several different ways
779
+ - Example: "Add caching" - could use Redis, in-memory, file-based, etc.
780
+ - Example: "Improve performance" - many optimization strategies possible
781
+
782
+ 3. **Modifications**: Changes that affect existing behavior or structure
783
+ - Example: "Update the workflow" - what exactly should change?
784
+ - Example: "Refactor this component" - what's the target architecture?
785
+
786
+ 4. **Architectural Decisions**: The task requires choosing between patterns or technologies
787
+ - Example: "Add real-time updates" - WebSockets vs SSE vs polling
788
+ - Example: "Implement state management" - different approaches possible
789
+
790
+ 5. **Multi-File Changes**: The task will likely touch more than 2-3 files
791
+
792
+ 6. **Unclear Requirements**: You need to explore before understanding the full scope
793
+ - Example: "Make this faster" - need to profile and identify bottlenecks
794
+ - Example: "Fix the bug" - need to investigate root cause
795
+
796
+ 7. **User Preferences Matter**: The approach could reasonably go multiple ways
797
+ - If you would use AskUser to clarify the approach, use EnterPlanMode instead
798
+ - Plan mode lets you explore first, then present options with context
799
+
800
+ ## When NOT to Use This Tool
801
+
802
+ Only skip EnterPlanMode for simple tasks:
803
+ - Single-line or few-line fixes (typos, obvious bugs, small tweaks)
804
+ - Adding a single function with clear requirements
805
+ - Tasks where the user has given very specific, detailed instructions
806
+ - Pure research/exploration tasks
807
+
808
+ ## What Happens in Plan Mode
809
+
810
+ In plan mode, you'll:
811
+ 1. Thoroughly explore using Glob, Grep, and Read tools
812
+ 2. Understand existing patterns and architecture
813
+ 3. Design an approach
814
+ 4. Present your plan to the user for approval
815
+ 5. Use AskUser if you need to clarify approaches
816
+ 6. Exit plan mode with ExitPlanMode when ready to execute
817
+
818
+ ## Important Notes
819
+
820
+ - This tool REQUIRES user approval - they must consent to entering plan mode
821
+ - If unsure whether to use it, err on the side of planning - it's better to get alignment upfront than to redo work
822
+ - Users appreciate being consulted before significant changes`;
567
823
  function createEnterPlanModeTool(state, onEnter) {
568
824
  return tool4({
569
825
  description: ENTER_PLAN_MODE_DESCRIPTION,
@@ -602,9 +858,30 @@ import { z as z5 } from "zod";
602
858
  var exitPlanModeInputSchema = z5.object({
603
859
  plan: z5.string().describe("The plan to present to the user for approval")
604
860
  });
605
- function createExitPlanModeTool(config, onPlanSubmit) {
861
+ var EXIT_PLAN_MODE_DESCRIPTION = `Use this tool when you are in plan mode and have finished planning and are ready for user approval.
862
+
863
+ ## How This Tool Works
864
+ - Pass your completed plan as a parameter
865
+ - This tool signals that you're done planning and ready for the user to review
866
+ - The user will see your plan and can approve or request changes
867
+
868
+ ## When to Use This Tool
869
+ IMPORTANT: Only use this tool when the task requires planning implementation steps. For research tasks where you're gathering information, searching files, or understanding the codebase - do NOT use this tool.
870
+
871
+ ## Handling Ambiguity in Plans
872
+ Before using this tool, ensure your plan is clear and unambiguous. If there are multiple valid approaches or unclear requirements:
873
+ 1. Ask the user to clarify (use AskUser tool if available)
874
+ 2. Ask about specific implementation choices (e.g., architectural patterns, which library to use)
875
+ 3. Clarify any assumptions that could affect the implementation
876
+ 4. Only proceed with ExitPlanMode after resolving ambiguities
877
+
878
+ ## Examples
879
+ 1. "Search for and understand the implementation of X" - Do NOT use this tool (research task)
880
+ 2. "Help me implement feature Y" - Use this tool after planning the implementation steps
881
+ 3. "Add user authentication" - If unsure about approach (OAuth vs JWT), clarify first, then use this tool`;
882
+ function createExitPlanModeTool(onPlanSubmit) {
606
883
  return tool5({
607
- description: "Exits planning mode and prompts the user to approve the plan. Use this when you have finished planning and want user confirmation before proceeding.",
884
+ description: EXIT_PLAN_MODE_DESCRIPTION,
608
885
  inputSchema: zodSchema5(exitPlanModeInputSchema),
609
886
  execute: async ({
610
887
  plan
@@ -634,10 +911,21 @@ var globInputSchema = z6.object({
634
911
  pattern: z6.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js", "*.md")'),
635
912
  path: z6.string().optional().describe("Directory to search in (defaults to working directory)")
636
913
  });
914
+ var GLOB_DESCRIPTION = `
915
+ - Fast file pattern matching tool that works with any codebase size
916
+ - Supports glob patterns like "**/*.js" or "src/**/*.ts"
917
+ - Returns matching file paths sorted by modification time
918
+ - Use this tool when you need to find files by name patterns
919
+ - When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Task tool instead
920
+ - It is always better to speculatively perform multiple searches in parallel if they are potentially useful
921
+ `;
637
922
  function createGlobTool(sandbox, config) {
638
923
  return tool6({
639
- description: "Search for files matching a glob pattern. Returns file paths sorted by modification time. Use this instead of `find` command.",
924
+ description: GLOB_DESCRIPTION,
640
925
  inputSchema: zodSchema6(globInputSchema),
926
+ strict: config?.strict,
927
+ needsApproval: config?.needsApproval,
928
+ providerOptions: config?.providerOptions,
641
929
  execute: async ({
642
930
  pattern,
643
931
  path
@@ -674,36 +962,64 @@ function createGlobTool(sandbox, config) {
674
962
  import { tool as tool7, zodSchema as zodSchema7 } from "ai";
675
963
  import { z as z7 } from "zod";
676
964
  var grepInputSchema = z7.object({
677
- pattern: z7.string().describe("The regular expression pattern to search for"),
965
+ pattern: z7.string().describe("The regular expression pattern to search for in file contents"),
678
966
  path: z7.string().optional().describe("File or directory to search in (defaults to cwd)"),
679
- glob: z7.string().optional().describe('Glob pattern to filter files (e.g. "*.js")'),
967
+ glob: z7.string().optional().describe('Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}")'),
680
968
  type: z7.string().optional().describe('File type to search (e.g. "js", "py", "rust")'),
681
- output_mode: z7.enum(["content", "files_with_matches", "count"]).optional().describe('Output mode: "content", "files_with_matches", or "count"'),
969
+ 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'),
682
970
  "-i": z7.boolean().optional().describe("Case insensitive search"),
683
- "-n": z7.boolean().optional().describe("Show line numbers (for content mode)"),
684
- "-B": z7.number().optional().describe("Lines to show before each match"),
685
- "-A": z7.number().optional().describe("Lines to show after each match"),
686
- "-C": z7.number().optional().describe("Lines to show before and after each match"),
687
- head_limit: z7.number().optional().describe("Limit output to first N lines/entries"),
688
- multiline: z7.boolean().optional().describe("Enable multiline mode")
971
+ "-n": z7.boolean().optional().describe("Show line numbers in output. Requires output_mode: 'content'. Defaults to true."),
972
+ "-B": z7.number().optional().describe("Number of lines to show before each match. Requires output_mode: 'content'."),
973
+ "-A": z7.number().optional().describe("Number of lines to show after each match. Requires output_mode: 'content'."),
974
+ "-C": z7.number().optional().describe("Number of lines to show before and after each match. Requires output_mode: 'content'."),
975
+ head_limit: z7.number().optional().describe("Limit output to first N lines/entries. Works across all output modes. Defaults to 0 (unlimited)."),
976
+ offset: z7.number().optional().describe("Skip first N lines/entries before applying head_limit. Works across all output modes. Defaults to 0."),
977
+ multiline: z7.boolean().optional().describe("Enable multiline mode where patterns can span lines (requires ripgrep). Default: false.")
689
978
  });
979
+ var GREP_DESCRIPTION = `A powerful content search tool with regex support. Use this instead of running grep commands directly.
980
+
981
+ **Usage:**
982
+ - ALWAYS use Grep for search tasks. NEVER invoke \`grep\` or \`rg\` as a Bash command.
983
+ - Supports regex syntax (e.g., "log.*Error", "function\\s+\\w+")
984
+ - Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
985
+
986
+ **Output modes:**
987
+ - "content": Shows matching lines with optional context
988
+ - "files_with_matches": Shows only file paths (default)
989
+ - "count": Shows match counts per file
990
+
991
+ **Context options (content mode only):**
992
+ - -B: Lines to show before each match
993
+ - -A: Lines to show after each match
994
+ - -C: Lines to show before and after each match
995
+
996
+ **Pagination:**
997
+ - Use offset to skip results (useful for pagination)
998
+ - Use head_limit to limit total results returned
999
+
1000
+ **Note:** Set useRipgrep: true in config for better performance and multiline support (requires ripgrep installed).`;
690
1001
  function createGrepTool(sandbox, config) {
1002
+ const useRipgrep = config?.useRipgrep ?? false;
691
1003
  return tool7({
692
- description: "Powerful search tool built on ripgrep with regex support. Use this instead of the grep command.",
1004
+ description: GREP_DESCRIPTION,
693
1005
  inputSchema: zodSchema7(grepInputSchema),
1006
+ strict: config?.strict,
1007
+ needsApproval: config?.needsApproval,
1008
+ providerOptions: config?.providerOptions,
694
1009
  execute: async (input) => {
695
1010
  const {
696
1011
  pattern,
697
1012
  path,
698
1013
  glob,
699
1014
  type,
700
- output_mode = "content",
1015
+ output_mode = "files_with_matches",
701
1016
  "-i": caseInsensitive,
702
1017
  "-n": showLineNumbers = true,
703
1018
  "-B": beforeContext,
704
1019
  "-A": afterContext,
705
1020
  "-C": context,
706
1021
  head_limit,
1022
+ offset = 0,
707
1023
  multiline
708
1024
  } = input;
709
1025
  const searchPath = path || ".";
@@ -713,32 +1029,52 @@ function createGrepTool(sandbox, config) {
713
1029
  return { error: `Path not allowed: ${searchPath}` };
714
1030
  }
715
1031
  }
1032
+ if (multiline && !useRipgrep) {
1033
+ return {
1034
+ error: "Multiline mode requires ripgrep. Set useRipgrep: true in config."
1035
+ };
1036
+ }
716
1037
  try {
717
- const flags = [];
718
- if (caseInsensitive)
719
- flags.push("-i");
720
- if (showLineNumbers && output_mode === "content")
721
- flags.push("-n");
722
- if (multiline)
723
- flags.push("-U");
724
- if (context) {
725
- flags.push(`-C ${context}`);
726
- } else {
727
- if (beforeContext)
728
- flags.push(`-B ${beforeContext}`);
729
- if (afterContext)
730
- flags.push(`-A ${afterContext}`);
1038
+ let paginationSuffix = "";
1039
+ if (offset > 0) {
1040
+ paginationSuffix += ` | tail -n +${offset + 1}`;
1041
+ }
1042
+ if (head_limit && head_limit > 0) {
1043
+ paginationSuffix += ` | head -${head_limit}`;
731
1044
  }
732
- if (glob)
733
- flags.push(`--include="${glob}"`);
734
- if (type)
735
- flags.push(`--include="*.${type}"`);
736
- const flagStr = flags.join(" ");
737
- const limit = head_limit || 1000;
738
1045
  let cmd;
1046
+ if (useRipgrep) {
1047
+ cmd = buildRipgrepCommand({
1048
+ pattern,
1049
+ searchPath,
1050
+ output_mode,
1051
+ caseInsensitive,
1052
+ showLineNumbers,
1053
+ beforeContext,
1054
+ afterContext,
1055
+ context,
1056
+ glob,
1057
+ type,
1058
+ multiline,
1059
+ paginationSuffix
1060
+ });
1061
+ } else {
1062
+ cmd = buildGrepCommand({
1063
+ pattern,
1064
+ searchPath,
1065
+ output_mode,
1066
+ caseInsensitive,
1067
+ showLineNumbers,
1068
+ beforeContext,
1069
+ afterContext,
1070
+ context,
1071
+ glob,
1072
+ type,
1073
+ paginationSuffix
1074
+ });
1075
+ }
1076
+ const result = await sandbox.exec(cmd, { timeout: config?.timeout });
739
1077
  if (output_mode === "files_with_matches") {
740
- cmd = `grep -rl ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | head -${limit}`;
741
- const result = await sandbox.exec(cmd, { timeout: config?.timeout });
742
1078
  const files = result.stdout.split(`
743
1079
  `).filter(Boolean);
744
1080
  return {
@@ -746,8 +1082,6 @@ function createGrepTool(sandbox, config) {
746
1082
  count: files.length
747
1083
  };
748
1084
  } else if (output_mode === "count") {
749
- cmd = `grep -rc ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | grep -v ':0$' | head -${limit}`;
750
- const result = await sandbox.exec(cmd, { timeout: config?.timeout });
751
1085
  const lines = result.stdout.split(`
752
1086
  `).filter(Boolean);
753
1087
  const counts = lines.map((line) => {
@@ -763,8 +1097,6 @@ function createGrepTool(sandbox, config) {
763
1097
  total
764
1098
  };
765
1099
  } else {
766
- cmd = `grep -rn ${flagStr} "${pattern}" ${searchPath} 2>/dev/null | head -${limit}`;
767
- const result = await sandbox.exec(cmd, { timeout: config?.timeout });
768
1100
  if (!result.stdout.trim()) {
769
1101
  return {
770
1102
  matches: [],
@@ -798,6 +1130,66 @@ function createGrepTool(sandbox, config) {
798
1130
  }
799
1131
  });
800
1132
  }
1133
+ function buildRipgrepCommand(opts) {
1134
+ const flags = [];
1135
+ if (opts.caseInsensitive)
1136
+ flags.push("-i");
1137
+ if (opts.multiline)
1138
+ flags.push("-U", "--multiline-dotall");
1139
+ if (opts.output_mode === "content") {
1140
+ if (opts.showLineNumbers)
1141
+ flags.push("-n");
1142
+ if (opts.context) {
1143
+ flags.push(`-C ${opts.context}`);
1144
+ } else {
1145
+ if (opts.beforeContext)
1146
+ flags.push(`-B ${opts.beforeContext}`);
1147
+ if (opts.afterContext)
1148
+ flags.push(`-A ${opts.afterContext}`);
1149
+ }
1150
+ }
1151
+ if (opts.glob)
1152
+ flags.push(`-g "${opts.glob}"`);
1153
+ if (opts.type)
1154
+ flags.push(`-t ${opts.type}`);
1155
+ const flagStr = flags.join(" ");
1156
+ if (opts.output_mode === "files_with_matches") {
1157
+ return `rg -l ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1158
+ } else if (opts.output_mode === "count") {
1159
+ return `rg -c ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1160
+ } else {
1161
+ return `rg ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1162
+ }
1163
+ }
1164
+ function buildGrepCommand(opts) {
1165
+ const flags = ["-r"];
1166
+ if (opts.caseInsensitive)
1167
+ flags.push("-i");
1168
+ if (opts.output_mode === "content") {
1169
+ if (opts.showLineNumbers)
1170
+ flags.push("-n");
1171
+ if (opts.context) {
1172
+ flags.push(`-C ${opts.context}`);
1173
+ } else {
1174
+ if (opts.beforeContext)
1175
+ flags.push(`-B ${opts.beforeContext}`);
1176
+ if (opts.afterContext)
1177
+ flags.push(`-A ${opts.afterContext}`);
1178
+ }
1179
+ }
1180
+ if (opts.glob)
1181
+ flags.push(`--include="${opts.glob}"`);
1182
+ if (opts.type)
1183
+ flags.push(`--include="*.${opts.type}"`);
1184
+ const flagStr = flags.join(" ");
1185
+ if (opts.output_mode === "files_with_matches") {
1186
+ return `grep -l ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1187
+ } else if (opts.output_mode === "count") {
1188
+ return `grep -c ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null | grep -v ':0$'${opts.paginationSuffix}`;
1189
+ } else {
1190
+ return `grep ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1191
+ }
1192
+ }
801
1193
 
802
1194
  // src/tools/read.ts
803
1195
  import { tool as tool8, zodSchema as zodSchema8 } from "ai";
@@ -807,10 +1199,25 @@ var readInputSchema = z8.object({
807
1199
  offset: z8.number().optional().describe("Line number to start reading from (1-indexed)"),
808
1200
  limit: z8.number().optional().describe("Maximum number of lines to read")
809
1201
  });
1202
+ var READ_DESCRIPTION = `Reads a file from the local filesystem. You can access any file directly by using this tool.
1203
+ 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.
1204
+
1205
+ Usage:
1206
+ - The file_path parameter must be an absolute path, not a relative path
1207
+ - By default, it reads up to 500 lines starting from the beginning of the file
1208
+ - You can optionally specify a line offset and limit (especially handy for long files)
1209
+ - Results are returned with line numbers starting at 1
1210
+ - This tool can only read text files, not binary files (images, PDFs, etc.)
1211
+ - This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
1212
+ - It is always better to speculatively read multiple potentially useful files in parallel
1213
+ - If you read a file that exists but has empty contents you will receive a warning in place of file contents`;
810
1214
  function createReadTool(sandbox, config) {
811
1215
  return tool8({
812
- description: "Read the contents of a file or list directory entries. For text files, returns numbered lines with total line count. For directories, returns file/folder names. Use this instead of `cat`, `head`, or `tail` commands.",
1216
+ description: READ_DESCRIPTION,
813
1217
  inputSchema: zodSchema8(readInputSchema),
1218
+ strict: config?.strict,
1219
+ needsApproval: config?.needsApproval,
1220
+ providerOptions: config?.providerOptions,
814
1221
  execute: async ({
815
1222
  file_path,
816
1223
  offset,
@@ -967,16 +1374,39 @@ function createSkillTool(config) {
967
1374
  import { generateText, tool as tool10, zodSchema as zodSchema10 } from "ai";
968
1375
  import Parallel from "parallel-web";
969
1376
  import { z as z10 } from "zod";
1377
+
1378
+ // src/utils/http-constants.ts
1379
+ var RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503];
1380
+
1381
+ // src/tools/web-fetch.ts
970
1382
  var webFetchInputSchema = z10.object({
971
1383
  url: z10.string().describe("The URL to fetch content from"),
972
1384
  prompt: z10.string().describe("The prompt to run on the fetched content")
973
1385
  });
974
- var RETRYABLE_CODES = [408, 429, 500, 502, 503];
1386
+ var WEB_FETCH_DESCRIPTION = `
1387
+ - Fetches content from a specified URL and processes it using an AI model
1388
+ - Takes a URL and a prompt as input
1389
+ - Fetches the URL content and extracts text
1390
+ - Processes the content with the prompt using the configured model
1391
+ - Returns the model's response about the content
1392
+ - Use this tool when you need to retrieve and analyze web content
1393
+
1394
+ Usage notes:
1395
+ - The URL must be a fully-formed valid URL
1396
+ - HTTP URLs will be automatically upgraded to HTTPS
1397
+ - The prompt should describe what information you want to extract from the page
1398
+ - This tool is read-only and does not modify any files
1399
+ - Results may be summarized if the content is very large
1400
+ - When a URL redirects to a different host, the tool will inform you and provide the redirect URL. You should then make a new WebFetch request with the redirect URL to fetch the content.
1401
+ `;
975
1402
  function createWebFetchTool(config) {
976
- const { apiKey, model } = config;
1403
+ const { apiKey, model, strict, needsApproval, providerOptions } = config;
977
1404
  return tool10({
978
- description: "Fetches content from a URL and processes it with an AI model. Use this to analyze web pages, extract information, or summarize content.",
1405
+ description: WEB_FETCH_DESCRIPTION,
979
1406
  inputSchema: zodSchema10(webFetchInputSchema),
1407
+ strict,
1408
+ needsApproval,
1409
+ providerOptions,
980
1410
  execute: async (input) => {
981
1411
  const { url, prompt } = input;
982
1412
  try {
@@ -1024,7 +1454,7 @@ ${content}`
1024
1454
  return {
1025
1455
  error: message,
1026
1456
  status_code: statusCode,
1027
- retryable: RETRYABLE_CODES.includes(statusCode)
1457
+ retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
1028
1458
  };
1029
1459
  }
1030
1460
  return {
@@ -1044,12 +1474,34 @@ var webSearchInputSchema = z11.object({
1044
1474
  allowed_domains: z11.array(z11.string()).optional().describe("Only include results from these domains"),
1045
1475
  blocked_domains: z11.array(z11.string()).optional().describe("Never include results from these domains")
1046
1476
  });
1047
- var RETRYABLE_CODES2 = [408, 429, 500, 502, 503];
1477
+ var WEB_SEARCH_DESCRIPTION = `Searches the web and returns results with links. Use this for accessing up-to-date information beyond your knowledge cutoff.
1478
+
1479
+ **Capabilities:**
1480
+ - Provides current information for recent events and data
1481
+ - Returns results formatted with titles, URLs, and snippets
1482
+ - Supports domain filtering to include or block specific websites
1483
+
1484
+ **CRITICAL REQUIREMENT - You MUST follow this:**
1485
+ After answering using search results, you MUST include a "Sources:" section listing relevant URLs as markdown hyperlinks:
1486
+
1487
+ Sources:
1488
+ - [Source Title 1](https://example.com/1)
1489
+ - [Source Title 2](https://example.com/2)
1490
+
1491
+ **Important - Use the correct year:**
1492
+ When searching for recent information, documentation, or current events, use the current year in your query (e.g., "React documentation 2025" not "2024").
1493
+
1494
+ **Domain filtering:**
1495
+ - allowed_domains: Only include results from these domains
1496
+ - blocked_domains: Never include results from these domains`;
1048
1497
  function createWebSearchTool(config) {
1049
- const { apiKey } = config;
1498
+ const { apiKey, strict, needsApproval, providerOptions } = config;
1050
1499
  return tool11({
1051
- description: "Searches the web and returns formatted results. Use this to find current information, documentation, articles, and more.",
1500
+ description: WEB_SEARCH_DESCRIPTION,
1052
1501
  inputSchema: zodSchema11(webSearchInputSchema),
1502
+ strict,
1503
+ needsApproval,
1504
+ providerOptions,
1053
1505
  execute: async (input) => {
1054
1506
  const { query, allowed_domains, blocked_domains } = input;
1055
1507
  try {
@@ -1083,7 +1535,7 @@ function createWebSearchTool(config) {
1083
1535
  return {
1084
1536
  error: message,
1085
1537
  status_code: statusCode,
1086
- retryable: RETRYABLE_CODES2.includes(statusCode)
1538
+ retryable: RETRYABLE_STATUS_CODES.includes(statusCode)
1087
1539
  };
1088
1540
  }
1089
1541
  return {
@@ -1101,10 +1553,25 @@ var writeInputSchema = z12.object({
1101
1553
  file_path: z12.string().describe("Path to the file to write"),
1102
1554
  content: z12.string().describe("Content to write to the file")
1103
1555
  });
1556
+ var WRITE_DESCRIPTION = `Writes content to a file on the filesystem.
1557
+
1558
+ **Important guidelines:**
1559
+ - This tool will overwrite existing files at the provided path
1560
+ - If modifying an existing file, you MUST use the Read tool first to read the file's contents
1561
+ - ALWAYS prefer editing existing files over creating new ones
1562
+ - NEVER proactively create documentation files (*.md) or README files unless explicitly requested
1563
+ - The file_path must be an absolute path, not relative
1564
+
1565
+ **When to use Write vs Edit:**
1566
+ - Use Write for creating new files or completely replacing file contents
1567
+ - Use Edit for making targeted changes to existing files (preferred for modifications)`;
1104
1568
  function createWriteTool(sandbox, config) {
1105
1569
  return tool12({
1106
- description: "Write content to a file. Creates the file if it does not exist, overwrites if it does.",
1570
+ description: WRITE_DESCRIPTION,
1107
1571
  inputSchema: zodSchema12(writeInputSchema),
1572
+ strict: config?.strict,
1573
+ needsApproval: config?.needsApproval,
1574
+ providerOptions: config?.providerOptions,
1108
1575
  execute: async ({
1109
1576
  file_path,
1110
1577
  content
@@ -1148,8 +1615,32 @@ import { z as z13 } from "zod";
1148
1615
  var taskInputSchema = z13.object({
1149
1616
  description: z13.string().describe("A short (3-5 word) description of the task"),
1150
1617
  prompt: z13.string().describe("The task for the agent to perform"),
1151
- subagent_type: z13.string().describe("The type of specialized agent to use for this task")
1618
+ subagent_type: z13.string().describe("The type of specialized agent to use for this task"),
1619
+ 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."),
1620
+ 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.")
1152
1621
  });
1622
+ var TASK_DESCRIPTION = `Launch a new agent to handle complex, multi-step tasks autonomously.
1623
+
1624
+ The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
1625
+
1626
+ **Subagent types:**
1627
+ - Use predefined subagent_type values for common task patterns
1628
+ - For dynamic agents: provide custom system_prompt and/or tools to create a specialized agent on the fly
1629
+
1630
+ **When NOT to use the Task tool:**
1631
+ - If you want to read a specific file path, use the Read or Glob tool instead, to find the match more quickly
1632
+ - If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly
1633
+ - If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead, to find the match more quickly
1634
+ - Other tasks that are not related to the available agent types
1635
+
1636
+ **Usage notes:**
1637
+ - Always include a short description (3-5 words) summarizing what the agent will do
1638
+ - Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
1639
+ - When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
1640
+ - Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
1641
+ - The agent's outputs should generally be trusted
1642
+ - Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
1643
+ - If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.`;
1153
1644
  var eventCounter = 0;
1154
1645
  function generateEventId() {
1155
1646
  return `subagent-${Date.now()}-${++eventCounter}`;
@@ -1175,19 +1666,21 @@ function createTaskTool(config) {
1175
1666
  streamWriter
1176
1667
  } = config;
1177
1668
  return tool13({
1178
- description: "Launches a new agent to handle complex, multi-step tasks autonomously. Use this for tasks that require multiple steps, research, or specialized expertise.",
1669
+ description: TASK_DESCRIPTION,
1179
1670
  inputSchema: zodSchema13(taskInputSchema),
1180
1671
  execute: async ({
1181
1672
  description,
1182
1673
  prompt,
1183
- subagent_type
1674
+ subagent_type,
1675
+ system_prompt,
1676
+ tools: customTools
1184
1677
  }) => {
1185
1678
  const startTime = performance.now();
1186
1679
  try {
1187
1680
  const typeConfig = subagentTypes[subagent_type] || {};
1188
1681
  const model = typeConfig.model || defaultModel;
1189
- const tools = filterTools(allTools, typeConfig.tools);
1190
- const systemPrompt = typeConfig.systemPrompt;
1682
+ const tools = filterTools(allTools, customTools ?? typeConfig.tools);
1683
+ const systemPrompt = system_prompt ?? typeConfig.systemPrompt;
1191
1684
  const commonOptions = {
1192
1685
  model,
1193
1686
  tools,
@@ -1308,9 +1801,41 @@ var todoWriteInputSchema = z14.object({
1308
1801
  activeForm: z14.string().describe("Active form of the task description")
1309
1802
  })).describe("The updated todo list")
1310
1803
  });
1311
- function createTodoWriteTool(state, config, onUpdate) {
1804
+ var TODO_WRITE_DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress. This helps organize complex tasks and gives the user visibility into your work.
1805
+
1806
+ **When to use this tool proactively:**
1807
+ 1. Complex multi-step tasks - When a task requires 3 or more distinct steps
1808
+ 2. Non-trivial tasks - Tasks requiring careful planning or multiple operations
1809
+ 3. User explicitly requests a todo list
1810
+ 4. User provides multiple tasks - Numbered lists or comma-separated items
1811
+ 5. After receiving new instructions - Immediately capture requirements as todos
1812
+ 6. When starting work - Mark task as in_progress BEFORE beginning
1813
+ 7. After completing - Mark as completed and add any follow-up tasks discovered
1814
+
1815
+ **When NOT to use:**
1816
+ 1. Single, straightforward tasks
1817
+ 2. Trivial tasks with no organizational benefit
1818
+ 3. Tasks completable in less than 3 trivial steps
1819
+ 4. Purely conversational or informational requests
1820
+
1821
+ **Task states:**
1822
+ - pending: Not yet started
1823
+ - in_progress: Currently working on (limit to ONE at a time)
1824
+ - completed: Finished successfully
1825
+
1826
+ **Task format (both required):**
1827
+ - content: Imperative form ("Run tests", "Analyze data")
1828
+ - activeForm: Present continuous form ("Running tests", "Analyzing data")
1829
+
1830
+ **Task management rules:**
1831
+ - Update status in real-time as you work
1832
+ - Mark complete IMMEDIATELY after finishing (don't batch)
1833
+ - Keep exactly ONE task in_progress at any time
1834
+ - ONLY mark completed when FULLY accomplished
1835
+ - If blocked/errors, keep in_progress and create new task for the blocker`;
1836
+ function createTodoWriteTool(state, onUpdate) {
1312
1837
  return tool14({
1313
- description: "Creates and manages a structured task list for tracking progress. Use this to plan complex tasks and track completion.",
1838
+ description: TODO_WRITE_DESCRIPTION,
1314
1839
  inputSchema: zodSchema14(todoWriteInputSchema),
1315
1840
  execute: async ({
1316
1841
  todos
@@ -1340,6 +1865,58 @@ function createTodoWriteTool(state, config, onUpdate) {
1340
1865
  }
1341
1866
 
1342
1867
  // src/tools/index.ts
1868
+ var DEFAULT_CACHEABLE = [
1869
+ "Read",
1870
+ "Glob",
1871
+ "Grep",
1872
+ "WebFetch",
1873
+ "WebSearch"
1874
+ ];
1875
+ function resolveCache(config) {
1876
+ if (!config) {
1877
+ return { store: null, ttl: 0, debug: false, enabled: new Set };
1878
+ }
1879
+ if (config === true) {
1880
+ return {
1881
+ store: new LRUCacheStore,
1882
+ ttl: 5 * 60 * 1000,
1883
+ debug: false,
1884
+ enabled: new Set(DEFAULT_CACHEABLE)
1885
+ };
1886
+ }
1887
+ if (typeof config === "object" && typeof config.get === "function" && typeof config.set === "function" && typeof config.delete === "function" && typeof config.clear === "function") {
1888
+ return {
1889
+ store: config,
1890
+ ttl: 5 * 60 * 1000,
1891
+ debug: false,
1892
+ enabled: new Set(DEFAULT_CACHEABLE)
1893
+ };
1894
+ }
1895
+ const enabled = new Set;
1896
+ for (const tool15 of DEFAULT_CACHEABLE) {
1897
+ if (config[tool15] !== false) {
1898
+ enabled.add(tool15);
1899
+ }
1900
+ }
1901
+ for (const [key, value] of Object.entries(config)) {
1902
+ if (["store", "ttl", "debug", "onHit", "onMiss", "keyGenerator"].includes(key))
1903
+ continue;
1904
+ if (value === true)
1905
+ enabled.add(key);
1906
+ if (value === false)
1907
+ enabled.delete(key);
1908
+ }
1909
+ const cfg = config;
1910
+ return {
1911
+ store: cfg.store ?? new LRUCacheStore,
1912
+ ttl: cfg.ttl ?? 5 * 60 * 1000,
1913
+ debug: cfg.debug ?? false,
1914
+ onHit: cfg.onHit,
1915
+ onMiss: cfg.onMiss,
1916
+ keyGenerator: cfg.keyGenerator,
1917
+ enabled
1918
+ };
1919
+ }
1343
1920
  function createAgentTools(sandbox, config) {
1344
1921
  const toolsConfig = {
1345
1922
  ...DEFAULT_CONFIG.tools,
@@ -1375,6 +1952,21 @@ function createAgentTools(sandbox, config) {
1375
1952
  if (config?.webFetch) {
1376
1953
  tools.WebFetch = createWebFetchTool(config.webFetch);
1377
1954
  }
1955
+ const cacheConfig = resolveCache(config?.cache);
1956
+ if (cacheConfig.store) {
1957
+ for (const [name, tool15] of Object.entries(tools)) {
1958
+ if (cacheConfig.enabled.has(name)) {
1959
+ tools[name] = cached(tool15, name, {
1960
+ store: cacheConfig.store,
1961
+ ttl: cacheConfig.ttl,
1962
+ debug: cacheConfig.debug,
1963
+ onHit: cacheConfig.onHit,
1964
+ onMiss: cacheConfig.onMiss,
1965
+ keyGenerator: cacheConfig.keyGenerator
1966
+ });
1967
+ }
1968
+ }
1969
+ }
1378
1970
  return { tools, planModeState };
1379
1971
  }
1380
1972
  // src/utils/compact-conversation.ts
@@ -1728,11 +2320,13 @@ function contextNeedsCompaction(status) {
1728
2320
  return status.status === "critical";
1729
2321
  }
1730
2322
  // src/skills/discovery.ts
1731
- import { readdir, readFile, stat } from "node:fs/promises";
2323
+ import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
1732
2324
  import { homedir } from "node:os";
1733
- import { join, resolve } from "node:path";
2325
+ import { join as join2, resolve } from "node:path";
1734
2326
 
1735
2327
  // src/skills/loader.ts
2328
+ import { readdir, readFile } from "node:fs/promises";
2329
+ import { basename, dirname, join } from "node:path";
1736
2330
  function parseSkillMetadata(content, skillPath) {
1737
2331
  const frontmatter = extractFrontmatter(content);
1738
2332
  if (!frontmatter) {
@@ -1821,6 +2415,32 @@ function parseYaml(yaml) {
1821
2415
  }
1822
2416
  return result;
1823
2417
  }
2418
+ async function loadSkillBundle(skillDir) {
2419
+ const files = {};
2420
+ const name = basename(skillDir);
2421
+ async function readDirRecursive(dir, prefix = "") {
2422
+ const entries = await readdir(dir, { withFileTypes: true });
2423
+ for (const entry of entries) {
2424
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
2425
+ const fullPath = join(dir, entry.name);
2426
+ if (entry.isDirectory()) {
2427
+ await readDirRecursive(fullPath, relativePath);
2428
+ } else {
2429
+ files[relativePath] = await readFile(fullPath, "utf-8");
2430
+ }
2431
+ }
2432
+ }
2433
+ await readDirRecursive(skillDir);
2434
+ return { name, files };
2435
+ }
2436
+ async function loadSkillBundles(skills) {
2437
+ const bundles = {};
2438
+ for (const skill of skills) {
2439
+ const skillDir = dirname(skill.path);
2440
+ bundles[skill.name] = await loadSkillBundle(skillDir);
2441
+ }
2442
+ return bundles;
2443
+ }
1824
2444
 
1825
2445
  // src/skills/discovery.ts
1826
2446
  var DEFAULT_SKILL_PATHS = [".skills", "~/.bashkit/skills"];
@@ -1843,7 +2463,7 @@ async function discoverSkills(options) {
1843
2463
  }
1844
2464
  function resolvePath(path, cwd) {
1845
2465
  if (path.startsWith("~/")) {
1846
- return join(homedir(), path.slice(2));
2466
+ return join2(homedir(), path.slice(2));
1847
2467
  }
1848
2468
  if (path.startsWith("/")) {
1849
2469
  return path;
@@ -1853,18 +2473,18 @@ function resolvePath(path, cwd) {
1853
2473
  async function scanDirectory(dirPath) {
1854
2474
  const skills = [];
1855
2475
  try {
1856
- const entries = await readdir(dirPath, { withFileTypes: true });
2476
+ const entries = await readdir2(dirPath, { withFileTypes: true });
1857
2477
  for (const entry of entries) {
1858
2478
  if (!entry.isDirectory()) {
1859
2479
  continue;
1860
2480
  }
1861
- const skillPath = join(dirPath, entry.name, "SKILL.md");
2481
+ const skillPath = join2(dirPath, entry.name, "SKILL.md");
1862
2482
  try {
1863
2483
  const skillStat = await stat(skillPath);
1864
2484
  if (!skillStat.isFile()) {
1865
2485
  continue;
1866
2486
  }
1867
- const content = await readFile(skillPath, "utf-8");
2487
+ const content = await readFile2(skillPath, "utf-8");
1868
2488
  const metadata = parseSkillMetadata(content, skillPath);
1869
2489
  if (metadata.name !== entry.name) {
1870
2490
  console.warn(`Skill name "${metadata.name}" does not match folder name "${entry.name}" in ${skillPath}`);
@@ -2037,6 +2657,8 @@ export {
2037
2657
  setupAgentEnvironment,
2038
2658
  pruneMessagesByTokens,
2039
2659
  parseSkillMetadata,
2660
+ loadSkillBundles,
2661
+ loadSkillBundle,
2040
2662
  getContextStatus,
2041
2663
  fetchSkills,
2042
2664
  fetchSkill,
@@ -2051,6 +2673,7 @@ export {
2051
2673
  createTodoWriteTool,
2052
2674
  createTaskTool,
2053
2675
  createSkillTool,
2676
+ createRedisCacheStore,
2054
2677
  createReadTool,
2055
2678
  createLocalSandbox,
2056
2679
  createGrepTool,
@@ -2066,7 +2689,10 @@ export {
2066
2689
  contextNeedsCompaction,
2067
2690
  contextNeedsAttention,
2068
2691
  compactConversation,
2692
+ cached,
2693
+ anthropicPromptCacheMiddlewareV2,
2069
2694
  anthropicPromptCacheMiddleware,
2070
2695
  MODEL_CONTEXT_LIMITS,
2696
+ LRUCacheStore,
2071
2697
  DEFAULT_CONFIG
2072
2698
  };