@noobdemon/noob-cli 1.10.3 → 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.3",
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) {
@@ -500,6 +500,10 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
500
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).
501
501
  const effort = classifyEffort(history.find((m) => m.role === "user")?.content || "");
502
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
+ }
503
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).
504
508
  if (step > 0 && step % 100 === 0) onStatus?.(`đã chạy ${step} bước…`);
505
509
 
@@ -555,6 +559,7 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
555
559
  });
556
560
  continue;
557
561
  }
562
+ // Model dừng (không tool call, không incomplete) → return để repl quyết định tiếp tục hay không
558
563
  return text; // final answer
559
564
  }
560
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);