@probelabs/probe 0.6.0-rc198 → 0.6.0-rc200

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