@noobdemon/noob-cli 1.10.6 → 1.10.8

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.6",
3
+ "version": "1.10.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/agent.js CHANGED
@@ -496,6 +496,8 @@ 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 loopDetectedCount = 0; // số lần loop detection liên tiếp — reset khi model gọi tool khác
500
+ const MAX_LOOP_DETECTIONS = 3; // sau 3 lần loop detection liên tiếp → force stop
499
501
  // Effort classifier: phân loại task từ user message gốc → set effort level.
500
502
  // 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
503
  const effort = classifyEffort(history.find((m) => m.role === "user")?.content || "");
@@ -586,8 +588,8 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
586
588
 
587
589
  // ── Loop detection ──────────────────────────────────────────────────
588
590
  // Theo dõi N tool call gần nhất. Nếu cùng tên + input giống nhau liên tiếp
589
- // quá ngưỡng → model bị kẹt. Inject thông báo nhắc model chuyển bước,
590
- // KHÔNG ngắt task (vẫn thể Ctrl+C thủ công).
591
+ // quá ngưỡng → model bị kẹt. Inject thông báo nhắc model chuyển bước.
592
+ // Nếu model vẫn lặp force stop sau MAX_LOOP_DETECTIONS lần.
591
593
  const inputStr = JSON.stringify(call.input || {});
592
594
  recentCalls.push({ name: call.name, inputStr });
593
595
  if (recentCalls.length > LOOP_DETECT_WINDOW) recentCalls.shift();
@@ -595,13 +597,25 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
595
597
  const lastN = recentCalls.slice(-LOOP_DETECT_THRESHOLD);
596
598
  const allSame = lastN.every((c) => c.name === lastN[0].name && c.inputStr === lastN[0].inputStr);
597
599
  if (allSame) {
600
+ loopDetectedCount++;
601
+ // Đã lặp quá nhiều lần liên tiếp → force stop
602
+ if (loopDetectedCount >= MAX_LOOP_DETECTIONS) {
603
+ history.push({
604
+ role: "tool",
605
+ name: "loop_detection",
606
+ content: `[LOOP DETECTED × ${loopDetectedCount}] Bạn đã gọi ${lastN[0].name} nhiều lần liên tiếp với cùng input — KHÔNG THỂ TIẾP TỤC. Dừng ngay lập tức.`,
607
+ });
608
+ return `[LOOP STOPPED] Đã dừng vì model bị kẹt trong vòng lặp gọi ${lastN[0].name} liên tục.`;
609
+ }
598
610
  // Đã lặp — inject cảnh báo, xoá calls để tránh trigger liên tục
599
611
  recentCalls.length = 0;
600
612
  history.push({
601
613
  role: "tool",
602
614
  name: "loop_detection",
603
- content: `[LOOP DETECTED] Bạn vừa gọi ${lastN[0].name} ${LOOP_DETECT_THRESHOLD} lần liên tiếp với cùng input — có vẻ bạn đang bị kẹt trong vòng lặp. HÃY CHUYỂN BƯỚC: gọi tool khác, hoặc trả lời Markdown nếu đã xong. KHÔNG được gọi cùng tool cùng input lần nữa.`,
615
+ content: `[LOOP DETECTED × ${loopDetectedCount}] Bạn vừa gọi ${lastN[0].name} ${LOOP_DETECT_THRESHOLD} lần liên tiếp với cùng input — có vẻ bạn đang bị kẹt trong vòng lặp. HÃY CHUYỂN BƯỚC: gọi tool khác, hoặc trả lời Markdown nếu đã xong. KHÔNG được gọi cùng tool cùng input lần nữa. Nếu tiếp tục lặp, task sẽ bị dừng.`,
604
616
  });
617
+ } else {
618
+ loopDetectedCount = 0; // model gọi tool khác → reset counter
605
619
  }
606
620
  }
607
621
  }
package/src/api.js CHANGED
@@ -116,7 +116,7 @@ function hasUnclosedToolBlock(text) {
116
116
  *
117
117
  * @returns {Promise<{text:string, reasoning:string}>}
118
118
  */
119
- export async function stream({ mode = "chat", message, model, system, conversation, effort, signal, onDelta, onReasoning, onStatus, idleMs = 45000, maxContinues = Infinity }) {
119
+ export async function stream({ mode = "chat", message, model, system, conversation, effort, signal, onDelta, onReasoning, onStatus, idleMs = 240000, maxContinues = Infinity }) {
120
120
  const endpoint = mode === "search" ? "/api/search" : mode === "merge" ? "/api/merge" : "/api/chat";
121
121
 
122
122
  let fullText = "";
package/src/repl.js CHANGED
@@ -1351,75 +1351,6 @@ NGUYÊN TẮC:
1351
1351
  if ((!printer || !printer.started) && answer?.trim())
1352
1352
  printAnswer(answer, state.model.name, providerColor(state.model.provider));
1353
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
-
1423
1354
  // Parse todo từ model output → render trên status bar.
1424
1355
  state.todos = parseTodosFromHistory(state.history);
1425
1356
  tui.setTodos(state.todos);