aether-code 0.28.0 → 0.29.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/bin/aether-code.js +1 -1
- package/package.json +1 -1
- package/src/agent.js +12 -0
- package/src/api.js +65 -32
- package/src/repl.js +1 -1
package/bin/aether-code.js
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import readline from "node:readline";
|
|
29
29
|
import { c, errorLine, divider, setTerminalTitle } from "../src/render.js";
|
|
30
30
|
|
|
31
|
-
const VERSION = "0.
|
|
31
|
+
const VERSION = "0.29.0";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Try to start MCP servers from ~/.aether/mcp.json. Returns a started
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
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
|
@@ -18,6 +18,17 @@ function envContext(cwd) {
|
|
|
18
18
|
const home = os.homedir();
|
|
19
19
|
const desktop = path.join(home, "Desktop");
|
|
20
20
|
const documents = path.join(home, "Documents");
|
|
21
|
+
const win = process.platform === "win32";
|
|
22
|
+
// OS-specific shell guidance: gemma tends to emit Unix commands (mkdir -p,
|
|
23
|
+
// touch, &&-chains) which FAIL in Windows cmd.exe ("The syntax of the command
|
|
24
|
+
// is incorrect"). Steer it to the file tools (which are cross-platform and
|
|
25
|
+
// auto-create parent dirs) and OS-appropriate shell usage.
|
|
26
|
+
const shellNote = win
|
|
27
|
+
? `shell: Windows cmd.exe. To create files OR directories, use the write_file tool — ` +
|
|
28
|
+
`it creates any missing parent folders automatically. Do NOT use shell "mkdir -p", ` +
|
|
29
|
+
`"touch", "ls", "rm", "cp", "mv", or "&&"-chained Unix commands; they FAIL in cmd.exe. ` +
|
|
30
|
+
`Run programs/tests with their interpreter (python, node, java, etc.).`
|
|
31
|
+
: `shell: POSIX sh. Standard Unix commands are available. write_file still auto-creates parent dirs.`;
|
|
21
32
|
return (
|
|
22
33
|
`[environment]\n` +
|
|
23
34
|
`os: ${process.platform}\n` +
|
|
@@ -25,6 +36,7 @@ function envContext(cwd) {
|
|
|
25
36
|
`home: ${home}\n` +
|
|
26
37
|
`desktop: ${desktop}\n` +
|
|
27
38
|
`documents: ${documents}\n` +
|
|
39
|
+
`${shellNote}\n` +
|
|
28
40
|
`When the user names a location ("my desktop", "home", "documents"), write to ` +
|
|
29
41
|
`the matching ABSOLUTE path above (e.g. desktop -> ${desktop}). Otherwise ` +
|
|
30
42
|
`work under the cwd. Use absolute paths when a specific location is named.\n` +
|
package/src/api.js
CHANGED
|
@@ -33,6 +33,9 @@ export async function fetchWithRetry(url, options, { retries = 2, baseDelay = 50
|
|
|
33
33
|
try {
|
|
34
34
|
const res = await fetch(url, options);
|
|
35
35
|
if (res.status >= 500 && attempt < retries) {
|
|
36
|
+
// Release the connection back to the pool before retrying — an
|
|
37
|
+
// unconsumed body keeps the socket open (undici won't reuse it).
|
|
38
|
+
res.body?.cancel().catch(() => {});
|
|
36
39
|
attempt++;
|
|
37
40
|
if (onRetry) onRetry(attempt, `HTTP ${res.status}`);
|
|
38
41
|
await sleep(baseDelay * 2 ** (attempt - 1));
|
|
@@ -175,39 +178,69 @@ export async function agentTurnStream({
|
|
|
175
178
|
const reader = res.body.getReader();
|
|
176
179
|
const decoder = new TextDecoder();
|
|
177
180
|
let buffer = "";
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
181
|
+
|
|
182
|
+
// Dispatch a single parsed SSE "data:" line.
|
|
183
|
+
const handleLine = (line) => {
|
|
184
|
+
if (!line.startsWith("data:")) return;
|
|
185
|
+
const json = line.slice(5).trim(); // .trim() also drops a CRLF trailing \r
|
|
186
|
+
if (!json) return;
|
|
187
|
+
let evt;
|
|
188
|
+
try { evt = JSON.parse(json); } catch { return; }
|
|
189
|
+
if (evt.kind === "delta") {
|
|
190
|
+
textBuf += evt.content;
|
|
191
|
+
if (onDelta) onDelta(evt.content);
|
|
192
|
+
} else if (evt.kind === "tool_call_delta") {
|
|
193
|
+
const slot = toolBuf.get(evt.index) || { id: undefined, name: undefined, args: "" };
|
|
194
|
+
if (evt.id) slot.id = evt.id;
|
|
195
|
+
if (evt.name) slot.name = evt.name;
|
|
196
|
+
if (evt.args_delta) slot.args += evt.args_delta;
|
|
197
|
+
toolBuf.set(evt.index, slot);
|
|
198
|
+
if (onToolCallDelta) onToolCallDelta(evt);
|
|
199
|
+
} else if (evt.kind === "finish") {
|
|
200
|
+
finishReason = evt.reason;
|
|
201
|
+
if (onFinish) onFinish(evt.reason);
|
|
202
|
+
} else if (evt.kind === "done") {
|
|
203
|
+
creditsCharged = evt.creditsCharged ?? 0;
|
|
204
|
+
balanceAfter = evt.balanceAfter ?? null;
|
|
205
|
+
usage = evt.usage ?? usage;
|
|
206
|
+
} else if (evt.kind === "error") {
|
|
207
|
+
streamError = evt.error;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Watchdog: if the open stream stalls (no bytes) for too long, abort instead
|
|
212
|
+
// of hanging the CLI forever.
|
|
213
|
+
const READ_TIMEOUT_MS = 120_000;
|
|
214
|
+
const readOnce = () => {
|
|
215
|
+
let timer;
|
|
216
|
+
const timeout = new Promise((_, reject) => {
|
|
217
|
+
timer = setTimeout(
|
|
218
|
+
() => reject(new AetherError("Stream stalled — no data for 120s", "STREAM_TIMEOUT", 0)),
|
|
219
|
+
READ_TIMEOUT_MS,
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
return Promise.race([reader.read(), timeout]).finally(() => clearTimeout(timer));
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
while (true) {
|
|
227
|
+
const { value, done } = await readOnce();
|
|
228
|
+
if (done) break;
|
|
229
|
+
buffer += decoder.decode(value, { stream: true });
|
|
230
|
+
const lines = buffer.split("\n");
|
|
231
|
+
buffer = lines.pop() ?? "";
|
|
232
|
+
for (const line of lines) handleLine(line);
|
|
210
233
|
}
|
|
234
|
+
// Flush any multibyte bytes the decoder is holding, then process a final
|
|
235
|
+
// line that arrived without a trailing newline (else the "done" event with
|
|
236
|
+
// credits/usage is silently dropped).
|
|
237
|
+
buffer += decoder.decode();
|
|
238
|
+
if (buffer.trim()) handleLine(buffer);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
reader.cancel().catch(() => {});
|
|
241
|
+
throw e;
|
|
242
|
+
} finally {
|
|
243
|
+
try { reader.releaseLock(); } catch { /* already released / pending — ignore */ }
|
|
211
244
|
}
|
|
212
245
|
|
|
213
246
|
if (streamError) {
|
package/src/repl.js
CHANGED
|
@@ -16,7 +16,7 @@ import { c, errorLine, boxLines, sideBySide } from "./render.js";
|
|
|
16
16
|
import { checkForUpdate } from "./update-check.js";
|
|
17
17
|
import { promptBox, EXIT_SIGNAL } from "./box-input.js";
|
|
18
18
|
|
|
19
|
-
const VERSION = "0.
|
|
19
|
+
const VERSION = "0.29.0";
|
|
20
20
|
const MODEL_NAME = "Aether Core";
|
|
21
21
|
|
|
22
22
|
const SHORTCUTS = `
|