coder-agent 2.7.2 → 2.8.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 +72 -0
- package/dist/config.js +13 -0
- package/dist/index.js +39 -4
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -180,6 +180,62 @@ function formatResponseText(text) {
|
|
|
180
180
|
return chalk.white(part);
|
|
181
181
|
}).join('');
|
|
182
182
|
}
|
|
183
|
+
function normalizeContentForLoopCheck(content) {
|
|
184
|
+
if (!content)
|
|
185
|
+
return "";
|
|
186
|
+
return content
|
|
187
|
+
.toLowerCase()
|
|
188
|
+
.replace(/\s+/g, " ")
|
|
189
|
+
.trim();
|
|
190
|
+
}
|
|
191
|
+
function normalizeToolCallsForLoopCheck(toolCalls) {
|
|
192
|
+
if (!toolCalls || toolCalls.length === 0)
|
|
193
|
+
return "";
|
|
194
|
+
return toolCalls.map(tc => {
|
|
195
|
+
const name = tc.function?.name || "";
|
|
196
|
+
let argsStr = "";
|
|
197
|
+
try {
|
|
198
|
+
const rawArgs = tc.function?.arguments;
|
|
199
|
+
const args = typeof rawArgs === 'string'
|
|
200
|
+
? JSON.parse(rawArgs)
|
|
201
|
+
: rawArgs;
|
|
202
|
+
if (args && typeof args === 'object') {
|
|
203
|
+
const sortedArgs = {};
|
|
204
|
+
for (const key of Object.keys(args).sort()) {
|
|
205
|
+
sortedArgs[key] = args[key];
|
|
206
|
+
}
|
|
207
|
+
argsStr = JSON.stringify(sortedArgs);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
argsStr = tc.function?.arguments || "";
|
|
212
|
+
}
|
|
213
|
+
return `${name}(${argsStr})`;
|
|
214
|
+
}).join(";");
|
|
215
|
+
}
|
|
216
|
+
function hasRepeatingCycle(history) {
|
|
217
|
+
const n = history.length;
|
|
218
|
+
for (let len = 1; len <= 4; len++) {
|
|
219
|
+
if (n >= len * 2) {
|
|
220
|
+
const minRepeats = len === 1 ? 3 : 2;
|
|
221
|
+
if (n >= len * minRepeats) {
|
|
222
|
+
let isLoop = true;
|
|
223
|
+
const lastBlock = history.slice(n - len);
|
|
224
|
+
for (let r = 1; r < minRepeats; r++) {
|
|
225
|
+
const prevBlock = history.slice(n - len * (r + 1), n - len * r);
|
|
226
|
+
if (JSON.stringify(lastBlock) !== JSON.stringify(prevBlock)) {
|
|
227
|
+
isLoop = false;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (isLoop) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
183
239
|
// ─── Extract Text Tool Calls ──────────────────────────────────────────────────
|
|
184
240
|
function extractTextToolCalls(content) {
|
|
185
241
|
const calls = [];
|
|
@@ -568,6 +624,7 @@ export class Agent {
|
|
|
568
624
|
const MAX_WAITS = 3;
|
|
569
625
|
const modifiedFiles = new Set();
|
|
570
626
|
let cleanContent = "";
|
|
627
|
+
const stateHistory = [];
|
|
571
628
|
while (true) {
|
|
572
629
|
if (signal?.aborted) {
|
|
573
630
|
const abortErr = new Error("The user aborted a request.");
|
|
@@ -707,6 +764,21 @@ export class Agent {
|
|
|
707
764
|
for (const line of statusLines) {
|
|
708
765
|
console.log(line);
|
|
709
766
|
}
|
|
767
|
+
// Loop detection & intervention
|
|
768
|
+
const currentKey = `${normalizeContentForLoopCheck(msg.content || "")}|${normalizeToolCallsForLoopCheck(toolCalls)}`;
|
|
769
|
+
const tempHistory = [...stateHistory, currentKey];
|
|
770
|
+
if (hasRepeatingCycle(tempHistory)) {
|
|
771
|
+
const warningMessage = `⚠️ [LOOP DETECTED] You are repeating the exact same thoughts or tool calls. Please break out of this loop. Do not repeat the same actions. Re-evaluate your strategy, look at a different file, run a different command, or ask the user for clarification if you cannot proceed.`;
|
|
772
|
+
console.log(chalk.hex('#ff9f0a')('\n⚠ Loop detected! Intervening to break the loop...'));
|
|
773
|
+
this.memory.add({
|
|
774
|
+
role: "user",
|
|
775
|
+
content: warningMessage,
|
|
776
|
+
});
|
|
777
|
+
stateHistory.length = 0; // Reset history to allow a fresh start
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
stateHistory.push(currentKey);
|
|
781
|
+
}
|
|
710
782
|
if (iterations < MAX_ITERATIONS) {
|
|
711
783
|
console.log(chalk.dim('\n' + '─'.repeat(48) + '\n'));
|
|
712
784
|
startSpinner("thinking...");
|
package/dist/config.js
CHANGED
|
@@ -80,3 +80,16 @@ export async function saveGeminiApiKey(geminiApiKey) {
|
|
|
80
80
|
config.geminiApiKey = geminiApiKey;
|
|
81
81
|
await writeConfig(config);
|
|
82
82
|
}
|
|
83
|
+
export async function getLastUsedInfo() {
|
|
84
|
+
const config = await readConfig();
|
|
85
|
+
return {
|
|
86
|
+
lastUsedVersion: config.lastUsedVersion,
|
|
87
|
+
lastUsedTime: config.lastUsedTime,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export async function saveLastUsedInfo(version, time) {
|
|
91
|
+
const config = await readConfig();
|
|
92
|
+
config.lastUsedVersion = version;
|
|
93
|
+
config.lastUsedTime = time;
|
|
94
|
+
await writeConfig(config);
|
|
95
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,8 @@ import * as readline from "readline";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import figlet from "figlet";
|
|
5
5
|
import { Agent } from "./agent.js";
|
|
6
|
-
import { getStoredApiKey, saveApiKey, getStoredModel, saveModel } from "./config.js";
|
|
6
|
+
import { getStoredApiKey, saveApiKey, getStoredModel, saveModel, getLastUsedInfo, saveLastUsedInfo } from "./config.js";
|
|
7
|
+
const CURRENT_VERSION = "2.7.3";
|
|
7
8
|
const VALID_MODELS = [
|
|
8
9
|
"gemini-2.5-flash",
|
|
9
10
|
"gemini-2.5-pro",
|
|
@@ -37,7 +38,7 @@ function printBanner(modelName) {
|
|
|
37
38
|
});
|
|
38
39
|
console.log('');
|
|
39
40
|
console.log(' ' + chalk.white.bold('coder-agent') +
|
|
40
|
-
chalk.dim('
|
|
41
|
+
chalk.dim(' v' + CURRENT_VERSION));
|
|
41
42
|
console.log('');
|
|
42
43
|
console.log(chalk.dim(' ─────────────────────────────────────'));
|
|
43
44
|
console.log('');
|
|
@@ -55,7 +56,7 @@ function printBanner(modelName) {
|
|
|
55
56
|
console.log('');
|
|
56
57
|
}
|
|
57
58
|
function printHelp() {
|
|
58
|
-
console.log(chalk.white.bold(
|
|
59
|
+
console.log(chalk.white.bold(`\n Coder CLI v${CURRENT_VERSION}\n`));
|
|
59
60
|
console.log(chalk.gray(" Usage:"));
|
|
60
61
|
console.log(chalk.white(" coder-agent — Start the interactive REPL"));
|
|
61
62
|
console.log(chalk.white(" coder-agent [options] — Run with config options"));
|
|
@@ -151,7 +152,7 @@ async function main() {
|
|
|
151
152
|
process.exit(0);
|
|
152
153
|
}
|
|
153
154
|
else if (args[i] === "-v" || args[i] === "--version") {
|
|
154
|
-
console.log(
|
|
155
|
+
console.log(`coder-agent v${CURRENT_VERSION}`);
|
|
155
156
|
process.exit(0);
|
|
156
157
|
}
|
|
157
158
|
else {
|
|
@@ -269,12 +270,46 @@ async function main() {
|
|
|
269
270
|
process.exit(0);
|
|
270
271
|
}
|
|
271
272
|
// Interactive REPL Mode
|
|
273
|
+
const lastUsedInfo = await getLastUsedInfo();
|
|
274
|
+
const INACTIVE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
275
|
+
const isNewUser = !lastUsedInfo.lastUsedTime;
|
|
276
|
+
const isInactive = lastUsedInfo.lastUsedTime ? (Date.now() - lastUsedInfo.lastUsedTime > INACTIVE_THRESHOLD_MS) : false;
|
|
277
|
+
const isNewVersion = lastUsedInfo.lastUsedVersion !== CURRENT_VERSION;
|
|
278
|
+
const showWelcomeCommands = isNewUser || isInactive || isNewVersion;
|
|
279
|
+
// Save/update last used info immediately
|
|
280
|
+
await saveLastUsedInfo(CURRENT_VERSION, Date.now());
|
|
272
281
|
printBanner(modelToUse);
|
|
282
|
+
if (showWelcomeCommands) {
|
|
283
|
+
console.log(chalk.white.bold(" 💡 Get started with interactive slash commands:"));
|
|
284
|
+
console.log(` ${chalk.hex('#0a84ff')('/model')} ${chalk.gray('[name]')} — View active model or switch to [name]`);
|
|
285
|
+
console.log(` ${chalk.hex('#0a84ff')('/clear')} — Wipe conversation memory`);
|
|
286
|
+
console.log(` ${chalk.hex('#0a84ff')('/status')} — Show active model and memory usage`);
|
|
287
|
+
console.log(` ${chalk.hex('#0a84ff')('/help')} — Show help screen with configuration details`);
|
|
288
|
+
console.log(` ${chalk.hex('#0a84ff')('/exit')} — Exit Coder`);
|
|
289
|
+
console.log();
|
|
290
|
+
}
|
|
273
291
|
rl = readline.createInterface({
|
|
274
292
|
input: process.stdin,
|
|
275
293
|
output: process.stdout,
|
|
276
294
|
terminal: true,
|
|
277
295
|
});
|
|
296
|
+
process.stdin.on("keypress", (char, key) => {
|
|
297
|
+
if (isHijacked || !rl)
|
|
298
|
+
return;
|
|
299
|
+
setImmediate(() => {
|
|
300
|
+
if (rl && rl.line === "/") {
|
|
301
|
+
console.log();
|
|
302
|
+
console.log(chalk.dim(" Available Commands:"));
|
|
303
|
+
console.log(` ${chalk.hex('#0a84ff')('/model')} ${chalk.gray('[name]')} ${chalk.dim('— View active model or switch to [name]')}`);
|
|
304
|
+
console.log(` ${chalk.hex('#0a84ff')('/clear')} ${chalk.dim('— Wipe conversation memory')}`);
|
|
305
|
+
console.log(` ${chalk.hex('#0a84ff')('/status')} ${chalk.dim('— Show active model and memory usage')}`);
|
|
306
|
+
console.log(` ${chalk.hex('#0a84ff')('/help')} ${chalk.dim('— Show help screen')}`);
|
|
307
|
+
console.log(` ${chalk.hex('#0a84ff')('/exit')} ${chalk.dim('— Exit Coder')}`);
|
|
308
|
+
console.log();
|
|
309
|
+
rl._refreshLine();
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
278
313
|
let inputBuffer = "";
|
|
279
314
|
let pasteTimeout = null;
|
|
280
315
|
let lineCountInBurst = 0;
|