@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 +1 -1
- package/src/agent.js +17 -3
- package/src/api.js +1 -1
- package/src/repl.js +0 -69
package/package.json
CHANGED
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
|
-
//
|
|
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 =
|
|
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);
|