@travisennis/acai 0.0.10 → 0.0.12

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.
Files changed (179) hide show
  1. package/README.md +7 -4
  2. package/dist/agent/index.d.ts.map +1 -1
  3. package/dist/agent/index.js +29 -27
  4. package/dist/cli/stdin.d.ts +2 -1
  5. package/dist/cli/stdin.d.ts.map +1 -1
  6. package/dist/commands/generate-rules/service.d.ts +3 -2
  7. package/dist/commands/generate-rules/service.d.ts.map +1 -1
  8. package/dist/commands/health/utils.d.ts +3 -2
  9. package/dist/commands/health/utils.d.ts.map +1 -1
  10. package/dist/commands/init-project/utils.d.ts +2 -1
  11. package/dist/commands/init-project/utils.d.ts.map +1 -1
  12. package/dist/commands/init-project/utils.js +0 -11
  13. package/dist/commands/manager.d.ts.map +1 -1
  14. package/dist/commands/manager.js +6 -1
  15. package/dist/commands/resources/index.d.ts.map +1 -1
  16. package/dist/commands/resources/index.js +4 -1
  17. package/dist/commands/review/utils.d.ts +6 -1
  18. package/dist/commands/review/utils.d.ts.map +1 -1
  19. package/dist/commands/session/index.d.ts.map +1 -1
  20. package/dist/commands/session/index.js +6 -0
  21. package/dist/commands/session/types.d.ts +1 -0
  22. package/dist/commands/session/types.d.ts.map +1 -1
  23. package/dist/commands/tools/index.d.ts +3 -0
  24. package/dist/commands/tools/index.d.ts.map +1 -0
  25. package/dist/commands/tools/index.js +190 -0
  26. package/dist/commands/tools/templates.d.ts +6 -0
  27. package/dist/commands/tools/templates.d.ts.map +1 -0
  28. package/dist/commands/tools/templates.js +97 -0
  29. package/dist/config/index.d.ts +5 -0
  30. package/dist/config/index.d.ts.map +1 -1
  31. package/dist/config/index.js +41 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +15 -3
  34. package/dist/models/anthropic-provider.d.ts +1 -1
  35. package/dist/models/deepseek-provider.d.ts +3 -3
  36. package/dist/models/deepseek-provider.js +17 -17
  37. package/dist/models/google-provider.d.ts +2 -4
  38. package/dist/models/google-provider.d.ts.map +1 -1
  39. package/dist/models/google-provider.js +2 -17
  40. package/dist/models/groq-provider.d.ts +2 -4
  41. package/dist/models/groq-provider.d.ts.map +1 -1
  42. package/dist/models/groq-provider.js +3 -21
  43. package/dist/models/opencode-go-provider.d.ts +35 -0
  44. package/dist/models/opencode-go-provider.d.ts.map +1 -0
  45. package/dist/models/opencode-go-provider.js +214 -0
  46. package/dist/models/opencode-zen-provider.d.ts +5 -5
  47. package/dist/models/opencode-zen-provider.d.ts.map +1 -1
  48. package/dist/models/opencode-zen-provider.js +41 -47
  49. package/dist/models/openrouter-provider.d.ts +5 -13
  50. package/dist/models/openrouter-provider.d.ts.map +1 -1
  51. package/dist/models/openrouter-provider.js +34 -138
  52. package/dist/models/providers.d.ts +3 -3
  53. package/dist/models/providers.d.ts.map +1 -1
  54. package/dist/models/providers.js +6 -0
  55. package/dist/models/xai-provider.d.ts +1 -2
  56. package/dist/models/xai-provider.d.ts.map +1 -1
  57. package/dist/models/xai-provider.js +0 -13
  58. package/dist/prompts/manager.d.ts.map +1 -1
  59. package/dist/prompts/manager.js +5 -1
  60. package/dist/prompts/mentions.d.ts.map +1 -1
  61. package/dist/prompts/mentions.js +35 -6
  62. package/dist/prompts/system-prompt.d.ts +1 -0
  63. package/dist/prompts/system-prompt.d.ts.map +1 -1
  64. package/dist/prompts/system-prompt.js +20 -5
  65. package/dist/repl/index.d.ts +1 -2
  66. package/dist/repl/index.d.ts.map +1 -1
  67. package/dist/repl/index.js +14 -53
  68. package/dist/sessions/manager.d.ts +3 -3
  69. package/dist/sessions/manager.d.ts.map +1 -1
  70. package/dist/sessions/manager.js +1 -1
  71. package/dist/skills/activated-tracker.d.ts +11 -0
  72. package/dist/skills/activated-tracker.d.ts.map +1 -0
  73. package/dist/skills/activated-tracker.js +16 -0
  74. package/dist/skills/index.d.ts +3 -2
  75. package/dist/skills/index.d.ts.map +1 -1
  76. package/dist/skills/index.js +7 -1
  77. package/dist/subagents/index.d.ts +2 -1
  78. package/dist/subagents/index.d.ts.map +1 -1
  79. package/dist/terminal/table/utils.d.ts +1 -1
  80. package/dist/terminal/table/utils.d.ts.map +1 -1
  81. package/dist/terminal/wrap-ansi.js +2 -2
  82. package/dist/tools/agent.js +1 -1
  83. package/dist/tools/apply-patch.d.ts +62 -0
  84. package/dist/tools/apply-patch.d.ts.map +1 -0
  85. package/dist/tools/apply-patch.js +377 -0
  86. package/dist/tools/bash.d.ts +4 -4
  87. package/dist/tools/bash.d.ts.map +1 -1
  88. package/dist/tools/bash.js +40 -8
  89. package/dist/tools/directory-tree.d.ts +4 -4
  90. package/dist/tools/directory-tree.d.ts.map +1 -1
  91. package/dist/tools/directory-tree.js +3 -1
  92. package/dist/tools/dynamic-tool-loader.d.ts +12 -3
  93. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  94. package/dist/tools/dynamic-tool-loader.js +299 -39
  95. package/dist/tools/edit-file.d.ts +2 -2
  96. package/dist/tools/edit-file.d.ts.map +1 -1
  97. package/dist/tools/edit-file.js +188 -79
  98. package/dist/tools/glob.d.ts +16 -16
  99. package/dist/tools/glob.d.ts.map +1 -1
  100. package/dist/tools/glob.js +30 -15
  101. package/dist/tools/grep.d.ts +14 -14
  102. package/dist/tools/grep.d.ts.map +1 -1
  103. package/dist/tools/grep.js +50 -29
  104. package/dist/tools/index.d.ts +57 -84
  105. package/dist/tools/index.d.ts.map +1 -1
  106. package/dist/tools/index.js +20 -5
  107. package/dist/tools/ls.d.ts +2 -2
  108. package/dist/tools/ls.d.ts.map +1 -1
  109. package/dist/tools/ls.js +2 -1
  110. package/dist/tools/read-file.d.ts +9 -11
  111. package/dist/tools/read-file.d.ts.map +1 -1
  112. package/dist/tools/read-file.js +21 -16
  113. package/dist/tools/save-file.d.ts +4 -4
  114. package/dist/tools/save-file.d.ts.map +1 -1
  115. package/dist/tools/save-file.js +26 -21
  116. package/dist/tools/skill.d.ts +2 -1
  117. package/dist/tools/skill.d.ts.map +1 -1
  118. package/dist/tools/skill.js +55 -12
  119. package/dist/tools/types.d.ts +8 -2
  120. package/dist/tools/types.d.ts.map +1 -1
  121. package/dist/tools/web-fetch.d.ts +6 -18
  122. package/dist/tools/web-fetch.d.ts.map +1 -1
  123. package/dist/tools/web-fetch.js +45 -9
  124. package/dist/tools/web-search.d.ts +4 -22
  125. package/dist/tools/web-search.d.ts.map +1 -1
  126. package/dist/tools/web-search.js +1 -1
  127. package/dist/tui/autocomplete/file-search-provider.js +1 -1
  128. package/dist/tui/autocomplete/utils.d.ts +2 -1
  129. package/dist/tui/autocomplete/utils.d.ts.map +1 -1
  130. package/dist/tui/autocomplete/utils.js +25 -23
  131. package/dist/tui/components/editor.d.ts +2 -1
  132. package/dist/tui/components/editor.d.ts.map +1 -1
  133. package/dist/tui/components/editor.js +1 -1
  134. package/dist/tui/components/footer.d.ts +0 -2
  135. package/dist/tui/components/footer.d.ts.map +1 -1
  136. package/dist/tui/components/footer.js +1 -17
  137. package/dist/tui/components/markdown.d.ts +2 -2
  138. package/dist/tui/components/markdown.d.ts.map +1 -1
  139. package/dist/tui/components/welcome.d.ts +2 -1
  140. package/dist/tui/components/welcome.d.ts.map +1 -1
  141. package/dist/tui/editor-launcher.d.ts +3 -2
  142. package/dist/tui/editor-launcher.d.ts.map +1 -1
  143. package/dist/tui/index.d.ts +0 -1
  144. package/dist/tui/index.d.ts.map +1 -1
  145. package/dist/tui/tui.d.ts +1 -0
  146. package/dist/tui/tui.d.ts.map +1 -1
  147. package/dist/tui/tui.js +9 -0
  148. package/dist/tui/utils.d.ts +1 -5
  149. package/dist/tui/utils.d.ts.map +1 -1
  150. package/dist/tui/utils.js +271 -44
  151. package/dist/utils/binary-output.d.ts +32 -0
  152. package/dist/utils/binary-output.d.ts.map +1 -0
  153. package/dist/utils/binary-output.js +127 -0
  154. package/dist/utils/command-protection.d.ts.map +1 -1
  155. package/dist/utils/command-protection.js +92 -9
  156. package/dist/utils/parsing.d.ts +1 -1
  157. package/dist/utils/parsing.d.ts.map +1 -1
  158. package/package.json +28 -26
  159. package/dist/commands/add-directory/types.d.ts +0 -6
  160. package/dist/commands/add-directory/types.d.ts.map +0 -1
  161. package/dist/commands/add-directory/types.js +0 -1
  162. package/dist/commands/copy/types.d.ts +0 -3
  163. package/dist/commands/copy/types.d.ts.map +0 -1
  164. package/dist/commands/copy/types.js +0 -1
  165. package/dist/commands/review/types.d.ts +0 -12
  166. package/dist/commands/review/types.d.ts.map +0 -1
  167. package/dist/commands/review/types.js +0 -1
  168. package/dist/modes/manager.d.ts +0 -23
  169. package/dist/modes/manager.d.ts.map +0 -1
  170. package/dist/modes/manager.js +0 -77
  171. package/dist/modes/prompts.d.ts +0 -2
  172. package/dist/modes/prompts.d.ts.map +0 -1
  173. package/dist/modes/prompts.js +0 -143
  174. package/dist/tools/code-search.d.ts +0 -41
  175. package/dist/tools/code-search.d.ts.map +0 -1
  176. package/dist/tools/code-search.js +0 -195
  177. package/dist/utils/iterables.d.ts +0 -2
  178. package/dist/utils/iterables.d.ts.map +0 -1
  179. 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
+ };
@@ -6,8 +6,8 @@ export declare const BashTool: {
6
6
  };
7
7
  declare const inputSchema: z.ZodObject<{
8
8
  command: z.ZodString;
9
- cwd: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodString>>;
10
- timeout: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
9
+ cwd: z.ZodPreprocess<z.ZodNullable<z.ZodString>>;
10
+ timeout: z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
11
11
  background: z.ZodOptional<z.ZodBoolean>;
12
12
  }, z.core.$strip>;
13
13
  type BashInputSchema = z.infer<typeof inputSchema>;
@@ -19,8 +19,8 @@ export declare const createBashTool: (options: {
19
19
  description: string;
20
20
  inputSchema: z.ZodObject<{
21
21
  command: z.ZodString;
22
- cwd: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodString>>;
23
- timeout: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
22
+ cwd: z.ZodPreprocess<z.ZodNullable<z.ZodString>>;
23
+ timeout: z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
24
24
  background: z.ZodOptional<z.ZodBoolean>;
25
25
  }, z.core.$strip>;
26
26
  };
@@ -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;AAyLF,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;;;;;;;;;;yBAqFwB,eAAe;mDAIK,eAAe,mBACrC,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EA6BrB,CAAC"}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../source/tools/bash.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAepD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AA0CvD,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AA6MF,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;;;;;;;;;;yBAyGwB,eAAe;mDAIK,eAAe,mBACrC,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EA6BrB,CAAC"}
@@ -1,4 +1,3 @@
1
- // import { execSync } from "node:child_process";
2
1
  import { randomBytes } from "node:crypto";
3
2
  import { mkdirSync, writeFileSync } from "node:fs";
4
3
  import { dirname } from "node:path";
@@ -6,6 +5,7 @@ import { z } from "zod";
6
5
  import { initExecutionEnvironment } from "../execution/index.js";
7
6
  import style from "../terminal/style.js";
8
7
  import { resolveCwd, validatePaths } from "../utils/bash.js";
8
+ import { formatBinaryMessage, isBinaryOutput, saveBinaryOutput, } from "../utils/binary-output.js";
9
9
  import { detectDestructiveCommand, formatBlockedCommandMessage, } from "../utils/command-protection.js";
10
10
  import { expandEnvVars } from "../utils/env-expand.js";
11
11
  import { logger } from "../utils/logger.js";
@@ -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") && !trimmed.startsWith("git ")) {
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,22 @@ 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
+ * The footer is always appended at the end, even when output is truncated.
218
+ */
219
+ function truncateOutput(output, footer) {
220
+ if (output.length <= MAX_OUTPUT_SIZE) {
221
+ return `${output}\n${footer}`;
222
+ }
223
+ const truncatedLength = MAX_OUTPUT_SIZE;
224
+ const originalLength = output.length;
225
+ const truncated = output.slice(0, truncatedLength);
226
+ 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).]\n${footer}`;
227
+ }
212
228
  const inputSchema = z.object({
213
229
  command: z.string().describe("Full CLI command to execute."),
214
230
  cwd: z
@@ -268,7 +284,8 @@ export const createBashTool = async (options) => {
268
284
  return `Background process started with PID: ${proc.pid}`;
269
285
  }
270
286
  async function executeSync(cmd, cwd, timeout, signal) {
271
- const { output, exitCode } = await execEnv.executeCommand(cmd, {
287
+ const startTime = Date.now();
288
+ const { output, exitCode, error } = await execEnv.executeCommand(cmd, {
272
289
  cwd,
273
290
  timeout,
274
291
  abortSignal: signal,
@@ -276,10 +293,25 @@ export const createBashTool = async (options) => {
276
293
  captureStderr: true,
277
294
  throwOnError: false,
278
295
  });
296
+ const elapsedMs = Date.now() - startTime;
297
+ const timeStr = elapsedMs < 1000 ? `${elapsedMs}ms` : `${(elapsedMs / 1000).toFixed(1)}s`;
298
+ const metadataFooter = `[exit:${exitCode} | ${timeStr}]`;
279
299
  if (exitCode !== 0) {
280
- throw new Error(output);
300
+ const errorMessage = error
301
+ ? error.message
302
+ : `Command exited with code ${exitCode}`;
303
+ const combinedOutput = output
304
+ ? `${errorMessage}\n${output}`
305
+ : errorMessage;
306
+ throw new Error(truncateOutput(combinedOutput, metadataFooter));
307
+ }
308
+ // Check for binary output and handle specially
309
+ if (isBinaryOutput(output)) {
310
+ const saveResult = saveBinaryOutput(output);
311
+ const binaryMessage = formatBinaryMessage(saveResult);
312
+ return `${binaryMessage}\n${metadataFooter}`;
281
313
  }
282
- return output;
314
+ return truncateOutput(output, metadataFooter);
283
315
  }
284
316
  return {
285
317
  toolDef: {
@@ -411,9 +443,9 @@ function fixRgCommand(command) {
411
443
  // If it's just alphanumeric with maybe some regex chars, it's probably a pattern
412
444
  // Common pattern chars: ., *, +, ?, [, ], ^, $, (, ), |, \\
413
445
  // But we want to be conservative - if it looks like a filename without path, add .
414
- if (!lastToken.includes("/") && !lastToken.includes("*")) {
415
- // Doesn't look like a path with glob or directory, likely a pattern
416
- // Need to add path
446
+ if (!lastToken.includes("/") &&
447
+ !lastToken.includes("*") &&
448
+ !lastToken.includes(".")) {
417
449
  logger.debug(`Adding '.' to rg command: ${command}`);
418
450
  return `${command} .`;
419
451
  }
@@ -6,8 +6,8 @@ export declare const DirectoryTreeTool: {
6
6
  };
7
7
  declare const inputSchema: z.ZodObject<{
8
8
  path: z.ZodString;
9
- maxResults: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
10
- maxDepth: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
9
+ maxResults: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
10
+ maxDepth: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
11
11
  }, z.core.$strip>;
12
12
  type DirectoryTreeInputSchema = z.infer<typeof inputSchema>;
13
13
  export declare const createDirectoryTreeTool: (options: {
@@ -17,8 +17,8 @@ export declare const createDirectoryTreeTool: (options: {
17
17
  description: string;
18
18
  inputSchema: z.ZodObject<{
19
19
  path: z.ZodString;
20
- maxResults: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
21
- maxDepth: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNullable<z.ZodCoercedNumber<unknown>>>;
20
+ maxResults: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
21
+ maxDepth: z.ZodDefault<z.ZodPreprocess<z.ZodNullable<z.ZodCoercedNumber<unknown>>>>;
22
22
  }, z.core.$strip>;
23
23
  };
24
24
  display({ path, maxDepth, maxResults }: DirectoryTreeInputSchema): string;
@@ -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;;;;;;;;;4CAQ2C,wBAAwB;4CAY9B,wBAAwB,mBACvC,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EAyBrB,CAAC"}
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;;;;iBAcf,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"}
@@ -15,9 +15,11 @@ const inputSchema = z.object({
15
15
  path: z.string().describe("The path"),
16
16
  maxResults: z
17
17
  .preprocess((val) => convertNullString(val), z.coerce.number().nullable())
18
+ .default(null)
18
19
  .describe(`Maximum number of items (files + directories) to return. Set to 0 for no limit. (default: ${DEFAULT_ITEM_LIMIT})`),
19
20
  maxDepth: z
20
21
  .preprocess((val) => convertNullString(val), z.coerce.number().nullable())
22
+ .default(null)
21
23
  .describe(`Maximum recursion depth. Set to 0 for no limit. (default: ${DEFAULT_DEPTH_LIMIT})`),
22
24
  });
23
25
  export const createDirectoryTreeTool = async (options) => {
@@ -25,7 +27,7 @@ export const createDirectoryTreeTool = async (options) => {
25
27
  const allowedDirectory = allowedDirs ?? [primaryDir];
26
28
  return {
27
29
  toolDef: {
28
- description: "Show directory structure as a tree.",
30
+ 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
31
  inputSchema,
30
32
  },
31
33
  display({ path, maxDepth, maxResults }) {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import type { ToolExecutionOptions } from "./types.ts";
2
+ import type { SessionContext, ToolExecutionOptions } from "./types.ts";
3
3
  declare const toolMetadataSchema: z.ZodObject<{
4
4
  name: z.ZodString;
5
5
  description: z.ZodString;
@@ -16,7 +16,15 @@ declare const toolMetadataSchema: z.ZodObject<{
16
16
  }, z.core.$strip>>;
17
17
  needsApproval: z.ZodDefault<z.ZodBoolean>;
18
18
  }, z.core.$strip>;
19
- export type ToolMetadata = z.infer<typeof toolMetadataSchema>;
19
+ type ToolMetadata = z.infer<typeof toolMetadataSchema>;
20
+ interface InterpreterResult {
21
+ command: string;
22
+ args: string[];
23
+ }
24
+ export declare function getShebang(scriptPath: string): string | null;
25
+ export declare function parseShebang(shebang: string, scriptPath: string): InterpreterResult;
26
+ export declare function resolveToolInterpreter(scriptPath: string): InterpreterResult | null;
27
+ export declare function parseTextSchema(content: string): ToolMetadata | null;
20
28
  export declare function parseToolMetadata(output: string): ToolMetadata;
21
29
  interface DynamicToolObject {
22
30
  toolDef: {
@@ -25,9 +33,10 @@ interface DynamicToolObject {
25
33
  };
26
34
  execute: (input: Record<string, unknown>, options: ToolExecutionOptions) => Promise<string>;
27
35
  }
28
- export declare function loadDynamicTools({ baseDir, existingToolNames, }: {
36
+ export declare function loadDynamicTools({ baseDir, existingToolNames, sessionContext, }: {
29
37
  baseDir: string;
30
38
  existingToolNames?: string[];
39
+ sessionContext?: SessionContext;
31
40
  }): Promise<Record<string, DynamicToolObject>>;
32
41
  export {};
33
42
  //# sourceMappingURL=dynamic-tool-loader.d.ts.map
@@ -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,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,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
+ {"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,cAAc,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvE,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;iBAatB,CAAC;AAEH,KAAK,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAMvD,UAAU,iBAAiB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAyBD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc5D;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,iBAAiB,CAUnB;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,GACjB,iBAAiB,GAAG,IAAI,CA0B1B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAwFpE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAS9D;AAsOD,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;AAwED,wBAAsB,gBAAgB,CAAC,EACrC,OAAO,EACP,iBAAsB,EACtB,cAAc,GACf,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC,8CAuJA"}