@noobdemon/noob-cli 1.10.4 → 1.10.5

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.10.4",
3
+ "version": "1.10.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/agent.js CHANGED
@@ -392,7 +392,7 @@ export function relTime(ts) {
392
392
  // chèn ngay sau SYSTEM để model biết và dùng được.
393
393
  // recentSessions: breadcrumbs các phiên trước cùng workspace (repl.js cung cấp)
394
394
  // → chèn ngay sau memoryBlock() để model "thấy" lịch sử dù chưa /resume.
395
- function buildSystem(history, extraToolsDoc, goal, recentSessions) {
395
+ export function buildSystem(history, extraToolsDoc, goal, recentSessions) {
396
396
  const parts = [SYSTEM, "", memoryBlock()];
397
397
  if (recentSessions && recentSessions.length) {
398
398
  parts.push("", recentSessionsBlock(recentSessions));
@@ -403,7 +403,7 @@ function buildSystem(history, extraToolsDoc, goal, recentSessions) {
403
403
  return parts.join("\n");
404
404
  }
405
405
 
406
- function buildUserMessage(history) {
406
+ export function buildUserMessage(history) {
407
407
  const msgs = compact(history, MAX_PROMPT_CHARS);
408
408
  const parts = [filesLedger(history), "", "=".repeat(60), "# CONVERSATION", ""];
409
409
  for (const m of msgs) {
@@ -496,11 +496,14 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
496
496
  // chạy không giới hạn token. Dừng theo: GOAL đạt, <<LOOP_DONE>>, <<ULTRA_DONE>>,
497
497
  // model tự kết thúc reply không có tool block, hoặc user Ctrl+C.
498
498
  const recentCalls = []; // {name, inputStr} — theo dõi vòng lặp
499
- let idleRecoveries = 0; // đếm số lần idle recovery —max 5 để tránh infinite loop
500
499
  // Effort classifier: phân loại task từ user message gốc → set effort level.
501
500
  // Chỉ classify 1 lần ở bước đầu, giữ nguyên suốt task (thay đổi giữa chừng gây bất ổn).
502
501
  const effort = classifyEffort(history.find((m) => m.role === "user")?.content || "");
503
502
  for (let step = 0; step < MAX_STEPS; step++) {
503
+ // Check abort signal ngay đầu mỗi iteration để Ctrl+C dừng ngay lập tức
504
+ if (signal?.aborted) {
505
+ throw new Error("aborted");
506
+ }
504
507
  // Mỗi 100 bước log một mốc để người dùng biết noob vẫn đang chạy (task dài).
505
508
  if (step > 0 && step % 100 === 0) onStatus?.(`đã chạy ${step} bước…`);
506
509
 
@@ -556,22 +559,7 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
556
559
  });
557
560
  continue;
558
561
  }
559
- // ── Idle recovery: model dừng nhưng còn task chưa xong ────────────
560
- // Nếu pendingTasks còn items → KHÔNG return, inject nudge rồi continue loop.
561
- // Giới hạn 5 lần recovery để tránh infinite loop.
562
- {
563
- const tasks = pendingTasks || [];
564
- const remaining = tasks.length;
565
- if (remaining > 0 && idleRecoveries < 5) {
566
- idleRecoveries++;
567
- const next = tasks[0];
568
- history.push({
569
- role: "user",
570
- content: `[SYSTEM] Bạn vừa dừng giữa chừng. Còn ${remaining} việc chưa hoàn thành: ${tasks.map((t) => `"${t}"`).join(", ")}. Việc tiếp theo BẮT BUỘC phải làm ngay: "${next}". Gọi tool (write_file/edit_file/run_command) để làm việc này. KHÔNG dừng, KHÔNG tóm tắt, KHÔNG đợi user.`,
571
- });
572
- continue;
573
- }
574
- }
562
+ // Model dừng (không tool call, không incomplete) return để repl quyết định tiếp tục hay không
575
563
  return text; // final answer
576
564
  }
577
565
 
package/src/repl.js CHANGED
@@ -3,9 +3,9 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import chalk from "chalk";
5
5
  import { createTui } from "./tui.js";
6
- import { runAgent, maybeSummarize } from "./agent.js";
6
+ import { runAgent, maybeSummarize, buildSystem, buildUserMessage } from "./agent.js";
7
7
  import { runSubAgent, spawnAgentToolsDoc, MAX_SUBAGENT_DEPTH } from "./subagent.js";
8
- import { TokenMeter, countMessages, CONTEXT_WINDOW } from "./tokens.js";
8
+ import { TokenMeter, countMessages, CONTEXT_WINDOW, countTokens } 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";
@@ -1254,7 +1254,9 @@ NGUYÊN TẮC:
1254
1254
  updateTitle();
1255
1255
  }
1256
1256
  // Tính context tokens realtime — đếm system prompt + history trước khi gửi.
1257
- tokenMeter.setContext(countMessages(state.history));
1257
+ const systemPrompt = buildSystem(state.history, state.agentMode ? spawnAgentToolsDoc(0) : "", state.goal, sessions.list(5, process.cwd()).filter((s) => s.id !== session?.id));
1258
+ const userMessage = buildUserMessage(state.history);
1259
+ tokenMeter.setContext(countTokens(systemPrompt) + countTokens(userMessage));
1258
1260
  if (process.stdin.isTTY && !state.steerHintShown) {
1259
1261
  console.log(c.dim(" " + t.steerHint));
1260
1262
  state.steerHintShown = true;
@@ -1348,6 +1350,76 @@ NGUYÊN TẮC:
1348
1350
  // Lượt cuối không stream ra gì (vd model suy luận trả 1 cục) → in fallback.
1349
1351
  if ((!printer || !printer.started) && answer?.trim())
1350
1352
  printAnswer(answer, state.model.name, providerColor(state.model.provider));
1353
+
1354
+ // ── Idle verification: model dừng → gọi model check task hoàn thành chưa ───
1355
+ // Nếu chưa xong → chạy runAgent tiếp với prompt verify
1356
+ // Giới hạn 3 lần để tránh infinite loop
1357
+ const MAX_IDLE_VERIFY = 3;
1358
+ let idleVerifyCount = 0;
1359
+ while (idleVerifyCount < MAX_IDLE_VERIFY) {
1360
+ // Kiểm tra xem model có thực sự xong không
1361
+ const verifyPrompt = `[SYSTEM] Bạn vừa dừng lại. Hãy KIỂM TRA LẠI xem task đã hoàn thành chưa:
1362
+ 1. Đọc file kết quả (nếu có) bằng read_file để xác nhận nội dung
1363
+ 2. Chạy test/build nếu task liên quan code
1364
+ 3. So sánh với yêu cầu ban đầu của user
1365
+
1366
+ Nếu thực sự HOÀN THÀNH → trả lời "XÁC NHẬN: [tóm tắt ngắn những gì đã làm]"
1367
+ Nếu CHƯA XONG → gọi tool (write_file/edit_file/run_command) để hoàn thành tiếp. KHÔNG dừng.`;
1368
+
1369
+ state.history.push({ role: "user", content: verifyPrompt });
1370
+
1371
+ const nextAnswer = await runAgent({
1372
+ history: state.history,
1373
+ model: state.model.id,
1374
+ signal: abort.signal,
1375
+ tokenMeter,
1376
+ goal: state.goal,
1377
+ recentSessions: sessions.list(5, process.cwd()).filter((s) => s.id !== session?.id),
1378
+ extraToolsDoc: state.agentMode ? spawnAgentToolsDoc(0) : "",
1379
+ pendingTasks: (state.todos || []).filter((t) => !t.done).map((t) => t.text),
1380
+ onStatus: () => tick(t.thinking),
1381
+ onSteer: () => {
1382
+ if (!pending.length) return [];
1383
+ const msgs = pending.splice(0);
1384
+ stopSpin();
1385
+ for (const msg of msgs) console.log(c.user(" " + t.steerInject(truncate(msg, 70))));
1386
+ startSpin(t.thinking);
1387
+ return msgs;
1388
+ },
1389
+ onDelta: (ev) => {
1390
+ if (ev.type === "step-start") {
1391
+ printer = makeStreamPrinter(state.model.name, providerColor(state.model.provider));
1392
+ } else if (ev.type === "delta") {
1393
+ if (printer.suppressing) return printer.push(ev.text);
1394
+ stopSpin();
1395
+ printer.push(ev.text);
1396
+ if (printer.suppressing) startSpin(t.thinking);
1397
+ } else if (ev.type === "step-end") {
1398
+ printer?.flush();
1399
+ }
1400
+ },
1401
+ onTool: (name, input) => dispatchTool(name, input, 0),
1402
+ });
1403
+
1404
+ if (!nextAnswer?.trim()) {
1405
+ idleVerifyCount++;
1406
+ continue;
1407
+ }
1408
+
1409
+ // In response nếu có
1410
+ if ((!printer || !printer.started) && nextAnswer?.trim())
1411
+ printAnswer(nextAnswer, state.model.name, providerColor(state.model.provider));
1412
+
1413
+ // Check nếu model confirm "XÁC NHẬN"
1414
+ if (/XÁC NHẬN/i.test(nextAnswer)) {
1415
+ answer = nextAnswer;
1416
+ break;
1417
+ }
1418
+
1419
+ idleVerifyCount++;
1420
+ // Nếu model gọi tool thì tiếp tục loop (sẽ được xử lý bởi runAgent tiếp theo)
1421
+ }
1422
+
1351
1423
  // Parse todo từ model output → render trên status bar.
1352
1424
  state.todos = parseTodosFromHistory(state.history);
1353
1425
  tui.setTodos(state.todos);