@noobdemon/noob-cli 1.7.6 → 1.7.7

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/agent.js +22 -8
  3. package/src/tui.js +11 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noobdemon/noob-cli",
3
- "version": "1.7.6",
3
+ "version": "1.7.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/agent.js CHANGED
@@ -123,14 +123,18 @@ function runtimeContext() {
123
123
  // Lược ngữ cảnh để không vượt context khi phiên dài. KHÔNG đụng vào history thật
124
124
  // (vẫn lưu/đầy đủ để resume) — chỉ thu gọn BẢN SAO dùng cho prompt.
125
125
  // Nếu history đã có summary (do summarizeHistory ghi vào _summary), dùng làm head.
126
- function compact(history, budget) {
126
+ // Exported cho test (scripts/test-compact.js) — không dùng ngoài file này.
127
+ export function compact(history, budget) {
127
128
  const len = (m) => (m.content || "").length + 24;
128
129
  let total = history.reduce((s, m) => s + len(m), 0);
129
130
  if (total <= budget) return history;
130
131
  const out = history.map((m) => ({ ...m }));
131
132
  // (1) Rút gọn các TOOL RESULT cũ trước (giữ 5 cái gần nhất) — đây là phần phình
132
133
  // nhất (đọc file lớn) và model đã xử lý xong rồi.
133
- const toolIdx = out.map((m, i) => (m.role === "tool" ? i : -1)).filter((i) => i >= 0);
134
+ // SKIP session_summary đó bộ nhớ dài hạn, không phải tool result cồng kềnh.
135
+ const toolIdx = out
136
+ .map((m, i) => (m.role === "tool" && m.name !== "session_summary" ? i : -1))
137
+ .filter((i) => i >= 0);
134
138
  for (const i of toolIdx.slice(0, Math.max(0, toolIdx.length - 5))) {
135
139
  if (total <= budget) break;
136
140
  const before = len(out[i]);
@@ -138,11 +142,17 @@ function compact(history, budget) {
138
142
  total -= before - len(out[i]);
139
143
  }
140
144
  if (total <= budget) return out;
141
- // (2) Vẫn dài → bỏ các lượt cũ nhất, giữ USER đầu tiên ( tả nhiệm vụ gốc) +
142
- // 12 message gần nhất.
145
+ // (2) Vẫn dài → bỏ các lượt cũ nhất, giữ MỌI session_summary (bộ nhớ dài hạn
146
+ // do maybeSummarize() chèn) + USER đầu tiên (nhiệm vụ gốc) + 12 message gần
147
+ // nhất. KHÔNG BỎ session_summary — đó là bộ nhớ phiên, mất nó là mất hết.
148
+ const summaryIdx = out
149
+ .map((m, i) => (m.role === "tool" && m.name === "session_summary" ? i : -1))
150
+ .filter((i) => i >= 0);
143
151
  const firstUser = out.findIndex((m) => m.role === "user");
144
- const head = firstUser >= 0 ? [out[firstUser]] : [];
145
- const tailStart = Math.max(firstUser + 1, out.length - 12);
152
+ const headIndices = new Set([...summaryIdx, ...(firstUser >= 0 ? [firstUser] : [])]);
153
+ const head = [...headIndices].sort((a, b) => a - b).map((i) => out[i]);
154
+ const maxHeadIdx = head.length ? Math.max(...headIndices) : -1;
155
+ const tailStart = Math.max(maxHeadIdx + 1, out.length - 12);
146
156
  const elided = { role: "tool", name: "context", content: "[… các lượt trước đã được lược bớt …]" };
147
157
  return [...head, elided, ...out.slice(tailStart)];
148
158
  }
@@ -214,7 +224,8 @@ ${transcript}`;
214
224
  // tool có thật (không phải từ lời model tự kể). Chống lỗi model "tưởng đã tạo
215
225
  // file" (chỉ kể trong văn xuôi, quên gọi write_file) rồi khăng khăng "file bị
216
226
  // revert". Dựng từ history ĐẦY ĐỦ (kể cả khi đã nén) để luôn đúng thực tế.
217
- function filesLedger(history) {
227
+ // Exported cho test (scripts/test-files-ledger.js).
228
+ export function filesLedger(history) {
218
229
  const touched = new Map(); // path -> "đã ghi" | "đã sửa"
219
230
  for (const m of history) {
220
231
  if (m.role !== "tool" || typeof m.content !== "string" || m.content.startsWith("ERROR")) continue;
@@ -265,7 +276,10 @@ function buildPrompt(history, extraToolsDoc) {
265
276
  parts.push("");
266
277
  }
267
278
  parts.push("=".repeat(60));
268
- parts.push("Continue. Emit a tool block to act, or reply in Markdown if done.");
279
+ // Recency bias: câu chốt cuối prompt nằm vị trí attention mạnh nhất. Nhắc
280
+ // model đối chiếu FILES CHANGED trước khi claim đã sửa file — chống ảo giác
281
+ // "đã tạo file" khi chưa gọi write_file/edit_file.
282
+ parts.push("Continue. Emit a tool block to act, or reply in Markdown if done. Before claiming any file was created/edited, verify it appears in the FILES CHANGED list above — if not, emit the tool call now.");
269
283
  return parts.join("\n");
270
284
  }
271
285
 
package/src/tui.js CHANGED
@@ -163,14 +163,20 @@ export function createTui({ onLine, onInterrupt, onEOF, onShiftTab, completer }
163
163
  function topRow() {
164
164
  if (liveOut) {
165
165
  // Khi đang stream prose mà busy, ghép meta (elapsed+token) vào cuối liveOut
166
- // để user vẫn thấy phiên đang sống không bị che status bar.
166
+ // CHỈ KHI liveOut đủ ngắn để cả dòng (liveOut + meta) chắc chắn fit trong
167
+ // 1 dòng terminal. Nếu liveOut quá dài → ưu tiên prose, bỏ meta lượt này
168
+ // (tránh terminal wrap làm dòng tạm kẹt lại trong prose vĩnh viễn — xem
169
+ // Note "token meter chèn vào prose" trong noob.md).
167
170
  if (busy && busyMeta) {
168
171
  const meta = c.dim(" · " + busyMeta);
169
- const budget = Math.max(0, cols() - visLen(meta));
170
- const head = liveOut.length > budget ? liveOut.slice(0, budget) : liveOut;
171
- return head + meta;
172
+ const metaLen = visLen(meta);
173
+ const liveLen = visLen(liveOut);
174
+ if (liveLen + metaLen <= cols()) {
175
+ return liveOut + meta;
176
+ }
177
+ // liveOut đã dài: hiện prose nguyên trạng, bỏ meta để tránh wrap.
172
178
  }
173
- return liveOut.slice(0, cols());
179
+ return liveOut.length > cols() ? liveOut.slice(0, cols()) : liveOut;
174
180
  }
175
181
  const spin = FRAMES[frame % FRAMES.length];
176
182
  // Khi busy, LUÔN hiện elapsed+tokens (busyMeta) cạnh statusText/busyLabel để