coder-agent 2.2.0 → 2.2.2

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
@@ -1,5 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import * as path from "path";
3
+ import * as fs from "fs/promises";
3
4
  import { TOOL_DEFINITIONS, dispatchTool } from "./tools.js";
4
5
  import { Memory } from "./memory.js";
5
6
  // ─── Loading Spinner ──────────────────────────────────────────────────────────
@@ -28,7 +29,55 @@ function stopSpinner() {
28
29
  process.stdout.write('\r\x1b[K');
29
30
  }
30
31
  }
31
- // ─── Formatting Helpers ──────────────────────────────────────────────────────
32
+ // ─── Formatting & Parsing Helpers ───────────────────────────────────────────
33
+ function normalizeFilePath(p) {
34
+ let normalized = p;
35
+ if (process.platform === "win32" && /^\/[a-zA-Z]:/.test(normalized)) {
36
+ normalized = normalized.slice(1);
37
+ }
38
+ return path.normalize(normalized);
39
+ }
40
+ function extractDiagnostics(text) {
41
+ const diagnostics = [];
42
+ const startIdx = Math.min(text.indexOf('[') === -1 ? Infinity : text.indexOf('['), text.indexOf('{') === -1 ? Infinity : text.indexOf('{'));
43
+ if (startIdx !== -1 && startIdx !== Infinity) {
44
+ const potentialJson = text.slice(startIdx).trim();
45
+ try {
46
+ let cleanedJson = potentialJson;
47
+ if (cleanedJson.endsWith('```')) {
48
+ cleanedJson = cleanedJson.slice(0, -3).trim();
49
+ }
50
+ const parsed = JSON.parse(cleanedJson);
51
+ const items = Array.isArray(parsed) ? parsed : [parsed];
52
+ for (const item of items) {
53
+ if (item && typeof item === 'object' && item.resource && item.message) {
54
+ diagnostics.push({
55
+ resource: String(item.resource),
56
+ message: String(item.message),
57
+ startLineNumber: Number(item.startLineNumber || item.line || 1),
58
+ endLineNumber: item.endLineNumber ? Number(item.endLineNumber) : undefined
59
+ });
60
+ }
61
+ }
62
+ }
63
+ catch {
64
+ try {
65
+ const resourceMatch = text.match(/"resource"\s*:\s*"([^"]+)"/);
66
+ const messageMatch = text.match(/"message"\s*:\s*"([^"]+)"/);
67
+ const lineMatch = text.match(/"startLineNumber"\s*:\s*(\d+)/) || text.match(/"line"\s*:\s*(\d+)/);
68
+ if (resourceMatch && messageMatch) {
69
+ diagnostics.push({
70
+ resource: resourceMatch[1],
71
+ message: messageMatch[1].replace(/\\n/g, '\n'),
72
+ startLineNumber: lineMatch ? parseInt(lineMatch[1], 10) : 1
73
+ });
74
+ }
75
+ }
76
+ catch { }
77
+ }
78
+ }
79
+ return diagnostics;
80
+ }
32
81
  function getToolBadge(name) {
33
82
  const blue = chalk.hex('#0a84ff');
34
83
  const amber = chalk.hex('#ff9f0a');
@@ -134,7 +183,6 @@ function formatResponseText(text) {
134
183
  // ─── Extract Text Tool Calls ──────────────────────────────────────────────────
135
184
  function extractTextToolCalls(content) {
136
185
  const calls = [];
137
- // Pattern 1: <function(name)> {args} </function>
138
186
  const pattern1 = /<function\((\w+)\)>([\s\S]*?)(?:<\/function>)?/g;
139
187
  let match;
140
188
  while ((match = pattern1.exec(content)) !== null) {
@@ -151,7 +199,6 @@ function extractTextToolCalls(content) {
151
199
  }
152
200
  catch { }
153
201
  }
154
- // Pattern 1b: <function(name){args}></function>
155
202
  const pattern1b = /<function\((\w+)\)({[\s\S]*?})>(?:<\/function>)?/g;
156
203
  pattern1b.lastIndex = 0;
157
204
  while ((match = pattern1b.exec(content)) !== null) {
@@ -170,7 +217,6 @@ function extractTextToolCalls(content) {
170
217
  }
171
218
  catch { }
172
219
  }
173
- // Pattern 2: <function=name>{args}</function>
174
220
  const pattern2 = /<function=(\w+)>({[\s\S]*?})<\/function>/g;
175
221
  pattern2.lastIndex = 0;
176
222
  while ((match = pattern2.exec(content)) !== null) {
@@ -250,14 +296,49 @@ export class Agent {
250
296
  }
251
297
  async chat(userMessage) {
252
298
  await this.memory.init(this.memoryScope, "coder");
253
- this.memory.add({ role: "user", content: userMessage });
299
+ // ── Phase 1: Input & Enriched Context Pre-Parsing ──────────────────────
300
+ console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
301
+ startSpinner("thinking...");
302
+ const diagnostics = extractDiagnostics(userMessage);
303
+ let enrichedPrompt = userMessage;
304
+ if (diagnostics.length > 0) {
305
+ updateSpinner("resolving files & context...");
306
+ const contexts = [];
307
+ for (const diag of diagnostics) {
308
+ const filePath = normalizeFilePath(diag.resource);
309
+ try {
310
+ const content = await fs.readFile(filePath, "utf-8");
311
+ const lines = content.split(/\r?\n/);
312
+ const startLine = Math.max(1, diag.startLineNumber - 10);
313
+ const endLine = Math.min(lines.length, (diag.endLineNumber || diag.startLineNumber) + 10);
314
+ const slice = lines.slice(startLine - 1, endLine);
315
+ const numberedLines = slice.map((line, idx) => `${startLine + idx}: ${line}`);
316
+ contexts.push(`File contents of ${filePath} (around line ${diag.startLineNumber}):\n` + numberedLines.join("\n"));
317
+ }
318
+ catch (err) {
319
+ // If the absolute path doesn't exist directly (e.g. running in virtual workspaces), try to resolve it relative to current cwd
320
+ try {
321
+ const relativePath = path.relative("/", filePath).replace(/^[a-zA-Z]:/, "").replace(/^\\+|^[//]+/, "");
322
+ const localContent = await fs.readFile(relativePath, "utf-8");
323
+ const lines = localContent.split(/\r?\n/);
324
+ const startLine = Math.max(1, diag.startLineNumber - 10);
325
+ const endLine = Math.min(lines.length, (diag.endLineNumber || diag.startLineNumber) + 10);
326
+ const slice = lines.slice(startLine - 1, endLine);
327
+ const numberedLines = slice.map((line, idx) => `${startLine + idx}: ${line}`);
328
+ contexts.push(`File contents of resolved path ${relativePath} (around line ${diag.startLineNumber}):\n` + numberedLines.join("\n"));
329
+ }
330
+ catch {
331
+ contexts.push(`[File Context: failed to read path ${filePath} - ${err.message}]`);
332
+ }
333
+ }
334
+ }
335
+ enrichedPrompt += "\n\n=== Enriched Code Context (Auto-Parsed) ===\n" + contexts.join("\n\n") + "\n===========================================";
336
+ }
337
+ this.memory.add({ role: "user", content: enrichedPrompt });
254
338
  let iterations = 0;
255
339
  const MAX_ITERATIONS = 12;
256
340
  const modifiedFiles = new Set();
257
341
  let cleanContent = "";
258
- // ── Phase 1: Input to Thinking ──────────────────────────────────────────
259
- console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
260
- startSpinner("thinking...");
261
342
  while (iterations < MAX_ITERATIONS) {
262
343
  iterations++;
263
344
  updateSpinner("thinking...");
package/dist/index.js CHANGED
@@ -172,13 +172,26 @@ async function main() {
172
172
  output: process.stdout,
173
173
  terminal: true,
174
174
  });
175
- const prompt = () => {
176
- rl.question(chalk.hex('#0a84ff')('›') + ' ', async (input) => {
177
- const trimmed = input.trim();
175
+ let inputBuffer = "";
176
+ let pasteTimeout = null;
177
+ rl.setPrompt(chalk.hex('#0a84ff')('›') + ' ');
178
+ rl.prompt();
179
+ rl.on("line", (line) => {
180
+ inputBuffer += (inputBuffer ? "\n" : "") + line;
181
+ if (pasteTimeout) {
182
+ clearTimeout(pasteTimeout);
183
+ }
184
+ pasteTimeout = setTimeout(async () => {
185
+ pasteTimeout = null;
186
+ const accumulatedInput = inputBuffer;
187
+ inputBuffer = "";
188
+ const trimmed = accumulatedInput.trim();
178
189
  if (!trimmed) {
179
- prompt();
190
+ rl.prompt();
180
191
  return;
181
192
  }
193
+ // Pause standard input processing during agent thinking & updates
194
+ rl.pause();
182
195
  // Built-in slash commands
183
196
  if (trimmed === "/exit" || trimmed === "/quit") {
184
197
  rl.close();
@@ -187,12 +200,14 @@ async function main() {
187
200
  if (trimmed === "/clear") {
188
201
  agent.clearMemory();
189
202
  console.log(chalk.hex('#30d158')('✓') + ' ' + chalk.gray('Memory cleared'));
190
- prompt();
203
+ rl.resume();
204
+ rl.prompt();
191
205
  return;
192
206
  }
193
207
  if (trimmed === "/status") {
194
208
  console.log(chalk.dim(`session · ${agent.memoryStatus()}`));
195
- prompt();
209
+ rl.resume();
210
+ rl.prompt();
196
211
  return;
197
212
  }
198
213
  if (trimmed.startsWith("/model")) {
@@ -212,7 +227,8 @@ async function main() {
212
227
  console.log(chalk.dim(` Model must be one of: ${VALID_MODELS.join(" · ")}`));
213
228
  }
214
229
  }
215
- prompt();
230
+ rl.resume();
231
+ rl.prompt();
216
232
  return;
217
233
  }
218
234
  if (trimmed === "/help") {
@@ -225,7 +241,8 @@ async function main() {
225
241
  console.log(chalk.gray(` • Stored at: ~/.coder-config.json
226
242
  • To change key: Exit and run 'coder-agent --set-key <key>'
227
243
  • Env variable option: GEMINI_API_KEY`));
228
- prompt();
244
+ rl.resume();
245
+ rl.prompt();
229
246
  return;
230
247
  }
231
248
  try {
@@ -243,14 +260,14 @@ async function main() {
243
260
  console.log(chalk.dim(` ${err.message}`));
244
261
  }
245
262
  }
246
- prompt();
247
- });
248
- };
263
+ rl.resume();
264
+ rl.prompt();
265
+ }, 80);
266
+ });
249
267
  // Handle Ctrl+C gracefully
250
268
  rl.on("SIGINT", () => {
251
269
  process.exit(0);
252
270
  });
253
- prompt();
254
271
  }
255
272
  main().catch((err) => {
256
273
  console.error(chalk.hex('#ff453a')('✕ error'));
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "coder-agent",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "CLI coding agent powered by Google Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
+ "coder-agent": "dist/index.js",
8
9
  "coder": "dist/index.js",
9
10
  "gemini-agent": "dist/index.js",
10
11
  "groq-agent": "dist/index.js"