@noobdemon/noob-cli 1.6.0 → 1.7.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noobdemon/noob-cli",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
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.
@@ -634,10 +638,49 @@ NGUYÊN TẮC:
634
638
  startSpin(t.thinking);
635
639
  let printer = null;
636
640
 
641
+ const dispatchTool = async (name, input, depth = 0) => {
642
+ // spawn_agent / spawn_agents chỉ được phép khi agentMode bật; depth giới hạn
643
+ // bởi MAX_SUBAGENT_DEPTH để tránh đệ quy nổ.
644
+ if (name === "spawn_agent" || name === "spawn_agents") {
645
+ if (!state.agentMode)
646
+ return { allow: true, result: "ERROR: agent mode đang TẮT — gõ /agent on để bật trước khi spawn." };
647
+ if (depth >= MAX_SUBAGENT_DEPTH)
648
+ return { allow: true, result: `ERROR: đã đạt depth tối đa (${MAX_SUBAGENT_DEPTH}) — không spawn thêm.` };
649
+ const tasks = name === "spawn_agent" ? [input] : (Array.isArray(input?.agents) ? input.agents : []);
650
+ if (!tasks.length) return { allow: true, result: "ERROR: thiếu task cho sub-agent." };
651
+ stopSpin();
652
+ console.log(chalk.hex("#8b5cf6")(` ⊕ spawn ${tasks.length} sub-agent (depth ${depth + 1}/${MAX_SUBAGENT_DEPTH})`));
653
+ startSpin(t.thinking);
654
+ try {
655
+ const results = await Promise.all(tasks.map((task, i) =>
656
+ runSubAgent({
657
+ task: task?.task || task?.prompt || "",
658
+ context: task?.context || "",
659
+ depth: depth + 1,
660
+ model: state.model.id,
661
+ signal: abort.signal,
662
+ tokenMeter,
663
+ dispatchTool: (n, inp) => dispatchTool(n, inp, depth + 1),
664
+ onLog: (msg) => { stopSpin(); console.log(chalk.hex("#8b5cf6")(" " + msg)); startSpin(t.thinking); },
665
+ }).then((r) => `── sub-agent #${i + 1} ──\n${r}`).catch((e) => `── sub-agent #${i + 1} (LỖI) ──\n${e?.message || String(e)}`)
666
+ ));
667
+ return { allow: true, result: results.join("\n\n") };
668
+ } catch (err) {
669
+ return { allow: true, result: "ERROR sub-agent: " + (err?.message || String(err)) };
670
+ }
671
+ }
672
+ stopSpin();
673
+ const res = await execTool(name, input);
674
+ startSpin(t.thinking);
675
+ return res;
676
+ };
677
+
637
678
  const answer = await runAgent({
638
679
  history: state.history,
639
680
  model: state.model.id,
640
681
  signal: abort.signal,
682
+ tokenMeter,
683
+ extraToolsDoc: state.agentMode ? spawnAgentToolsDoc(0) : "",
641
684
  onStatus: () => tick(t.thinking),
642
685
  onSteer: () => {
643
686
  if (!pending.length) return [];
@@ -659,12 +702,7 @@ NGUYÊN TẮC:
659
702
  printer?.flush();
660
703
  }
661
704
  },
662
- onTool: async (name, input) => {
663
- stopSpin();
664
- const res = await execTool(name, input);
665
- startSpin(t.thinking);
666
- return res;
667
- },
705
+ onTool: (name, input) => dispatchTool(name, input, 0),
668
706
  });
669
707
 
670
708
  stopSpin();
@@ -773,6 +811,18 @@ NGUYÊN TẮC:
773
811
  state.yolo = !state.yolo;
774
812
  console.log((state.yolo ? c.err : c.ok)(" " + (state.yolo ? t.yoloOn : t.yoloOff)));
775
813
  break;
814
+ case "agent": {
815
+ const v = arg.toLowerCase();
816
+ if (v === "on" || v === "bật" || v === "bat") state.agentMode = true;
817
+ else if (v === "off" || v === "tắt" || v === "tat") state.agentMode = false;
818
+ else state.agentMode = !state.agentMode;
819
+ 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")));
820
+ break;
821
+ }
822
+ case "tokens": {
823
+ 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()}`));
824
+ break;
825
+ }
776
826
  case "auto-yolo":
777
827
  case "autoyolo":
778
828
  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
- const meter = new TokenMeter();
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
- onLog?.(`↳ sub-agent (depth=${depth}) xong (${meter.format()})`);
55
- return { result, tokens: { input: meter.input, output: meter.output } };
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
  }