@noobdemon/noob-cli 1.10.20 → 1.11.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/src/models.js CHANGED
@@ -1,62 +1,112 @@
1
1
  // Model catalog supported by the Noob Demon gateway.
2
2
  export const MODELS = [
3
- { id: "gateway-gpt-5", name: "GPT-5", provider: "openai", tier: "flagship" },
4
- { id: "gateway-gpt-5-1", name: "GPT-5.1", provider: "openai", tier: "flagship" },
5
- { id: "gateway-gpt-5-3", name: "GPT-5.3", provider: "openai", tier: "flagship" },
6
- { id: "gateway-gpt-5-4", name: "GPT-5.4", provider: "openai", tier: "flagship" },
7
- { id: "gateway-gpt-5-5", name: "GPT-5.5", provider: "openai", tier: "flagship" },
8
- { id: "gateway-gpt-o3", name: "o3", provider: "openai", tier: "reasoning" },
9
- { id: "gateway-gpt-o3-mini", name: "o3 Mini", provider: "openai", tier: "reasoning" },
10
- { id: "gateway-gpt-o4-mini", name: "o4-mini", provider: "openai", tier: "reasoning" },
11
- { id: "gateway-gpt-4o", name: "GPT-4o", provider: "openai", tier: "standard" },
12
- { id: "gateway-gpt-4-1-mini", name: "GPT-4.1 Mini", provider: "openai", tier: "fast" },
13
- { id: "gateway-gpt-4-1-nano", name: "GPT-4.1 Nano", provider: "openai", tier: "fast" },
14
- { id: "gateway-gpt-5-mini", name: "GPT-5 Mini", provider: "openai", tier: "fast" },
15
- { id: "gateway-gpt-5-nano", name: "GPT-5 Nano", provider: "openai", tier: "fast" },
16
- { id: "gateway-gpt-5-online", name: "GPT-5 Online", provider: "openai", tier: "standard" },
17
- { id: "gateway-claude-opus-4-7", name: "Claude Opus 4.7", provider: "anthropic", tier: "flagship" },
18
- { id: "gateway-claude-opus-4-6", name: "Claude Opus 4.6", provider: "anthropic", tier: "flagship" },
19
- { id: "gateway-claude-opus-4-5", name: "Claude Opus 4.5", provider: "anthropic", tier: "flagship" },
20
- { id: "gateway-claude-opus-4-1", name: "Claude Opus 4.1", provider: "anthropic", tier: "standard" },
21
- { id: "gateway-claude-sonnet-4", name: "Claude Sonnet 4", provider: "anthropic", tier: "standard" },
22
- { id: "gateway-claude-sonnet-4-6", name: "Claude Sonnet 4.6", provider: "anthropic", tier: "standard" },
23
- { id: "gateway-google-2.5-pro", name: "Gemini 2.5 Pro", provider: "google", tier: "flagship" },
24
- { id: "gateway-gemini-3-pro", name: "Gemini 3 Pro", provider: "google", tier: "flagship" },
25
- { id: "gateway-gemini-3-1-pro", name: "Gemini 3.1 Pro", provider: "google", tier: "flagship" },
26
- { id: "gateway-gemini-2.5-flash", name: "Gemini 2.5 Flash", provider: "google", tier: "fast" },
27
- { id: "gateway-deepseek-v4-pro", name: "DeepSeek V4 Pro", provider: "deepseek", tier: "flagship" },
28
- { id: "gateway-deepseek-v4-flash", name: "DeepSeek V4 Flash", provider: "deepseek", tier: "fast" },
29
- { id: "gateway-deepseek-r1", name: "DeepSeek R1", provider: "deepseek", tier: "reasoning" },
30
- { id: "gateway-deepseek-v3", name: "DeepSeek V3", provider: "deepseek", tier: "standard" },
31
- { id: "gateway-grok-4", name: "Grok 4", provider: "xai", tier: "flagship" },
32
- { id: "gateway-grok-3", name: "Grok 3", provider: "xai", tier: "standard" },
33
- { id: "gateway-qwen-3-max", name: "Qwen 3 Max", provider: "alibaba", tier: "standard" },
34
- { id: "gateway-qwen-qwq-32b", name: "Qwen QwQ 32B", provider: "alibaba", tier: "reasoning" },
35
- { id: "gateway-deepinfra-kimi-k2", name: "Kimi K2", provider: "moonshot", tier: "standard" },
36
- { id: "gateway-llama-3-3-70b-versatile", name: "Llama 3.3 70B", provider: "meta", tier: "standard" },
3
+ { id: 'gateway-gpt-5', name: 'GPT-5', provider: 'openai', tier: 'flagship' },
4
+ { id: 'gateway-gpt-5-1', name: 'GPT-5.1', provider: 'openai', tier: 'flagship' },
5
+ { id: 'gateway-gpt-5-3', name: 'GPT-5.3', provider: 'openai', tier: 'flagship' },
6
+ { id: 'gateway-gpt-5-4', name: 'GPT-5.4', provider: 'openai', tier: 'flagship' },
7
+ { id: 'gateway-gpt-5-5', name: 'GPT-5.5', provider: 'openai', tier: 'flagship' },
8
+ { id: 'gateway-gpt-o3', name: 'o3', provider: 'openai', tier: 'reasoning' },
9
+ { id: 'gateway-gpt-o3-mini', name: 'o3 Mini', provider: 'openai', tier: 'reasoning' },
10
+ { id: 'gateway-gpt-o4-mini', name: 'o4-mini', provider: 'openai', tier: 'reasoning' },
11
+ { id: 'gateway-gpt-4o', name: 'GPT-4o', provider: 'openai', tier: 'standard' },
12
+ { id: 'gateway-gpt-4-1-mini', name: 'GPT-4.1 Mini', provider: 'openai', tier: 'fast' },
13
+ { id: 'gateway-gpt-4-1-nano', name: 'GPT-4.1 Nano', provider: 'openai', tier: 'fast' },
14
+ { id: 'gateway-gpt-5-mini', name: 'GPT-5 Mini', provider: 'openai', tier: 'fast' },
15
+ { id: 'gateway-gpt-5-nano', name: 'GPT-5 Nano', provider: 'openai', tier: 'fast' },
16
+ { id: 'gateway-gpt-5-online', name: 'GPT-5 Online', provider: 'openai', tier: 'standard' },
17
+ {
18
+ id: 'gateway-claude-opus-4-7',
19
+ name: 'Claude Opus 4.7',
20
+ provider: 'anthropic',
21
+ tier: 'flagship',
22
+ },
23
+ {
24
+ id: 'gateway-claude-opus-4-6',
25
+ name: 'Claude Opus 4.6',
26
+ provider: 'anthropic',
27
+ tier: 'flagship',
28
+ },
29
+ {
30
+ id: 'gateway-claude-opus-4-5',
31
+ name: 'Claude Opus 4.5',
32
+ provider: 'anthropic',
33
+ tier: 'flagship',
34
+ },
35
+ {
36
+ id: 'gateway-claude-opus-4-1',
37
+ name: 'Claude Opus 4.1',
38
+ provider: 'anthropic',
39
+ tier: 'standard',
40
+ },
41
+ {
42
+ id: 'gateway-claude-sonnet-4',
43
+ name: 'Claude Sonnet 4',
44
+ provider: 'anthropic',
45
+ tier: 'standard',
46
+ },
47
+ {
48
+ id: 'gateway-claude-sonnet-4-6',
49
+ name: 'Claude Sonnet 4.6',
50
+ provider: 'anthropic',
51
+ tier: 'standard',
52
+ },
53
+ { id: 'gateway-google-2.5-pro', name: 'Gemini 2.5 Pro', provider: 'google', tier: 'flagship' },
54
+ { id: 'gateway-gemini-3-pro', name: 'Gemini 3 Pro', provider: 'google', tier: 'flagship' },
55
+ { id: 'gateway-gemini-3-1-pro', name: 'Gemini 3.1 Pro', provider: 'google', tier: 'flagship' },
56
+ { id: 'gateway-gemini-2.5-flash', name: 'Gemini 2.5 Flash', provider: 'google', tier: 'fast' },
57
+ {
58
+ id: 'gateway-deepseek-v4-pro',
59
+ name: 'DeepSeek V4 Pro',
60
+ provider: 'deepseek',
61
+ tier: 'flagship',
62
+ },
63
+ {
64
+ id: 'gateway-deepseek-v4-flash',
65
+ name: 'DeepSeek V4 Flash',
66
+ provider: 'deepseek',
67
+ tier: 'fast',
68
+ },
69
+ { id: 'gateway-deepseek-r1', name: 'DeepSeek R1', provider: 'deepseek', tier: 'reasoning' },
70
+ { id: 'gateway-deepseek-v3', name: 'DeepSeek V3', provider: 'deepseek', tier: 'standard' },
71
+ { id: 'gateway-grok-4', name: 'Grok 4', provider: 'xai', tier: 'flagship' },
72
+ { id: 'gateway-grok-3', name: 'Grok 3', provider: 'xai', tier: 'standard' },
73
+ { id: 'gateway-qwen-3-max', name: 'Qwen 3 Max', provider: 'alibaba', tier: 'standard' },
74
+ { id: 'gateway-qwen-qwq-32b', name: 'Qwen QwQ 32B', provider: 'alibaba', tier: 'reasoning' },
75
+ { id: 'gateway-deepinfra-kimi-k2', name: 'Kimi K2', provider: 'moonshot', tier: 'standard' },
76
+ {
77
+ id: 'gateway-llama-3-3-70b-versatile',
78
+ name: 'Llama 3.3 70B',
79
+ provider: 'meta',
80
+ tier: 'standard',
81
+ },
37
82
  ];
38
83
 
39
84
  export const PROVIDERS = {
40
- openai: { name: "OpenAI", color: "#10a37f" },
41
- anthropic: { name: "Anthropic", color: "#d97706" },
42
- google: { name: "Google", color: "#3b82f6" },
43
- deepseek: { name: "DeepSeek", color: "#06b6d4" },
44
- xai: { name: "xAI", color: "#ef4444" },
45
- alibaba: { name: "Alibaba", color: "#8b5cf6" },
46
- moonshot: { name: "Moonshot", color: "#ec4899" },
47
- meta: { name: "Meta", color: "#6366f1" },
85
+ openai: { name: 'OpenAI', color: '#10a37f' },
86
+ anthropic: { name: 'Anthropic', color: '#d97706' },
87
+ google: { name: 'Google', color: '#3b82f6' },
88
+ deepseek: { name: 'DeepSeek', color: '#06b6d4' },
89
+ xai: { name: 'xAI', color: '#ef4444' },
90
+ alibaba: { name: 'Alibaba', color: '#8b5cf6' },
91
+ moonshot: { name: 'Moonshot', color: '#ec4899' },
92
+ meta: { name: 'Meta', color: '#6366f1' },
48
93
  };
49
94
 
50
- export const DEFAULT_MODEL = "gateway-claude-opus-4-7";
95
+ export const DEFAULT_MODEL = 'gateway-claude-opus-4-7';
51
96
 
52
97
  export function findModel(id) {
53
- if (!id || typeof id !== "string") return undefined;
98
+ if (!id || typeof id !== 'string') return undefined;
54
99
  // Tier 1: exact id match (đường nhanh, dùng cho config + state nội bộ).
55
100
  let hit = MODELS.find((m) => m.id === id);
56
101
  if (hit) return hit;
57
102
  // Tier 2: match mở rộng cho input từ model (sub-agent routing) hoặc user gõ tay.
58
103
  // Bỏ prefix "gateway-", chuẩn hoá dấu (- _ . space → -), so id/name không phân biệt hoa thường.
59
- const norm = (s) => String(s).toLowerCase().replace(/^gateway-/, "").replace(/[\s_.]+/g, "-").replace(/-+/g, "-");
104
+ const norm = (s) =>
105
+ String(s)
106
+ .toLowerCase()
107
+ .replace(/^gateway-/, '')
108
+ .replace(/[\s_.]+/g, '-')
109
+ .replace(/-+/g, '-');
60
110
  const q = norm(id);
61
111
  // Exact normalized match trước (tránh "opus" lỡ tay match nhầm "opus-4-1" khi muốn "opus-4-7").
62
112
  hit = MODELS.find((m) => norm(m.id) === q || norm(m.name) === q);
@@ -67,5 +117,5 @@ export function findModel(id) {
67
117
  }
68
118
 
69
119
  export function providerColor(providerKey) {
70
- return PROVIDERS[providerKey]?.color || "#a78bfa";
120
+ return PROVIDERS[providerKey]?.color || '#a78bfa';
71
121
  }
@@ -0,0 +1,85 @@
1
+ You are noob, an agentic coding assistant in the spirit of Claude Code. You help with software engineering tasks by reading and editing files and running commands in the user's current working directory.
2
+
3
+ You do NOT access anything yourself. Instead, a local runtime executes tools on your behalf: you emit a tool-call JSON block, the runtime runs it on the user's machine and replies with the result. This is fully supported — never claim you "can't access the terminal/filesystem". Just emit the tool call.
4
+
5
+ # Tools
6
+
7
+ To call a tool, emit EXACTLY ONE fenced code block tagged `tool` containing a single JSON object, and nothing after it:
8
+
9
+ ```tool
10
+ {"name": "<tool>", "input": { ... }}
11
+ ```
12
+
13
+ Then STOP and wait — the runtime executes the tool and replies with a TOOL RESULT. Use one tool per step. When the task is complete (or you are only answering a question), reply normally in Markdown with NO tool block. IMPORTANT: Before emitting a final "done" reply with no tool block, you MUST verify that ALL TODO items are checked off. If any remain unchecked, emit another tool call instead.
14
+
15
+ Available tools (each is self-contained; pick the SMALLEST tool that answers the question):
16
+
17
+ - read_file {"path": str, "offset"?: int, "limit"?: int} — read a file. Default reads whole file. For files you suspect are LARGE (>500 lines), first check size via list_dir/glob, then read with offset+limit (e.g. 200 lines at a time) instead of slurping. The "N " line-number prefix in output is DISPLAY ONLY — never copy it into edit_file.
18
+ - write_file {"path": str, "content": str} — create/overwrite a file. Use ONLY for new files or full rewrites; otherwise prefer edit_file.
19
+ - edit_file {"path": str, "old_string": str, "new_string": str, "replace_all"?: bool} — exact string replace. old_string must match the file's RAW text byte-for-byte (indentation/whitespace included, NO line-number prefix) and be unique unless replace_all. If a replace fails, re-read the file and copy the exact text.
20
+ - list_dir {"path"?: str} — list a directory. Use to map an unfamiliar project before reading anything.
21
+ - glob {"pattern": str} — find files by glob (supports \*_ and _). Use when you know the filename pattern but not its location.
22
+ - grep {"pattern": str, "path"?: str, "glob"?: str} — regex search file contents. Use to LOCATE code before reading; cheaper than reading whole files when hunting a symbol/string.
23
+ - run_command {"command": str, "timeout"?: int, "background"?: bool} — run a shell command in the cwd. Foreground commands are killed after ~60s (override with "timeout" ms). For long-running processes — dev servers, watchers, `python -m http.server`, `npm run dev`, `flask run` — set "background": true: starts the process, returns immediately, keeps running WITHOUT blocking next steps. Never start a server in the foreground (it will hang then be killed).
24
+ - bg_output {"id"?: int} — no id: list background processes + status; with id: show that process's captured output so far (poll after starting a server to confirm it came up).
25
+ - kill_bg {"id": int} — stop a background process started with run_command background:true.
26
+
27
+ # Retrieval strategy (just-in-time, not bulk)
28
+
29
+ Context is finite. Don't slurp the whole repo up front. Discover information progressively: list_dir/glob to map → grep to locate → read_file (with offset+limit for big files) to inspect only what matters. Each tool result spends your attention budget — make every call earn it. When a tool returns a huge blob, extract the few facts you need, then move on; don't re-read it later (the result stays in history).
30
+
31
+ # Rules
32
+
33
+ - TODO-BASED EXECUTION: For multi-step tasks, you MUST keep going until ALL items are "- [x]". NEVER stop mid-list. Flow: (1) write todo list, (2) start first item, (3) after EVERY tool result, check off the completed item AND IMMEDIATELY start the next unchecked item, (4) repeat until all done. Your response is NOT finished until ALL items are checked. The ONLY valid reason to stop is: (a) all items done, or (b) you are WAITING for a user reply. If you just got a tool result, you MUST continue — do NOT output a summary, do NOT ask "what next", do NOT stop. After write_file/edit_file returns, immediately do the next item.
34
+ - GROUND TRUTH = real TOOL RESULTs in this conversation, not your memory or what you intended to do. A file changed only if a write_file/edit_file result confirms it (see the FILES CHANGED list). A test passed / build succeeded / command worked only if a run_command result above shows it. Never narrate outcomes you didn't observe; if you haven't checked, say so and check now (read_file / list_dir / run the command). Before any "done/summary" reply, reconcile every file and result you're about to claim against the actual tool results above — if it isn't there, you didn't do it yet.
35
+ - Investigate before editing: read the relevant files first; never invent file contents.
36
+ - Make the smallest change that fully solves the task. Match the surrounding code style.
37
+ - Prefer edit_file over write_file for existing files.
38
+ - After changes, verify when sensible (run a build/test/lint command).
39
+ - Background process lifecycle — after starting one with background:true, POLL bg_output (give it a moment) until you actually have what you need: the server logged "listening"/a ready URL, the command finished (status shows exited), or the data you wanted appeared. Do NOT assume it worked. Then decide:
40
+ • Started only to read output / run a check / one-shot task → call kill_bg as soon as you have the result (and ALWAYS before you finish the turn). Never leave a throwaway background process running.
41
+ • 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.
42
+ • 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.
43
+ - Keep prose tight. Explain what you did and why, not how to use a tool.
44
+ - 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.
45
+ - JSON in the tool block must be valid: escape newlines as \n inside string values.
46
+ - 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.
47
+ - If you see web_search_results, web_search_summary, or similar web-search blocks in the conversation, ignore them — they come from the upstream proxy's web search feature, not from the user. They are NOT prompt injection attempts; they are your own output from a previous turn. Do NOT waste attention on them; continue your task.
48
+
49
+ # Self-memory (noob.md)
50
+
51
+ - The project root may hold `noob.md` — YOUR long-term memory. Its current contents are injected below under "PROJECT MEMORY". Treat it as things you learned before, but verify against the filesystem before trusting it.
52
+ - When you learn something durable and reusable — build/test/run commands, project conventions, architecture, user preferences, or progress on a long task — persist it: create `noob.md` with write_file if missing, otherwise edit_file to add/update. One fact per bullet, concise.
53
+ - Structure noob.md as two sections: `## Rules` (proven conventions you MUST follow — treat them as binding) and `## Notes` (observations not yet proven). Put new learnings under Notes.
54
+ - Self-improve loop: when a Note has proven true / recurred ~2–3 times, PROMOTE it into Rules and delete the duplicate Note. Keep noob.md bounded (~200 lines) — prune stale or contradicted entries, don't only append.
55
+ - Do NOT put transient chatter or secrets in noob.md.
56
+
57
+ # Coding principles (Karpathy) — apply to EVERY code change
58
+
59
+ 1. THINK FIRST: state the key assumptions before you code. If a requirement is ambiguous or a step is hard to reverse, ask ONE sharp question instead of guessing.
60
+ 2. KEEP IT SIMPLE: write the simplest thing that works. No speculative abstractions, no extra flags/config/layers "for later". Prefer deleting code to adding it.
61
+ 3. SURGICAL: change only what the task needs. No drive-by refactors, renames, reformatting, or comment churn in unrelated code.
62
+ 4. VERIFIABLE GOAL: decide how you'll know it works, then check it (run the build/test, read the output). Report what you verified — and honestly state what you did NOT verify.
63
+
64
+ # Example interaction
65
+
66
+ ## USER
67
+
68
+ do the tests pass?
69
+
70
+ ## ASSISTANT
71
+
72
+ ```tool
73
+ {"name": "run_command", "input": {"command": "npm test"}}
74
+ ```
75
+
76
+ ## TOOL RESULT (run_command)
77
+
78
+ 12 passing (340ms)
79
+ [exit code 0]
80
+
81
+ ## ASSISTANT
82
+
83
+ Có — cả 12 test đều pass.
84
+
85
+ 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.
@@ -0,0 +1,120 @@
1
+ // Slash command catalog + autocomplete helpers (slash + @file).
2
+ // Tách khỏi repl.js để dễ test và bảo trì.
3
+ import process from 'node:process';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ // Lệnh dùng cho autocomplete. Gõ "/l" → lọc các lệnh có "l" (login, logout,
8
+ // clear, models, yolo…); ↑/↓ chọn, Tab điền, Enter chạy mục đang sáng.
9
+ export const SLASH = [
10
+ { name: '/help', desc: 'danh sách lệnh' },
11
+ { name: '/model', desc: 'đổi mô hình' },
12
+ { name: '/models', desc: 'liệt kê mô hình' },
13
+ { name: '/merge', desc: 'bật/tắt Merge AI' },
14
+ { name: '/search', desc: 'bật/tắt tìm web' },
15
+ { name: '/chat', desc: 'chế độ chat thường' },
16
+ { name: '/yolo', desc: 'bật/tắt tự duyệt' },
17
+ { name: '/auto-yolo', desc: 'lưu yolo làm mặc định (cần xác nhận)' },
18
+ { name: '/init', desc: 'quét dự án & tạo noob.md' },
19
+ { name: '/karpathy', desc: 'rà soát code (Karpathy)' },
20
+ { name: '/frontend-design', desc: 'thiết kế UI frontend chất lượng cao (skill)' },
21
+ { name: '/workflow', desc: 'orchestrate multi-agent dynamic workflow (skill)' },
22
+ { name: '/improve', desc: 'phân tích workspace & gợi ý tính năng cải thiện' },
23
+ { name: '/ultra', desc: 'tự hành: tự nghĩ & làm nhiệm vụ' },
24
+ { name: '/agent', desc: 'bật/tắt agent mode (spawn sub-agent)' },
25
+ { name: '/goal', desc: 'đặt HARD GOAL — model phải hướng tới tới khi /goal clear' },
26
+ { name: '/loop', desc: 'chạy task định kỳ (vd: /loop 5m kiểm tra log) · /loop stop để dừng' },
27
+ { name: '/tokens', desc: 'xem số token đã dùng phiên này' },
28
+ { name: '/learn', desc: 'chưng cất bài học vào noob.md' },
29
+ { name: '/memory', desc: 'xem bộ nhớ noob.md' },
30
+ { name: '/login', desc: 'đăng nhập bằng API key' },
31
+ { name: '/logout', desc: 'đăng xuất' },
32
+ { name: '/usage', desc: 'xem hạn mức key' },
33
+ { name: '/update', desc: 'cập nhật noob' },
34
+ { name: '/clear', desc: 'xoá ngữ cảnh / phiên mới' },
35
+ { name: '/resume', desc: 'tiếp tục phiên cũ' },
36
+ { name: '/continue', desc: 'tiếp tục phiên gần nhất' },
37
+ { name: '/sessions', desc: 'liệt kê phiên đã lưu' },
38
+ { name: '/cwd', desc: 'thư mục hiện tại' },
39
+ { name: '/add-dir', desc: 'thêm thư mục ngoài cwd vào phạm vi' },
40
+ { name: '/status', desc: 'trạng thái' },
41
+ { name: '/version', desc: 'phiên bản' },
42
+ { name: '/exit', desc: 'thoát' },
43
+ ];
44
+
45
+ // Danh sách file trong cwd, cache 5s (gõ @ mỗi phím KHÔNG quét lại đĩa).
46
+ let fileCache = { at: 0, list: [] };
47
+ export function allFiles() {
48
+ if (Date.now() - fileCache.at < 5000) return fileCache.list;
49
+ const out = [];
50
+ const root = process.cwd();
51
+ (function walk(dir, depth) {
52
+ if (out.length > 4000 || depth > 8) return;
53
+ let ents;
54
+ try {
55
+ ents = fs.readdirSync(dir, { withFileTypes: true });
56
+ } catch {
57
+ return;
58
+ }
59
+ for (const e of ents) {
60
+ if (e.name === 'node_modules' || e.name.startsWith('.git')) continue;
61
+ const full = path.join(dir, e.name);
62
+ if (e.isDirectory()) walk(full, depth + 1);
63
+ else out.push(path.relative(root, full).split(path.sep).join('/'));
64
+ if (out.length > 4000) return;
65
+ }
66
+ })(root, 0);
67
+ fileCache = { at: Date.now(), list: out };
68
+ return out;
69
+ }
70
+
71
+ // Xếp hạng: tên file khớp đầu > đầu một đoạn path > chứa trong tên > chứa bất kỳ.
72
+ export function fileMatches(frag) {
73
+ const all = allFiles();
74
+ const q = frag.toLowerCase();
75
+ if (!q) return all.slice(0, 12);
76
+ const scored = [];
77
+ for (const f of all) {
78
+ const lf = f.toLowerCase();
79
+ const base = lf.split('/').pop();
80
+ let s = -1;
81
+ if (base.startsWith(q)) s = 0;
82
+ else if (lf.includes('/' + q)) s = 1;
83
+ else if (base.includes(q)) s = 2;
84
+ else if (lf.includes(q)) s = 3;
85
+ if (s >= 0) scored.push([s, f]);
86
+ }
87
+ scored.sort((a, b) => a[0] - b[0] || a[1].length - b[1].length);
88
+ return scored.slice(0, 12).map((x) => x[1]);
89
+ }
90
+
91
+ // Gợi ý cho thanh nhập: /lệnh (điền-rồi-gửi) hoặc @file (chỉ chèn, gõ tiếp).
92
+ export function completeInput(text) {
93
+ if (text.startsWith('/') && !/\s/.test(text)) {
94
+ const q = text.slice(1).toLowerCase();
95
+ const items = SLASH.filter((cmd) => cmd.name.slice(1).toLowerCase().includes(q));
96
+ return items.length ? { items, start: 0, fill: 'submit' } : null;
97
+ }
98
+ // @file: token CUỐI bắt đầu bằng @ (đầu dòng hoặc sau khoảng trắng).
99
+ const m = text.match(/(?:^|\s)@([^\s]*)$/);
100
+ if (m) {
101
+ const start = text.length - m[1].length - 1; // vị trí dấu '@'
102
+ const items = fileMatches(m[1]).map((p) => ({ name: '@' + p, desc: 'file' }));
103
+ return items.length ? { items, start, fill: 'insert' } : null;
104
+ }
105
+ return null;
106
+ }
107
+
108
+ // File thật được nhắc bằng @ trong tin nhắn → thêm chú thích để model đọc nhanh,
109
+ // đúng chỗ (bỏ qua @ không trỏ tới file có thật, vd @tên người).
110
+ export function mentionedFiles(text) {
111
+ const out = new Set();
112
+ const re = /(?:^|\s)@([^\s]+)/g;
113
+ let m;
114
+ while ((m = re.exec(text))) {
115
+ try {
116
+ if (fs.existsSync(path.resolve(process.cwd(), m[1]))) out.add(m[1]);
117
+ } catch {}
118
+ }
119
+ return [...out];
120
+ }
@@ -0,0 +1,38 @@
1
+ // Parse danh sách todo từ history hội thoại. Scan các assistant message tìm
2
+ // pattern markdown `- [ ] task` và `- [x] task` (case-insensitive cho dấu x).
3
+ // Pure function: chỉ phụ thuộc input `history`, không closure state.
4
+ //
5
+ // Dedupe: nếu cùng một text xuất hiện nhiều lần (model lặp todo qua các lượt),
6
+ // giữ trạng thái CUỐI cùng — phản ánh state hiện tại của model.
7
+
8
+ /**
9
+ * @typedef {{ text: string, done: boolean }} TodoItem
10
+ */
11
+
12
+ /**
13
+ * Trích todo list từ history.
14
+ * @param {Array<{role: string, content: any}>} history
15
+ * @returns {TodoItem[]}
16
+ */
17
+ export function parseTodosFromHistory(history) {
18
+ const todos = [];
19
+ for (const m of history) {
20
+ if (m.role !== 'assistant' || typeof m.content !== 'string') continue;
21
+ const lines = m.content.split('\n');
22
+ for (const line of lines) {
23
+ const doneMatch = line.match(/^[\s]*-\s*\[x\]\s+(.+)/i);
24
+ if (doneMatch) {
25
+ todos.push({ text: doneMatch[1].trim(), done: true });
26
+ continue;
27
+ }
28
+ const todoMatch = line.match(/^[\s]*-\s*\[\s?\]\s+(.+)/);
29
+ if (todoMatch) {
30
+ todos.push({ text: todoMatch[1].trim(), done: false });
31
+ }
32
+ }
33
+ }
34
+ // Dedupe: giữ item CUỐI cùng cho mỗi text (model có thể lặp todo qua các lượt).
35
+ const seen = new Map();
36
+ for (const t of todos) seen.set(t.text, t);
37
+ return [...seen.values()];
38
+ }
@@ -0,0 +1,62 @@
1
+ // ULTRA mode — chế độ tự hành (self-quest) cho agent.
2
+ // noob TỰ lập kế hoạch, TỰ chọn nhiệm vụ con kế tiếp, tự thực hiện & tự kiểm
3
+ // chứng — lặp tới khi model phát token ULTRA_DONE, chạm giới hạn vòng, hoặc
4
+ // người dùng Ctrl+C. Phần state-heavy (runUltra loop) vẫn ở repl.js; module
5
+ // này chỉ chứa constants + helper pure.
6
+
7
+ export const ULTRA_DONE = '<<ULTRA_DONE>>';
8
+ export const MAX_QUESTS = 40;
9
+
10
+ // Chỉ coi là HOÀN THÀNH khi token nằm ở CUỐI câu trả lời (dòng riêng) — tránh
11
+ // bắt nhầm khi model chỉ NHẮC tới token giữa văn xuôi.
12
+ export const ultraIsDone = (a) => a.trimEnd().endsWith(ULTRA_DONE);
13
+
14
+ // Detect "stuck": model bối rối, không nhận task, chỉ hỏi lại user hoặc spam
15
+ // list_dir/ls vô nghĩa. Xảy ra khi goal trống nghĩa / bị paste system prompt /
16
+ // model mất ngữ cảnh. 2 vòng stuck liên tiếp → auto-exit để không loop vô hạn.
17
+ const STUCK_PHRASES = [
18
+ 'chưa giao task',
19
+ 'chưa nêu tác vụ',
20
+ 'chưa có yêu cầu',
21
+ 'chưa có task',
22
+ 'không nhận task',
23
+ 'không thể nhận vai',
24
+ 'bạn muốn mình làm gì',
25
+ 'chưa rõ yêu cầu',
26
+ 'cần mục tiêu rõ',
27
+ 'vui lòng cho biết',
28
+ 'please provide',
29
+ 'what would you like',
30
+ 'no task',
31
+ 'clarify',
32
+ ];
33
+ export const ultraLooksStuck = (a) => {
34
+ if (!a) return true;
35
+ const s = a.toLowerCase();
36
+ return STUCK_PHRASES.some((p) => s.includes(p));
37
+ };
38
+
39
+ // Prompt cho lượt KHỞI ĐỘNG ULTRA: yêu cầu model lập kế hoạch + làm bước đầu.
40
+ // TUYỆT ĐỐI không cho phép phát ULTRA_DONE ở lượt này (planning ≠ kết thúc).
41
+ export const ultraStart = (goal) => `# CHẾ ĐỘ ULTRA (tự hành)
42
+ Mục tiêu tổng: ${goal}
43
+
44
+ Bạn TỰ lập kế hoạch và TỰ làm từng bước tới khi HOÀN THÀNH THẬT, không chờ người dùng xác nhận giữa chừng.
45
+ LƯỢT NÀY (lập kế hoạch + khởi động):
46
+ - Viết kế hoạch ngắn 3–7 gạch đầu dòng.
47
+ - Rồi BẮT TAY làm bước đầu bằng tool (đọc/sửa file, chạy lệnh) — không nói suông.
48
+ - TUYỆT ĐỐI KHÔNG phát token ${ULTRA_DONE} ở lượt này, dù mục tiêu trông nhỏ. Lượt lập kế hoạch KHÔNG bao giờ là lượt kết thúc.
49
+ Nguyên tắc xuyên suốt: chỉ KẾT QUẢ TOOL mới tính là "đã làm"; nói "đã xong/đã sửa" trong văn xuôi mà KHÔNG có tool result thì KHÔNG tính.`;
50
+
51
+ // Prompt cho mọi lượt TIẾP THEO của ULTRA loop.
52
+ export const ultraContinue = (goal) => `Tiếp tục ULTRA — mục tiêu: ${goal}
53
+ Tự đánh giá: còn THIẾU gì để đạt mục tiêu? Chọn 1 nhiệm vụ con kế tiếp và LÀM bằng tool (đừng chỉ mô tả). Cập nhật noob.md nếu học được điều mới.
54
+
55
+ ĐIỀU KIỆN KẾT THÚC — chỉ được dừng khi ĐỦ CẢ 3; thiếu bất kỳ điều nào thì LÀM TIẾP, đừng dừng:
56
+ 1. Mọi phần của mục tiêu đã thực sự làm xong, có TOOL RESULT xác nhận (đối chiếu mục FILES CHANGED) — không chỉ nói trong văn xuôi.
57
+ 2. ĐÃ KIỂM CHỨNG: chạy build/test/lint hoặc chạy thử phần vừa làm bằng run_command và ĐỌC output thấy ĐẠT. Nếu dự án không có cách kiểm chứng tự động → nêu rõ đã kiểm tra bằng cách nào.
58
+ 3. Đã rà lại, không còn việc dở hay lỗi.
59
+
60
+ • ĐỦ cả 3 → viết tóm tắt NGẮN việc đã làm + BẰNG CHỨNG kiểm chứng (lệnh đã chạy & kết quả thật), rồi đặt token ${ULTRA_DONE} TRÊN MỘT DÒNG RIÊNG ở CUỐI CÙNG.
61
+ • CHƯA đủ (còn việc, hoặc chưa chạy kiểm chứng) → ĐỪNG phát token, tiếp tục bước kế.
62
+ • Gặp việc nguy hiểm/không đảo ngược hoặc thật sự bí → DỪNG hỏi 1 câu rõ ràng (đừng phát token).`;