@raolin2025/claude-code-node 2.8.20 → 2.8.21
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 +1 -1
- package/src/__tests__/compact-window.test.js +60 -0
- package/src/core/compact.js +48 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raolin2025/claude-code-node",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.21",
|
|
4
4
|
"description": "Node.js AI Code Agent CLI - Zero dependencies, pure JavaScript, security hardened, multi-channel notifications, Telegram & QQ Bot remote programming, rich media upload, multi-account management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/core/index.js",
|
|
@@ -320,3 +320,63 @@ test('trimToWindow:所有 system 均在开头,body 中无 system', () => {
|
|
|
320
320
|
const afterBody = roles.slice(firstBody + 1)
|
|
321
321
|
assert.ok(!afterBody.includes('system'), `trim 后 body 中不应有 system(实际 roles=${roles.join(',')})`)
|
|
322
322
|
})
|
|
323
|
+
|
|
324
|
+
test('条数折叠:保留当前进行中的任务链,只折叠已完成的早期历史', () => {
|
|
325
|
+
// 场景:正在工具循环中(最近 user 之后有完整工具链),早期有已完成任务
|
|
326
|
+
const msgs = [
|
|
327
|
+
{ role: 'system', content: 'SYS' },
|
|
328
|
+
// 早期已完成历史
|
|
329
|
+
{ role: 'user', content: '任务1' },
|
|
330
|
+
{ role: 'assistant', content: 'a1', toolCalls: [{ id: 't1', name: 'Bash', input: {} }] },
|
|
331
|
+
{ role: 'tool', tool_call_id: 't1', content: 'r1' },
|
|
332
|
+
{ role: 'assistant', content: '完成1' },
|
|
333
|
+
// 当前进行中的任务(工具链未完成)
|
|
334
|
+
{ role: 'user', content: '按计划实现第一阶段' },
|
|
335
|
+
{ role: 'assistant', content: '', toolCalls: [{ id: 'c1', name: 'Read', input: {} }] },
|
|
336
|
+
{ role: 'tool', tool_call_id: 'c1', content: '计划内容' },
|
|
337
|
+
{ role: 'assistant', content: '', toolCalls: [{ id: 'c2', name: 'Write', input: {} }] },
|
|
338
|
+
{ role: 'tool', tool_call_id: 'c2', content: '写入成功' },
|
|
339
|
+
]
|
|
340
|
+
const r = foldHistoryByCount(msgs, { maxMessages: 8, keepRecentTurns: 2 })
|
|
341
|
+
assert.equal(r.folded, true)
|
|
342
|
+
const all = JSON.stringify(r.messages)
|
|
343
|
+
// 当前任务(最近的 user 及其后的工具链)必须完整保留
|
|
344
|
+
assert.ok(all.includes('按计划实现第一阶段'), '当前任务 user 应保留')
|
|
345
|
+
assert.ok(all.includes('计划内容'), '当前任务的 Read 结果应保留')
|
|
346
|
+
assert.ok(all.includes('写入成功'), '当前任务的 Write 结果应保留')
|
|
347
|
+
// 早期已完成历史应被折叠
|
|
348
|
+
assert.ok(!all.includes('完成1'), '早期已完成历史应被折叠')
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
test('条数折叠:当前任务很大时按完整 user 轮次折叠,不截断工具链', () => {
|
|
352
|
+
const msgs = [{ role: 'system', content: 'SYS' }]
|
|
353
|
+
// 早期历史
|
|
354
|
+
for (let i = 0; i < 5; i++) {
|
|
355
|
+
msgs.push({ role: 'user', content: '旧任务' + i })
|
|
356
|
+
msgs.push({ role: 'assistant', content: '旧完成' + i })
|
|
357
|
+
}
|
|
358
|
+
// 当前大任务(多轮工具循环)
|
|
359
|
+
msgs.push({ role: 'user', content: '实现第一阶段' })
|
|
360
|
+
for (let i = 0; i < 15; i++) {
|
|
361
|
+
msgs.push({ role: 'assistant', content: '', toolCalls: [{ id: 'c' + i, name: 'Write', input: {} }] })
|
|
362
|
+
msgs.push({ role: 'tool', tool_call_id: 'c' + i, content: '写模块' + i })
|
|
363
|
+
}
|
|
364
|
+
const r = foldHistoryByCount(msgs, { maxMessages: 20, keepRecentTurns: 3 })
|
|
365
|
+
assert.equal(r.folded, true)
|
|
366
|
+
const all = JSON.stringify(r.messages)
|
|
367
|
+
// 当前任务 user 保留
|
|
368
|
+
assert.ok(all.includes('实现第一阶段'), '当前任务 user 应保留')
|
|
369
|
+
// 折叠后工具链连续(无中间截断)
|
|
370
|
+
const roles = r.messages.map(m => m.role).filter(x => x !== 'system')
|
|
371
|
+
let expectUser = true
|
|
372
|
+
for (const role of roles) {
|
|
373
|
+
if (role === 'user') { expectUser = false }
|
|
374
|
+
}
|
|
375
|
+
// 验证角色序列是 user,assistant,tool,assistant,tool... 的连续模式(无孤立 tool)
|
|
376
|
+
for (let i = 0; i < roles.length; i++) {
|
|
377
|
+
if (roles[i] === 'tool') {
|
|
378
|
+
// tool 之前必须是 assistant
|
|
379
|
+
assert.ok(i > 0 && roles[i - 1] === 'assistant', `第 ${i} 个 tool 前应为 assistant(实际 ${roles.join(',')})`)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
})
|
package/src/core/compact.js
CHANGED
|
@@ -198,9 +198,6 @@ export function foldHistoryByCount(messages, options = {}) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
// 分离 system 提示与普通消息
|
|
201
|
-
// 注意:把所有 system 都收集到 systemMsgs(不只是第一条)——多次折叠后 body 里
|
|
202
|
-
// 可能残留旧的摘要 system,若不全部隔离,折叠结果会把 system 挤到对话中间,
|
|
203
|
-
// 触发 llama.cpp "System message must be at the beginning" (500)。
|
|
204
201
|
const systemMsgs = []
|
|
205
202
|
const body = []
|
|
206
203
|
for (const m of messages) {
|
|
@@ -211,18 +208,55 @@ export function foldHistoryByCount(messages, options = {}) {
|
|
|
211
208
|
}
|
|
212
209
|
}
|
|
213
210
|
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
211
|
+
// 找到分界点。
|
|
212
|
+
// 核心原则:**绝不能破坏"当前正在进行的任务链"的完整性**。
|
|
213
|
+
// 小模型"工作到一半变傻"的根因,就是折叠把正在进行的任务(最近的 user 指令
|
|
214
|
+
// 及其后的 tool 调用链)折叠成模糊摘要,模型丢失了"当前任务状态"(在做什么、
|
|
215
|
+
// 做到哪、下一步干什么)而停止调用工具。
|
|
216
|
+
// 因此:
|
|
217
|
+
// 1. 优先只折叠【最近 user 指令之前】的早期历史——当前任务(最近 user 指令
|
|
218
|
+
// 之后所有消息,含进行中的工具调用链)完整保留,永不折叠。
|
|
219
|
+
// 2. 若当前任务本身消息仍过多(单个任务几十轮),再从当前任务内部按
|
|
220
|
+
// 【完整 user 轮次】为单位折叠(保留最近 keepRecentTurns 个 user 任务),
|
|
221
|
+
// 绝不从 tool/assistant 消息中间截断工具链。
|
|
222
|
+
let splitIndex = -1
|
|
223
|
+
// 找最近的 user 指令位置
|
|
224
|
+
let lastUserIdx = -1
|
|
220
225
|
for (let i = body.length - 1; i >= 0; i--) {
|
|
221
226
|
if (body[i].role === 'user') {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
lastUserIdx = i
|
|
228
|
+
break
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// 最近的 user 之后的消息数(当前任务的大小)
|
|
232
|
+
const currentTaskSize = lastUserIdx === -1 ? body.length : body.length - lastUserIdx
|
|
233
|
+
if (lastUserIdx > 0 && currentTaskSize <= maxMessages) {
|
|
234
|
+
// 当前任务本身未超限 → 折叠当前任务之前的历史(保留最近 user 指令起全部)
|
|
235
|
+
splitIndex = lastUserIdx
|
|
236
|
+
} else if (lastUserIdx >= 0) {
|
|
237
|
+
// 当前任务本身也超限 → 从当前任务内部按完整 user 轮次折叠
|
|
238
|
+
let turnCount = 0
|
|
239
|
+
splitIndex = lastUserIdx
|
|
240
|
+
for (let i = body.length - 1; i >= lastUserIdx; i--) {
|
|
241
|
+
if (body[i].role === 'user') {
|
|
242
|
+
turnCount++
|
|
243
|
+
if (turnCount >= keepRecentTurns) {
|
|
244
|
+
splitIndex = i
|
|
245
|
+
break
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
// 没有 user 消息(异常)→ 用 keepRecentTurns 兜底
|
|
251
|
+
let turnCount = 0
|
|
252
|
+
splitIndex = body.length
|
|
253
|
+
for (let i = body.length - 1; i >= 0; i--) {
|
|
254
|
+
if (body[i].role === 'user') {
|
|
255
|
+
turnCount++
|
|
256
|
+
if (turnCount >= keepRecentTurns) {
|
|
257
|
+
splitIndex = i
|
|
258
|
+
break
|
|
259
|
+
}
|
|
226
260
|
}
|
|
227
261
|
}
|
|
228
262
|
}
|
|
@@ -238,7 +272,7 @@ export function foldHistoryByCount(messages, options = {}) {
|
|
|
238
272
|
// 对折叠掉的历史生成摘要(保留 Main goal / 工具 / 关键结果)
|
|
239
273
|
const summary = generateSummary(earlyMessages)
|
|
240
274
|
|
|
241
|
-
// 构建折叠后的消息列表:system + (摘要 system) +
|
|
275
|
+
// 构建折叠后的消息列表:system + (摘要 system) + 当前任务完整对话
|
|
242
276
|
const folded = [...systemMsgs]
|
|
243
277
|
folded.push({
|
|
244
278
|
role: 'system',
|