coder-agent 2.3.4 → 2.4.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
@@ -405,14 +405,41 @@ export class Agent {
405
405
  this.memory.add({ role: "user", content: enrichedPrompt });
406
406
  let iterations = 0;
407
407
  const MAX_ITERATIONS = 12;
408
+ let waitCount = 0;
409
+ const MAX_WAITS = 3;
408
410
  const modifiedFiles = new Set();
409
411
  let cleanContent = "";
410
- while (iterations < MAX_ITERATIONS) {
412
+ while (true) {
411
413
  if (signal?.aborted) {
412
414
  const abortErr = new Error("The user aborted a request.");
413
415
  abortErr.name = "AbortError";
414
416
  throw abortErr;
415
417
  }
418
+ if (iterations >= MAX_ITERATIONS) {
419
+ if (waitCount < MAX_WAITS) {
420
+ waitCount++;
421
+ iterations = 0;
422
+ console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
423
+ startSpinner("thinking...");
424
+ // Silent wait for 30-40 seconds
425
+ const delayMs = 30000 + Math.random() * 10000;
426
+ const checkInterval = 100;
427
+ let elapsed = 0;
428
+ while (elapsed < delayMs) {
429
+ if (signal?.aborted) {
430
+ const abortErr = new Error("The user aborted a request.");
431
+ abortErr.name = "AbortError";
432
+ throw abortErr;
433
+ }
434
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
435
+ elapsed += checkInterval;
436
+ }
437
+ continue;
438
+ }
439
+ else {
440
+ break;
441
+ }
442
+ }
416
443
  iterations++;
417
444
  updateSpinner("thinking...");
418
445
  const responseObj = await callGeminiAPIWithRotation(this.apiKey, {
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ function printBanner(modelName) {
35
35
  });
36
36
  console.log('');
37
37
  console.log(' ' + chalk.white.bold('coder-agent') +
38
- chalk.dim(' v1.1.0 · by antigravity'));
38
+ chalk.dim(' v1.1.0'));
39
39
  console.log('');
40
40
  console.log(chalk.dim(' ─────────────────────────────────────'));
41
41
  console.log('');
@@ -209,111 +209,135 @@ async function main() {
209
209
  });
210
210
  let inputBuffer = "";
211
211
  let pasteTimeout = null;
212
+ let lineCountInBurst = 0;
212
213
  let currentAbortController = null;
213
- rl.setPrompt(chalk.hex('#0a84ff')('›') + ' ');
214
- rl.prompt();
215
- rl.on("line", (line) => {
216
- inputBuffer += (inputBuffer ? "\n" : "") + line;
217
- if (pasteTimeout) {
218
- clearTimeout(pasteTimeout);
214
+ async function executeAgentChat(trimmed) {
215
+ // Pause standard input processing during agent thinking & updates
216
+ rl.pause();
217
+ // Built-in slash commands
218
+ if (trimmed === "/exit" || trimmed === "/quit") {
219
+ rl.close();
220
+ process.exit(0);
219
221
  }
220
- pasteTimeout = setTimeout(async () => {
221
- pasteTimeout = null;
222
- const accumulatedInput = inputBuffer;
223
- inputBuffer = "";
224
- const trimmed = accumulatedInput.trim();
225
- if (!trimmed) {
226
- rl.prompt();
227
- return;
228
- }
229
- // Pause standard input processing during agent thinking & updates
230
- rl.pause();
231
- // Built-in slash commands
232
- if (trimmed === "/exit" || trimmed === "/quit") {
233
- rl.close();
234
- process.exit(0);
235
- }
236
- if (trimmed === "/clear") {
237
- agent.clearMemory();
238
- console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray('Memory cleared'));
239
- rl.resume();
240
- rl.prompt();
241
- return;
242
- }
243
- if (trimmed === "/status") {
244
- console.log(chalk.dim(`session · ${agent.memoryStatus()}`));
245
- rl.resume();
246
- rl.prompt();
247
- return;
222
+ if (trimmed === "/clear") {
223
+ agent.clearMemory();
224
+ console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray('Memory cleared'));
225
+ rl.resume();
226
+ rl.prompt();
227
+ return;
228
+ }
229
+ if (trimmed === "/status") {
230
+ console.log(chalk.dim(`session · ${agent.memoryStatus()}`));
231
+ rl.resume();
232
+ rl.prompt();
233
+ return;
234
+ }
235
+ if (trimmed.startsWith("/model")) {
236
+ const parts = trimmed.split(/\s+/);
237
+ if (parts.length === 1) {
238
+ console.log(chalk.dim(`model `) + chalk.gray(agent.getModel()));
239
+ console.log(chalk.gray(`options `) + chalk.gray(VALID_MODELS.join(" · ")));
248
240
  }
249
- if (trimmed.startsWith("/model")) {
250
- const parts = trimmed.split(/\s+/);
251
- if (parts.length === 1) {
252
- console.log(chalk.dim(`model `) + chalk.gray(agent.getModel()));
253
- console.log(chalk.gray(`options `) + chalk.gray(VALID_MODELS.join(" · ")));
241
+ else {
242
+ const newModel = parts[1];
243
+ if (VALID_MODELS.includes(newModel)) {
244
+ agent.setModel(newModel);
245
+ console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray(`Switched model to: ${newModel}`));
254
246
  }
255
247
  else {
256
- const newModel = parts[1];
257
- if (VALID_MODELS.includes(newModel)) {
258
- agent.setModel(newModel);
259
- console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray(`Switched model to: ${newModel}`));
260
- }
261
- else {
262
- console.log(chalk.hex('#ff453a')('✕ error'));
263
- console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" · ")}`));
264
- }
248
+ console.log(chalk.hex('#ff453a')('✕ error'));
249
+ console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" · ")}`));
265
250
  }
266
- rl.resume();
267
- rl.prompt();
268
- return;
269
251
  }
270
- if (trimmed === "/help") {
271
- console.log(chalk.white.bold("\n Interactive Commands:"));
272
- console.log(chalk.gray(` /model [name] — View active model or switch to [name]
252
+ rl.resume();
253
+ rl.prompt();
254
+ return;
255
+ }
256
+ if (trimmed === "/help") {
257
+ console.log(chalk.white.bold("\n Interactive Commands:"));
258
+ console.log(chalk.gray(` /model [name] — View active model or switch to [name]
273
259
  /clear — Wipe conversation memory
274
260
  /status — Show active model and memory usage
275
261
  /exit — Exit Coder`));
276
- console.log(chalk.white.bold("\n API Keys & Configuration:"));
277
- console.log(chalk.gray(` • Stored at: ~/.coder-config.json
262
+ console.log(chalk.white.bold("\n API Keys & Configuration:"));
263
+ console.log(chalk.gray(` • Stored at: ~/.coder-config.json
278
264
  • To change key: Exit and run 'coder-agent --set-key <key>'
279
265
  • Env variable option: GEMINI_API_KEY`));
280
- rl.resume();
281
- rl.prompt();
282
- return;
283
- }
284
- currentAbortController = new AbortController();
285
- try {
286
- await agent.chat(trimmed, currentAbortController.signal);
266
+ rl.resume();
267
+ rl.prompt();
268
+ return;
269
+ }
270
+ currentAbortController = new AbortController();
271
+ try {
272
+ await agent.chat(trimmed, currentAbortController.signal);
273
+ }
274
+ catch (err) {
275
+ if (err?.name === "AbortError" || currentAbortController.signal.aborted) {
276
+ console.log(chalk.hex('#ff453a')('\n✕ cancelled'));
287
277
  }
288
- catch (err) {
289
- if (err?.name === "AbortError" || currentAbortController.signal.aborted) {
290
- console.log(chalk.hex('#ff453a')('\n✕ cancelled'));
278
+ else {
279
+ console.log(chalk.hex('#ff453a')('✕ error'));
280
+ if (err?.status === 401) {
281
+ console.log(chalk.dim(" Invalid API key. Check your configuration."));
282
+ }
283
+ else if (err?.status === 429) {
284
+ console.log(chalk.dim(" Rate limit exceeded on Gemini API."));
291
285
  }
292
286
  else {
293
- console.log(chalk.hex('#ff453a')('✕ error'));
294
- if (err?.status === 401) {
295
- console.log(chalk.dim(" Invalid API key. Check your configuration."));
296
- }
297
- else if (err?.status === 429) {
298
- console.log(chalk.dim(" Rate limit exceeded on Gemini API."));
299
- }
300
- else {
301
- console.log(chalk.dim(` ${err.message}`));
302
- }
287
+ console.log(chalk.dim(` ${err.message}`));
303
288
  }
304
289
  }
305
- finally {
306
- currentAbortController = null;
290
+ }
291
+ finally {
292
+ currentAbortController = null;
293
+ }
294
+ rl.resume();
295
+ rl.prompt();
296
+ }
297
+ rl.setPrompt(chalk.hex('#0a84ff')('›') + ' ');
298
+ rl.prompt();
299
+ rl.on("line", (line) => {
300
+ inputBuffer += (inputBuffer ? "\n" : "") + line;
301
+ lineCountInBurst++;
302
+ if (pasteTimeout) {
303
+ clearTimeout(pasteTimeout);
304
+ }
305
+ pasteTimeout = setTimeout(async () => {
306
+ pasteTimeout = null;
307
+ const burstCount = lineCountInBurst;
308
+ lineCountInBurst = 0;
309
+ // Check if there is an unfinished line in the readline buffer (e.g. pasted text without trailing newline)
310
+ const unfinishedLine = rl.line;
311
+ // If we got multiple lines in this burst, or there is an unfinished line,
312
+ // we consider it a multi-line paste. We don't submit yet; we let the user
313
+ // edit the unfinished line or type more.
314
+ if (burstCount > 1 || unfinishedLine.trim() !== "") {
315
+ return;
307
316
  }
308
- rl.resume();
309
- rl.prompt();
310
- }, 80);
317
+ const accumulatedInput = inputBuffer;
318
+ inputBuffer = "";
319
+ const trimmed = accumulatedInput.trim();
320
+ if (!trimmed) {
321
+ rl.prompt();
322
+ return;
323
+ }
324
+ await executeAgentChat(trimmed);
325
+ }, 50);
311
326
  });
312
327
  // Handle Ctrl+C gracefully
313
328
  const sigintHandler = () => {
314
329
  if (currentAbortController) {
315
330
  currentAbortController.abort();
316
331
  }
332
+ else if (inputBuffer !== "" || rl.line !== "") {
333
+ inputBuffer = "";
334
+ lineCountInBurst = 0;
335
+ // Clear the current input buffer in readline
336
+ rl.write(null, { ctrl: true, name: 'u' });
337
+ console.log();
338
+ rl.setPrompt(chalk.hex('#0a84ff')('›') + ' ');
339
+ rl.prompt();
340
+ }
317
341
  else {
318
342
  rl.close();
319
343
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.3.4",
3
+ "version": "2.4.0",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",