@noobdemon/noob-cli 1.9.12 → 1.10.1

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.12",
3
+ "version": "1.10.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/agent.js CHANGED
@@ -242,6 +242,24 @@ export function compact(history, budget) {
242
242
  return [...head, elided, ...out.slice(tailStart)];
243
243
  }
244
244
 
245
+ // Trích xuất todo items chưa hoàn thành từ history assistant messages.
246
+ // Format: `- [ ] task` (unchecked) / `- [x] task` (checked).
247
+ // Trả về mảng text của các item chưa check, giữ nguyên thứ tự.
248
+ function extractUncheckedTodos(history) {
249
+ const todos = [];
250
+ for (const m of history) {
251
+ if (m.role !== "assistant" || typeof m.content !== "string") continue;
252
+ for (const line of m.content.split("\n")) {
253
+ const unchecked = line.match(/^[\s]*-\s*\[\s?\]\s+(.+)/);
254
+ if (unchecked) todos.push(unchecked[1].trim());
255
+ }
256
+ }
257
+ // Dedupe: giữ item cuối cùng (model có thể repeat todo).
258
+ const seen = new Map();
259
+ for (const t of todos) seen.set(t, t);
260
+ return [...seen.values()];
261
+ }
262
+
245
263
  // Bộ nhớ dài hạn cho phiên: khi history phình to, gọi model phụ TÓM TẮT các
246
264
  // lượt cũ thành một message system gọn (giữ quyết định, file đã sửa, lý do,
247
265
  // việc dở) rồi thay phần đầu history bằng tóm tắt đó. Mutates `history` in place.
@@ -565,6 +583,21 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
565
583
  content: allow ? result : t.toolDenied,
566
584
  });
567
585
 
586
+ // ── Todo continuation nudge ──────────────────────────────────────────
587
+ // Sau mỗi tool result, kiểm tra xem còn todo item nào chưa check không.
588
+ // Nếu có → inject nudge nhắc model TIẾP TỤC, KHÔNG dừng.
589
+ {
590
+ const unchecked = extractUncheckedTodos(history);
591
+ if (unchecked.length > 0) {
592
+ const next = unchecked[0];
593
+ history.push({
594
+ role: "tool",
595
+ name: "todo_nudge",
596
+ content: `[TODO] Còn ${unchecked.length} việc chưa xong. Việc tiếp theo: "${next}". HÃY LÀM NGAY — gọi tool (write_file/edit_file/run_command) để hoàn thành việc này. KHÔNG dừng, KHÔNG tóm tắt, KHÔNG hỏi lại.`,
597
+ });
598
+ }
599
+ }
600
+
568
601
  // ── Loop detection ──────────────────────────────────────────────────
569
602
  // Theo dõi N tool call gần nhất. Nếu cùng tên + input giống nhau liên tiếp
570
603
  // quá ngưỡng → model bị kẹt. Inject thông báo nhắc model chuyển bước,
package/src/repl.js CHANGED
@@ -152,7 +152,10 @@ export async function startRepl(opts = {}) {
152
152
 
153
153
  // Set terminal title bar — hiện trên CMD/PowerShell.
154
154
  const updateTitle = () => {
155
- process.title = `noob — ${state.model.name}`;
155
+ const name = session?.title
156
+ ? session.title.slice(0, 40) + (session.title.length > 40 ? "…" : "")
157
+ : state.model.name;
158
+ process.title = `noob — ${name}`;
156
159
  };
157
160
  updateTitle();
158
161
 
@@ -1244,6 +1247,11 @@ NGUYÊN TẮC:
1244
1247
  ? 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.]`
1245
1248
  : text;
1246
1249
  state.history.push({ role: "user", content });
1250
+ // Update terminal title với session name (trích từ message đầu).
1251
+ if (session && !session.title) {
1252
+ session.title = content.replace(/\s+/g, " ").trim().slice(0, 60);
1253
+ updateTitle();
1254
+ }
1247
1255
  // Tính context tokens realtime — đếm system prompt + history trước khi gửi.
1248
1256
  tokenMeter.setContext(countMessages(state.history));
1249
1257
  if (process.stdin.isTTY && !state.steerHintShown) {
@@ -1808,21 +1816,37 @@ NGUYÊN TẮC:
1808
1816
  s.mode === "merge" ? c.tool("Merge AI") : s.mode === "search" ? c.accent("Tìm web") : modelBadge(s.model);
1809
1817
  const key = config.apiKey ? c.ok(" 🔑") : c.err(" 🔒");
1810
1818
  const yolo = s.yolo ? c.err(" ⚡ yolo: BẬT") : c.dim(" yolo: tắt");
1811
- // Size phiênmàu đổi theo mức: dim < 60k, tool 60-120k, accent 120-200k, err > 200k.
1812
- const totalChars = (s.history || []).reduce(
1813
- (a, m) => a + (typeof m.content === "string" ? m.content.length : 0),
1814
- 0,
1815
- );
1816
- const k = Math.round(totalChars / 1000);
1817
- const sizeColor = totalChars > 200000 ? c.err : totalChars > 120000 ? c.accent : totalChars > 60000 ? c.tool : c.dim;
1818
- const size = sizeColor(` ctx: ${k}k`);
1819
+ // Context %dùng token meter nếu data, fallback chars.
1820
+ const ctxPct = tokenMeter.contextPct();
1821
+ const ctxColor = ctxPct !== null
1822
+ ? (ctxPct >= 80 ? c.err : ctxPct >= 60 ? c.accent : ctxPct >= 40 ? c.tool : c.dim)
1823
+ : c.dim;
1824
+ const ctxLabel = ctxPct !== null
1825
+ ? ctxColor(` ctx: ${ctxPct}%`)
1826
+ : c.dim(" ctx: —");
1827
+ // Token usage.
1828
+ const tokLabel = c.dim(` ↑${fmtK(tokenMeter.input)} ↓${fmtK(tokenMeter.output)} (${fmtK(tokenMeter.total)})`);
1829
+ // Todo progress.
1830
+ const todos = s.todos || [];
1831
+ let todoLabel = "";
1832
+ if (todos.length) {
1833
+ const done = todos.filter((t) => t.done).length;
1834
+ todoLabel = c.ok(` ✓ ${done}/${todos.length}`);
1835
+ }
1836
+ // Session ID.
1837
+ const sidLabel = session?.id ? c.dim(` 📋 ${session.id}`) : "";
1838
+ // CWD.
1839
+ const cwdLabel = c.dim(" 📁 " + shortCwd());
1819
1840
  console.log(
1820
- " " + mode + key + yolo + size + c.dim(" v" + CURRENT) + c.dim(" thư mục: " + shortCwd()),
1841
+ ` ${mode}${key}${yolo}${ctxLabel}${tokLabel}${todoLabel}\n ${c.dim("v" + CURRENT)}${sidLabel}${cwdLabel}`,
1821
1842
  );
1822
1843
  }
1823
1844
  }
1824
1845
 
1825
1846
  // ── presentation helpers ───────────────────────────────────────────────────
1847
+ function fmtK(n) {
1848
+ return n >= 1000000 ? (n / 1000000).toFixed(1) + "M" : n >= 1000 ? (n / 1000).toFixed(1) + "k" : String(n);
1849
+ }
1826
1850
  function printAnswer(text, name, color) {
1827
1851
  if (!text?.trim()) return;
1828
1852
  console.log("\n" + chalk.hex(color).bold(" ● " + name));
package/src/tui.js CHANGED
@@ -581,6 +581,7 @@ export function createTui({ onLine, onInterrupt, onEOF, onShiftTab, completer }
581
581
  }
582
582
  if (ch === "\x7f" || ch === "\b") { backspace(); refreshMenu(); draw(); i++; continue; } // Backspace
583
583
  if (ch === "\x03") { onInterrupt?.(); i++; continue; } // Ctrl+C
584
+ if (ch === "\x0c") { console.clear(); draw(); i++; continue; } // Ctrl+L: clear screen
584
585
  if (ch === "\x01") { cur = 0; draw(); i++; continue; } // Ctrl+A → đầu dòng
585
586
  if (ch === "\x05") { cur = cells.length; draw(); i++; continue; } // Ctrl+E → cuối dòng
586
587
  if (ch === "\x15") { cells = cells.slice(cur); cur = 0; histPos = null; refreshMenu(); draw(); i++; continue; } // Ctrl+U: xoá tới đầu dòng