@noobdemon/noob-cli 1.10.13 → 1.10.15
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 -21
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -144,12 +144,14 @@ 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
|
-
const MAX_PROMPT_CHARS =
|
|
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
|
|
151
153
|
// → giữ được "trí nhớ dài hạn" trong phiên mà không nổ context.
|
|
152
|
-
const SUMMARIZE_THRESHOLD_CHARS =
|
|
154
|
+
const SUMMARIZE_THRESHOLD_CHARS = 1000000; // ~250k tokens (83% window) — summarize chỉ chạy sau auto-compact 80%
|
|
153
155
|
|
|
154
156
|
// HARD GOAL block (do /goal <text> set): chèn ngay sau memoryBlock, attention
|
|
155
157
|
// cao. Mục đích — chống 3 failure mode bài "dynamic workflows" của Anthropic
|
|
@@ -588,36 +590,56 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
|
|
|
588
590
|
}
|
|
589
591
|
|
|
590
592
|
// ── Loop detection ──────────────────────────────────────────────────
|
|
591
|
-
// Theo dõi N tool call gần nhất.
|
|
592
|
-
//
|
|
593
|
-
//
|
|
593
|
+
// Theo dõi N tool call gần nhất. Phát hiện 2 dạng kẹt:
|
|
594
|
+
// (A) Cùng tool + input liên tiếp (A-A-A) — dễ, như model hay né.
|
|
595
|
+
// (B) Pattern vòng (A-B-A-B, A-B-C-A-B-C) — model xen kẽ 2-3 tool
|
|
596
|
+
// khác nhau để tránh phát hiện cũ. So nửa đầu vs nửa cuối window.
|
|
597
|
+
// Nếu phát hiện → inject cảnh báo. Nếu tái diễn → force stop.
|
|
594
598
|
const inputStr = JSON.stringify(call.input || {});
|
|
595
599
|
recentCalls.push({ name: call.name, inputStr });
|
|
596
600
|
if (recentCalls.length > LOOP_DETECT_WINDOW) recentCalls.shift();
|
|
601
|
+
let loopType = null; // 'consecutive' | 'pattern' | null
|
|
597
602
|
if (recentCalls.length >= LOOP_DETECT_THRESHOLD + 1) {
|
|
603
|
+
// (A) Same consecutive check
|
|
598
604
|
const lastN = recentCalls.slice(-LOOP_DETECT_THRESHOLD);
|
|
599
605
|
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
|
-
|
|
606
|
+
if (allSame) loopType = 'consecutive';
|
|
607
|
+
}
|
|
608
|
+
// (B) Pattern cycle check: tìm chu kỳ lặp trong window (độ dài 2-3)
|
|
609
|
+
if (!loopType && recentCalls.length >= 4) {
|
|
610
|
+
for (let cycleLen = 2; cycleLen <= Math.min(3, Math.floor(recentCalls.length / 2)); cycleLen++) {
|
|
611
|
+
const half = Math.floor(recentCalls.length / cycleLen) * cycleLen;
|
|
612
|
+
const first = recentCalls.slice(-half, -half + cycleLen);
|
|
613
|
+
const rest = recentCalls.slice(-half + cycleLen);
|
|
614
|
+
let matched = true;
|
|
615
|
+
for (let i = 0; i < rest.length; i++) {
|
|
616
|
+
if (rest[i].name !== first[i % cycleLen].name || rest[i].inputStr !== first[i % cycleLen].inputStr) {
|
|
617
|
+
matched = false;
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
610
620
|
}
|
|
611
|
-
|
|
612
|
-
|
|
621
|
+
if (matched) { loopType = 'pattern'; break; }
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (loopType) {
|
|
625
|
+
const label = loopType === 'consecutive' ? lastN[0].name : `pattern [${recentCalls.slice(-4).map((c) => c.name).join(", ")}]`;
|
|
626
|
+
loopDetectedCount++;
|
|
627
|
+
if (loopDetectedCount >= MAX_LOOP_DETECTIONS) {
|
|
613
628
|
history.push({
|
|
614
629
|
role: "tool",
|
|
615
630
|
name: "loop_detection",
|
|
616
|
-
content: `[LOOP DETECTED × ${loopDetectedCount}] Bạn
|
|
631
|
+
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
632
|
});
|
|
618
|
-
|
|
619
|
-
loopDetectedCount = 0; // model gọi tool khác → reset counter
|
|
633
|
+
return `[LOOP STOPPED] Đã dừng vì model bị kẹt trong vòng lặp.`;
|
|
620
634
|
}
|
|
635
|
+
recentCalls.length = 0;
|
|
636
|
+
history.push({
|
|
637
|
+
role: "tool",
|
|
638
|
+
name: "loop_detection",
|
|
639
|
+
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.`,
|
|
640
|
+
});
|
|
641
|
+
} else {
|
|
642
|
+
loopDetectedCount = 0; // tool khác → reset
|
|
621
643
|
}
|
|
622
644
|
}
|
|
623
645
|
return t.maxSteps;
|