telegram-claude-mcp 2.0.2 → 2.0.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/bin/daemon-ctl.js +4 -3
- package/bin/daemon.js +0 -0
- package/bin/proxy.js +0 -0
- package/bin/setup.js +231 -156
- package/hooks-v2/hooks-config-example.json +28 -0
- package/hooks-v2/progress-post-tool.sh +61 -0
- package/hooks-v2/progress-pre-tool.sh +53 -0
- package/hooks-v2/progress-stop.sh +34 -0
- package/package.json +1 -1
- package/src/daemon/index.ts +48 -1
- package/src/daemon/telegram-multi.ts +33 -0
package/bin/daemon-ctl.js
CHANGED
|
@@ -76,12 +76,13 @@ async function start() {
|
|
|
76
76
|
|
|
77
77
|
// Determine which script to run
|
|
78
78
|
let script = daemonScript;
|
|
79
|
-
let
|
|
79
|
+
let args = [script];
|
|
80
80
|
|
|
81
81
|
if (!existsSync(daemonScript)) {
|
|
82
82
|
if (existsSync(daemonSrc)) {
|
|
83
83
|
script = daemonSrc;
|
|
84
|
-
|
|
84
|
+
// Use node with tsx for TypeScript support
|
|
85
|
+
args = ['--import', 'tsx', script];
|
|
85
86
|
} else {
|
|
86
87
|
console.error('Error: Daemon script not found. Run `npm run build` first.');
|
|
87
88
|
process.exit(1);
|
|
@@ -90,7 +91,7 @@ async function start() {
|
|
|
90
91
|
|
|
91
92
|
console.log('Starting telegram-claude-daemon...');
|
|
92
93
|
|
|
93
|
-
const child = spawn(
|
|
94
|
+
const child = spawn('node', args, {
|
|
94
95
|
detached: true,
|
|
95
96
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
96
97
|
env: process.env,
|
package/bin/daemon.js
CHANGED
|
File without changes
|
package/bin/proxy.js
CHANGED
|
File without changes
|
package/bin/setup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Setup script for telegram-claude-mcp hooks integration
|
|
4
|
+
* Setup script for telegram-claude-mcp hooks integration (v2 - Daemon Architecture)
|
|
5
5
|
*
|
|
6
6
|
* Usage: npx telegram-claude-mcp setup
|
|
7
7
|
*
|
|
@@ -22,198 +22,101 @@ const CLAUDE_DIR = join(homedir(), '.claude');
|
|
|
22
22
|
const HOOKS_DIR = join(CLAUDE_DIR, 'hooks');
|
|
23
23
|
const SETTINGS_FILE = join(CLAUDE_DIR, 'settings.json');
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
# Claude Code Permission Hook - forwards permission requests to Telegram
|
|
29
|
-
# Set SESSION_NAME env var to target a specific session (for multi-session setups)
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
33
|
-
|
|
34
|
-
find_session() {
|
|
35
|
-
if [ -n "$SESSION_NAME" ]; then
|
|
36
|
-
local specific_file="$SESSION_DIR/\${SESSION_NAME}.info"
|
|
37
|
-
if [ -f "$specific_file" ]; then
|
|
38
|
-
local pid
|
|
39
|
-
pid=$(jq -r '.pid // empty' "$specific_file" 2>/dev/null)
|
|
40
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
41
|
-
echo "$specific_file"
|
|
42
|
-
return
|
|
43
|
-
fi
|
|
44
|
-
fi
|
|
45
|
-
return
|
|
46
|
-
fi
|
|
25
|
+
// Default daemon configuration
|
|
26
|
+
const DAEMON_PORT = 3333;
|
|
27
|
+
const DAEMON_HOST = 'localhost';
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
[ -d "$SESSION_DIR" ] || return
|
|
51
|
-
|
|
52
|
-
for info_file in "$SESSION_DIR"/*.info; do
|
|
53
|
-
[ -e "$info_file" ] || continue
|
|
54
|
-
local pid
|
|
55
|
-
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
56
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
57
|
-
local file_time
|
|
58
|
-
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
59
|
-
if [ "$file_time" -gt "$latest_time" ]; then
|
|
60
|
-
latest_time=$file_time
|
|
61
|
-
latest_file=$info_file
|
|
62
|
-
fi
|
|
63
|
-
fi
|
|
64
|
-
done
|
|
65
|
-
echo "$latest_file"
|
|
66
|
-
}
|
|
29
|
+
// Hook scripts content - v2 daemon architecture
|
|
30
|
+
// These hooks communicate with a single daemon process via HTTP
|
|
67
31
|
|
|
68
|
-
|
|
69
|
-
|
|
32
|
+
const PERMISSION_HOOK = `#!/bin/bash
|
|
33
|
+
# Permission hook for telegram-claude-daemon (v2)
|
|
34
|
+
# Sends permission requests to the daemon for Telegram approval
|
|
35
|
+
|
|
36
|
+
DAEMON_PORT="\${TELEGRAM_CLAUDE_PORT:-${DAEMON_PORT}}"
|
|
37
|
+
DAEMON_HOST="\${TELEGRAM_CLAUDE_HOST:-${DAEMON_HOST}}"
|
|
38
|
+
HOOK_URL="http://\${DAEMON_HOST}:\${DAEMON_PORT}/permission"
|
|
70
39
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
40
|
+
# Auto-detect project name from current working directory
|
|
41
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
42
|
+
BASE_SESSION="\${SESSION_NAME:-default}"
|
|
43
|
+
FULL_SESSION_NAME="\${BASE_SESSION}:\${PROJECT_NAME}"
|
|
74
44
|
|
|
75
45
|
INPUT=$(cat)
|
|
76
|
-
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
77
|
-
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
|
|
78
46
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
47
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .name // empty')
|
|
48
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // .toolInput // .input // .arguments // {}')
|
|
49
|
+
|
|
50
|
+
[ -z "$TOOL_NAME" ] && exit 0
|
|
83
51
|
|
|
84
52
|
PAYLOAD=$(jq -n \\
|
|
85
53
|
--arg tool_name "$TOOL_NAME" \\
|
|
86
54
|
--argjson tool_input "$TOOL_INPUT" \\
|
|
87
|
-
|
|
55
|
+
--arg session_name "$FULL_SESSION_NAME" \\
|
|
56
|
+
'{tool_name: $tool_name, tool_input: $tool_input, session_name: $session_name}')
|
|
88
57
|
|
|
89
58
|
RESPONSE=$(curl -s -X POST "$HOOK_URL" \\
|
|
90
59
|
-H "Content-Type: application/json" \\
|
|
60
|
+
-H "X-Session-Name: $FULL_SESSION_NAME" \\
|
|
91
61
|
-d "$PAYLOAD" \\
|
|
92
|
-
--max-time 600)
|
|
62
|
+
--max-time 600 2>/dev/null)
|
|
93
63
|
|
|
94
|
-
[ $? -eq 0 ] && echo "$RESPONSE"
|
|
64
|
+
[ $? -eq 0 ] && [ -n "$RESPONSE" ] && echo "$RESPONSE"
|
|
95
65
|
`;
|
|
96
66
|
|
|
97
67
|
const STOP_HOOK = `#!/bin/bash
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# Set SESSION_NAME env var to target a specific session (for multi-session setups)
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
104
|
-
|
|
105
|
-
find_session() {
|
|
106
|
-
if [ -n "$SESSION_NAME" ]; then
|
|
107
|
-
local specific_file="$SESSION_DIR/\${SESSION_NAME}.info"
|
|
108
|
-
if [ -f "$specific_file" ]; then
|
|
109
|
-
local pid
|
|
110
|
-
pid=$(jq -r '.pid // empty' "$specific_file" 2>/dev/null)
|
|
111
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
112
|
-
echo "$specific_file"
|
|
113
|
-
return
|
|
114
|
-
fi
|
|
115
|
-
fi
|
|
116
|
-
return
|
|
117
|
-
fi
|
|
68
|
+
# Stop hook for telegram-claude-daemon (v2)
|
|
69
|
+
# Sends stop notification and waits for user response
|
|
118
70
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
128
|
-
local file_time
|
|
129
|
-
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
130
|
-
if [ "$file_time" -gt "$latest_time" ]; then
|
|
131
|
-
latest_time=$file_time
|
|
132
|
-
latest_file=$info_file
|
|
133
|
-
fi
|
|
134
|
-
fi
|
|
135
|
-
done
|
|
136
|
-
echo "$latest_file"
|
|
137
|
-
}
|
|
71
|
+
DAEMON_PORT="\${TELEGRAM_CLAUDE_PORT:-${DAEMON_PORT}}"
|
|
72
|
+
DAEMON_HOST="\${TELEGRAM_CLAUDE_HOST:-${DAEMON_HOST}}"
|
|
73
|
+
HOOK_URL="http://\${DAEMON_HOST}:\${DAEMON_PORT}/stop"
|
|
74
|
+
|
|
75
|
+
# Auto-detect project name from current working directory
|
|
76
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
77
|
+
BASE_SESSION="\${SESSION_NAME:-default}"
|
|
78
|
+
FULL_SESSION_NAME="\${BASE_SESSION}:\${PROJECT_NAME}"
|
|
138
79
|
|
|
139
80
|
INPUT=$(cat)
|
|
140
81
|
|
|
82
|
+
# Prevent recursion
|
|
141
83
|
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
|
|
142
84
|
[ "$STOP_HOOK_ACTIVE" = "true" ] && exit 0
|
|
143
85
|
|
|
144
|
-
INFO_FILE=$(find_session)
|
|
145
|
-
[ -z "$INFO_FILE" ] || [ ! -f "$INFO_FILE" ] && exit 0
|
|
146
|
-
|
|
147
|
-
HOOK_PORT=$(jq -r '.port' "$INFO_FILE")
|
|
148
|
-
HOOK_HOST=$(jq -r '.host // "localhost"' "$INFO_FILE")
|
|
149
|
-
HOOK_URL="http://\${HOOK_HOST}:\${HOOK_PORT}/stop"
|
|
150
|
-
|
|
151
86
|
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
|
152
87
|
|
|
153
88
|
PAYLOAD=$(jq -n \\
|
|
154
89
|
--arg transcript_path "$TRANSCRIPT_PATH" \\
|
|
155
|
-
|
|
90
|
+
--arg session_name "$FULL_SESSION_NAME" \\
|
|
91
|
+
'{transcript_path: $transcript_path, session_name: $session_name}')
|
|
156
92
|
|
|
157
93
|
RESPONSE=$(curl -s -X POST "$HOOK_URL" \\
|
|
158
94
|
-H "Content-Type: application/json" \\
|
|
95
|
+
-H "X-Session-Name: $FULL_SESSION_NAME" \\
|
|
159
96
|
-d "$PAYLOAD" \\
|
|
160
|
-
--max-time 300)
|
|
97
|
+
--max-time 300 2>/dev/null)
|
|
161
98
|
|
|
162
|
-
if [ $? -eq 0 ]; then
|
|
99
|
+
if [ $? -eq 0 ] && [ -n "$RESPONSE" ]; then
|
|
163
100
|
DECISION=$(echo "$RESPONSE" | jq -r '.decision // empty')
|
|
164
|
-
|
|
165
|
-
|
|
101
|
+
if [ "$DECISION" = "block" ]; then
|
|
102
|
+
echo "$RESPONSE"
|
|
103
|
+
exit 2
|
|
104
|
+
fi
|
|
166
105
|
fi
|
|
167
106
|
`;
|
|
168
107
|
|
|
169
108
|
const NOTIFY_HOOK = `#!/bin/bash
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
# Set SESSION_NAME env var to target a specific session (for multi-session setups)
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
SESSION_DIR="/tmp/telegram-claude-sessions"
|
|
176
|
-
|
|
177
|
-
find_session() {
|
|
178
|
-
if [ -n "$SESSION_NAME" ]; then
|
|
179
|
-
local specific_file="$SESSION_DIR/\${SESSION_NAME}.info"
|
|
180
|
-
if [ -f "$specific_file" ]; then
|
|
181
|
-
local pid
|
|
182
|
-
pid=$(jq -r '.pid // empty' "$specific_file" 2>/dev/null)
|
|
183
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
184
|
-
echo "$specific_file"
|
|
185
|
-
return
|
|
186
|
-
fi
|
|
187
|
-
fi
|
|
188
|
-
return
|
|
189
|
-
fi
|
|
190
|
-
|
|
191
|
-
local latest_file=""
|
|
192
|
-
local latest_time=0
|
|
193
|
-
[ -d "$SESSION_DIR" ] || return
|
|
194
|
-
|
|
195
|
-
for info_file in "$SESSION_DIR"/*.info; do
|
|
196
|
-
[ -e "$info_file" ] || continue
|
|
197
|
-
local pid
|
|
198
|
-
pid=$(jq -r '.pid // empty' "$info_file" 2>/dev/null)
|
|
199
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
200
|
-
local file_time
|
|
201
|
-
file_time=$(stat -f %m "$info_file" 2>/dev/null || stat -c %Y "$info_file" 2>/dev/null)
|
|
202
|
-
if [ "$file_time" -gt "$latest_time" ]; then
|
|
203
|
-
latest_time=$file_time
|
|
204
|
-
latest_file=$info_file
|
|
205
|
-
fi
|
|
206
|
-
fi
|
|
207
|
-
done
|
|
208
|
-
echo "$latest_file"
|
|
209
|
-
}
|
|
109
|
+
# Notification hook for telegram-claude-daemon (v2)
|
|
110
|
+
# Sends notifications to the daemon (fire-and-forget)
|
|
210
111
|
|
|
211
|
-
|
|
212
|
-
|
|
112
|
+
DAEMON_PORT="\${TELEGRAM_CLAUDE_PORT:-${DAEMON_PORT}}"
|
|
113
|
+
DAEMON_HOST="\${TELEGRAM_CLAUDE_HOST:-${DAEMON_HOST}}"
|
|
114
|
+
HOOK_URL="http://\${DAEMON_HOST}:\${DAEMON_PORT}/notify"
|
|
213
115
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
116
|
+
# Auto-detect project name from current working directory
|
|
117
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
118
|
+
BASE_SESSION="\${SESSION_NAME:-default}"
|
|
119
|
+
FULL_SESSION_NAME="\${BASE_SESSION}:\${PROJECT_NAME}"
|
|
217
120
|
|
|
218
121
|
INPUT=$(cat)
|
|
219
122
|
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Notification from Claude"')
|
|
@@ -221,12 +124,137 @@ MESSAGE=$(echo "$INPUT" | jq -r '.message // "Notification from Claude"')
|
|
|
221
124
|
PAYLOAD=$(jq -n \\
|
|
222
125
|
--arg type "notification" \\
|
|
223
126
|
--arg message "$MESSAGE" \\
|
|
224
|
-
|
|
127
|
+
--arg session_name "$FULL_SESSION_NAME" \\
|
|
128
|
+
'{type: $type, message: $message, session_name: $session_name}')
|
|
225
129
|
|
|
226
130
|
curl -s -X POST "$HOOK_URL" \\
|
|
227
131
|
-H "Content-Type: application/json" \\
|
|
132
|
+
-H "X-Session-Name: $FULL_SESSION_NAME" \\
|
|
133
|
+
-d "$PAYLOAD" \\
|
|
134
|
+
--max-time 10 >/dev/null 2>&1
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
// Progress tracking hooks - fire-and-forget for real-time progress updates
|
|
138
|
+
|
|
139
|
+
const PROGRESS_PRE_TOOL_HOOK = `#!/bin/bash
|
|
140
|
+
# PreToolUse progress hook for telegram-claude-daemon
|
|
141
|
+
# Sends tool start events to update progress display (fire-and-forget)
|
|
142
|
+
|
|
143
|
+
DAEMON_PORT="\${TELEGRAM_CLAUDE_PORT:-${DAEMON_PORT}}"
|
|
144
|
+
DAEMON_HOST="\${TELEGRAM_CLAUDE_HOST:-${DAEMON_HOST}}"
|
|
145
|
+
HOOK_URL="http://\${DAEMON_HOST}:\${DAEMON_PORT}/progress"
|
|
146
|
+
|
|
147
|
+
# Auto-detect project name from current working directory
|
|
148
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
149
|
+
BASE_SESSION="\${SESSION_NAME:-default}"
|
|
150
|
+
FULL_SESSION_NAME="\${BASE_SESSION}:\${PROJECT_NAME}"
|
|
151
|
+
|
|
152
|
+
INPUT=$(cat)
|
|
153
|
+
|
|
154
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .name // empty')
|
|
155
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // .toolInput // .input // .arguments // {}')
|
|
156
|
+
|
|
157
|
+
[ -z "$TOOL_NAME" ] && exit 0
|
|
158
|
+
|
|
159
|
+
# Skip progress for Telegram tools (would be recursive)
|
|
160
|
+
case "$TOOL_NAME" in
|
|
161
|
+
mcp__telegram__*|send_message|continue_chat|notify_user|end_chat)
|
|
162
|
+
exit 0
|
|
163
|
+
;;
|
|
164
|
+
esac
|
|
165
|
+
|
|
166
|
+
PAYLOAD=$(jq -n \\
|
|
167
|
+
--arg type "pre_tool" \\
|
|
168
|
+
--arg session_name "$FULL_SESSION_NAME" \\
|
|
169
|
+
--arg tool_name "$TOOL_NAME" \\
|
|
170
|
+
--argjson tool_input "$TOOL_INPUT" \\
|
|
171
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \\
|
|
172
|
+
'{type: $type, session_name: $session_name, tool_name: $tool_name, tool_input: $tool_input, timestamp: $timestamp}')
|
|
173
|
+
|
|
174
|
+
# Fire and forget
|
|
175
|
+
(curl -s -X POST "$HOOK_URL" \\
|
|
176
|
+
-H "Content-Type: application/json" \\
|
|
177
|
+
-H "X-Session-Name: $FULL_SESSION_NAME" \\
|
|
178
|
+
-d "$PAYLOAD" \\
|
|
179
|
+
--max-time 2 2>/dev/null) &
|
|
180
|
+
|
|
181
|
+
exit 0
|
|
182
|
+
`;
|
|
183
|
+
|
|
184
|
+
const PROGRESS_POST_TOOL_HOOK = `#!/bin/bash
|
|
185
|
+
# PostToolUse progress hook for telegram-claude-daemon
|
|
186
|
+
# Sends tool completion events to update progress display (fire-and-forget)
|
|
187
|
+
|
|
188
|
+
DAEMON_PORT="\${TELEGRAM_CLAUDE_PORT:-${DAEMON_PORT}}"
|
|
189
|
+
DAEMON_HOST="\${TELEGRAM_CLAUDE_HOST:-${DAEMON_HOST}}"
|
|
190
|
+
HOOK_URL="http://\${DAEMON_HOST}:\${DAEMON_PORT}/progress"
|
|
191
|
+
|
|
192
|
+
# Auto-detect project name from current working directory
|
|
193
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
194
|
+
BASE_SESSION="\${SESSION_NAME:-default}"
|
|
195
|
+
FULL_SESSION_NAME="\${BASE_SESSION}:\${PROJECT_NAME}"
|
|
196
|
+
|
|
197
|
+
INPUT=$(cat)
|
|
198
|
+
|
|
199
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .name // empty')
|
|
200
|
+
|
|
201
|
+
IS_ERROR=$(echo "$INPUT" | jq -r '.is_error // .isError // false')
|
|
202
|
+
[ "$IS_ERROR" = "true" ] && SUCCESS="false" || SUCCESS="true"
|
|
203
|
+
|
|
204
|
+
[ -z "$TOOL_NAME" ] && exit 0
|
|
205
|
+
|
|
206
|
+
# Skip progress for Telegram tools
|
|
207
|
+
case "$TOOL_NAME" in
|
|
208
|
+
mcp__telegram__*|send_message|continue_chat|notify_user|end_chat)
|
|
209
|
+
exit 0
|
|
210
|
+
;;
|
|
211
|
+
esac
|
|
212
|
+
|
|
213
|
+
PAYLOAD=$(jq -n \\
|
|
214
|
+
--arg type "post_tool" \\
|
|
215
|
+
--arg session_name "$FULL_SESSION_NAME" \\
|
|
216
|
+
--arg tool_name "$TOOL_NAME" \\
|
|
217
|
+
--argjson success "$SUCCESS" \\
|
|
218
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \\
|
|
219
|
+
'{type: $type, session_name: $session_name, tool_name: $tool_name, success: $success, timestamp: $timestamp}')
|
|
220
|
+
|
|
221
|
+
# Fire and forget
|
|
222
|
+
(curl -s -X POST "$HOOK_URL" \\
|
|
223
|
+
-H "Content-Type: application/json" \\
|
|
224
|
+
-H "X-Session-Name: $FULL_SESSION_NAME" \\
|
|
228
225
|
-d "$PAYLOAD" \\
|
|
229
|
-
--max-time
|
|
226
|
+
--max-time 2 2>/dev/null) &
|
|
227
|
+
|
|
228
|
+
exit 0
|
|
229
|
+
`;
|
|
230
|
+
|
|
231
|
+
const PROGRESS_STOP_HOOK = `#!/bin/bash
|
|
232
|
+
# Stop progress hook for telegram-claude-daemon
|
|
233
|
+
# Sends stop event to finalize progress display (fire-and-forget)
|
|
234
|
+
|
|
235
|
+
DAEMON_PORT="\${TELEGRAM_CLAUDE_PORT:-${DAEMON_PORT}}"
|
|
236
|
+
DAEMON_HOST="\${TELEGRAM_CLAUDE_HOST:-${DAEMON_HOST}}"
|
|
237
|
+
HOOK_URL="http://\${DAEMON_HOST}:\${DAEMON_PORT}/progress"
|
|
238
|
+
|
|
239
|
+
# Auto-detect project name from current working directory
|
|
240
|
+
PROJECT_NAME=$(basename "$(pwd)")
|
|
241
|
+
BASE_SESSION="\${SESSION_NAME:-default}"
|
|
242
|
+
FULL_SESSION_NAME="\${BASE_SESSION}:\${PROJECT_NAME}"
|
|
243
|
+
|
|
244
|
+
PAYLOAD=$(jq -n \\
|
|
245
|
+
--arg type "stop" \\
|
|
246
|
+
--arg session_name "$FULL_SESSION_NAME" \\
|
|
247
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \\
|
|
248
|
+
'{type: $type, session_name: $session_name, timestamp: $timestamp}')
|
|
249
|
+
|
|
250
|
+
# Fire and forget
|
|
251
|
+
(curl -s -X POST "$HOOK_URL" \\
|
|
252
|
+
-H "Content-Type: application/json" \\
|
|
253
|
+
-H "X-Session-Name: $FULL_SESSION_NAME" \\
|
|
254
|
+
-d "$PAYLOAD" \\
|
|
255
|
+
--max-time 2 2>/dev/null) &
|
|
256
|
+
|
|
257
|
+
exit 0
|
|
230
258
|
`;
|
|
231
259
|
|
|
232
260
|
// Generate hooks configuration for Claude settings
|
|
@@ -234,6 +262,7 @@ curl -s -X POST "$HOOK_URL" \\
|
|
|
234
262
|
function generateHooksConfig(sessionName = 'default') {
|
|
235
263
|
const envPrefix = `SESSION_NAME=${sessionName}`;
|
|
236
264
|
return {
|
|
265
|
+
// Permission request handling
|
|
237
266
|
PermissionRequest: [
|
|
238
267
|
{
|
|
239
268
|
matcher: '*',
|
|
@@ -245,17 +274,23 @@ function generateHooksConfig(sessionName = 'default') {
|
|
|
245
274
|
]
|
|
246
275
|
}
|
|
247
276
|
],
|
|
277
|
+
// Stop hook with interactive continue option
|
|
248
278
|
Stop: [
|
|
249
279
|
{
|
|
250
280
|
matcher: '*',
|
|
251
281
|
hooks: [
|
|
252
282
|
{
|
|
253
283
|
type: 'command',
|
|
254
|
-
command: `${envPrefix} ~/.claude/hooks/stop-hook.sh`
|
|
284
|
+
command: `${envPrefix} ~/.claude/hooks/progress-stop-hook.sh` // Progress finalization
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
type: 'command',
|
|
288
|
+
command: `${envPrefix} ~/.claude/hooks/stop-hook.sh` // Interactive stop
|
|
255
289
|
}
|
|
256
290
|
]
|
|
257
291
|
}
|
|
258
292
|
],
|
|
293
|
+
// Notification forwarding
|
|
259
294
|
Notification: [
|
|
260
295
|
{
|
|
261
296
|
matcher: '*',
|
|
@@ -266,6 +301,30 @@ function generateHooksConfig(sessionName = 'default') {
|
|
|
266
301
|
}
|
|
267
302
|
]
|
|
268
303
|
}
|
|
304
|
+
],
|
|
305
|
+
// Progress tracking - PreToolUse
|
|
306
|
+
PreToolUse: [
|
|
307
|
+
{
|
|
308
|
+
matcher: '*',
|
|
309
|
+
hooks: [
|
|
310
|
+
{
|
|
311
|
+
type: 'command',
|
|
312
|
+
command: `${envPrefix} ~/.claude/hooks/progress-pre-tool-hook.sh`
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
// Progress tracking - PostToolUse
|
|
318
|
+
PostToolUse: [
|
|
319
|
+
{
|
|
320
|
+
matcher: '*',
|
|
321
|
+
hooks: [
|
|
322
|
+
{
|
|
323
|
+
type: 'command',
|
|
324
|
+
command: `${envPrefix} ~/.claude/hooks/progress-post-tool-hook.sh`
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
}
|
|
269
328
|
]
|
|
270
329
|
};
|
|
271
330
|
}
|
|
@@ -280,7 +339,7 @@ function setup() {
|
|
|
280
339
|
console.log('✓ Created ~/.claude/hooks/');
|
|
281
340
|
}
|
|
282
341
|
|
|
283
|
-
// Write hook scripts
|
|
342
|
+
// Write hook scripts - core hooks
|
|
284
343
|
const permissionHookPath = join(HOOKS_DIR, 'permission-hook.sh');
|
|
285
344
|
writeFileSync(permissionHookPath, PERMISSION_HOOK);
|
|
286
345
|
chmodSync(permissionHookPath, 0o755);
|
|
@@ -296,6 +355,22 @@ function setup() {
|
|
|
296
355
|
chmodSync(notifyHookPath, 0o755);
|
|
297
356
|
console.log('✓ Installed notify-hook.sh');
|
|
298
357
|
|
|
358
|
+
// Write hook scripts - progress tracking
|
|
359
|
+
const progressPreToolPath = join(HOOKS_DIR, 'progress-pre-tool-hook.sh');
|
|
360
|
+
writeFileSync(progressPreToolPath, PROGRESS_PRE_TOOL_HOOK);
|
|
361
|
+
chmodSync(progressPreToolPath, 0o755);
|
|
362
|
+
console.log('✓ Installed progress-pre-tool-hook.sh');
|
|
363
|
+
|
|
364
|
+
const progressPostToolPath = join(HOOKS_DIR, 'progress-post-tool-hook.sh');
|
|
365
|
+
writeFileSync(progressPostToolPath, PROGRESS_POST_TOOL_HOOK);
|
|
366
|
+
chmodSync(progressPostToolPath, 0o755);
|
|
367
|
+
console.log('✓ Installed progress-post-tool-hook.sh');
|
|
368
|
+
|
|
369
|
+
const progressStopPath = join(HOOKS_DIR, 'progress-stop-hook.sh');
|
|
370
|
+
writeFileSync(progressStopPath, PROGRESS_STOP_HOOK);
|
|
371
|
+
chmodSync(progressStopPath, 0o755);
|
|
372
|
+
console.log('✓ Installed progress-stop-hook.sh');
|
|
373
|
+
|
|
299
374
|
// Update Claude settings
|
|
300
375
|
let settings = {};
|
|
301
376
|
if (existsSync(SETTINGS_FILE)) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"_comment": "Example Claude Code hooks configuration for progress tracking. Copy relevant sections to ~/.claude/settings.json or .claude/settings.json in your project.",
|
|
4
|
+
|
|
5
|
+
"hooks": {
|
|
6
|
+
"PreToolUse": [
|
|
7
|
+
{
|
|
8
|
+
"matcher": "*",
|
|
9
|
+
"command": "SESSION_NAME=${SESSION_NAME:-$(basename \"$PWD\")} /path/to/call-me/server/hooks-v2/progress-pre-tool.sh"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"PostToolUse": [
|
|
13
|
+
{
|
|
14
|
+
"matcher": "*",
|
|
15
|
+
"command": "SESSION_NAME=${SESSION_NAME:-$(basename \"$PWD\")} /path/to/call-me/server/hooks-v2/progress-post-tool.sh"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"Stop": [
|
|
19
|
+
{
|
|
20
|
+
"command": "SESSION_NAME=${SESSION_NAME:-$(basename \"$PWD\")} /path/to/call-me/server/hooks-v2/progress-stop.sh"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"_comment": "Optional: Interactive stop hook (waits for user response)",
|
|
24
|
+
"command": "SESSION_NAME=${SESSION_NAME:-$(basename \"$PWD\")} /path/to/call-me/server/hooks-v2/stop-hook.sh"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse progress hook for telegram-claude-daemon
|
|
3
|
+
# Sends tool completion events to update progress display
|
|
4
|
+
#
|
|
5
|
+
# This hook is fire-and-forget - it doesn't block Claude's execution
|
|
6
|
+
|
|
7
|
+
DAEMON_PORT="${TELEGRAM_CLAUDE_PORT:-3333}"
|
|
8
|
+
DAEMON_HOST="${TELEGRAM_CLAUDE_HOST:-localhost}"
|
|
9
|
+
HOOK_URL="http://${DAEMON_HOST}:${DAEMON_PORT}/progress"
|
|
10
|
+
|
|
11
|
+
# Read input from stdin
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
|
|
14
|
+
# Extract tool info
|
|
15
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .name // empty')
|
|
16
|
+
TOOL_RESULT=$(echo "$INPUT" | jq -c '.tool_result // .result // {}')
|
|
17
|
+
|
|
18
|
+
# Check if tool succeeded
|
|
19
|
+
IS_ERROR=$(echo "$INPUT" | jq -r '.is_error // .isError // false')
|
|
20
|
+
if [ "$IS_ERROR" = "true" ]; then
|
|
21
|
+
SUCCESS="false"
|
|
22
|
+
else
|
|
23
|
+
SUCCESS="true"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Skip if no tool name
|
|
27
|
+
if [ -z "$TOOL_NAME" ]; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Skip progress updates for Telegram tools (would be recursive)
|
|
32
|
+
case "$TOOL_NAME" in
|
|
33
|
+
mcp__telegram__*|send_message|continue_chat|notify_user|end_chat)
|
|
34
|
+
exit 0
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
38
|
+
# Build payload
|
|
39
|
+
PAYLOAD=$(jq -n \
|
|
40
|
+
--arg type "post_tool" \
|
|
41
|
+
--arg session_name "${SESSION_NAME:-default}" \
|
|
42
|
+
--arg tool_name "$TOOL_NAME" \
|
|
43
|
+
--argjson success "$SUCCESS" \
|
|
44
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
45
|
+
'{
|
|
46
|
+
type: $type,
|
|
47
|
+
session_name: $session_name,
|
|
48
|
+
tool_name: $tool_name,
|
|
49
|
+
success: $success,
|
|
50
|
+
timestamp: $timestamp
|
|
51
|
+
}')
|
|
52
|
+
|
|
53
|
+
# Fire and forget - send in background and don't wait
|
|
54
|
+
(curl -s -X POST "$HOOK_URL" \
|
|
55
|
+
-H "Content-Type: application/json" \
|
|
56
|
+
-H "X-Session-Name: ${SESSION_NAME:-default}" \
|
|
57
|
+
-d "$PAYLOAD" \
|
|
58
|
+
--max-time 2 2>/dev/null) &
|
|
59
|
+
|
|
60
|
+
# Exit immediately - don't block Claude
|
|
61
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreToolUse progress hook for telegram-claude-daemon
|
|
3
|
+
# Sends tool start events to update progress display
|
|
4
|
+
#
|
|
5
|
+
# This hook is fire-and-forget - it doesn't block Claude's execution
|
|
6
|
+
|
|
7
|
+
DAEMON_PORT="${TELEGRAM_CLAUDE_PORT:-3333}"
|
|
8
|
+
DAEMON_HOST="${TELEGRAM_CLAUDE_HOST:-localhost}"
|
|
9
|
+
HOOK_URL="http://${DAEMON_HOST}:${DAEMON_PORT}/progress"
|
|
10
|
+
|
|
11
|
+
# Read input from stdin
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
|
|
14
|
+
# Extract tool info
|
|
15
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .toolName // .name // empty')
|
|
16
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // .toolInput // .input // .arguments // {}')
|
|
17
|
+
|
|
18
|
+
# Skip if no tool name
|
|
19
|
+
if [ -z "$TOOL_NAME" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Skip progress updates for Telegram tools (would be recursive)
|
|
24
|
+
case "$TOOL_NAME" in
|
|
25
|
+
mcp__telegram__*|send_message|continue_chat|notify_user|end_chat)
|
|
26
|
+
exit 0
|
|
27
|
+
;;
|
|
28
|
+
esac
|
|
29
|
+
|
|
30
|
+
# Build payload
|
|
31
|
+
PAYLOAD=$(jq -n \
|
|
32
|
+
--arg type "pre_tool" \
|
|
33
|
+
--arg session_name "${SESSION_NAME:-default}" \
|
|
34
|
+
--arg tool_name "$TOOL_NAME" \
|
|
35
|
+
--argjson tool_input "$TOOL_INPUT" \
|
|
36
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
37
|
+
'{
|
|
38
|
+
type: $type,
|
|
39
|
+
session_name: $session_name,
|
|
40
|
+
tool_name: $tool_name,
|
|
41
|
+
tool_input: $tool_input,
|
|
42
|
+
timestamp: $timestamp
|
|
43
|
+
}')
|
|
44
|
+
|
|
45
|
+
# Fire and forget - send in background and don't wait
|
|
46
|
+
(curl -s -X POST "$HOOK_URL" \
|
|
47
|
+
-H "Content-Type: application/json" \
|
|
48
|
+
-H "X-Session-Name: ${SESSION_NAME:-default}" \
|
|
49
|
+
-d "$PAYLOAD" \
|
|
50
|
+
--max-time 2 2>/dev/null) &
|
|
51
|
+
|
|
52
|
+
# Exit immediately - don't block Claude
|
|
53
|
+
exit 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Stop progress hook for telegram-claude-daemon
|
|
3
|
+
# Sends stop events to finalize progress display
|
|
4
|
+
#
|
|
5
|
+
# This hook is fire-and-forget - it doesn't block Claude's execution
|
|
6
|
+
# Note: This is separate from stop-hook.sh which handles interactive stop
|
|
7
|
+
|
|
8
|
+
DAEMON_PORT="${TELEGRAM_CLAUDE_PORT:-3333}"
|
|
9
|
+
DAEMON_HOST="${TELEGRAM_CLAUDE_HOST:-localhost}"
|
|
10
|
+
HOOK_URL="http://${DAEMON_HOST}:${DAEMON_PORT}/progress"
|
|
11
|
+
|
|
12
|
+
# Read input from stdin (if any)
|
|
13
|
+
INPUT=$(cat)
|
|
14
|
+
|
|
15
|
+
# Build payload
|
|
16
|
+
PAYLOAD=$(jq -n \
|
|
17
|
+
--arg type "stop" \
|
|
18
|
+
--arg session_name "${SESSION_NAME:-default}" \
|
|
19
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
20
|
+
'{
|
|
21
|
+
type: $type,
|
|
22
|
+
session_name: $session_name,
|
|
23
|
+
timestamp: $timestamp
|
|
24
|
+
}')
|
|
25
|
+
|
|
26
|
+
# Fire and forget - send in background and don't wait
|
|
27
|
+
(curl -s -X POST "$HOOK_URL" \
|
|
28
|
+
-H "Content-Type: application/json" \
|
|
29
|
+
-H "X-Session-Name: ${SESSION_NAME:-default}" \
|
|
30
|
+
-d "$PAYLOAD" \
|
|
31
|
+
--max-time 2 2>/dev/null) &
|
|
32
|
+
|
|
33
|
+
# Exit immediately - don't block Claude
|
|
34
|
+
exit 0
|
package/package.json
CHANGED
package/src/daemon/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { existsSync, unlinkSync, writeFileSync, mkdirSync } from 'fs';
|
|
|
13
13
|
import { dirname } from 'path';
|
|
14
14
|
import { SessionManager } from './session-manager.js';
|
|
15
15
|
import { MultiTelegramManager, type HookEvent } from './telegram-multi.js';
|
|
16
|
+
import { ProgressTracker, type ProgressEvent } from './progress-tracker.js';
|
|
16
17
|
import {
|
|
17
18
|
DAEMON_SOCKET_PATH,
|
|
18
19
|
DAEMON_PID_FILE,
|
|
@@ -96,6 +97,16 @@ async function main() {
|
|
|
96
97
|
|
|
97
98
|
telegram.start();
|
|
98
99
|
|
|
100
|
+
// Create progress tracker
|
|
101
|
+
const progressTracker = new ProgressTracker(
|
|
102
|
+
telegram.getBot(),
|
|
103
|
+
telegram.getChatId(),
|
|
104
|
+
sessionManager
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Register progress tracker's callback handler for mute buttons
|
|
108
|
+
telegram.registerCallbackInterceptor((query) => progressTracker.handleCallback(query));
|
|
109
|
+
|
|
99
110
|
// Track socket buffers for each connection
|
|
100
111
|
const socketBuffers = new Map<net.Socket, string>();
|
|
101
112
|
|
|
@@ -223,6 +234,41 @@ async function main() {
|
|
|
223
234
|
return;
|
|
224
235
|
}
|
|
225
236
|
|
|
237
|
+
// Progress tracking endpoint
|
|
238
|
+
if (url === '/progress' || url === '/hooks/progress') {
|
|
239
|
+
const event = data as ProgressEvent;
|
|
240
|
+
|
|
241
|
+
console.error(`[HTTP] Progress: ${event.type} - ${event.tool_name || ''} (session: ${sessionName})`);
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
switch (event.type) {
|
|
245
|
+
case 'start':
|
|
246
|
+
await progressTracker.handleSessionStart(sessionName);
|
|
247
|
+
break;
|
|
248
|
+
case 'pre_tool':
|
|
249
|
+
await progressTracker.handlePreTool(sessionName, event.tool_name!, event.tool_input);
|
|
250
|
+
break;
|
|
251
|
+
case 'post_tool':
|
|
252
|
+
await progressTracker.handlePostTool(sessionName, event.tool_name!, event.success ?? true);
|
|
253
|
+
break;
|
|
254
|
+
case 'stop':
|
|
255
|
+
await progressTracker.handleStop(sessionName);
|
|
256
|
+
break;
|
|
257
|
+
case 'notification':
|
|
258
|
+
await progressTracker.handleNotification(sessionName, event.tool_name || '');
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
263
|
+
res.end(JSON.stringify({ ok: true }));
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('[HTTP] Progress error:', error);
|
|
266
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
267
|
+
res.end(JSON.stringify({ ok: true })); // Don't fail hooks
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
226
272
|
// Status endpoint
|
|
227
273
|
if (url === '/status') {
|
|
228
274
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -284,9 +330,10 @@ async function main() {
|
|
|
284
330
|
process.on('SIGINT', shutdown);
|
|
285
331
|
process.on('SIGTERM', shutdown);
|
|
286
332
|
|
|
287
|
-
// Periodic cleanup of dead sessions
|
|
333
|
+
// Periodic cleanup of dead sessions and progress state
|
|
288
334
|
setInterval(() => {
|
|
289
335
|
sessionManager.cleanup();
|
|
336
|
+
progressTracker.cleanup();
|
|
290
337
|
}, 30000);
|
|
291
338
|
}
|
|
292
339
|
|
|
@@ -54,6 +54,7 @@ export class MultiTelegramManager {
|
|
|
54
54
|
private pendingPermissions: Map<string, PendingPermission> = new Map();
|
|
55
55
|
private messageToSession: Map<number, string> = new Map(); // messageId -> sessionId
|
|
56
56
|
private isRunning = false;
|
|
57
|
+
private callbackInterceptors: Array<(query: TelegramBot.CallbackQuery) => Promise<boolean>> = [];
|
|
57
58
|
|
|
58
59
|
constructor(config: MultiTelegramConfig, sessionManager: SessionManager) {
|
|
59
60
|
this.config = {
|
|
@@ -68,6 +69,28 @@ export class MultiTelegramManager {
|
|
|
68
69
|
this.setupCallbackHandler();
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Get the underlying bot instance for advanced operations.
|
|
74
|
+
*/
|
|
75
|
+
getBot(): TelegramBot {
|
|
76
|
+
return this.bot;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the configured chat ID.
|
|
81
|
+
*/
|
|
82
|
+
getChatId(): number {
|
|
83
|
+
return this.config.chatId;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Register a callback interceptor that handles callback queries before the default handler.
|
|
88
|
+
* Return true to indicate the query was handled and stop further processing.
|
|
89
|
+
*/
|
|
90
|
+
registerCallbackInterceptor(handler: (query: TelegramBot.CallbackQuery) => Promise<boolean>): void {
|
|
91
|
+
this.callbackInterceptors.push(handler);
|
|
92
|
+
}
|
|
93
|
+
|
|
71
94
|
start(): void {
|
|
72
95
|
this.isRunning = true;
|
|
73
96
|
console.error('[MultiTelegram] Bot started');
|
|
@@ -544,6 +567,16 @@ export class MultiTelegramManager {
|
|
|
544
567
|
this.bot.on('callback_query', async (query) => {
|
|
545
568
|
if (!query.data || !query.message) return;
|
|
546
569
|
|
|
570
|
+
// Check interceptors first
|
|
571
|
+
for (const interceptor of this.callbackInterceptors) {
|
|
572
|
+
try {
|
|
573
|
+
const handled = await interceptor(query);
|
|
574
|
+
if (handled) return;
|
|
575
|
+
} catch (error) {
|
|
576
|
+
console.error('[MultiTelegram] Callback interceptor error:', error);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
547
580
|
const [action, messageId] = query.data.split(':');
|
|
548
581
|
const key = messageId;
|
|
549
582
|
|