@raolin2025/claude-code-node 2.6.5 → 2.6.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/core/cli.js +52 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raolin2025/claude-code-node",
3
- "version": "2.6.5",
3
+ "version": "2.6.7",
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
@@ -592,6 +592,10 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
592
592
  let tgListener = null
593
593
  let tgChatId = null
594
594
  let tgReplyTarget = null
595
+ // 当前正在处理的请求来源('cli' | 'telegram')— 用于权限确认等按来源分支的逻辑
596
+ let currentSource = 'cli'
597
+ // 等待中的 Telegram 权限确认(resolve 回调)— 远程用户回复 y/n/a 时响应
598
+ let pendingConfirm = null
595
599
 
596
600
  // 处理输入行
597
601
  // source: 'cli' 来自终端输入, 'telegram' 来自 Telegram
@@ -599,6 +603,32 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
599
603
  const trimmed = input.trim()
600
604
  if (!trimmed) { showPrompt(); return }
601
605
 
606
+ // 记录当前请求来源,供 onConfirmTool 等按来源分支的逻辑使用
607
+ currentSource = source
608
+
609
+ // 关键:来自 Telegram 的消息,如果当前正在等待远程权限确认(pendingConfirm),
610
+ // 则把它当作确认回复(y/n/a)处理,而不是当作新的 REPL 输入。
611
+ if (source === 'telegram' && pendingConfirm) {
612
+ const confirm = pendingConfirm
613
+ pendingConfirm = null
614
+ const a = trimmed.toLowerCase()
615
+ if (a === 'a') {
616
+ try { engine.permissionChecker.allowAllForSession() } catch {}
617
+ confirm(true)
618
+ } else if (a === 'y') {
619
+ confirm(true)
620
+ } else if (a === 'n') {
621
+ confirm(false)
622
+ } else {
623
+ // 不是 y/n/a,忽略这次(不打断确认等待),但也可能是误发,继续等待
624
+ pendingConfirm = confirm
625
+ if (tgListener?.bot) {
626
+ await sendTelegram('⚠️ 请回复 y(允许一次)/ n(拒绝)/ a(本会话全部允许)', tgChatId || null).catch(() => {})
627
+ }
628
+ }
629
+ return
630
+ }
631
+
602
632
  if (trimmed.startsWith('/')) {
603
633
  const [cmd, ...rest] = trimmed.slice(1).split(' ')
604
634
  switch (cmd) {
@@ -885,6 +915,25 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
885
915
  if (engine.permissionChecker.sessionAllowAll) return true
886
916
 
887
917
  const snippet = JSON.stringify(input).slice(0, 120) || '(no params)'
918
+
919
+ // Telegram 远程模式:把权限确认推送到 Telegram,等待远程用户回复 y/n/a
920
+ if (currentSource === 'telegram' && tgListener?.bot) {
921
+ const promptText = `⚠️ 需要工具权限\n工具: ${toolName}\n输入: ${snippet}\n\n请回复:\ny = 允许一次\nn = 拒绝\na = 本会话全部允许`
922
+ await sendTelegram(promptText, null).catch(() => {})
923
+ return new Promise((resolve) => {
924
+ // 60 秒内未回复则自动拒绝,避免远程确认永久挂起阻塞对话
925
+ const timer = setTimeout(() => {
926
+ if (pendingConfirm === resolve) pendingConfirm = null
927
+ resolve(false)
928
+ }, 60000)
929
+ pendingConfirm = (val) => {
930
+ clearTimeout(timer)
931
+ resolve(val)
932
+ }
933
+ })
934
+ }
935
+
936
+ // 本地 CLI:用终端交互确认
888
937
  return new Promise((resolve) => {
889
938
  rl.question(`\n⚠️ Allow tool "${toolName}"?\n Input: ${snippet}\n (y/N/a) a=all session `, (answer) => {
890
939
  const a = answer.toLowerCase()
@@ -949,6 +998,9 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
949
998
  channels: config.get('channels') || {},
950
999
  defaultChannel: config.get('defaultChannel') || process.env.CC_NODE_CHANNEL_DEFAULT || null,
951
1000
  onMessage: async (msg) => {
1001
+ // 记录全局回复目标,供权限确认 / AI 回复镜像发送到 Telegram 使用
1002
+ if (msg.chatId) tgChatId = msg.chatId
1003
+ if (msg.replyTo) tgReplyTarget = msg.replyTo
952
1004
  // 来自外部的消息 → 转发到 REPL 引擎
953
1005
  const text = msg.text || ''
954
1006
  if (text && !text.startsWith('/')) {