@noobdemon/noob-cli 1.10.14 → 1.10.16
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 +43 -19
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -144,7 +144,9 @@ const MAX_STEPS = 10000;
|
|
|
144
144
|
|
|
145
145
|
// Loop detection: nếu model gọi cùng 1 tool với input giống nhau liên tiếp
|
|
146
146
|
// quá ngưỡng này → coi là bị kẹt, inject thông báo để model chuyển bước.
|
|
147
|
-
|
|
147
|
+
// Cũng phát hiện pattern vòng lặp (A-B-A-B, A-B-C-A-B-C) — model hay thoát
|
|
148
|
+
// loop detection cũ bằng cách xen kẽ 2-3 tool call khác nhau.
|
|
149
|
+
const LOOP_DETECT_WINDOW = 6;
|
|
148
150
|
const LOOP_DETECT_THRESHOLD = 2;
|
|
149
151
|
const MAX_PROMPT_CHARS = 1200000; // ~300k tokens (ngang context window) — compact() KHÔNG chạy trước auto-compact 80% (240k token) của repl.js
|
|
150
152
|
// Khi history vượt ngưỡng này, gọi model phụ tóm tắt các lượt cũ thay vì cắt cụt
|
|
@@ -197,6 +199,8 @@ function runtimeContext() {
|
|
|
197
199
|
"- IMPORTANT: run_command runs in PowerShell on Windows — do NOT use Unix tools.",
|
|
198
200
|
" Use: Get-Content (not cat), Get-ChildItem (not ls), Select-String (not grep),",
|
|
199
201
|
" (Get-Content f | Measure-Object -Line) (not wc -l). Paths use backslashes.",
|
|
202
|
+
"- Trên Windows, script chạy bằng `script.bat` hoặc `script.cmd`, KHÔNG dùng `./script` (Unix).",
|
|
203
|
+
" VD: gradlew → gradlew.bat, mvnw → mvnw.cmd.",
|
|
200
204
|
"- Prefer the dedicated tools (read_file / list_dir / grep / glob) over shell commands;",
|
|
201
205
|
" they are cross-platform. Use run_command mainly for builds/tests/installs.",
|
|
202
206
|
);
|
|
@@ -588,36 +592,56 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
|
|
|
588
592
|
}
|
|
589
593
|
|
|
590
594
|
// ── Loop detection ──────────────────────────────────────────────────
|
|
591
|
-
// Theo dõi N tool call gần nhất.
|
|
592
|
-
//
|
|
593
|
-
//
|
|
595
|
+
// Theo dõi N tool call gần nhất. Phát hiện 2 dạng kẹt:
|
|
596
|
+
// (A) Cùng tool + input liên tiếp (A-A-A) — dễ, như model hay né.
|
|
597
|
+
// (B) Pattern vòng (A-B-A-B, A-B-C-A-B-C) — model xen kẽ 2-3 tool
|
|
598
|
+
// khác nhau để tránh phát hiện cũ. So nửa đầu vs nửa cuối window.
|
|
599
|
+
// Nếu phát hiện → inject cảnh báo. Nếu tái diễn → force stop.
|
|
594
600
|
const inputStr = JSON.stringify(call.input || {});
|
|
595
601
|
recentCalls.push({ name: call.name, inputStr });
|
|
596
602
|
if (recentCalls.length > LOOP_DETECT_WINDOW) recentCalls.shift();
|
|
603
|
+
let loopType = null; // 'consecutive' | 'pattern' | null
|
|
597
604
|
if (recentCalls.length >= LOOP_DETECT_THRESHOLD + 1) {
|
|
605
|
+
// (A) Same consecutive check
|
|
598
606
|
const lastN = recentCalls.slice(-LOOP_DETECT_THRESHOLD);
|
|
599
607
|
const allSame = lastN.every((c) => c.name === lastN[0].name && c.inputStr === lastN[0].inputStr);
|
|
600
|
-
if (allSame)
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
608
|
+
if (allSame) loopType = 'consecutive';
|
|
609
|
+
}
|
|
610
|
+
// (B) Pattern cycle check: tìm chu kỳ lặp trong window (độ dài 2-3)
|
|
611
|
+
if (!loopType && recentCalls.length >= 4) {
|
|
612
|
+
for (let cycleLen = 2; cycleLen <= Math.min(3, Math.floor(recentCalls.length / 2)); cycleLen++) {
|
|
613
|
+
const half = Math.floor(recentCalls.length / cycleLen) * cycleLen;
|
|
614
|
+
const first = recentCalls.slice(-half, -half + cycleLen);
|
|
615
|
+
const rest = recentCalls.slice(-half + cycleLen);
|
|
616
|
+
let matched = true;
|
|
617
|
+
for (let i = 0; i < rest.length; i++) {
|
|
618
|
+
if (rest[i].name !== first[i % cycleLen].name || rest[i].inputStr !== first[i % cycleLen].inputStr) {
|
|
619
|
+
matched = false;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
610
622
|
}
|
|
611
|
-
|
|
612
|
-
|
|
623
|
+
if (matched) { loopType = 'pattern'; break; }
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (loopType) {
|
|
627
|
+
const label = loopType === 'consecutive' ? lastN[0].name : `pattern [${recentCalls.slice(-4).map((c) => c.name).join(", ")}]`;
|
|
628
|
+
loopDetectedCount++;
|
|
629
|
+
if (loopDetectedCount >= MAX_LOOP_DETECTIONS) {
|
|
613
630
|
history.push({
|
|
614
631
|
role: "tool",
|
|
615
632
|
name: "loop_detection",
|
|
616
|
-
content: `[LOOP DETECTED × ${loopDetectedCount}] Bạn
|
|
633
|
+
content: `[LOOP DETECTED × ${loopDetectedCount}] Bạn lặp lại ${label} nhiều lần — KHÔNG THỂ TIẾP TỤC. Dừng ngay.`,
|
|
617
634
|
});
|
|
618
|
-
|
|
619
|
-
loopDetectedCount = 0; // model gọi tool khác → reset counter
|
|
635
|
+
return `[LOOP STOPPED] Đã dừng vì model bị kẹt trong vòng lặp.`;
|
|
620
636
|
}
|
|
637
|
+
recentCalls.length = 0;
|
|
638
|
+
history.push({
|
|
639
|
+
role: "tool",
|
|
640
|
+
name: "loop_detection",
|
|
641
|
+
content: `[LOOP DETECTED × ${loopDetectedCount}] Bạn lặp lại ${label} — có vẻ đang kẹt. HÃY CHUYỂN BƯỚC: gọi tool khác hoặc trả lời Markdown nếu xong. KHÔNG lặp lại. Nếu tiếp tục, task sẽ dừng.`,
|
|
642
|
+
});
|
|
643
|
+
} else {
|
|
644
|
+
loopDetectedCount = 0; // tool khác → reset
|
|
621
645
|
}
|
|
622
646
|
}
|
|
623
647
|
return t.maxSteps;
|