coder-agent 2.6.2 → 2.7.0
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.js +63 -5
- package/dist/index.js +18 -0
- package/dist/tools.js +32 -0
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -281,9 +281,53 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
|
|
|
281
281
|
let buffer = "";
|
|
282
282
|
let accumulatedContent = "";
|
|
283
283
|
let accumulatedToolCalls = [];
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
284
|
+
let typewriterQueue = [];
|
|
285
|
+
let typewriterActive = false;
|
|
286
|
+
let resolveTypewriterFinished = null;
|
|
287
|
+
const pushToTypewriter = (text) => {
|
|
288
|
+
typewriterQueue.push(...text.split(""));
|
|
289
|
+
if (!typewriterActive) {
|
|
290
|
+
typewriterActive = true;
|
|
291
|
+
runTypewriterLoop();
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const runTypewriterLoop = async () => {
|
|
295
|
+
while (typewriterQueue.length > 0) {
|
|
296
|
+
const len = typewriterQueue.length;
|
|
297
|
+
let batchSize = 1;
|
|
298
|
+
let delay = 15; // default smooth delay
|
|
299
|
+
if (len > 80) {
|
|
300
|
+
batchSize = 8;
|
|
301
|
+
delay = 1;
|
|
302
|
+
}
|
|
303
|
+
else if (len > 40) {
|
|
304
|
+
batchSize = 4;
|
|
305
|
+
delay = 2;
|
|
306
|
+
}
|
|
307
|
+
else if (len > 20) {
|
|
308
|
+
batchSize = 2;
|
|
309
|
+
delay = 5;
|
|
310
|
+
}
|
|
311
|
+
else if (len > 10) {
|
|
312
|
+
batchSize = 1;
|
|
313
|
+
delay = 8;
|
|
314
|
+
}
|
|
315
|
+
const chars = typewriterQueue.splice(0, batchSize).join("");
|
|
316
|
+
accumulatedContent += chars;
|
|
317
|
+
const maxLen = (process.stdout.columns || 80) - 20;
|
|
318
|
+
let display = accumulatedContent.replace(/\r?\n/g, " ");
|
|
319
|
+
if (display.length > maxLen) {
|
|
320
|
+
display = "..." + display.slice(-maxLen + 3);
|
|
321
|
+
}
|
|
322
|
+
updateSpinner(chalk.dim("thinking: ") + chalk.gray(display));
|
|
323
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
324
|
+
}
|
|
325
|
+
typewriterActive = false;
|
|
326
|
+
if (resolveTypewriterFinished) {
|
|
327
|
+
resolveTypewriterFinished();
|
|
328
|
+
resolveTypewriterFinished = null;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
287
331
|
const processChunk = (chunk) => {
|
|
288
332
|
buffer += decoder.decode(chunk, { stream: true });
|
|
289
333
|
const lines = buffer.split("\n");
|
|
@@ -302,9 +346,11 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
|
|
|
302
346
|
continue;
|
|
303
347
|
const content = choice.delta?.content;
|
|
304
348
|
if (content) {
|
|
305
|
-
accumulatedContent += content;
|
|
306
349
|
if (!silent) {
|
|
307
|
-
|
|
350
|
+
pushToTypewriter(content);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
accumulatedContent += content;
|
|
308
354
|
}
|
|
309
355
|
}
|
|
310
356
|
const toolCalls = choice.delta?.tool_calls;
|
|
@@ -361,6 +407,18 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
|
|
|
361
407
|
processChunk(value);
|
|
362
408
|
}
|
|
363
409
|
}
|
|
410
|
+
// Wait for the typewriter to finish writing before returning
|
|
411
|
+
if (typewriterActive && !silent) {
|
|
412
|
+
await new Promise((resolve) => {
|
|
413
|
+
resolveTypewriterFinished = resolve;
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
if (!silent) {
|
|
417
|
+
stopSpinner();
|
|
418
|
+
if (accumulatedToolCalls.length === 0 && accumulatedContent.trim() !== "") {
|
|
419
|
+
console.log(formatResponseText(accumulatedContent));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
364
422
|
const finalResponse = {
|
|
365
423
|
choices: [
|
|
366
424
|
{
|
package/dist/index.js
CHANGED
|
@@ -294,6 +294,20 @@ async function main() {
|
|
|
294
294
|
return;
|
|
295
295
|
}
|
|
296
296
|
currentAbortController = new AbortController();
|
|
297
|
+
// Hijack stdin data listeners during agent execution to allow Ctrl+C to abort the agent immediately
|
|
298
|
+
const originalListeners = process.stdin.listeners("data");
|
|
299
|
+
for (const listener of originalListeners) {
|
|
300
|
+
process.stdin.removeListener("data", listener);
|
|
301
|
+
}
|
|
302
|
+
const tempSigintHandler = (data) => {
|
|
303
|
+
if (data.includes(3)) { // Ctrl+C byte
|
|
304
|
+
if (currentAbortController) {
|
|
305
|
+
currentAbortController.abort();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
process.stdin.on("data", tempSigintHandler);
|
|
310
|
+
process.stdin.resume();
|
|
297
311
|
try {
|
|
298
312
|
await agent.chat(trimmed, currentAbortController.signal);
|
|
299
313
|
}
|
|
@@ -315,6 +329,10 @@ async function main() {
|
|
|
315
329
|
}
|
|
316
330
|
}
|
|
317
331
|
finally {
|
|
332
|
+
process.stdin.removeListener("data", tempSigintHandler);
|
|
333
|
+
for (const listener of originalListeners) {
|
|
334
|
+
process.stdin.on("data", listener);
|
|
335
|
+
}
|
|
318
336
|
currentAbortController = null;
|
|
319
337
|
}
|
|
320
338
|
rl.resume();
|
package/dist/tools.js
CHANGED
|
@@ -12,6 +12,17 @@ function normalizeFilePath(p) {
|
|
|
12
12
|
}
|
|
13
13
|
return path.normalize(normalized);
|
|
14
14
|
}
|
|
15
|
+
function isPathSafe(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
const resolvedPath = path.resolve(normalizeFilePath(filePath));
|
|
18
|
+
const cwd = path.resolve(process.cwd());
|
|
19
|
+
const relative = path.relative(cwd, resolvedPath);
|
|
20
|
+
return !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
15
26
|
function isProtectedPath(filePath) {
|
|
16
27
|
const normalized = path.resolve(normalizeFilePath(filePath));
|
|
17
28
|
const relativePath = path.relative(process.cwd(), normalized);
|
|
@@ -179,6 +190,9 @@ export const TOOL_DEFINITIONS = [
|
|
|
179
190
|
// ─── Tool Implementations ────────────────────────────────────────────────────
|
|
180
191
|
export async function read_file({ file_path }) {
|
|
181
192
|
try {
|
|
193
|
+
if (!isPathSafe(file_path)) {
|
|
194
|
+
return `ERROR: Access to files outside the project workspace is denied for security reasons.`;
|
|
195
|
+
}
|
|
182
196
|
const targetPath = normalizeFilePath(file_path);
|
|
183
197
|
const content = await fs.readFile(targetPath, "utf-8");
|
|
184
198
|
return content;
|
|
@@ -189,6 +203,9 @@ export async function read_file({ file_path }) {
|
|
|
189
203
|
}
|
|
190
204
|
export async function write_file({ file_path, content }) {
|
|
191
205
|
try {
|
|
206
|
+
if (!isPathSafe(file_path)) {
|
|
207
|
+
return `ERROR: Access to files outside the project workspace is denied for security reasons.`;
|
|
208
|
+
}
|
|
192
209
|
if (isProtectedPath(file_path)) {
|
|
193
210
|
return `ERROR: Modification of agent system files is strictly forbidden for security reasons.`;
|
|
194
211
|
}
|
|
@@ -203,6 +220,9 @@ export async function write_file({ file_path, content }) {
|
|
|
203
220
|
}
|
|
204
221
|
export async function list_directory({ dir_path = "." }) {
|
|
205
222
|
try {
|
|
223
|
+
if (!isPathSafe(dir_path)) {
|
|
224
|
+
return `ERROR: Access to directory outside the project workspace is denied for security reasons.`;
|
|
225
|
+
}
|
|
206
226
|
const targetPath = normalizeFilePath(dir_path);
|
|
207
227
|
const entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
208
228
|
const lines = entries.map((e) => `${e.isDirectory() ? "📁" : "📄"} ${e.name}`);
|
|
@@ -216,6 +236,9 @@ export async function run_shell({ command, cwd }, confirmHandler, signal) {
|
|
|
216
236
|
try {
|
|
217
237
|
let targetCwd = process.cwd();
|
|
218
238
|
if (cwd) {
|
|
239
|
+
if (!isPathSafe(cwd)) {
|
|
240
|
+
return `ERROR: Access to directory outside the project workspace is denied for security reasons.`;
|
|
241
|
+
}
|
|
219
242
|
const targetCwdPath = normalizeFilePath(cwd);
|
|
220
243
|
try {
|
|
221
244
|
const stats = await fs.stat(targetCwdPath);
|
|
@@ -317,6 +340,9 @@ async function walkDir(dir, fileList = []) {
|
|
|
317
340
|
export async function find_files({ query, dir_path = "." }) {
|
|
318
341
|
try {
|
|
319
342
|
const targetPath = normalizeFilePath(dir_path);
|
|
343
|
+
if (!isPathSafe(targetPath)) {
|
|
344
|
+
return `ERROR: Access to directory outside the project workspace is denied for security reasons.`;
|
|
345
|
+
}
|
|
320
346
|
const allFiles = await walkDir(targetPath);
|
|
321
347
|
const lowercaseQuery = query.toLowerCase();
|
|
322
348
|
const matches = allFiles
|
|
@@ -333,6 +359,9 @@ export async function find_files({ query, dir_path = "." }) {
|
|
|
333
359
|
}
|
|
334
360
|
export async function read_file_lines({ file_path, start_line, end_line }) {
|
|
335
361
|
try {
|
|
362
|
+
if (!isPathSafe(file_path)) {
|
|
363
|
+
return `ERROR: Access to files outside the project workspace is denied for security reasons.`;
|
|
364
|
+
}
|
|
336
365
|
const targetPath = normalizeFilePath(file_path);
|
|
337
366
|
const content = await fs.readFile(targetPath, "utf-8");
|
|
338
367
|
const lines = content.split(/\r?\n/);
|
|
@@ -386,6 +415,9 @@ export async function search_grep({ query, is_regex = false }) {
|
|
|
386
415
|
}
|
|
387
416
|
export async function patch_file({ file_path, target_code, replacement_code }) {
|
|
388
417
|
try {
|
|
418
|
+
if (!isPathSafe(file_path)) {
|
|
419
|
+
return `ERROR: Access to files outside the project workspace is denied for security reasons.`;
|
|
420
|
+
}
|
|
389
421
|
if (isProtectedPath(file_path)) {
|
|
390
422
|
return `ERROR: Modification of agent system files is strictly forbidden for security reasons.`;
|
|
391
423
|
}
|