@noobdemon/noob-cli 1.9.0 → 1.9.2

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.9.0",
3
+ "version": "1.9.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/agent.js CHANGED
@@ -43,6 +43,7 @@ Context is finite. Don't slurp the whole repo up front. Discover information pro
43
43
  • A server/service the USER wants to keep using (they asked to "run the app/server") → leave it running, and tell the user it is up: its id and the URL/port, and that they can ask you to stop it when done (you will kill_bg it). It also stops automatically when noob exits.
44
44
  • If bg_output shows it exited with a non-zero code or an error, treat it like a failed command: read the output and fix, don't silently move on.
45
45
  - Keep prose tight. Explain what you did and why, not how to use a tool.
46
+ - Do NOT call the same tool with the same input repeatedly. The runtime detects loops and will force a step change. If you need to re-read a file, use the data already in your history.
46
47
  - JSON in the tool block must be valid: escape newlines as \\n inside string values.
47
48
  - LANGUAGE: Always write your prose answers to the user in Vietnamese (tiếng Việt), unless the user explicitly writes in another language. Keep code, file paths, commands, and tool JSON unchanged.
48
49
 
@@ -77,6 +78,11 @@ Follow this pattern exactly. Your very first response to a task that needs the f
77
78
  // 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
78
79
  // 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.
79
80
  const MAX_STEPS = 10000;
81
+
82
+ // Loop detection: nếu model gọi cùng 1 tool với input giống nhau liên tiếp
83
+ // quá ngưỡng này → coi là bị kẹt, inject thông báo để model chuyển bước.
84
+ const LOOP_DETECT_WINDOW = 3;
85
+ const LOOP_DETECT_THRESHOLD = 2;
80
86
  const MAX_PROMPT_CHARS = 80000; // ngân sách ký tự cho phần hội thoại gửi lên model
81
87
  // 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
82
88
  // → giữ được "trí nhớ dài hạn" trong phiên mà không nổ context.
@@ -408,6 +414,7 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
408
414
  // [GỠ BUDGET 2026-06-06] Không còn token budget enforcement. Agent/loop/sub-agent
409
415
  // chạy không giới hạn token. Dừng theo: GOAL đạt, <<LOOP_DONE>>, <<ULTRA_DONE>>,
410
416
  // model tự kết thúc reply không có tool block, hoặc user Ctrl+C.
417
+ const recentCalls = []; // {name, inputStr} — theo dõi vòng lặp
411
418
  for (let step = 0; step < MAX_STEPS; step++) {
412
419
  // 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).
413
420
  if (step > 0 && step % 100 === 0) onStatus?.(`đã chạy ${step} bước…`);
@@ -460,6 +467,27 @@ export async function runAgent({ history, model, signal, onTool, onStatus, onDel
460
467
  name: call.name,
461
468
  content: allow ? result : t.toolDenied,
462
469
  });
470
+
471
+ // ── Loop detection ──────────────────────────────────────────────────
472
+ // Theo dõi N tool call gần nhất. Nếu cùng tên + input giống nhau liên tiếp
473
+ // quá ngưỡng → model bị kẹt. Inject thông báo nhắc model chuyển bước,
474
+ // KHÔNG ngắt task (vẫn có thể Ctrl+C thủ công).
475
+ const inputStr = JSON.stringify(call.input || {});
476
+ recentCalls.push({ name: call.name, inputStr });
477
+ if (recentCalls.length > LOOP_DETECT_WINDOW) recentCalls.shift();
478
+ if (recentCalls.length >= LOOP_DETECT_THRESHOLD + 1) {
479
+ const lastN = recentCalls.slice(-LOOP_DETECT_THRESHOLD);
480
+ const allSame = lastN.every((c) => c.name === lastN[0].name && c.inputStr === lastN[0].inputStr);
481
+ if (allSame) {
482
+ // Đã lặp — inject cảnh báo, xoá calls để tránh trigger liên tục
483
+ recentCalls.length = 0;
484
+ history.push({
485
+ role: "tool",
486
+ name: "loop_detection",
487
+ content: `[LOOP DETECTED] 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.`,
488
+ });
489
+ }
490
+ }
463
491
  }
464
492
  return t.maxSteps;
465
493
  }
package/src/i18n.js CHANGED
@@ -65,7 +65,7 @@ export const t = {
65
65
  cmdFrontendDesign: "/frontend-design <yêu cầu> thiết kế UI frontend chất lượng cao theo skill (/fd)",
66
66
  cmdImprove: "/improve [hint] phân tích workspace & đề xuất tính năng cải thiện (/imp)",
67
67
  cmdUltra: "/ultra <mục tiêu> tự hành: noob tự nghĩ & tự làm nhiệm vụ tới khi xong (/u)",
68
- cmdWorkflow: "/workflow <yêu cầu>|save|list|load|run|delete dynamic workflow đa sub-agent (/wf, /ultracode)",
68
+ cmdWorkflow: "/workflow <yêu cầu>|help|patterns|builtins|list|save|load|run|delete|rm dynamic workflow đa sub-agent (/wf, /ultracode)",
69
69
  cmdGoal: "/goal <text>|clear đặt HARD GOAL cho phiên (chống goal drift; không arg = xem)",
70
70
  cmdLoop: "/loop <interval> <task> chạy task lặp lại (vd /loop 10m triage); /loop stop để dừng",
71
71
  cmdLearn: "/learn [ghi chú] chưng cất bài học của phiên vào noob.md",
@@ -165,18 +165,31 @@ export const t = {
165
165
  workflowListEmpty: (dir) => `Chưa có workflow đã lưu. Tạo bằng /workflow save <name> <yêu cầu>. Thư mục: ${dir}`,
166
166
  workflowListHeader: (dir) => `Workflow đã lưu (${dir}):`,
167
167
  workflowSaveNeedArgs: "Cách dùng: /workflow save <name> <yêu cầu workflow>",
168
+ workflowSaveEmptyPrompt: "Thiếu yêu cầu workflow. VD: /workflow save code-audit-security \"audit src/ tìm SQL injection\"",
168
169
  workflowSaveBadName: (n) => `Tên workflow không hợp lệ: '${n}'. Chỉ chấp nhận [a-z0-9_-], bắt đầu bằng chữ/số, tối đa 64 ký tự.`,
169
170
  workflowSaveError: (n, e) => `Không lưu được workflow '${n}': ${e}`,
170
171
  workflowSaveOk: (n, p) => `Đã lưu workflow '${n}' → ${p}`,
172
+ workflowSaveAskDesc: "thêm mô tả ngắn để dễ tìm sau này? [y/n] › ",
173
+ workflowSaveDescPrompt: "mô tả (1 dòng): ",
174
+ workflowSaveDescSkipped: "(bỏ qua description — có thể thêm sau bằng cách save lại)",
175
+ workflowSaveDescOk: (n, d) => `Đã thêm mô tả cho '${n}': ${d}`,
171
176
  workflowRunNeedName: "Cách dùng: /workflow run <name> [thêm ngữ cảnh]",
172
177
  workflowRunError: (n, e) => `Không nạp được workflow '${n}': ${e}`,
173
178
  workflowRunOk: (n) => `Chạy workflow đã lưu '${n}'…`,
179
+ workflowRunPreviewBuiltin: (n, title) => `Built-in workflow '${n}' (${title})`,
180
+ workflowRunPreviewSaved: (n) => `Workflow đã lưu '${n}'`,
174
181
  workflowLoadNeedName: "Cách dùng: /workflow load <name>",
175
182
  workflowLoadError: (n, e) => `Không nạp được workflow '${n}': ${e}`,
176
183
  workflowLoadOk: (n, p) => `Workflow '${n}' (${p}):`,
177
184
  workflowDeleteNeedName: "Cách dùng: /workflow delete <name>",
178
185
  workflowDeleteError: (n, e) => `Không xoá được workflow '${n}': ${e}`,
179
186
  workflowDeleteOk: (n) => `Đã xoá workflow '${n}'.`,
187
+ workflowDeleteBuiltIn: (n) => `'${n}' là built-in workflow, không xoá được.`,
188
+ // discoverability (v1.9.1)
189
+ workflowHelpTitle: "🎼 /workflow — orchestrate multi-agent workflow",
190
+ workflowHelpSub: "Workflow chia task lớn thành sub-agent chạy song song/độc lập → chống 3 failure mode của single-context: agentic laziness, self-preferential bias, goal drift.",
191
+ workflowPatternsTitle: "🎼 6 pattern workflow (theo article Thariq)",
192
+ workflowBuiltinsTitle: (n) => `🎼 Workflow built-in (${n} mẫu ship sẵn):`,
180
193
  initOverwriteWarn: (p) => `⚠ Đã có noob.md tại ${p}. /init sẽ ghi đè nội dung hiện tại.`,
181
194
  initOverwriteConfirm: "Ghi đè? gõ 'y' để xác nhận, phím khác để huỷ › ",
182
195
  initCancel: "Huỷ /init — giữ nguyên noob.md.",
package/src/repl.js CHANGED
@@ -17,6 +17,7 @@ import { checkLatest, runUpdate, CURRENT } from "./update.js";
17
17
  import * as sessions from "./sessions.js";
18
18
  import { loadSkill, listSkills } from "./skills.js";
19
19
  import { saveWorkflow, loadWorkflow, listWorkflows, deleteWorkflow, workflowsDir } from "./workflows.js";
20
+ import { getBuiltinWorkflow, listBuiltinWorkflows, loadBuiltinPrompt } from "./workflows-builtin.js";
20
21
 
21
22
  // Lệnh dùng cho autocomplete. Gõ "/l" → lọc các lệnh có "l" (login, logout,
22
23
  // clear, models, yolo…); ↑/↓ chọn, Tab điền, Enter chạy mục đang sáng.
@@ -389,28 +390,45 @@ Thực thi: đọc/tạo file cần thiết bằng tool, viết code production-
389
390
  }
390
391
 
391
392
  // /workflow <yêu cầu> — chạy ad-hoc dynamic workflow
393
+ // /workflow help | ? — show syntax + patterns + builtins
394
+ // /workflow patterns — liệt kê 6 pattern workflow (theo article Thariq)
395
+ // /workflow builtins — liệt kê workflow built-in có sẵn
392
396
  // /workflow save <name> <req> — lưu prompt template ra ~/.noob/workflows/<name>.md
393
- // /workflow run <name> [extra] — chạy workflow đã lưu (extra context optional)
394
- // /workflow load <name> — xem nội dung workflow đã lưu (không chạy)
395
- // /workflow list — liệt kê workflow đã lưu
396
- // /workflow delete <name> — xoá workflow đã lưu
397
- // Cảm hứng tweet_dump.txt L183–193 ("saving and sharing dynamic workflows").
397
+ // /workflow run <name> [extra] — chạy workflow đã lưu HOẶC built-in (extra context optional)
398
+ // /workflow load <name> — xem nội dung workflow (saved hoặc built-in)
399
+ // /workflow list — liệt kê workflow đã lưu (cộng builtins)
400
+ // /workflow delete|rm <name> — xoá workflow đã lưu
401
+ // Cảm hứng tweet_dump.txt article Thariq "A harness for every task: dynamic
402
+ // workflows in Claude Code" (2026-06): L83-109 (6 pattern), L121 (deep-research
403
+ // built-in), L147-153 (triage + quarantine + pair-with-/loop), L177
404
+ // (repeatable workflow + /goal + /loop integration).
398
405
  async function runWorkflow(arg) {
399
406
  if (!config.apiKey) return console.log(c.tool(" " + t.notLoggedIn));
400
- if (!arg) return console.log(c.err(" " + (t.workflowNeedArg || "Cách dùng: /workflow <mô tả task lớn cần orchestrate>")));
401
- // Detect sub-command. Sub-command tách bằng khoảng trắng đầu tiên.
402
- const m = arg.match(/^(save|run|load|list|delete|rm|ls)\b\s*([\s\S]*)$/i);
407
+ // Empty arg KHÔNG báo lỗi "need arg" nữa, show menu trợ giúp user
408
+ // mới /workflow thể chưa biết phải gì. Thay vì đuổi đi, show help +
409
+ // builtins + saved → user thấy luôn có gì để chạy.
410
+ if (!arg || !arg.trim()) {
411
+ return workflowHelp();
412
+ }
413
+ const trimmed = arg.trim();
414
+ // Detect sub-command. Sub-command tách bằng khoảng trắng đầu tiên. Thứ tự
415
+ // match quan trọng: `help` / `?` / `patterns` / `builtins` / `list|ls` /
416
+ // `load` / `delete|rm` / `save` / `run`. Ad-hoc default = phần còn lại.
417
+ const m = trimmed.match(/^(help|\?|patterns|builtins|list|ls|load|delete|rm|save|run)\b\s*([\s\S]*)$/i);
403
418
  if (m) {
404
419
  const sub = m[1].toLowerCase();
405
420
  const rest = m[2].trim();
421
+ if (sub === "help" || sub === "?") return workflowHelp();
422
+ if (sub === "patterns") return workflowPatterns();
423
+ if (sub === "builtins") return workflowBuiltins();
406
424
  if (sub === "list" || sub === "ls") return workflowList();
407
425
  if (sub === "load") return workflowLoad(rest);
408
426
  if (sub === "delete" || sub === "rm") return workflowDelete(rest);
409
427
  if (sub === "save") return workflowSave(rest);
410
428
  if (sub === "run") return workflowRun(rest);
411
429
  }
412
- // Default: ad-hoc workflow (giữ behavior cũ).
413
- await workflowExecute(arg);
430
+ // Default: ad-hoc workflow (giữ behavior cũ — model design workflow từ request).
431
+ await workflowExecute(trimmed);
414
432
  }
415
433
 
416
434
  // Hỏi quyền bật agent mode để chạy workflow. CHỈ chấp nhận y/n (Enter = yes).
@@ -438,9 +456,38 @@ Thực thi: đọc/tạo file cần thiết bằng tool, viết code production-
438
456
  }
439
457
 
440
458
  // Chạy thật workflow prompt — chia sẻ giữa ad-hoc và `run <name>`.
441
- async function workflowExecute(userRequest) {
442
- const skill = loadSkill("dynamic-workflows");
443
- if (!skill) return console.log(c.err(" " + (t.workflowNoSkill || "Không tìm thấy skill dynamic-workflows")));
459
+ // `builtInName` (optional): nếu có thì SKIP loadSkill dynamic-workflows (prompt
460
+ // built-in đã hardcode pattern + step cụ thể rồi, không cần model design lại).
461
+ async function workflowExecute(userRequest, { builtInName = null } = {}) {
462
+ let prompt;
463
+ if (builtInName) {
464
+ // Built-in prompt đã đầy đủ PLAN + 4 bước thực thi — KHÔNG wrap thêm skill.
465
+ prompt = userRequest;
466
+ } else {
467
+ const skill = loadSkill("dynamic-workflows");
468
+ if (!skill) return console.log(c.err(" " + (t.workflowNoSkill || "Không tìm thấy skill dynamic-workflows")));
469
+ // Enforce PLAN xuất hiện TRƯỚC khi spawn bằng cách gộp vào bước 1 và yêu cầu
470
+ // output. Model hay skip bước này → user mất visibility vào plan.
471
+ prompt = `Bạn đang thực thi SKILL "dynamic-workflows". Đọc kỹ playbook dưới đây và TUÂN THỦ khi orchestrate multi-agent workflow.
472
+
473
+ === SKILL: dynamic-workflows ===
474
+ ${skill}
475
+ === HẾT SKILL ===
476
+
477
+ YÊU CẦU NGƯỜI DÙNG:
478
+ ${userRequest}
479
+
480
+ Thực thi THEO ĐÚNG THỨ TỰ (BẮT BUỘC):
481
+ 1. **PLAN (xuất ra TRƯỚC khi spawn bất kỳ sub-agent nào)** — bullet list ≤ 7 gạch:
482
+ - Sub-tasks cần làm
483
+ - Nhánh nào SONG SONG (spawn_agents) vs TUẦN TỰ (spawn_agent)
484
+ - Pattern chính (1 trong 6: classify-and-act / fan-out-synthesize / adversarial-verification / generate-and-filter / tournament / loop-until-done) + vì sao
485
+ - Nếu dùng fan-out-synthesize: nhắc rõ "synthesize step LÀ BARRIER" — đợi tất cả fan-out xong mới merge
486
+ - Synthesis step + stop condition
487
+ Viết plan RA trước, user cần thấy.
488
+ 2. Spawn sub-agent theo plan — mỗi prompt sub-agent có 5 mục GOAL/INPUTS/METHOD/OUTPUT SHAPE/STOP CONDITION.
489
+ 3. Gom kết quả, dedupe, reconcile mâu thuẫn, viết báo cáo cuối tiếng Việt. Sub-agent KHÔNG nói trực tiếp với user.`;
490
+ }
444
491
  if (!state.agent) {
445
492
  // Đừng tự bật — workflow cần spawn_agent, đây là quyền nặng (sub-agent chạy
446
493
  // tool độc lập). Hỏi 1 lần, user chọn y thì bật & chạy, n thì huỷ sạch +
@@ -452,41 +499,117 @@ Thực thi: đọc/tạo file cần thiết bằng tool, viết code production-
452
499
  state.agent = true;
453
500
  console.log(c.tool(" ✓ " + (t.workflowAgentEnabled || "đã bật agent mode cho workflow này.")));
454
501
  }
455
- const prompt = `Bạn đang thực thi SKILL "dynamic-workflows". Đọc kỹ playbook dưới đây và TUÂN THỦ khi orchestrate multi-agent workflow.
456
-
457
- === SKILL: dynamic-workflows ===
458
- ${skill}
459
- === HẾT SKILL ===
460
-
461
- YÊU CẦU NGƯỜI DÙNG:
462
- ${userRequest}
463
-
464
- Thực thi:
465
- 1. Viết PLAN ngắn (sub-tasks, sequential vs parallel, synthesis step, stop condition).
466
- 2. Spawn sub-agent theo plan — dùng spawn_agents cho công việc song song độc lập, spawn_agent tuần tự khi có phụ thuộc.
467
- 3. Mỗi prompt sub-agent đều có GOAL / INPUTS / METHOD / OUTPUT SHAPE / STOP CONDITION (theo skill).
468
- 4. Gom kết quả, dedupe, reconcile xung đột, viết báo cáo cuối tiếng Việt cho người dùng. Sub-agent KHÔNG nói trực tiếp với user.`;
469
502
  console.log(c.tool(" 🎼 " + (t.workflowRunning || "Dynamic workflow running…")));
470
503
  await handle(prompt);
471
504
  persist();
472
505
  }
473
506
 
474
- function workflowList() {
475
- const items = listWorkflows();
476
- if (!items.length) {
477
- console.log(c.dim(" " + (t.workflowListEmpty ? t.workflowListEmpty(workflowsDir()) : `Chưa có workflow đã lưu. Tạo bằng /workflow save <name> <yêu cầu>. Thư mục: ${workflowsDir()}`)));
478
- return;
507
+ // ── Help / patterns / builtins — khối discoverability (v1.9.1+) ─────────
508
+ // Trước đó /workflow không có gì để khám phá: user phải biết syntax sẵn. Giờ
509
+ // empty /workflow hoặc `/workflow help` show menu đầy đủ.
510
+ function workflowHelp() {
511
+ console.log(c.tool(" " + (t.workflowHelpTitle || "🎼 /workflow — orchestrate multi-agent workflow")));
512
+ console.log(c.dim(" " + (t.workflowHelpSub || "Workflow chia task lớn thành sub-agent chạy song song/độc lập → chống 3 failure mode của single-context: agentic laziness, self-preferential bias, goal drift.")));
513
+ console.log("");
514
+ console.log(c.accent(" Cú pháp:"));
515
+ console.log(" /workflow <yêu cầu> chạy ad-hoc (model tự design workflow)");
516
+ console.log(" /workflow help | ? menu này");
517
+ console.log(" /workflow patterns 6 pattern workflow (theo article Thariq)");
518
+ console.log(" /workflow builtins 3 workflow built-in có sẵn");
519
+ console.log(" /workflow list workflow đã lưu");
520
+ console.log(" /workflow save <name> <req> lưu prompt template → ~/.noob/workflows/");
521
+ console.log(" /workflow load <name> xem nội dung (saved hoặc built-in)");
522
+ console.log(" /workflow run <name> [extra] chạy (built-in HOẶC saved, có thể thêm ngữ cảnh)");
523
+ console.log(" /workflow delete|rm <name> xoá workflow đã lưu");
524
+ console.log("");
525
+ console.log(c.accent(" Nhanh nhất để thử:"));
526
+ console.log(" " + c.dim("/workflow builtins ") + "xem có sẵn cái nào");
527
+ console.log(" " + c.dim("/workflow run deep-research \"async-await trong Python\"") + " chạy ngay");
528
+ console.log(" " + c.dim("/workflow run verify-claims README.md ") + "verify claim trong tài liệu");
529
+ console.log("");
530
+ console.log(c.dim(" 💡 Repeatable workflow (triage, research, verify) — pair với /loop <interval> + /goal <text>"));
531
+ console.log("");
532
+ workflowBuiltins({ compact: true });
533
+ }
534
+
535
+ // Liệt kê 6 pattern từ article Thariq (L83-109). Mỗi pattern 1 dòng — user
536
+ // scan nhanh chọn pattern phù hợp task. Trước đó tôi liệt kê 7 (thêm
537
+ // "Diverse-Hypothesis Debug" tự bịa) → fix về 6 theo article.
538
+ function workflowPatterns() {
539
+ const PATTERNS = [
540
+ ["Classify-and-act", "classifier phân loại task → route tới sub-agent chuyên dụng. HOẶC classifier ở cuối để check output. Khi: input không đồng nhất, mỗi loại cần chiến lược khác."],
541
+ ["Fan-out-and-synthesize", "task lớn chia N nhánh độc lập song song → gom kết quả. SYNTHESIZE STEP LÀ BARRIER (article L93): đợi tất cả fan-out xong mới merge. Khi: partition rõ theo file/module/khía cạnh."],
542
+ ["Adversarial verification", "1 agent LÀM, 1 agent KHÁC verify output chống rubric/criteria. Khi: claim cần verify, code rủi ro, quyết định khó đảo."],
543
+ ["Generate-and-filter", "sinh nhiều phương án song song → 1 agent lọc theo rubric/verify → dedupe → trả về top. Khi: bài toán mở, cần đa dạng giải pháp đã verify."],
544
+ ["Tournament", "N agents CÙNG LÀM 1 task với approach khác nhau → judge pairwise cho tới khi có winner. Pairwise comparison reliable hơn absolute scoring. Khi: cần ranking/rubric, hoặc bài toán 'taste' (naming, design)."],
545
+ ["Loop-until-done", "sub-agent làm 1 vòng, parent check stop condition (no new findings / no more errors), chưa đạt → spawn lại. Khi: lượng work không biết trước, có metric đo được."],
546
+ ];
547
+ console.log(c.tool(" " + (t.workflowPatternsTitle || "🎼 6 pattern workflow (theo article Thariq — A harness for every task)")));
548
+ PATTERNS.forEach(([name, desc], i) => {
549
+ console.log(" " + c.accent(`${i + 1}. ${name}`));
550
+ console.log(" " + c.dim(desc));
551
+ });
552
+ console.log("");
553
+ console.log(c.dim(" Tổ hợp: 1 workflow có thể compose nhiều pattern (vd triage = classify-and-act + loop-until-done + quarantine)."));
554
+ console.log(c.dim(" Lưu ý (article L165-167): workflow KHÔNG cần cho mọi task — tốn nhiều token. Việc < vài file → tự làm."));
555
+ }
556
+
557
+ // Liệt kê built-in workflow có sẵn trong source. Built-in là 3 mẫu ship sẵn
558
+ // để user `run` ngay, không phải tự viết. `compact=true` dùng trong help để
559
+ // khỏi tốn dòng; default = list đầy đủ pattern + description.
560
+ function workflowBuiltins({ compact = false } = {}) {
561
+ const items = listBuiltinWorkflows();
562
+ if (!compact) {
563
+ console.log(c.tool(" " + (t.workflowBuiltinsTitle || `🎼 Workflow built-in (${items.length} mẫu ship sẵn):`)));
564
+ } else {
565
+ console.log(c.accent(" Built-in workflow:"));
479
566
  }
567
+ for (const w of items) {
568
+ console.log(" " + c.accent("/workflow run " + w.name) + c.dim(" · " + w.pattern));
569
+ if (!compact) console.log(" " + c.dim(w.description));
570
+ }
571
+ if (!compact) {
572
+ console.log("");
573
+ console.log(c.dim(" Chạy: /workflow run <name> [input]. VD: /workflow run verify-claims README.md"));
574
+ }
575
+ }
576
+
577
+ function workflowList() {
578
+ const saved = listWorkflows();
579
+ const builtins = listBuiltinWorkflows();
580
+ // Luôn show cả 2 nhóm — built-in quan trọng vì user quên chúng có sẵn.
480
581
  console.log(c.tool(" " + (t.workflowListHeader ? t.workflowListHeader(workflowsDir()) : `Workflow đã lưu (${workflowsDir()}):`)));
481
- for (const it of items) {
482
- const desc = it.description ? c.dim(" — " + it.description) : "";
483
- const date = it.updated ? c.dim(" [" + it.updated.slice(0, 10) + "]") : "";
484
- console.log(" " + c.accent(it.name) + desc + date);
582
+ if (saved.length) {
583
+ for (const it of saved) {
584
+ const desc = it.description ? c.dim(" " + it.description) : "";
585
+ const date = it.updated ? c.dim(" [" + it.updated.slice(0, 10) + "]") : "";
586
+ console.log(" " + c.accent(it.name) + desc + date);
587
+ }
588
+ } else {
589
+ console.log(c.dim(" (chưa có — /workflow save <name> <yêu cầu> để tạo)"));
485
590
  }
591
+ console.log("");
592
+ console.log(c.accent(" Built-in workflow (chạy ngay, không cần save):"));
593
+ for (const w of builtins) {
594
+ console.log(" " + c.accent("/workflow run " + w.name) + c.dim(" · " + w.title));
595
+ }
596
+ console.log("");
597
+ console.log(c.dim(" Dùng: /workflow <yêu cầu> hoặc /workflow run <name> [input] hoặc /workflow help"));
486
598
  }
487
599
 
488
600
  function workflowLoad(name) {
489
601
  if (!name) return console.log(c.err(" " + (t.workflowLoadNeedName || "Cách dùng: /workflow load <name>")));
602
+ // Check built-in trước — user có thể quên chúng có sẵn.
603
+ const builtin = getBuiltinWorkflow(name);
604
+ if (builtin) {
605
+ console.log(c.tool(" " + `🎼 Built-in workflow '${builtin.name}' — ${builtin.title}`));
606
+ console.log(c.dim(" pattern: " + builtin.pattern));
607
+ console.log(c.dim(" " + builtin.description));
608
+ console.log("");
609
+ console.log(c.dim(" ── prompt template (chạy bằng /workflow run " + builtin.name + " <input>) ──"));
610
+ console.log(builtin.buildPrompt("<input>"));
611
+ return;
612
+ }
490
613
  const r = loadWorkflow(name);
491
614
  if (!r.ok) return console.log(c.err(" " + (t.workflowLoadError ? t.workflowLoadError(name, r.error) : `Không nạp được workflow '${name}': ${r.error}`)));
492
615
  console.log(c.tool(" " + (t.workflowLoadOk ? t.workflowLoadOk(r.name, r.path) : `Workflow '${r.name}' (${r.path}):`)));
@@ -498,6 +621,9 @@ Thực thi:
498
621
 
499
622
  function workflowDelete(name) {
500
623
  if (!name) return console.log(c.err(" " + (t.workflowDeleteNeedName || "Cách dùng: /workflow delete <name>")));
624
+ // Chỉ xoá saved — built-in không xoá được.
625
+ const builtin = getBuiltinWorkflow(name);
626
+ if (builtin) return console.log(c.err(" " + (t.workflowDeleteBuiltIn ? t.workflowDeleteBuiltIn(name) : `'${name}' là built-in workflow, không xoá được.`)));
501
627
  const r = deleteWorkflow(name);
502
628
  if (!r.ok) return console.log(c.err(" " + (t.workflowDeleteError ? t.workflowDeleteError(name, r.error) : `Không xoá được workflow '${name}': ${r.error}`)));
503
629
  console.log(c.tool(" " + (t.workflowDeleteOk ? t.workflowDeleteOk(name) : `Đã xoá workflow '${name}'.`)));
@@ -509,6 +635,7 @@ Thực thi:
509
635
  if (!m) return console.log(c.err(" " + (t.workflowSaveNeedArgs || "Cách dùng: /workflow save <name> <yêu cầu workflow>")));
510
636
  const name = m[1];
511
637
  const prompt = m[2].trim();
638
+ if (!prompt) return console.log(c.err(" " + (t.workflowSaveEmptyPrompt || "Thiếu yêu cầu workflow. VD: /workflow save code-audit-security \"audit src/ tìm SQL injection\"")));
512
639
  const r = saveWorkflow(name, prompt);
513
640
  if (!r.ok) {
514
641
  const msg = r.error === "invalid_name"
@@ -517,17 +644,65 @@ Thực thi:
517
644
  return console.log(c.err(" " + msg));
518
645
  }
519
646
  console.log(c.tool(" 💾 " + (t.workflowSaveOk ? t.workflowSaveOk(name, r.path) : `Đã lưu workflow '${name}' → ${r.path}`)));
647
+ // Hỏi thêm description (1 dòng) — list/load sau này có ích, user nhìn 1 dòng
648
+ // là biết workflow này làm gì. Không bắt buộc: n / Enter = skip.
649
+ return maybeAskWorkflowDescription(name, prompt);
650
+ }
651
+
652
+ // Hỏi user có muốn thêm description cho workflow vừa lưu không. Nếu y → ask
653
+ // 1 dòng rồi saveWorkflow lại (ghi đè file, description vào front-matter).
654
+ // Tái sử dụng pattern ask() + pending.push (paste-spam protection).
655
+ async function maybeAskWorkflowDescription(name, currentPrompt) {
656
+ tui.setBusy(false);
657
+ try {
658
+ const raw = await ask(c.tool(" " + (t.workflowSaveAskDesc || "thêm mô tả ngắn để dễ tìm sau này? [y/n] › ")) + c.dim("(y = hỏi 1 dòng, phím khác = bỏ qua) "));
659
+ if (raw == null) return; // stdin đóng
660
+ const a = raw.trim().toLowerCase();
661
+ if (a !== "y" && a !== "yes" && a !== "có") {
662
+ console.log(c.dim(" " + (t.workflowSaveDescSkipped || "(bỏ qua description — có thể thêm sau bằng cách save lại)")));
663
+ return;
664
+ }
665
+ const descRaw = await ask(c.tool(" " + (t.workflowSaveDescPrompt || "mô tả (1 dòng): ")));
666
+ if (descRaw == null) return;
667
+ const desc = descRaw.trim();
668
+ if (!desc) {
669
+ console.log(c.dim(" (description trống — bỏ qua)"));
670
+ return;
671
+ }
672
+ const r2 = saveWorkflow(name, currentPrompt, { description: desc });
673
+ if (r2.ok) console.log(c.ok(" ✓ " + (t.workflowSaveDescOk ? t.workflowSaveDescOk(name, desc) : `Đã thêm mô tả cho '${name}': ${desc}`)));
674
+ } catch (e) {
675
+ console.log(c.dim(" (lỗi thêm description, workflow vẫn được lưu)"));
676
+ } finally {
677
+ tui.setBusy(true, t.thinking);
678
+ }
520
679
  }
521
680
 
522
681
  async function workflowRun(rest) {
523
682
  if (!rest) return console.log(c.err(" " + (t.workflowRunNeedName || "Cách dùng: /workflow run <name> [thêm ngữ cảnh]")));
683
+ // Tách name (1 từ, kebab-case theo sanitize) + extra context phần còn lại.
524
684
  const m = rest.match(/^(\S+)(?:\s+([\s\S]+))?$/);
525
685
  const name = m[1];
526
686
  const extra = (m[2] || "").trim();
687
+ // Built-in trước — user có thể gõ `run deep-research ...` mà quên đó là built-in.
688
+ const builtin = getBuiltinWorkflow(name);
689
+ if (builtin) {
690
+ const userInput = extra || c.dim("(không có input — workflow sẽ chạy với placeholder)");
691
+ const prompt = builtin.buildPrompt(userInput);
692
+ console.log(c.tool(" ▶️ " + (t.workflowRunPreviewBuiltin ? t.workflowRunPreviewBuiltin(name, builtin.title) : `Built-in workflow '${name}' (${builtin.title}) — pattern: ${builtin.pattern}`)));
693
+ console.log(c.dim(" input: " + truncate(userInput, 80)));
694
+ console.log(c.dim(" prompt: " + prompt.length + " chars"));
695
+ return await workflowExecute(prompt, { builtInName: name });
696
+ }
527
697
  const r = loadWorkflow(name);
528
698
  if (!r.ok) return console.log(c.err(" " + (t.workflowRunError ? t.workflowRunError(name, r.error) : `Không nạp được workflow '${name}': ${r.error}`)));
529
699
  const userRequest = extra ? `${r.prompt}\n\nNgữ cảnh bổ sung cho lần chạy này:\n${extra}` : r.prompt;
530
- console.log(c.tool(" ▶️ " + (t.workflowRunOk ? t.workflowRunOk(name) : `Chạy workflow đã lưu '${name}'…`)));
700
+ // Preview banner trước khi execute user verify "đúng cái mình muốn" trước
701
+ // khi bỏ 30s+ chờ. Tránh case user gõ nhầm `run my-workflow` thành
702
+ // `run my-workflw` (saved khác) và mất 1 phút mới biết.
703
+ console.log(c.tool(" ▶️ " + (t.workflowRunPreviewSaved ? t.workflowRunPreviewSaved(name) : `Workflow đã lưu '${name}'`)));
704
+ console.log(c.dim(" prompt: " + r.prompt.length + " chars · " + (r.meta.description || "(chưa có description)")));
705
+ if (extra) console.log(c.dim(" extra context: " + truncate(extra, 80)));
531
706
  await workflowExecute(userRequest);
532
707
  }
533
708
 
@@ -0,0 +1,127 @@
1
+ // Built-in workflow templates — ship sẵn để user `run` ngay mà không phải tự
2
+ // viết. 3 mẫu khớp VỚI 3 example prompts Thariq nêu trong bài "A harness for
3
+ // every task: dynamic workflows in Claude Code":
4
+ // • deep-research — `/deep-research` skill nội bộ Claude Code (article L121).
5
+ // Fan-out web/code search + adversarial verify + synthesize cited report.
6
+ // • verify-claims — example L45/L127: "Go through my blog post draft and
7
+ // using a workflow verify every technical claim against the codebase."
8
+ // Adversarial verification pattern: 1 agent list claims, N agent verify.
9
+ // • triage — article L147-153: "Pair triage workflows with /loop". Classify
10
+ // + dedupe + route, với QUARANTINE (article L151): agent đọc untrusted
11
+ // content KHÔNG được gọi tool destructive — chỉ route đề xuất, parent mới
12
+ // thực thi.
13
+ //
14
+ // Mỗi built-in trả về string prompt hoàn chỉnh (KHÔNG load lại skill
15
+ // dynamic-workflows — built-in đã hardcode pattern + step cụ thể rồi).
16
+
17
+ /**
18
+ * @typedef {Object} BuiltinWorkflow
19
+ * @property {string} name — id (kebab-case, dùng cho `run <name>`)
20
+ * @property {string} title — tiêu đề hiển thị trong `/workflow builtins`
21
+ * @property {string} pattern — pattern chính theo article
22
+ * @property {string} description — 1-2 dòng tóm tắt
23
+ * @property {string} buildPrompt — (userInput: string) => string prompt hoàn chỉnh
24
+ */
25
+
26
+ /** @type {BuiltinWorkflow[]} */
27
+ export const BUILTIN_WORKFLOWS = [
28
+ {
29
+ name: "deep-research",
30
+ title: "Deep Research",
31
+ pattern: "Fan-out-and-Synthesize + Adversarial Verification",
32
+ description:
33
+ "Đào sâu 1 chủ đề: fan-out N search song song, mỗi search có adversarial verify, parent synthesize báo cáo có trích dẫn.",
34
+ buildPrompt: (topic) => `Bạn đang chạy workflow DEEP-RESEARCH (built-in) cho chủ đề: ${topic}
35
+
36
+ Mục tiêu: báo cáo TIẾNG VIỆT có trích dẫn, mỗi claim đã được verify chống nguồn gốc, parent tổng hợp từ ≥3 sub-agent độc lập.
37
+
38
+ Thực thi ĐÚNG thứ tự (4 bước):
39
+ 1. **PLAN** (xuất ra TRƯỚC khi spawn sub-agent nào, ≤ 5 gạch đầu dòng): chia chủ đề thành N=3–5 nhánh độc lập (vd "lịch sử", "hiện trạng", "ưu nhược", "use case", "rủi ro"). Mỗi nhánh = 1 sub-agent.
40
+ 2. **FAN-OUT** (spawn_agents SONG SONG): mỗi sub-agent có GOAL / INPUTS / METHOD / OUTPUT SHAPE / STOP CONDITION đầy đủ (xem SKILL dynamic-workflows). OUTPUT SHAPE BẮT BUỘC: "≤ 800 token bullet list, mỗi fact kèm [source: <url hoặc tên file>], tuyệt đối KHÔNG bịa URL".
41
+ 3. **ADVERSARIAL VERIFY**: 1 sub-agent phản biện đọc output của TẤT CẢ sub-agent trên, đánh dấu [✓ verified] / [⚠ chưa verify được] / [✗ nghi ngờ sai] cho từng fact. Source nào không truy cập được → đánh dấu ngay, KHÔNG tự suy.
42
+ 4. **SYNTHESIZE** — BƯỚC NÀY LÀ BARRIER: phải ĐỢI TẤT CẢ fan-out + verify xong mới gom. Gom output + verify marker → báo cáo cuối tiếng Việt có cấu trúc:
43
+ - Tóm tắt 1 đoạn
44
+ - Findings chính (có trích dẫn [n])
45
+ - Điểm còn tranh cãi / chưa verify
46
+ - Nguồn tham khảo (list URL)
47
+ Báo cáo cuối PHẢI nêu rõ fact nào đã verify, fact nào chưa — user quyết định có tin hay không.
48
+
49
+ Không spawn reader agent đọc nguồn untrusted nếu không cần — chỉ spawn search/verify agent. KHÔNG tự đoán URL.
50
+
51
+ 💡 Repeatable? Thêm \`/loop <interval>\` để chạy lặp, \`/goal <text>\` để set hard completion requirement.`,
52
+ },
53
+
54
+ {
55
+ name: "verify-claims",
56
+ title: "Verify Claims",
57
+ pattern: "Adversarial Verification",
58
+ description:
59
+ "Verify mọi technical claim trong 1 tài liệu (blog/code/README) chống codebase thật: 1 agent list claims, N agent verify từng claim.",
60
+ buildPrompt: (target) => `Bạn đang chạy workflow VERIFY-CLAIMS (built-in) cho tài liệu: ${target || "(user chưa chỉ rõ — dùng context gần nhất)"}
61
+
62
+ Mục tiêu: với MỖI technical claim trong tài liệu, xác minh chống codebase THẬT (bằng read_file/grep/run_command), đánh dấu [✓ đúng] / [✗ sai] / [⚠ cần verify thêm]. Báo cáo cuối: claim nào sai → user phải sửa trước khi ship.
63
+
64
+ Thực thi ĐÚNG thứ tự (4 bước):
65
+ 1. **PLAN**: đọc tài liệu (read_file). Liệt kê tất cả technical claim (vd "function X trả về Y", "class Z có method W", "API endpoint A chấp nhận B"). Nếu < 5 claim → chạy tuần tự. Nếu ≥ 5 → fan-out theo claim.
66
+ 2. **LIST CLAIMS** (1 sub-agent, KHÔNG cần verify): output bảng [claim_id] [claim_text] [relevant file/module hint].
67
+ 3. **ADVERSARIAL VERIFY** (N sub-agent SONG SONG, mỗi cái verify 1 nhóm claim): với MỖI claim, tìm trong codebase bằng read_file/grep. Output [claim_id] [verdict: ✓ đúng / ✗ sai / ⚠ cần verify thêm] [evidence: <file:line trích dẫn>] [1-câu giải thích].
68
+ 4. **SYNTHESIZE** — báo cáo cuối tiếng Việt:
69
+ - Tóm tắt (tổng claim, bao nhiêu đúng/sai/cần verify)
70
+ - ✗ Claims SAI (kèm evidence — user ưu tiên sửa)
71
+ - ⚠ Claims cần verify thêm
72
+ - ✓ Claims đúng
73
+ Mỗi verdict có "file:line" cụ thể. KHÔNG bịa evidence — nếu không tìm thấy, nói "không tìm thấy trong codebase".
74
+
75
+ KHÔNG tự sửa tài liệu — chỉ report. User quyết định sửa.`,
76
+ },
77
+
78
+ {
79
+ name: "triage",
80
+ title: "Triage Queue",
81
+ pattern: "Classify-and-Route + Quarantine",
82
+ description:
83
+ "Phân loại 1 danh sách item (ticket/bug/idea): classify + dedupe + route. Pair với /loop. QUARANTINE: agent đọc untrusted content KHÔNG gọi tool destructive.",
84
+ buildPrompt: (input) => `Bạn đang chạy workflow TRIAGE (built-in) cho input: ${input}
85
+
86
+ Mục tiêu: phân loại MỖI item trong input, dedupe với nhau + với context đã biết, route hành động (auto-fix/escalate-to-human/defer/drop). Áp dụng QUARANTINE pattern (article L151): agent đọc untrusted content KHÔNG ĐƯỢC gọi tool có high-privilege — chỉ route đề xuất, parent mới thực thi.
87
+
88
+ Thực thi ĐÚNG thứ tự (4 bước):
89
+ 1. **PLAN**: xác định input là gì (1 danh sách item? file CSV? raw text?). Nếu input > 20 item → cân nhắc Loop-Until-Done, mỗi vòng 5–10 item.
90
+ 2. **CLASSIFY** (1 sub-agent, READER ROLE — KHÔNG ĐƯỢC gọi write_file/edit_file/run_command có side-effect. Đây là QUARANTINE.): đọc input, phân loại mỗi item theo:
91
+ - **Category**: bug / feature-request / question / duplicate / spam / out-of-scope.
92
+ - **Severity** (chỉ bug): 🔴 P0 / 🟡 P1 / 🔵 P2.
93
+ - **Suggested action**: auto-fix / escalate-to-human / defer / drop.
94
+ OUTPUT SHAPE: bảng markdown, cột: [item_id] [category] [severity] [action] [1-câu lý do].
95
+ 3. **DEDUPE** (1 sub-agent khác): đọc output classify + context đã biết (noob.md, git log gần đây), tìm item trùng → gộp, giữ lại id gốc + reference id trùng.
96
+ 4. **ROUTE — PARENT MỚI THỰC THI** (article L151: high-privilege action do parent — agent chịu trách nhiệm về thông tin — làm, KHÔNG phải reader agent):
97
+ - Tóm tắt (tổng item, phân bổ category/severity)
98
+ - 🔴 P0 cần fix ngay (nếu có)
99
+ - 🟡 P1 + 🟢 P2 (gom nhóm nếu nhiều)
100
+ - Dropped (spam/duplicate/out-of-scope) + lý do
101
+ - Action items cho user (manual)
102
+ Nếu action = auto-fix → parent TỰ gọi write_file/edit_file (KHÔNG phải classify agent). Nếu escalate → in note cho user.
103
+
104
+ QUARANTINE REMINDER: classify/dedupe agent chỉ ĐỌC. Mọi tool có side-effect (write_file/edit_file/run_command thay đổi hệ thống) đều do parent gọi.
105
+
106
+ 💡 Repeatable? Thêm \`/loop <interval>\` để chạy queue liên tục (article L153). \`/goal <text>\` để set completion requirement.`,
107
+ },
108
+ ];
109
+
110
+ export function listBuiltinWorkflows() {
111
+ return BUILTIN_WORKFLOWS.map((w) => ({
112
+ name: w.name,
113
+ title: w.title,
114
+ pattern: w.pattern,
115
+ description: w.description,
116
+ }));
117
+ }
118
+
119
+ export function getBuiltinWorkflow(name) {
120
+ return BUILTIN_WORKFLOWS.find((w) => w.name === name) || null;
121
+ }
122
+
123
+ export function loadBuiltinPrompt(name, userInput) {
124
+ const wf = getBuiltinWorkflow(name);
125
+ if (!wf) return null;
126
+ return wf.buildPrompt(userInput || "");
127
+ }