thepopebot 1.2.76-beta.7 → 1.2.76-beta.9
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/api/index.js +22 -8
- package/lib/ai/index.js +4 -4
- package/lib/ai/workspace-setup.js +18 -5
- package/lib/channels/telegram.js +78 -7
- package/lib/chat/actions.js +145 -0
- package/lib/chat/components/code-mode-toggle.js +3 -3
- package/lib/chat/components/code-mode-toggle.jsx +3 -3
- package/lib/chat/components/message.js +1 -1
- package/lib/chat/components/message.jsx +1 -1
- package/lib/chat/components/settings-secrets-page.js +274 -75
- package/lib/chat/components/settings-secrets-page.jsx +327 -65
- package/lib/config.js +11 -1
- package/lib/tools/telegram.js +115 -0
- package/package.json +1 -1
- package/setup/lib/telegram.mjs +9 -69
package/api/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createAgentJob } from '../lib/tools/create-agent-job.js';
|
|
|
3
3
|
import { setWebhook } from '../lib/tools/telegram.js';
|
|
4
4
|
import { getAgentJobStatus, fetchAgentJobLog } from '../lib/tools/github.js';
|
|
5
5
|
import { getTelegramAdapter } from '../lib/channels/index.js';
|
|
6
|
-
import { chat, summarizeAgentJob } from '../lib/ai/index.js';
|
|
6
|
+
import { chat, chatStream, summarizeAgentJob } from '../lib/ai/index.js';
|
|
7
7
|
import { createNotification } from '../lib/db/notifications.js';
|
|
8
8
|
import { loadTriggers } from '../lib/triggers.js';
|
|
9
9
|
import { verifyApiKey } from '../lib/db/api-keys.js';
|
|
@@ -211,19 +211,33 @@ async function handleTelegramWebhook(request) {
|
|
|
211
211
|
/**
|
|
212
212
|
* Process a normalized message through the AI layer with channel UX.
|
|
213
213
|
* Message persistence is handled centrally by the AI layer.
|
|
214
|
+
*
|
|
215
|
+
* Uses chatStream() for progressive tool-call rendering when the adapter
|
|
216
|
+
* supports it (Telegram: sends each tool call as a message, reacts on completion).
|
|
217
|
+
* Falls back to chat() for adapters without streamChatResponse.
|
|
214
218
|
*/
|
|
215
219
|
async function processChannelMessage(adapter, normalized) {
|
|
216
220
|
await adapter.acknowledge(normalized.metadata);
|
|
217
221
|
const stopIndicator = adapter.startProcessingIndicator(normalized.metadata);
|
|
218
222
|
|
|
219
223
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
if (adapter.streamChatResponse) {
|
|
225
|
+
const chunks = chatStream(
|
|
226
|
+
normalized.threadId,
|
|
227
|
+
normalized.text,
|
|
228
|
+
normalized.attachments,
|
|
229
|
+
{ userId: 'telegram', chatTitle: 'Telegram' }
|
|
230
|
+
);
|
|
231
|
+
await adapter.streamChatResponse(normalized.metadata.chatId, chunks);
|
|
232
|
+
} else {
|
|
233
|
+
const response = await chat(
|
|
234
|
+
normalized.threadId,
|
|
235
|
+
normalized.text,
|
|
236
|
+
normalized.attachments,
|
|
237
|
+
{ userId: 'telegram', chatTitle: 'Telegram' }
|
|
238
|
+
);
|
|
239
|
+
await adapter.sendResponse(normalized.threadId, response, normalized.metadata);
|
|
240
|
+
}
|
|
227
241
|
} catch (err) {
|
|
228
242
|
console.error('Failed to process message with AI:', err);
|
|
229
243
|
await adapter
|
package/lib/ai/index.js
CHANGED
|
@@ -178,19 +178,19 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
|
|
|
178
178
|
const setupArgs = { repo, branch, featureBranch };
|
|
179
179
|
|
|
180
180
|
if (needsSetup) {
|
|
181
|
-
yield { type: 'tool-call', toolCallId: setupToolCallId, toolName: '
|
|
181
|
+
yield { type: 'tool-call', toolCallId: setupToolCallId, toolName: 'workspace', args: setupArgs };
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
try {
|
|
185
|
-
await ensureWorkspaceRepo({ workspaceDir: repoDir, repo, branch, featureBranch });
|
|
185
|
+
const setupOutput = await ensureWorkspaceRepo({ workspaceDir: repoDir, repo, branch, featureBranch });
|
|
186
186
|
ensureSkills(repoDir, isCodeMode ? 'code' : 'agent');
|
|
187
187
|
if (needsSetup) {
|
|
188
|
-
const result = `Workspace ready on ${featureBranch || branch}`;
|
|
188
|
+
const result = setupOutput || `Workspace ready on ${featureBranch || branch}`;
|
|
189
189
|
yield { type: 'tool-result', toolCallId: setupToolCallId, result };
|
|
190
190
|
persistMessage(threadId, 'assistant', JSON.stringify({
|
|
191
191
|
type: 'tool-invocation',
|
|
192
192
|
toolCallId: setupToolCallId,
|
|
193
|
-
toolName: '
|
|
193
|
+
toolName: 'workspace',
|
|
194
194
|
state: 'output-available',
|
|
195
195
|
input: setupArgs,
|
|
196
196
|
output: result,
|
|
@@ -32,20 +32,24 @@ export async function ensureWorkspaceRepo({ workspaceDir, repo, branch, featureB
|
|
|
32
32
|
if (ghToken) env.GH_TOKEN = ghToken;
|
|
33
33
|
|
|
34
34
|
const execOpts = { cwd: workspaceDir, env };
|
|
35
|
+
const log = [];
|
|
35
36
|
|
|
36
37
|
// 1. Create workspace directory
|
|
37
38
|
mkdirSync(workspaceDir, { recursive: true });
|
|
38
39
|
|
|
39
40
|
// 2. Configure git to use GH_TOKEN for GitHub HTTPS URLs (mirrors setup-git.sh)
|
|
40
41
|
if (ghToken) {
|
|
41
|
-
await run('gh', ['auth', 'setup-git'], execOpts);
|
|
42
|
+
const out = await run('gh', ['auth', 'setup-git'], execOpts);
|
|
43
|
+
if (out) log.push(out);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
// 3. Clone if not already a git repo
|
|
45
47
|
const hasGit = existsSync(path.join(workspaceDir, '.git'));
|
|
46
48
|
if (!hasGit) {
|
|
47
49
|
if (!repo) throw new Error('ensureWorkspaceRepo: repo is required for initial clone');
|
|
48
|
-
await run('git', ['clone', '--branch', branch || 'main', `https://github.com/${repo}`, '.'], execOpts);
|
|
50
|
+
const out = await run('git', ['clone', '--branch', branch || 'main', `https://github.com/${repo}`, '.'], execOpts);
|
|
51
|
+
log.push(`Cloned ${repo} (branch: ${branch || 'main'})`);
|
|
52
|
+
if (out) log.push(out);
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
// 3. Git identity (only if not already configured)
|
|
@@ -61,6 +65,7 @@ export async function ensureWorkspaceRepo({ workspaceDir, repo, branch, featureB
|
|
|
61
65
|
const email = user.email || `${user.id}+${user.login}@users.noreply.github.com`;
|
|
62
66
|
await run('git', ['config', 'user.name', name], execOpts);
|
|
63
67
|
await run('git', ['config', 'user.email', email], execOpts);
|
|
68
|
+
log.push(`Git identity: ${name} <${email}>`);
|
|
64
69
|
} catch (err) {
|
|
65
70
|
console.error('[workspace-setup] Failed to set git identity:', err.message);
|
|
66
71
|
}
|
|
@@ -68,7 +73,7 @@ export async function ensureWorkspaceRepo({ workspaceDir, repo, branch, featureB
|
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
// 4. Feature branch checkout
|
|
71
|
-
if (!featureBranch) return;
|
|
76
|
+
if (!featureBranch) return log.join('\n');
|
|
72
77
|
|
|
73
78
|
// Already on the right branch locally?
|
|
74
79
|
try {
|
|
@@ -77,8 +82,11 @@ export async function ensureWorkspaceRepo({ workspaceDir, repo, branch, featureB
|
|
|
77
82
|
const current = await run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], execOpts);
|
|
78
83
|
if (current !== featureBranch) {
|
|
79
84
|
await run('git', ['checkout', featureBranch], execOpts);
|
|
85
|
+
log.push(`Checked out ${featureBranch}`);
|
|
86
|
+
} else {
|
|
87
|
+
log.push(`Already on ${featureBranch}`);
|
|
80
88
|
}
|
|
81
|
-
return;
|
|
89
|
+
return log.join('\n');
|
|
82
90
|
} catch {
|
|
83
91
|
// Branch doesn't exist locally — check remote
|
|
84
92
|
}
|
|
@@ -88,15 +96,20 @@ export async function ensureWorkspaceRepo({ workspaceDir, repo, branch, featureB
|
|
|
88
96
|
if (remoteCheck) {
|
|
89
97
|
// Remote branch exists — checkout tracking it
|
|
90
98
|
await run('git', ['checkout', '-B', featureBranch, `origin/${featureBranch}`], execOpts);
|
|
99
|
+
log.push(`Checked out ${featureBranch} (tracking origin)`);
|
|
91
100
|
} else {
|
|
92
101
|
// Create new branch and push
|
|
93
102
|
await run('git', ['checkout', '-b', featureBranch], execOpts);
|
|
94
|
-
await run('git', ['push', '-u', 'origin', featureBranch], execOpts);
|
|
103
|
+
const pushOut = await run('git', ['push', '-u', 'origin', featureBranch], execOpts);
|
|
104
|
+
log.push(`Created and pushed ${featureBranch}`);
|
|
105
|
+
if (pushOut) log.push(pushOut);
|
|
95
106
|
}
|
|
96
107
|
} catch (err) {
|
|
97
108
|
console.error('[workspace-setup] Feature branch error:', err.message);
|
|
98
109
|
throw err;
|
|
99
110
|
}
|
|
111
|
+
|
|
112
|
+
return log.join('\n');
|
|
100
113
|
}
|
|
101
114
|
|
|
102
115
|
/**
|
package/lib/channels/telegram.js
CHANGED
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
downloadFile,
|
|
5
5
|
reactToMessage,
|
|
6
6
|
startTypingIndicator,
|
|
7
|
+
formatToolCall,
|
|
7
8
|
} from '../tools/telegram.js';
|
|
8
9
|
import { isWhisperEnabled, transcribeAudio } from '../tools/openai.js';
|
|
10
|
+
import { getConfig } from '../config.js';
|
|
9
11
|
|
|
10
12
|
class TelegramAdapter extends ChannelAdapter {
|
|
11
13
|
constructor(botToken) {
|
|
@@ -19,15 +21,18 @@ class TelegramAdapter extends ChannelAdapter {
|
|
|
19
21
|
* Returns null if the update should be ignored.
|
|
20
22
|
*/
|
|
21
23
|
async receive(request) {
|
|
22
|
-
|
|
24
|
+
// Read config from DB (with .env fallback via getConfig)
|
|
25
|
+
const webhookSecret = getConfig('TELEGRAM_WEBHOOK_SECRET');
|
|
26
|
+
const allowedChatId = getConfig('TELEGRAM_CHAT_ID');
|
|
27
|
+
const verificationCode = getConfig('TELEGRAM_VERIFICATION');
|
|
23
28
|
|
|
24
29
|
// Validate secret token (required)
|
|
25
|
-
if (!
|
|
30
|
+
if (!webhookSecret) {
|
|
26
31
|
console.error('[telegram] TELEGRAM_WEBHOOK_SECRET not configured — rejecting webhook');
|
|
27
32
|
return null;
|
|
28
33
|
}
|
|
29
34
|
const headerSecret = request.headers.get('x-telegram-bot-api-secret-token');
|
|
30
|
-
if (headerSecret !==
|
|
35
|
+
if (headerSecret !== webhookSecret) {
|
|
31
36
|
return null;
|
|
32
37
|
}
|
|
33
38
|
|
|
@@ -40,17 +45,27 @@ class TelegramAdapter extends ChannelAdapter {
|
|
|
40
45
|
let text = message.text || null;
|
|
41
46
|
const attachments = [];
|
|
42
47
|
|
|
43
|
-
// Check for verification code — works even before TELEGRAM_CHAT_ID is set
|
|
44
|
-
|
|
48
|
+
// Check for verification code — works even before TELEGRAM_CHAT_ID is set.
|
|
49
|
+
// On match, save the chat ID directly and clear the (one-time) verification code.
|
|
50
|
+
if (verificationCode && text === verificationCode) {
|
|
45
51
|
await sendMessage(this.botToken, chatId, `Your chat ID:\n<code>${chatId}</code>`);
|
|
52
|
+
try {
|
|
53
|
+
const { setConfigValue, deleteConfigValue } = await import('../db/config.js');
|
|
54
|
+
const { invalidateConfigCache } = await import('../config.js');
|
|
55
|
+
setConfigValue('TELEGRAM_CHAT_ID', chatId);
|
|
56
|
+
deleteConfigValue('TELEGRAM_VERIFICATION');
|
|
57
|
+
invalidateConfigCache();
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('[telegram] Failed to persist verified chat ID:', err.message);
|
|
60
|
+
}
|
|
46
61
|
return null;
|
|
47
62
|
}
|
|
48
63
|
|
|
49
64
|
// Security: if no TELEGRAM_CHAT_ID configured, ignore all messages
|
|
50
|
-
if (!
|
|
65
|
+
if (!allowedChatId) return null;
|
|
51
66
|
|
|
52
67
|
// Security: only accept messages from configured chat
|
|
53
|
-
if (chatId !==
|
|
68
|
+
if (chatId !== allowedChatId) return null;
|
|
54
69
|
|
|
55
70
|
// Voice messages → transcribe to text
|
|
56
71
|
if (message.voice) {
|
|
@@ -140,6 +155,62 @@ class TelegramAdapter extends ChannelAdapter {
|
|
|
140
155
|
await sendMessage(this.botToken, threadId, text);
|
|
141
156
|
}
|
|
142
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Consume a chatStream() async iterable and send progressive messages.
|
|
160
|
+
* - Text chunks accumulate and flush when a tool-call arrives or stream ends.
|
|
161
|
+
* - Each tool-call sends immediately as its own message.
|
|
162
|
+
* - Tool-results react to the tool-call message with ✅ (or ❌ on error).
|
|
163
|
+
*
|
|
164
|
+
* @param {string} chatId - Telegram chat ID
|
|
165
|
+
* @param {AsyncIterable} chunks - chatStream() output
|
|
166
|
+
*/
|
|
167
|
+
async streamChatResponse(chatId, chunks) {
|
|
168
|
+
let textBuffer = '';
|
|
169
|
+
// Map toolCallId → { telegramMessageId, hasCompleteArgs }
|
|
170
|
+
const toolMessages = new Map();
|
|
171
|
+
|
|
172
|
+
for await (const chunk of chunks) {
|
|
173
|
+
if (chunk.type === 'text') {
|
|
174
|
+
textBuffer += chunk.text;
|
|
175
|
+
} else if (chunk.type === 'tool-call') {
|
|
176
|
+
// Skip the first empty-args emission — wait for complete args
|
|
177
|
+
if (!chunk.args || Object.keys(chunk.args).length === 0) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Flush accumulated text before tool call
|
|
182
|
+
if (textBuffer.trim()) {
|
|
183
|
+
await sendMessage(this.botToken, chatId, textBuffer.trim());
|
|
184
|
+
textBuffer = '';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Send tool call as its own message
|
|
188
|
+
const text = formatToolCall(chunk.toolName, chunk.args);
|
|
189
|
+
try {
|
|
190
|
+
const msg = await sendMessage(this.botToken, chatId, text);
|
|
191
|
+
toolMessages.set(chunk.toolCallId, msg.message_id);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error('[telegram] Failed to send tool call:', err.message);
|
|
194
|
+
}
|
|
195
|
+
} else if (chunk.type === 'tool-result') {
|
|
196
|
+
const messageId = toolMessages.get(chunk.toolCallId);
|
|
197
|
+
if (messageId) {
|
|
198
|
+
const emoji = chunk.result?.includes?.('error') || chunk.result?.includes?.('Error')
|
|
199
|
+
? '👎'
|
|
200
|
+
: '👍';
|
|
201
|
+
reactToMessage(this.botToken, chatId, messageId, emoji).catch(() => {});
|
|
202
|
+
toolMessages.delete(chunk.toolCallId);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Skip: meta, result, thinking-*, unknown
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Flush remaining text
|
|
209
|
+
if (textBuffer.trim()) {
|
|
210
|
+
await sendMessage(this.botToken, chatId, textBuffer.trim());
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
143
214
|
get supportsStreaming() {
|
|
144
215
|
return false;
|
|
145
216
|
}
|
package/lib/chat/actions.js
CHANGED
|
@@ -966,6 +966,151 @@ export async function regenerateWebhookSecret(key) {
|
|
|
966
966
|
}
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
970
|
+
// Settings — Telegram setup flow
|
|
971
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Get Telegram status: bot info (if token valid), webhook info, chat ID.
|
|
975
|
+
* Used by the admin UI to render the current state of each setup step.
|
|
976
|
+
*/
|
|
977
|
+
export async function getTelegramStatus() {
|
|
978
|
+
await requireAuth();
|
|
979
|
+
try {
|
|
980
|
+
const { getConfigSecret, getConfigValue } = await import('../db/config.js');
|
|
981
|
+
const { validateBotToken, getTelegramWebhookInfo } = await import('../tools/telegram.js');
|
|
982
|
+
|
|
983
|
+
const botToken = getConfigSecret('TELEGRAM_BOT_TOKEN');
|
|
984
|
+
const webhookSecret = getConfigSecret('TELEGRAM_WEBHOOK_SECRET');
|
|
985
|
+
const chatId = getConfigValue('TELEGRAM_CHAT_ID');
|
|
986
|
+
const verificationCode = getConfigValue('TELEGRAM_VERIFICATION');
|
|
987
|
+
|
|
988
|
+
let botInfo = null;
|
|
989
|
+
let webhookInfo = null;
|
|
990
|
+
if (botToken) {
|
|
991
|
+
const v = await validateBotToken(botToken);
|
|
992
|
+
if (v.valid) botInfo = { username: v.botInfo.username, id: v.botInfo.id };
|
|
993
|
+
try {
|
|
994
|
+
const info = await getTelegramWebhookInfo(botToken);
|
|
995
|
+
if (info.ok) {
|
|
996
|
+
webhookInfo = {
|
|
997
|
+
url: info.result.url || null,
|
|
998
|
+
hasCustomCertificate: info.result.has_custom_certificate,
|
|
999
|
+
pendingUpdates: info.result.pending_update_count,
|
|
1000
|
+
lastErrorMessage: info.result.last_error_message || null,
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
} catch {
|
|
1004
|
+
// ignore — webhook info is best-effort
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return {
|
|
1009
|
+
botInfo,
|
|
1010
|
+
botTokenSet: !!botToken,
|
|
1011
|
+
webhookSecretSet: !!webhookSecret,
|
|
1012
|
+
webhookInfo,
|
|
1013
|
+
chatId: chatId || null,
|
|
1014
|
+
pendingVerification: !!verificationCode,
|
|
1015
|
+
};
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
console.error('Failed to get Telegram status:', err);
|
|
1018
|
+
return { error: 'Failed to load Telegram status' };
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Validate a Telegram bot token without saving it.
|
|
1024
|
+
* Called from the UI as the user pastes a token to show live feedback.
|
|
1025
|
+
*/
|
|
1026
|
+
export async function validateTelegramToken(token) {
|
|
1027
|
+
await requireAuth();
|
|
1028
|
+
if (!token) return { valid: false, error: 'Token is required' };
|
|
1029
|
+
try {
|
|
1030
|
+
const { validateBotToken } = await import('../tools/telegram.js');
|
|
1031
|
+
const result = await validateBotToken(token);
|
|
1032
|
+
if (result.valid) {
|
|
1033
|
+
return { valid: true, botInfo: { username: result.botInfo.username, id: result.botInfo.id } };
|
|
1034
|
+
}
|
|
1035
|
+
return { valid: false, error: result.error };
|
|
1036
|
+
} catch (err) {
|
|
1037
|
+
return { valid: false, error: err.message };
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Register the Telegram webhook with the currently saved bot token.
|
|
1043
|
+
* Generates a fresh webhook secret, saves it, and calls Telegram's setWebhook.
|
|
1044
|
+
* APP_URL must be configured.
|
|
1045
|
+
*/
|
|
1046
|
+
export async function registerTelegramWebhook() {
|
|
1047
|
+
const user = await requireAuth();
|
|
1048
|
+
try {
|
|
1049
|
+
const { getConfigSecret, setConfigSecret } = await import('../db/config.js');
|
|
1050
|
+
const { invalidateConfigCache, getConfig } = await import('../config.js');
|
|
1051
|
+
const { setTelegramWebhook, generateWebhookSecret } = await import('../tools/telegram.js');
|
|
1052
|
+
|
|
1053
|
+
const botToken = getConfigSecret('TELEGRAM_BOT_TOKEN');
|
|
1054
|
+
if (!botToken) return { error: 'Bot token must be set first' };
|
|
1055
|
+
|
|
1056
|
+
const appUrl = getConfig('APP_URL');
|
|
1057
|
+
if (!appUrl) return { error: 'APP_URL must be configured first' };
|
|
1058
|
+
|
|
1059
|
+
const webhookUrl = `${appUrl.replace(/\/$/, '')}/api/telegram/webhook`;
|
|
1060
|
+
const secret = generateWebhookSecret();
|
|
1061
|
+
setConfigSecret('TELEGRAM_WEBHOOK_SECRET', secret, user.id);
|
|
1062
|
+
invalidateConfigCache();
|
|
1063
|
+
|
|
1064
|
+
const result = await setTelegramWebhook(botToken, webhookUrl, secret);
|
|
1065
|
+
if (!result.ok) {
|
|
1066
|
+
return { error: result.description || 'Failed to register webhook' };
|
|
1067
|
+
}
|
|
1068
|
+
return { success: true, webhookUrl };
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
console.error('Failed to register Telegram webhook:', err);
|
|
1071
|
+
return { error: err.message };
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Start verification: generate a code, save it, return it to the UI.
|
|
1077
|
+
* User sends the code to the bot; the webhook handler captures the chat ID,
|
|
1078
|
+
* saves it as TELEGRAM_CHAT_ID, and clears this verification code.
|
|
1079
|
+
* The UI polls getTelegramStatus() to detect completion.
|
|
1080
|
+
*/
|
|
1081
|
+
export async function startTelegramVerification() {
|
|
1082
|
+
const user = await requireAuth();
|
|
1083
|
+
try {
|
|
1084
|
+
const { setConfigValue } = await import('../db/config.js');
|
|
1085
|
+
const { invalidateConfigCache } = await import('../config.js');
|
|
1086
|
+
const { generateVerificationCode } = await import('../tools/telegram.js');
|
|
1087
|
+
|
|
1088
|
+
const code = generateVerificationCode();
|
|
1089
|
+
setConfigValue('TELEGRAM_VERIFICATION', code, user.id);
|
|
1090
|
+
invalidateConfigCache();
|
|
1091
|
+
return { success: true, code };
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
console.error('Failed to start Telegram verification:', err);
|
|
1094
|
+
return { error: err.message };
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Cancel pending verification (clear the code).
|
|
1100
|
+
*/
|
|
1101
|
+
export async function cancelTelegramVerification() {
|
|
1102
|
+
await requireAuth();
|
|
1103
|
+
try {
|
|
1104
|
+
const { deleteConfigValue } = await import('../db/config.js');
|
|
1105
|
+
const { invalidateConfigCache } = await import('../config.js');
|
|
1106
|
+
deleteConfigValue('TELEGRAM_VERIFICATION');
|
|
1107
|
+
invalidateConfigCache();
|
|
1108
|
+
return { success: true };
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
return { error: err.message };
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
969
1114
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
970
1115
|
// Settings — Chat sub-tab
|
|
971
1116
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -200,7 +200,7 @@ function WorkspaceBar({
|
|
|
200
200
|
repoName && /* @__PURE__ */ jsx("span", { className: "shrink-0 cursor-default hidden md:inline", title: repo, children: repoName }),
|
|
201
201
|
branch && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
202
202
|
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground/30 hidden md:inline", children: "/" }),
|
|
203
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
203
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 max-w-[120px] md:max-w-[160px]", children: /* @__PURE__ */ jsx(
|
|
204
204
|
Combobox,
|
|
205
205
|
{
|
|
206
206
|
options: branches.map((b) => ({ value: b.name, label: b.name })),
|
|
@@ -218,8 +218,8 @@ function WorkspaceBar({
|
|
|
218
218
|
}).finally(() => setLoadingBranches(false));
|
|
219
219
|
}
|
|
220
220
|
},
|
|
221
|
-
triggerClassName: "font-medium text-foreground hover:text-primary hover:bg-accent transition-colors cursor-pointer truncate text-xs rounded px-1 -mx-1",
|
|
222
|
-
triggerLabel: /* @__PURE__ */ jsx("span", { className: "truncate", title: branch, children: branch })
|
|
221
|
+
triggerClassName: "block w-full text-left font-medium text-foreground hover:text-primary hover:bg-accent transition-colors cursor-pointer truncate text-xs rounded px-1 -mx-1",
|
|
222
|
+
triggerLabel: /* @__PURE__ */ jsx("span", { className: "block truncate", title: branch, children: branch })
|
|
223
223
|
}
|
|
224
224
|
) })
|
|
225
225
|
] }),
|
|
@@ -225,7 +225,7 @@ export function WorkspaceBar({
|
|
|
225
225
|
{branch && (
|
|
226
226
|
<>
|
|
227
227
|
<span className="shrink-0 text-muted-foreground/30 hidden md:inline">/</span>
|
|
228
|
-
<div className="
|
|
228
|
+
<div className="min-w-0 max-w-[120px] md:max-w-[160px]">
|
|
229
229
|
<Combobox
|
|
230
230
|
options={branches.map((b) => ({ value: b.name, label: b.name }))}
|
|
231
231
|
value={branch}
|
|
@@ -242,8 +242,8 @@ export function WorkspaceBar({
|
|
|
242
242
|
}).finally(() => setLoadingBranches(false));
|
|
243
243
|
}
|
|
244
244
|
}}
|
|
245
|
-
triggerClassName="font-medium text-foreground hover:text-primary hover:bg-accent transition-colors cursor-pointer truncate text-xs rounded px-1 -mx-1"
|
|
246
|
-
triggerLabel={<span className="truncate" title={branch}>{branch}</span>}
|
|
245
|
+
triggerClassName="block w-full text-left font-medium text-foreground hover:text-primary hover:bg-accent transition-colors cursor-pointer truncate text-xs rounded px-1 -mx-1"
|
|
246
|
+
triggerLabel={<span className="block truncate" title={branch}>{branch}</span>}
|
|
247
247
|
/>
|
|
248
248
|
</div>
|
|
249
249
|
</>
|
|
@@ -434,7 +434,7 @@ function PreviewMessage({ message, isLoading, onRetry, onEdit }) {
|
|
|
434
434
|
function ThinkingMessage() {
|
|
435
435
|
return /* @__PURE__ */ jsx("div", { className: "flex gap-4 w-full justify-start", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 text-sm text-muted-foreground", children: [
|
|
436
436
|
/* @__PURE__ */ jsx(SpinnerIcon, { size: 14 }),
|
|
437
|
-
/* @__PURE__ */ jsx("span", { children: "
|
|
437
|
+
/* @__PURE__ */ jsx("span", { className: "thinking-shimmer", children: "Waiting..." })
|
|
438
438
|
] }) });
|
|
439
439
|
}
|
|
440
440
|
export {
|
|
@@ -523,7 +523,7 @@ export function ThinkingMessage() {
|
|
|
523
523
|
<div className="flex gap-4 w-full justify-start">
|
|
524
524
|
<div className="flex items-center gap-2 px-4 py-3 text-sm text-muted-foreground">
|
|
525
525
|
<SpinnerIcon size={14} />
|
|
526
|
-
<span>
|
|
526
|
+
<span className="thinking-shimmer">Waiting...</span>
|
|
527
527
|
</div>
|
|
528
528
|
</div>
|
|
529
529
|
);
|