@noobdemon/noob-cli 1.11.0 → 1.11.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/CHANGELOG.md +11 -0
- package/package.json +1 -1
- package/src/api.js +26 -9
- package/src/tools.js +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
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.11.1] - 2026-06-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Câu trả lời không còn bị cắt cụt khi viết về web search / SEO** (`src/api.js`): regex dọn block `## Web Search Results` chỉ chạy ở `/search` mode. Trước đây nó chạy luôn ở chat → nếu bạn đang viết content/SEO có heading dạng đó, phần còn lại của câu trả lời bị nuốt mất.
|
|
9
|
+
- **SSE stream bền hơn với reverse proxy CRLF**: chuẩn hoá `\r\n` → `\n` trước khi parse, tránh dòng SSE bị bỏ sót khi gateway trả line-ending hỗn tạp.
|
|
10
|
+
- **Cờ `--insecure-tls` áp dụng đúng thời điểm** (`src/api.js`): bỏ lệnh áp dụng top-level (chạy trước parse argv vì ESM hoist). Giờ chỉ áp dụng đúng một lần từ `bin/noob.js` sau khi đọc argv.
|
|
11
|
+
- **Không còn `MaxListenersExceededWarning` khi chạy test / dynamic import** (`src/tools.js`): handler cleanup tiến trình nền (`exit` + `SIGTERM`) có guard chống đăng ký kép.
|
|
12
|
+
|
|
13
|
+
### Chore
|
|
14
|
+
- **`.gitignore` / `.npmignore`**: bổ sung `*.bak`, `*.swp`, `Thumbs.db`, `Desktop.ini`, lockfile backup của npm/yarn/pnpm — defence-in-depth, không ảnh hưởng nội dung package đã publish.
|
|
15
|
+
|
|
5
16
|
## [1.11.0] - 2026-06-10
|
|
6
17
|
|
|
7
18
|
### Fixed
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -45,8 +45,10 @@ export function applyInsecureTLS() {
|
|
|
45
45
|
'\x1b[33m⚠ NOOB_INSECURE_TLS=1: TLS verification DISABLED for this process. MITM-vulnerable. Unset when done.\x1b[0m'
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
|
-
//
|
|
49
|
-
|
|
48
|
+
// KHÔNG gọi applyInsecureTLS() ở top-level: vì ESM hoist, lệnh này sẽ chạy
|
|
49
|
+
// TRƯỚC khi bin/noob.js kịp parse `--insecure-tls` và set env. bin/noob.js đã
|
|
50
|
+
// gọi applyInsecureTLS() sau khi parse argv — đó là điểm duy nhất.
|
|
51
|
+
// Nếu env NOOB_INSECURE_TLS=1 đã có sẵn từ shell, bin/noob.js cũng sẽ áp dụng.
|
|
50
52
|
|
|
51
53
|
function authHeaders() {
|
|
52
54
|
const h = { 'Content-Type': 'application/json' };
|
|
@@ -98,7 +100,13 @@ async function parseError(resp) {
|
|
|
98
100
|
// Unlimited.surf & similar proxies inject web search results as XML/markdown blocks
|
|
99
101
|
// into the SSE stream. These get appended as regular text and confuse the AI model
|
|
100
102
|
// when seen in subsequent turns (it thinks they are prompt injection attempts).
|
|
101
|
-
|
|
103
|
+
//
|
|
104
|
+
// QUAN TRỌNG: chỉ chạy stripping markdown-heading khi mode === 'search'. Với
|
|
105
|
+
// chat/merge, người dùng có thể đang viết về chính chủ đề "Web Search Results"
|
|
106
|
+
// (vd nội dung SEO/content) — regex tham sẽ nuốt mất câu trả lời thật.
|
|
107
|
+
// Các tag XML/bracket/plain-marker thì hiếm khi xuất hiện tự nhiên nên vẫn an
|
|
108
|
+
// toàn để strip ở mọi mode.
|
|
109
|
+
function cleanResponseText(text, mode = 'chat') {
|
|
102
110
|
if (!text) return text;
|
|
103
111
|
let cleaned = text;
|
|
104
112
|
// XML/SGML style: <web_search_results>...</web_search_results>
|
|
@@ -110,11 +118,15 @@ function cleanResponseText(text) {
|
|
|
110
118
|
// Plain text markers: web_search_results ... web_search_results_end
|
|
111
119
|
cleaned = cleaned.replace(/web_search_results[\s\S]*?web_search_results_end/gi, '');
|
|
112
120
|
cleaned = cleaned.replace(/web_search_summary[\s\S]*?web_search_summary_end/gi, '');
|
|
113
|
-
// Markdown headings:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
// Markdown headings: chỉ áp dụng cho search mode (nguồn duy nhất bị inject
|
|
122
|
+
// dạng heading bởi proxy). Lookahead bám sát: dừng tại heading kế tiếp HOẶC
|
|
123
|
+
// 2 dòng trống liên tiếp (đoạn văn mới) — tránh nuốt phần còn lại của câu trả lời.
|
|
124
|
+
if (mode === 'search') {
|
|
125
|
+
cleaned = cleaned.replace(
|
|
126
|
+
/^#{1,3}\s+Web\s+Search\s+(Results|Summary)\b[^\n]*\n[\s\S]*?(?=^#{1,3}\s|\n\n\n|$)/gim,
|
|
127
|
+
''
|
|
128
|
+
);
|
|
129
|
+
}
|
|
118
130
|
return cleaned.trim();
|
|
119
131
|
}
|
|
120
132
|
|
|
@@ -213,7 +225,7 @@ export async function stream({
|
|
|
213
225
|
}
|
|
214
226
|
|
|
215
227
|
return {
|
|
216
|
-
text: cleanResponseText(fullText.trim()),
|
|
228
|
+
text: cleanResponseText(fullText.trim(), mode),
|
|
217
229
|
reasoning: reasoning.trim(),
|
|
218
230
|
finishReason: lastFinishReason,
|
|
219
231
|
};
|
|
@@ -321,6 +333,11 @@ async function streamOnce({
|
|
|
321
333
|
const { done, value } = await reader.read();
|
|
322
334
|
if (done) break;
|
|
323
335
|
buf += decoder.decode(value, { stream: true });
|
|
336
|
+
// Chuẩn hoá CRLF→LF: một số reverse proxy (Cloudflare, nginx config khác)
|
|
337
|
+
// gửi line endings \r\n. processLine() có trim() nên \r trailing tự rụng,
|
|
338
|
+
// nhưng nếu chunk biên giới rơi đúng giữa \r và \n thì vẫn ổn — đây là
|
|
339
|
+
// safety belt cho trường hợp \r đơn lẻ (rất hiếm nhưng có thấy thực tế).
|
|
340
|
+
buf = buf.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
324
341
|
let nl;
|
|
325
342
|
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
326
343
|
processLine(buf.slice(0, nl));
|
package/src/tools.js
CHANGED
|
@@ -217,11 +217,19 @@ function killBgTree(child) {
|
|
|
217
217
|
function cleanupBg() {
|
|
218
218
|
for (const p of bg.values()) killBgTree(p.child);
|
|
219
219
|
}
|
|
220
|
-
|
|
221
|
-
process.on('SIGTERM', () => {
|
|
220
|
+
function onSigterm() {
|
|
222
221
|
cleanupBg();
|
|
223
222
|
process.exit(143);
|
|
224
|
-
}
|
|
223
|
+
}
|
|
224
|
+
// Guard chống đăng ký kép: vitest hot-reload / dynamic import có thể re-evaluate
|
|
225
|
+
// module này → nhiều listener → MaxListenersExceededWarning + leak. Chỉ add khi
|
|
226
|
+
// chưa có handler chính xác này.
|
|
227
|
+
if (!process.listeners('exit').includes(cleanupBg)) {
|
|
228
|
+
process.on('exit', cleanupBg);
|
|
229
|
+
}
|
|
230
|
+
if (!process.listeners('SIGTERM').includes(onSigterm)) {
|
|
231
|
+
process.on('SIGTERM', onSigterm);
|
|
232
|
+
}
|
|
225
233
|
|
|
226
234
|
// Helper: throw nếu signal đã abort. Dùng ở đầu mỗi tool + giữa các vòng walk dài
|
|
227
235
|
// để tool fs (glob/grep) cũng phản ứng với Ctrl+C, không chỉ run_command.
|