zames_pro 2.80.4 → 2.80.8

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/CHANGELOG.md CHANGED
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.80.8] - 2026-10-20
11
+
12
+ ### Changed
13
+
14
+ - Internal improvements.
15
+
16
+ ## [2.80.7] - 2026-10-20
17
+
18
+ ### Fixed
19
+
20
+ - stop filling the disk with temp dirs, coverage dumps and unbounded logs
21
+ - word delete and word-left stop at a newline
22
+
23
+ ## [2.80.6] - 2026-10-20
24
+
25
+ ### Fixed
26
+
27
+ - stop filling the disk with temp dirs, coverage dumps and unbounded logs
28
+ - word delete and word-left stop at a newline
29
+
30
+ ## [2.80.5] - 2026-10-20
31
+
32
+ ### Fixed
33
+
34
+ - a JSON array line in a transcript is not an event
35
+
10
36
  ## [2.80.4] - 2026-10-20
11
37
 
12
38
  ### Fixed
package/README.md CHANGED
@@ -124,6 +124,10 @@ Key pieces:
124
124
 
125
125
  ## Why zames?
126
126
 
127
+ - **Built with itself** — `zames_pro` is developed by `zames_pro`. No other
128
+ coding agent is used on this repository: the agent in this very terminal
129
+ reads the sources, runs the tests, fixes the bugs and commits the result.
130
+ That is the daily proof that it works and is actually used.
127
131
  - **No API key, no per-token bill** — it uses your own DeepSeek or Qwen chat
128
132
  account, not the paid API. Good for long, tool-heavy tasks.
129
133
  - **Same workflow as Claude Code / Codex** — tools, `AGENTS.md`, skills, MCP
@@ -707,6 +711,10 @@ zames не вызывает API модели. Он запускает headless C
707
711
 
708
712
  ## Почему zames?
709
713
 
714
+ - **Разрабатывается сам собой** — над `zames_pro` работает `zames_pro`, а не
715
+ какой-то другой кодинг-агент: агент в этом самом терминале читает исходники,
716
+ гоняет тесты, чинит баги и коммитит результат. Это ежедневное доказательство,
717
+ что он работает и реально используется.
710
718
  - **Нет API-ключа и счёта за токены** — используется ваш собственный аккаунт
711
719
  чата DeepSeek или Qwen, а не платный API. Удобно для долгих задач с
712
720
  инструментами.
package/dist/commands.js CHANGED
@@ -143,7 +143,9 @@ export function parseTranscript(body) {
143
143
  continue;
144
144
  try {
145
145
  const v = JSON.parse(t);
146
- if (v && typeof v === 'object')
146
+ // Only a plain OBJECT is an event: an ARRAY (a stray JSON array line in
147
+ // a hand-edited/merged transcript) must not be treated as one.
148
+ if (v && typeof v === 'object' && !Array.isArray(v))
147
149
  out.push(v);
148
150
  }
149
151
  catch {
@@ -348,19 +348,72 @@ export function extractTokenUsage(body) {
348
348
  return found;
349
349
  }
350
350
  // Saves the DeepSeek network response body to disk for post-mortem analysis.
351
- // The files live in ~/.zames/net-log — from them the real answer format is visible.
352
- // DEBUG ONLY: disabled unless ZAMES_NET_DEBUG=1. It writes a file per network
353
- // response (thousands of files / tens of MB) and is not needed for the agent
354
- // to work — the answer is taken from extractAnswer() in memory.
351
+ // DEBUG ONLY: disabled unless ZAMES_NET_DEBUG=1.
352
+ //
353
+ // The dumps live in ~/.zames/net-log and one file PER RESPONSE is written, so
354
+ // an unattended `npm run dev` (which sets ZAMES_NET_DEBUG=1) used to grow the
355
+ // directory without bound: 77k files / 800 MB after a few days. On WSL that
356
+ // directory sits inside ext4.vhdx, and the space of thousands of tiny deleted
357
+ // files is never returned to Windows — the host disk fills up. So the log is
358
+ // ROTATED here: `NET_LOG_KEEP` newest dumps are kept and a rotation runs at
359
+ // most once every `NET_LOG_ROTATE_MS` (listing the dir on every response would
360
+ // itself cost time on a huge directory).
361
+ const NET_LOG_KEEP = 200;
362
+ const NET_LOG_ROTATE_MS = 5 * 60_000;
363
+ const NET_LOG_MAX_BYTES = 32 * 1024 * 1024;
364
+ let netLogDir = null;
365
+ let netLogLastRotate = 0;
366
+ function netLogDirectory() {
367
+ if (netLogDir === null) {
368
+ netLogDir = path.join(os.homedir(), '.zames', 'net-log');
369
+ }
370
+ return netLogDir;
371
+ }
372
+ /** A stamp prefix (`YYYY-MM-DDTHH-MM-SS-mmmZ_`) sorts chronologically. */
373
+ function netLogStamp() {
374
+ return new Date().toISOString().replace(/[:.]/g, '-');
375
+ }
355
376
  export async function dumpNetBody(url, body) {
356
377
  if (!process.env.ZAMES_NET_DEBUG)
357
378
  return;
358
379
  try {
359
- const dir = path.join(os.homedir(), '.zames', 'net-log');
380
+ const dir = netLogDirectory();
360
381
  await fs.mkdir(dir, { recursive: true });
361
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
362
382
  const safe = url.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 80);
363
- await fs.writeFile(path.join(dir, stamp + '_' + safe + '.txt'), body, 'utf-8');
383
+ await fs.writeFile(path.join(dir, netLogStamp() + '_' + safe + '.txt'), body.slice(0, NET_LOG_MAX_BYTES), 'utf-8');
384
+ await rotateNetLogThrottled(dir);
364
385
  }
365
386
  catch { }
366
387
  }
388
+ /**
389
+ * Keeps only the newest `NET_LOG_KEEP` dumps. Exported for the test: the
390
+ * rotation is the only thing standing between `ZAMES_NET_DEBUG=1` and an
391
+ * unbounded directory, so it is checked directly.
392
+ */
393
+ export async function rotateNetLog(dir, keep = NET_LOG_KEEP, now = Date.now()) {
394
+ // The stamp prefix sorts the names chronologically, so a plain sort is
395
+ // enough — no stat() per file (a directory with 77k entries would make
396
+ // 77k syscalls on every response).
397
+ const names = (await fs.readdir(dir).catch(() => [])).filter((n) => n.endsWith('.txt'));
398
+ if (names.length <= keep)
399
+ return 0;
400
+ names.sort();
401
+ const stale = names.slice(0, names.length - keep);
402
+ let removed = 0;
403
+ for (const n of stale) {
404
+ await fs
405
+ .rm(path.join(dir, n), { force: true })
406
+ .then(() => removed++)
407
+ .catch(() => { });
408
+ }
409
+ netLogLastRotate = now;
410
+ return removed;
411
+ }
412
+ /** Throttled rotation used by dumpNetBody (at most once per interval). */
413
+ async function rotateNetLogThrottled(dir) {
414
+ const now = Date.now();
415
+ if (now - netLogLastRotate < NET_LOG_ROTATE_MS)
416
+ return;
417
+ netLogLastRotate = now;
418
+ await rotateNetLog(dir, NET_LOG_KEEP, now);
419
+ }
@@ -1,5 +1,36 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ // A transcript file is appended per RUN and never deleted, so ~/.zames/logs
4
+ // grew without bound (177 files / 138 MB observed). On WSL the directory sits
5
+ // inside ext4.vhdx: the space of deleted files is not returned to the Windows
6
+ // host, so a big log dir is also a host-disk problem. Rotate on start-up: keep
7
+ // the newest MAX_TRANSCRIPT_FILES and drop the rest.
8
+ const MAX_TRANSCRIPT_FILES = 200;
9
+ // The constructor is SYNCHRONOUS (it opens the stream right away), so the
10
+ // rotation that must happen before the create has to be sync as well.
11
+ export function rotateTranscripts(dir, keep = MAX_TRANSCRIPT_FILES) {
12
+ let names;
13
+ try {
14
+ names = fs.readdirSync(dir).filter((n) => n.endsWith('.jsonl'));
15
+ }
16
+ catch {
17
+ // A missing/unreadable dir must not break the run: the transcript is
18
+ // created right after and mkdirSync has its own error handling.
19
+ return 0;
20
+ }
21
+ if (names.length <= keep)
22
+ return 0;
23
+ names.sort();
24
+ let removed = 0;
25
+ for (const n of names.slice(0, names.length - keep)) {
26
+ try {
27
+ fs.rmSync(path.join(dir, n), { force: true });
28
+ removed++;
29
+ }
30
+ catch { }
31
+ }
32
+ return removed;
33
+ }
3
34
  function transcriptErrorLine(e) {
4
35
  return `transcript: failed to create file: ${e.message}`;
5
36
  }
@@ -27,6 +58,9 @@ export class Transcript {
27
58
  const safeName = String(sessionName)
28
59
  .replace(/[\\/:*?"<>|\u0000-\u001f]/g, '_')
29
60
  .trim() || 'session';
61
+ // Rotation BEFORE the create: the new file then has the newest stamp,
62
+ // so the current run's transcript is never a deletion candidate.
63
+ rotateTranscripts(dir);
30
64
  this.file = path.join(dir, `${safeName}-${stamp}.jsonl`);
31
65
  this.stream = fs.createWriteStream(this.file, { flags: 'a' });
32
66
  // Write errors (disk full, file deleted, etc.) arrive as an 'error'
package/dist/tui/edit.js CHANGED
@@ -74,9 +74,12 @@ export function del(s) {
74
74
  export function deleteWordLeft(s) {
75
75
  const chars = charsOf(s);
76
76
  let i = s.cursor;
77
- while (i > 0 && /\s/.test(chars[i - 1]))
77
+ // A NEWLINE is a hard boundary: at the start of a line the key does
78
+ // nothing instead of eating the break and the previous line's word
79
+ // (deleteWordRight and wordLeft already stop at a newline).
80
+ while (i > 0 && chars[i - 1] !== NL && /\s/.test(chars[i - 1]))
78
81
  i--;
79
- while (i > 0 && !/\s/.test(chars[i - 1]))
82
+ while (i > 0 && chars[i - 1] !== NL && !/\s/.test(chars[i - 1]))
80
83
  i--;
81
84
  chars.splice(i, s.cursor - i);
82
85
  return { ...s, buf: chars.join(''), cursor: i };
@@ -137,9 +140,11 @@ export function moveEnd(s) {
137
140
  export function wordLeft(s) {
138
141
  const chars = charsOf(s);
139
142
  let i = s.cursor;
140
- while (i > 0 && /\s/.test(chars[i - 1]))
143
+ // Stop at a newline (mirrors deleteWordLeft/deleteWordRight): the cursor
144
+ // must not jump to the previous LINE, only to the previous word.
145
+ while (i > 0 && chars[i - 1] !== NL && /\s/.test(chars[i - 1]))
141
146
  i--;
142
- while (i > 0 && !/\s/.test(chars[i - 1]))
147
+ while (i > 0 && chars[i - 1] !== NL && !/\s/.test(chars[i - 1]))
143
148
  i--;
144
149
  return i === s.cursor ? s : { ...s, cursor: i };
145
150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.80.4",
3
+ "version": "2.80.8",
4
4
  "description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen and other web LLMs — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
5
5
  "type": "module",
6
6
  "bin": {