@probelabs/probe 0.6.0-rc199 → 0.6.0-rc201

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.
@@ -3554,7 +3554,9 @@ async function delegate({
3554
3554
  debug,
3555
3555
  tracer,
3556
3556
  path: path9,
3557
- // Inherit from parent
3557
+ // Workspace root (from delegateTool)
3558
+ cwd: path9,
3559
+ // Explicitly set cwd to workspace root to prevent path doubling
3558
3560
  provider,
3559
3561
  // Inherit from parent
3560
3562
  model,
@@ -7869,556 +7871,879 @@ var init_zod = __esm({
7869
7871
  }
7870
7872
  });
7871
7873
 
7872
- // src/tools/common.js
7873
- import { resolve, isAbsolute } from "path";
7874
- function buildToolTagPattern(tools2 = DEFAULT_VALID_TOOLS) {
7875
- const allTools = [...tools2];
7876
- if (allTools.includes("attempt_completion") && !allTools.includes("attempt_complete")) {
7877
- allTools.push("attempt_complete");
7878
- }
7879
- const escaped = allTools.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
7880
- return new RegExp(`<(${escaped.join("|")})>`);
7881
- }
7882
- function getValidParamsForTool(toolName) {
7883
- const schemaMap = {
7884
- search: searchSchema,
7885
- query: querySchema,
7886
- extract: extractSchema,
7887
- delegate: delegateSchema,
7888
- bash: bashSchema,
7889
- attempt_completion: attemptCompletionSchema
7890
- };
7891
- const schema = schemaMap[toolName];
7892
- if (!schema) {
7893
- return ["path", "directory", "pattern", "recursive", "includeHidden", "task", "files", "autoCommits", "result"];
7894
- }
7895
- if (toolName === "attempt_completion") {
7896
- return ["result"];
7897
- }
7898
- if (schema && schema._def && schema._def.shape) {
7899
- return Object.keys(schema._def.shape());
7900
- }
7901
- return [];
7902
- }
7903
- function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
7904
- for (const toolName of validTools) {
7905
- const openTag = `<${toolName}>`;
7906
- const closeTag = `</${toolName}>`;
7907
- const openIndex = xmlString.indexOf(openTag);
7908
- if (openIndex === -1) {
7909
- continue;
7910
- }
7911
- let closeIndex;
7912
- if (toolName === "attempt_completion") {
7913
- closeIndex = xmlString.lastIndexOf(closeTag);
7914
- if (closeIndex !== -1 && closeIndex <= openIndex + openTag.length) {
7915
- closeIndex = -1;
7916
- }
7917
- } else {
7918
- closeIndex = xmlString.indexOf(closeTag, openIndex + openTag.length);
7919
- }
7920
- let hasClosingTag = closeIndex !== -1;
7921
- if (closeIndex === -1) {
7922
- closeIndex = xmlString.length;
7923
- }
7924
- const innerContent = xmlString.substring(
7925
- openIndex + openTag.length,
7926
- closeIndex
7927
- );
7928
- const params = {};
7929
- const validParams = getValidParamsForTool(toolName);
7930
- for (const paramName of validParams) {
7931
- const paramOpenTag = `<${paramName}>`;
7932
- const paramCloseTag = `</${paramName}>`;
7933
- const paramOpenIndex = innerContent.indexOf(paramOpenTag);
7934
- if (paramOpenIndex === -1) {
7935
- continue;
7936
- }
7937
- let paramCloseIndex = innerContent.indexOf(paramCloseTag, paramOpenIndex + paramOpenTag.length);
7938
- if (paramCloseIndex === -1) {
7939
- let nextTagIndex = innerContent.length;
7940
- for (const nextParam of validParams) {
7941
- const nextOpenTag = `<${nextParam}>`;
7942
- const nextIndex = innerContent.indexOf(nextOpenTag, paramOpenIndex + paramOpenTag.length);
7943
- if (nextIndex !== -1 && nextIndex < nextTagIndex) {
7944
- nextTagIndex = nextIndex;
7945
- }
7946
- }
7947
- paramCloseIndex = nextTagIndex;
7948
- }
7949
- let paramValue = innerContent.substring(
7950
- paramOpenIndex + paramOpenTag.length,
7951
- paramCloseIndex
7952
- ).trim();
7953
- if (paramValue.toLowerCase() === "true") {
7954
- paramValue = true;
7955
- } else if (paramValue.toLowerCase() === "false") {
7956
- paramValue = false;
7957
- } else if (!isNaN(paramValue) && paramValue.trim() !== "") {
7958
- const num = Number(paramValue);
7959
- if (Number.isFinite(num)) {
7960
- paramValue = num;
7961
- }
7962
- }
7963
- params[paramName] = paramValue;
7964
- }
7965
- if (toolName === "attempt_completion") {
7966
- params["result"] = innerContent.trim();
7967
- if (params.command) {
7968
- delete params.command;
7969
- }
7970
- }
7971
- return { toolName, params };
7972
- }
7973
- return null;
7974
- }
7975
- function createMessagePreview(message, charsPerSide = 200) {
7976
- if (message === null || message === void 0) {
7977
- return "null/undefined";
7978
- }
7979
- if (typeof message !== "string") {
7980
- return "null/undefined";
7981
- }
7982
- const totalChars = charsPerSide * 2;
7983
- if (message.length <= totalChars) {
7984
- return message;
7985
- }
7986
- const start = message.substring(0, charsPerSide);
7987
- const end = message.substring(message.length - charsPerSide);
7988
- return `${start}...${end}`;
7989
- }
7990
- function parseTargets(targets) {
7991
- if (!targets || typeof targets !== "string") {
7992
- return [];
7874
+ // src/tools/edit.js
7875
+ import { tool } from "ai";
7876
+ import { promises as fs6 } from "fs";
7877
+ import { dirname, resolve, isAbsolute, sep } from "path";
7878
+ import { existsSync } from "fs";
7879
+ function isPathAllowed(filePath, allowedFolders) {
7880
+ if (!allowedFolders || allowedFolders.length === 0) {
7881
+ const resolvedPath2 = resolve(filePath);
7882
+ const cwd = resolve(process.cwd());
7883
+ return resolvedPath2 === cwd || resolvedPath2.startsWith(cwd + sep);
7993
7884
  }
7994
- return targets.split(/[\s,]+/).filter((f) => f.length > 0);
7995
- }
7996
- function parseAndResolvePaths(pathStr, cwd) {
7997
- if (!pathStr) return [];
7998
- const paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
7999
- return paths.map((p) => {
8000
- if (isAbsolute(p)) {
8001
- return p;
8002
- }
8003
- return cwd ? resolve(cwd, p) : p;
7885
+ const resolvedPath = resolve(filePath);
7886
+ return allowedFolders.some((folder) => {
7887
+ const allowedPath = resolve(folder);
7888
+ return resolvedPath === allowedPath || resolvedPath.startsWith(allowedPath + sep);
8004
7889
  });
8005
7890
  }
8006
- function resolveTargetPath(target, cwd) {
8007
- const searchStart = target.length > 2 && target[1] === ":" && /[a-zA-Z]/.test(target[0]) ? 2 : 0;
8008
- const colonIdx = target.indexOf(":", searchStart);
8009
- const hashIdx = target.indexOf("#");
8010
- let filePart, suffix;
8011
- if (colonIdx !== -1 && (hashIdx === -1 || colonIdx < hashIdx)) {
8012
- filePart = target.substring(0, colonIdx);
8013
- suffix = target.substring(colonIdx);
8014
- } else if (hashIdx !== -1) {
8015
- filePart = target.substring(0, hashIdx);
8016
- suffix = target.substring(hashIdx);
8017
- } else {
8018
- filePart = target;
8019
- suffix = "";
8020
- }
8021
- if (!isAbsolute(filePart) && cwd) {
8022
- filePart = resolve(cwd, filePart);
8023
- }
8024
- return filePart + suffix;
7891
+ function parseFileToolOptions(options = {}) {
7892
+ return {
7893
+ debug: options.debug || false,
7894
+ allowedFolders: options.allowedFolders || [],
7895
+ cwd: options.cwd
7896
+ };
8025
7897
  }
8026
- var searchSchema, querySchema, extractSchema, delegateSchema, bashSchema, attemptCompletionSchema, searchToolDefinition, queryToolDefinition, extractToolDefinition, delegateToolDefinition, attemptCompletionToolDefinition, bashToolDefinition, searchDescription, queryDescription, extractDescription, delegateDescription, DEFAULT_VALID_TOOLS;
8027
- var init_common = __esm({
8028
- "src/tools/common.js"() {
7898
+ var editTool, createTool, editSchema, createSchema, editDescription, createDescription, editToolDefinition, createToolDefinition;
7899
+ var init_edit = __esm({
7900
+ "src/tools/edit.js"() {
8029
7901
  "use strict";
8030
- init_zod();
8031
- searchSchema = external_exports.object({
8032
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
8033
- path: external_exports.string().optional().default(".").describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.')
8034
- });
8035
- querySchema = external_exports.object({
8036
- pattern: external_exports.string().describe("AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc."),
8037
- path: external_exports.string().optional().default(".").describe("Path to search in"),
8038
- language: external_exports.string().optional().default("rust").describe("Programming language to use for parsing"),
8039
- allow_tests: external_exports.boolean().optional().default(true).describe("Allow test files in search results")
8040
- });
8041
- extractSchema = external_exports.object({
8042
- targets: external_exports.string().optional().describe('File paths or symbols to extract from. Formats: "file.js" (whole file), "file.js:42" (line 42), "file.js:10-20" (lines 10-20), "file.js#funcName" (symbol). Multiple targets separated by spaces.'),
8043
- input_content: external_exports.string().optional().describe("Text content to extract file paths from (alternative to targets)"),
8044
- allow_tests: external_exports.boolean().optional().default(true).describe("Include test files in extraction results")
8045
- });
8046
- delegateSchema = external_exports.object({
8047
- task: external_exports.string().describe("The task to delegate to a subagent. Be specific about what needs to be accomplished.")
8048
- });
8049
- bashSchema = external_exports.object({
8050
- command: external_exports.string().describe("The bash command to execute"),
8051
- workingDirectory: external_exports.string().optional().describe("Directory to execute the command in (optional)"),
8052
- timeout: external_exports.number().optional().describe("Command timeout in milliseconds (optional)"),
8053
- env: external_exports.record(external_exports.string()).optional().describe("Additional environment variables (optional)")
8054
- });
8055
- attemptCompletionSchema = {
8056
- // Custom validation that requires result parameter but allows direct XML response
8057
- safeParse: (params) => {
8058
- if (!params || typeof params !== "object") {
8059
- return {
8060
- success: false,
8061
- error: {
8062
- issues: [{
8063
- code: "invalid_type",
8064
- expected: "object",
8065
- received: typeof params,
8066
- path: [],
8067
- message: "Expected object"
8068
- }]
8069
- }
8070
- };
8071
- }
8072
- if (!("result" in params)) {
8073
- return {
8074
- success: false,
8075
- error: {
8076
- issues: [{
8077
- code: "invalid_type",
8078
- expected: "string",
8079
- received: "undefined",
8080
- path: ["result"],
8081
- message: "Required"
8082
- }]
8083
- }
8084
- };
8085
- }
8086
- if (typeof params.result !== "string") {
8087
- return {
8088
- success: false,
8089
- error: {
8090
- issues: [{
8091
- code: "invalid_type",
8092
- expected: "string",
8093
- received: typeof params.result,
8094
- path: ["result"],
8095
- message: "Expected string"
8096
- }]
8097
- }
8098
- };
8099
- }
8100
- const filteredData = { result: params.result };
8101
- return {
8102
- success: true,
8103
- data: filteredData
8104
- };
8105
- }
8106
- };
8107
- searchToolDefinition = `
8108
- ## search
8109
- Description: Search code in the repository using Elasticsearch query syntax (except field based queries, e.g. "filename:..." NOT supported).
8110
-
8111
- You need to focus on main keywords when constructing the query, and always use elastic search syntax like OR AND and brackets to group keywords.
7902
+ editTool = (options = {}) => {
7903
+ const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
7904
+ return tool({
7905
+ name: "edit",
7906
+ description: `Edit files using exact string replacement (Claude Code style).
8112
7907
 
8113
- **Session Management & Caching:**
8114
- - Ensure not to re-read the same symbols twice - reuse context from previous tool calls
8115
- - Probe returns a session ID on first run - reuse it for subsequent calls to avoid redundant searches
8116
- - Once data is returned, it's cached and won't return on next runs (this is expected behavior)
7908
+ This tool performs exact string replacements in files. It requires the old_string to match exactly what's in the file, including all whitespace and indentation.
8117
7909
 
8118
7910
  Parameters:
8119
- - query: (required) Search query with Elasticsearch syntax. Use quotes for exact matches ("functionName"), AND/OR for boolean logic, - for negation, + for important terms.
8120
- - path: (optional, default: '.') Path to search in. All dependencies located in /dep folder, under language sub folders, like this: "/dep/go/github.com/owner/repo", "/dep/js/package_name", or "/dep/rust/cargo_name" etc.
8121
-
8122
- **Workflow:** Always start with search, then use extract for detailed context when needed.
8123
-
8124
- Usage Example:
8125
-
8126
- <examples>
8127
-
8128
- User: Where is the login logic?
8129
- Assistant workflow:
8130
- 1. <search>
8131
- <query>login AND auth AND token</query>
8132
- <path>.</path>
8133
- </search>
8134
- 2. Now lets look closer: <extract>
8135
- <targets>session.rs#AuthService.login auth.rs:2-100</targets>
8136
- </extract>
8137
-
8138
- User: How to calculate the total amount in the payments module?
8139
- <search>
8140
- <query>calculate AND payment</query>
8141
- <path>src/utils</path>
8142
- </search>
8143
-
8144
- User: How do the user authentication and authorization work?
8145
- <search>
8146
- <query>+user AND (authentication OR authorization OR authz)</query>
8147
- <path>.</path>
8148
- </search>
7911
+ - file_path: Path to the file to edit (absolute or relative)
7912
+ - old_string: Exact text to find and replace (must be unique in the file unless replace_all is true)
7913
+ - new_string: Text to replace with
7914
+ - replace_all: (optional) Replace all occurrences instead of requiring uniqueness
8149
7915
 
8150
- User: Find all react imports in the project.
8151
- <search>
8152
- <query>"import" AND "react"</query>
8153
- <path>.</path>
8154
- </search>
7916
+ Important:
7917
+ - The old_string must match EXACTLY including whitespace
7918
+ - If old_string appears multiple times and replace_all is false, the edit will fail
7919
+ - Use larger context around the string to ensure uniqueness when needed`,
7920
+ inputSchema: {
7921
+ type: "object",
7922
+ properties: {
7923
+ file_path: {
7924
+ type: "string",
7925
+ description: "Path to the file to edit"
7926
+ },
7927
+ old_string: {
7928
+ type: "string",
7929
+ description: "Exact text to find and replace"
7930
+ },
7931
+ new_string: {
7932
+ type: "string",
7933
+ description: "Text to replace with"
7934
+ },
7935
+ replace_all: {
7936
+ type: "boolean",
7937
+ description: "Replace all occurrences (default: false)",
7938
+ default: false
7939
+ }
7940
+ },
7941
+ required: ["file_path", "old_string", "new_string"]
7942
+ },
7943
+ execute: async ({ file_path, old_string, new_string, replace_all = false }) => {
7944
+ try {
7945
+ if (!file_path || typeof file_path !== "string" || file_path.trim() === "") {
7946
+ return `Error editing file: Invalid file_path - must be a non-empty string`;
7947
+ }
7948
+ if (old_string === void 0 || old_string === null || typeof old_string !== "string") {
7949
+ return `Error editing file: Invalid old_string - must be a string`;
7950
+ }
7951
+ if (new_string === void 0 || new_string === null || typeof new_string !== "string") {
7952
+ return `Error editing file: Invalid new_string - must be a string`;
7953
+ }
7954
+ const resolvedPath = isAbsolute(file_path) ? file_path : resolve(cwd || process.cwd(), file_path);
7955
+ if (debug) {
7956
+ console.error(`[Edit] Attempting to edit file: ${resolvedPath}`);
7957
+ }
7958
+ if (!isPathAllowed(resolvedPath, allowedFolders)) {
7959
+ return `Error editing file: Permission denied - ${file_path} is outside allowed directories`;
7960
+ }
7961
+ if (!existsSync(resolvedPath)) {
7962
+ return `Error editing file: File not found - ${file_path}`;
7963
+ }
7964
+ const content = await fs6.readFile(resolvedPath, "utf-8");
7965
+ if (!content.includes(old_string)) {
7966
+ return `Error editing file: String not found - the specified old_string was not found in ${file_path}`;
7967
+ }
7968
+ const occurrences = content.split(old_string).length - 1;
7969
+ if (!replace_all && occurrences > 1) {
7970
+ return `Error editing file: Multiple occurrences found - the old_string appears ${occurrences} times. Use replace_all: true to replace all occurrences, or provide more context to make the string unique.`;
7971
+ }
7972
+ let newContent;
7973
+ if (replace_all) {
7974
+ newContent = content.replaceAll(old_string, new_string);
7975
+ } else {
7976
+ newContent = content.replace(old_string, new_string);
7977
+ }
7978
+ if (newContent === content) {
7979
+ return `Error editing file: No changes made - old_string and new_string might be the same`;
7980
+ }
7981
+ await fs6.writeFile(resolvedPath, newContent, "utf-8");
7982
+ const replacedCount = replace_all ? occurrences : 1;
7983
+ if (debug) {
7984
+ console.error(`[Edit] Successfully edited ${resolvedPath}, replaced ${replacedCount} occurrence(s)`);
7985
+ }
7986
+ return `Successfully edited ${file_path} (${replacedCount} replacement${replacedCount !== 1 ? "s" : ""})`;
7987
+ } catch (error) {
7988
+ console.error("[Edit] Error:", error);
7989
+ return `Error editing file: ${error.message}`;
7990
+ }
7991
+ }
7992
+ });
7993
+ };
7994
+ createTool = (options = {}) => {
7995
+ const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
7996
+ return tool({
7997
+ name: "create",
7998
+ description: `Create new files with specified content.
8155
7999
 
8156
- User: Find how decompound library works?
8157
- <search>
8158
- <query>decompound</query>
8159
- <path>/dep/rust/decompound</path>
8160
- </search>
8000
+ This tool creates new files in the filesystem. It will create parent directories if they don't exist.
8161
8001
 
8162
- </examples>
8163
- `;
8164
- queryToolDefinition = `
8165
- ## query
8166
- Description: Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.
8167
8002
  Parameters:
8168
- - pattern: (required) AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc.
8169
- - path: (optional, default: '.') Path to search in.
8170
- - language: (optional, default: 'rust') Programming language to use for parsing.
8171
- - allow_tests: (optional, default: true) Allow test files in search results (true/false).
8172
- Usage Example:
8173
-
8174
- <examples>
8175
-
8176
- <query>
8177
- <pattern>function $FUNC($$$PARAMS) { $$$BODY }</pattern>
8178
- <path>src/parser</path>
8179
- <language>js</language>
8180
- </query>
8003
+ - file_path: Path where the file should be created (absolute or relative)
8004
+ - content: Content to write to the file
8005
+ - overwrite: (optional) Whether to overwrite if file exists (default: false)
8181
8006
 
8182
- </examples>
8183
- `;
8184
- extractToolDefinition = `
8185
- ## extract
8186
- Description: Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. It can be used to read full files as well.
8187
- Full file extraction should be the LAST RESORT! Always prefer search.
8007
+ Important:
8008
+ - By default, will fail if the file already exists
8009
+ - Set overwrite: true to replace existing files
8010
+ - Parent directories will be created automatically if needed`,
8011
+ inputSchema: {
8012
+ type: "object",
8013
+ properties: {
8014
+ file_path: {
8015
+ type: "string",
8016
+ description: "Path where the file should be created"
8017
+ },
8018
+ content: {
8019
+ type: "string",
8020
+ description: "Content to write to the file"
8021
+ },
8022
+ overwrite: {
8023
+ type: "boolean",
8024
+ description: "Overwrite if file exists (default: false)",
8025
+ default: false
8026
+ }
8027
+ },
8028
+ required: ["file_path", "content"]
8029
+ },
8030
+ execute: async ({ file_path, content, overwrite = false }) => {
8031
+ try {
8032
+ if (!file_path || typeof file_path !== "string" || file_path.trim() === "") {
8033
+ return `Error creating file: Invalid file_path - must be a non-empty string`;
8034
+ }
8035
+ if (content === void 0 || content === null || typeof content !== "string") {
8036
+ return `Error creating file: Invalid content - must be a string`;
8037
+ }
8038
+ const resolvedPath = isAbsolute(file_path) ? file_path : resolve(cwd || process.cwd(), file_path);
8039
+ if (debug) {
8040
+ console.error(`[Create] Attempting to create file: ${resolvedPath}`);
8041
+ }
8042
+ if (!isPathAllowed(resolvedPath, allowedFolders)) {
8043
+ return `Error creating file: Permission denied - ${file_path} is outside allowed directories`;
8044
+ }
8045
+ if (existsSync(resolvedPath) && !overwrite) {
8046
+ return `Error creating file: File already exists - ${file_path}. Use overwrite: true to replace it.`;
8047
+ }
8048
+ const dir = dirname(resolvedPath);
8049
+ await fs6.mkdir(dir, { recursive: true });
8050
+ await fs6.writeFile(resolvedPath, content, "utf-8");
8051
+ const action = existsSync(resolvedPath) && overwrite ? "overwrote" : "created";
8052
+ const bytes = Buffer.byteLength(content, "utf-8");
8053
+ if (debug) {
8054
+ console.error(`[Create] Successfully ${action} ${resolvedPath}`);
8055
+ }
8056
+ return `Successfully ${action} ${file_path} (${bytes} bytes)`;
8057
+ } catch (error) {
8058
+ console.error("[Create] Error:", error);
8059
+ return `Error creating file: ${error.message}`;
8060
+ }
8061
+ }
8062
+ });
8063
+ };
8064
+ editSchema = {
8065
+ type: "object",
8066
+ properties: {
8067
+ file_path: {
8068
+ type: "string",
8069
+ description: "Path to the file to edit"
8070
+ },
8071
+ old_string: {
8072
+ type: "string",
8073
+ description: "Exact text to find and replace"
8074
+ },
8075
+ new_string: {
8076
+ type: "string",
8077
+ description: "Text to replace with"
8078
+ },
8079
+ replace_all: {
8080
+ type: "boolean",
8081
+ description: "Replace all occurrences (default: false)"
8082
+ }
8083
+ },
8084
+ required: ["file_path", "old_string", "new_string"]
8085
+ };
8086
+ createSchema = {
8087
+ type: "object",
8088
+ properties: {
8089
+ file_path: {
8090
+ type: "string",
8091
+ description: "Path where the file should be created"
8092
+ },
8093
+ content: {
8094
+ type: "string",
8095
+ description: "Content to write to the file"
8096
+ },
8097
+ overwrite: {
8098
+ type: "boolean",
8099
+ description: "Overwrite if file exists (default: false)"
8100
+ }
8101
+ },
8102
+ required: ["file_path", "content"]
8103
+ };
8104
+ editDescription = "Edit files using exact string replacement. Requires exact match including whitespace.";
8105
+ createDescription = "Create new files with specified content. Will create parent directories if needed.";
8106
+ editToolDefinition = `
8107
+ ## edit
8108
+ Description: ${editDescription}
8188
8109
 
8189
- **Multiple Extraction:** You can extract multiple symbols/files in one call by providing multiple file paths separated by spaces.
8110
+ When to use:
8111
+ - For precise, surgical edits to existing files
8112
+ - When you need to change specific lines or blocks of code
8113
+ - For renaming functions, variables, or updating configuration values
8114
+ - When the exact text to replace is known and unique (or use replace_all for multiple occurrences)
8190
8115
 
8191
- **Session Awareness:** Reuse context from previous tool calls. Don't re-extract the same symbols you already have.
8116
+ When NOT to use:
8117
+ - For creating new files (use 'create' tool instead)
8118
+ - When you cannot determine the exact text to replace
8119
+ - When changes span multiple locations that would be better handled together
8192
8120
 
8193
8121
  Parameters:
8194
- - targets: (required) File paths or symbols to extract from. Formats: "file.js" (whole file), "file.js:42" (code block at line 42), "file.js:10-20" (lines 10-20), "file.js#funcName" (specific symbol). Multiple targets separated by spaces.
8195
- - input_content: (optional) Text content to extract file paths from (alternative to targets for processing diffs/logs).
8196
- - allow_tests: (optional, default: true) Include test files in extraction results.
8122
+ - file_path: (required) Path to the file to edit
8123
+ - old_string: (required) Exact text to find and replace (must match including whitespace, newlines, and indentation)
8124
+ - new_string: (required) Text to replace with
8125
+ - replace_all: (optional, default: false) Replace all occurrences if the string appears multiple times
8197
8126
 
8198
- Usage Example:
8127
+ Important notes:
8128
+ - The old_string MUST match EXACTLY, including all whitespace, indentation, and line breaks
8129
+ - If old_string appears multiple times and replace_all is false, the tool will fail
8130
+ - Always verify the exact formatting of the text you want to replace
8199
8131
 
8200
- <examples>
8201
-
8202
- User: Where is the login logic? (After search found relevant files)
8203
- <extract>
8204
- <targets>session.rs#AuthService.login auth.rs:2-100 config.rs#DatabaseConfig</targets>
8205
- </extract>
8206
-
8207
- User: How does error handling work? (After search identified files)
8208
- <extract>
8209
- <targets>error.rs#ErrorType utils.rs#handle_error src/main.rs:50-80</targets>
8210
- </extract>
8211
-
8212
- User: How RankManager works
8213
- <extract>
8214
- <targets>src/search/ranking.rs#RankManager</targets>
8215
- </extract>
8216
-
8217
- User: Lets read the whole file
8218
- <extract>
8219
- <targets>src/search/ranking.rs</targets>
8220
- </extract>
8221
-
8222
- User: Read the first 10 lines of the file
8223
- <extract>
8224
- <targets>src/search/ranking.rs:1-10</targets>
8225
- </extract>
8226
-
8227
- User: Read file inside the dependency
8228
- <extract>
8229
- <targets>/dep/go/github.com/gorilla/mux/router.go</targets>
8230
- </extract>
8231
-
8232
- </examples>
8233
- `;
8234
- delegateToolDefinition = `
8235
- ## delegate
8236
- Description: Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Use this when you recognize that a user's request involves multiple large, distinct components that would benefit from parallel processing or specialized focus. The AI agent should automatically identify opportunities for task separation and use delegation without explicit user instruction.
8237
-
8238
- Parameters:
8239
- - task: (required) A complete, self-contained task that can be executed independently by a subagent. Should be specific and focused on one area of expertise.
8240
-
8241
- Usage Pattern:
8242
- When the AI agent encounters complex multi-part requests, it should automatically break them down and delegate:
8132
+ Examples:
8133
+ <edit>
8134
+ <file_path>src/main.js</file_path>
8135
+ <old_string>function oldName() {
8136
+ return 42;
8137
+ }</old_string>
8138
+ <new_string>function newName() {
8139
+ return 42;
8140
+ }</new_string>
8141
+ </edit>
8243
8142
 
8244
- <delegate>
8245
- <task>Analyze all authentication and authorization code in the codebase for security vulnerabilities and provide specific remediation recommendations</task>
8246
- </delegate>
8143
+ <edit>
8144
+ <file_path>config.json</file_path>
8145
+ <old_string>"debug": false</old_string>
8146
+ <new_string>"debug": true</new_string>
8147
+ <replace_all>true</replace_all>
8148
+ </edit>`;
8149
+ createToolDefinition = `
8150
+ ## create
8151
+ Description: ${createDescription}
8247
8152
 
8248
- <delegate>
8249
- <task>Review database queries and API endpoints for performance bottlenecks and suggest optimization strategies</task>
8250
- </delegate>
8153
+ When to use:
8154
+ - For creating brand new files from scratch
8155
+ - When you need to add configuration files, documentation, or new modules
8156
+ - For generating boilerplate code or templates
8157
+ - When you have the complete content ready to write
8251
8158
 
8252
- The agent uses this tool automatically when it identifies that work can be separated into distinct, parallel tasks for more efficient processing.
8253
- `;
8254
- attemptCompletionToolDefinition = `
8255
- ## attempt_completion
8256
- Description: Use this tool ONLY when the task is fully complete and you have received confirmation of success for all previous tool uses. Presents the final result to the user. You can provide your response directly inside the XML tags without any parameter wrapper.
8257
- Parameters:
8258
- - No validation required - provide your complete answer directly inside the XML tags.
8259
- Usage Example:
8260
- <attempt_completion>
8261
- I have refactored the search module according to the requirements and verified the tests pass. The module now uses the new BM25 ranking algorithm and has improved error handling.
8262
- </attempt_completion>
8263
- `;
8264
- bashToolDefinition = `
8265
- ## bash
8266
- Description: Execute bash commands for system exploration and development tasks. This tool has built-in security with allow/deny lists. By default, only safe read-only commands are allowed for code exploration.
8159
+ When NOT to use:
8160
+ - For editing existing files (use 'edit' tool instead)
8161
+ - When a file already exists unless you explicitly want to overwrite it
8267
8162
 
8268
8163
  Parameters:
8269
- - command: (required) The bash command to execute
8270
- - workingDirectory: (optional) Directory to execute the command in
8271
- - timeout: (optional) Command timeout in milliseconds
8272
- - env: (optional) Additional environment variables as an object
8273
-
8274
- Security: Commands are filtered through allow/deny lists for safety:
8275
- - Allowed by default: ls, cat, git status, npm list, find, grep, etc.
8276
- - Denied by default: rm -rf, sudo, npm install, dangerous system commands
8277
-
8278
- Usage Examples:
8279
-
8280
- <examples>
8281
-
8282
- User: What files are in the src directory?
8283
- <bash>
8284
- <command>ls -la src/</command>
8285
- </bash>
8286
-
8287
- User: Show me the git status
8288
- <bash>
8289
- <command>git status</command>
8290
- </bash>
8291
-
8292
- User: Find all TypeScript files
8293
- <bash>
8294
- <command>find . -name "*.ts" -type f</command>
8295
- </bash>
8296
-
8297
- User: Check installed npm packages
8298
- <bash>
8299
- <command>npm list --depth=0</command>
8300
- </bash>
8164
+ - file_path: (required) Path where the file should be created
8165
+ - content: (required) Complete content to write to the file
8166
+ - overwrite: (optional, default: false) Whether to overwrite if file already exists
8301
8167
 
8302
- User: Search for TODO comments in code
8303
- <bash>
8304
- <command>grep -r "TODO" src/</command>
8305
- </bash>
8168
+ Important notes:
8169
+ - Parent directories will be created automatically if they don't exist
8170
+ - The tool will fail if the file already exists and overwrite is false
8171
+ - Be careful with the overwrite option as it completely replaces existing files
8306
8172
 
8307
- User: Show recent git commits
8308
- <bash>
8309
- <command>git log --oneline -10</command>
8310
- </bash>
8173
+ Examples:
8174
+ <create>
8175
+ <file_path>src/newFile.js</file_path>
8176
+ <content>export function hello() {
8177
+ return "Hello, world!";
8178
+ }</content>
8179
+ </create>
8311
8180
 
8312
- User: Check system info
8313
- <bash>
8314
- <command>uname -a</command>
8315
- </bash>
8181
+ <create>
8182
+ <file_path>README.md</file_path>
8183
+ <content># My Project
8316
8184
 
8317
- </examples>
8318
- `;
8319
- searchDescription = "Search code in the repository using Elasticsearch-like query syntax. Use this tool first for any code-related questions.";
8320
- queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
8321
- extractDescription = "Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files.";
8322
- delegateDescription = "Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Used by AI agents to break down complex requests into focused, parallel tasks.";
8323
- DEFAULT_VALID_TOOLS = [
8324
- "search",
8325
- "query",
8326
- "extract",
8327
- "delegate",
8328
- "listFiles",
8329
- "searchFiles",
8330
- "implement",
8331
- "bash",
8332
- "attempt_completion"
8333
- ];
8185
+ This is a new project.</content>
8186
+ <overwrite>true</overwrite>
8187
+ </create>`;
8334
8188
  }
8335
8189
  });
8336
8190
 
8337
- // src/tools/vercel.js
8338
- import { tool } from "ai";
8339
- var searchTool, queryTool, extractTool, delegateTool;
8340
- var init_vercel = __esm({
8341
- "src/tools/vercel.js"() {
8342
- "use strict";
8343
- init_search();
8344
- init_query();
8345
- init_extract();
8346
- init_delegate();
8347
- init_common();
8348
- searchTool = (options = {}) => {
8349
- const { sessionId, maxTokens = 1e4, debug = false, outline = false } = options;
8350
- return tool({
8351
- name: "search",
8352
- description: searchDescription,
8353
- inputSchema: searchSchema,
8354
- execute: async ({ query: searchQuery, path: path9, allow_tests, exact, maxTokens: paramMaxTokens, language }) => {
8355
- try {
8356
- const effectiveMaxTokens = paramMaxTokens || maxTokens;
8357
- let searchPaths;
8358
- if (path9) {
8359
- searchPaths = parseAndResolvePaths(path9, options.cwd);
8360
- }
8361
- if (!searchPaths || searchPaths.length === 0) {
8362
- searchPaths = [options.cwd || "."];
8363
- }
8364
- const searchPath = searchPaths.join(" ");
8365
- if (debug) {
8366
- console.error(`Executing search with query: "${searchQuery}", path: "${searchPath}", exact: ${exact ? "true" : "false"}, language: ${language || "all"}, session: ${sessionId || "none"}`);
8367
- }
8368
- const searchOptions = {
8369
- query: searchQuery,
8370
- path: searchPath,
8371
- cwd: options.cwd,
8372
- // Working directory for resolving relative paths
8373
- allowTests: allow_tests ?? true,
8374
- exact,
8375
- json: false,
8376
- maxTokens: effectiveMaxTokens,
8377
- session: sessionId,
8378
- // Pass session ID if provided
8379
- language
8380
- // Pass language parameter if provided
8381
- };
8382
- if (outline) {
8383
- searchOptions.format = "outline-xml";
8384
- }
8385
- const results = await search(searchOptions);
8386
- return results;
8387
- } catch (error) {
8388
- console.error("Error executing search command:", error);
8389
- return `Error executing search command: ${error.message}`;
8390
- }
8391
- }
8392
- });
8393
- };
8394
- queryTool = (options = {}) => {
8395
- const { debug = false } = options;
8396
- return tool({
8397
- name: "query",
8398
- description: queryDescription,
8399
- inputSchema: querySchema,
8400
- execute: async ({ pattern, path: path9, language, allow_tests }) => {
8401
- try {
8402
- let queryPaths;
8403
- if (path9) {
8404
- queryPaths = parseAndResolvePaths(path9, options.cwd);
8405
- }
8406
- if (!queryPaths || queryPaths.length === 0) {
8407
- queryPaths = [options.cwd || "."];
8408
- }
8409
- const queryPath = queryPaths.join(" ");
8410
- if (debug) {
8411
- console.error(`Executing query with pattern: "${pattern}", path: "${queryPath}", language: ${language || "auto"}`);
8412
- }
8413
- const results = await query({
8414
- pattern,
8415
- path: queryPath,
8416
- cwd: options.cwd,
8417
- // Working directory for resolving relative paths
8418
- language,
8419
- allowTests: allow_tests ?? true,
8420
- json: false
8421
- });
8191
+ // src/tools/common.js
8192
+ import { resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
8193
+ function buildToolTagPattern(tools2 = DEFAULT_VALID_TOOLS) {
8194
+ const allTools = [...tools2];
8195
+ if (allTools.includes("attempt_completion") && !allTools.includes("attempt_complete")) {
8196
+ allTools.push("attempt_complete");
8197
+ }
8198
+ const escaped = allTools.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
8199
+ return new RegExp(`<(${escaped.join("|")})>`);
8200
+ }
8201
+ function getValidParamsForTool(toolName) {
8202
+ const schemaMap = {
8203
+ search: searchSchema,
8204
+ query: querySchema,
8205
+ extract: extractSchema,
8206
+ delegate: delegateSchema,
8207
+ bash: bashSchema,
8208
+ attempt_completion: attemptCompletionSchema,
8209
+ edit: editSchema,
8210
+ create: createSchema
8211
+ };
8212
+ const schema = schemaMap[toolName];
8213
+ if (!schema) {
8214
+ return ["path", "directory", "pattern", "recursive", "includeHidden", "task", "files", "autoCommits", "result"];
8215
+ }
8216
+ if (toolName === "attempt_completion") {
8217
+ return ["result"];
8218
+ }
8219
+ if (schema._def && schema._def.shape) {
8220
+ return Object.keys(schema._def.shape());
8221
+ }
8222
+ if (schema.properties) {
8223
+ return Object.keys(schema.properties);
8224
+ }
8225
+ return [];
8226
+ }
8227
+ function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
8228
+ for (const toolName of validTools) {
8229
+ const openTag = `<${toolName}>`;
8230
+ const closeTag = `</${toolName}>`;
8231
+ const openIndex = xmlString.indexOf(openTag);
8232
+ if (openIndex === -1) {
8233
+ continue;
8234
+ }
8235
+ let closeIndex;
8236
+ if (toolName === "attempt_completion") {
8237
+ closeIndex = xmlString.lastIndexOf(closeTag);
8238
+ if (closeIndex !== -1 && closeIndex <= openIndex + openTag.length) {
8239
+ closeIndex = -1;
8240
+ }
8241
+ } else {
8242
+ closeIndex = xmlString.indexOf(closeTag, openIndex + openTag.length);
8243
+ }
8244
+ let hasClosingTag = closeIndex !== -1;
8245
+ if (closeIndex === -1) {
8246
+ closeIndex = xmlString.length;
8247
+ }
8248
+ const innerContent = xmlString.substring(
8249
+ openIndex + openTag.length,
8250
+ closeIndex
8251
+ );
8252
+ const params = {};
8253
+ const validParams = getValidParamsForTool(toolName);
8254
+ for (const paramName of validParams) {
8255
+ const paramOpenTag = `<${paramName}>`;
8256
+ const paramCloseTag = `</${paramName}>`;
8257
+ const paramOpenIndex = innerContent.indexOf(paramOpenTag);
8258
+ if (paramOpenIndex === -1) {
8259
+ continue;
8260
+ }
8261
+ let paramCloseIndex = innerContent.indexOf(paramCloseTag, paramOpenIndex + paramOpenTag.length);
8262
+ if (paramCloseIndex === -1) {
8263
+ let nextTagIndex = innerContent.length;
8264
+ for (const nextParam of validParams) {
8265
+ const nextOpenTag = `<${nextParam}>`;
8266
+ const nextIndex = innerContent.indexOf(nextOpenTag, paramOpenIndex + paramOpenTag.length);
8267
+ if (nextIndex !== -1 && nextIndex < nextTagIndex) {
8268
+ nextTagIndex = nextIndex;
8269
+ }
8270
+ }
8271
+ paramCloseIndex = nextTagIndex;
8272
+ }
8273
+ let paramValue = innerContent.substring(
8274
+ paramOpenIndex + paramOpenTag.length,
8275
+ paramCloseIndex
8276
+ ).trim();
8277
+ if (paramValue.toLowerCase() === "true") {
8278
+ paramValue = true;
8279
+ } else if (paramValue.toLowerCase() === "false") {
8280
+ paramValue = false;
8281
+ } else if (!isNaN(paramValue) && paramValue.trim() !== "") {
8282
+ const num = Number(paramValue);
8283
+ if (Number.isFinite(num)) {
8284
+ paramValue = num;
8285
+ }
8286
+ }
8287
+ params[paramName] = paramValue;
8288
+ }
8289
+ if (toolName === "attempt_completion") {
8290
+ params["result"] = innerContent.trim();
8291
+ if (params.command) {
8292
+ delete params.command;
8293
+ }
8294
+ }
8295
+ return { toolName, params };
8296
+ }
8297
+ return null;
8298
+ }
8299
+ function createMessagePreview(message, charsPerSide = 200) {
8300
+ if (message === null || message === void 0) {
8301
+ return "null/undefined";
8302
+ }
8303
+ if (typeof message !== "string") {
8304
+ return "null/undefined";
8305
+ }
8306
+ const totalChars = charsPerSide * 2;
8307
+ if (message.length <= totalChars) {
8308
+ return message;
8309
+ }
8310
+ const start = message.substring(0, charsPerSide);
8311
+ const end = message.substring(message.length - charsPerSide);
8312
+ return `${start}...${end}`;
8313
+ }
8314
+ function parseTargets(targets) {
8315
+ if (!targets || typeof targets !== "string") {
8316
+ return [];
8317
+ }
8318
+ return targets.split(/[\s,]+/).filter((f) => f.length > 0);
8319
+ }
8320
+ function parseAndResolvePaths(pathStr, cwd) {
8321
+ if (!pathStr) return [];
8322
+ const paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
8323
+ return paths.map((p) => {
8324
+ if (isAbsolute2(p)) {
8325
+ return p;
8326
+ }
8327
+ return cwd ? resolve2(cwd, p) : p;
8328
+ });
8329
+ }
8330
+ function resolveTargetPath(target, cwd) {
8331
+ const searchStart = target.length > 2 && target[1] === ":" && /[a-zA-Z]/.test(target[0]) ? 2 : 0;
8332
+ const colonIdx = target.indexOf(":", searchStart);
8333
+ const hashIdx = target.indexOf("#");
8334
+ let filePart, suffix;
8335
+ if (colonIdx !== -1 && (hashIdx === -1 || colonIdx < hashIdx)) {
8336
+ filePart = target.substring(0, colonIdx);
8337
+ suffix = target.substring(colonIdx);
8338
+ } else if (hashIdx !== -1) {
8339
+ filePart = target.substring(0, hashIdx);
8340
+ suffix = target.substring(hashIdx);
8341
+ } else {
8342
+ filePart = target;
8343
+ suffix = "";
8344
+ }
8345
+ if (!isAbsolute2(filePart) && cwd) {
8346
+ filePart = resolve2(cwd, filePart);
8347
+ }
8348
+ return filePart + suffix;
8349
+ }
8350
+ var searchSchema, querySchema, extractSchema, delegateSchema, bashSchema, attemptCompletionSchema, searchToolDefinition, queryToolDefinition, extractToolDefinition, delegateToolDefinition, attemptCompletionToolDefinition, bashToolDefinition, searchDescription, queryDescription, extractDescription, delegateDescription, DEFAULT_VALID_TOOLS;
8351
+ var init_common = __esm({
8352
+ "src/tools/common.js"() {
8353
+ "use strict";
8354
+ init_zod();
8355
+ init_edit();
8356
+ searchSchema = external_exports.object({
8357
+ query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
8358
+ path: external_exports.string().optional().default(".").describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.')
8359
+ });
8360
+ querySchema = external_exports.object({
8361
+ pattern: external_exports.string().describe("AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc."),
8362
+ path: external_exports.string().optional().default(".").describe("Path to search in"),
8363
+ language: external_exports.string().optional().default("rust").describe("Programming language to use for parsing"),
8364
+ allow_tests: external_exports.boolean().optional().default(true).describe("Allow test files in search results")
8365
+ });
8366
+ extractSchema = external_exports.object({
8367
+ targets: external_exports.string().optional().describe('File paths or symbols to extract from. Formats: "file.js" (whole file), "file.js:42" (line 42), "file.js:10-20" (lines 10-20), "file.js#funcName" (symbol). Multiple targets separated by spaces.'),
8368
+ input_content: external_exports.string().optional().describe("Text content to extract file paths from (alternative to targets)"),
8369
+ allow_tests: external_exports.boolean().optional().default(true).describe("Include test files in extraction results")
8370
+ });
8371
+ delegateSchema = external_exports.object({
8372
+ task: external_exports.string().describe("The task to delegate to a subagent. Be specific about what needs to be accomplished.")
8373
+ });
8374
+ bashSchema = external_exports.object({
8375
+ command: external_exports.string().describe("The bash command to execute"),
8376
+ workingDirectory: external_exports.string().optional().describe("Directory to execute the command in (optional)"),
8377
+ timeout: external_exports.number().optional().describe("Command timeout in milliseconds (optional)"),
8378
+ env: external_exports.record(external_exports.string()).optional().describe("Additional environment variables (optional)")
8379
+ });
8380
+ attemptCompletionSchema = {
8381
+ // Custom validation that requires result parameter but allows direct XML response
8382
+ safeParse: (params) => {
8383
+ if (!params || typeof params !== "object") {
8384
+ return {
8385
+ success: false,
8386
+ error: {
8387
+ issues: [{
8388
+ code: "invalid_type",
8389
+ expected: "object",
8390
+ received: typeof params,
8391
+ path: [],
8392
+ message: "Expected object"
8393
+ }]
8394
+ }
8395
+ };
8396
+ }
8397
+ if (!("result" in params)) {
8398
+ return {
8399
+ success: false,
8400
+ error: {
8401
+ issues: [{
8402
+ code: "invalid_type",
8403
+ expected: "string",
8404
+ received: "undefined",
8405
+ path: ["result"],
8406
+ message: "Required"
8407
+ }]
8408
+ }
8409
+ };
8410
+ }
8411
+ if (typeof params.result !== "string") {
8412
+ return {
8413
+ success: false,
8414
+ error: {
8415
+ issues: [{
8416
+ code: "invalid_type",
8417
+ expected: "string",
8418
+ received: typeof params.result,
8419
+ path: ["result"],
8420
+ message: "Expected string"
8421
+ }]
8422
+ }
8423
+ };
8424
+ }
8425
+ const filteredData = { result: params.result };
8426
+ return {
8427
+ success: true,
8428
+ data: filteredData
8429
+ };
8430
+ }
8431
+ };
8432
+ searchToolDefinition = `
8433
+ ## search
8434
+ Description: Search code in the repository using Elasticsearch query syntax (except field based queries, e.g. "filename:..." NOT supported).
8435
+
8436
+ You need to focus on main keywords when constructing the query, and always use elastic search syntax like OR AND and brackets to group keywords.
8437
+
8438
+ **Session Management & Caching:**
8439
+ - Ensure not to re-read the same symbols twice - reuse context from previous tool calls
8440
+ - Probe returns a session ID on first run - reuse it for subsequent calls to avoid redundant searches
8441
+ - Once data is returned, it's cached and won't return on next runs (this is expected behavior)
8442
+
8443
+ Parameters:
8444
+ - query: (required) Search query with Elasticsearch syntax. Use quotes for exact matches ("functionName"), AND/OR for boolean logic, - for negation, + for important terms.
8445
+ - path: (optional, default: '.') Path to search in. All dependencies located in /dep folder, under language sub folders, like this: "/dep/go/github.com/owner/repo", "/dep/js/package_name", or "/dep/rust/cargo_name" etc.
8446
+
8447
+ **Workflow:** Always start with search, then use extract for detailed context when needed.
8448
+
8449
+ Usage Example:
8450
+
8451
+ <examples>
8452
+
8453
+ User: Where is the login logic?
8454
+ Assistant workflow:
8455
+ 1. <search>
8456
+ <query>login AND auth AND token</query>
8457
+ <path>.</path>
8458
+ </search>
8459
+ 2. Now lets look closer: <extract>
8460
+ <targets>session.rs#AuthService.login auth.rs:2-100</targets>
8461
+ </extract>
8462
+
8463
+ User: How to calculate the total amount in the payments module?
8464
+ <search>
8465
+ <query>calculate AND payment</query>
8466
+ <path>src/utils</path>
8467
+ </search>
8468
+
8469
+ User: How do the user authentication and authorization work?
8470
+ <search>
8471
+ <query>+user AND (authentication OR authorization OR authz)</query>
8472
+ <path>.</path>
8473
+ </search>
8474
+
8475
+ User: Find all react imports in the project.
8476
+ <search>
8477
+ <query>"import" AND "react"</query>
8478
+ <path>.</path>
8479
+ </search>
8480
+
8481
+ User: Find how decompound library works?
8482
+ <search>
8483
+ <query>decompound</query>
8484
+ <path>/dep/rust/decompound</path>
8485
+ </search>
8486
+
8487
+ </examples>
8488
+ `;
8489
+ queryToolDefinition = `
8490
+ ## query
8491
+ Description: Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.
8492
+ Parameters:
8493
+ - pattern: (required) AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc.
8494
+ - path: (optional, default: '.') Path to search in.
8495
+ - language: (optional, default: 'rust') Programming language to use for parsing.
8496
+ - allow_tests: (optional, default: true) Allow test files in search results (true/false).
8497
+ Usage Example:
8498
+
8499
+ <examples>
8500
+
8501
+ <query>
8502
+ <pattern>function $FUNC($$$PARAMS) { $$$BODY }</pattern>
8503
+ <path>src/parser</path>
8504
+ <language>js</language>
8505
+ </query>
8506
+
8507
+ </examples>
8508
+ `;
8509
+ extractToolDefinition = `
8510
+ ## extract
8511
+ Description: Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. It can be used to read full files as well.
8512
+ Full file extraction should be the LAST RESORT! Always prefer search.
8513
+
8514
+ **Multiple Extraction:** You can extract multiple symbols/files in one call by providing multiple file paths separated by spaces.
8515
+
8516
+ **Session Awareness:** Reuse context from previous tool calls. Don't re-extract the same symbols you already have.
8517
+
8518
+ Parameters:
8519
+ - targets: (required) File paths or symbols to extract from. Formats: "file.js" (whole file), "file.js:42" (code block at line 42), "file.js:10-20" (lines 10-20), "file.js#funcName" (specific symbol). Multiple targets separated by spaces.
8520
+ - input_content: (optional) Text content to extract file paths from (alternative to targets for processing diffs/logs).
8521
+ - allow_tests: (optional, default: true) Include test files in extraction results.
8522
+
8523
+ Usage Example:
8524
+
8525
+ <examples>
8526
+
8527
+ User: Where is the login logic? (After search found relevant files)
8528
+ <extract>
8529
+ <targets>session.rs#AuthService.login auth.rs:2-100 config.rs#DatabaseConfig</targets>
8530
+ </extract>
8531
+
8532
+ User: How does error handling work? (After search identified files)
8533
+ <extract>
8534
+ <targets>error.rs#ErrorType utils.rs#handle_error src/main.rs:50-80</targets>
8535
+ </extract>
8536
+
8537
+ User: How RankManager works
8538
+ <extract>
8539
+ <targets>src/search/ranking.rs#RankManager</targets>
8540
+ </extract>
8541
+
8542
+ User: Lets read the whole file
8543
+ <extract>
8544
+ <targets>src/search/ranking.rs</targets>
8545
+ </extract>
8546
+
8547
+ User: Read the first 10 lines of the file
8548
+ <extract>
8549
+ <targets>src/search/ranking.rs:1-10</targets>
8550
+ </extract>
8551
+
8552
+ User: Read file inside the dependency
8553
+ <extract>
8554
+ <targets>/dep/go/github.com/gorilla/mux/router.go</targets>
8555
+ </extract>
8556
+
8557
+ </examples>
8558
+ `;
8559
+ delegateToolDefinition = `
8560
+ ## delegate
8561
+ Description: Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Use this when you recognize that a user's request involves multiple large, distinct components that would benefit from parallel processing or specialized focus. The AI agent should automatically identify opportunities for task separation and use delegation without explicit user instruction.
8562
+
8563
+ Parameters:
8564
+ - task: (required) A complete, self-contained task that can be executed independently by a subagent. Should be specific and focused on one area of expertise.
8565
+
8566
+ Usage Pattern:
8567
+ When the AI agent encounters complex multi-part requests, it should automatically break them down and delegate:
8568
+
8569
+ <delegate>
8570
+ <task>Analyze all authentication and authorization code in the codebase for security vulnerabilities and provide specific remediation recommendations</task>
8571
+ </delegate>
8572
+
8573
+ <delegate>
8574
+ <task>Review database queries and API endpoints for performance bottlenecks and suggest optimization strategies</task>
8575
+ </delegate>
8576
+
8577
+ The agent uses this tool automatically when it identifies that work can be separated into distinct, parallel tasks for more efficient processing.
8578
+ `;
8579
+ attemptCompletionToolDefinition = `
8580
+ ## attempt_completion
8581
+ Description: Use this tool ONLY when the task is fully complete and you have received confirmation of success for all previous tool uses. Presents the final result to the user. You can provide your response directly inside the XML tags without any parameter wrapper.
8582
+ Parameters:
8583
+ - No validation required - provide your complete answer directly inside the XML tags.
8584
+ Usage Example:
8585
+ <attempt_completion>
8586
+ I have refactored the search module according to the requirements and verified the tests pass. The module now uses the new BM25 ranking algorithm and has improved error handling.
8587
+ </attempt_completion>
8588
+ `;
8589
+ bashToolDefinition = `
8590
+ ## bash
8591
+ Description: Execute bash commands for system exploration and development tasks. This tool has built-in security with allow/deny lists. By default, only safe read-only commands are allowed for code exploration.
8592
+
8593
+ Parameters:
8594
+ - command: (required) The bash command to execute
8595
+ - workingDirectory: (optional) Directory to execute the command in
8596
+ - timeout: (optional) Command timeout in milliseconds
8597
+ - env: (optional) Additional environment variables as an object
8598
+
8599
+ Security: Commands are filtered through allow/deny lists for safety:
8600
+ - Allowed by default: ls, cat, git status, npm list, find, grep, etc.
8601
+ - Denied by default: rm -rf, sudo, npm install, dangerous system commands
8602
+
8603
+ Usage Examples:
8604
+
8605
+ <examples>
8606
+
8607
+ User: What files are in the src directory?
8608
+ <bash>
8609
+ <command>ls -la src/</command>
8610
+ </bash>
8611
+
8612
+ User: Show me the git status
8613
+ <bash>
8614
+ <command>git status</command>
8615
+ </bash>
8616
+
8617
+ User: Find all TypeScript files
8618
+ <bash>
8619
+ <command>find . -name "*.ts" -type f</command>
8620
+ </bash>
8621
+
8622
+ User: Check installed npm packages
8623
+ <bash>
8624
+ <command>npm list --depth=0</command>
8625
+ </bash>
8626
+
8627
+ User: Search for TODO comments in code
8628
+ <bash>
8629
+ <command>grep -r "TODO" src/</command>
8630
+ </bash>
8631
+
8632
+ User: Show recent git commits
8633
+ <bash>
8634
+ <command>git log --oneline -10</command>
8635
+ </bash>
8636
+
8637
+ User: Check system info
8638
+ <bash>
8639
+ <command>uname -a</command>
8640
+ </bash>
8641
+
8642
+ </examples>
8643
+ `;
8644
+ searchDescription = "Search code in the repository using Elasticsearch-like query syntax. Use this tool first for any code-related questions.";
8645
+ queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
8646
+ extractDescription = "Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files.";
8647
+ delegateDescription = "Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Used by AI agents to break down complex requests into focused, parallel tasks.";
8648
+ DEFAULT_VALID_TOOLS = [
8649
+ "search",
8650
+ "query",
8651
+ "extract",
8652
+ "delegate",
8653
+ "listFiles",
8654
+ "searchFiles",
8655
+ "implement",
8656
+ "bash",
8657
+ "attempt_completion"
8658
+ ];
8659
+ }
8660
+ });
8661
+
8662
+ // src/tools/vercel.js
8663
+ import { tool as tool2 } from "ai";
8664
+ var searchTool, queryTool, extractTool, delegateTool;
8665
+ var init_vercel = __esm({
8666
+ "src/tools/vercel.js"() {
8667
+ "use strict";
8668
+ init_search();
8669
+ init_query();
8670
+ init_extract();
8671
+ init_delegate();
8672
+ init_common();
8673
+ searchTool = (options = {}) => {
8674
+ const { sessionId, maxTokens = 1e4, debug = false, outline = false } = options;
8675
+ return tool2({
8676
+ name: "search",
8677
+ description: searchDescription,
8678
+ inputSchema: searchSchema,
8679
+ execute: async ({ query: searchQuery, path: path9, allow_tests, exact, maxTokens: paramMaxTokens, language }) => {
8680
+ try {
8681
+ const effectiveMaxTokens = paramMaxTokens || maxTokens;
8682
+ let searchPaths;
8683
+ if (path9) {
8684
+ searchPaths = parseAndResolvePaths(path9, options.cwd);
8685
+ }
8686
+ if (!searchPaths || searchPaths.length === 0) {
8687
+ searchPaths = [options.cwd || "."];
8688
+ }
8689
+ const searchPath = searchPaths.join(" ");
8690
+ if (debug) {
8691
+ console.error(`Executing search with query: "${searchQuery}", path: "${searchPath}", exact: ${exact ? "true" : "false"}, language: ${language || "all"}, session: ${sessionId || "none"}`);
8692
+ }
8693
+ const searchOptions = {
8694
+ query: searchQuery,
8695
+ path: searchPath,
8696
+ cwd: options.cwd,
8697
+ // Working directory for resolving relative paths
8698
+ allowTests: allow_tests ?? true,
8699
+ exact,
8700
+ json: false,
8701
+ maxTokens: effectiveMaxTokens,
8702
+ session: sessionId,
8703
+ // Pass session ID if provided
8704
+ language
8705
+ // Pass language parameter if provided
8706
+ };
8707
+ if (outline) {
8708
+ searchOptions.format = "outline-xml";
8709
+ }
8710
+ const results = await search(searchOptions);
8711
+ return results;
8712
+ } catch (error) {
8713
+ console.error("Error executing search command:", error);
8714
+ return `Error executing search command: ${error.message}`;
8715
+ }
8716
+ }
8717
+ });
8718
+ };
8719
+ queryTool = (options = {}) => {
8720
+ const { debug = false } = options;
8721
+ return tool2({
8722
+ name: "query",
8723
+ description: queryDescription,
8724
+ inputSchema: querySchema,
8725
+ execute: async ({ pattern, path: path9, language, allow_tests }) => {
8726
+ try {
8727
+ let queryPaths;
8728
+ if (path9) {
8729
+ queryPaths = parseAndResolvePaths(path9, options.cwd);
8730
+ }
8731
+ if (!queryPaths || queryPaths.length === 0) {
8732
+ queryPaths = [options.cwd || "."];
8733
+ }
8734
+ const queryPath = queryPaths.join(" ");
8735
+ if (debug) {
8736
+ console.error(`Executing query with pattern: "${pattern}", path: "${queryPath}", language: ${language || "auto"}`);
8737
+ }
8738
+ const results = await query({
8739
+ pattern,
8740
+ path: queryPath,
8741
+ cwd: options.cwd,
8742
+ // Working directory for resolving relative paths
8743
+ language,
8744
+ allowTests: allow_tests ?? true,
8745
+ json: false
8746
+ });
8422
8747
  return results;
8423
8748
  } catch (error) {
8424
8749
  console.error("Error executing query command:", error);
@@ -8429,7 +8754,7 @@ var init_vercel = __esm({
8429
8754
  };
8430
8755
  extractTool = (options = {}) => {
8431
8756
  const { debug = false, outline = false } = options;
8432
- return tool({
8757
+ return tool2({
8433
8758
  name: "extract",
8434
8759
  description: extractDescription,
8435
8760
  inputSchema: extractSchema,
@@ -8505,7 +8830,7 @@ var init_vercel = __esm({
8505
8830
  };
8506
8831
  delegateTool = (options = {}) => {
8507
8832
  const { debug = false, timeout = 300, cwd, allowedFolders, enableBash = false, bashConfig } = options;
8508
- return tool({
8833
+ return tool2({
8509
8834
  name: "delegate",
8510
8835
  description: delegateDescription,
8511
8836
  inputSchema: delegateSchema,
@@ -8534,14 +8859,15 @@ var init_vercel = __esm({
8534
8859
  if (model !== void 0 && model !== null && typeof model !== "string") {
8535
8860
  throw new TypeError("model must be a string, null, or undefined");
8536
8861
  }
8537
- const effectivePath = path9 || cwd || allowedFolders && allowedFolders[0];
8862
+ const workspaceRoot = allowedFolders && allowedFolders[0];
8863
+ const effectivePath = path9 || workspaceRoot || cwd;
8538
8864
  if (debug) {
8539
8865
  console.error(`Executing delegate with task: "${task.substring(0, 100)}${task.length > 100 ? "..." : ""}"`);
8540
8866
  if (parentSessionId) {
8541
8867
  console.error(`Parent session: ${parentSessionId}`);
8542
8868
  }
8543
8869
  if (effectivePath && effectivePath !== path9) {
8544
- console.error(`Using inherited path: ${effectivePath}`);
8870
+ console.error(`Using workspace root: ${effectivePath} (cwd was: ${cwd || "not set"})`);
8545
8871
  }
8546
8872
  }
8547
8873
  const result = await delegate({
@@ -9105,1083 +9431,806 @@ function parseSimpleCommand(command) {
9105
9431
  /\*\*/,
9106
9432
  // Glob patterns (potentially dangerous)
9107
9433
  /^\s*\{.*,.*\}|\{.*\.\.\.*\}/
9108
- // Brace expansion like {a,b} or {1..10} (but not find {} placeholders)
9109
- ];
9110
- for (const pattern of complexPatterns) {
9111
- if (pattern.test(trimmed)) {
9112
- return {
9113
- success: false,
9114
- error: "Complex shell commands with pipes, operators, or redirections are not supported for security reasons",
9115
- command: null,
9116
- args: [],
9117
- isComplex: true,
9118
- detected: pattern.toString()
9119
- };
9120
- }
9121
- }
9122
- const args = [];
9123
- let current = "";
9124
- let inQuotes = false;
9125
- let quoteChar = "";
9126
- let escaped = false;
9127
- for (let i = 0; i < trimmed.length; i++) {
9128
- const char = trimmed[i];
9129
- const nextChar = i + 1 < trimmed.length ? trimmed[i + 1] : "";
9130
- if (escaped) {
9131
- current += char;
9132
- escaped = false;
9133
- continue;
9134
- }
9135
- if (char === "\\" && !inQuotes) {
9136
- escaped = true;
9137
- continue;
9138
- }
9139
- if (!inQuotes && (char === '"' || char === "'")) {
9140
- inQuotes = true;
9141
- quoteChar = char;
9142
- } else if (inQuotes && char === quoteChar) {
9143
- inQuotes = false;
9144
- quoteChar = "";
9145
- } else if (!inQuotes && char === " ") {
9146
- if (current.trim()) {
9147
- args.push(current.trim());
9148
- current = "";
9149
- }
9150
- } else {
9151
- current += char;
9152
- }
9153
- }
9154
- if (current.trim()) {
9155
- args.push(current.trim());
9156
- }
9157
- if (inQuotes) {
9158
- return {
9159
- success: false,
9160
- error: `Unclosed quote in command: ${quoteChar}`,
9161
- command: null,
9162
- args: [],
9163
- isComplex: false
9164
- };
9165
- }
9166
- if (args.length === 0) {
9167
- return {
9168
- success: false,
9169
- error: "No command found after parsing",
9170
- command: null,
9171
- args: [],
9172
- isComplex: false
9173
- };
9174
- }
9175
- const [baseCommand, ...commandArgs] = args;
9176
- return {
9177
- success: true,
9178
- error: null,
9179
- command: baseCommand,
9180
- args: commandArgs,
9181
- fullArgs: args,
9182
- isComplex: false,
9183
- original: command
9184
- };
9185
- }
9186
- function isComplexCommand(command) {
9187
- const result = parseSimpleCommand(command);
9188
- return result.isComplex;
9189
- }
9190
- function isComplexPattern(pattern) {
9191
- if (!pattern || typeof pattern !== "string") return false;
9192
- const operatorPatterns = [
9193
- /\|/,
9194
- // Pipes
9195
- /&&/,
9196
- // Logical AND
9197
- /\|\|/,
9198
- // Logical OR
9199
- /;/,
9200
- // Command separator
9201
- /&$/,
9202
- // Background execution
9203
- /\$\(/,
9204
- // Command substitution $()
9205
- /`/,
9206
- // Command substitution ``
9207
- />/,
9208
- // Redirection >
9209
- /</
9210
- // Redirection <
9211
- ];
9212
- return operatorPatterns.some((p) => p.test(pattern));
9213
- }
9214
- function globToRegex(pattern) {
9215
- let escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
9216
- escaped = escaped.replace(/\*/g, ".*?");
9217
- return new RegExp("^" + escaped + "$", "i");
9218
- }
9219
- function matchesComplexPattern(command, pattern) {
9220
- if (!command || !pattern) return false;
9221
- const normalizedCommand = command.trim().replace(/\s+/g, " ");
9222
- const normalizedPattern = pattern.trim().replace(/\s+/g, " ");
9223
- try {
9224
- const regex = globToRegex(normalizedPattern);
9225
- return regex.test(normalizedCommand);
9226
- } catch (e) {
9227
- return normalizedCommand === normalizedPattern;
9228
- }
9229
- }
9230
- function parseCommand(command) {
9231
- const result = parseSimpleCommand(command);
9232
- if (!result.success) {
9233
- return {
9234
- command: "",
9235
- args: [],
9236
- error: result.error,
9237
- isComplex: result.isComplex
9238
- };
9239
- }
9240
- return {
9241
- command: result.command,
9242
- args: result.args,
9243
- error: null,
9244
- isComplex: result.isComplex
9245
- };
9246
- }
9247
- function parseCommandForExecution(command) {
9248
- const result = parseSimpleCommand(command);
9249
- if (!result.success) {
9250
- return null;
9251
- }
9252
- return result.fullArgs;
9253
- }
9254
- var init_bashCommandUtils = __esm({
9255
- "src/agent/bashCommandUtils.js"() {
9256
- "use strict";
9257
- }
9258
- });
9259
-
9260
- // src/agent/bashPermissions.js
9261
- function matchesPattern(parsedCommand, pattern) {
9262
- if (!parsedCommand || !pattern) return false;
9263
- const { command, args } = parsedCommand;
9264
- if (!command) return false;
9265
- const patternParts = pattern.split(":");
9266
- const commandName = patternParts[0];
9267
- if (commandName === "*") {
9268
- return true;
9269
- } else if (commandName !== command) {
9270
- return false;
9271
- }
9272
- if (patternParts.length === 1) {
9273
- return true;
9434
+ // Brace expansion like {a,b} or {1..10} (but not find {} placeholders)
9435
+ ];
9436
+ for (const pattern of complexPatterns) {
9437
+ if (pattern.test(trimmed)) {
9438
+ return {
9439
+ success: false,
9440
+ error: "Complex shell commands with pipes, operators, or redirections are not supported for security reasons",
9441
+ command: null,
9442
+ args: [],
9443
+ isComplex: true,
9444
+ detected: pattern.toString()
9445
+ };
9446
+ }
9274
9447
  }
9275
- for (let i = 1; i < patternParts.length; i++) {
9276
- const patternArg = patternParts[i];
9277
- const argIndex = i - 1;
9278
- if (patternArg === "*") {
9448
+ const args = [];
9449
+ let current = "";
9450
+ let inQuotes = false;
9451
+ let quoteChar = "";
9452
+ let escaped = false;
9453
+ for (let i = 0; i < trimmed.length; i++) {
9454
+ const char = trimmed[i];
9455
+ const nextChar = i + 1 < trimmed.length ? trimmed[i + 1] : "";
9456
+ if (escaped) {
9457
+ current += char;
9458
+ escaped = false;
9279
9459
  continue;
9280
9460
  }
9281
- if (argIndex >= args.length) {
9282
- return false;
9283
- }
9284
- const actualArg = args[argIndex];
9285
- if (patternArg !== actualArg) {
9286
- return false;
9461
+ if (char === "\\" && !inQuotes) {
9462
+ escaped = true;
9463
+ continue;
9287
9464
  }
9288
- }
9289
- return true;
9290
- }
9291
- function matchesAnyPattern(parsedCommand, patterns) {
9292
- if (!patterns || patterns.length === 0) return false;
9293
- return patterns.some((pattern) => matchesPattern(parsedCommand, pattern));
9294
- }
9295
- var BashPermissionChecker;
9296
- var init_bashPermissions = __esm({
9297
- "src/agent/bashPermissions.js"() {
9298
- "use strict";
9299
- init_bashDefaults();
9300
- init_bashCommandUtils();
9301
- BashPermissionChecker = class {
9302
- /**
9303
- * Create a permission checker
9304
- * @param {Object} config - Configuration options
9305
- * @param {string[]} [config.allow] - Additional allow patterns
9306
- * @param {string[]} [config.deny] - Additional deny patterns
9307
- * @param {boolean} [config.disableDefaultAllow] - Disable default allow list
9308
- * @param {boolean} [config.disableDefaultDeny] - Disable default deny list
9309
- * @param {boolean} [config.debug] - Enable debug logging
9310
- */
9311
- constructor(config = {}) {
9312
- this.debug = config.debug || false;
9313
- this.allowPatterns = [];
9314
- if (!config.disableDefaultAllow) {
9315
- this.allowPatterns.push(...DEFAULT_ALLOW_PATTERNS);
9316
- if (this.debug) {
9317
- console.log(`[BashPermissions] Added ${DEFAULT_ALLOW_PATTERNS.length} default allow patterns`);
9318
- }
9319
- }
9320
- if (config.allow && Array.isArray(config.allow)) {
9321
- this.allowPatterns.push(...config.allow);
9322
- if (this.debug) {
9323
- console.log(`[BashPermissions] Added ${config.allow.length} custom allow patterns:`, config.allow);
9324
- }
9325
- }
9326
- this.denyPatterns = [];
9327
- if (!config.disableDefaultDeny) {
9328
- this.denyPatterns.push(...DEFAULT_DENY_PATTERNS);
9329
- if (this.debug) {
9330
- console.log(`[BashPermissions] Added ${DEFAULT_DENY_PATTERNS.length} default deny patterns`);
9331
- }
9332
- }
9333
- if (config.deny && Array.isArray(config.deny)) {
9334
- this.denyPatterns.push(...config.deny);
9335
- if (this.debug) {
9336
- console.log(`[BashPermissions] Added ${config.deny.length} custom deny patterns:`, config.deny);
9337
- }
9338
- }
9339
- if (this.debug) {
9340
- console.log(`[BashPermissions] Total patterns - Allow: ${this.allowPatterns.length}, Deny: ${this.denyPatterns.length}`);
9341
- }
9342
- }
9343
- /**
9344
- * Check if a simple command is allowed (complex commands allowed if they match patterns)
9345
- * @param {string} command - Command to check
9346
- * @returns {Object} Permission result
9347
- */
9348
- check(command) {
9349
- if (!command || typeof command !== "string") {
9350
- return {
9351
- allowed: false,
9352
- reason: "Invalid or empty command",
9353
- command
9354
- };
9355
- }
9356
- const commandIsComplex = isComplexCommand(command);
9357
- if (commandIsComplex) {
9358
- return this._checkComplexCommand(command);
9359
- }
9360
- const parsed = parseCommand(command);
9361
- if (parsed.error) {
9362
- return {
9363
- allowed: false,
9364
- reason: parsed.error,
9365
- command
9366
- };
9367
- }
9368
- if (!parsed.command) {
9369
- return {
9370
- allowed: false,
9371
- reason: "No valid command found",
9372
- command
9373
- };
9374
- }
9375
- if (this.debug) {
9376
- console.log(`[BashPermissions] Checking simple command: "${command}"`);
9377
- console.log(`[BashPermissions] Parsed: ${parsed.command} with args: [${parsed.args.join(", ")}]`);
9378
- }
9379
- if (matchesAnyPattern(parsed, this.denyPatterns)) {
9380
- const matchedPatterns = this.denyPatterns.filter((pattern) => matchesPattern(parsed, pattern));
9381
- return {
9382
- allowed: false,
9383
- reason: `Command matches deny pattern: ${matchedPatterns[0]}`,
9384
- command,
9385
- parsed,
9386
- matchedPatterns
9387
- };
9388
- }
9389
- if (this.allowPatterns.length > 0) {
9390
- if (!matchesAnyPattern(parsed, this.allowPatterns)) {
9391
- return {
9392
- allowed: false,
9393
- reason: "Command not in allow list",
9394
- command,
9395
- parsed
9396
- };
9397
- }
9398
- }
9399
- const result = {
9400
- allowed: true,
9401
- command,
9402
- parsed,
9403
- isComplex: false
9404
- };
9405
- if (this.debug) {
9406
- console.log(`[BashPermissions] ALLOWED - command passed all checks`);
9407
- }
9408
- return result;
9409
- }
9410
- /**
9411
- * Check a complex command against complex patterns in allow/deny lists
9412
- * @private
9413
- * @param {string} command - Complex command to check
9414
- * @returns {Object} Permission result
9415
- */
9416
- _checkComplexCommand(command) {
9417
- if (this.debug) {
9418
- console.log(`[BashPermissions] Checking complex command: "${command}"`);
9419
- }
9420
- const complexAllowPatterns = this.allowPatterns.filter((p) => isComplexPattern(p));
9421
- const complexDenyPatterns = this.denyPatterns.filter((p) => isComplexPattern(p));
9422
- if (this.debug) {
9423
- console.log(`[BashPermissions] Complex allow patterns: ${complexAllowPatterns.length}`);
9424
- console.log(`[BashPermissions] Complex deny patterns: ${complexDenyPatterns.length}`);
9425
- }
9426
- for (const pattern of complexDenyPatterns) {
9427
- if (matchesComplexPattern(command, pattern)) {
9428
- if (this.debug) {
9429
- console.log(`[BashPermissions] DENIED - matches complex deny pattern: ${pattern}`);
9430
- }
9431
- return {
9432
- allowed: false,
9433
- reason: `Command matches deny pattern: ${pattern}`,
9434
- command,
9435
- isComplex: true,
9436
- matchedPatterns: [pattern]
9437
- };
9438
- }
9439
- }
9440
- for (const pattern of complexAllowPatterns) {
9441
- if (matchesComplexPattern(command, pattern)) {
9442
- if (this.debug) {
9443
- console.log(`[BashPermissions] ALLOWED - matches complex allow pattern: ${pattern}`);
9444
- }
9445
- return {
9446
- allowed: true,
9447
- command,
9448
- isComplex: true,
9449
- matchedPattern: pattern
9450
- };
9451
- }
9452
- }
9453
- if (this.debug) {
9454
- console.log(`[BashPermissions] DENIED - no matching complex pattern found`);
9455
- }
9456
- return {
9457
- allowed: false,
9458
- reason: 'Complex shell commands require explicit allow patterns (e.g., "cd * && git *")',
9459
- command,
9460
- isComplex: true
9461
- };
9462
- }
9463
- /**
9464
- * Get configuration summary
9465
- * @returns {Object} Configuration info
9466
- */
9467
- getConfig() {
9468
- return {
9469
- allowPatterns: this.allowPatterns.length,
9470
- denyPatterns: this.denyPatterns.length,
9471
- totalPatterns: this.allowPatterns.length + this.denyPatterns.length
9472
- };
9465
+ if (!inQuotes && (char === '"' || char === "'")) {
9466
+ inQuotes = true;
9467
+ quoteChar = char;
9468
+ } else if (inQuotes && char === quoteChar) {
9469
+ inQuotes = false;
9470
+ quoteChar = "";
9471
+ } else if (!inQuotes && char === " ") {
9472
+ if (current.trim()) {
9473
+ args.push(current.trim());
9474
+ current = "";
9473
9475
  }
9474
- };
9475
- }
9476
- });
9477
-
9478
- // src/agent/bashExecutor.js
9479
- import { spawn as spawn2 } from "child_process";
9480
- import { resolve as resolve2, join } from "path";
9481
- import { existsSync } from "fs";
9482
- async function executeBashCommand(command, options = {}) {
9483
- const {
9484
- workingDirectory = process.cwd(),
9485
- timeout = 12e4,
9486
- // 2 minutes default
9487
- env = {},
9488
- maxBuffer = 10 * 1024 * 1024,
9489
- // 10MB
9490
- debug = false
9491
- } = options;
9492
- let cwd = workingDirectory;
9493
- try {
9494
- cwd = resolve2(cwd);
9495
- if (!existsSync(cwd)) {
9496
- throw new Error(`Working directory does not exist: ${cwd}`);
9476
+ } else {
9477
+ current += char;
9497
9478
  }
9498
- } catch (error) {
9479
+ }
9480
+ if (current.trim()) {
9481
+ args.push(current.trim());
9482
+ }
9483
+ if (inQuotes) {
9499
9484
  return {
9500
9485
  success: false,
9501
- error: `Invalid working directory: ${error.message}`,
9502
- stdout: "",
9503
- stderr: "",
9504
- exitCode: 1,
9505
- command,
9506
- workingDirectory: cwd,
9507
- duration: 0
9486
+ error: `Unclosed quote in command: ${quoteChar}`,
9487
+ command: null,
9488
+ args: [],
9489
+ isComplex: false
9508
9490
  };
9509
9491
  }
9510
- const startTime = Date.now();
9511
- if (debug) {
9512
- console.log(`[BashExecutor] Executing command: "${command}"`);
9513
- console.log(`[BashExecutor] Working directory: "${cwd}"`);
9514
- console.log(`[BashExecutor] Timeout: ${timeout}ms`);
9515
- }
9516
- return new Promise((resolve7, reject2) => {
9517
- const processEnv = {
9518
- ...process.env,
9519
- ...env
9492
+ if (args.length === 0) {
9493
+ return {
9494
+ success: false,
9495
+ error: "No command found after parsing",
9496
+ command: null,
9497
+ args: [],
9498
+ isComplex: false
9520
9499
  };
9521
- const isComplex = isComplexCommand(command);
9522
- let cmd, cmdArgs, useShell;
9523
- if (isComplex) {
9524
- cmd = "sh";
9525
- cmdArgs = ["-c", command];
9526
- useShell = false;
9527
- if (debug) {
9528
- console.log(`[BashExecutor] Complex command - using sh -c`);
9529
- }
9530
- } else {
9531
- const args = parseCommandForExecution(command);
9532
- if (!args || args.length === 0) {
9533
- resolve7({
9534
- success: false,
9535
- error: "Failed to parse command",
9536
- stdout: "",
9537
- stderr: "",
9538
- exitCode: 1,
9539
- command,
9540
- workingDirectory: cwd,
9541
- duration: Date.now() - startTime
9542
- });
9543
- return;
9544
- }
9545
- [cmd, ...cmdArgs] = args;
9546
- useShell = false;
9547
- }
9548
- const child = spawn2(cmd, cmdArgs, {
9549
- cwd,
9550
- env: processEnv,
9551
- stdio: ["ignore", "pipe", "pipe"],
9552
- // stdin ignored, capture stdout/stderr
9553
- shell: useShell,
9554
- // false for security
9555
- windowsHide: true
9556
- });
9557
- let stdout = "";
9558
- let stderr = "";
9559
- let killed = false;
9560
- let timeoutHandle;
9561
- if (timeout > 0) {
9562
- timeoutHandle = setTimeout(() => {
9563
- if (!killed) {
9564
- killed = true;
9565
- child.kill("SIGTERM");
9566
- setTimeout(() => {
9567
- if (child.exitCode === null) {
9568
- child.kill("SIGKILL");
9569
- }
9570
- }, 5e3);
9571
- }
9572
- }, timeout);
9573
- }
9574
- child.stdout.on("data", (data) => {
9575
- const chunk = data.toString();
9576
- if (stdout.length + chunk.length <= maxBuffer) {
9577
- stdout += chunk;
9578
- } else {
9579
- if (!killed) {
9580
- killed = true;
9581
- child.kill("SIGTERM");
9582
- }
9583
- }
9584
- });
9585
- child.stderr.on("data", (data) => {
9586
- const chunk = data.toString();
9587
- if (stderr.length + chunk.length <= maxBuffer) {
9588
- stderr += chunk;
9589
- } else {
9590
- if (!killed) {
9591
- killed = true;
9592
- child.kill("SIGTERM");
9593
- }
9594
- }
9595
- });
9596
- child.on("close", (code, signal) => {
9597
- if (timeoutHandle) {
9598
- clearTimeout(timeoutHandle);
9599
- }
9600
- const duration = Date.now() - startTime;
9601
- if (debug) {
9602
- console.log(`[BashExecutor] Command completed - Code: ${code}, Signal: ${signal}, Duration: ${duration}ms`);
9603
- console.log(`[BashExecutor] Stdout length: ${stdout.length}, Stderr length: ${stderr.length}`);
9604
- }
9605
- let success = true;
9606
- let error = "";
9607
- if (killed) {
9608
- success = false;
9609
- if (stdout.length + stderr.length > maxBuffer) {
9610
- error = `Command output exceeded maximum buffer size (${maxBuffer} bytes)`;
9611
- } else {
9612
- error = `Command timed out after ${timeout}ms`;
9613
- }
9614
- } else if (code !== 0) {
9615
- success = false;
9616
- error = `Command exited with code ${code}`;
9617
- }
9618
- resolve7({
9619
- success,
9620
- error,
9621
- stdout: stdout.trim(),
9622
- stderr: stderr.trim(),
9623
- exitCode: code,
9624
- signal,
9625
- command,
9626
- workingDirectory: cwd,
9627
- duration,
9628
- killed
9629
- });
9630
- });
9631
- child.on("error", (error) => {
9632
- if (timeoutHandle) {
9633
- clearTimeout(timeoutHandle);
9634
- }
9635
- if (debug) {
9636
- console.log(`[BashExecutor] Spawn error:`, error);
9637
- }
9638
- resolve7({
9639
- success: false,
9640
- error: `Failed to execute command: ${error.message}`,
9641
- stdout: "",
9642
- stderr: "",
9643
- exitCode: 1,
9644
- command,
9645
- workingDirectory: cwd,
9646
- duration: Date.now() - startTime
9647
- });
9648
- });
9649
- });
9500
+ }
9501
+ const [baseCommand, ...commandArgs] = args;
9502
+ return {
9503
+ success: true,
9504
+ error: null,
9505
+ command: baseCommand,
9506
+ args: commandArgs,
9507
+ fullArgs: args,
9508
+ isComplex: false,
9509
+ original: command
9510
+ };
9650
9511
  }
9651
- function formatExecutionResult(result, includeMetadata = false) {
9652
- if (!result) {
9653
- return "No result available";
9512
+ function isComplexCommand(command) {
9513
+ const result = parseSimpleCommand(command);
9514
+ return result.isComplex;
9515
+ }
9516
+ function isComplexPattern(pattern) {
9517
+ if (!pattern || typeof pattern !== "string") return false;
9518
+ const operatorPatterns = [
9519
+ /\|/,
9520
+ // Pipes
9521
+ /&&/,
9522
+ // Logical AND
9523
+ /\|\|/,
9524
+ // Logical OR
9525
+ /;/,
9526
+ // Command separator
9527
+ /&$/,
9528
+ // Background execution
9529
+ /\$\(/,
9530
+ // Command substitution $()
9531
+ /`/,
9532
+ // Command substitution ``
9533
+ />/,
9534
+ // Redirection >
9535
+ /</
9536
+ // Redirection <
9537
+ ];
9538
+ return operatorPatterns.some((p) => p.test(pattern));
9539
+ }
9540
+ function globToRegex(pattern) {
9541
+ let escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
9542
+ escaped = escaped.replace(/\*/g, ".*?");
9543
+ return new RegExp("^" + escaped + "$", "i");
9544
+ }
9545
+ function matchesComplexPattern(command, pattern) {
9546
+ if (!command || !pattern) return false;
9547
+ const normalizedCommand = command.trim().replace(/\s+/g, " ");
9548
+ const normalizedPattern = pattern.trim().replace(/\s+/g, " ");
9549
+ try {
9550
+ const regex = globToRegex(normalizedPattern);
9551
+ return regex.test(normalizedCommand);
9552
+ } catch (e) {
9553
+ return normalizedCommand === normalizedPattern;
9654
9554
  }
9655
- let output = "";
9656
- if (includeMetadata) {
9657
- output += `Command: ${result.command}
9658
- `;
9659
- output += `Working directory: ${result.workingDirectory}
9660
- `;
9661
- output += `Duration: ${result.duration}ms
9662
- `;
9663
- output += `Exit Code: ${result.exitCode}
9664
- `;
9665
- if (result.signal) {
9666
- output += `Signal: ${result.signal}
9667
- `;
9668
- }
9669
- output += "\n";
9555
+ }
9556
+ function parseCommand(command) {
9557
+ const result = parseSimpleCommand(command);
9558
+ if (!result.success) {
9559
+ return {
9560
+ command: "",
9561
+ args: [],
9562
+ error: result.error,
9563
+ isComplex: result.isComplex
9564
+ };
9670
9565
  }
9671
- if (result.stdout) {
9672
- if (includeMetadata) {
9673
- output += "--- STDOUT ---\n";
9674
- }
9675
- output += result.stdout;
9676
- if (includeMetadata && result.stderr) {
9677
- output += "\n";
9678
- }
9566
+ return {
9567
+ command: result.command,
9568
+ args: result.args,
9569
+ error: null,
9570
+ isComplex: result.isComplex
9571
+ };
9572
+ }
9573
+ function parseCommandForExecution(command) {
9574
+ const result = parseSimpleCommand(command);
9575
+ if (!result.success) {
9576
+ return null;
9679
9577
  }
9680
- if (result.stderr) {
9681
- if (includeMetadata) {
9682
- if (result.stdout) output += "\n";
9683
- output += "--- STDERR ---\n";
9684
- } else if (result.stdout) {
9685
- output += "\n--- STDERR ---\n";
9686
- }
9687
- output += result.stderr;
9578
+ return result.fullArgs;
9579
+ }
9580
+ var init_bashCommandUtils = __esm({
9581
+ "src/agent/bashCommandUtils.js"() {
9582
+ "use strict";
9688
9583
  }
9689
- if (!result.success && result.error && !result.stderr) {
9690
- if (output) output += "\n";
9691
- output += `Error: ${result.error}`;
9584
+ });
9585
+
9586
+ // src/agent/bashPermissions.js
9587
+ function matchesPattern(parsedCommand, pattern) {
9588
+ if (!parsedCommand || !pattern) return false;
9589
+ const { command, args } = parsedCommand;
9590
+ if (!command) return false;
9591
+ const patternParts = pattern.split(":");
9592
+ const commandName = patternParts[0];
9593
+ if (commandName === "*") {
9594
+ return true;
9595
+ } else if (commandName !== command) {
9596
+ return false;
9692
9597
  }
9693
- if (!result.success && result.exitCode !== void 0 && result.exitCode !== 0) {
9694
- if (output) output += "\n";
9695
- output += `Exit code: ${result.exitCode}`;
9598
+ if (patternParts.length === 1) {
9599
+ return true;
9696
9600
  }
9697
- return output || (result.success ? "Command completed successfully (no output)" : "Command failed (no output)");
9698
- }
9699
- function validateExecutionOptions(options = {}) {
9700
- const errors = [];
9701
- const warnings = [];
9702
- if (options.timeout !== void 0) {
9703
- if (typeof options.timeout !== "number" || options.timeout < 0) {
9704
- errors.push("timeout must be a non-negative number");
9705
- } else if (options.timeout > 6e5) {
9706
- warnings.push("timeout is very high (>10 minutes)");
9601
+ for (let i = 1; i < patternParts.length; i++) {
9602
+ const patternArg = patternParts[i];
9603
+ const argIndex = i - 1;
9604
+ if (patternArg === "*") {
9605
+ continue;
9707
9606
  }
9708
- }
9709
- if (options.maxBuffer !== void 0) {
9710
- if (typeof options.maxBuffer !== "number" || options.maxBuffer < 1024) {
9711
- errors.push("maxBuffer must be at least 1024 bytes");
9712
- } else if (options.maxBuffer > 100 * 1024 * 1024) {
9713
- warnings.push("maxBuffer is very high (>100MB)");
9607
+ if (argIndex >= args.length) {
9608
+ return false;
9714
9609
  }
9715
- }
9716
- if (options.workingDirectory) {
9717
- if (typeof options.workingDirectory !== "string") {
9718
- errors.push("workingDirectory must be a string");
9719
- } else if (!existsSync(options.workingDirectory)) {
9720
- errors.push(`workingDirectory does not exist: ${options.workingDirectory}`);
9610
+ const actualArg = args[argIndex];
9611
+ if (patternArg !== actualArg) {
9612
+ return false;
9721
9613
  }
9722
9614
  }
9723
- if (options.env && typeof options.env !== "object") {
9724
- errors.push("env must be an object");
9725
- }
9726
- return {
9727
- valid: errors.length === 0,
9728
- errors,
9729
- warnings
9730
- };
9615
+ return true;
9731
9616
  }
9732
- var init_bashExecutor = __esm({
9733
- "src/agent/bashExecutor.js"() {
9617
+ function matchesAnyPattern(parsedCommand, patterns) {
9618
+ if (!patterns || patterns.length === 0) return false;
9619
+ return patterns.some((pattern) => matchesPattern(parsedCommand, pattern));
9620
+ }
9621
+ var BashPermissionChecker;
9622
+ var init_bashPermissions = __esm({
9623
+ "src/agent/bashPermissions.js"() {
9734
9624
  "use strict";
9625
+ init_bashDefaults();
9735
9626
  init_bashCommandUtils();
9736
- }
9737
- });
9738
-
9739
- // src/tools/bash.js
9740
- import { tool as tool2 } from "ai";
9741
- import { resolve as resolve3, isAbsolute as isAbsolute2, sep } from "path";
9742
- var bashTool;
9743
- var init_bash = __esm({
9744
- "src/tools/bash.js"() {
9745
- "use strict";
9746
- init_bashPermissions();
9747
- init_bashExecutor();
9748
- bashTool = (options = {}) => {
9749
- const {
9750
- bashConfig = {},
9751
- debug = false,
9752
- cwd,
9753
- allowedFolders = []
9754
- } = options;
9755
- const permissionChecker = new BashPermissionChecker({
9756
- allow: bashConfig.allow,
9757
- deny: bashConfig.deny,
9758
- disableDefaultAllow: bashConfig.disableDefaultAllow,
9759
- disableDefaultDeny: bashConfig.disableDefaultDeny,
9760
- debug
9761
- });
9762
- const getDefaultWorkingDirectory = () => {
9763
- if (bashConfig.workingDirectory) {
9764
- return bashConfig.workingDirectory;
9627
+ BashPermissionChecker = class {
9628
+ /**
9629
+ * Create a permission checker
9630
+ * @param {Object} config - Configuration options
9631
+ * @param {string[]} [config.allow] - Additional allow patterns
9632
+ * @param {string[]} [config.deny] - Additional deny patterns
9633
+ * @param {boolean} [config.disableDefaultAllow] - Disable default allow list
9634
+ * @param {boolean} [config.disableDefaultDeny] - Disable default deny list
9635
+ * @param {boolean} [config.debug] - Enable debug logging
9636
+ */
9637
+ constructor(config = {}) {
9638
+ this.debug = config.debug || false;
9639
+ this.allowPatterns = [];
9640
+ if (!config.disableDefaultAllow) {
9641
+ this.allowPatterns.push(...DEFAULT_ALLOW_PATTERNS);
9642
+ if (this.debug) {
9643
+ console.log(`[BashPermissions] Added ${DEFAULT_ALLOW_PATTERNS.length} default allow patterns`);
9644
+ }
9765
9645
  }
9766
- if (cwd) {
9767
- return cwd;
9646
+ if (config.allow && Array.isArray(config.allow)) {
9647
+ this.allowPatterns.push(...config.allow);
9648
+ if (this.debug) {
9649
+ console.log(`[BashPermissions] Added ${config.allow.length} custom allow patterns:`, config.allow);
9650
+ }
9768
9651
  }
9769
- if (allowedFolders && allowedFolders.length > 0) {
9770
- return allowedFolders[0];
9652
+ this.denyPatterns = [];
9653
+ if (!config.disableDefaultDeny) {
9654
+ this.denyPatterns.push(...DEFAULT_DENY_PATTERNS);
9655
+ if (this.debug) {
9656
+ console.log(`[BashPermissions] Added ${DEFAULT_DENY_PATTERNS.length} default deny patterns`);
9657
+ }
9771
9658
  }
9772
- return process.cwd();
9773
- };
9774
- return tool2({
9775
- name: "bash",
9776
- description: `Execute bash commands for system exploration and development tasks.
9777
-
9778
- Security: This tool has built-in security with allow/deny lists. By default, only safe read-only commands are allowed for code exploration.
9779
-
9780
- Parameters:
9781
- - command: (required) The bash command to execute
9782
- - workingDirectory: (optional) Directory to execute command in
9783
- - timeout: (optional) Command timeout in milliseconds
9784
- - env: (optional) Additional environment variables
9785
-
9786
- Examples of allowed commands by default:
9787
- - File exploration: ls, cat, head, tail, find, grep
9788
- - Git operations: git status, git log, git diff, git show
9789
- - Package info: npm list, pip list, cargo --version
9790
- - System info: whoami, pwd, uname, date
9791
-
9792
- Dangerous commands are blocked by default (rm -rf, sudo, npm install, etc.)`,
9793
- inputSchema: {
9794
- type: "object",
9795
- properties: {
9796
- command: {
9797
- type: "string",
9798
- description: "The bash command to execute"
9799
- },
9800
- workingDirectory: {
9801
- type: "string",
9802
- description: "Directory to execute the command in (optional)"
9803
- },
9804
- timeout: {
9805
- type: "number",
9806
- description: "Command timeout in milliseconds (optional)",
9807
- minimum: 1e3,
9808
- maximum: 6e5
9809
- },
9810
- env: {
9811
- type: "object",
9812
- description: "Additional environment variables (optional)",
9813
- additionalProperties: {
9814
- type: "string"
9815
- }
9816
- }
9817
- },
9818
- required: ["command"],
9819
- additionalProperties: false
9820
- },
9821
- execute: async ({ command, workingDirectory, timeout, env }) => {
9822
- try {
9823
- if (command === null || command === void 0 || typeof command !== "string") {
9824
- return "Error: Command is required and must be a string";
9825
- }
9826
- if (command.trim().length === 0) {
9827
- return "Error: Command cannot be empty";
9828
- }
9829
- const permissionResult = permissionChecker.check(command.trim());
9830
- if (!permissionResult.allowed) {
9831
- if (debug) {
9832
- console.log(`[BashTool] Permission denied for command: "${command}"`);
9833
- console.log(`[BashTool] Reason: ${permissionResult.reason}`);
9834
- }
9835
- return `Permission denied: ${permissionResult.reason}
9836
-
9837
- This command is not allowed by the current security policy.
9838
-
9839
- Common reasons:
9840
- 1. The command is in the deny list (potentially dangerous)
9841
- 2. The command is not in the allow list (not a recognized safe command)
9842
-
9843
- If you believe this command should be allowed, you can:
9844
- - Use the --bash-allow option to add specific patterns
9845
- - Use the --no-default-bash-deny flag to remove default restrictions (not recommended)
9846
-
9847
- For code exploration, try these safe alternatives:
9848
- - ls, cat, head, tail for file operations
9849
- - find, grep, rg for searching
9850
- - git status, git log, git show for git operations
9851
- - npm list, pip list for package information`;
9852
- }
9853
- const defaultDir = getDefaultWorkingDirectory();
9854
- const workingDir = workingDirectory ? isAbsolute2(workingDirectory) ? resolve3(workingDirectory) : resolve3(defaultDir, workingDirectory) : defaultDir;
9855
- if (allowedFolders && allowedFolders.length > 0) {
9856
- const resolvedWorkingDir = resolve3(workingDir);
9857
- const isAllowed = allowedFolders.some((folder) => {
9858
- const resolvedFolder = resolve3(folder);
9859
- return resolvedWorkingDir === resolvedFolder || resolvedWorkingDir.startsWith(resolvedFolder + sep);
9860
- });
9861
- if (!isAllowed) {
9862
- return `Error: Working directory "${workingDir}" is not within allowed folders: ${allowedFolders.join(", ")}`;
9863
- }
9864
- }
9865
- const executionOptions = {
9866
- workingDirectory: workingDir,
9867
- timeout: timeout || bashConfig.timeout || 12e4,
9868
- env: { ...bashConfig.env, ...env },
9869
- maxBuffer: bashConfig.maxBuffer,
9870
- debug
9659
+ if (config.deny && Array.isArray(config.deny)) {
9660
+ this.denyPatterns.push(...config.deny);
9661
+ if (this.debug) {
9662
+ console.log(`[BashPermissions] Added ${config.deny.length} custom deny patterns:`, config.deny);
9663
+ }
9664
+ }
9665
+ if (this.debug) {
9666
+ console.log(`[BashPermissions] Total patterns - Allow: ${this.allowPatterns.length}, Deny: ${this.denyPatterns.length}`);
9667
+ }
9668
+ }
9669
+ /**
9670
+ * Check if a simple command is allowed (complex commands allowed if they match patterns)
9671
+ * @param {string} command - Command to check
9672
+ * @returns {Object} Permission result
9673
+ */
9674
+ check(command) {
9675
+ if (!command || typeof command !== "string") {
9676
+ return {
9677
+ allowed: false,
9678
+ reason: "Invalid or empty command",
9679
+ command
9680
+ };
9681
+ }
9682
+ const commandIsComplex = isComplexCommand(command);
9683
+ if (commandIsComplex) {
9684
+ return this._checkComplexCommand(command);
9685
+ }
9686
+ const parsed = parseCommand(command);
9687
+ if (parsed.error) {
9688
+ return {
9689
+ allowed: false,
9690
+ reason: parsed.error,
9691
+ command
9692
+ };
9693
+ }
9694
+ if (!parsed.command) {
9695
+ return {
9696
+ allowed: false,
9697
+ reason: "No valid command found",
9698
+ command
9699
+ };
9700
+ }
9701
+ if (this.debug) {
9702
+ console.log(`[BashPermissions] Checking simple command: "${command}"`);
9703
+ console.log(`[BashPermissions] Parsed: ${parsed.command} with args: [${parsed.args.join(", ")}]`);
9704
+ }
9705
+ if (matchesAnyPattern(parsed, this.denyPatterns)) {
9706
+ const matchedPatterns = this.denyPatterns.filter((pattern) => matchesPattern(parsed, pattern));
9707
+ return {
9708
+ allowed: false,
9709
+ reason: `Command matches deny pattern: ${matchedPatterns[0]}`,
9710
+ command,
9711
+ parsed,
9712
+ matchedPatterns
9713
+ };
9714
+ }
9715
+ if (this.allowPatterns.length > 0) {
9716
+ if (!matchesAnyPattern(parsed, this.allowPatterns)) {
9717
+ return {
9718
+ allowed: false,
9719
+ reason: "Command not in allow list",
9720
+ command,
9721
+ parsed
9871
9722
  };
9872
- const validation = validateExecutionOptions(executionOptions);
9873
- if (!validation.valid) {
9874
- return `Error: Invalid execution options: ${validation.errors.join(", ")}`;
9875
- }
9876
- if (validation.warnings.length > 0 && debug) {
9877
- console.log("[BashTool] Warnings:", validation.warnings);
9878
- }
9879
- if (debug) {
9880
- console.log(`[BashTool] Executing command: "${command}"`);
9881
- console.log(`[BashTool] Working directory: "${workingDir}"`);
9882
- console.log(`[BashTool] Timeout: ${executionOptions.timeout}ms`);
9723
+ }
9724
+ }
9725
+ const result = {
9726
+ allowed: true,
9727
+ command,
9728
+ parsed,
9729
+ isComplex: false
9730
+ };
9731
+ if (this.debug) {
9732
+ console.log(`[BashPermissions] ALLOWED - command passed all checks`);
9733
+ }
9734
+ return result;
9735
+ }
9736
+ /**
9737
+ * Check a complex command against complex patterns in allow/deny lists
9738
+ * @private
9739
+ * @param {string} command - Complex command to check
9740
+ * @returns {Object} Permission result
9741
+ */
9742
+ _checkComplexCommand(command) {
9743
+ if (this.debug) {
9744
+ console.log(`[BashPermissions] Checking complex command: "${command}"`);
9745
+ }
9746
+ const complexAllowPatterns = this.allowPatterns.filter((p) => isComplexPattern(p));
9747
+ const complexDenyPatterns = this.denyPatterns.filter((p) => isComplexPattern(p));
9748
+ if (this.debug) {
9749
+ console.log(`[BashPermissions] Complex allow patterns: ${complexAllowPatterns.length}`);
9750
+ console.log(`[BashPermissions] Complex deny patterns: ${complexDenyPatterns.length}`);
9751
+ }
9752
+ for (const pattern of complexDenyPatterns) {
9753
+ if (matchesComplexPattern(command, pattern)) {
9754
+ if (this.debug) {
9755
+ console.log(`[BashPermissions] DENIED - matches complex deny pattern: ${pattern}`);
9883
9756
  }
9884
- const result = await executeBashCommand(command.trim(), executionOptions);
9885
- if (debug) {
9886
- console.log(`[BashTool] Command completed - Success: ${result.success}, Duration: ${result.duration}ms`);
9757
+ return {
9758
+ allowed: false,
9759
+ reason: `Command matches deny pattern: ${pattern}`,
9760
+ command,
9761
+ isComplex: true,
9762
+ matchedPatterns: [pattern]
9763
+ };
9764
+ }
9765
+ }
9766
+ for (const pattern of complexAllowPatterns) {
9767
+ if (matchesComplexPattern(command, pattern)) {
9768
+ if (this.debug) {
9769
+ console.log(`[BashPermissions] ALLOWED - matches complex allow pattern: ${pattern}`);
9887
9770
  }
9888
- const formattedResult = formatExecutionResult(result, debug);
9889
- if (!result.success) {
9890
- let errorInfo = `
9771
+ return {
9772
+ allowed: true,
9773
+ command,
9774
+ isComplex: true,
9775
+ matchedPattern: pattern
9776
+ };
9777
+ }
9778
+ }
9779
+ if (this.debug) {
9780
+ console.log(`[BashPermissions] DENIED - no matching complex pattern found`);
9781
+ }
9782
+ return {
9783
+ allowed: false,
9784
+ reason: 'Complex shell commands require explicit allow patterns (e.g., "cd * && git *")',
9785
+ command,
9786
+ isComplex: true
9787
+ };
9788
+ }
9789
+ /**
9790
+ * Get configuration summary
9791
+ * @returns {Object} Configuration info
9792
+ */
9793
+ getConfig() {
9794
+ return {
9795
+ allowPatterns: this.allowPatterns.length,
9796
+ denyPatterns: this.denyPatterns.length,
9797
+ totalPatterns: this.allowPatterns.length + this.denyPatterns.length
9798
+ };
9799
+ }
9800
+ };
9801
+ }
9802
+ });
9891
9803
 
9892
- Command failed with exit code ${result.exitCode}`;
9893
- if (result.killed) {
9894
- errorInfo += ` (${result.error})`;
9895
- }
9896
- return formattedResult + errorInfo;
9897
- }
9898
- return formattedResult;
9899
- } catch (error) {
9900
- if (debug) {
9901
- console.error("[BashTool] Execution error:", error);
9804
+ // src/agent/bashExecutor.js
9805
+ import { spawn as spawn2 } from "child_process";
9806
+ import { resolve as resolve3, join } from "path";
9807
+ import { existsSync as existsSync2 } from "fs";
9808
+ async function executeBashCommand(command, options = {}) {
9809
+ const {
9810
+ workingDirectory = process.cwd(),
9811
+ timeout = 12e4,
9812
+ // 2 minutes default
9813
+ env = {},
9814
+ maxBuffer = 10 * 1024 * 1024,
9815
+ // 10MB
9816
+ debug = false
9817
+ } = options;
9818
+ let cwd = workingDirectory;
9819
+ try {
9820
+ cwd = resolve3(cwd);
9821
+ if (!existsSync2(cwd)) {
9822
+ throw new Error(`Working directory does not exist: ${cwd}`);
9823
+ }
9824
+ } catch (error) {
9825
+ return {
9826
+ success: false,
9827
+ error: `Invalid working directory: ${error.message}`,
9828
+ stdout: "",
9829
+ stderr: "",
9830
+ exitCode: 1,
9831
+ command,
9832
+ workingDirectory: cwd,
9833
+ duration: 0
9834
+ };
9835
+ }
9836
+ const startTime = Date.now();
9837
+ if (debug) {
9838
+ console.log(`[BashExecutor] Executing command: "${command}"`);
9839
+ console.log(`[BashExecutor] Working directory: "${cwd}"`);
9840
+ console.log(`[BashExecutor] Timeout: ${timeout}ms`);
9841
+ }
9842
+ return new Promise((resolve7, reject2) => {
9843
+ const processEnv = {
9844
+ ...process.env,
9845
+ ...env
9846
+ };
9847
+ const isComplex = isComplexCommand(command);
9848
+ let cmd, cmdArgs, useShell;
9849
+ if (isComplex) {
9850
+ cmd = "sh";
9851
+ cmdArgs = ["-c", command];
9852
+ useShell = false;
9853
+ if (debug) {
9854
+ console.log(`[BashExecutor] Complex command - using sh -c`);
9855
+ }
9856
+ } else {
9857
+ const args = parseCommandForExecution(command);
9858
+ if (!args || args.length === 0) {
9859
+ resolve7({
9860
+ success: false,
9861
+ error: "Failed to parse command",
9862
+ stdout: "",
9863
+ stderr: "",
9864
+ exitCode: 1,
9865
+ command,
9866
+ workingDirectory: cwd,
9867
+ duration: Date.now() - startTime
9868
+ });
9869
+ return;
9870
+ }
9871
+ [cmd, ...cmdArgs] = args;
9872
+ useShell = false;
9873
+ }
9874
+ const child = spawn2(cmd, cmdArgs, {
9875
+ cwd,
9876
+ env: processEnv,
9877
+ stdio: ["ignore", "pipe", "pipe"],
9878
+ // stdin ignored, capture stdout/stderr
9879
+ shell: useShell,
9880
+ // false for security
9881
+ windowsHide: true
9882
+ });
9883
+ let stdout = "";
9884
+ let stderr = "";
9885
+ let killed = false;
9886
+ let timeoutHandle;
9887
+ if (timeout > 0) {
9888
+ timeoutHandle = setTimeout(() => {
9889
+ if (!killed) {
9890
+ killed = true;
9891
+ child.kill("SIGTERM");
9892
+ setTimeout(() => {
9893
+ if (child.exitCode === null) {
9894
+ child.kill("SIGKILL");
9902
9895
  }
9903
- return `Error executing bash command: ${error.message}`;
9904
- }
9896
+ }, 5e3);
9897
+ }
9898
+ }, timeout);
9899
+ }
9900
+ child.stdout.on("data", (data) => {
9901
+ const chunk = data.toString();
9902
+ if (stdout.length + chunk.length <= maxBuffer) {
9903
+ stdout += chunk;
9904
+ } else {
9905
+ if (!killed) {
9906
+ killed = true;
9907
+ child.kill("SIGTERM");
9908
+ }
9909
+ }
9910
+ });
9911
+ child.stderr.on("data", (data) => {
9912
+ const chunk = data.toString();
9913
+ if (stderr.length + chunk.length <= maxBuffer) {
9914
+ stderr += chunk;
9915
+ } else {
9916
+ if (!killed) {
9917
+ killed = true;
9918
+ child.kill("SIGTERM");
9919
+ }
9920
+ }
9921
+ });
9922
+ child.on("close", (code, signal) => {
9923
+ if (timeoutHandle) {
9924
+ clearTimeout(timeoutHandle);
9925
+ }
9926
+ const duration = Date.now() - startTime;
9927
+ if (debug) {
9928
+ console.log(`[BashExecutor] Command completed - Code: ${code}, Signal: ${signal}, Duration: ${duration}ms`);
9929
+ console.log(`[BashExecutor] Stdout length: ${stdout.length}, Stderr length: ${stderr.length}`);
9930
+ }
9931
+ let success = true;
9932
+ let error = "";
9933
+ if (killed) {
9934
+ success = false;
9935
+ if (stdout.length + stderr.length > maxBuffer) {
9936
+ error = `Command output exceeded maximum buffer size (${maxBuffer} bytes)`;
9937
+ } else {
9938
+ error = `Command timed out after ${timeout}ms`;
9905
9939
  }
9940
+ } else if (code !== 0) {
9941
+ success = false;
9942
+ error = `Command exited with code ${code}`;
9943
+ }
9944
+ resolve7({
9945
+ success,
9946
+ error,
9947
+ stdout: stdout.trim(),
9948
+ stderr: stderr.trim(),
9949
+ exitCode: code,
9950
+ signal,
9951
+ command,
9952
+ workingDirectory: cwd,
9953
+ duration,
9954
+ killed
9906
9955
  });
9907
- };
9956
+ });
9957
+ child.on("error", (error) => {
9958
+ if (timeoutHandle) {
9959
+ clearTimeout(timeoutHandle);
9960
+ }
9961
+ if (debug) {
9962
+ console.log(`[BashExecutor] Spawn error:`, error);
9963
+ }
9964
+ resolve7({
9965
+ success: false,
9966
+ error: `Failed to execute command: ${error.message}`,
9967
+ stdout: "",
9968
+ stderr: "",
9969
+ exitCode: 1,
9970
+ command,
9971
+ workingDirectory: cwd,
9972
+ duration: Date.now() - startTime
9973
+ });
9974
+ });
9975
+ });
9976
+ }
9977
+ function formatExecutionResult(result, includeMetadata = false) {
9978
+ if (!result) {
9979
+ return "No result available";
9908
9980
  }
9909
- });
9910
-
9911
- // src/tools/edit.js
9912
- import { tool as tool3 } from "ai";
9913
- import { promises as fs6 } from "fs";
9914
- import { dirname, resolve as resolve4, isAbsolute as isAbsolute3, sep as sep2 } from "path";
9915
- import { existsSync as existsSync2 } from "fs";
9916
- function isPathAllowed(filePath, allowedFolders) {
9917
- if (!allowedFolders || allowedFolders.length === 0) {
9918
- const resolvedPath2 = resolve4(filePath);
9919
- const cwd = resolve4(process.cwd());
9920
- return resolvedPath2 === cwd || resolvedPath2.startsWith(cwd + sep2);
9981
+ let output = "";
9982
+ if (includeMetadata) {
9983
+ output += `Command: ${result.command}
9984
+ `;
9985
+ output += `Working directory: ${result.workingDirectory}
9986
+ `;
9987
+ output += `Duration: ${result.duration}ms
9988
+ `;
9989
+ output += `Exit Code: ${result.exitCode}
9990
+ `;
9991
+ if (result.signal) {
9992
+ output += `Signal: ${result.signal}
9993
+ `;
9994
+ }
9995
+ output += "\n";
9921
9996
  }
9922
- const resolvedPath = resolve4(filePath);
9923
- return allowedFolders.some((folder) => {
9924
- const allowedPath = resolve4(folder);
9925
- return resolvedPath === allowedPath || resolvedPath.startsWith(allowedPath + sep2);
9926
- });
9997
+ if (result.stdout) {
9998
+ if (includeMetadata) {
9999
+ output += "--- STDOUT ---\n";
10000
+ }
10001
+ output += result.stdout;
10002
+ if (includeMetadata && result.stderr) {
10003
+ output += "\n";
10004
+ }
10005
+ }
10006
+ if (result.stderr) {
10007
+ if (includeMetadata) {
10008
+ if (result.stdout) output += "\n";
10009
+ output += "--- STDERR ---\n";
10010
+ } else if (result.stdout) {
10011
+ output += "\n--- STDERR ---\n";
10012
+ }
10013
+ output += result.stderr;
10014
+ }
10015
+ if (!result.success && result.error && !result.stderr) {
10016
+ if (output) output += "\n";
10017
+ output += `Error: ${result.error}`;
10018
+ }
10019
+ if (!result.success && result.exitCode !== void 0 && result.exitCode !== 0) {
10020
+ if (output) output += "\n";
10021
+ output += `Exit code: ${result.exitCode}`;
10022
+ }
10023
+ return output || (result.success ? "Command completed successfully (no output)" : "Command failed (no output)");
9927
10024
  }
9928
- function parseFileToolOptions(options = {}) {
10025
+ function validateExecutionOptions(options = {}) {
10026
+ const errors = [];
10027
+ const warnings = [];
10028
+ if (options.timeout !== void 0) {
10029
+ if (typeof options.timeout !== "number" || options.timeout < 0) {
10030
+ errors.push("timeout must be a non-negative number");
10031
+ } else if (options.timeout > 6e5) {
10032
+ warnings.push("timeout is very high (>10 minutes)");
10033
+ }
10034
+ }
10035
+ if (options.maxBuffer !== void 0) {
10036
+ if (typeof options.maxBuffer !== "number" || options.maxBuffer < 1024) {
10037
+ errors.push("maxBuffer must be at least 1024 bytes");
10038
+ } else if (options.maxBuffer > 100 * 1024 * 1024) {
10039
+ warnings.push("maxBuffer is very high (>100MB)");
10040
+ }
10041
+ }
10042
+ if (options.workingDirectory) {
10043
+ if (typeof options.workingDirectory !== "string") {
10044
+ errors.push("workingDirectory must be a string");
10045
+ } else if (!existsSync2(options.workingDirectory)) {
10046
+ errors.push(`workingDirectory does not exist: ${options.workingDirectory}`);
10047
+ }
10048
+ }
10049
+ if (options.env && typeof options.env !== "object") {
10050
+ errors.push("env must be an object");
10051
+ }
9929
10052
  return {
9930
- debug: options.debug || false,
9931
- allowedFolders: options.allowedFolders || [],
9932
- cwd: options.cwd
10053
+ valid: errors.length === 0,
10054
+ errors,
10055
+ warnings
9933
10056
  };
9934
10057
  }
9935
- var editTool, createTool, editDescription, createDescription, editToolDefinition, createToolDefinition;
9936
- var init_edit = __esm({
9937
- "src/tools/edit.js"() {
10058
+ var init_bashExecutor = __esm({
10059
+ "src/agent/bashExecutor.js"() {
9938
10060
  "use strict";
9939
- editTool = (options = {}) => {
9940
- const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
9941
- return tool3({
9942
- name: "edit",
9943
- description: `Edit files using exact string replacement (Claude Code style).
9944
-
9945
- This tool performs exact string replacements in files. It requires the old_string to match exactly what's in the file, including all whitespace and indentation.
9946
-
9947
- Parameters:
9948
- - file_path: Path to the file to edit (absolute or relative)
9949
- - old_string: Exact text to find and replace (must be unique in the file unless replace_all is true)
9950
- - new_string: Text to replace with
9951
- - replace_all: (optional) Replace all occurrences instead of requiring uniqueness
10061
+ init_bashCommandUtils();
10062
+ }
10063
+ });
9952
10064
 
9953
- Important:
9954
- - The old_string must match EXACTLY including whitespace
9955
- - If old_string appears multiple times and replace_all is false, the edit will fail
9956
- - Use larger context around the string to ensure uniqueness when needed`,
9957
- inputSchema: {
9958
- type: "object",
9959
- properties: {
9960
- file_path: {
9961
- type: "string",
9962
- description: "Path to the file to edit"
9963
- },
9964
- old_string: {
9965
- type: "string",
9966
- description: "Exact text to find and replace"
9967
- },
9968
- new_string: {
9969
- type: "string",
9970
- description: "Text to replace with"
9971
- },
9972
- replace_all: {
9973
- type: "boolean",
9974
- description: "Replace all occurrences (default: false)",
9975
- default: false
9976
- }
9977
- },
9978
- required: ["file_path", "old_string", "new_string"]
9979
- },
9980
- execute: async ({ file_path, old_string, new_string, replace_all = false }) => {
9981
- try {
9982
- if (!file_path || typeof file_path !== "string" || file_path.trim() === "") {
9983
- return `Error editing file: Invalid file_path - must be a non-empty string`;
9984
- }
9985
- if (old_string === void 0 || old_string === null || typeof old_string !== "string") {
9986
- return `Error editing file: Invalid old_string - must be a string`;
9987
- }
9988
- if (new_string === void 0 || new_string === null || typeof new_string !== "string") {
9989
- return `Error editing file: Invalid new_string - must be a string`;
9990
- }
9991
- const resolvedPath = isAbsolute3(file_path) ? file_path : resolve4(cwd || process.cwd(), file_path);
9992
- if (debug) {
9993
- console.error(`[Edit] Attempting to edit file: ${resolvedPath}`);
9994
- }
9995
- if (!isPathAllowed(resolvedPath, allowedFolders)) {
9996
- return `Error editing file: Permission denied - ${file_path} is outside allowed directories`;
9997
- }
9998
- if (!existsSync2(resolvedPath)) {
9999
- return `Error editing file: File not found - ${file_path}`;
10000
- }
10001
- const content = await fs6.readFile(resolvedPath, "utf-8");
10002
- if (!content.includes(old_string)) {
10003
- return `Error editing file: String not found - the specified old_string was not found in ${file_path}`;
10004
- }
10005
- const occurrences = content.split(old_string).length - 1;
10006
- if (!replace_all && occurrences > 1) {
10007
- return `Error editing file: Multiple occurrences found - the old_string appears ${occurrences} times. Use replace_all: true to replace all occurrences, or provide more context to make the string unique.`;
10008
- }
10009
- let newContent;
10010
- if (replace_all) {
10011
- newContent = content.replaceAll(old_string, new_string);
10012
- } else {
10013
- newContent = content.replace(old_string, new_string);
10014
- }
10015
- if (newContent === content) {
10016
- return `Error editing file: No changes made - old_string and new_string might be the same`;
10017
- }
10018
- await fs6.writeFile(resolvedPath, newContent, "utf-8");
10019
- const replacedCount = replace_all ? occurrences : 1;
10020
- if (debug) {
10021
- console.error(`[Edit] Successfully edited ${resolvedPath}, replaced ${replacedCount} occurrence(s)`);
10022
- }
10023
- return `Successfully edited ${file_path} (${replacedCount} replacement${replacedCount !== 1 ? "s" : ""})`;
10024
- } catch (error) {
10025
- console.error("[Edit] Error:", error);
10026
- return `Error editing file: ${error.message}`;
10027
- }
10028
- }
10065
+ // src/tools/bash.js
10066
+ import { tool as tool3 } from "ai";
10067
+ import { resolve as resolve4, isAbsolute as isAbsolute3, sep as sep2 } from "path";
10068
+ var bashTool;
10069
+ var init_bash = __esm({
10070
+ "src/tools/bash.js"() {
10071
+ "use strict";
10072
+ init_bashPermissions();
10073
+ init_bashExecutor();
10074
+ bashTool = (options = {}) => {
10075
+ const {
10076
+ bashConfig = {},
10077
+ debug = false,
10078
+ cwd,
10079
+ allowedFolders = []
10080
+ } = options;
10081
+ const permissionChecker = new BashPermissionChecker({
10082
+ allow: bashConfig.allow,
10083
+ deny: bashConfig.deny,
10084
+ disableDefaultAllow: bashConfig.disableDefaultAllow,
10085
+ disableDefaultDeny: bashConfig.disableDefaultDeny,
10086
+ debug
10029
10087
  });
10030
- };
10031
- createTool = (options = {}) => {
10032
- const { debug, allowedFolders, cwd } = parseFileToolOptions(options);
10088
+ const getDefaultWorkingDirectory = () => {
10089
+ if (bashConfig.workingDirectory) {
10090
+ return bashConfig.workingDirectory;
10091
+ }
10092
+ if (cwd) {
10093
+ return cwd;
10094
+ }
10095
+ if (allowedFolders && allowedFolders.length > 0) {
10096
+ return allowedFolders[0];
10097
+ }
10098
+ return process.cwd();
10099
+ };
10033
10100
  return tool3({
10034
- name: "create",
10035
- description: `Create new files with specified content.
10101
+ name: "bash",
10102
+ description: `Execute bash commands for system exploration and development tasks.
10036
10103
 
10037
- This tool creates new files in the filesystem. It will create parent directories if they don't exist.
10104
+ Security: This tool has built-in security with allow/deny lists. By default, only safe read-only commands are allowed for code exploration.
10038
10105
 
10039
10106
  Parameters:
10040
- - file_path: Path where the file should be created (absolute or relative)
10041
- - content: Content to write to the file
10042
- - overwrite: (optional) Whether to overwrite if file exists (default: false)
10107
+ - command: (required) The bash command to execute
10108
+ - workingDirectory: (optional) Directory to execute command in
10109
+ - timeout: (optional) Command timeout in milliseconds
10110
+ - env: (optional) Additional environment variables
10043
10111
 
10044
- Important:
10045
- - By default, will fail if the file already exists
10046
- - Set overwrite: true to replace existing files
10047
- - Parent directories will be created automatically if needed`,
10112
+ Examples of allowed commands by default:
10113
+ - File exploration: ls, cat, head, tail, find, grep
10114
+ - Git operations: git status, git log, git diff, git show
10115
+ - Package info: npm list, pip list, cargo --version
10116
+ - System info: whoami, pwd, uname, date
10117
+
10118
+ Dangerous commands are blocked by default (rm -rf, sudo, npm install, etc.)`,
10048
10119
  inputSchema: {
10049
10120
  type: "object",
10050
10121
  properties: {
10051
- file_path: {
10122
+ command: {
10052
10123
  type: "string",
10053
- description: "Path where the file should be created"
10124
+ description: "The bash command to execute"
10054
10125
  },
10055
- content: {
10126
+ workingDirectory: {
10056
10127
  type: "string",
10057
- description: "Content to write to the file"
10128
+ description: "Directory to execute the command in (optional)"
10058
10129
  },
10059
- overwrite: {
10060
- type: "boolean",
10061
- description: "Overwrite if file exists (default: false)",
10062
- default: false
10130
+ timeout: {
10131
+ type: "number",
10132
+ description: "Command timeout in milliseconds (optional)",
10133
+ minimum: 1e3,
10134
+ maximum: 6e5
10135
+ },
10136
+ env: {
10137
+ type: "object",
10138
+ description: "Additional environment variables (optional)",
10139
+ additionalProperties: {
10140
+ type: "string"
10141
+ }
10063
10142
  }
10064
10143
  },
10065
- required: ["file_path", "content"]
10144
+ required: ["command"],
10145
+ additionalProperties: false
10066
10146
  },
10067
- execute: async ({ file_path, content, overwrite = false }) => {
10147
+ execute: async ({ command, workingDirectory, timeout, env }) => {
10068
10148
  try {
10069
- if (!file_path || typeof file_path !== "string" || file_path.trim() === "") {
10070
- return `Error creating file: Invalid file_path - must be a non-empty string`;
10149
+ if (command === null || command === void 0 || typeof command !== "string") {
10150
+ return "Error: Command is required and must be a string";
10071
10151
  }
10072
- if (content === void 0 || content === null || typeof content !== "string") {
10073
- return `Error creating file: Invalid content - must be a string`;
10152
+ if (command.trim().length === 0) {
10153
+ return "Error: Command cannot be empty";
10074
10154
  }
10075
- const resolvedPath = isAbsolute3(file_path) ? file_path : resolve4(cwd || process.cwd(), file_path);
10076
- if (debug) {
10077
- console.error(`[Create] Attempting to create file: ${resolvedPath}`);
10155
+ const permissionResult = permissionChecker.check(command.trim());
10156
+ if (!permissionResult.allowed) {
10157
+ if (debug) {
10158
+ console.log(`[BashTool] Permission denied for command: "${command}"`);
10159
+ console.log(`[BashTool] Reason: ${permissionResult.reason}`);
10160
+ }
10161
+ return `Permission denied: ${permissionResult.reason}
10162
+
10163
+ This command is not allowed by the current security policy.
10164
+
10165
+ Common reasons:
10166
+ 1. The command is in the deny list (potentially dangerous)
10167
+ 2. The command is not in the allow list (not a recognized safe command)
10168
+
10169
+ If you believe this command should be allowed, you can:
10170
+ - Use the --bash-allow option to add specific patterns
10171
+ - Use the --no-default-bash-deny flag to remove default restrictions (not recommended)
10172
+
10173
+ For code exploration, try these safe alternatives:
10174
+ - ls, cat, head, tail for file operations
10175
+ - find, grep, rg for searching
10176
+ - git status, git log, git show for git operations
10177
+ - npm list, pip list for package information`;
10078
10178
  }
10079
- if (!isPathAllowed(resolvedPath, allowedFolders)) {
10080
- return `Error creating file: Permission denied - ${file_path} is outside allowed directories`;
10179
+ const defaultDir = getDefaultWorkingDirectory();
10180
+ const workingDir = workingDirectory ? isAbsolute3(workingDirectory) ? resolve4(workingDirectory) : resolve4(defaultDir, workingDirectory) : defaultDir;
10181
+ if (allowedFolders && allowedFolders.length > 0) {
10182
+ const resolvedWorkingDir = resolve4(workingDir);
10183
+ const isAllowed = allowedFolders.some((folder) => {
10184
+ const resolvedFolder = resolve4(folder);
10185
+ return resolvedWorkingDir === resolvedFolder || resolvedWorkingDir.startsWith(resolvedFolder + sep2);
10186
+ });
10187
+ if (!isAllowed) {
10188
+ return `Error: Working directory "${workingDir}" is not within allowed folders: ${allowedFolders.join(", ")}`;
10189
+ }
10081
10190
  }
10082
- if (existsSync2(resolvedPath) && !overwrite) {
10083
- return `Error creating file: File already exists - ${file_path}. Use overwrite: true to replace it.`;
10191
+ const executionOptions = {
10192
+ workingDirectory: workingDir,
10193
+ timeout: timeout || bashConfig.timeout || 12e4,
10194
+ env: { ...bashConfig.env, ...env },
10195
+ maxBuffer: bashConfig.maxBuffer,
10196
+ debug
10197
+ };
10198
+ const validation = validateExecutionOptions(executionOptions);
10199
+ if (!validation.valid) {
10200
+ return `Error: Invalid execution options: ${validation.errors.join(", ")}`;
10201
+ }
10202
+ if (validation.warnings.length > 0 && debug) {
10203
+ console.log("[BashTool] Warnings:", validation.warnings);
10084
10204
  }
10085
- const dir = dirname(resolvedPath);
10086
- await fs6.mkdir(dir, { recursive: true });
10087
- await fs6.writeFile(resolvedPath, content, "utf-8");
10088
- const action = existsSync2(resolvedPath) && overwrite ? "overwrote" : "created";
10089
- const bytes = Buffer.byteLength(content, "utf-8");
10090
10205
  if (debug) {
10091
- console.error(`[Create] Successfully ${action} ${resolvedPath}`);
10206
+ console.log(`[BashTool] Executing command: "${command}"`);
10207
+ console.log(`[BashTool] Working directory: "${workingDir}"`);
10208
+ console.log(`[BashTool] Timeout: ${executionOptions.timeout}ms`);
10092
10209
  }
10093
- return `Successfully ${action} ${file_path} (${bytes} bytes)`;
10210
+ const result = await executeBashCommand(command.trim(), executionOptions);
10211
+ if (debug) {
10212
+ console.log(`[BashTool] Command completed - Success: ${result.success}, Duration: ${result.duration}ms`);
10213
+ }
10214
+ const formattedResult = formatExecutionResult(result, debug);
10215
+ if (!result.success) {
10216
+ let errorInfo = `
10217
+
10218
+ Command failed with exit code ${result.exitCode}`;
10219
+ if (result.killed) {
10220
+ errorInfo += ` (${result.error})`;
10221
+ }
10222
+ return formattedResult + errorInfo;
10223
+ }
10224
+ return formattedResult;
10094
10225
  } catch (error) {
10095
- console.error("[Create] Error:", error);
10096
- return `Error creating file: ${error.message}`;
10226
+ if (debug) {
10227
+ console.error("[BashTool] Execution error:", error);
10228
+ }
10229
+ return `Error executing bash command: ${error.message}`;
10097
10230
  }
10098
10231
  }
10099
10232
  });
10100
10233
  };
10101
- editDescription = "Edit files using exact string replacement. Requires exact match including whitespace.";
10102
- createDescription = "Create new files with specified content. Will create parent directories if needed.";
10103
- editToolDefinition = `
10104
- ## edit
10105
- Description: ${editDescription}
10106
-
10107
- When to use:
10108
- - For precise, surgical edits to existing files
10109
- - When you need to change specific lines or blocks of code
10110
- - For renaming functions, variables, or updating configuration values
10111
- - When the exact text to replace is known and unique (or use replace_all for multiple occurrences)
10112
-
10113
- When NOT to use:
10114
- - For creating new files (use 'create' tool instead)
10115
- - When you cannot determine the exact text to replace
10116
- - When changes span multiple locations that would be better handled together
10117
-
10118
- Parameters:
10119
- - file_path: (required) Path to the file to edit
10120
- - old_string: (required) Exact text to find and replace (must match including whitespace, newlines, and indentation)
10121
- - new_string: (required) Text to replace with
10122
- - replace_all: (optional, default: false) Replace all occurrences if the string appears multiple times
10123
-
10124
- Important notes:
10125
- - The old_string MUST match EXACTLY, including all whitespace, indentation, and line breaks
10126
- - If old_string appears multiple times and replace_all is false, the tool will fail
10127
- - Always verify the exact formatting of the text you want to replace
10128
-
10129
- Examples:
10130
- <edit>
10131
- <file_path>src/main.js</file_path>
10132
- <old_string>function oldName() {
10133
- return 42;
10134
- }</old_string>
10135
- <new_string>function newName() {
10136
- return 42;
10137
- }</new_string>
10138
- </edit>
10139
-
10140
- <edit>
10141
- <file_path>config.json</file_path>
10142
- <old_string>"debug": false</old_string>
10143
- <new_string>"debug": true</new_string>
10144
- <replace_all>true</replace_all>
10145
- </edit>`;
10146
- createToolDefinition = `
10147
- ## create
10148
- Description: ${createDescription}
10149
-
10150
- When to use:
10151
- - For creating brand new files from scratch
10152
- - When you need to add configuration files, documentation, or new modules
10153
- - For generating boilerplate code or templates
10154
- - When you have the complete content ready to write
10155
-
10156
- When NOT to use:
10157
- - For editing existing files (use 'edit' tool instead)
10158
- - When a file already exists unless you explicitly want to overwrite it
10159
-
10160
- Parameters:
10161
- - file_path: (required) Path where the file should be created
10162
- - content: (required) Complete content to write to the file
10163
- - overwrite: (optional, default: false) Whether to overwrite if file already exists
10164
-
10165
- Important notes:
10166
- - Parent directories will be created automatically if they don't exist
10167
- - The tool will fail if the file already exists and overwrite is false
10168
- - Be careful with the overwrite option as it completely replaces existing files
10169
-
10170
- Examples:
10171
- <create>
10172
- <file_path>src/newFile.js</file_path>
10173
- <content>export function hello() {
10174
- return "Hello, world!";
10175
- }</content>
10176
- </create>
10177
-
10178
- <create>
10179
- <file_path>README.md</file_path>
10180
- <content># My Project
10181
-
10182
- This is a new project.</content>
10183
- <overwrite>true</overwrite>
10184
- </create>`;
10185
10234
  }
10186
10235
  });
10187
10236