@yeaft/webchat-agent 0.1.949 → 0.1.950

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.949",
3
+ "version": "0.1.950",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/yeaft/cli.js CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Features:
9
9
  * --dry-run "prompt" — Assemble system prompt + messages, don't call LLM
10
- * --trace stats|recent|search <keyword> — Query debug.db
10
+ * --trace stats|recent|search <keyword>|tools|compact — Query/maintain debug.db
11
11
  * -i / --interactive — REPL mode with / commands
12
12
  * <prompt> — One-shot query (Phase 1: engine.query)
13
13
  * --skip-mcp — Skip MCP server connections (faster startup)
@@ -173,8 +173,17 @@ function handleTraceQuery(args, config) {
173
173
  }
174
174
  break;
175
175
  }
176
+ case 'compact': {
177
+ const s = trace.stats();
178
+ console.log(`Compacting debug database (${(s.dbSizeBytes / 1048576).toFixed(1)} MB, ${s.turnCount} turns)...`);
179
+ console.log('This rebuilds the file and may take a while on a large DB. Do not interrupt.');
180
+ const { before, after } = trace.compact();
181
+ const saved = Math.max(0, before - after);
182
+ console.log(`Done. ${(before / 1048576).toFixed(1)} MB → ${(after / 1048576).toFixed(1)} MB (reclaimed ${(saved / 1048576).toFixed(1)} MB).`);
183
+ break;
184
+ }
176
185
  default:
177
- throw new Error(`Unknown trace command: ${args.trace}. Available: stats, recent, search <keyword>, tools [name]`);
186
+ throw new Error(`Unknown trace command: ${args.trace}. Available: stats, recent, search <keyword>, tools [name], compact`);
178
187
  }
179
188
  } finally {
180
189
  trace.close();
@@ -176,6 +176,14 @@ export class DebugTrace {
176
176
  constructor(dbPath) {
177
177
  this.#dbPath = dbPath;
178
178
  this.#db = new DatabaseSync(dbPath);
179
+ // INCREMENTAL auto-vacuum lets cleanup() return freed pages to the OS via
180
+ // `PRAGMA incremental_vacuum` instead of leaving the file at its historical
181
+ // peak. SQLite only honours an auto_vacuum *change* before the first table
182
+ // is created (a fresh DB) — on a pre-existing store it is a silent no-op, so
183
+ // existing databases keep their default `auto_vacuum=NONE` and are
184
+ // unaffected (they only shrink under a manual compact()/VACUUM). Databases
185
+ // created from this version on are self-trimming. Must precede SCHEMA.
186
+ this.#db.exec('PRAGMA auto_vacuum = INCREMENTAL');
179
187
  this.#db.exec('PRAGMA journal_mode = WAL');
180
188
  this.#db.exec('PRAGMA foreign_keys = ON');
181
189
  this.#db.exec(SCHEMA);
@@ -608,11 +616,24 @@ export class DebugTrace {
608
616
  // ─── Maintenance ─────────────────────────────────────────────
609
617
 
610
618
  /**
611
- * Delete data older than retentionDays.
612
- * @param {number} [retentionDays=30]
619
+ * Delete trajectory data older than retentionDays, then mark the freed pages
620
+ * reclaimable.
621
+ *
622
+ * The always-on trace stamps every turn with the cumulative request/response
623
+ * snapshot, so each long-session row is MB-scale and the file grows fast
624
+ * (a real deployment hit 5GB in 15 days). A plain DELETE marks pages free but
625
+ * leaves the file at its peak size; `PRAGMA incremental_vacuum` moves those
626
+ * pages onto the freelist for return to the OS — but only when the DB was
627
+ * created with `auto_vacuum=INCREMENTAL` (see constructor). On a legacy
628
+ * `auto_vacuum=NONE` store the vacuum is a harmless no-op, so this is safe to
629
+ * call unconditionally. Note: in WAL mode the on-disk file truncates at the
630
+ * next checkpoint (the running agent's automatic checkpoints handle this), so
631
+ * the page_count drops here but the file size catches up shortly after.
632
+ *
633
+ * @param {number} [retentionDays=10]
613
634
  * @returns {{ deletedTurns: number, deletedTools: number, deletedEvents: number }}
614
635
  */
615
- cleanup(retentionDays = 30) {
636
+ cleanup(retentionDays = 10) {
616
637
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
617
638
  const deletedTools = Number(this.#db.prepare(`
618
639
  DELETE FROM trace_tools WHERE turn_id IN (
@@ -625,9 +646,42 @@ export class DebugTrace {
625
646
  const deletedEvents = Number(this.#db.prepare(`
626
647
  DELETE FROM trace_events WHERE created_at < ?
627
648
  `).run(cutoff).changes);
649
+ // Reclaim freed pages (no-op on legacy auto_vacuum=NONE DBs). Wrapped so a
650
+ // vacuum failure can never mask a successful delete, but surfaced as a warn
651
+ // because this is the one operation the whole disk-growth fix relies on — a
652
+ // silent persistent failure would look exactly like "the fix works".
653
+ if (deletedTurns || deletedTools || deletedEvents) {
654
+ try { this.#db.exec('PRAGMA incremental_vacuum'); }
655
+ catch (err) { console.warn('[Yeaft] trace incremental_vacuum failed:', err?.message || err); }
656
+ }
628
657
  return { deletedTurns, deletedTools, deletedEvents };
629
658
  }
630
659
 
660
+ /**
661
+ * One-shot full compaction (VACUUM). Rebuilds the entire database file,
662
+ * reclaiming all free space AND converting a legacy `auto_vacuum=NONE` store
663
+ * to INCREMENTAL going forward. This is a HEAVY operation: it locks the DB
664
+ * and needs temporary scratch space up to the current file size, so it is
665
+ * NOT called automatically on session load — invoke it deliberately (e.g.
666
+ * from the `yeaft --trace` CLI) when an oversized legacy debug.db needs to be
667
+ * shrunk in place.
668
+ * @returns {{ before: number, after: number }} file size in bytes
669
+ */
670
+ compact() {
671
+ let before = 0;
672
+ try { before = statSync(this.#dbPath).size; } catch { /* ignore */ }
673
+ this.#db.exec('PRAGMA auto_vacuum = INCREMENTAL');
674
+ this.#db.exec('VACUUM');
675
+ // In WAL mode VACUUM writes the rebuilt (smaller) DB into the -wal file;
676
+ // the main .db file does not shrink until a checkpoint folds the WAL back
677
+ // in. TRUNCATE checkpoints and resets the WAL so the on-disk size we report
678
+ // (and the user sees) reflects the reclaimed space immediately.
679
+ try { this.#db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch { /* best-effort */ }
680
+ let after = 0;
681
+ try { after = statSync(this.#dbPath).size; } catch { /* ignore */ }
682
+ return { before, after };
683
+ }
684
+
631
685
  /** Delete all trace data. */
632
686
  purge() {
633
687
  this.#db.exec('DELETE FROM trace_tools');
@@ -695,6 +749,7 @@ export class NullTrace {
695
749
  search() { return []; }
696
750
  stats() { return { turnCount: 0, toolCount: 0, eventCount: 0, dbSizeBytes: 0 }; }
697
751
  cleanup() { return { deletedTurns: 0, deletedTools: 0, deletedEvents: 0 }; }
752
+ compact() { return { before: 0, after: 0 }; }
698
753
  purge() {}
699
754
  close() {}
700
755
  fetchRecentDebugHistory() { return { loops: [], turns: [], dreamEvents: [] }; }
package/yeaft/session.js CHANGED
@@ -231,11 +231,13 @@ export async function loadSession(options = {}) {
231
231
  enabled: true,
232
232
  dbPath: join(yeaftDir, 'debug.db'),
233
233
  });
234
- // Bound disk growth: prune trajectories older than 30 days on session load.
235
- // Cheap (indexed DELETE), runs once per process start, not per turn. Without
236
- // this the always-on store grows unbounded cleanup() existed but had zero
237
- // call sites before this PR.
238
- try { trace.cleanup?.(30); } catch (err) {
234
+ // Bound disk growth: prune trajectories older than 10 days on session load.
235
+ // Cheap (indexed DELETE + incremental_vacuum), runs once per process start,
236
+ // not per turn. The always-on store stamps every turn with the *cumulative*
237
+ // request/response (each long-session row is ~MB), so without a tight TTL the
238
+ // file balloons — a real deployment hit 5GB in 15 days. 10 days keeps enough
239
+ // history for debug-panel replay while capping the steady-state footprint.
240
+ try { trace.cleanup?.(10); } catch (err) {
239
241
  console.warn('[Yeaft] trace.cleanup failed:', err?.message || err);
240
242
  }
241
243