aether-code 0.43.0 → 0.43.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/package.json +1 -1
- package/src/agent.js +39 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-code",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.2",
|
|
4
4
|
"description": "Uncensored AI coding agent for your terminal — Claude Code alternative with MCP support. Reads code, writes files, runs commands. Drives IDA Pro, Roblox Studio, Wireshark, Blender, and any MCP server. No refusal layer.",
|
|
5
5
|
"homepage": "https://trynoguard.com",
|
|
6
6
|
"repository": {
|
package/src/agent.js
CHANGED
|
@@ -13,6 +13,9 @@ import { scanProject, detectVerifyCommand, detectBuildCommand } from "./project-
|
|
|
13
13
|
import { c, divider, turn, toolLabel, toolSummary, makeTokenStripper, errorLine, startSpinner } from "./render.js";
|
|
14
14
|
|
|
15
15
|
const DEFAULT_MAX_TURNS = 25;
|
|
16
|
+
const DEFAULT_MAX_CREDITS = 500;
|
|
17
|
+
const MAX_TOTAL_TOOL_CALLS = 40;
|
|
18
|
+
const MAX_CONSECUTIVE_TOOL_ONLY = 6;
|
|
16
19
|
|
|
17
20
|
// Rolling history window (messages) sent upstream per turn. The session keeps
|
|
18
21
|
// the full transcript locally, but we only forward the anchor (first message,
|
|
@@ -86,6 +89,7 @@ export async function runAgent({
|
|
|
86
89
|
autoYes = false,
|
|
87
90
|
unsafePaths = false,
|
|
88
91
|
maxTurns = DEFAULT_MAX_TURNS,
|
|
92
|
+
maxCredits = DEFAULT_MAX_CREDITS,
|
|
89
93
|
model = null, // null = server default (gemma). Premium models gated server-side.
|
|
90
94
|
onTokens = () => {},
|
|
91
95
|
// Optional MCPManager. When provided, its tools are merged into the agent's
|
|
@@ -141,6 +145,8 @@ export async function runAgent({
|
|
|
141
145
|
let appliedNothingNudges = 0;
|
|
142
146
|
let verifyRounds = 0;
|
|
143
147
|
let buildRounds = 0;
|
|
148
|
+
let consecutiveToolOnlyTurns = 0;
|
|
149
|
+
let totalToolCalls = 0;
|
|
144
150
|
|
|
145
151
|
for (let i = 0; i < maxTurns; i++) {
|
|
146
152
|
// No turn header and no leading blank here — each step (assistant text and
|
|
@@ -215,13 +221,17 @@ export async function runAgent({
|
|
|
215
221
|
process.stdout.write(tail);
|
|
216
222
|
}
|
|
217
223
|
if (lastWasText) process.stdout.write("\n");
|
|
218
|
-
|
|
224
|
+
const turnCredits = res.creditsCharged ?? 0;
|
|
225
|
+
totalCredits += turnCredits;
|
|
219
226
|
totalIn += res.usage?.prompt_tokens ?? 0;
|
|
220
227
|
totalOut += res.usage?.completion_tokens ?? 0;
|
|
221
228
|
if (typeof res.balanceAfter === "number") lastBalance = res.balanceAfter;
|
|
222
229
|
onTokens({ totalCredits, totalIn, totalOut, balance: lastBalance });
|
|
223
|
-
|
|
224
|
-
//
|
|
230
|
+
|
|
231
|
+
// Per-turn cost — visible so users catch runaway loops early
|
|
232
|
+
const inK = ((res.usage?.prompt_tokens ?? 0) / 1000).toFixed(1);
|
|
233
|
+
const outTok = res.usage?.completion_tokens ?? 0;
|
|
234
|
+
console.log(c.gray(` ${inK}k→${outTok} tokens · ${turnCredits} credits (total: ${totalCredits})`));
|
|
225
235
|
|
|
226
236
|
// Push assistant message into history
|
|
227
237
|
messages.push({
|
|
@@ -231,6 +241,31 @@ export async function runAgent({
|
|
|
231
241
|
});
|
|
232
242
|
|
|
233
243
|
const toolCalls = res.message.tool_calls ?? [];
|
|
244
|
+
const hasTextContent = !!(res.message.content && res.message.content.trim());
|
|
245
|
+
|
|
246
|
+
if (toolCalls.length > 0 && !hasTextContent) {
|
|
247
|
+
consecutiveToolOnlyTurns++;
|
|
248
|
+
} else if (hasTextContent) {
|
|
249
|
+
consecutiveToolOnlyTurns = 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Credit cap — hard stop to prevent runaway cost
|
|
253
|
+
if (maxCredits > 0 && totalCredits >= maxCredits) {
|
|
254
|
+
console.log(c.red(`\n[Credit budget exhausted: ${totalCredits} credits used, limit was ${maxCredits}. Stopping.]`));
|
|
255
|
+
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Tool call count cap
|
|
259
|
+
if (toolCalls.length > 0 && totalToolCalls + toolCalls.length > MAX_TOTAL_TOOL_CALLS) {
|
|
260
|
+
console.log(c.red(`\n[Tool call limit reached (${MAX_TOTAL_TOOL_CALLS}). Stopping.]`));
|
|
261
|
+
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Consecutive tool-only turns cap (model stuck in a tool loop with no text)
|
|
265
|
+
if (consecutiveToolOnlyTurns >= MAX_CONSECUTIVE_TOOL_ONLY) {
|
|
266
|
+
console.log(c.red(`\n[Too many consecutive tool-only turns (${MAX_CONSECUTIVE_TOOL_ONLY}). Stopping.]`));
|
|
267
|
+
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages };
|
|
268
|
+
}
|
|
234
269
|
if (toolCalls.length === 0) {
|
|
235
270
|
// Completeness guard: on a modification task, if the model read files it
|
|
236
271
|
// never edited, it likely either described the fix instead of applying it
|
|
@@ -343,6 +378,7 @@ export async function runAgent({
|
|
|
343
378
|
|
|
344
379
|
// Execute each tool call. Show the actual args (now that we have them
|
|
345
380
|
// fully assembled) and run.
|
|
381
|
+
totalToolCalls += toolCalls.length;
|
|
346
382
|
for (const call of toolCalls) {
|
|
347
383
|
let args = {};
|
|
348
384
|
try { args = JSON.parse(call.function.arguments || "{}"); } catch { /* leave empty */ }
|