coder-agent 2.6.3 → 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 CHANGED
@@ -281,9 +281,6 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
281
281
  let buffer = "";
282
282
  let accumulatedContent = "";
283
283
  let accumulatedToolCalls = [];
284
- if (!silent) {
285
- stopSpinner();
286
- }
287
284
  let typewriterQueue = [];
288
285
  let typewriterActive = false;
289
286
  let resolveTypewriterFinished = null;
@@ -316,7 +313,13 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
316
313
  delay = 8;
317
314
  }
318
315
  const chars = typewriterQueue.splice(0, batchSize).join("");
319
- process.stdout.write(chars);
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));
320
323
  await new Promise((resolve) => setTimeout(resolve, delay));
321
324
  }
322
325
  typewriterActive = false;
@@ -343,10 +346,12 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
343
346
  continue;
344
347
  const content = choice.delta?.content;
345
348
  if (content) {
346
- accumulatedContent += content;
347
349
  if (!silent) {
348
350
  pushToTypewriter(content);
349
351
  }
352
+ else {
353
+ accumulatedContent += content;
354
+ }
350
355
  }
351
356
  const toolCalls = choice.delta?.tool_calls;
352
357
  if (toolCalls) {
@@ -408,6 +413,12 @@ async function callGeminiAPIWithRotation(apiKey, params, maxRetries = 3, initial
408
413
  resolveTypewriterFinished = resolve;
409
414
  });
410
415
  }
416
+ if (!silent) {
417
+ stopSpinner();
418
+ if (accumulatedToolCalls.length === 0 && accumulatedContent.trim() !== "") {
419
+ console.log(formatResponseText(accumulatedContent));
420
+ }
421
+ }
411
422
  const finalResponse = {
412
423
  choices: [
413
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.6.3",
3
+ "version": "2.7.0",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",