@noobdemon/noob-cli 1.10.4 → 1.10.5
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 +7 -19
- package/src/repl.js +75 -3
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -392,7 +392,7 @@ export function relTime(ts) {
|
|
|
392
392
|
// chèn ngay sau SYSTEM để model biết và dùng được.
|
|
393
393
|
// recentSessions: breadcrumbs các phiên trước cùng workspace (repl.js cung cấp)
|
|
394
394
|
// → chèn ngay sau memoryBlock() để model "thấy" lịch sử dù chưa /resume.
|
|
395
|
-
function buildSystem(history, extraToolsDoc, goal, recentSessions) {
|
|
395
|
+
export function buildSystem(history, extraToolsDoc, goal, recentSessions) {
|
|
396
396
|
const parts = [SYSTEM, "", memoryBlock()];
|
|
397
397
|
if (recentSessions && recentSessions.length) {
|
|
398
398
|
parts.push("", recentSessionsBlock(recentSessions));
|
|
@@ -403,7 +403,7 @@ function buildSystem(history, extraToolsDoc, goal, recentSessions) {
|
|
|
403
403
|
return parts.join("\n");
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
function buildUserMessage(history) {
|
|
406
|
+
export function buildUserMessage(history) {
|
|
407
407
|
const msgs = compact(history, MAX_PROMPT_CHARS);
|
|
408
408
|
const parts = [filesLedger(history), "", "=".repeat(60), "# CONVERSATION", ""];
|
|
409
409
|
for (const m of msgs) {
|
|
@@ -496,11 +496,14 @@ 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 idleRecoveries = 0; // đếm số lần idle recovery —max 5 để tránh infinite loop
|
|
500
499
|
// Effort classifier: phân loại task từ user message gốc → set effort level.
|
|
501
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).
|
|
502
501
|
const effort = classifyEffort(history.find((m) => m.role === "user")?.content || "");
|
|
503
502
|
for (let step = 0; step < MAX_STEPS; step++) {
|
|
503
|
+
// Check abort signal ngay đầu mỗi iteration để Ctrl+C dừng ngay lập tức
|
|
504
|
+
if (signal?.aborted) {
|
|
505
|
+
throw new Error("aborted");
|
|
506
|
+
}
|
|
504
507
|
// 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).
|
|
505
508
|
if (step > 0 && step % 100 === 0) onStatus?.(`đã chạy ${step} bước…`);
|
|
506
509
|
|
|
@@ -556,22 +559,7 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
|
|
|
556
559
|
});
|
|
557
560
|
continue;
|
|
558
561
|
}
|
|
559
|
-
//
|
|
560
|
-
// Nếu pendingTasks còn items → KHÔNG return, inject nudge rồi continue loop.
|
|
561
|
-
// Giới hạn 5 lần recovery để tránh infinite loop.
|
|
562
|
-
{
|
|
563
|
-
const tasks = pendingTasks || [];
|
|
564
|
-
const remaining = tasks.length;
|
|
565
|
-
if (remaining > 0 && idleRecoveries < 5) {
|
|
566
|
-
idleRecoveries++;
|
|
567
|
-
const next = tasks[0];
|
|
568
|
-
history.push({
|
|
569
|
-
role: "user",
|
|
570
|
-
content: `[SYSTEM] Bạn vừa dừng giữa chừng. Còn ${remaining} việc chưa hoàn thành: ${tasks.map((t) => `"${t}"`).join(", ")}. Việc tiếp theo BẮT BUỘC phải làm ngay: "${next}". Gọi tool (write_file/edit_file/run_command) để làm việc này. KHÔNG dừng, KHÔNG tóm tắt, KHÔNG đợi user.`,
|
|
571
|
-
});
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
562
|
+
// Model dừng (không tool call, không incomplete) → return để repl quyết định tiếp tục hay không
|
|
575
563
|
return text; // final answer
|
|
576
564
|
}
|
|
577
565
|
|
package/src/repl.js
CHANGED
|
@@ -3,9 +3,9 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { createTui } from "./tui.js";
|
|
6
|
-
import { runAgent, maybeSummarize } from "./agent.js";
|
|
6
|
+
import { runAgent, maybeSummarize, buildSystem, buildUserMessage } from "./agent.js";
|
|
7
7
|
import { runSubAgent, spawnAgentToolsDoc, MAX_SUBAGENT_DEPTH } from "./subagent.js";
|
|
8
|
-
import { TokenMeter, countMessages, CONTEXT_WINDOW } from "./tokens.js";
|
|
8
|
+
import { TokenMeter, countMessages, CONTEXT_WINDOW, countTokens } from "./tokens.js";
|
|
9
9
|
import { stream, usage, ApiError, resetMemoryToken } from "./api.js";
|
|
10
10
|
import { runTool, describe, DESTRUCTIVE, addRoot, removeRoot, listRoots, OutOfScopeError, nearestExistingDir } from "./tools.js";
|
|
11
11
|
import { MODELS, PROVIDERS, findModel, providerColor, DEFAULT_MODEL } from "./models.js";
|
|
@@ -1254,7 +1254,9 @@ NGUYÊN TẮC:
|
|
|
1254
1254
|
updateTitle();
|
|
1255
1255
|
}
|
|
1256
1256
|
// Tính context tokens realtime — đếm system prompt + history trước khi gửi.
|
|
1257
|
-
|
|
1257
|
+
const systemPrompt = buildSystem(state.history, state.agentMode ? spawnAgentToolsDoc(0) : "", state.goal, sessions.list(5, process.cwd()).filter((s) => s.id !== session?.id));
|
|
1258
|
+
const userMessage = buildUserMessage(state.history);
|
|
1259
|
+
tokenMeter.setContext(countTokens(systemPrompt) + countTokens(userMessage));
|
|
1258
1260
|
if (process.stdin.isTTY && !state.steerHintShown) {
|
|
1259
1261
|
console.log(c.dim(" " + t.steerHint));
|
|
1260
1262
|
state.steerHintShown = true;
|
|
@@ -1348,6 +1350,76 @@ NGUYÊN TẮC:
|
|
|
1348
1350
|
// Lượt cuối không stream ra gì (vd model suy luận trả 1 cục) → in fallback.
|
|
1349
1351
|
if ((!printer || !printer.started) && answer?.trim())
|
|
1350
1352
|
printAnswer(answer, state.model.name, providerColor(state.model.provider));
|
|
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
|
+
|
|
1351
1423
|
// Parse todo từ model output → render trên status bar.
|
|
1352
1424
|
state.todos = parseTodosFromHistory(state.history);
|
|
1353
1425
|
tui.setTodos(state.todos);
|