@noobdemon/noob-cli 1.9.7 → 1.9.9

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": "@noobdemon/noob-cli",
3
- "version": "1.9.7",
3
+ "version": "1.9.9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/agent.js CHANGED
@@ -513,6 +513,9 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
513
513
 
514
514
  const system = buildSystem(history, extraToolsDoc, goal, recentSessions);
515
515
  const message = buildUserMessage(history);
516
+ // Cập nhật context tokens realtime — đếm system + message sau khi build xong.
517
+ // Mỗi iteration (tool call, steer) history thay đổi → size thay đổi → cần update.
518
+ tokenMeter?.setContext(countTokens(system) + countTokens(message));
516
519
  tokenMeter?.addInput(countTokens(message));
517
520
  onStatus?.("thinking");
518
521
  onDelta?.({ type: "step-start" });
package/src/repl.js CHANGED
@@ -5,7 +5,7 @@ import chalk from "chalk";
5
5
  import { createTui } from "./tui.js";
6
6
  import { runAgent, maybeSummarize } from "./agent.js";
7
7
  import { runSubAgent, spawnAgentToolsDoc, MAX_SUBAGENT_DEPTH } from "./subagent.js";
8
- import { TokenMeter } from "./tokens.js";
8
+ import { TokenMeter, countMessages, CONTEXT_WINDOW } from "./tokens.js";
9
9
  import { stream, usage, ApiError, resetMemoryToken } from "./api.js";
10
10
  import { runTool, describe, DESTRUCTIVE, addRoot, removeRoot, listRoots, OutOfScopeError, nearestExistingDir } from "./tools.js";
11
11
  import { MODELS, PROVIDERS, findModel, providerColor, DEFAULT_MODEL } from "./models.js";
@@ -1186,7 +1186,7 @@ NGUYÊN TẮC:
1186
1186
  // Nhờ vậy người dùng LUÔN thấy đồng hồ + token đang chạy, kể cả khi treo chờ y/n.
1187
1187
  const tickMeta = () => {
1188
1188
  const elapsed = ((Date.now() - t0) / 1000).toFixed(0);
1189
- tui.setMeta(`${elapsed}s · ${tokenMeter.format()}`);
1189
+ tui.setMeta(`${elapsed}s · ${tokenMeter.formatWithPct()}`);
1190
1190
  };
1191
1191
  const tick = (label) => {
1192
1192
  tui.status(c.dim(`${label}…`));
@@ -1237,6 +1237,8 @@ NGUYÊN TẮC:
1237
1237
  ? text + `\n\n[File người dùng nhắc tới bằng @: ${files.join(", ")} — đọc bằng read_file nếu cần.]`
1238
1238
  : text;
1239
1239
  state.history.push({ role: "user", content });
1240
+ // Tính context tokens realtime — đếm system prompt + history trước khi gửi.
1241
+ tokenMeter.setContext(countMessages(state.history));
1240
1242
  if (process.stdin.isTTY && !state.steerHintShown) {
1241
1243
  console.log(c.dim(" " + t.steerHint));
1242
1244
  state.steerHintShown = true;
@@ -1341,34 +1343,28 @@ NGUYÊN TẮC:
1341
1343
  } finally {
1342
1344
  abort = null;
1343
1345
  tui.setBusy(false);
1344
- // Cảnh báo phiên dài: in một lần khi tổng chars vượt ~2× ngưỡng summarize
1345
- // (60k trong agent.js). Tự maybeSummarize đã chạy bên trong, nhưng người
1346
- // dùng thể muốn /clear chủ động cho gọn ngữ cảnh + tốc độ.
1346
+ // Auto-compact dựa trên context tokens thay chars.
1347
+ // 80% context window (160k tokens) auto compact.
1348
+ // 70% (140k tokens) cảnh báo mạnh.
1349
+ // 60% (120k tokens) → nhắc nhẹ.
1347
1350
  try {
1348
- const totalChars = state.history.reduce(
1349
- (a, m) => a + (typeof m.content === "string" ? m.content.length : JSON.stringify(m.content || "").length),
1350
- 0,
1351
- );
1352
- const k = Math.round(totalChars / 1000);
1353
- // Mốc 3 (240k+): TỰ ĐỘNG compact không hỏi, không chờ user. Mục tiêu là
1354
- // giữ model chạy mượt khi user mải làm việc, không để phiên phình mãi.
1355
- // Dùng cờ _autoCompacting chống re-entrant (nếu compact lâu, lượt sau
1356
- // tới trước khi xong thì bỏ qua).
1357
- if (totalChars > 240000 && !state._autoCompacting) {
1351
+ const totalTokens = countMessages(state.history);
1352
+ tokenMeter.setContext(totalTokens);
1353
+ const k = Math.round(totalTokens / 1000);
1354
+ const pct = Math.round((totalTokens / CONTEXT_WINDOW) * 100);
1355
+ // Mốc 3 (≥80% 160k tokens): TỰ ĐỘNG compact.
1356
+ if (totalTokens >= CONTEXT_WINDOW * 0.8 && !state._autoCompacting) {
1358
1357
  state._autoCompacting = true;
1359
- console.log(c.accent("" + t.autoCompactTrigger(k)));
1358
+ console.log(c.accent(`${t.autoCompactTrigger(k)} (${pct}% context)`));
1360
1359
  tui.setBusy(true, t.compactRunning);
1361
1360
  try {
1362
1361
  const ok = await maybeSummarize(state.history, { model: state.model, force: true });
1363
1362
  tui.setBusy(false);
1364
1363
  if (ok) {
1365
- const afterChars = state.history.reduce(
1366
- (a, m) => a + (typeof m.content === "string" ? m.content.length : 0),
1367
- 0,
1368
- );
1369
- const aK = Math.round(afterChars / 1000);
1370
- const pct = totalChars > 0 ? Math.round(((totalChars - afterChars) / totalChars) * 100) : 0;
1371
- console.log(c.ok(" " + t.autoCompactDone(k, aK, pct)));
1364
+ const afterTokens = countMessages(state.history);
1365
+ const aK = Math.round(afterTokens / 1000);
1366
+ const saved = totalTokens > 0 ? Math.round(((totalTokens - afterTokens) / totalTokens) * 100) : 0;
1367
+ console.log(c.ok(` ${t.autoCompactDone(k, aK, saved)} (${Math.round((afterTokens / CONTEXT_WINDOW) * 100)}% context)`));
1372
1368
  state._longSessionWarned = false;
1373
1369
  persist();
1374
1370
  } else {
@@ -1380,13 +1376,13 @@ NGUYÊN TẮC:
1380
1376
  } finally {
1381
1377
  state._autoCompacting = false;
1382
1378
  }
1383
- } else if (totalChars > 200000) {
1384
- // Mốc 2 (200k–240k): cảnh báo mạnh, in lại mỗi lượt.
1385
- console.log(c.err(" " + t.veryLongSession(k)));
1379
+ } else if (totalTokens >= CONTEXT_WINDOW * 0.7) {
1380
+ // Mốc 2 (≥70% — 140k tokens): cảnh báo mạnh.
1381
+ console.log(c.err(` ${t.veryLongSession(k)} (${pct}% context)`));
1386
1382
  state._longSessionWarned = true;
1387
- } else if (totalChars > 120000 && !state._longSessionWarned) {
1388
- // Mốc 1 (120k+): nhắc nhẹ một lần.
1389
- console.log(c.dim("" + t.longSession(k)));
1383
+ } else if (totalTokens >= CONTEXT_WINDOW * 0.6 && !state._longSessionWarned) {
1384
+ // Mốc 1 (≥60% — 120k tokens): nhắc nhẹ một lần.
1385
+ console.log(c.dim(`${t.longSession(k)} (${pct}% context)`));
1390
1386
  state._longSessionWarned = true;
1391
1387
  }
1392
1388
  } catch {}
package/src/tokens.js CHANGED
@@ -32,11 +32,16 @@ export function countMessages(messages = []) {
32
32
  // window đủ rộng (256 chars) để qua mọi ranh giới token thực tế của cl100k_base
33
33
  // (token dài nhất ~ vài chục byte).
34
34
  const TAIL_WINDOW = 256;
35
+ // Context window tối đa của model (200k tokens). Dùng để tính % usage realtime.
36
+ export const CONTEXT_WINDOW = 200000;
35
37
 
36
38
  export class TokenMeter {
37
39
  constructor() {
38
40
  this.input = 0;
39
41
  this.output = 0;
42
+ // Context size (tokens) hiện tại của messages sắp gửi lên API.
43
+ // Được set từ repl.js trước mỗi lượt gọi, cập nhật realtime.
44
+ this.contextTokens = 0;
40
45
  // Phần đầu output đã "commit" — không đụng vào nữa.
41
46
  this._committedChars = 0; // số ký tự đã đẩy qua khỏi tail window
42
47
  this._committedTokens = 0; // tổng token tương ứng đã cộng vào this.output
@@ -92,9 +97,26 @@ export class TokenMeter {
92
97
  const fmt = (n) => (n >= 1000 ? (n / 1000).toFixed(1) + "k" : String(n));
93
98
  return `↑${fmt(this.input)} ↓${fmt(this.output)} (${fmt(this.total)})`;
94
99
  }
100
+ // Định dạng kèm context usage: "↑1.2k ↓340 (1.5k) · ctx 45%".
101
+ formatWithPct() {
102
+ const base = this.format();
103
+ const pct = this.contextPct();
104
+ if (pct === null) return base;
105
+ return `${base} · ctx ${pct}%`;
106
+ }
107
+ // Phần trăm context window đã dùng (0–100). Trả null nếu chưa có data.
108
+ contextPct() {
109
+ if (!this.contextTokens) return null;
110
+ return Math.min(100, Math.round((this.contextTokens / CONTEXT_WINDOW) * 100));
111
+ }
112
+ // Đặt context size (tokens) — gọi từ repl.js trước mỗi lượt API.
113
+ setContext(n) {
114
+ this.contextTokens = Math.max(0, n | 0);
115
+ }
95
116
  reset() {
96
117
  this.input = 0;
97
118
  this.output = 0;
119
+ this.contextTokens = 0;
98
120
  this._committedChars = 0;
99
121
  this._committedTokens = 0;
100
122
  this._tail = "";