@travisennis/acai 0.0.10 → 0.0.11
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/README.md +5 -1
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +29 -27
- package/dist/cli/stdin.d.ts +2 -1
- package/dist/cli/stdin.d.ts.map +1 -1
- package/dist/commands/generate-rules/service.d.ts +3 -2
- package/dist/commands/generate-rules/service.d.ts.map +1 -1
- package/dist/commands/health/utils.d.ts +3 -2
- package/dist/commands/health/utils.d.ts.map +1 -1
- package/dist/commands/init-project/utils.d.ts +2 -1
- package/dist/commands/init-project/utils.d.ts.map +1 -1
- package/dist/commands/review/utils.d.ts +6 -1
- package/dist/commands/review/utils.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/models/opencode-go-provider.d.ts +25 -0
- package/dist/models/opencode-go-provider.d.ts.map +1 -0
- package/dist/models/opencode-go-provider.js +78 -0
- package/dist/models/opencode-zen-provider.d.ts +3 -3
- package/dist/models/opencode-zen-provider.js +17 -17
- package/dist/models/openrouter-provider.d.ts +4 -1
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +39 -0
- package/dist/models/providers.d.ts +3 -3
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +6 -0
- package/dist/modes/manager.d.ts +2 -1
- package/dist/modes/manager.d.ts.map +1 -1
- package/dist/modes/prompts.d.ts +1 -1
- package/dist/modes/prompts.d.ts.map +1 -1
- package/dist/modes/prompts.js +1 -2
- package/dist/prompts/mentions.d.ts.map +1 -1
- package/dist/prompts/mentions.js +35 -6
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +9 -1
- package/dist/sessions/manager.d.ts +3 -3
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +1 -1
- package/dist/skills/index.d.ts +2 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/subagents/index.d.ts +2 -1
- package/dist/subagents/index.d.ts.map +1 -1
- package/dist/terminal/table/utils.d.ts +1 -1
- package/dist/terminal/table/utils.d.ts.map +1 -1
- package/dist/terminal/wrap-ansi.js +2 -2
- package/dist/tools/agent.js +1 -1
- package/dist/tools/apply-patch.d.ts +62 -0
- package/dist/tools/apply-patch.d.ts.map +1 -0
- package/dist/tools/apply-patch.js +377 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +28 -7
- package/dist/tools/directory-tree.d.ts.map +1 -1
- package/dist/tools/directory-tree.js +1 -1
- package/dist/tools/dynamic-tool-loader.d.ts +1 -1
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +188 -79
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +22 -15
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +43 -29
- package/dist/tools/index.d.ts +15 -48
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -4
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +1 -1
- package/dist/tools/read-file.d.ts +1 -3
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +21 -16
- package/dist/tools/save-file.d.ts.map +1 -1
- package/dist/tools/save-file.js +26 -21
- package/dist/tools/web-fetch.d.ts +0 -12
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +18 -1
- package/dist/tools/web-search.d.ts +0 -18
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tui/autocomplete/file-search-provider.js +1 -1
- package/dist/tui/autocomplete/utils.d.ts +2 -1
- package/dist/tui/autocomplete/utils.d.ts.map +1 -1
- package/dist/tui/autocomplete/utils.js +25 -23
- package/dist/tui/components/editor.d.ts +2 -1
- package/dist/tui/components/editor.d.ts.map +1 -1
- package/dist/tui/components/editor.js +1 -1
- package/dist/tui/components/markdown.d.ts +2 -2
- package/dist/tui/components/markdown.d.ts.map +1 -1
- package/dist/tui/components/welcome.d.ts +2 -1
- package/dist/tui/components/welcome.d.ts.map +1 -1
- package/dist/tui/editor-launcher.d.ts +3 -2
- package/dist/tui/editor-launcher.d.ts.map +1 -1
- package/dist/tui/index.d.ts +0 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/tui.d.ts +1 -0
- package/dist/tui/tui.d.ts.map +1 -1
- package/dist/tui/tui.js +9 -0
- package/dist/tui/utils.d.ts +1 -5
- package/dist/tui/utils.d.ts.map +1 -1
- package/dist/tui/utils.js +271 -44
- package/package.json +18 -18
- package/dist/commands/add-directory/types.d.ts +0 -6
- package/dist/commands/add-directory/types.d.ts.map +0 -1
- package/dist/commands/add-directory/types.js +0 -1
- package/dist/commands/copy/types.d.ts +0 -3
- package/dist/commands/copy/types.d.ts.map +0 -1
- package/dist/commands/copy/types.js +0 -1
- package/dist/commands/review/types.d.ts +0 -12
- package/dist/commands/review/types.d.ts.map +0 -1
- package/dist/commands/review/types.js +0 -1
- package/dist/tools/code-search.d.ts +0 -41
- package/dist/tools/code-search.d.ts.map +0 -1
- package/dist/tools/code-search.js +0 -195
- package/dist/utils/iterables.d.ts +0 -2
- package/dist/utils/iterables.d.ts.map +0 -1
- package/dist/utils/iterables.js +0 -6
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createTwoFilesPatch } from "diff";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { config } from "../config/index.js";
|
|
7
|
+
import { clearProjectStatusCache } from "../repl/project-status.js";
|
|
8
|
+
import style from "../terminal/style.js";
|
|
9
|
+
import { validateFileNotReadOnly, validatePath, } from "../utils/filesystem/security.js";
|
|
10
|
+
export const ApplyPatchTool = {
|
|
11
|
+
name: "ApplyPatch",
|
|
12
|
+
};
|
|
13
|
+
const inputSchema = z.object({
|
|
14
|
+
patchText: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe("The full apply_patch text between *** Begin Patch and *** End Patch markers"),
|
|
17
|
+
});
|
|
18
|
+
// Patch parsing functions
|
|
19
|
+
function parsePatchHeader(lines, startIdx) {
|
|
20
|
+
const line = lines[startIdx];
|
|
21
|
+
if (line.startsWith("*** Add File:")) {
|
|
22
|
+
const filePath = line.split(":", 2)[1]?.trim();
|
|
23
|
+
return filePath ? { filePath, nextIdx: startIdx + 1 } : null;
|
|
24
|
+
}
|
|
25
|
+
if (line.startsWith("*** Delete File:")) {
|
|
26
|
+
const filePath = line.split(":", 2)[1]?.trim();
|
|
27
|
+
return filePath ? { filePath, nextIdx: startIdx + 1 } : null;
|
|
28
|
+
}
|
|
29
|
+
if (line.startsWith("*** Update File:")) {
|
|
30
|
+
const filePath = line.split(":", 2)[1]?.trim();
|
|
31
|
+
let movePath;
|
|
32
|
+
let nextIdx = startIdx + 1;
|
|
33
|
+
if (nextIdx < lines.length && lines[nextIdx].startsWith("*** Move to:")) {
|
|
34
|
+
movePath = lines[nextIdx].split(":", 2)[1]?.trim();
|
|
35
|
+
nextIdx++;
|
|
36
|
+
}
|
|
37
|
+
return filePath ? { filePath, movePath, nextIdx } : null;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function parseUpdateFileChunks(lines, startIdx) {
|
|
42
|
+
const chunks = [];
|
|
43
|
+
let i = startIdx;
|
|
44
|
+
while (i < lines.length && !lines[i].startsWith("***")) {
|
|
45
|
+
if (lines[i].startsWith("@@")) {
|
|
46
|
+
const contextLine = lines[i].substring(2).trim();
|
|
47
|
+
i++;
|
|
48
|
+
const oldLines = [];
|
|
49
|
+
const newLines = [];
|
|
50
|
+
let isEndOfFile = false;
|
|
51
|
+
while (i < lines.length && !lines[i].startsWith("@@")) {
|
|
52
|
+
const changeLine = lines[i];
|
|
53
|
+
// Check for end of file marker first (before general *** check)
|
|
54
|
+
if (changeLine === "*** End of File") {
|
|
55
|
+
isEndOfFile = true;
|
|
56
|
+
i++;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
// Check for other *** markers (new file operations)
|
|
60
|
+
if (changeLine.startsWith("***")) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if (changeLine.startsWith(" ")) {
|
|
64
|
+
const content = changeLine.substring(1);
|
|
65
|
+
oldLines.push(content);
|
|
66
|
+
newLines.push(content);
|
|
67
|
+
}
|
|
68
|
+
else if (changeLine.startsWith("-")) {
|
|
69
|
+
oldLines.push(changeLine.substring(1));
|
|
70
|
+
}
|
|
71
|
+
else if (changeLine.startsWith("+")) {
|
|
72
|
+
newLines.push(changeLine.substring(1));
|
|
73
|
+
}
|
|
74
|
+
i++;
|
|
75
|
+
}
|
|
76
|
+
chunks.push({
|
|
77
|
+
oldLines,
|
|
78
|
+
newLines,
|
|
79
|
+
changeContext: contextLine || undefined,
|
|
80
|
+
isEndOfFile: isEndOfFile || undefined,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
i++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { chunks, nextIdx: i };
|
|
88
|
+
}
|
|
89
|
+
function parseAddFileContent(lines, startIdx) {
|
|
90
|
+
let content = "";
|
|
91
|
+
let i = startIdx;
|
|
92
|
+
while (i < lines.length && !lines[i].startsWith("***")) {
|
|
93
|
+
if (lines[i].startsWith("+")) {
|
|
94
|
+
content += `${lines[i].substring(1)}\n`;
|
|
95
|
+
}
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
if (content.endsWith("\n"))
|
|
99
|
+
content = content.slice(0, -1);
|
|
100
|
+
return { content, nextIdx: i };
|
|
101
|
+
}
|
|
102
|
+
export function parsePatch(patchText) {
|
|
103
|
+
const lines = patchText.split("\n");
|
|
104
|
+
const hunks = [];
|
|
105
|
+
const beginMarker = "*** Begin Patch";
|
|
106
|
+
const endMarker = "*** End Patch";
|
|
107
|
+
const beginIdx = lines.findIndex((l) => l.trim() === beginMarker);
|
|
108
|
+
const endIdx = lines.findIndex((l) => l.trim() === endMarker);
|
|
109
|
+
if (beginIdx === -1 || endIdx === -1 || beginIdx >= endIdx) {
|
|
110
|
+
throw new Error("Invalid patch format: missing Begin/End markers");
|
|
111
|
+
}
|
|
112
|
+
let i = beginIdx + 1;
|
|
113
|
+
while (i < endIdx) {
|
|
114
|
+
const header = parsePatchHeader(lines, i);
|
|
115
|
+
if (!header) {
|
|
116
|
+
i++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (lines[i].startsWith("*** Add File:")) {
|
|
120
|
+
const { content, nextIdx } = parseAddFileContent(lines, header.nextIdx);
|
|
121
|
+
hunks.push({ type: "add", path: header.filePath, contents: content });
|
|
122
|
+
i = nextIdx;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (lines[i].startsWith("*** Delete File:")) {
|
|
126
|
+
hunks.push({ type: "delete", path: header.filePath });
|
|
127
|
+
i = header.nextIdx;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (lines[i].startsWith("*** Update File:")) {
|
|
131
|
+
const { chunks: parsedChunks, nextIdx } = parseUpdateFileChunks(lines, header.nextIdx);
|
|
132
|
+
hunks.push({
|
|
133
|
+
type: "update",
|
|
134
|
+
path: header.filePath,
|
|
135
|
+
movePath: header.movePath,
|
|
136
|
+
chunks: parsedChunks,
|
|
137
|
+
});
|
|
138
|
+
i = nextIdx;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
i++;
|
|
142
|
+
}
|
|
143
|
+
return { hunks };
|
|
144
|
+
}
|
|
145
|
+
function resolvePathInRoot(rootAbs, p) {
|
|
146
|
+
// Relative paths are resolved against rootAbs
|
|
147
|
+
// Absolute paths are used as-is (but validated later)
|
|
148
|
+
const abs = path.isAbsolute(p) ? path.normalize(p) : path.resolve(rootAbs, p);
|
|
149
|
+
const rel = path.relative(rootAbs, abs) || ".";
|
|
150
|
+
return { abs, rel };
|
|
151
|
+
}
|
|
152
|
+
function seekSequence(lines, pattern, startIndex) {
|
|
153
|
+
if (pattern.length === 0)
|
|
154
|
+
return -1;
|
|
155
|
+
for (let i = startIndex; i <= lines.length - pattern.length; i++) {
|
|
156
|
+
let matches = true;
|
|
157
|
+
for (let j = 0; j < pattern.length; j++) {
|
|
158
|
+
if (lines[i + j] !== pattern[j]) {
|
|
159
|
+
matches = false;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (matches)
|
|
164
|
+
return i;
|
|
165
|
+
}
|
|
166
|
+
return -1;
|
|
167
|
+
}
|
|
168
|
+
function applyReplacements(lines, replacements) {
|
|
169
|
+
const result = [...lines];
|
|
170
|
+
for (let i = replacements.length - 1; i >= 0; i--) {
|
|
171
|
+
const [startIdx, oldLen, newSegment] = replacements[i];
|
|
172
|
+
result.splice(startIdx, oldLen);
|
|
173
|
+
for (let j = 0; j < newSegment.length; j++)
|
|
174
|
+
result.splice(startIdx + j, 0, newSegment[j]);
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function computeReplacements(originalLines, filePath, chunks) {
|
|
179
|
+
const replacements = [];
|
|
180
|
+
let lineIndex = 0;
|
|
181
|
+
for (const chunk of chunks) {
|
|
182
|
+
if (chunk.changeContext) {
|
|
183
|
+
const contextIdx = seekSequence(originalLines, [chunk.changeContext], lineIndex);
|
|
184
|
+
if (contextIdx === -1) {
|
|
185
|
+
throw new Error(`Failed to find context '${chunk.changeContext}' in ${filePath}`);
|
|
186
|
+
}
|
|
187
|
+
lineIndex = contextIdx + 1;
|
|
188
|
+
}
|
|
189
|
+
if (chunk.oldLines.length === 0) {
|
|
190
|
+
const insertionIdx = originalLines.length > 0 &&
|
|
191
|
+
originalLines[originalLines.length - 1] === ""
|
|
192
|
+
? originalLines.length - 1
|
|
193
|
+
: originalLines.length;
|
|
194
|
+
replacements.push([insertionIdx, 0, chunk.newLines]);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
let pattern = chunk.oldLines;
|
|
198
|
+
let newSlice = chunk.newLines;
|
|
199
|
+
let found = seekSequence(originalLines, pattern, lineIndex);
|
|
200
|
+
if (found === -1 &&
|
|
201
|
+
pattern.length > 0 &&
|
|
202
|
+
pattern[pattern.length - 1] === "") {
|
|
203
|
+
pattern = pattern.slice(0, -1);
|
|
204
|
+
if (newSlice.length > 0 && newSlice[newSlice.length - 1] === "")
|
|
205
|
+
newSlice = newSlice.slice(0, -1);
|
|
206
|
+
found = seekSequence(originalLines, pattern, lineIndex);
|
|
207
|
+
}
|
|
208
|
+
if (found !== -1) {
|
|
209
|
+
replacements.push([found, pattern.length, newSlice]);
|
|
210
|
+
lineIndex = found + pattern.length;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
throw new Error(`Failed to find expected lines in ${filePath}:\n${chunk.oldLines.join("\n")}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
replacements.sort((a, b) => a[0] - b[0]);
|
|
217
|
+
return replacements;
|
|
218
|
+
}
|
|
219
|
+
function generateUnifiedDiff(oldContent, newContent, filePath) {
|
|
220
|
+
return createTwoFilesPatch(filePath, filePath, oldContent, newContent, "original", "modified");
|
|
221
|
+
}
|
|
222
|
+
function deriveNewContentsFromChunks(fileAbsPath, chunks) {
|
|
223
|
+
let originalContent;
|
|
224
|
+
try {
|
|
225
|
+
originalContent = fs.readFileSync(fileAbsPath, "utf-8");
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
throw new Error(`Failed to read file ${fileAbsPath}: ${String(error)}`);
|
|
229
|
+
}
|
|
230
|
+
const originalLines = originalContent.split("\n");
|
|
231
|
+
if (originalLines.length > 0 &&
|
|
232
|
+
originalLines[originalLines.length - 1] === "")
|
|
233
|
+
originalLines.pop();
|
|
234
|
+
const replacements = computeReplacements(originalLines, fileAbsPath, chunks);
|
|
235
|
+
const newLines = applyReplacements(originalLines, replacements);
|
|
236
|
+
// ensure trailing newline
|
|
237
|
+
if (newLines.length === 0 || newLines[newLines.length - 1] !== "")
|
|
238
|
+
newLines.push("");
|
|
239
|
+
const newContent = newLines.join("\n");
|
|
240
|
+
return {
|
|
241
|
+
unifiedDiff: generateUnifiedDiff(originalContent, newContent, fileAbsPath),
|
|
242
|
+
content: newContent,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function applyChanges(changes, signal) {
|
|
246
|
+
const changed = [];
|
|
247
|
+
for (const change of changes) {
|
|
248
|
+
if (signal?.aborted)
|
|
249
|
+
throw new Error("Cancelled");
|
|
250
|
+
if (change.type === "add") {
|
|
251
|
+
const dir = path.dirname(change.path);
|
|
252
|
+
if (dir !== "." && dir !== "/")
|
|
253
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
254
|
+
await fsp.writeFile(change.path, change.content, "utf-8");
|
|
255
|
+
changed.push(change.path);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (change.type === "delete") {
|
|
259
|
+
await fsp.unlink(change.path).catch(() => { });
|
|
260
|
+
changed.push(change.path);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
// update
|
|
264
|
+
if (change.movePath) {
|
|
265
|
+
const dir = path.dirname(change.movePath);
|
|
266
|
+
if (dir !== "." && dir !== "/")
|
|
267
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
268
|
+
await fsp.writeFile(change.movePath, change.newContent, "utf-8");
|
|
269
|
+
await fsp.unlink(change.path).catch(() => { });
|
|
270
|
+
changed.push(change.movePath);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
await fsp.writeFile(change.path, change.newContent, "utf-8");
|
|
274
|
+
changed.push(change.path);
|
|
275
|
+
}
|
|
276
|
+
return changed;
|
|
277
|
+
}
|
|
278
|
+
export const createApplyPatchTool = async (options) => {
|
|
279
|
+
const { primaryDir, allowedDirs } = options.workspace;
|
|
280
|
+
const allowedDirectories = allowedDirs ?? [primaryDir];
|
|
281
|
+
const projectConfig = await config.getConfig();
|
|
282
|
+
return {
|
|
283
|
+
toolDef: {
|
|
284
|
+
description: "Apply a high-level apply_patch diff to modify files.\n\n" +
|
|
285
|
+
"Input must be in the apply_patch format surrounded by markers:\n\n" +
|
|
286
|
+
"*** Begin Patch\n" +
|
|
287
|
+
"*** Update File: path/to/file\n" +
|
|
288
|
+
"@@ optional context line\n" +
|
|
289
|
+
" unchanged line\n" +
|
|
290
|
+
"- removed line\n" +
|
|
291
|
+
"+ added line\n" +
|
|
292
|
+
"*** End Patch\n\n" +
|
|
293
|
+
"Supported operations:\n" +
|
|
294
|
+
"- *** Add File: path/to/file (content lines prefixed with +)\n" +
|
|
295
|
+
"- *** Update File (with optional *** Move to: new/path)\n" +
|
|
296
|
+
"- *** Delete File: path/to/file\n\n" +
|
|
297
|
+
"File paths may be absolute or relative to the project directory.",
|
|
298
|
+
inputSchema,
|
|
299
|
+
},
|
|
300
|
+
display({ patchText }) {
|
|
301
|
+
// Extract number of files being modified from patch text
|
|
302
|
+
try {
|
|
303
|
+
const { hunks } = parsePatch(patchText);
|
|
304
|
+
return `${style.cyan("apply patch")} (${hunks.length} file${hunks.length === 1 ? "" : "s"})`;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return `${style.cyan("apply patch")}`;
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
async execute({ patchText }, { abortSignal }) {
|
|
311
|
+
if (abortSignal?.aborted) {
|
|
312
|
+
throw new Error("Apply patch aborted");
|
|
313
|
+
}
|
|
314
|
+
const root = path.resolve(primaryDir);
|
|
315
|
+
const { hunks } = parsePatch(patchText);
|
|
316
|
+
if (hunks.length === 0) {
|
|
317
|
+
return "No changes found in patch.";
|
|
318
|
+
}
|
|
319
|
+
// Build a list of resolved file changes (absolute paths), and a diff preview.
|
|
320
|
+
const changes = [];
|
|
321
|
+
let diffOutput = "";
|
|
322
|
+
for (const hunk of hunks) {
|
|
323
|
+
if (abortSignal?.aborted)
|
|
324
|
+
throw new Error("Cancelled");
|
|
325
|
+
const p = resolvePathInRoot(root, hunk.path);
|
|
326
|
+
// Validate path is within allowed directories
|
|
327
|
+
const validPath = await validatePath(p.abs, allowedDirectories, {
|
|
328
|
+
requireExistence: hunk.type !== "add",
|
|
329
|
+
abortSignal,
|
|
330
|
+
});
|
|
331
|
+
if (hunk.type === "add") {
|
|
332
|
+
changes.push({
|
|
333
|
+
type: "add",
|
|
334
|
+
path: validPath,
|
|
335
|
+
content: hunk.contents,
|
|
336
|
+
});
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (hunk.type === "delete") {
|
|
340
|
+
// Validate file is not read-only before deletion
|
|
341
|
+
validateFileNotReadOnly(validPath, projectConfig, primaryDir);
|
|
342
|
+
changes.push({ type: "delete", path: validPath });
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
// update - validate file is not read-only
|
|
346
|
+
validateFileNotReadOnly(validPath, projectConfig, primaryDir);
|
|
347
|
+
let moveAbs;
|
|
348
|
+
if (hunk.movePath) {
|
|
349
|
+
const moveP = resolvePathInRoot(root, hunk.movePath);
|
|
350
|
+
moveAbs = await validatePath(moveP.abs, allowedDirectories, {
|
|
351
|
+
requireExistence: false,
|
|
352
|
+
abortSignal,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
const upd = deriveNewContentsFromChunks(validPath, hunk.chunks);
|
|
356
|
+
changes.push({
|
|
357
|
+
type: "update",
|
|
358
|
+
path: validPath,
|
|
359
|
+
movePath: moveAbs,
|
|
360
|
+
newContent: upd.content,
|
|
361
|
+
unifiedDiff: upd.unifiedDiff,
|
|
362
|
+
});
|
|
363
|
+
if (upd.unifiedDiff) {
|
|
364
|
+
diffOutput += `*** Update File: ${path.relative(root, moveAbs ?? validPath)}\n`;
|
|
365
|
+
diffOutput += `${upd.unifiedDiff}\n`;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Apply changes
|
|
369
|
+
const changedAbs = await applyChanges(changes, abortSignal);
|
|
370
|
+
const changedRel = changedAbs.map((p) => path.relative(root, p));
|
|
371
|
+
clearProjectStatusCache();
|
|
372
|
+
const summary = `${changedRel.length} file(s) changed`;
|
|
373
|
+
const filesList = changedRel.map((x) => ` ${x}`).join("\n");
|
|
374
|
+
return `Patch applied successfully. ${summary}\n${filesList}\n\n${diffOutput}`;
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
};
|
package/dist/tools/bash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../source/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAUpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA0CvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../source/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAUpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA0CvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AA4MF,QAAA,MAAM,WAAW;;;;;iBAkBf,CAAC;AAEH,KAAK,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD,eAAO,MAAM,cAAc,GAAU,SAAS;IAC5C,SAAS,EAAE,gBAAgB,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;;;;;;;;;;yBA2FwB,eAAe;mDAIK,eAAe,mBACrC,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EA6BrB,CAAC"}
|
package/dist/tools/bash.js
CHANGED
|
@@ -18,7 +18,7 @@ import { convertNullString } from "../utils/zod.js";
|
|
|
18
18
|
function detectMultilineGitCommit(command) {
|
|
19
19
|
const trimmed = command.trim();
|
|
20
20
|
// Check if it's a git commit command
|
|
21
|
-
if (!trimmed.startsWith("git commit")
|
|
21
|
+
if (!trimmed.startsWith("git commit")) {
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
24
|
// Look for -m or -am flags with a message containing newlines
|
|
@@ -209,6 +209,21 @@ export const BashTool = {
|
|
|
209
209
|
const simpleDescription = "Run terminal commands.";
|
|
210
210
|
// Command execution timeout in milliseconds
|
|
211
211
|
const DEFAULT_TIMEOUT = 1.5 * 60 * 1000; // 1.5 minutes
|
|
212
|
+
// Maximum output size in bytes (50KB) to prevent context window exhaustion
|
|
213
|
+
const MAX_OUTPUT_SIZE = 50 * 1024;
|
|
214
|
+
/**
|
|
215
|
+
* Truncates output if it exceeds MAX_OUTPUT_SIZE and adds a clear message.
|
|
216
|
+
* This prevents extremely large outputs from exhausting the context window.
|
|
217
|
+
*/
|
|
218
|
+
function truncateOutput(output) {
|
|
219
|
+
if (output.length <= MAX_OUTPUT_SIZE) {
|
|
220
|
+
return output;
|
|
221
|
+
}
|
|
222
|
+
const truncatedLength = MAX_OUTPUT_SIZE;
|
|
223
|
+
const originalLength = output.length;
|
|
224
|
+
const truncated = output.slice(0, truncatedLength);
|
|
225
|
+
return `${truncated}\n\n[OUTPUT TRUNCATED: ${originalLength.toLocaleString()} characters total, showing first ${truncatedLength.toLocaleString()} characters. The output was too large and was truncated to prevent context window exhaustion. Consider using commands that produce smaller output (e.g., head, tail with line limits, or redirecting to a file).]`;
|
|
226
|
+
}
|
|
212
227
|
const inputSchema = z.object({
|
|
213
228
|
command: z.string().describe("Full CLI command to execute."),
|
|
214
229
|
cwd: z
|
|
@@ -268,7 +283,7 @@ export const createBashTool = async (options) => {
|
|
|
268
283
|
return `Background process started with PID: ${proc.pid}`;
|
|
269
284
|
}
|
|
270
285
|
async function executeSync(cmd, cwd, timeout, signal) {
|
|
271
|
-
const { output, exitCode } = await execEnv.executeCommand(cmd, {
|
|
286
|
+
const { output, exitCode, error } = await execEnv.executeCommand(cmd, {
|
|
272
287
|
cwd,
|
|
273
288
|
timeout,
|
|
274
289
|
abortSignal: signal,
|
|
@@ -277,9 +292,15 @@ export const createBashTool = async (options) => {
|
|
|
277
292
|
throwOnError: false,
|
|
278
293
|
});
|
|
279
294
|
if (exitCode !== 0) {
|
|
280
|
-
|
|
295
|
+
const errorMessage = error
|
|
296
|
+
? error.message
|
|
297
|
+
: `Command exited with code ${exitCode}`;
|
|
298
|
+
const combinedOutput = output
|
|
299
|
+
? `${errorMessage}\n${output}`
|
|
300
|
+
: errorMessage;
|
|
301
|
+
throw new Error(truncateOutput(combinedOutput));
|
|
281
302
|
}
|
|
282
|
-
return output;
|
|
303
|
+
return truncateOutput(output);
|
|
283
304
|
}
|
|
284
305
|
return {
|
|
285
306
|
toolDef: {
|
|
@@ -411,9 +432,9 @@ function fixRgCommand(command) {
|
|
|
411
432
|
// If it's just alphanumeric with maybe some regex chars, it's probably a pattern
|
|
412
433
|
// Common pattern chars: ., *, +, ?, [, ], ^, $, (, ), |, \\
|
|
413
434
|
// But we want to be conservative - if it looks like a filename without path, add .
|
|
414
|
-
if (!lastToken.includes("/") &&
|
|
415
|
-
|
|
416
|
-
|
|
435
|
+
if (!lastToken.includes("/") &&
|
|
436
|
+
!lastToken.includes("*") &&
|
|
437
|
+
!lastToken.includes(".")) {
|
|
417
438
|
logger.debug(`Adding '.' to rg command: ${command}`);
|
|
418
439
|
return `${command} .`;
|
|
419
440
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-tree.d.ts","sourceRoot":"","sources":["../../source/tools/directory-tree.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAMpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,eAAO,MAAM,iBAAiB;;CAE7B,CAAC;AAEF,QAAA,MAAM,WAAW;;;;iBAYf,CAAC;AAEH,KAAK,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAE5D,eAAO,MAAM,uBAAuB,GAAU,SAAS;IACrD,SAAS,EAAE,gBAAgB,CAAC;CAC7B;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"directory-tree.d.ts","sourceRoot":"","sources":["../../source/tools/directory-tree.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAMpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAKvD,eAAO,MAAM,iBAAiB;;CAE7B,CAAC;AAEF,QAAA,MAAM,WAAW;;;;iBAYf,CAAC;AAEH,KAAK,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAE5D,eAAO,MAAM,uBAAuB,GAAU,SAAS;IACrD,SAAS,EAAE,gBAAgB,CAAC;CAC7B;;;;;;;;;4CAS2C,wBAAwB;4CAY9B,wBAAwB,mBACvC,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EAyBrB,CAAC"}
|
|
@@ -25,7 +25,7 @@ export const createDirectoryTreeTool = async (options) => {
|
|
|
25
25
|
const allowedDirectory = allowedDirs ?? [primaryDir];
|
|
26
26
|
return {
|
|
27
27
|
toolDef: {
|
|
28
|
-
description: "Show directory structure as a tree.",
|
|
28
|
+
description: "Show directory structure as a recursive tree. Use this to explore nested directories and understand the overall project structure. For a simple flat list of a single directory, use LS instead.",
|
|
29
29
|
inputSchema,
|
|
30
30
|
},
|
|
31
31
|
display({ path, maxDepth, maxResults }) {
|
|
@@ -16,7 +16,7 @@ declare const toolMetadataSchema: z.ZodObject<{
|
|
|
16
16
|
}, z.core.$strip>>;
|
|
17
17
|
needsApproval: z.ZodDefault<z.ZodBoolean>;
|
|
18
18
|
}, z.core.$strip>;
|
|
19
|
-
|
|
19
|
+
type ToolMetadata = z.infer<typeof toolMetadataSchema>;
|
|
20
20
|
export declare function parseToolMetadata(output: string): ToolMetadata;
|
|
21
21
|
interface DynamicToolObject {
|
|
22
22
|
toolDef: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-tool-loader.d.ts","sourceRoot":"","sources":["../../source/tools/dynamic-tool-loader.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;iBAatB,CAAC;AAEH,
|
|
1
|
+
{"version":3,"file":"dynamic-tool-loader.d.ts","sourceRoot":"","sources":["../../source/tools/dynamic-tool-loader.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;iBAatB,CAAC;AAEH,KAAK,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEvD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAS9D;AA2LD,UAAU,iBAAiB;IACzB,OAAO,EAAE;QACP,WAAW,EAAE,MAAM,CAAC;QAEpB,WAAW,EAAE,GAAG,CAAC;KAClB,CAAC;IACF,OAAO,EAAE,CACP,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,oBAAoB,KAC1B,OAAO,CAAC,MAAM,CAAC,CAAC;CACtB;AAwCD,wBAAsB,gBAAgB,CAAC,EACrC,OAAO,EACP,iBAAsB,GACvB,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,8CA2FA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit-file.d.ts","sourceRoot":"","sources":["../../source/tools/edit-file.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AASpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,eAAO,MAAM,YAAY;;CAExB,CAAC;AAIF,QAAA,MAAM,WAAW;;;;;;iBAqCf,CAAC;AAEH,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEvD,eAAO,MAAM,kBAAkB,GAAU,SAAS;IAChD,SAAS,EAAE,gBAAgB,CAAC;CAC7B;;;;;;;;;;;6BAY4B,mBAAmB;6BAKzB,mBAAmB,mBACnB,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EA6BrB,CAAC;AAiDF,UAAU,QAAQ;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"edit-file.d.ts","sourceRoot":"","sources":["../../source/tools/edit-file.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AASpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,eAAO,MAAM,YAAY;;CAExB,CAAC;AAIF,QAAA,MAAM,WAAW;;;;;;iBAqCf,CAAC;AAEH,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEvD,eAAO,MAAM,kBAAkB,GAAU,SAAS;IAChD,SAAS,EAAE,gBAAgB,CAAC;CAC7B;;;;;;;;;;;6BAY4B,mBAAmB;6BAKzB,mBAAmB,mBACnB,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EA6BrB,CAAC;AAiDF,UAAU,QAAQ;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAgQD,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,UAAQ,EACd,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,MAAM,CAAC,CAqEjB"}
|