@noobdemon/noob-cli 1.6.0 → 1.7.1
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/repl.js +91 -23
- package/src/subagent.js +9 -4
package/package.json
CHANGED
package/src/repl.js
CHANGED
|
@@ -30,6 +30,8 @@ const SLASH = [
|
|
|
30
30
|
{ name: "/init", desc: "quét dự án & tạo noob.md" },
|
|
31
31
|
{ name: "/karpathy", desc: "rà soát code (Karpathy)" },
|
|
32
32
|
{ name: "/ultra", desc: "tự hành: tự nghĩ & làm nhiệm vụ" },
|
|
33
|
+
{ name: "/agent", desc: "bật/tắt agent mode (spawn sub-agent)" },
|
|
34
|
+
{ name: "/tokens", desc: "xem số token đã dùng phiên này" },
|
|
33
35
|
{ name: "/learn", desc: "chưng cất bài học vào noob.md" },
|
|
34
36
|
{ name: "/memory", desc: "xem bộ nhớ noob.md" },
|
|
35
37
|
{ name: "/login", desc: "đăng nhập bằng API key" },
|
|
@@ -129,7 +131,9 @@ export async function startRepl(opts = {}) {
|
|
|
129
131
|
autoApprove: new Set(),
|
|
130
132
|
yolo: !!opts.yolo || config.yoloDefault, // cờ --yolo HOẶC mặc định đã lưu (/auto-yolo)
|
|
131
133
|
ultra: false, // chế độ tự hành (self-quest) đang chạy?
|
|
134
|
+
agentMode: false, // /agent on → cho phép spawn_agent / spawn_agents
|
|
132
135
|
};
|
|
136
|
+
const tokenMeter = new TokenMeter();
|
|
133
137
|
|
|
134
138
|
// Prompt = dòng trạng thái sống. Luôn phản ánh yolo + version theo thời gian
|
|
135
139
|
// thực (vẽ lại mỗi lượt và ngay khi Shift+Tab), nên không cần gõ /status.
|
|
@@ -379,24 +383,39 @@ Tự đánh giá: còn THIẾU gì để đạt mục tiêu? Chọn 1 nhiệm v
|
|
|
379
383
|
state.mode = "chat"; // tự hành chỉ chạy ở chế độ agent
|
|
380
384
|
state.ultra = true;
|
|
381
385
|
console.log(c.accent(" 🚀 " + t.ultraOn));
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// "tiếp tục" có cổng kiểm chứng; chỉ dừng khi token nằm ở CUỐI câu trả lời.
|
|
387
|
-
while (state.ultra && i < MAX_QUESTS) {
|
|
388
|
-
if (!answer) break; // lượt bị ngắt/ lỗi → dừng tự hành, đừng quay vô ích
|
|
389
|
-
i++;
|
|
390
|
-
console.log(c.accent(" ↻ " + t.ultraQuest(i)));
|
|
391
|
-
answer = await handle(ultraContinue(goal));
|
|
386
|
+
// Mốc history TRƯỚC khi ULTRA bơm prompt — kết thúc thì cắt về để các lượt sau không bị "dính" mục tiêu cũ.
|
|
387
|
+
const baseLen = state.history.length;
|
|
388
|
+
try {
|
|
389
|
+
let answer = await handle(ultraStart(goal));
|
|
392
390
|
persist();
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
391
|
+
let i = 0;
|
|
392
|
+
// Lượt đầu = lập kế hoạch → KHÔNG xét hoàn thành. Mỗi vòng sau là một lượt
|
|
393
|
+
// "tiếp tục" có cổng kiểm chứng; chỉ dừng khi token nằm ở CUỐI câu trả lời.
|
|
394
|
+
while (state.ultra && i < MAX_QUESTS) {
|
|
395
|
+
if (!answer) break; // lượt bị ngắt/ lỗi → dừng tự hành, đừng quay vô ích
|
|
396
|
+
i++;
|
|
397
|
+
console.log(c.accent(" ↻ " + t.ultraQuest(i)));
|
|
398
|
+
answer = await handle(ultraContinue(goal));
|
|
399
|
+
persist();
|
|
400
|
+
if (answer && ultraIsDone(answer)) {
|
|
401
|
+
console.log(c.ok(" ✓ " + t.ultraDone));
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
396
404
|
}
|
|
405
|
+
if (state.ultra && i >= MAX_QUESTS) console.log(c.tool(" " + t.ultraMax));
|
|
406
|
+
} finally {
|
|
407
|
+
// Dọn dấu vết ULTRA khỏi history (prompt khởi động, các lượt "tiếp tục",
|
|
408
|
+
// token <<ULTRA_DONE>>…) để các yêu cầu SAU đó không bị model coi như vẫn
|
|
409
|
+
// đang tự hành / vẫn theo đuổi mục tiêu cũ.
|
|
410
|
+
state.ultra = false;
|
|
411
|
+
if (state.history.length > baseLen) state.history.length = baseLen;
|
|
412
|
+
state.history.push({
|
|
413
|
+
role: "user",
|
|
414
|
+
content: "[Phiên ULTRA đã KẾT THÚC — mục tiêu cũ: " + goal + ". Bỏ qua mọi chỉ dẫn ULTRA trước đó, KHÔNG tự hành tiếp, KHÔNG phát token " + ULTRA_DONE + ". Chờ yêu cầu mới.]",
|
|
415
|
+
});
|
|
416
|
+
state.history.push({ role: "assistant", content: "OK, đã thoát chế độ ULTRA." });
|
|
417
|
+
persist();
|
|
397
418
|
}
|
|
398
|
-
if (state.ultra && i >= MAX_QUESTS) console.log(c.tool(" " + t.ultraMax));
|
|
399
|
-
state.ultra = false;
|
|
400
419
|
}
|
|
401
420
|
|
|
402
421
|
// /init — quét dự án & sinh noob.md tổng quan (giống `/init` của Claude Code).
|
|
@@ -584,7 +603,9 @@ NGUYÊN TẮC:
|
|
|
584
603
|
let timer = null;
|
|
585
604
|
const tick = (label) => {
|
|
586
605
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(0);
|
|
587
|
-
|
|
606
|
+
// Chèn token usage realtime (↑input ↓output) ngay cạnh spinner để người
|
|
607
|
+
// dùng thấy được số token đang cộng dồn trong khi model stream.
|
|
608
|
+
tui.status(c.dim(`${label}… ${elapsed}s · ${tokenMeter.format()}`));
|
|
588
609
|
};
|
|
589
610
|
const stopSpin = () => {
|
|
590
611
|
if (timer) {
|
|
@@ -609,7 +630,8 @@ NGUYÊN TẮC:
|
|
|
609
630
|
message: text,
|
|
610
631
|
signal: abort.signal,
|
|
611
632
|
onStatus: (s) => {
|
|
612
|
-
|
|
633
|
+
// Kèm token meter để nhánh merge/search cũng thấy ↑input ↓output realtime.
|
|
634
|
+
if (!printer.started) tui.status(c.dim(` ${s} · ${tokenMeter.format()}`));
|
|
613
635
|
},
|
|
614
636
|
onDelta: (d) => {
|
|
615
637
|
stopSpin();
|
|
@@ -634,10 +656,49 @@ NGUYÊN TẮC:
|
|
|
634
656
|
startSpin(t.thinking);
|
|
635
657
|
let printer = null;
|
|
636
658
|
|
|
659
|
+
const dispatchTool = async (name, input, depth = 0) => {
|
|
660
|
+
// spawn_agent / spawn_agents chỉ được phép khi agentMode bật; depth giới hạn
|
|
661
|
+
// bởi MAX_SUBAGENT_DEPTH để tránh đệ quy nổ.
|
|
662
|
+
if (name === "spawn_agent" || name === "spawn_agents") {
|
|
663
|
+
if (!state.agentMode)
|
|
664
|
+
return { allow: true, result: "ERROR: agent mode đang TẮT — gõ /agent on để bật trước khi spawn." };
|
|
665
|
+
if (depth >= MAX_SUBAGENT_DEPTH)
|
|
666
|
+
return { allow: true, result: `ERROR: đã đạt depth tối đa (${MAX_SUBAGENT_DEPTH}) — không spawn thêm.` };
|
|
667
|
+
const tasks = name === "spawn_agent" ? [input] : (Array.isArray(input?.agents) ? input.agents : []);
|
|
668
|
+
if (!tasks.length) return { allow: true, result: "ERROR: thiếu task cho sub-agent." };
|
|
669
|
+
stopSpin();
|
|
670
|
+
console.log(chalk.hex("#8b5cf6")(` ⊕ spawn ${tasks.length} sub-agent (depth ${depth + 1}/${MAX_SUBAGENT_DEPTH})`));
|
|
671
|
+
startSpin(t.thinking);
|
|
672
|
+
try {
|
|
673
|
+
const results = await Promise.all(tasks.map((task, i) =>
|
|
674
|
+
runSubAgent({
|
|
675
|
+
task: task?.task || task?.prompt || "",
|
|
676
|
+
context: task?.context || "",
|
|
677
|
+
depth: depth + 1,
|
|
678
|
+
model: state.model.id,
|
|
679
|
+
signal: abort.signal,
|
|
680
|
+
tokenMeter,
|
|
681
|
+
dispatchTool: (n, inp) => dispatchTool(n, inp, depth + 1),
|
|
682
|
+
onLog: (msg) => { stopSpin(); console.log(chalk.hex("#8b5cf6")(" " + msg)); startSpin(t.thinking); },
|
|
683
|
+
}).then((r) => `── sub-agent #${i + 1} ──\n${r}`).catch((e) => `── sub-agent #${i + 1} (LỖI) ──\n${e?.message || String(e)}`)
|
|
684
|
+
));
|
|
685
|
+
return { allow: true, result: results.join("\n\n") };
|
|
686
|
+
} catch (err) {
|
|
687
|
+
return { allow: true, result: "ERROR sub-agent: " + (err?.message || String(err)) };
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
stopSpin();
|
|
691
|
+
const res = await execTool(name, input);
|
|
692
|
+
startSpin(t.thinking);
|
|
693
|
+
return res;
|
|
694
|
+
};
|
|
695
|
+
|
|
637
696
|
const answer = await runAgent({
|
|
638
697
|
history: state.history,
|
|
639
698
|
model: state.model.id,
|
|
640
699
|
signal: abort.signal,
|
|
700
|
+
tokenMeter,
|
|
701
|
+
extraToolsDoc: state.agentMode ? spawnAgentToolsDoc(0) : "",
|
|
641
702
|
onStatus: () => tick(t.thinking),
|
|
642
703
|
onSteer: () => {
|
|
643
704
|
if (!pending.length) return [];
|
|
@@ -659,12 +720,7 @@ NGUYÊN TẮC:
|
|
|
659
720
|
printer?.flush();
|
|
660
721
|
}
|
|
661
722
|
},
|
|
662
|
-
onTool:
|
|
663
|
-
stopSpin();
|
|
664
|
-
const res = await execTool(name, input);
|
|
665
|
-
startSpin(t.thinking);
|
|
666
|
-
return res;
|
|
667
|
-
},
|
|
723
|
+
onTool: (name, input) => dispatchTool(name, input, 0),
|
|
668
724
|
});
|
|
669
725
|
|
|
670
726
|
stopSpin();
|
|
@@ -773,6 +829,18 @@ NGUYÊN TẮC:
|
|
|
773
829
|
state.yolo = !state.yolo;
|
|
774
830
|
console.log((state.yolo ? c.err : c.ok)(" " + (state.yolo ? t.yoloOn : t.yoloOff)));
|
|
775
831
|
break;
|
|
832
|
+
case "agent": {
|
|
833
|
+
const v = arg.toLowerCase();
|
|
834
|
+
if (v === "on" || v === "bật" || v === "bat") state.agentMode = true;
|
|
835
|
+
else if (v === "off" || v === "tắt" || v === "tat") state.agentMode = false;
|
|
836
|
+
else state.agentMode = !state.agentMode;
|
|
837
|
+
console.log((state.agentMode ? c.accent : c.dim)(" agent mode: " + (state.agentMode ? "BẬT (spawn_agent / spawn_agents khả dụng, depth tối đa " + MAX_SUBAGENT_DEPTH + ")" : "tắt")));
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
case "tokens": {
|
|
841
|
+
console.log(c.dim(` tokens — input: ${tokenMeter.input.toLocaleString("vi-VN")} · output: ${tokenMeter.output.toLocaleString("vi-VN")} · tổng: ${tokenMeter.total.toLocaleString("vi-VN")} · ${tokenMeter.format()}`));
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
776
844
|
case "auto-yolo":
|
|
777
845
|
case "autoyolo":
|
|
778
846
|
await toggleAutoYolo();
|
package/src/subagent.js
CHANGED
|
@@ -31,14 +31,17 @@ Ví dụ phân cấp: cha giao "build full app" → đẻ 1 sub-agent "build bac
|
|
|
31
31
|
|
|
32
32
|
// Chạy một sub-agent. dispatchTool: hàm để thực thi tool con (chia sẻ với cha).
|
|
33
33
|
// model: dùng chung model của cha. onLog: callback để log tiến độ ra UI cha.
|
|
34
|
-
export async function runSubAgent({ task, context, model, signal, dispatchTool, depth = 1, onLog }) {
|
|
34
|
+
export async function runSubAgent({ task, context, model, signal, dispatchTool, depth = 1, onLog, tokenMeter }) {
|
|
35
35
|
const sys = `Bạn là SUB-AGENT (depth=${depth}) được agent cha ủy thác MỘT nhiệm vụ cụ thể. Làm xong → trả lời NGẮN GỌN bằng Markdown tóm tắt KẾT QUẢ (file đã đụng, phát hiện, lỗi nếu có). Không tán gẫu. Không hỏi lại cha — tự quyết với thông tin được cấp.
|
|
36
36
|
|
|
37
37
|
# NHIỆM VỤ
|
|
38
38
|
${task}
|
|
39
39
|
${context ? `\n# NGỮ CẢNH TỪ CHA\n${context}` : ""}`;
|
|
40
40
|
const history = [{ role: "user", content: sys }];
|
|
41
|
-
|
|
41
|
+
// Dùng chung meter của cha nếu được truyền vào → token sub-agent cộng dồn
|
|
42
|
+
// vào tổng phiên. Nếu không có thì tự tạo cục bộ (giữ tương thích cũ).
|
|
43
|
+
const meter = tokenMeter || new TokenMeter();
|
|
44
|
+
const before = { input: meter.input, output: meter.output };
|
|
42
45
|
onLog?.(`↳ sub-agent (depth=${depth}) bắt đầu: ${task.slice(0, 80)}${task.length > 80 ? "…" : ""}`);
|
|
43
46
|
const result = await runAgent({
|
|
44
47
|
history,
|
|
@@ -51,6 +54,8 @@ ${context ? `\n# NGỮ CẢNH TỪ CHA\n${context}` : ""}`;
|
|
|
51
54
|
onDelta: () => {},
|
|
52
55
|
onSteer: () => [],
|
|
53
56
|
});
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const used = { input: meter.input - before.input, output: meter.output - before.output };
|
|
58
|
+
onLog?.(`↳ sub-agent (depth=${depth}) xong (↑${used.input} ↓${used.output})`);
|
|
59
|
+
// Trả về string sạch để cha (model) đọc dễ. Token đã cộng vào meter rồi.
|
|
60
|
+
return result;
|
|
56
61
|
}
|