@raolin2025/claude-code-node 2.8.23 → 2.8.24

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raolin2025/claude-code-node",
3
- "version": "2.8.23",
3
+ "version": "2.8.24",
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",
package/src/core/cli.js CHANGED
@@ -374,9 +374,9 @@ Options:
374
374
  -v, --verbose Verbose mode
375
375
  --no-stream Disable streaming
376
376
  --max-messages N Fold history when message count exceeds N (default: 0 = off)
377
- --small-model Enable small-model adaptation (tool-call enforcement, filler retry, intent guidance)
377
+ --small-model Reserved flag (no-op now tool engine uses plain mode like v2.8.11)
378
378
  --max-output-tokens N Override max single-response output tokens (default: computed from window size)
379
- --small-model-max-turns N Small-model tool-loop cap (default: 8, prevents infinite loops)
379
+ --small-model-max-turns N Reserved (no-op tool loop uses maxTurns)
380
380
  --with-notify Start built-in channel listener (Telegram)
381
381
  (replaces cc-notify daemon — no external script needed)
382
382
  -h, --help Show this help
@@ -15,15 +15,6 @@ import crypto from 'crypto'
15
15
  import { UserMessage, AssistantMessage, ToolCall, ToolResult, SessionState } from '../types/index.js'
16
16
  import { parseStream, parseNonStreamResponse } from './streaming.js'
17
17
  import { compactMessages, trimToWindow, foldHistoryByCount, trimToolResults } from './compact.js'
18
- import {
19
- isSmallModelEnabled,
20
- buildSmallModelSystemPrompt,
21
- isFillerResponse,
22
- buildCombinedGuidance,
23
- selectRelevantTools,
24
- RETRY_GUIDANCE,
25
- extractFrameworkAction,
26
- } from './small-model.js'
27
18
  import { CostTracker } from './cost-tracker.js'
28
19
  import { EnhancedPermissionChecker } from '../security/enhanced-permission.js'
29
20
  import { isLocalLlmServer, buildAuthHeaders } from '../utils/index.js'
@@ -97,8 +88,6 @@ export class QueryEngine {
97
88
  this.tokenBudget = this.config.tokenBudget || null
98
89
  // 最近一次 processMessage 是否已通过流式回调/直写把正文输出(供调用方避免重复打印 response)
99
90
  this.lastStreamed = false
100
- // 当前正在处理的用户输入(小模型模式用于按意图精简工具集)
101
- this.currentUserInput = ''
102
91
  }
103
92
 
104
93
  /**
@@ -114,7 +103,6 @@ export class QueryEngine {
114
103
  this.state.turnCount++
115
104
  this.lastStreamed = false // 本轮是否已流式输出正文
116
105
  this.abortController = new AbortController()
117
- this.currentUserInput = userInput // 记录当前用户指令(小模型模式按意图精简工具)
118
106
  const userMsg = new UserMessage(userInput, images)
119
107
  this.state.messages.push(userMsg)
120
108
 
@@ -226,65 +214,17 @@ export class QueryEngine {
226
214
  */
227
215
  async _runToolLoop(userMessage) {
228
216
  let finalResponse = ''
229
- const smallModel = isSmallModelEnabled(this.config)
230
- // 小模型模式:首轮注入意图引导(层 B),帮助模型锁定该用哪些工具
231
- let intentGuided = false
232
- // 敷衍重试次数(层 A):模型没调工具只回空话时,追加强引导重试
233
- let fillerRetries = 0
234
- const MAX_FILLER_RETRIES = 1
235
- // 工具循环轮数上限:
236
- // - 小模型模式:用更小的上限(默认 8),避免模型陷入"写一个又写一个"的无限循环
237
- // - 普通模式:用 config.maxTurns
238
- const toolLoopLimit = smallModel
239
- ? (this.config.smallModelMaxTurns || 8)
240
- : this.config.maxTurns
241
-
242
- for (let turn = 0; turn < toolLoopLimit; turn++) {
217
+
218
+ // 朴素工具循环(v2.8.11 风格):
219
+ // 实测结论——小模型在"朴素模式"(16 个工具全上、无引导注入、无代执行、
220
+ // 无工具精简)下才能正常工作。v2.8.13 后我加的意图引导注入、工具精简、
221
+ // 多步计划、框架代执行等"辅助机制"反而让模型乱套(不调用工具、瞎编内容)。
222
+ // 因此这里【不做任何干预】,回归最原始行为:构建请求 调 LLM →
223
+ // 有工具调用就执行 → 循环。
224
+ for (let turn = 0; turn < this.config.maxTurns; turn++) {
243
225
  // 发送前硬校验:工具结果可能已使上下文超窗,确保 ≤ 窗口(摘要优先 + 滑动窗口裁剪兜底)
244
226
  this._ensureFitWindow()
245
227
 
246
- // 小模型模式:首轮若识别到意图/多步计划,把引导【追加到首条 system 消息】。
247
- // 注意:不能 push 一条新的 system 消息到中间——llama.cpp/Jinja 严格要求
248
- // system 消息必须在对话开头,中间插 system 会报 "System message must be at the beginning" (500)。
249
- if (smallModel && !intentGuided && this.state.messages.length > 0) {
250
- const guidance = buildCombinedGuidance(userMessage.content, {
251
- enable: true,
252
- cwd: this.config.cwd,
253
- })
254
- if (guidance) {
255
- // 找到第一条 system 消息,把引导拼接进去(保持 system 全在开头)
256
- const sysIdx = this.state.messages.findIndex(m => m.role === 'system')
257
- if (sysIdx !== -1) {
258
- const sysMsg = this.state.messages[sysIdx]
259
- this.state.messages[sysIdx] = {
260
- ...sysMsg,
261
- content: (typeof sysMsg.content === 'string' ? sysMsg.content : '') + '\n\n' + guidance,
262
- }
263
- } else {
264
- // 没有 system 消息 → 作为 user 引导插入(跟随在最新 user 之后作为新 user)
265
- this.state.messages.push({ role: 'user', content: guidance })
266
- }
267
- if (this.config.verbose) console.error('[small-model] 已注入引导(并入首条 system):' + guidance.slice(0, 60) + '...')
268
- }
269
- intentGuided = true
270
- }
271
-
272
- // 小模型模式:接近工具循环上限时,注入"总结收尾"引导,防止模型无限循环
273
- if (smallModel && turn === toolLoopLimit - 1) {
274
- const stopGuidance = `[系统提示] 你已经完成了大部分工作。现在请【停止调用新工具】,把已经完成的内容整理成一段总结回复给用户。如果确实还有关键步骤未完成,只再调用一次工具完成它,然后立即总结。不要再继续无休止地调用工具。`
275
- const sysIdx = this.state.messages.findIndex(m => m.role === 'system')
276
- if (sysIdx !== -1) {
277
- const sysMsg = this.state.messages[sysIdx]
278
- this.state.messages[sysIdx] = {
279
- ...sysMsg,
280
- content: (typeof sysMsg.content === 'string' ? sysMsg.content : '') + '\n\n' + stopGuidance,
281
- }
282
- } else {
283
- this.state.messages.push({ role: 'user', content: stopGuidance })
284
- }
285
- if (this.config.verbose) console.error(`[small-model] 已到工具循环第 ${turn + 1} 轮(上限 ${toolLoopLimit}),注入收尾引导`)
286
- }
287
-
288
228
  const requestMessages = this._buildRequest(this.state.messages)
289
229
  const response = await this._callLLM(requestMessages, this.state.messages)
290
230
 
@@ -292,32 +232,8 @@ export class QueryEngine {
292
232
  throw new Error('操作已取消')
293
233
  }
294
234
 
295
- // 没有工具调用 → 潜在最终回复
235
+ // 没有工具调用 → 最终回复
296
236
  if (!response.toolCalls || response.toolCalls.length === 0) {
297
- // 小模型模式:检测敷衍输出(空话/太短/命中占位句)
298
- if (smallModel && isFillerResponse(response)) {
299
- // 第一次敷衍 → 追加强引导重试
300
- if (fillerRetries < MAX_FILLER_RETRIES) {
301
- fillerRetries++
302
- if (this.config.verbose) {
303
- console.error(`[small-model] 检测到敷衍输出(第 ${fillerRetries} 次),追加强引导重试`)
304
- }
305
- this.state.messages.push(new AssistantMessage(response.content, [], response.reasoningContent))
306
- this.state.messages.push({ role: 'user', content: RETRY_GUIDANCE })
307
- continue
308
- }
309
- // 重试后仍敷衍 → 框架代执行,且【直接把结果作为最终答案】返回,
310
- // 不再 continue 让模型总结——模型已证明它只会敷衍,继续循环只会无限重复。
311
- // 这样既完成任务,又不会陷入"代执行→敷衍→再代执行"的死循环。
312
- const execResult = await this._frameworkExecute(userMessage.content)
313
- if (execResult.done) {
314
- if (this.config.verbose) console.error(`[small-model] 框架代执行完成,直接返回结果`)
315
- // 框架已执行并把结果注入对话;直接把真实结果作为最终回复
316
- finalResponse = execResult.resultText
317
- break
318
- }
319
- }
320
-
321
237
  finalResponse = response.content
322
238
  this.state.messages.push(new AssistantMessage(response.content, [], response.reasoningContent))
323
239
  break
@@ -344,9 +260,8 @@ export class QueryEngine {
344
260
  }
345
261
  }
346
262
 
347
- // 工具循环达到上限但仍未收尾(模型一直调用工具)→ 强制收尾
348
- if (!finalResponse) {
349
- finalResponse = `[已达到工具循环上限 (${toolLoopLimit}),已停止进一步调用工具。请查看上方工具执行结果,确认任务完成情况。]`
263
+ if (!finalResponse && this.state.turnCount >= this.config.maxTurns) {
264
+ finalResponse = `[达到最大回合数限制 (${this.config.maxTurns}),停止响应]`
350
265
  }
351
266
 
352
267
  return {
@@ -388,12 +303,10 @@ export class QueryEngine {
388
303
  _buildRequest(messages) {
389
304
  const request = []
390
305
 
391
- // 系统提示(小模型模式:追加强制工具调用引导 + 当前工作目录)
306
+ // 系统提示(朴素模式:直接用基础 system prompt,不做 cwd/工具铁律等注入——
307
+ // 实测 v2.8.11 朴素模式能正常工作,注入反而干扰模型)
392
308
  if (this.config.systemPrompt) {
393
- const sysContent = isSmallModelEnabled(this.config)
394
- ? buildSmallModelSystemPrompt(this.config.systemPrompt, { cwd: this.config.cwd })
395
- : this.config.systemPrompt
396
- request.push({ role: 'system', content: sysContent })
309
+ request.push({ role: 'system', content: this.config.systemPrompt })
397
310
  }
398
311
 
399
312
  // 历史消息 — 转换为 OpenAI 兼容格式
@@ -512,47 +425,6 @@ export class QueryEngine {
512
425
  return results
513
426
  }
514
427
 
515
- /**
516
- * 框架代执行 — 模型敷衍调不起工具时,框架直接替它执行最合理的工具
517
- *
518
- * 背景:27B Q3 量化模型工具调用极弱,即使强引导也常只回"研究一下"而不调工具。
519
- * 此时框架【绕过模型】,根据指令推断该执行什么工具并直接运行,把真实结果
520
- * 注入对话,再让模型基于结果总结——"一句话调用小模型工作"才能成立。
521
- *
522
- * @param {string} userInput — 用户指令
523
- * @returns {Promise<{ done: boolean, summary: string }>}
524
- * done=true 表示框架已执行工具(结果已注入 state.messages)
525
- */
526
- async _frameworkExecute(userInput) {
527
- const action = extractFrameworkAction(userInput, { cwd: this.config.cwd })
528
- if (!action) return { done: false, summary: '', resultText: '' }
529
-
530
- // 找到对应的工具
531
- const tool = this.config.tools.find(t => t.name === action.tool)
532
- if (!tool) return { done: false, summary: '', resultText: '' }
533
-
534
- // 执行工具(直接调用 handler)
535
- try {
536
- const content = await tool.handler(action.input, { cwd: this.config.cwd, engine: this, readline: this.config.readline })
537
-
538
- const resultText = typeof content === 'string' ? content : JSON.stringify(content)
539
-
540
- // 注入框架代执行结果(作为 user 消息,避免 tool 消息无对应 assistant tool_call 报错)
541
- // 说明:框架代执行不是模型发起的工具调用,不能作为 tool 角色(会缺 assistant tool_call)。
542
- this.state.messages.push({
543
- role: 'user',
544
- content: `[框架代执行结果] 框架已替你执行工具 ${tool.name}(参数 ${JSON.stringify(action.input)}),结果如下:\n\n${resultText.slice(0, 8000)}`,
545
- })
546
-
547
- const summary = `框架已代执行 ${tool.name}(${JSON.stringify(action.input)})`
548
- if (this.config.verbose) console.error(`[small-model] 框架代执行 ${tool.name} 完成`)
549
- return { done: true, summary, resultText }
550
- } catch (err) {
551
- if (this.config.verbose) console.error(`[small-model] 框架代执行失败: ${err.message}`)
552
- return { done: false, summary: '', resultText: '' }
553
- }
554
- }
555
-
556
428
  async _callLLM(messages, contextMessages) {
557
429
  const apiKey = this.config.apiKey
558
430
  const apiBase = this.config.apiBase
@@ -580,16 +452,9 @@ export class QueryEngine {
580
452
  }
581
453
 
582
454
  // 构建工具定义
583
- // 小模型模式:按用户指令意图精简工具集,减少模型的选择负担(层 A)
584
- let effectiveTools = this.config.tools
585
- if (isSmallModelEnabled(this.config)) {
586
- const reduced = selectRelevantTools(this.config.tools, this.currentUserInput, { enable: true })
587
- effectiveTools = reduced
588
- if (this.config.verbose && reduced.length !== this.config.tools.length) {
589
- console.error(`[small-model] 按意图精简工具:${this.config.tools.length} → ${reduced.length}(${reduced.map(t => t.name).join(', ')})`)
590
- }
591
- }
592
- const tools = effectiveTools.map(t => ({
455
+ // 朴素模式(v2.8.11 风格):16 个工具全上,不做精简。
456
+ // 实测结论——小模型在工具全给时才能正常调用;按意图精简反而让它不会调用、瞎编。
457
+ const tools = this.config.tools.map(t => ({
593
458
  type: 'function',
594
459
  function: { name: t.name, description: t.description, parameters: t.parameters },
595
460
  }))