@raolin2025/claude-code-node 2.7.2 → 2.7.4
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/notify-daemon.js +51 -20
- package/src/core/cli.js +26 -2
- package/src/tools/npm-publish.js +5 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raolin2025/claude-code-node",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.4",
|
|
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",
|
|
@@ -283,6 +283,50 @@ async function createTelegramListener(config) {
|
|
|
283
283
|
|
|
284
284
|
|
|
285
285
|
|
|
286
|
+
// ============================================================
|
|
287
|
+
// Telegram typing 心跳 — 让"正在输入"持续显示直到处理完成
|
|
288
|
+
// ============================================================
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 发送一次 Telegram typing 动作。
|
|
292
|
+
* 兼容代理 / 直连两种方式。
|
|
293
|
+
*/
|
|
294
|
+
async function tgSendTyping(config, chatId) {
|
|
295
|
+
if (!config.channels?.telegram?.token || !chatId) return
|
|
296
|
+
try {
|
|
297
|
+
const proxyAddr = config.channels.telegram.proxy || process.env.CC_NODE_CHANNEL_TELEGRAM_PROXY || ''
|
|
298
|
+
const apiBase = config.channels.telegram.apiBase || `https://api.telegram.org`
|
|
299
|
+
const url = `${apiBase}/bot${config.channels.telegram.token}/sendChatAction`
|
|
300
|
+
const body = JSON.stringify({ chat_id: chatId, action: 'typing' })
|
|
301
|
+
if (proxyAddr) {
|
|
302
|
+
const { fetchViaSocks5 } = await import('./tg-proxy.js')
|
|
303
|
+
await fetchViaSocks5(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body }, proxyAddr)
|
|
304
|
+
} else {
|
|
305
|
+
await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body })
|
|
306
|
+
}
|
|
307
|
+
} catch {}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* 启动 typing 心跳:Telegram 的 typing 提示约 5 秒后自动消失,
|
|
312
|
+
* 若处理耗时较长(cc-node 多轮思考 / 工具执行),需周期重发才能持续显示。
|
|
313
|
+
* 返回停止函数,处理结束后调用即可让"正在输入"消失。
|
|
314
|
+
*/
|
|
315
|
+
function tgStartTypingHeartbeat(config, chatId) {
|
|
316
|
+
if (!config.channels?.telegram?.token || !chatId) return () => {}
|
|
317
|
+
let stopped = false
|
|
318
|
+
const timer = setInterval(async () => {
|
|
319
|
+
if (stopped) return
|
|
320
|
+
await tgSendTyping(config, chatId)
|
|
321
|
+
}, 4000)
|
|
322
|
+
// 立即发送一次,让提示马上出现
|
|
323
|
+
tgSendTyping(config, chatId)
|
|
324
|
+
return () => {
|
|
325
|
+
stopped = true
|
|
326
|
+
clearInterval(timer)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
286
330
|
// ============================================================
|
|
287
331
|
// 统一消息处理器
|
|
288
332
|
// ============================================================
|
|
@@ -434,27 +478,10 @@ function createMessageHandler(config) {
|
|
|
434
478
|
// ============================================================
|
|
435
479
|
log(`[route] processing: "${text.slice(0, 50)}${text.length > 50 ? '...' : ''}"`)
|
|
436
480
|
|
|
437
|
-
// 发送"处理中"提示
|
|
481
|
+
// 发送"处理中"提示 — 启动 typing 心跳,让"正在输入"持续显示直到处理完成
|
|
482
|
+
let stopTyping = () => {}
|
|
438
483
|
if (isTelegram && config.channels.telegram?.token) {
|
|
439
|
-
|
|
440
|
-
const proxyAddr = config.channels.telegram.proxy || process.env.CC_NODE_CHANNEL_TELEGRAM_PROXY || ''
|
|
441
|
-
const apiBase = config.channels.telegram.apiBase || `https://api.telegram.org`
|
|
442
|
-
const url = `${apiBase}/bot${config.channels.telegram.token}/sendChatAction`
|
|
443
|
-
if (proxyAddr) {
|
|
444
|
-
const { fetchViaSocks5 } = await import('./tg-proxy.js')
|
|
445
|
-
await fetchViaSocks5(url, {
|
|
446
|
-
method: 'POST',
|
|
447
|
-
headers: { 'Content-Type': 'application/json' },
|
|
448
|
-
body: JSON.stringify({ chat_id: chatId, action: 'typing' }),
|
|
449
|
-
}, proxyAddr)
|
|
450
|
-
} else {
|
|
451
|
-
await fetch(url, {
|
|
452
|
-
method: 'POST',
|
|
453
|
-
headers: { 'Content-Type': 'application/json' },
|
|
454
|
-
body: JSON.stringify({ chat_id: chatId, action: 'typing' }),
|
|
455
|
-
})
|
|
456
|
-
}
|
|
457
|
-
} catch {}
|
|
484
|
+
stopTyping = tgStartTypingHeartbeat(config, chatId)
|
|
458
485
|
}
|
|
459
486
|
if (isQQBot) {
|
|
460
487
|
await sendToChannel(config.channels, 'qqbot', '🤖 收到,正在处理...')
|
|
@@ -462,6 +489,8 @@ function createMessageHandler(config) {
|
|
|
462
489
|
|
|
463
490
|
try {
|
|
464
491
|
const result = await routeMessage(text, config)
|
|
492
|
+
// 处理完成,停掉 typing 心跳 → "正在输入" 提示消失
|
|
493
|
+
stopTyping()
|
|
465
494
|
const reply = result || '(no response)'
|
|
466
495
|
|
|
467
496
|
if (isTelegram && config.channels.telegram?.token) {
|
|
@@ -494,6 +523,8 @@ function createMessageHandler(config) {
|
|
|
494
523
|
log(`[route] done (${reply.length} chars)`)
|
|
495
524
|
|
|
496
525
|
} catch (e) {
|
|
526
|
+
// 出错也停掉 typing 心跳,避免提示一直挂起
|
|
527
|
+
stopTyping()
|
|
497
528
|
log(`[route] error: ${e.message}`)
|
|
498
529
|
const errMsg = `❌ Error processing: ${e.message}`
|
|
499
530
|
if (isTelegram && config.channels.telegram?.token) {
|
package/src/core/cli.js
CHANGED
|
@@ -1008,16 +1008,38 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
|
|
|
1008
1008
|
// 让远程用户看到 AI 正在工作,而不是"以为没回应"。
|
|
1009
1009
|
// 采用节流 + 编辑同一消息的方式,避免刷屏和触发速率限制。
|
|
1010
1010
|
// ============================================================
|
|
1011
|
-
const tgThinking = { buffer: '', timer: null, target: null, lastMsgId: null, flushing: false }
|
|
1011
|
+
const tgThinking = { buffer: '', timer: null, target: null, lastMsgId: null, flushing: false, typingTimer: null }
|
|
1012
|
+
|
|
1013
|
+
// Telegram 的 typing 提示约 5 秒后自动消失。若 cc-node 处理耗时较长
|
|
1014
|
+
//(如执行工具、读取文件、多轮思考),单次 sendChatAction 撑不住整个周期,
|
|
1015
|
+
// 用户会看到"正在输入"消失,误以为已完成/卡死。
|
|
1016
|
+
// 因此用一个心跳定时器在整轮处理期间每隔几秒重发一次 typing 动作,
|
|
1017
|
+
// 让"正在输入"持续显示,直到本轮处理完全结束(tgThinkingEnd 清掉定时器)。
|
|
1018
|
+
const TG_TYPING_HEARTBEAT_MS = 4000
|
|
1019
|
+
|
|
1020
|
+
function tgStartTypingHeartbeat() {
|
|
1021
|
+
// 停止旧的,避免重复
|
|
1022
|
+
if (tgThinking.typingTimer) { clearInterval(tgThinking.typingTimer); tgThinking.typingTimer = null }
|
|
1023
|
+
if (!tgListener?.bot || !tgThinking.target) return
|
|
1024
|
+
tgThinking.typingTimer = setInterval(() => {
|
|
1025
|
+
if (!tgThinking.target) { tgStopTypingHeartbeat(); return }
|
|
1026
|
+
tgListener.bot.sendChatAction(tgThinking.target, 'typing').catch(() => {})
|
|
1027
|
+
}, TG_TYPING_HEARTBEAT_MS)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function tgStopTypingHeartbeat() {
|
|
1031
|
+
if (tgThinking.typingTimer) { clearInterval(tgThinking.typingTimer); tgThinking.typingTimer = null }
|
|
1032
|
+
}
|
|
1012
1033
|
|
|
1013
1034
|
function tgThinkingStart(target) {
|
|
1014
1035
|
tgThinking.target = target || null
|
|
1015
1036
|
tgThinking.buffer = ''
|
|
1016
1037
|
tgThinking.lastMsgId = null
|
|
1017
1038
|
tgThinking.flushing = false
|
|
1018
|
-
// 先发送 typing 动作,让 Telegram 立即显示"正在输入"
|
|
1039
|
+
// 先发送 typing 动作,让 Telegram 立即显示"正在输入",并启动心跳保持持续显示
|
|
1019
1040
|
if (tgListener?.bot && tgThinking.target) {
|
|
1020
1041
|
tgListener.bot.sendChatAction(tgThinking.target, 'typing').catch(() => {})
|
|
1042
|
+
tgStartTypingHeartbeat()
|
|
1021
1043
|
}
|
|
1022
1044
|
}
|
|
1023
1045
|
|
|
@@ -1060,6 +1082,8 @@ const systemPrompt = cliArgs.systemPrompt || DEFAULT_SYSTEM_PROMPT
|
|
|
1060
1082
|
|
|
1061
1083
|
function tgThinkingEnd() {
|
|
1062
1084
|
if (tgThinking.timer) { clearTimeout(tgThinking.timer); tgThinking.timer = null }
|
|
1085
|
+
// 处理结束,停掉 typing 心跳 → "正在输入" 提示消失
|
|
1086
|
+
tgStopTypingHeartbeat()
|
|
1063
1087
|
tgThinking.buffer = ''
|
|
1064
1088
|
tgThinking.lastMsgId = null
|
|
1065
1089
|
tgThinking.target = null
|
package/src/tools/npm-publish.js
CHANGED
|
@@ -57,7 +57,7 @@ const TOOL_PARAMETERS = {
|
|
|
57
57
|
},
|
|
58
58
|
squash: {
|
|
59
59
|
type: 'boolean',
|
|
60
|
-
description: 'publish
|
|
60
|
+
description: 'publish 时把本次改动合并为单个 release 提交(默认 true)。始终基于当前 HEAD 提交,不会回溯到旧 release,故不会产生分叉(默认 true)',
|
|
61
61
|
default: true
|
|
62
62
|
},
|
|
63
63
|
doGitPush: {
|
|
@@ -270,19 +270,10 @@ async function doPublish({ cwd, version, commitMessage, squash, doGitPush, doNpm
|
|
|
270
270
|
// 4. git 提交(合并或直接提交)
|
|
271
271
|
const msg = commitMessage || `release: v${pkg.version}`
|
|
272
272
|
try {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
// 最近的两个 release 提交中,取最早那个作为基点(即当前 release 之前的状态)
|
|
279
|
-
const releases = sh('git log --format="%h" --grep="^release:"', cwd).split('\n').filter(Boolean)
|
|
280
|
-
// releases[0] 是最近的 release(可能是本次或上一次);若 HEAD 就是 release 则取 [1]
|
|
281
|
-
const headIsRelease = sh('git log -1 --format="%s"', cwd).startsWith('release:')
|
|
282
|
-
baseCommit = headIsRelease ? (releases[1] || releases[0]) : (releases[0] || 'HEAD')
|
|
283
|
-
} catch { baseCommit = 'HEAD' }
|
|
284
|
-
sh(`git reset --soft ${baseCommit}`, cwd)
|
|
285
|
-
}
|
|
273
|
+
// 合并为单个 release 提交:直接基于当前 HEAD 提交本次产生的全部改动。
|
|
274
|
+
// 不再用 git reset --soft 回溯到旧 release(会吞掉 HEAD 之后已提交已推送的历史,
|
|
275
|
+
// 导致 release 提交父基点错误、与远程分叉、push 报 non-fast-forward,见 KNOWN_ISSUES.md 第 3 条)。
|
|
276
|
+
// 直接 git add -A && git commit 后,新提交父即当前 HEAD,天然不会分叉。
|
|
286
277
|
sh(`git add -A`, cwd)
|
|
287
278
|
sh(`git commit -m "${msg.replace(/"/g, '\\"')}"`, cwd)
|
|
288
279
|
steps.push(`git 提交: ${msg}`)
|