@noobdemon/noob-cli 1.9.4 → 1.9.6
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 +96 -2
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -76,6 +76,67 @@ Có — cả 12 test đều pass.
|
|
|
76
76
|
|
|
77
77
|
Follow this pattern exactly. Your very first response to a task that needs the filesystem MUST be a tool block — do not refuse or explain limitations.`;
|
|
78
78
|
|
|
79
|
+
// ── Effort classifier ──────────────────────────────────────────────────────
|
|
80
|
+
// Phân loại mức độ phức tạp task để set effort level cho model:
|
|
81
|
+
// low — câu hỏi đơn, đọc 1 file, grep nhanh, list dir
|
|
82
|
+
// medium — đa file, edit vừa, chạy test/build, task coding thông thường
|
|
83
|
+
// high — refactor lớn, debug phức tạp, multi-step agentic, workflow
|
|
84
|
+
// Effort直接影响: số token model dùng, thời gian suy nghĩ, số tool call.
|
|
85
|
+
// LOW/MEDIUM = model skip thinking cho vấn đề đơn → nhanh hơn nhiều.
|
|
86
|
+
const LOW_PATTERNS = [
|
|
87
|
+
/^(list|ls|dir)\s/i,
|
|
88
|
+
/^(xem|hiện|đọc|read)\s+(file|thư mục|folder)/i,
|
|
89
|
+
/^(tìm|find|grep|search)\s+.{0,30}$/i,
|
|
90
|
+
/^(có|is|are|was|were)\s+.+\?$/i,
|
|
91
|
+
/^(version|phiên bản)\s*\??$/i,
|
|
92
|
+
/^(help|trợ giúp|help)\s*$/i,
|
|
93
|
+
/^(cwd|thư mục hiện tại)\s*$/i,
|
|
94
|
+
/^(status|trạng thái)\s*$/i,
|
|
95
|
+
/^(tokens?|token)\s*$/i,
|
|
96
|
+
/^(memory|noob\.md)\s*$/i,
|
|
97
|
+
/^(logout|đăng xuất)\s*$/i,
|
|
98
|
+
/^@/, // @file reference — typically a quick read
|
|
99
|
+
];
|
|
100
|
+
const MEDIUM_PATTERNS = [
|
|
101
|
+
/^(edit|sửa|fix|thay đổi)\s/i,
|
|
102
|
+
/^(thêm|add|tạo|create|write)\s+(file|function|hàm|class|module)/i,
|
|
103
|
+
/^(chạy|run)\s+(test|build|lint|npm|npx)/i,
|
|
104
|
+
/^(đọc|read)\s+\S+\s+\S+/, // read with multiple files
|
|
105
|
+
/^(so sánh|compare|diff)\s/i,
|
|
106
|
+
/^(tóm tắt|summarize|overview)\s/i,
|
|
107
|
+
/^(cập nhật|update|upgrade)\s/i,
|
|
108
|
+
/^(triển khai|deploy|publish)\s/i,
|
|
109
|
+
/^(install|cài đặt)\s/i,
|
|
110
|
+
];
|
|
111
|
+
const HIGH_PATTERNS = [
|
|
112
|
+
/(refactor|tái cấu trúc|đóng gói|restructure)/i,
|
|
113
|
+
/(implement|triển khai|xây dựng|build)\s+(hệ thống|system|feature|tính năng)/i,
|
|
114
|
+
/(debug|gỡ lỗi|tìm nguyên nhân|root cause)/i,
|
|
115
|
+
/(workflow|multi-agent|orchestrat|pipeline)/i,
|
|
116
|
+
/(architecture|kiến trúc|thiết kế|design)\s+(system|module)/i,
|
|
117
|
+
/(migrate|di chuyển|chuyển đổi)\s+(from|từ)/i,
|
|
118
|
+
/(review|rà soát|kiểm tra)\s+(code|toàn bộ|all)/i,
|
|
119
|
+
/(audit|kiểm toán|security|bảo mật)/i,
|
|
120
|
+
/(performance|hiệu năng|optimize|tối ưu)/i,
|
|
121
|
+
/(test|kiểm chứng)\s+(toàn bộ|all|comprehensive|end.to.end)/i,
|
|
122
|
+
/(tạo|create|write)\s+(noob\.md|SKILL|skill|workflow)/i,
|
|
123
|
+
/(ghi|write)\s+.+\s+(vào|into|to)\s+.+/i, // write X into Y — multi-step
|
|
124
|
+
/\b(ultra|goal|workflow)\b/i,
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
export function classifyEffort(userMessage) {
|
|
128
|
+
const msg = (userMessage || "").trim();
|
|
129
|
+
if (!msg) return "medium";
|
|
130
|
+
// Kiểm high TRƯỚC (nhiều pattern hơn, ưu tiên)
|
|
131
|
+
for (const rx of HIGH_PATTERNS) if (rx.test(msg)) return "high";
|
|
132
|
+
// Kiểm low TRƯỚC medium — các thao tác đọc/list đơn nên ưu tiên low
|
|
133
|
+
for (const rx of LOW_PATTERNS) if (rx.test(msg)) return "low";
|
|
134
|
+
// Kiểm medium
|
|
135
|
+
for (const rx of MEDIUM_PATTERNS) if (rx.test(msg)) return "medium";
|
|
136
|
+
// Mặc định: message dài (>200 chars) → medium, ngắn → low
|
|
137
|
+
return msg.length > 200 ? "medium" : "low";
|
|
138
|
+
}
|
|
139
|
+
|
|
79
140
|
// Số bước tool tối đa cho một lượt. Đặt rất cao theo yêu cầu người dùng: task
|
|
80
141
|
// dài cứ chạy, đừng tự dừng. Người dùng vẫn có thể Ctrl+C bất cứ lúc nào.
|
|
81
142
|
const MAX_STEPS = 10000;
|
|
@@ -355,6 +416,25 @@ function buildUserMessage(history) {
|
|
|
355
416
|
return parts.join("\n");
|
|
356
417
|
}
|
|
357
418
|
|
|
419
|
+
// Detect câu trả lời bị cắt giữa chừng — KHÔNG phải câu hoàn chỉnh.
|
|
420
|
+
// Trả true nếu text kết thúc đột ngột (thiếu dấu câu, list chưa đóng, v.v.).
|
|
421
|
+
function isIncompleteResponse(text) {
|
|
422
|
+
if (!text) return false;
|
|
423
|
+
const t = text.trimEnd();
|
|
424
|
+
// Kết thúc bằng dấu cuối danh sách chưa đóng: "A.", "B.", "1.", "(1)" mà không có gì sau
|
|
425
|
+
if (/[A-Z]\.\s*$/.test(t) || /\(\d+\)\s*$/.test(t) || /^\d+\.\s*$/m.test(t)) return true;
|
|
426
|
+
// Kết thúc giữa câu — không có dấu câu cuối cùng (. ! ? : ; ) và không phải markdown/code
|
|
427
|
+
const lastChar = t.slice(-1);
|
|
428
|
+
if (lastChar && !/[.!?:;)\]"'`#>\n]/.test(lastChar) && t.length > 50) {
|
|
429
|
+
// Kiểm thêm: dòng cuối chứa từ khóa bị cắt (ví dụ, vd, hay, hoặc, và, nhưng, mà)
|
|
430
|
+
const lastLine = t.split("\n").pop().trim();
|
|
431
|
+
if (/\s(ví|vd|hay|hoặc|và|nhưng|mà|hoac|or|and|but|e\.g|i\.e)\s*$/i.test(lastLine)) return true;
|
|
432
|
+
// Dòng cuối là 1 câu bắt đầu nhưng chưa xong (có chủ ngữ nhưng không có vị ngữ hoàn chỉnh)
|
|
433
|
+
if (/\s(Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn|Bạn)\s+(muốn|có|thấy|nên|cần|đã|đang|sẽ|chọn|chọn|chọn)\s*$/i.test(lastLine)) return true;
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
|
|
358
438
|
// Extract a single tool call from an assistant message, if present.
|
|
359
439
|
// NOTE: we do NOT match up to a closing ``` fence — write_file content routinely
|
|
360
440
|
// contains its own ```code``` fences (e.g. a README), and the first inner fence
|
|
@@ -416,6 +496,9 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
|
|
|
416
496
|
// chạy không giới hạn token. Dừng theo: GOAL đạt, <<LOOP_DONE>>, <<ULTRA_DONE>>,
|
|
417
497
|
// model tự kết thúc reply không có tool block, hoặc user Ctrl+C.
|
|
418
498
|
const recentCalls = []; // {name, inputStr} — theo dõi vòng lặp
|
|
499
|
+
// Effort classifier: phân loại task từ user message gốc → set effort level.
|
|
500
|
+
// 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
|
+
const effort = classifyEffort(history.find((m) => m.role === "user")?.content || "");
|
|
419
502
|
for (let step = 0; step < MAX_STEPS; step++) {
|
|
420
503
|
// Mỗi 100 bước log một mốc để người dùng biết noob vẫn đang chạy (task dài).
|
|
421
504
|
if (step > 0 && step % 100 === 0) onStatus?.(`đã chạy ${step} bước…`);
|
|
@@ -438,7 +521,7 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
|
|
|
438
521
|
// trường hợp api.js trả về với finishReason bất thường (tool_unclosed/empty) hoặc
|
|
439
522
|
// throw ApiError retryable (network drop, 5xx, timeout).
|
|
440
523
|
const { text, finishReason } = await streamWithRetry({
|
|
441
|
-
model, message, system, signal, tokenMeter, onDelta, onStatus,
|
|
524
|
+
model, message, system, signal, tokenMeter, onDelta, onStatus, effort,
|
|
442
525
|
});
|
|
443
526
|
tokenMeter?.endOutput();
|
|
444
527
|
onDelta?.({ type: "step-end" });
|
|
@@ -459,6 +542,16 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
|
|
|
459
542
|
});
|
|
460
543
|
continue;
|
|
461
544
|
}
|
|
545
|
+
// Phát hiện câu trả lời bị cắt giữa chừng (truncated stream): text kết thúc
|
|
546
|
+
// đột ngột không có dấu câu/đóng danh sách/tool block → nudge model viết tiếp.
|
|
547
|
+
if (isIncompleteResponse(text)) {
|
|
548
|
+
history.push({
|
|
549
|
+
role: "tool",
|
|
550
|
+
name: "stream_recovery",
|
|
551
|
+
content: "[STREAM CUT] Lượt vừa bị cắt giữa chừng. Hãy viết TIẾP liền mạch — nếu đang hỏi user thì hoàn tất câu hỏi, nếu đang list thì đóng danh sách, nếu đang viết tool block thì đóng đúng JSON.",
|
|
552
|
+
});
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
462
555
|
return text; // final answer
|
|
463
556
|
}
|
|
464
557
|
|
|
@@ -500,7 +593,7 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
|
|
|
500
593
|
* backoff (1s, 2s, 4s, 8s, max 30s), tối đa 8 lần thử trước khi bỏ cuộc.
|
|
501
594
|
* - Throw lại nếu signal abort hoặc lỗi không retryable.
|
|
502
595
|
*/
|
|
503
|
-
async function streamWithRetry({ model, message, system, signal, tokenMeter, onDelta, onStatus }) {
|
|
596
|
+
async function streamWithRetry({ model, message, system, signal, tokenMeter, onDelta, onStatus, effort }) {
|
|
504
597
|
const MAX_RETRIES = 8;
|
|
505
598
|
let lastErr = null;
|
|
506
599
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -511,6 +604,7 @@ async function streamWithRetry({ model, message, system, signal, tokenMeter, onD
|
|
|
511
604
|
message,
|
|
512
605
|
system,
|
|
513
606
|
signal,
|
|
607
|
+
effort,
|
|
514
608
|
onDelta: (d) => {
|
|
515
609
|
tokenMeter?.pushOutputDelta(d);
|
|
516
610
|
onDelta?.({ type: "delta", text: d });
|