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