@noobdemon/noob-cli 1.13.2 → 1.13.4
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/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/agent.js +5 -5
- package/src/i18n.js +1 -0
- package/src/repl.js +16 -1
- package/src/tools.js +4 -1
- package/src/tui.js +25 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Tất cả thay đổi đáng kể của `@noobdemon/noob-cli` được ghi vào file này.
|
|
4
4
|
|
|
5
|
+
## [1.13.4] - 2026-06-29
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Ctrl+Backspace / Ctrl+W xoá nguyên từ** (`src/tui.js`): trên thanh nhập, Ctrl+Backspace (`\x08`/Ctrl+H ở phần lớn terminal) và Ctrl+W xoá cả từ trước con trỏ thay vì từng ký tự. Dùng cùng quy ước ranh giới từ với di chuyển từ (khoảng trắng + chip ảnh dán tính là ranh giới).
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **Chặn slash command khi model đang chạy** (`src/repl.js` + `src/i18n.js`): gõ slash (`/kc`, `/model`, …) lúc task đang chạy không còn bị xếp hàng / chèn nhầm cho AI — in cảnh báo `⏳ model đang chạy — chờ xong task mới dùng được lệnh` và bỏ qua. Tin nhắn thường vẫn xếp hàng/chèn cho AI như cũ.
|
|
12
|
+
|
|
13
|
+
### Verified
|
|
14
|
+
- `npm test` 109/109 pass.
|
|
15
|
+
|
|
16
|
+
## [1.13.3] - 2026-06-29
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Message nhắc việc nội bộ không còn gây nhiễu mô hình** (`src/agent.js`): các message nhắc tiếp tục công việc (nhắc todo còn dở, hướng dẫn khi tool lỗi, cảnh báo gọi trùng tool) trước đây được chèn vào hội thoại như thể là tin nhắn người dùng và mở đầu bằng nhãn `[SYSTEM]`. Mô hình ở lượt sau đọc lại tưởng có người chèn lệnh giả dạng hệ thống vào cuộc trò chuyện nên sinh nghi ngờ, đi sửa lung tung. Giờ các message này đi đúng kênh kết quả tool và đổi nhãn thành `[TODO]`/`[TOOL GUIDANCE]`.
|
|
20
|
+
|
|
21
|
+
### Verified
|
|
22
|
+
- `npm test` 109/109 pass.
|
|
23
|
+
|
|
5
24
|
## [1.13.2] - 2026-06-28
|
|
6
25
|
|
|
7
26
|
### Added
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -479,18 +479,18 @@ export function todoContinuationMessage(toolName, ok, tasks) {
|
|
|
479
479
|
if (!tasks?.length) return null;
|
|
480
480
|
const next = tasks[0];
|
|
481
481
|
const status = ok ? `đã hoàn thành` : `vừa LỖI`;
|
|
482
|
-
return `[
|
|
482
|
+
return `[TODO] Việc "${toolName}" ${status}. Còn ${tasks.length} việc: ${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.`;
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
export function toolErrorGuidance(name, { result } = {}) {
|
|
486
486
|
if (name !== 'edit_file' || !String(result || '').includes('old_string not found')) return null;
|
|
487
487
|
const m = String(result).match(/NEXT REQUIRED TOOL:\s*(read_file \{[^\n]+\})/);
|
|
488
488
|
if (!m) return null;
|
|
489
|
-
return `[
|
|
489
|
+
return `[TOOL GUIDANCE] edit_file thất bại vì old_string sai. Bạn PHẢI gọi đúng tool này ngay: ${m[1]}. KHÔNG gọi edit_file lại trước khi đọc file. Sau đó copy đúng text từ read_file mới để retry.`;
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
export function duplicateToolGuidance(name, input) {
|
|
493
|
-
return `[
|
|
493
|
+
return `[TOOL GUIDANCE] Tool call vừa rồi TRÙNG HỆT lần trước: ${name} ${JSON.stringify(input || {})}. KHÔNG chạy tool này lại. Dùng kết quả đã có trong history; nếu cần tiến thêm thì gọi tool khác (grep/read_file/edit_file/run_command) hoặc trả lời Markdown nếu xong.`;
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
// Detect câu trả lời bị cắt giữa chừng — KHÔNG phải câu hoàn chỉnh.
|
|
@@ -722,7 +722,7 @@ export async function runAgent({
|
|
|
722
722
|
});
|
|
723
723
|
const toolOk = allow && !String(result || '').startsWith('ERROR:');
|
|
724
724
|
const errorNudge = allow ? toolErrorGuidance(call.name, { result }) : null;
|
|
725
|
-
if (errorNudge) history.push({ role: '
|
|
725
|
+
if (errorNudge) history.push({ role: 'tool', name: 'tool_guidance', content: errorNudge });
|
|
726
726
|
|
|
727
727
|
// ── Todo continuation nudge ──────────────────────────────────────────
|
|
728
728
|
// Sau mỗi tool result, inject nudge nếu còn task chưa xong.
|
|
@@ -730,7 +730,7 @@ export async function runAgent({
|
|
|
730
730
|
{
|
|
731
731
|
const tasks = pendingTasks || [];
|
|
732
732
|
const msg = todoContinuationMessage(call.name, toolOk, tasks);
|
|
733
|
-
if (msg) history.push({ role: '
|
|
733
|
+
if (msg) history.push({ role: 'tool', name: 'todo_continuation', content: msg });
|
|
734
734
|
}
|
|
735
735
|
|
|
736
736
|
// ── Loop detection ──────────────────────────────────────────────────
|
package/src/i18n.js
CHANGED
|
@@ -12,6 +12,7 @@ export const t = {
|
|
|
12
12
|
running: 'đang chạy…',
|
|
13
13
|
denied: 'đã từ chối',
|
|
14
14
|
queued: (n, txt) => `⏎ đã xếp hàng [${n}] · gửi khi model xong: ${txt}`,
|
|
15
|
+
slashBusy: '⏳ model đang chạy — chờ xong task mới dùng được lệnh (Ctrl+C để dừng)',
|
|
15
16
|
queueCleared: (n) => `(đã xoá ${n} tin đang xếp hàng)`,
|
|
16
17
|
steerHint:
|
|
17
18
|
'💬 Gõ + Enter bất cứ lúc nào để chèn ý cho AI giữa chừng (không ngắt task đang chạy).',
|
package/src/repl.js
CHANGED
|
@@ -164,6 +164,12 @@ export async function startRepl(opts = {}) {
|
|
|
164
164
|
onLine: (line) => {
|
|
165
165
|
// Submit khi KHÔNG có read() đang chờ = tin xếp hàng. Đang chạy task → sẽ
|
|
166
166
|
// CHÈN cho AI ở bước kế tiếp (steering); rảnh → gửi như lượt mới.
|
|
167
|
+
// ponytail: slash command lúc busy KHÔNG vào pending — chặn, bắt user chờ
|
|
168
|
+
// model xong task mới dùng lệnh (tránh lệnh bị xếp hàng / chèn nhầm cho AI).
|
|
169
|
+
if (abort && line.trim().startsWith('/')) {
|
|
170
|
+
tui.print(c.dim(' ' + t.slashBusy));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
167
173
|
pending.push(line);
|
|
168
174
|
tui.print(
|
|
169
175
|
abort
|
|
@@ -1869,7 +1875,16 @@ NGUYÊN TẮC:
|
|
|
1869
1875
|
return await execToolCore(name, input, { retried: true });
|
|
1870
1876
|
}
|
|
1871
1877
|
console.log(c.err(' ✗ ' + err.message));
|
|
1872
|
-
|
|
1878
|
+
// Framing: lỗi tool = GỌI SAI CÁCH, không phải mất quyền. Model có lịch sử
|
|
1879
|
+
// diễn giải nhầm 'ERROR:' thành 'mình không có quyền truy cập tool' rồi bỏ
|
|
1880
|
+
// cuộc. Nói rõ model VẪN có quyền, chỉ cần sửa tham số rồi gọi lại.
|
|
1881
|
+
return {
|
|
1882
|
+
allow: true,
|
|
1883
|
+
result:
|
|
1884
|
+
'ERROR: ' +
|
|
1885
|
+
err.message +
|
|
1886
|
+
'\n[Đây là lỗi do tham số gọi tool chưa đúng, KHÔNG phải mất quyền truy cập. Bạn VẪN có quyền chạy tool này — đọc kỹ thông báo lỗi, sửa tham số (path/old_string/...) rồi GỌI LẠI. Đừng kết luận là không truy cập được.]',
|
|
1887
|
+
};
|
|
1873
1888
|
}
|
|
1874
1889
|
}
|
|
1875
1890
|
|
package/src/tools.js
CHANGED
|
@@ -373,7 +373,10 @@ export const TOOLS = {
|
|
|
373
373
|
checkAbort(signal);
|
|
374
374
|
const file = abs(p);
|
|
375
375
|
const data = await fs.readFile(file, 'utf8');
|
|
376
|
-
if (old_string === new_string)
|
|
376
|
+
if (old_string === new_string)
|
|
377
|
+
throw new Error(
|
|
378
|
+
`old_string and new_string are identical in ${rel(file)} — no change to make. This is a CALL mistake, NOT a permissions/access problem: you DO have edit access. Set new_string to the DIFFERENT text you want, then call edit_file again.`
|
|
379
|
+
);
|
|
377
380
|
const useCRLF = data.includes('\r\n');
|
|
378
381
|
const adapt = (s) => {
|
|
379
382
|
const lf = s.replace(/\r\n/g, '\n');
|
package/src/tui.js
CHANGED
|
@@ -322,9 +322,7 @@ export function createTui({ onLine, onInterrupt, onEOF, onShiftTab, onCtrlT, com
|
|
|
322
322
|
if (x.image) return c.dim(imageLabel(x));
|
|
323
323
|
if (x.paste === undefined) return x.c;
|
|
324
324
|
const preview = pastePreview(x.paste);
|
|
325
|
-
const label = preview
|
|
326
|
-
? `[pasted ${x.lines} lines: "${preview}"]`
|
|
327
|
-
: `[pasted ${x.lines} lines]`;
|
|
325
|
+
const label = preview ? `[pasted ${x.lines} lines: "${preview}"]` : `[pasted ${x.lines} lines]`;
|
|
328
326
|
return c.dim(label);
|
|
329
327
|
};
|
|
330
328
|
const coloredInput = () => {
|
|
@@ -424,7 +422,8 @@ export function createTui({ onLine, onInterrupt, onEOF, onShiftTab, onCtrlT, com
|
|
|
424
422
|
let body = l;
|
|
425
423
|
if (i === curLine) {
|
|
426
424
|
if (colInLine >= l.length) body = l + `${REV} ${UNREV}`;
|
|
427
|
-
else
|
|
425
|
+
else
|
|
426
|
+
body = l.slice(0, colInLine) + `${REV}${l[colInLine]}${UNREV}` + l.slice(colInLine + 1);
|
|
428
427
|
}
|
|
429
428
|
return (i === 0 ? promptLabel : indent) + body;
|
|
430
429
|
})
|
|
@@ -716,6 +715,19 @@ export function createTui({ onLine, onInterrupt, onEOF, onShiftTab, onCtrlT, com
|
|
|
716
715
|
histPos = null;
|
|
717
716
|
}
|
|
718
717
|
}
|
|
718
|
+
// Ctrl+Backspace / Ctrl+W: xoá nguyên từ trước con trỏ (cùng quy ước ranh giới
|
|
719
|
+
// từ với moveWordLeft — khoảng trắng + chip dán tính là ranh giới).
|
|
720
|
+
function deleteWordLeft() {
|
|
721
|
+
const start = cur;
|
|
722
|
+
while (cur > 0 && charAt(cur - 1) === ' ') cur -= 1;
|
|
723
|
+
if (cur > 0 && charAt(cur - 1) === null)
|
|
724
|
+
cur -= 1; // qua 1 chip
|
|
725
|
+
else while (cur > 0 && charAt(cur - 1) !== null && charAt(cur - 1) !== ' ') cur -= 1;
|
|
726
|
+
if (cur < start) {
|
|
727
|
+
cells.splice(cur, start - cur);
|
|
728
|
+
histPos = null;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
719
731
|
// null = ô dán (coi như ranh giới từ); ngược lại trả ký tự của ô.
|
|
720
732
|
const charAt = (k) =>
|
|
721
733
|
cells[k] && cells[k].paste === undefined && !cells[k].image ? cells[k].c : null;
|
|
@@ -923,7 +935,15 @@ export function createTui({ onLine, onInterrupt, onEOF, onShiftTab, onCtrlT, com
|
|
|
923
935
|
i++;
|
|
924
936
|
continue;
|
|
925
937
|
}
|
|
926
|
-
if (ch === '\
|
|
938
|
+
if (ch === '\x08' || ch === '\x17') {
|
|
939
|
+
// Ctrl+Backspace (nhiều terminal gửi \x08 = Ctrl+H) và Ctrl+W: xoá 1 từ.
|
|
940
|
+
deleteWordLeft();
|
|
941
|
+
refreshMenu();
|
|
942
|
+
draw();
|
|
943
|
+
i++;
|
|
944
|
+
continue;
|
|
945
|
+
} // Ctrl+Backspace / Ctrl+W
|
|
946
|
+
if (ch === '\x7f') {
|
|
927
947
|
backspace();
|
|
928
948
|
refreshMenu();
|
|
929
949
|
draw();
|