@raolin2025/claude-code-node 2.6.4 → 2.6.6
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/channel/tg-listener.js +16 -1
- package/src/channel/tg-proxy.js +4 -1
- package/src/core/cli.js +49 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raolin2025/claude-code-node",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.6",
|
|
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",
|
|
@@ -273,6 +273,8 @@ export class TelegramListener {
|
|
|
273
273
|
this._pollTimer = null
|
|
274
274
|
this._retryDelay = 1000
|
|
275
275
|
this.maxRetryDelay = 30000
|
|
276
|
+
this._lastPollError = null // 最近一次轮询错误消息(用于去抖刷屏)
|
|
277
|
+
this._lastPollErrorAt = 0 // 最近一次轮询错误时间戳
|
|
276
278
|
this.conversations = new ConversationState()
|
|
277
279
|
this._onMessage = null
|
|
278
280
|
this._handlers = {}
|
|
@@ -323,6 +325,9 @@ export class TelegramListener {
|
|
|
323
325
|
const res = await this._fetch(url, {
|
|
324
326
|
method: 'POST',
|
|
325
327
|
headers: { 'Content-Type': 'application/json' },
|
|
328
|
+
// 长轮询本身最多等 30s(Telegram API 参数),HTTP 层超时必须更长,
|
|
329
|
+
// 否则代理路径(fetchViaSocks5)30s 硬超时会误杀长轮询 → HTTP request timeout 刷屏。
|
|
330
|
+
timeout: 60000,
|
|
326
331
|
body: JSON.stringify({
|
|
327
332
|
offset: this.lastUpdateId + 1,
|
|
328
333
|
timeout: 30,
|
|
@@ -360,7 +365,17 @@ export class TelegramListener {
|
|
|
360
365
|
this._retryDelay = 1000
|
|
361
366
|
|
|
362
367
|
} catch (e) {
|
|
363
|
-
|
|
368
|
+
// 错误去抖:连续相同的错误(如长轮询超时)只在首次/状态变化时打印,
|
|
369
|
+
// 避免 AI 处理长任务时 getUpdates 空转超时不断刷屏。
|
|
370
|
+
const msg = e.message || 'unknown'
|
|
371
|
+
const now = Date.now()
|
|
372
|
+
const isSame = msg === this._lastPollError
|
|
373
|
+
const isRecent = (now - (this._lastPollErrorAt || 0)) < 60000
|
|
374
|
+
if (!isSame || !isRecent) {
|
|
375
|
+
log(`[TG] Poll error: ${msg} (retry in ${this._retryDelay}ms)`)
|
|
376
|
+
this._lastPollError = msg
|
|
377
|
+
this._lastPollErrorAt = now
|
|
378
|
+
}
|
|
364
379
|
await this._sleep(this._retryDelay)
|
|
365
380
|
this._retryDelay = Math.min(this._retryDelay * 2, this.maxRetryDelay)
|
|
366
381
|
}
|
package/src/channel/tg-proxy.js
CHANGED
|
@@ -271,10 +271,13 @@ export async function fetchViaSocks5(url, options = {}, proxyAddr) {
|
|
|
271
271
|
|
|
272
272
|
return new Promise((resolve, reject) => {
|
|
273
273
|
let responseData = ''
|
|
274
|
+
// 超时可配置(options.timeout,毫秒),默认 30s。
|
|
275
|
+
// Telegram getUpdates 是长轮询(最多等 30s),必须传更大的超时避免误判超时。
|
|
276
|
+
const timeoutMs = options.timeout || 30000
|
|
274
277
|
const timeout = setTimeout(() => {
|
|
275
278
|
socket.destroy()
|
|
276
279
|
reject(new Error('HTTP request timeout'))
|
|
277
|
-
},
|
|
280
|
+
}, timeoutMs)
|
|
278
281
|
|
|
279
282
|
socket.write(reqBuf)
|
|
280
283
|
socket.on('data', (chunk) => {
|
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()
|