@raolin2025/claude-code-node 2.6.14 → 2.6.16

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/core/cli.js +63 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raolin2025/claude-code-node",
3
- "version": "2.6.14",
3
+ "version": "2.6.16",
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
@@ -599,6 +599,8 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
599
599
  let currentSource = 'cli'
600
600
  // 等待中的 Telegram 权限确认(resolve 回调)— 远程用户回复 y/n/a 时响应
601
601
  let pendingConfirm = null
602
+ // 最近一次 /models 拉取的模型列表(供 /model <编号> 选择)
603
+ let modelList = []
602
604
 
603
605
  // 处理输入行
604
606
  // source: 'cli' 来自终端输入, 'telegram' 来自 Telegram
@@ -634,6 +636,15 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
634
636
  }
635
637
 
636
638
  if (trimmed.startsWith('/')) {
639
+ // 来自 Telegram 的命令:捕获 console 输出并回发到 Telegram
640
+ const isTgCmd = source === 'telegram' && tgListener?.bot
641
+ let tgOut = ''
642
+ const origLog = console.log
643
+ if (isTgCmd) {
644
+ tgOut = ''
645
+ console.log = (...args) => { tgOut += args.map(String).join(' ') + '\n'; origLog(...args) }
646
+ }
647
+ try {
637
648
  const [cmd, ...rest] = trimmed.slice(1).split(' ')
638
649
  switch (cmd) {
639
650
  case 'help':
@@ -646,7 +657,16 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
646
657
  }
647
658
  break
648
659
  case 'model':
649
- if (rest[0]) { engine.config.model = rest.join(' '); console.log(`Model → ${engine.config.model}`) }
660
+ // /model <名字或编号> 支持用 /models 列表里的编号切换
661
+ if (rest[0]) {
662
+ const num = parseInt(rest[0], 10)
663
+ if (!isNaN(num) && modelList.length > 0 && num >= 1 && num <= modelList.length) {
664
+ engine.config.model = modelList[num - 1]
665
+ } else {
666
+ engine.config.model = rest.join(' ')
667
+ }
668
+ console.log(`Model → ${engine.config.model}`)
669
+ }
650
670
  else console.log(`Model: ${engine.config.model}`)
651
671
  break
652
672
  case 'models': {
@@ -668,17 +688,23 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
668
688
  const data = await res.json()
669
689
  const models = data.data || []
670
690
  if (models.length === 0) { console.log('No models returned'); break }
691
+ // 记录模型列表,供 /model <编号> 选择
692
+ modelList = models.map(m => m.id || m)
671
693
  console.log(`\nAvailable models (${models.length}):`)
672
- models.forEach((m, i) => {
673
- const id = m.id || m
694
+ modelList.forEach((id, i) => {
674
695
  console.log(` ${(i + 1).toString().padStart(2)}. ${id}`)
675
696
  })
697
+ // 来自 Telegram:直接列出,不等待 CLI 交互(用 /model <名字或编号> 切换)
698
+ if (source === 'telegram') {
699
+ console.log('\nℹ️ 用 /model <名字或编号> 切换模型')
700
+ break
701
+ }
676
702
  console.log('\nType a number to select, or model name directly:')
677
703
  const answer = await new Promise(resolve => rl.question('> ', resolve))
678
704
  const num = parseInt(answer, 10)
679
705
  let selected
680
- if (!isNaN(num) && num >= 1 && num <= models.length) {
681
- selected = models[num - 1].id || models[num - 1]
706
+ if (!isNaN(num) && num >= 1 && num <= modelList.length) {
707
+ selected = modelList[num - 1]
682
708
  } else if (answer.trim()) {
683
709
  selected = answer.trim()
684
710
  }
@@ -851,6 +877,13 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
851
877
  default:
852
878
  console.log(`Unknown command: /${cmd}. Type /help for available commands.`)
853
879
  }
880
+ } finally {
881
+ // 恢复 console.log 并把命令输出回发到 Telegram
882
+ if (isTgCmd) {
883
+ console.log = origLog
884
+ if (tgOut.trim()) await sendTelegram(tgOut.trim(), tgChatId || null)
885
+ }
886
+ }
854
887
  showPrompt()
855
888
  return
856
889
  }
@@ -933,17 +966,18 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
933
966
  }
934
967
 
935
968
  // ============================================================
936
- // 思维链流式推送 → Telegram
937
- // 把引擎 onDelta 产出的 reasoning(思考过程)实时推送到 Telegram,
969
+ // 流式推送 → Telegram
970
+ // 把引擎 onDelta 产出的生成内容(text / reasoning)实时推送到 Telegram,
938
971
  // 让远程用户看到 AI 正在工作,而不是"以为没回应"。
939
972
  // 采用节流 + 编辑同一消息的方式,避免刷屏和触发速率限制。
940
973
  // ============================================================
941
- const tgThinking = { buffer: '', timer: null, target: null, lastMsgId: null }
974
+ const tgThinking = { buffer: '', timer: null, target: null, lastMsgId: null, flushing: false }
942
975
 
943
976
  function tgThinkingStart(target) {
944
977
  tgThinking.target = target || null
945
978
  tgThinking.buffer = ''
946
979
  tgThinking.lastMsgId = null
980
+ tgThinking.flushing = false
947
981
  // 先发送 typing 动作,让 Telegram 立即显示"正在输入"
948
982
  if (tgListener?.bot && tgThinking.target) {
949
983
  tgListener.bot.sendChatAction(tgThinking.target, 'typing').catch(() => {})
@@ -955,14 +989,17 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
955
989
  tgThinking.buffer += text
956
990
  // 只保留最近 8000 字符,避免无限增长
957
991
  if (tgThinking.buffer.length > 8000) tgThinking.buffer = tgThinking.buffer.slice(-8000)
958
- // 节流:2 秒内最多刷新一次
959
992
  if (tgThinking.timer) clearTimeout(tgThinking.timer)
960
- tgThinking.timer = setTimeout(() => tgThinkingFlush(), 2000)
993
+ // 首帧快速发送(500ms 聚合首段),之后节流编辑(1.5s)
994
+ const delay = tgThinking.lastMsgId ? 1500 : 500
995
+ tgThinking.timer = setTimeout(() => tgThinkingFlush(), delay)
961
996
  }
962
997
 
963
998
  async function tgThinkingFlush() {
999
+ if (tgThinking.flushing) return // 防止并发
964
1000
  if (tgThinking.timer) { clearTimeout(tgThinking.timer); tgThinking.timer = null }
965
1001
  if (!tgListener?.bot || !tgThinking.target || !tgThinking.buffer) return
1002
+ tgThinking.flushing = true
966
1003
  const target = tgThinking.target
967
1004
  const body = `🧠 思考中…\n\n${tgThinking.buffer.slice(-3500)}`
968
1005
  try {
@@ -979,6 +1016,8 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
979
1016
  const res = await tgListener.bot.sendMessage(target, body, { parseMode: 'HTML' })
980
1017
  tgThinking.lastMsgId = res?.message_id || null
981
1018
  } catch {}
1019
+ } finally {
1020
+ tgThinking.flushing = false
982
1021
  }
983
1022
  }
984
1023
 
@@ -987,6 +1026,7 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
987
1026
  tgThinking.buffer = ''
988
1027
  tgThinking.lastMsgId = null
989
1028
  tgThinking.target = null
1029
+ tgThinking.flushing = false
990
1030
  }
991
1031
 
992
1032
  // REPL 主循环 — 由 readline 原生处理回显、退格、行回绕与 Enter 提交
@@ -1061,10 +1101,10 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
1061
1101
  tgReplyTarget = msg.replyTo || null
1062
1102
  const text = msg.text || msg.callbackData || ''
1063
1103
  if (!text) return
1064
- // 命令由 listener 内部处理,普通消息转给引擎
1065
- if (!text.startsWith('/')) {
1066
- await processInputLine(text, 'telegram', msg.chatId)
1067
- }
1104
+ // 普通消息和 / 命令都转给引擎(/命令由 processInputLine 处理)
1105
+ // 注意:tg-listener 已内部处理 /ping /status /run 等自己的命令,
1106
+ // 只有未识别的 / 命令(如 /models /stop /resume)才会到达这里。
1107
+ await processInputLine(text, 'telegram', msg.chatId)
1068
1108
  }).catch(e => console.error(`[TG] listener error: ${e.message}`))
1069
1109
  console.log(`✅ Telegram channel ready (bot ${tgToken.slice(0, 12)}...)`)
1070
1110
  } catch (e) {
@@ -1087,9 +1127,9 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
1087
1127
  // 记录全局回复目标,供权限确认 / AI 回复镜像发送到 Telegram 使用
1088
1128
  if (msg.chatId) tgChatId = msg.chatId
1089
1129
  if (msg.replyTo) tgReplyTarget = msg.replyTo
1090
- // 来自外部的消息 → 转发到 REPL 引擎
1130
+ // 来自外部的消息 → 转发到 REPL 引擎(含 / 命令,由 processInputLine 处理)
1091
1131
  const text = msg.text || ''
1092
- if (text && !text.startsWith('/')) {
1132
+ if (text) {
1093
1133
  await processInputLine(text, msg.channel || 'external', msg.chatId)
1094
1134
  }
1095
1135
  },
@@ -1101,20 +1141,17 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
1101
1141
  }
1102
1142
 
1103
1143
  // ============================================================
1104
- // 引擎流式回调 — 保持 CLI 实时输出,并把思维链推送到 Telegram
1144
+ // 引擎流式回调 — 保持 CLI 实时输出,并把生成内容推送到 Telegram
1105
1145
  // onDelta 会在流式生成时被调用({type:'text'|'reasoning', text})。
1146
+ // 注意:DeepSeek 模型 thinking 被禁用时只有 text 事件、没有 reasoning,
1147
+ // 因此 text 也必须推送到 Telegram,否则远程看不到任何进度。
1106
1148
  // 设置 onDelta 后引擎不再直接写终端,因此这里需手动维持终端输出。
1107
1149
  // ============================================================
1108
1150
  engine.config.onDelta = ({ type, text }) => {
1109
- if (type === 'text') {
1110
- // 保持 CLI 实时输出(与未设置 onDelta 时行为一致)
1111
- process.stdout.write(text)
1112
- } else if (type === 'reasoning') {
1113
- // CLI 也显示思维链
1114
- process.stdout.write(text)
1115
- // 实时推送到 Telegram(节流)
1116
- tgThinkingPush(text)
1117
- }
1151
+ // 保持 CLI 实时输出
1152
+ process.stdout.write(text)
1153
+ // 无论 text 还是 reasoning,都实时推送到 Telegram(节流)
1154
+ tgThinkingPush(text)
1118
1155
  }
1119
1156
 
1120
1157
  console.log(buildBanner({ model, permissionMode, session, maxTokens: tokenBudget.maxTokens }))