@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/agent.js +43 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noobdemon/noob-cli",
3
- "version": "1.10.14",
3
+ "version": "1.10.16",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
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
- const LOOP_DETECT_WINDOW = 3;
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. Nếu cùng tên + input giống nhau liên tiếp
592
- // quá ngưỡng model bị kẹt. Inject thông báo nhắc model chuyển bước.
593
- // Nếu model vẫn lặp force stop sau MAX_LOOP_DETECTIONS lần.
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
- loopDetectedCount++;
602
- // Đã lặp quá nhiều lần liên tiếp force stop
603
- if (loopDetectedCount >= MAX_LOOP_DETECTIONS) {
604
- history.push({
605
- role: "tool",
606
- name: "loop_detection",
607
- 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.`,
608
- });
609
- return `[LOOP STOPPED] Đã dừng model bị kẹt trong vòng lặp gọi ${lastN[0].name} liên tục.`;
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
- // Đã lặp inject cảnh báo, xoá calls để tránh trigger liên tục
612
- recentCalls.length = 0;
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 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.`,
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
- } else {
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;