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 +89 -8
- package/dist/index.js +29 -12
- package/package.json +2 -1
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
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|