thepopebot 1.2.78 → 1.2.82
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
CHANGED
|
@@ -16,15 +16,8 @@ import { setAgentJobSecret } from '../lib/db/config.js';
|
|
|
16
16
|
// ── Per-key lock for OAuth token refresh ────────────────────────────
|
|
17
17
|
const _refreshLocks = new Map();
|
|
18
18
|
|
|
19
|
-
// Bot token — resolved from DB/env
|
|
20
|
-
let telegramBotToken = null;
|
|
21
|
-
|
|
22
|
-
|
|
23
19
|
function getTelegramBotToken() {
|
|
24
|
-
|
|
25
|
-
telegramBotToken = getConfig('TELEGRAM_BOT_TOKEN') || null;
|
|
26
|
-
}
|
|
27
|
-
return telegramBotToken;
|
|
20
|
+
return getConfig('TELEGRAM_BOT_TOKEN') || null;
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
|
package/lib/chat/actions.js
CHANGED
|
@@ -287,9 +287,7 @@ export async function createOAuthToken(tokenType, name, token) {
|
|
|
287
287
|
const user = await requireAdmin();
|
|
288
288
|
try {
|
|
289
289
|
const { createOAuthToken: dbCreate } = await import('../db/oauth-tokens.js');
|
|
290
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
291
290
|
const result = dbCreate(tokenType, name || 'OAuth Token', token, user.id);
|
|
292
|
-
invalidateConfigCache();
|
|
293
291
|
return result;
|
|
294
292
|
} catch (err) {
|
|
295
293
|
console.error('Failed to create OAuth token:', err);
|
|
@@ -322,9 +320,7 @@ export async function deleteOAuthToken(id) {
|
|
|
322
320
|
await requireAdmin();
|
|
323
321
|
try {
|
|
324
322
|
const { deleteOAuthTokenById } = await import('../db/oauth-tokens.js');
|
|
325
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
326
323
|
deleteOAuthTokenById(id);
|
|
327
|
-
invalidateConfigCache();
|
|
328
324
|
return { success: true };
|
|
329
325
|
} catch (err) {
|
|
330
326
|
console.error('Failed to delete OAuth token:', err);
|
|
@@ -793,7 +789,6 @@ export async function updateCodingAgentConfig(agent, config) {
|
|
|
793
789
|
await requireAdmin();
|
|
794
790
|
try {
|
|
795
791
|
const { setConfigValue } = await import('../db/config.js');
|
|
796
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
797
792
|
|
|
798
793
|
if (agent === 'claude-code') {
|
|
799
794
|
if (config.enabled !== undefined) setConfigValue('CODING_AGENT_CLAUDE_CODE_ENABLED', String(config.enabled));
|
|
@@ -823,7 +818,6 @@ export async function updateCodingAgentConfig(agent, config) {
|
|
|
823
818
|
return { error: 'Invalid agent' };
|
|
824
819
|
}
|
|
825
820
|
|
|
826
|
-
invalidateConfigCache();
|
|
827
821
|
if (agent === 'claude-code' && config.backend !== undefined) {
|
|
828
822
|
await syncLitellmConfig();
|
|
829
823
|
}
|
|
@@ -842,9 +836,7 @@ export async function setCodingAgentDefault(agent) {
|
|
|
842
836
|
await requireAdmin();
|
|
843
837
|
try {
|
|
844
838
|
const { setConfigValue } = await import('../db/config.js');
|
|
845
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
846
839
|
setConfigValue('CODING_AGENT', agent);
|
|
847
|
-
invalidateConfigCache();
|
|
848
840
|
return { success: true };
|
|
849
841
|
} catch (err) {
|
|
850
842
|
console.error('Failed to set default coding agent:', err);
|
|
@@ -893,9 +885,7 @@ export async function setModeDefault(mode, field, value) {
|
|
|
893
885
|
const stored = field === 'autoRun' ? (value ? 'true' : 'false') : value;
|
|
894
886
|
|
|
895
887
|
const { setConfigValue } = await import('../db/config.js');
|
|
896
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
897
888
|
setConfigValue(key, stored);
|
|
898
|
-
invalidateConfigCache();
|
|
899
889
|
return { success: true };
|
|
900
890
|
} catch (err) {
|
|
901
891
|
console.error('Failed to set mode default:', err);
|
|
@@ -937,9 +927,7 @@ export async function updateGeneralSetting(key, value) {
|
|
|
937
927
|
}
|
|
938
928
|
try {
|
|
939
929
|
const { setConfigValue } = await import('../db/config.js');
|
|
940
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
941
930
|
setConfigValue(key, value);
|
|
942
|
-
invalidateConfigCache();
|
|
943
931
|
return { success: true };
|
|
944
932
|
} catch (err) {
|
|
945
933
|
console.error('Failed to update general setting:', err);
|
|
@@ -985,7 +973,6 @@ export async function updateApiKeySetting(key, value) {
|
|
|
985
973
|
}
|
|
986
974
|
try {
|
|
987
975
|
const { setConfigSecret, deleteConfigSecret } = await import('../db/config.js');
|
|
988
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
989
976
|
|
|
990
977
|
if (value) {
|
|
991
978
|
setConfigSecret(key, value, user.id);
|
|
@@ -993,7 +980,6 @@ export async function updateApiKeySetting(key, value) {
|
|
|
993
980
|
deleteConfigSecret(key);
|
|
994
981
|
}
|
|
995
982
|
|
|
996
|
-
invalidateConfigCache();
|
|
997
983
|
return { success: true };
|
|
998
984
|
} catch (err) {
|
|
999
985
|
console.error('Failed to update API key setting:', err);
|
|
@@ -1013,9 +999,7 @@ export async function regenerateWebhookSecret(key) {
|
|
|
1013
999
|
const { randomBytes } = await import('crypto');
|
|
1014
1000
|
const secret = randomBytes(32).toString('hex');
|
|
1015
1001
|
const { setConfigSecret } = await import('../db/config.js');
|
|
1016
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
1017
1002
|
setConfigSecret(key, secret, user.id);
|
|
1018
|
-
invalidateConfigCache();
|
|
1019
1003
|
return { success: true };
|
|
1020
1004
|
} catch (err) {
|
|
1021
1005
|
console.error('Failed to regenerate webhook secret:', err);
|
|
@@ -1034,12 +1018,19 @@ export async function regenerateWebhookSecret(key) {
|
|
|
1034
1018
|
export async function getTelegramStatus() {
|
|
1035
1019
|
await requireAuth();
|
|
1036
1020
|
try {
|
|
1037
|
-
const { getConfigSecret } = await import('../db/config.js');
|
|
1021
|
+
const { getConfigSecret, getConfigValue } = await import('../db/config.js');
|
|
1022
|
+
const { getConfig } = await import('../config.js');
|
|
1038
1023
|
const { validateBotToken, getTelegramWebhookInfo } = await import('../tools/telegram.js');
|
|
1039
1024
|
|
|
1040
1025
|
const botToken = getConfigSecret('TELEGRAM_BOT_TOKEN');
|
|
1041
1026
|
const webhookSecret = getConfigSecret('TELEGRAM_WEBHOOK_SECRET');
|
|
1042
1027
|
|
|
1028
|
+
const appUrl = getConfig('APP_URL');
|
|
1029
|
+
const defaultWebhookUrl = appUrl
|
|
1030
|
+
? `${appUrl.replace(/\/$/, '')}/api/telegram/webhook`
|
|
1031
|
+
: '';
|
|
1032
|
+
const webhookUrlOverride = getConfigValue('TELEGRAM_WEBHOOK_URL') || null;
|
|
1033
|
+
|
|
1043
1034
|
let botInfo = null;
|
|
1044
1035
|
let webhookInfo = null;
|
|
1045
1036
|
if (botToken) {
|
|
@@ -1065,6 +1056,8 @@ export async function getTelegramStatus() {
|
|
|
1065
1056
|
botTokenSet: !!botToken,
|
|
1066
1057
|
webhookSecretSet: !!webhookSecret,
|
|
1067
1058
|
webhookInfo,
|
|
1059
|
+
defaultWebhookUrl,
|
|
1060
|
+
webhookUrlOverride,
|
|
1068
1061
|
};
|
|
1069
1062
|
} catch (err) {
|
|
1070
1063
|
console.error('Failed to get Telegram status:', err);
|
|
@@ -1094,31 +1087,64 @@ export async function validateTelegramToken(token) {
|
|
|
1094
1087
|
/**
|
|
1095
1088
|
* Register the Telegram webhook with the currently saved bot token.
|
|
1096
1089
|
* Generates a fresh webhook secret, saves it, and calls Telegram's setWebhook.
|
|
1097
|
-
*
|
|
1098
|
-
|
|
1099
|
-
|
|
1090
|
+
*
|
|
1091
|
+
* URL resolution:
|
|
1092
|
+
* - explicit `webhookUrl` arg (persisted as override if it differs from default)
|
|
1093
|
+
* - stored `TELEGRAM_WEBHOOK_URL` override
|
|
1094
|
+
* - `${APP_URL}/api/telegram/webhook` default
|
|
1095
|
+
*
|
|
1096
|
+
* Pass an empty string for `webhookUrl` to clear the override and fall back
|
|
1097
|
+
* to the APP_URL-derived default.
|
|
1098
|
+
*/
|
|
1099
|
+
export async function registerTelegramWebhook(webhookUrl) {
|
|
1100
1100
|
const user = await requireAdmin();
|
|
1101
1101
|
try {
|
|
1102
|
-
const { getConfigSecret, setConfigSecret } =
|
|
1103
|
-
|
|
1102
|
+
const { getConfigSecret, setConfigSecret, getConfigValue, setConfigValue, deleteConfigValue } =
|
|
1103
|
+
await import('../db/config.js');
|
|
1104
|
+
const { getConfig } = await import('../config.js');
|
|
1104
1105
|
const { setTelegramWebhook, generateWebhookSecret } = await import('../tools/telegram.js');
|
|
1105
1106
|
|
|
1106
1107
|
const botToken = getConfigSecret('TELEGRAM_BOT_TOKEN');
|
|
1107
1108
|
if (!botToken) return { error: 'Bot token must be set first' };
|
|
1108
1109
|
|
|
1109
1110
|
const appUrl = getConfig('APP_URL');
|
|
1110
|
-
|
|
1111
|
+
const defaultUrl = appUrl ? `${appUrl.replace(/\/$/, '')}/api/telegram/webhook` : '';
|
|
1112
|
+
|
|
1113
|
+
// Resolve the URL to register.
|
|
1114
|
+
let urlToRegister;
|
|
1115
|
+
if (typeof webhookUrl === 'string') {
|
|
1116
|
+
const trimmed = webhookUrl.trim();
|
|
1117
|
+
if (!trimmed || trimmed === defaultUrl) {
|
|
1118
|
+
deleteConfigValue('TELEGRAM_WEBHOOK_URL');
|
|
1119
|
+
urlToRegister = trimmed || defaultUrl;
|
|
1120
|
+
} else {
|
|
1121
|
+
setConfigValue('TELEGRAM_WEBHOOK_URL', trimmed, user.id);
|
|
1122
|
+
urlToRegister = trimmed;
|
|
1123
|
+
}
|
|
1124
|
+
} else {
|
|
1125
|
+
urlToRegister = getConfigValue('TELEGRAM_WEBHOOK_URL') || defaultUrl;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
if (!urlToRegister) {
|
|
1129
|
+
return { error: 'Webhook URL is required (set APP_URL or supply a URL)' };
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
const parsed = new URL(urlToRegister);
|
|
1133
|
+
if (parsed.protocol !== 'https:') {
|
|
1134
|
+
return { error: 'Webhook URL must use https://' };
|
|
1135
|
+
}
|
|
1136
|
+
} catch {
|
|
1137
|
+
return { error: 'Webhook URL is not a valid URL' };
|
|
1138
|
+
}
|
|
1111
1139
|
|
|
1112
|
-
const webhookUrl = `${appUrl.replace(/\/$/, '')}/api/telegram/webhook`;
|
|
1113
1140
|
const secret = generateWebhookSecret();
|
|
1114
1141
|
setConfigSecret('TELEGRAM_WEBHOOK_SECRET', secret, user.id);
|
|
1115
|
-
invalidateConfigCache();
|
|
1116
1142
|
|
|
1117
|
-
const result = await setTelegramWebhook(botToken,
|
|
1143
|
+
const result = await setTelegramWebhook(botToken, urlToRegister, secret);
|
|
1118
1144
|
if (!result.ok) {
|
|
1119
1145
|
return { error: result.description || 'Failed to register webhook' };
|
|
1120
1146
|
}
|
|
1121
|
-
return { success: true, webhookUrl };
|
|
1147
|
+
return { success: true, webhookUrl: urlToRegister };
|
|
1122
1148
|
} catch (err) {
|
|
1123
1149
|
console.error('Failed to register Telegram webhook:', err);
|
|
1124
1150
|
return { error: err.message };
|
|
@@ -1180,13 +1206,11 @@ export async function updateProviderCredential(credentialKey, value) {
|
|
|
1180
1206
|
return { error: 'Invalid credential key' };
|
|
1181
1207
|
}
|
|
1182
1208
|
const { setConfigSecret, deleteConfigSecret } = await import('../db/config.js');
|
|
1183
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
1184
1209
|
if (value) {
|
|
1185
1210
|
setConfigSecret(credentialKey, value, user.id);
|
|
1186
1211
|
} else {
|
|
1187
1212
|
deleteConfigSecret(credentialKey);
|
|
1188
1213
|
}
|
|
1189
|
-
invalidateConfigCache();
|
|
1190
1214
|
await syncLitellmConfig();
|
|
1191
1215
|
return { success: true };
|
|
1192
1216
|
} catch (err) {
|
|
@@ -1257,11 +1281,9 @@ export async function addCustomProvider(config) {
|
|
|
1257
1281
|
const user = await requireAdmin();
|
|
1258
1282
|
try {
|
|
1259
1283
|
const { setCustomProvider } = await import('../db/config.js');
|
|
1260
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
1261
1284
|
// Generate slug from name
|
|
1262
1285
|
const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
1263
1286
|
setCustomProvider(slug, config, user.id);
|
|
1264
|
-
invalidateConfigCache();
|
|
1265
1287
|
await syncLitellmConfig();
|
|
1266
1288
|
return { success: true, key: slug };
|
|
1267
1289
|
} catch (err) {
|
|
@@ -1277,9 +1299,7 @@ export async function updateCustomProvider(key, config) {
|
|
|
1277
1299
|
const user = await requireAdmin();
|
|
1278
1300
|
try {
|
|
1279
1301
|
const { setCustomProvider } = await import('../db/config.js');
|
|
1280
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
1281
1302
|
setCustomProvider(key, config, user.id);
|
|
1282
|
-
invalidateConfigCache();
|
|
1283
1303
|
await syncLitellmConfig();
|
|
1284
1304
|
return { success: true };
|
|
1285
1305
|
} catch (err) {
|
|
@@ -1295,9 +1315,7 @@ export async function removeCustomProvider(key) {
|
|
|
1295
1315
|
const user = await requireAdmin();
|
|
1296
1316
|
try {
|
|
1297
1317
|
const { deleteCustomProvider } = await import('../db/config.js');
|
|
1298
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
1299
1318
|
deleteCustomProvider(key);
|
|
1300
|
-
invalidateConfigCache();
|
|
1301
1319
|
await syncLitellmConfig();
|
|
1302
1320
|
return { success: true };
|
|
1303
1321
|
} catch (err) {
|
|
@@ -1313,11 +1331,9 @@ export async function setActiveLlm(provider, model, maxTokens) {
|
|
|
1313
1331
|
const user = await requireAdmin();
|
|
1314
1332
|
try {
|
|
1315
1333
|
const { setConfigValue } = await import('../db/config.js');
|
|
1316
|
-
const { invalidateConfigCache } = await import('../config.js');
|
|
1317
1334
|
setConfigValue('LLM_PROVIDER', provider, user.id);
|
|
1318
1335
|
setConfigValue('LLM_MODEL', model, user.id);
|
|
1319
1336
|
if (maxTokens) setConfigValue('LLM_MAX_TOKENS', maxTokens, user.id);
|
|
1320
|
-
invalidateConfigCache();
|
|
1321
1337
|
return { success: true };
|
|
1322
1338
|
} catch (err) {
|
|
1323
1339
|
console.error('Failed to set active LLM:', err);
|
|
@@ -273,6 +273,8 @@ function ApiKeysTelegramPage() {
|
|
|
273
273
|
const [tokenError, setTokenError] = useState(null);
|
|
274
274
|
const [webhookSaving, setWebhookSaving] = useState(false);
|
|
275
275
|
const [webhookError, setWebhookError] = useState(null);
|
|
276
|
+
const [webhookEditing, setWebhookEditing] = useState(false);
|
|
277
|
+
const [webhookUrlInput, setWebhookUrlInput] = useState("");
|
|
276
278
|
const loadStatus = async () => {
|
|
277
279
|
try {
|
|
278
280
|
const result = await getTelegramStatus();
|
|
@@ -284,6 +286,12 @@ function ApiKeysTelegramPage() {
|
|
|
284
286
|
useEffect(() => {
|
|
285
287
|
loadStatus();
|
|
286
288
|
}, []);
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (!status || webhookEditing) return;
|
|
291
|
+
setWebhookUrlInput(
|
|
292
|
+
status.webhookUrlOverride || status.webhookInfo?.url || status.defaultWebhookUrl || ""
|
|
293
|
+
);
|
|
294
|
+
}, [status, webhookEditing]);
|
|
287
295
|
if (loading) {
|
|
288
296
|
return /* @__PURE__ */ jsx("div", { className: "h-48 animate-pulse rounded-md bg-border/50" });
|
|
289
297
|
}
|
|
@@ -318,11 +326,33 @@ function ApiKeysTelegramPage() {
|
|
|
318
326
|
const handleRegisterWebhook = async () => {
|
|
319
327
|
setWebhookSaving(true);
|
|
320
328
|
setWebhookError(null);
|
|
321
|
-
const
|
|
322
|
-
|
|
329
|
+
const url = webhookEditing ? webhookUrlInput.trim() : void 0;
|
|
330
|
+
const result = await registerTelegramWebhook(url);
|
|
331
|
+
if (result?.error) {
|
|
332
|
+
setWebhookError(result.error);
|
|
333
|
+
setWebhookSaving(false);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
setWebhookEditing(false);
|
|
337
|
+
setWebhookUrlInput("");
|
|
323
338
|
await loadStatus();
|
|
324
339
|
setWebhookSaving(false);
|
|
325
340
|
};
|
|
341
|
+
const startWebhookEdit = () => {
|
|
342
|
+
setWebhookError(null);
|
|
343
|
+
setWebhookUrlInput(
|
|
344
|
+
status.webhookUrlOverride || status.webhookInfo?.url || status.defaultWebhookUrl || ""
|
|
345
|
+
);
|
|
346
|
+
setWebhookEditing(true);
|
|
347
|
+
};
|
|
348
|
+
const cancelWebhookEdit = () => {
|
|
349
|
+
setWebhookEditing(false);
|
|
350
|
+
setWebhookUrlInput("");
|
|
351
|
+
setWebhookError(null);
|
|
352
|
+
};
|
|
353
|
+
const resetWebhookToDefault = () => {
|
|
354
|
+
setWebhookUrlInput(status.defaultWebhookUrl || "");
|
|
355
|
+
};
|
|
326
356
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
327
357
|
/* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
|
|
328
358
|
/* @__PURE__ */ jsx("h2", { className: "text-base font-medium", children: "Telegram" }),
|
|
@@ -417,33 +447,75 @@ function ApiKeysTelegramPage() {
|
|
|
417
447
|
/* @__PURE__ */ jsx("div", { className: `rounded-lg border bg-card p-4 ${!step1Done ? "opacity-50 pointer-events-none" : ""}`, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
418
448
|
/* @__PURE__ */ jsx(StepIndicator, { n: 2, state: step2Done ? "done" : step1Done ? "active" : "pending" }),
|
|
419
449
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
420
|
-
/* @__PURE__ */
|
|
421
|
-
/* @__PURE__ */
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
/* @__PURE__ */
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
"
|
|
432
|
-
|
|
450
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
451
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
452
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Webhook" }),
|
|
453
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Register a public URL with Telegram so it can deliver messages to your bot." }),
|
|
454
|
+
step2Done && !webhookEditing && /* @__PURE__ */ jsxs("div", { className: "mt-2 text-xs text-muted-foreground truncate", children: [
|
|
455
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: status.webhookInfo.url }),
|
|
456
|
+
status.webhookInfo.pendingUpdates > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-yellow-500", children: [
|
|
457
|
+
"(",
|
|
458
|
+
status.webhookInfo.pendingUpdates,
|
|
459
|
+
" pending)"
|
|
460
|
+
] }),
|
|
461
|
+
status.webhookInfo.lastErrorMessage && /* @__PURE__ */ jsxs("div", { className: "mt-1 text-destructive", children: [
|
|
462
|
+
"Last error: ",
|
|
463
|
+
status.webhookInfo.lastErrorMessage
|
|
464
|
+
] })
|
|
433
465
|
] })
|
|
434
|
-
] })
|
|
435
|
-
|
|
436
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
|
|
437
|
-
webhookError && /* @__PURE__ */ jsx("div", { className: "text-xs text-destructive mb-2", children: webhookError }),
|
|
438
|
-
/* @__PURE__ */ jsx(
|
|
466
|
+
] }),
|
|
467
|
+
step2Done && !webhookEditing && /* @__PURE__ */ jsx(
|
|
439
468
|
"button",
|
|
440
469
|
{
|
|
441
|
-
onClick:
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
children: webhookSaving ? "Registering..." : step2Done ? "Re-register Webhook" : "Register Webhook"
|
|
470
|
+
onClick: startWebhookEdit,
|
|
471
|
+
className: "shrink-0 text-xs text-muted-foreground hover:text-foreground underline transition-colors",
|
|
472
|
+
children: "Change"
|
|
445
473
|
}
|
|
446
474
|
)
|
|
475
|
+
] }),
|
|
476
|
+
(!step2Done || webhookEditing) && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-col gap-2", children: [
|
|
477
|
+
/* @__PURE__ */ jsx(
|
|
478
|
+
"input",
|
|
479
|
+
{
|
|
480
|
+
type: "text",
|
|
481
|
+
value: webhookUrlInput,
|
|
482
|
+
onChange: (e) => {
|
|
483
|
+
if (!webhookEditing) setWebhookEditing(true);
|
|
484
|
+
setWebhookUrlInput(e.target.value);
|
|
485
|
+
},
|
|
486
|
+
placeholder: "https://example.com/api/telegram/webhook",
|
|
487
|
+
spellCheck: false,
|
|
488
|
+
className: "rounded-md border border-border bg-background px-3 py-1.5 text-sm font-mono focus:outline-none focus:ring-1 focus:ring-foreground"
|
|
489
|
+
}
|
|
490
|
+
),
|
|
491
|
+
webhookError && /* @__PURE__ */ jsx("div", { className: "text-xs text-destructive", children: webhookError }),
|
|
492
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
493
|
+
/* @__PURE__ */ jsx(
|
|
494
|
+
"button",
|
|
495
|
+
{
|
|
496
|
+
onClick: handleRegisterWebhook,
|
|
497
|
+
disabled: !step1Done || webhookSaving || !webhookUrlInput.trim(),
|
|
498
|
+
className: "rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors",
|
|
499
|
+
children: webhookSaving ? "Registering..." : step2Done ? "Re-register Webhook" : "Register Webhook"
|
|
500
|
+
}
|
|
501
|
+
),
|
|
502
|
+
webhookEditing && step2Done && /* @__PURE__ */ jsx(
|
|
503
|
+
"button",
|
|
504
|
+
{
|
|
505
|
+
onClick: cancelWebhookEdit,
|
|
506
|
+
className: "rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
|
|
507
|
+
children: "Cancel"
|
|
508
|
+
}
|
|
509
|
+
),
|
|
510
|
+
status.defaultWebhookUrl && webhookUrlInput.trim() !== status.defaultWebhookUrl && /* @__PURE__ */ jsx(
|
|
511
|
+
"button",
|
|
512
|
+
{
|
|
513
|
+
onClick: resetWebhookToDefault,
|
|
514
|
+
className: "ml-auto text-xs text-muted-foreground hover:text-foreground underline transition-colors",
|
|
515
|
+
children: "Reset to default"
|
|
516
|
+
}
|
|
517
|
+
)
|
|
518
|
+
] })
|
|
447
519
|
] })
|
|
448
520
|
] })
|
|
449
521
|
] }) })
|
|
@@ -329,6 +329,8 @@ export function ApiKeysTelegramPage() {
|
|
|
329
329
|
// Step 2 — webhook
|
|
330
330
|
const [webhookSaving, setWebhookSaving] = useState(false);
|
|
331
331
|
const [webhookError, setWebhookError] = useState(null);
|
|
332
|
+
const [webhookEditing, setWebhookEditing] = useState(false);
|
|
333
|
+
const [webhookUrlInput, setWebhookUrlInput] = useState('');
|
|
332
334
|
|
|
333
335
|
const loadStatus = async () => {
|
|
334
336
|
try {
|
|
@@ -343,6 +345,16 @@ export function ApiKeysTelegramPage() {
|
|
|
343
345
|
loadStatus();
|
|
344
346
|
}, []);
|
|
345
347
|
|
|
348
|
+
// Keep the webhook URL input in sync with the saved/effective URL when not
|
|
349
|
+
// actively editing — covers the "unregistered" case where the field needs
|
|
350
|
+
// to be prefilled with the default so the user can just hit Register.
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
if (!status || webhookEditing) return;
|
|
353
|
+
setWebhookUrlInput(
|
|
354
|
+
status.webhookUrlOverride || status.webhookInfo?.url || status.defaultWebhookUrl || ''
|
|
355
|
+
);
|
|
356
|
+
}, [status, webhookEditing]);
|
|
357
|
+
|
|
346
358
|
if (loading) {
|
|
347
359
|
return <div className="h-48 animate-pulse rounded-md bg-border/50" />;
|
|
348
360
|
}
|
|
@@ -383,12 +395,37 @@ export function ApiKeysTelegramPage() {
|
|
|
383
395
|
const handleRegisterWebhook = async () => {
|
|
384
396
|
setWebhookSaving(true);
|
|
385
397
|
setWebhookError(null);
|
|
386
|
-
const
|
|
387
|
-
|
|
398
|
+
const url = webhookEditing ? webhookUrlInput.trim() : undefined;
|
|
399
|
+
const result = await registerTelegramWebhook(url);
|
|
400
|
+
if (result?.error) {
|
|
401
|
+
setWebhookError(result.error);
|
|
402
|
+
setWebhookSaving(false);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
setWebhookEditing(false);
|
|
406
|
+
setWebhookUrlInput('');
|
|
388
407
|
await loadStatus();
|
|
389
408
|
setWebhookSaving(false);
|
|
390
409
|
};
|
|
391
410
|
|
|
411
|
+
const startWebhookEdit = () => {
|
|
412
|
+
setWebhookError(null);
|
|
413
|
+
setWebhookUrlInput(
|
|
414
|
+
status.webhookUrlOverride || status.webhookInfo?.url || status.defaultWebhookUrl || ''
|
|
415
|
+
);
|
|
416
|
+
setWebhookEditing(true);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const cancelWebhookEdit = () => {
|
|
420
|
+
setWebhookEditing(false);
|
|
421
|
+
setWebhookUrlInput('');
|
|
422
|
+
setWebhookError(null);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const resetWebhookToDefault = () => {
|
|
426
|
+
setWebhookUrlInput(status.defaultWebhookUrl || '');
|
|
427
|
+
};
|
|
428
|
+
|
|
392
429
|
return (
|
|
393
430
|
<div>
|
|
394
431
|
<div className="mb-4">
|
|
@@ -488,9 +525,9 @@ export function ApiKeysTelegramPage() {
|
|
|
488
525
|
<div className="min-w-0">
|
|
489
526
|
<h3 className="text-sm font-medium">Webhook</h3>
|
|
490
527
|
<p className="text-xs text-muted-foreground mt-0.5">
|
|
491
|
-
Register
|
|
528
|
+
Register a public URL with Telegram so it can deliver messages to your bot.
|
|
492
529
|
</p>
|
|
493
|
-
{step2Done && (
|
|
530
|
+
{step2Done && !webhookEditing && (
|
|
494
531
|
<div className="mt-2 text-xs text-muted-foreground truncate">
|
|
495
532
|
<span className="font-mono">{status.webhookInfo.url}</span>
|
|
496
533
|
{status.webhookInfo.pendingUpdates > 0 && (
|
|
@@ -506,24 +543,62 @@ export function ApiKeysTelegramPage() {
|
|
|
506
543
|
</div>
|
|
507
544
|
)}
|
|
508
545
|
</div>
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
546
|
+
{step2Done && !webhookEditing && (
|
|
547
|
+
<button
|
|
548
|
+
onClick={startWebhookEdit}
|
|
549
|
+
className="shrink-0 text-xs text-muted-foreground hover:text-foreground underline transition-colors"
|
|
550
|
+
>
|
|
551
|
+
Change
|
|
552
|
+
</button>
|
|
514
553
|
)}
|
|
515
|
-
<button
|
|
516
|
-
onClick={handleRegisterWebhook}
|
|
517
|
-
disabled={!step1Done || webhookSaving}
|
|
518
|
-
className="rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors"
|
|
519
|
-
>
|
|
520
|
-
{webhookSaving
|
|
521
|
-
? 'Registering...'
|
|
522
|
-
: step2Done
|
|
523
|
-
? 'Re-register Webhook'
|
|
524
|
-
: 'Register Webhook'}
|
|
525
|
-
</button>
|
|
526
554
|
</div>
|
|
555
|
+
|
|
556
|
+
{(!step2Done || webhookEditing) && (
|
|
557
|
+
<div className="mt-3 flex flex-col gap-2">
|
|
558
|
+
<input
|
|
559
|
+
type="text"
|
|
560
|
+
value={webhookUrlInput}
|
|
561
|
+
onChange={(e) => {
|
|
562
|
+
if (!webhookEditing) setWebhookEditing(true);
|
|
563
|
+
setWebhookUrlInput(e.target.value);
|
|
564
|
+
}}
|
|
565
|
+
placeholder="https://example.com/api/telegram/webhook"
|
|
566
|
+
spellCheck={false}
|
|
567
|
+
className="rounded-md border border-border bg-background px-3 py-1.5 text-sm font-mono focus:outline-none focus:ring-1 focus:ring-foreground"
|
|
568
|
+
/>
|
|
569
|
+
{webhookError && <div className="text-xs text-destructive">{webhookError}</div>}
|
|
570
|
+
<div className="flex items-center gap-2">
|
|
571
|
+
<button
|
|
572
|
+
onClick={handleRegisterWebhook}
|
|
573
|
+
disabled={!step1Done || webhookSaving || !webhookUrlInput.trim()}
|
|
574
|
+
className="rounded-md bg-foreground text-background px-2.5 py-1.5 text-xs font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors"
|
|
575
|
+
>
|
|
576
|
+
{webhookSaving
|
|
577
|
+
? 'Registering...'
|
|
578
|
+
: step2Done
|
|
579
|
+
? 'Re-register Webhook'
|
|
580
|
+
: 'Register Webhook'}
|
|
581
|
+
</button>
|
|
582
|
+
{webhookEditing && step2Done && (
|
|
583
|
+
<button
|
|
584
|
+
onClick={cancelWebhookEdit}
|
|
585
|
+
className="rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
586
|
+
>
|
|
587
|
+
Cancel
|
|
588
|
+
</button>
|
|
589
|
+
)}
|
|
590
|
+
{status.defaultWebhookUrl &&
|
|
591
|
+
webhookUrlInput.trim() !== status.defaultWebhookUrl && (
|
|
592
|
+
<button
|
|
593
|
+
onClick={resetWebhookToDefault}
|
|
594
|
+
className="ml-auto text-xs text-muted-foreground hover:text-foreground underline transition-colors"
|
|
595
|
+
>
|
|
596
|
+
Reset to default
|
|
597
|
+
</button>
|
|
598
|
+
)}
|
|
599
|
+
</div>
|
|
600
|
+
</div>
|
|
601
|
+
)}
|
|
527
602
|
</div>
|
|
528
603
|
</div>
|
|
529
604
|
</div>
|
package/lib/config.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Config resolver. Reads from DB, falls back to defaults.
|
|
3
|
-
* In-memory cache, invalidated on config writes.
|
|
4
3
|
*
|
|
5
4
|
* Usage:
|
|
6
5
|
* import { getConfig } from '../config.js';
|
|
@@ -63,6 +62,7 @@ const CONFIG_KEYS = new Set([
|
|
|
63
62
|
'CODE_MODE_GIT_ACTION',
|
|
64
63
|
'AGENT_MODE_AUTO_RUN',
|
|
65
64
|
'CODE_MODE_AUTO_RUN',
|
|
65
|
+
'TELEGRAM_WEBHOOK_URL',
|
|
66
66
|
]);
|
|
67
67
|
|
|
68
68
|
// Default values
|
|
@@ -87,23 +87,12 @@ const DEFAULTS = {
|
|
|
87
87
|
CODE_MODE_AUTO_RUN: 'false',
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
// In-memory cache on globalThis to survive Next.js webpack chunk duplication.
|
|
91
|
-
// Server actions and route handlers may be bundled into separate chunks, each
|
|
92
|
-
// with their own copy of module-level variables. globalThis is shared across all chunks.
|
|
93
|
-
const _cache = (globalThis.__popebotConfigCache ??= new Map());
|
|
94
|
-
|
|
95
90
|
/**
|
|
96
91
|
* Get a config value. Resolution: DB → default.
|
|
97
92
|
* @param {string} key
|
|
98
93
|
* @returns {string|undefined}
|
|
99
94
|
*/
|
|
100
95
|
export function getConfig(key) {
|
|
101
|
-
// Check cache first
|
|
102
|
-
const cached = _cache.get(key);
|
|
103
|
-
if (cached) {
|
|
104
|
-
return cached.value;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
96
|
let value;
|
|
108
97
|
|
|
109
98
|
// OAuth tokens: multi-token support with LRU rotation
|
|
@@ -154,14 +143,5 @@ export function getConfig(key) {
|
|
|
154
143
|
value = getDefaultModel(provider);
|
|
155
144
|
}
|
|
156
145
|
|
|
157
|
-
// Cache and return
|
|
158
|
-
_cache.set(key, { value });
|
|
159
146
|
return value;
|
|
160
147
|
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Invalidate the config cache. Call after any config write.
|
|
164
|
-
*/
|
|
165
|
-
export function invalidateConfigCache() {
|
|
166
|
-
_cache.clear();
|
|
167
|
-
}
|