@acontext/acontext 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/prompts.d.ts +1 -1
- package/dist/agent/prompts.js +3 -1
- package/dist/agent/sandbox.d.ts +3 -0
- package/dist/agent/sandbox.js +20 -8
- package/dist/agent/text-editor.d.ts +7 -8
- package/dist/agent/text-editor.js +89 -127
- package/package.json +1 -1
package/dist/agent/prompts.d.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare const SKILL_REMINDER = "MANDATORY SKILL READING AND EXECUTION PROTOCOL:\nBEFORE writing ANY code or using ANY execution tools, You MUST complete ALL of\nthese steps:\n\nSTEP 1 - IDENTIFY ALL RELEVANT SKILLS:\n- Scan the user message for ALL trigger words from ALL skills\n- Identify EVERY skill that matches ANY trigger word\n- If multiple skills match, ALL must be processed\n- If no skills match, you can skip the following steps\n\nSTEP 2 - READ ALL SKILL FILES:\n- Use the text_editor_sandbox tool to view EACH identified skill's SKILL.md file\n- READ COMPLETELY - do not skim or skip sections\n- This step is MANDATORY even if multiple skills are involved\n- DO NOT proceed until ALL relevant skill files have been read\n\nSTEP 3 - EXECUTE ALL SKILL INSTRUCTIONS:\n- Follow the EXACT instructions from EACH skill file read\n- If a skill file says to execute file X, then EXECUTE file X\n- If a skill file provides code patterns, USE those patterns\n- Apply instructions from ALL skills, not just the first one\n- NEVER write generic code when skill-specific code exists\n\nCRITICAL RULES:\n- Reading the skill file is NOT sufficient - you must FOLLOW its instructions\n- Multiple skills = multiple skill files to read AND follow\n- Each skill's instructions must be executed, not just acknowledged\n- NEVER skip a skill because you already read another skill\n- The skills contain specialized, tested code that MUST be used\n\nDO NOT SKIP ANY SKILL FILES OR THEIR INSTRUCTIONS. This protocol applies to EVERY\nskill that matches the user's request, without exception.";
|
|
5
5
|
export declare const SANDBOX_TEXT_EDITOR_REMINDER = "The text_editor_sandbox tool enables viewing, creating, and modifying text files within\nthe secure sandboxed container environment.\n\nHow it works:\n- All file operations occur within the sandboxed container filesystem\n\nCommand guidelines:\n- Always use view before editing to understand file structure\n- For str_replace commands, ensure search strings are unique and exact\n- Include sufficient context in str_replace for accurate placement\n- Use proper escaping for special characters in search/replace strings";
|
|
6
|
-
export declare const SANDBOX_BASH_REMINDER = "When to use the bash_execution_sandbox tool directly:\n- File system operations requiring shell commands (moving, copying, renaming, organizing files)\n- Text processing and manipulation using standard Unix tools (grep, sed, awk, cut, sort, etc.) that\nshould not be done by the text editor tool\n- Batch processing of multiple files using shell loops and wildcards\n- System inspection tasks (checking file sizes, permissions, directory structures)\n- Combining multiple command-line tools in pipelines for complex data processing\n- Archive operations (tar, unzip) and file compression/decompression\n- Converting between file formats using command-line utilities\n\nWhen you should write Python file and use bash tool to run it:\n- Complex data analysis or numerical computation (use file operations to write a Python script instead, and\nthen the bash to run the script)\n- Tasks requiring advanced programming logic or data structures\n\nWhen NOT to use the bash_execution_sandbox tool:\n- Simple questions that can be answered without executing commands\n- Tasks that only require explaining shell concepts without actual execution\n\nHow it works:\n- Scripts are saved to a temporary sandbox and executed with bash\n- Tool results will include stdout, stderr, and return code\n- User-uploaded files are accessible in the directory specified by the INPUT_DIR environment variable. If\nyou know the file path and don't need to open the full INPUT_DIR, then just open the file directly\n\nFile Operations (CRITICAL - READ CAREFULLY):\n- use text_editor_sandbox tool to view, create, and edit files.\n\nExport Your Result:\n- All the files you created kept in the sandbox, which user can't see or access.\n- If you want to export them to user, use `export_file_sandbox` tool.\n- If too many files to export(>= 6 files), zip those files and export the zip file.\n- Result files' names should be unique and descriptive, (wrong: result.md, output.md... right: 2026_us_market_trending.png)\n\nScript guidelines:\n- Write POSIX-compliant bash scripts\n- Use proper error handling and exit codes\n- Quote variables appropriately to handle spaces in filenames\n- Keep scripts clean and well-organized\n- For file operations, use text_editor_sandbox tool instead of bash commands.\n\nNever write blocking script:\n- python codes like `plt.show()` or `input()`... will block the execution of the script, don't use them. write non-blocking code instead.\n\nContainer environment:\n- Filesystem persists across multiple executions within the same container\n- Standard Unix utilities available (grep, sed, awk, etc.)\n- Archive tools: tar, unzip, zip\n- Additional tools: ripgrep, fd, sqlite3, jq, imagemagick\n-
|
|
6
|
+
export declare const SANDBOX_BASH_REMINDER = "When to use the bash_execution_sandbox tool directly:\n- File system operations requiring shell commands (moving, copying, renaming, organizing files)\n- Text processing and manipulation using standard Unix tools (grep, sed, awk, cut, sort, etc.) that\nshould not be done by the text editor tool\n- Batch processing of multiple files using shell loops and wildcards\n- System inspection tasks (checking file sizes, permissions, directory structures)\n- Combining multiple command-line tools in pipelines for complex data processing\n- Archive operations (tar, unzip) and file compression/decompression\n- Converting between file formats using command-line utilities\n\nWhen you should write Python file and use bash tool to run it:\n- Complex data analysis or numerical computation (use file operations to write a Python script instead, and\nthen the bash to run the script)\n- Tasks requiring advanced programming logic or data structures\n\nWhen NOT to use the bash_execution_sandbox tool:\n- Simple questions that can be answered without executing commands\n- Tasks that only require explaining shell concepts without actual execution\n\nHow it works:\n- Scripts are saved to a temporary sandbox and executed with bash\n- Tool results will include stdout, stderr, and return code\n- User-uploaded files are accessible in the directory specified by the INPUT_DIR environment variable. If\nyou know the file path and don't need to open the full INPUT_DIR, then just open the file directly\n\nFile Operations (CRITICAL - READ CAREFULLY):\n- use text_editor_sandbox tool to view, create, and edit files.\n\nExport Your Result:\n- All the files you created kept in the sandbox, which user can't see or access.\n- If you want to export them to user, use `export_file_sandbox` tool.\n- If too many files to export(>= 6 files), zip those files and export the zip file.\n- Result files' names should be unique and descriptive, (wrong: result.md, output.md... right: 2026_us_market_trending.png)\n\nScript guidelines:\n- Write POSIX-compliant bash scripts\n- Use proper error handling and exit codes\n- Quote variables appropriately to handle spaces in filenames\n- Keep scripts clean and well-organized\n- For file operations, use text_editor_sandbox tool instead of bash commands.\n\nNever write blocking script:\n- python codes like `plt.show()` or `input()`... will block the execution of the script, don't use them. write non-blocking code instead.\n\nContainer environment:\n- Filesystem persists across multiple executions within the same container\n- Standard Unix utilities available (grep, sed, awk, etc.)\n- Archive tools: tar, unzip, zip\n- Additional tools: ripgrep, fd, sqlite3, jq, imagemagick\n- You can install new packages with pip if needed (internet access is available)\n\nRemember to always export your artifacts at the end of your task so that the user can view them.\n";
|
package/dist/agent/prompts.js
CHANGED
|
@@ -96,5 +96,7 @@ Container environment:
|
|
|
96
96
|
- Standard Unix utilities available (grep, sed, awk, etc.)
|
|
97
97
|
- Archive tools: tar, unzip, zip
|
|
98
98
|
- Additional tools: ripgrep, fd, sqlite3, jq, imagemagick
|
|
99
|
-
-
|
|
99
|
+
- You can install new packages with pip if needed (internet access is available)
|
|
100
|
+
|
|
101
|
+
Remember to always export your artifacts at the end of your task so that the user can view them.
|
|
100
102
|
`;
|
package/dist/agent/sandbox.d.ts
CHANGED
package/dist/agent/sandbox.js
CHANGED
|
@@ -41,6 +41,13 @@ const path = __importStar(require("path"));
|
|
|
41
41
|
const base_1 = require("./base");
|
|
42
42
|
const prompts_1 = require("./prompts");
|
|
43
43
|
const text_editor_1 = require("./text-editor");
|
|
44
|
+
const MAX_OUTPUT_CHARS = 20000;
|
|
45
|
+
function truncateOutput(text, maxChars = MAX_OUTPUT_CHARS) {
|
|
46
|
+
if (text.length > maxChars) {
|
|
47
|
+
return text.slice(0, maxChars) + '...[truncated]';
|
|
48
|
+
}
|
|
49
|
+
return text;
|
|
50
|
+
}
|
|
44
51
|
function formatMountedSkills(mountedSkillPaths) {
|
|
45
52
|
if (mountedSkillPaths.size === 0) {
|
|
46
53
|
return '';
|
|
@@ -120,8 +127,8 @@ class BashTool extends base_1.AbstractBaseTool {
|
|
|
120
127
|
timeout,
|
|
121
128
|
});
|
|
122
129
|
return JSON.stringify({
|
|
123
|
-
stdout: result.stdout,
|
|
124
|
-
stderr: result.stderr,
|
|
130
|
+
stdout: truncateOutput(result.stdout),
|
|
131
|
+
stderr: truncateOutput(result.stderr),
|
|
125
132
|
exit_code: result.exit_code,
|
|
126
133
|
});
|
|
127
134
|
}
|
|
@@ -136,27 +143,32 @@ class TextEditorTool extends base_1.AbstractBaseTool {
|
|
|
136
143
|
command: {
|
|
137
144
|
type: 'string',
|
|
138
145
|
enum: ['view', 'create', 'str_replace'],
|
|
139
|
-
description: "The operation to perform: 'view', 'create', or 'str_replace'"
|
|
146
|
+
description: "The operation to perform: 'view', 'create', or 'str_replace'. " +
|
|
147
|
+
"Required parameters per command: " +
|
|
148
|
+
"'view' requires path (view_range is optional); " +
|
|
149
|
+
"'create' requires path and file_text; " +
|
|
150
|
+
"'str_replace' requires path, old_str, and new_str.",
|
|
140
151
|
},
|
|
141
152
|
path: {
|
|
142
153
|
type: 'string',
|
|
143
|
-
description: "The file path in the sandbox (e.g., '/workspace/script.py')",
|
|
154
|
+
description: "Required for all commands. The file path in the sandbox (e.g., '/workspace/script.py')",
|
|
144
155
|
},
|
|
145
156
|
file_text: {
|
|
146
157
|
type: ['string', 'null'],
|
|
147
|
-
description: "
|
|
158
|
+
description: "Required for 'create' command. The content to write to the file.",
|
|
148
159
|
},
|
|
149
160
|
old_str: {
|
|
150
161
|
type: ['string', 'null'],
|
|
151
|
-
description: "
|
|
162
|
+
description: "Required for 'str_replace' command. The exact string to find and replace.",
|
|
152
163
|
},
|
|
153
164
|
new_str: {
|
|
154
165
|
type: ['string', 'null'],
|
|
155
|
-
description: "
|
|
166
|
+
description: "Required for 'str_replace' command. The string to replace old_str with.",
|
|
156
167
|
},
|
|
157
168
|
view_range: {
|
|
158
169
|
type: ['array', 'null'],
|
|
159
|
-
|
|
170
|
+
items: { type: 'integer' },
|
|
171
|
+
description: "Optional for 'view' command. An array [start_line, end_line] to view specific lines. If not provided, shows the first 200 lines.",
|
|
160
172
|
},
|
|
161
173
|
};
|
|
162
174
|
this.requiredArguments = ['command', 'path'];
|
|
@@ -22,23 +22,22 @@ export interface CreateFileResult {
|
|
|
22
22
|
stderr?: string;
|
|
23
23
|
}
|
|
24
24
|
export interface StrReplaceResult {
|
|
25
|
-
|
|
26
|
-
oldLines?: number;
|
|
27
|
-
newStart?: number;
|
|
28
|
-
newLines?: number;
|
|
29
|
-
lines?: string[];
|
|
25
|
+
msg?: string;
|
|
30
26
|
error?: string;
|
|
31
27
|
stderr?: string;
|
|
32
28
|
}
|
|
33
29
|
/**
|
|
34
30
|
* View file content with line numbers.
|
|
35
31
|
*/
|
|
36
|
-
export declare function viewFile(ctx: SandboxContext,
|
|
32
|
+
export declare function viewFile(ctx: SandboxContext, filePath: string, viewRange: number[] | null, timeout?: number): Promise<ViewFileResult>;
|
|
37
33
|
/**
|
|
38
34
|
* Create a new file with content.
|
|
39
35
|
*/
|
|
40
|
-
export declare function createFile(ctx: SandboxContext,
|
|
36
|
+
export declare function createFile(ctx: SandboxContext, filePath: string, fileText: string, timeout?: number): Promise<CreateFileResult>;
|
|
41
37
|
/**
|
|
42
38
|
* Replace a string in a file.
|
|
39
|
+
*
|
|
40
|
+
* Uses a Python script on the sandbox to avoid transferring the entire file.
|
|
41
|
+
* Only the base64-encoded oldStr and newStr are sent.
|
|
43
42
|
*/
|
|
44
|
-
export declare function strReplace(ctx: SandboxContext,
|
|
43
|
+
export declare function strReplace(ctx: SandboxContext, filePath: string, oldStr: string, newStr: string, timeout?: number): Promise<StrReplaceResult>;
|
|
@@ -2,11 +2,25 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Text editor file operations for sandbox environments.
|
|
4
4
|
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
5
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
9
|
exports.escapeForShell = escapeForShell;
|
|
7
10
|
exports.viewFile = viewFile;
|
|
8
11
|
exports.createFile = createFile;
|
|
9
12
|
exports.strReplace = strReplace;
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const MAX_CONTENT_CHARS = 20000;
|
|
15
|
+
/**
|
|
16
|
+
* Truncate text to maxChars, appending a truncation flag if needed.
|
|
17
|
+
*/
|
|
18
|
+
function truncateContent(text, maxChars = MAX_CONTENT_CHARS) {
|
|
19
|
+
if (text.length > maxChars) {
|
|
20
|
+
return text.slice(0, maxChars) + '...[truncated]';
|
|
21
|
+
}
|
|
22
|
+
return text;
|
|
23
|
+
}
|
|
10
24
|
/**
|
|
11
25
|
* Escape a string for safe use in shell commands.
|
|
12
26
|
*/
|
|
@@ -17,54 +31,48 @@ function escapeForShell(s) {
|
|
|
17
31
|
/**
|
|
18
32
|
* View file content with line numbers.
|
|
19
33
|
*/
|
|
20
|
-
async function viewFile(ctx,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
sandboxId: ctx.sandboxId,
|
|
25
|
-
command: checkCmd,
|
|
26
|
-
timeout,
|
|
27
|
-
});
|
|
28
|
-
if (checkResult.stdout.includes('FILE_NOT_FOUND') || checkResult.exit_code !== 0) {
|
|
29
|
-
return {
|
|
30
|
-
error: `File not found: ${path}`,
|
|
31
|
-
stderr: checkResult.stderr,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
const totalLines = /^\d+$/.test(checkResult.stdout.trim())
|
|
35
|
-
? parseInt(checkResult.stdout.trim(), 10)
|
|
36
|
-
: 0;
|
|
37
|
-
// Build the view command with line numbers
|
|
38
|
-
let cmd;
|
|
34
|
+
async function viewFile(ctx, filePath, viewRange, timeout) {
|
|
35
|
+
const escapedPath = escapeForShell(filePath);
|
|
36
|
+
// Build combined command: check existence, get total lines, and view content in one exec
|
|
37
|
+
let viewCmd;
|
|
39
38
|
let startLine;
|
|
40
39
|
if (viewRange && viewRange.length === 2) {
|
|
41
40
|
const [rangeStart, rangeEnd] = viewRange;
|
|
42
|
-
|
|
41
|
+
viewCmd = `sed -n '${rangeStart},${rangeEnd}p' ${escapedPath} | nl -ba -v ${rangeStart}`;
|
|
43
42
|
startLine = rangeStart;
|
|
44
43
|
}
|
|
45
44
|
else {
|
|
46
|
-
|
|
45
|
+
const maxLines = 200;
|
|
46
|
+
viewCmd = `head -n ${maxLines} ${escapedPath} | nl -ba`;
|
|
47
47
|
startLine = 1;
|
|
48
48
|
}
|
|
49
|
+
// Single combined command: outputs "TOTAL:<n>" on first line, then file content
|
|
50
|
+
const cmd = `if [ ! -f ${escapedPath} ]; then echo 'FILE_NOT_FOUND'; exit 1; fi; echo "TOTAL:$(wc -l < ${escapedPath})"; ${viewCmd}`;
|
|
49
51
|
const result = await ctx.client.sandboxes.execCommand({
|
|
50
52
|
sandboxId: ctx.sandboxId,
|
|
51
53
|
command: cmd,
|
|
52
54
|
timeout,
|
|
53
55
|
});
|
|
54
|
-
if (result.exit_code !== 0) {
|
|
56
|
+
if (result.exit_code !== 0 || result.stdout.includes('FILE_NOT_FOUND')) {
|
|
55
57
|
return {
|
|
56
|
-
error: `
|
|
58
|
+
error: `File not found: ${filePath}`,
|
|
57
59
|
stderr: result.stderr,
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
// Parse output: first line is "TOTAL:<n>", rest is content
|
|
63
|
+
const lines = result.stdout.split('\n');
|
|
64
|
+
let totalLines = 0;
|
|
65
|
+
let content = '';
|
|
66
|
+
if (lines.length > 0 && lines[0].startsWith('TOTAL:')) {
|
|
67
|
+
const totalStr = lines[0].substring(6).trim();
|
|
68
|
+
totalLines = /^\d+$/.test(totalStr) ? parseInt(totalStr, 10) : 0;
|
|
69
|
+
content = lines.slice(1).join('\n');
|
|
70
|
+
}
|
|
71
|
+
const contentLines = content.trim() ? content.trimEnd().split('\n') : [];
|
|
64
72
|
const numLines = contentLines.length;
|
|
65
73
|
return {
|
|
66
74
|
file_type: 'text',
|
|
67
|
-
content:
|
|
75
|
+
content: truncateContent(content),
|
|
68
76
|
numLines,
|
|
69
77
|
startLine: viewRange ? startLine : 1,
|
|
70
78
|
totalLines: totalLines + 1, // wc -l doesn't count last line without newline
|
|
@@ -73,129 +81,83 @@ async function viewFile(ctx, path, viewRange, timeout) {
|
|
|
73
81
|
/**
|
|
74
82
|
* Create a new file with content.
|
|
75
83
|
*/
|
|
76
|
-
async function createFile(ctx,
|
|
77
|
-
|
|
78
|
-
const checkCmd = `test -f ${escapeForShell(path)} && echo 'EXISTS' || echo 'NEW'`;
|
|
79
|
-
const checkResult = await ctx.client.sandboxes.execCommand({
|
|
80
|
-
sandboxId: ctx.sandboxId,
|
|
81
|
-
command: checkCmd,
|
|
82
|
-
timeout,
|
|
83
|
-
});
|
|
84
|
-
const isUpdate = checkResult.stdout.includes('EXISTS');
|
|
85
|
-
// Create directory if needed
|
|
86
|
-
const parts = path.split('/');
|
|
87
|
-
parts.pop();
|
|
88
|
-
const dirPath = parts.join('/');
|
|
89
|
-
if (dirPath) {
|
|
90
|
-
const mkdirCmd = `mkdir -p ${escapeForShell(dirPath)}`;
|
|
91
|
-
await ctx.client.sandboxes.execCommand({
|
|
92
|
-
sandboxId: ctx.sandboxId,
|
|
93
|
-
command: mkdirCmd,
|
|
94
|
-
timeout,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
// Write file using base64 encoding to safely transfer content
|
|
84
|
+
async function createFile(ctx, filePath, fileText, timeout) {
|
|
85
|
+
const escapedPath = escapeForShell(filePath);
|
|
98
86
|
const encodedContent = Buffer.from(fileText, 'utf-8').toString('base64');
|
|
99
|
-
|
|
87
|
+
// Get directory path for mkdir
|
|
88
|
+
const dirPath = path_1.default.posix.dirname(filePath);
|
|
89
|
+
const mkdirPart = dirPath && dirPath !== '.' ? `mkdir -p ${escapeForShell(dirPath)} && ` : '';
|
|
90
|
+
// Single combined command: check existence, create dir, write file
|
|
91
|
+
const cmd = `is_update=$(test -f ${escapedPath} && echo 1 || echo 0); ${mkdirPart}echo ${escapeForShell(encodedContent)} | base64 -d > ${escapedPath} && echo "STATUS:$is_update"`;
|
|
100
92
|
const result = await ctx.client.sandboxes.execCommand({
|
|
101
93
|
sandboxId: ctx.sandboxId,
|
|
102
|
-
command:
|
|
94
|
+
command: cmd,
|
|
103
95
|
timeout,
|
|
104
96
|
});
|
|
105
|
-
if (result.exit_code !== 0) {
|
|
97
|
+
if (result.exit_code !== 0 || !result.stdout.includes('STATUS:')) {
|
|
106
98
|
return {
|
|
107
|
-
error: `Failed to create file: ${
|
|
99
|
+
error: `Failed to create file: ${filePath}`,
|
|
108
100
|
stderr: result.stderr,
|
|
109
101
|
};
|
|
110
102
|
}
|
|
103
|
+
const isUpdate = result.stdout.includes('STATUS:1');
|
|
111
104
|
return {
|
|
112
105
|
is_file_update: isUpdate,
|
|
113
|
-
message: `File ${isUpdate ? 'updated' : 'created'}: ${
|
|
106
|
+
message: `File ${isUpdate ? 'updated' : 'created'}: ${filePath}`,
|
|
114
107
|
};
|
|
115
108
|
}
|
|
116
109
|
/**
|
|
117
110
|
* Replace a string in a file.
|
|
111
|
+
*
|
|
112
|
+
* Uses a Python script on the sandbox to avoid transferring the entire file.
|
|
113
|
+
* Only the base64-encoded oldStr and newStr are sent.
|
|
118
114
|
*/
|
|
119
|
-
async function strReplace(ctx,
|
|
120
|
-
|
|
121
|
-
const
|
|
115
|
+
async function strReplace(ctx, filePath, oldStr, newStr, timeout) {
|
|
116
|
+
const oldB64 = Buffer.from(oldStr, 'utf-8').toString('base64');
|
|
117
|
+
const newB64 = Buffer.from(newStr, 'utf-8').toString('base64');
|
|
118
|
+
// Write Python script and base64 encode it to avoid shell escaping issues
|
|
119
|
+
const pyScript = `import sys, base64, os
|
|
120
|
+
old = base64.b64decode("${oldB64}").decode()
|
|
121
|
+
new = base64.b64decode("${newB64}").decode()
|
|
122
|
+
path = "${filePath}"
|
|
123
|
+
if not os.path.exists(path):
|
|
124
|
+
print("FILE_NOT_FOUND")
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
with open(path, "r") as f:
|
|
127
|
+
content = f.read()
|
|
128
|
+
count = content.count(old)
|
|
129
|
+
if count == 0:
|
|
130
|
+
print("NOT_FOUND")
|
|
131
|
+
sys.exit(0)
|
|
132
|
+
if count > 1:
|
|
133
|
+
print(f"MULTIPLE:{count}")
|
|
134
|
+
sys.exit(0)
|
|
135
|
+
with open(path, "w") as f:
|
|
136
|
+
f.write(content.replace(old, new, 1))
|
|
137
|
+
print("SUCCESS")
|
|
138
|
+
`;
|
|
139
|
+
const scriptB64 = Buffer.from(pyScript, 'utf-8').toString('base64');
|
|
140
|
+
const cmd = `echo ${escapeForShell(scriptB64)} | base64 -d | python3`;
|
|
122
141
|
const result = await ctx.client.sandboxes.execCommand({
|
|
123
142
|
sandboxId: ctx.sandboxId,
|
|
124
|
-
command:
|
|
143
|
+
command: cmd,
|
|
125
144
|
timeout,
|
|
126
145
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
stderr: result.stderr,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
const originalContent = result.stdout;
|
|
134
|
-
// Check if oldStr exists in the file
|
|
135
|
-
if (!originalContent.includes(oldStr)) {
|
|
136
|
-
return {
|
|
137
|
-
error: `String not found in file: ${oldStr.substring(0, 50)}...`,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
// Count occurrences
|
|
141
|
-
let occurrences = 0;
|
|
142
|
-
let searchIndex = 0;
|
|
143
|
-
while (searchIndex < originalContent.length) {
|
|
144
|
-
const foundIndex = originalContent.indexOf(oldStr, searchIndex);
|
|
145
|
-
if (foundIndex === -1)
|
|
146
|
-
break;
|
|
147
|
-
occurrences++;
|
|
148
|
-
searchIndex = foundIndex + oldStr.length;
|
|
149
|
-
}
|
|
150
|
-
if (occurrences > 1) {
|
|
151
|
-
return {
|
|
152
|
-
error: `Multiple occurrences (${occurrences}) of the string found. Please provide more context to make the match unique.`,
|
|
153
|
-
};
|
|
146
|
+
const output = result.stdout.trim();
|
|
147
|
+
if (result.exit_code !== 0 || output === 'FILE_NOT_FOUND') {
|
|
148
|
+
return { error: `File not found: ${filePath}`, stderr: result.stderr };
|
|
154
149
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// Find the line numbers affected
|
|
158
|
-
const oldLines = originalContent.split('\n');
|
|
159
|
-
const newLines = newContent.split('\n');
|
|
160
|
-
// Find where the change starts
|
|
161
|
-
let oldStart = 1;
|
|
162
|
-
const minLen = Math.min(oldLines.length, newLines.length);
|
|
163
|
-
for (let i = 0; i < minLen; i++) {
|
|
164
|
-
if (oldLines[i] !== newLines[i]) {
|
|
165
|
-
oldStart = i + 1;
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
150
|
+
if (output === 'NOT_FOUND') {
|
|
151
|
+
return { error: `String not found in file: ${oldStr.substring(0, 50)}...` };
|
|
168
152
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const writeCmd = `echo ${escapeForShell(encodedContent)} | base64 -d > ${escapeForShell(path)}`;
|
|
172
|
-
const writeResult = await ctx.client.sandboxes.execCommand({
|
|
173
|
-
sandboxId: ctx.sandboxId,
|
|
174
|
-
command: writeCmd,
|
|
175
|
-
timeout,
|
|
176
|
-
});
|
|
177
|
-
if (writeResult.exit_code !== 0) {
|
|
153
|
+
if (output.startsWith('MULTIPLE:')) {
|
|
154
|
+
const count = output.split(':')[1];
|
|
178
155
|
return {
|
|
179
|
-
error: `
|
|
180
|
-
stderr: writeResult.stderr,
|
|
156
|
+
error: `Multiple occurrences (${count}) of the string found. Please provide more context to make the match unique.`,
|
|
181
157
|
};
|
|
182
158
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const newStrLines = newStr.split('\n').length;
|
|
186
|
-
// Build diff lines
|
|
187
|
-
const diffLines = [];
|
|
188
|
-
for (const line of oldStr.split('\n')) {
|
|
189
|
-
diffLines.push(`-${line}`);
|
|
190
|
-
}
|
|
191
|
-
for (const line of newStr.split('\n')) {
|
|
192
|
-
diffLines.push(`+${line}`);
|
|
159
|
+
if (output === 'SUCCESS') {
|
|
160
|
+
return { msg: 'Successfully replaced text at exactly one location.' };
|
|
193
161
|
}
|
|
194
|
-
return {
|
|
195
|
-
oldStart,
|
|
196
|
-
oldLines: oldStrLines,
|
|
197
|
-
newStart: oldStart,
|
|
198
|
-
newLines: newStrLines,
|
|
199
|
-
lines: diffLines,
|
|
200
|
-
};
|
|
162
|
+
return { error: `Unexpected response: ${output}`, stderr: result.stderr };
|
|
201
163
|
}
|