@pixelbyte-software/pixcode 1.53.4 → 1.53.5
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/dist/assets/{index-DlwfTw9d.js → index-CyeODv14.js} +155 -155
- package/dist/index.html +1 -1
- package/dist-server/server/database/db.js +28 -0
- package/dist-server/server/database/db.js.map +1 -1
- package/dist-server/server/routes/telegram.js +16 -2
- package/dist-server/server/routes/telegram.js.map +1 -1
- package/dist-server/server/services/telegram/bot.js +9 -4
- package/dist-server/server/services/telegram/bot.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +390 -67
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/telegram-gateway.js +220 -0
- package/dist-server/server/services/telegram/telegram-gateway.js.map +1 -0
- package/dist-server/server/services/telegram/telegram-http-client.js +10 -8
- package/dist-server/server/services/telegram/telegram-http-client.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +18 -0
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/telegram-control.mjs +10 -1
- package/server/database/db.js +28 -0
- package/server/routes/telegram.js +25 -2
- package/server/services/telegram/bot.js +8 -4
- package/server/services/telegram/control-center.js +410 -66
- package/server/services/telegram/telegram-gateway.js +250 -0
- package/server/services/telegram/telegram-http-client.js +7 -5
- package/server/services/telegram/translations.js +18 -0
|
@@ -4,9 +4,24 @@ import { apiKeysDb, telegramLinksDb } from '../../database/db.js';
|
|
|
4
4
|
import { getProjects } from '../../projects.js';
|
|
5
5
|
import { getStaticProviderModels } from '../model-registry.js';
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
TELEGRAM_CONTROL_SCOPES,
|
|
9
|
+
TELEGRAM_PROVIDERS,
|
|
10
|
+
buildTelegramAgentPrompt,
|
|
11
|
+
classifyTelegramIntent,
|
|
12
|
+
clearTelegramConfirmation,
|
|
13
|
+
consumeTelegramConfirmation,
|
|
14
|
+
createTelegramConfirmation,
|
|
15
|
+
enqueueTelegramJob,
|
|
16
|
+
resolveTelegramModel,
|
|
17
|
+
resolveTelegramProvider,
|
|
18
|
+
retryWithBackoff,
|
|
19
|
+
runTelegramTool,
|
|
20
|
+
splitTelegramText,
|
|
21
|
+
} from './telegram-gateway.js';
|
|
7
22
|
import { SUPPORTED_LANGUAGES, t } from './translations.js';
|
|
8
23
|
|
|
9
|
-
const PROVIDERS =
|
|
24
|
+
const PROVIDERS = TELEGRAM_PROVIDERS;
|
|
10
25
|
const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
|
|
11
26
|
const CALLBACK_TTL_MS = 10 * 60 * 1000;
|
|
12
27
|
const MAX_CALLBACK_ACTIONS = 1000;
|
|
@@ -130,7 +145,11 @@ function readAction(data) {
|
|
|
130
145
|
callbackActions.delete(id);
|
|
131
146
|
return null;
|
|
132
147
|
}
|
|
133
|
-
return entry;
|
|
148
|
+
return { id, ...entry };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function forgetAction(id) {
|
|
152
|
+
if (id) callbackActions.delete(id);
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
function button(text, action, payload = {}) {
|
|
@@ -150,27 +169,44 @@ async function send(bot, chatId, text, options = {}) {
|
|
|
150
169
|
disable_web_page_preview: true,
|
|
151
170
|
...telegramOptions,
|
|
152
171
|
};
|
|
153
|
-
const
|
|
172
|
+
const chunks = splitTelegramText(text, MAX_TELEGRAM_TEXT);
|
|
173
|
+
const [firstChunk = ''] = chunks;
|
|
174
|
+
let startIndex = 0;
|
|
154
175
|
if (editMessageId && typeof bot.editMessageText === 'function') {
|
|
155
176
|
try {
|
|
156
|
-
|
|
177
|
+
const edited = await bot.editMessageText(firstChunk, {
|
|
157
178
|
chat_id: chatId,
|
|
158
179
|
message_id: editMessageId,
|
|
159
180
|
...extra,
|
|
160
181
|
});
|
|
182
|
+
startIndex = 1;
|
|
183
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
184
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
185
|
+
}
|
|
186
|
+
return edited;
|
|
161
187
|
} catch (err) {
|
|
162
188
|
const description = err?.response?.body?.description || err?.message || '';
|
|
163
|
-
if (/message is not modified/i.test(description))
|
|
189
|
+
if (/message is not modified/i.test(description)) {
|
|
190
|
+
startIndex = 1;
|
|
191
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
192
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
164
196
|
console.warn('[telegram-control] editMessageText failed:', description || err);
|
|
165
197
|
}
|
|
166
198
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
199
|
+
let result = null;
|
|
200
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
201
|
+
try {
|
|
202
|
+
result = await bot.sendMessage(chatId, chunk, extra);
|
|
203
|
+
} catch {
|
|
204
|
+
const fallback = { ...extra };
|
|
205
|
+
delete fallback.parse_mode;
|
|
206
|
+
result = await bot.sendMessage(chatId, chunk, fallback);
|
|
207
|
+
}
|
|
173
208
|
}
|
|
209
|
+
return result;
|
|
174
210
|
}
|
|
175
211
|
|
|
176
212
|
function localApiBase() {
|
|
@@ -182,19 +218,35 @@ function getOrCreateTelegramApiKey(userId) {
|
|
|
182
218
|
const existing = apiKeysDb
|
|
183
219
|
.getApiKeys(userId)
|
|
184
220
|
.find((key) => key.key_name === 'Telegram Control' && Boolean(key.is_active));
|
|
185
|
-
if (existing?.api_key)
|
|
186
|
-
|
|
221
|
+
if (existing?.api_key) {
|
|
222
|
+
const existingScopes = apiKeysDb.normalizeScopes(existing.scopes || []);
|
|
223
|
+
const nextScopes = apiKeysDb.normalizeScopes([...existingScopes, ...TELEGRAM_CONTROL_SCOPES]);
|
|
224
|
+
const hasAllScopes = TELEGRAM_CONTROL_SCOPES.every((scope) => existingScopes.includes(scope));
|
|
225
|
+
if (!hasAllScopes || nextScopes.length !== existingScopes.length) {
|
|
226
|
+
apiKeysDb.updateApiKeyScopes(userId, existing.id, nextScopes);
|
|
227
|
+
}
|
|
228
|
+
return existing.api_key;
|
|
229
|
+
}
|
|
230
|
+
return apiKeysDb.createApiKey(userId, 'Telegram Control', TELEGRAM_CONTROL_SCOPES).apiKey;
|
|
187
231
|
}
|
|
188
232
|
|
|
189
233
|
async function localApi(userId, path, { method = 'GET', body } = {}) {
|
|
190
234
|
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
191
|
-
const response = await
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
235
|
+
const response = await retryWithBackoff(async () => {
|
|
236
|
+
const res = await fetch(`${localApiBase()}${path}`, {
|
|
237
|
+
method,
|
|
238
|
+
headers: {
|
|
239
|
+
'Content-Type': 'application/json',
|
|
240
|
+
'X-API-Key': apiKey,
|
|
241
|
+
},
|
|
242
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
243
|
+
});
|
|
244
|
+
if ([408, 429, 500, 502, 503, 504].includes(res.status)) {
|
|
245
|
+
const error = new Error(`HTTP ${res.status}`);
|
|
246
|
+
error.status = res.status;
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
return res;
|
|
198
250
|
});
|
|
199
251
|
const data = await response.json().catch(() => ({}));
|
|
200
252
|
if (!response.ok) {
|
|
@@ -487,6 +539,39 @@ function extractAssistantText(response) {
|
|
|
487
539
|
return chunks.join('\n\n').trim();
|
|
488
540
|
}
|
|
489
541
|
|
|
542
|
+
function confirmationLabel(lang, action, payload = {}) {
|
|
543
|
+
if (action === 'install_provider') {
|
|
544
|
+
return t(lang, 'control.confirmInstall', { provider: payload.provider || '' });
|
|
545
|
+
}
|
|
546
|
+
if (action === 'run_cancel') {
|
|
547
|
+
return t(lang, 'control.confirmCancelRun', { runId: payload.runId || '' });
|
|
548
|
+
}
|
|
549
|
+
return t(lang, 'control.confirmationRequired');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function requestConfirmation({ bot, chatId, link, action, payload = {}, editMessageId }) {
|
|
553
|
+
const lang = languageFor(link);
|
|
554
|
+
const pending = createTelegramConfirmation(link.user_id, action, payload);
|
|
555
|
+
await send(bot, chatId, confirmationLabel(lang, action, payload), {
|
|
556
|
+
editMessageId,
|
|
557
|
+
reply_markup: {
|
|
558
|
+
inline_keyboard: [[
|
|
559
|
+
button(t(lang, 'control.button.confirm'), 'confirm_action', { id: pending.id }),
|
|
560
|
+
button(t(lang, 'control.button.cancel'), 'cancel_confirmation', { id: pending.id }),
|
|
561
|
+
]],
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
return { ok: false, requiresConfirmation: true };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function sendToolFailure({ bot, chatId, link, result, editMessageId }) {
|
|
568
|
+
const lang = languageFor(link);
|
|
569
|
+
const message = result?.message === 'REMOTE_CONTROL_DISABLED'
|
|
570
|
+
? t(lang, 'control.disabled')
|
|
571
|
+
: (result?.message || t(lang, 'error.generic'));
|
|
572
|
+
await send(bot, chatId, message, { editMessageId });
|
|
573
|
+
}
|
|
574
|
+
|
|
490
575
|
async function runAgent({ bot, chatId, link, prompt }) {
|
|
491
576
|
const lang = languageFor(link);
|
|
492
577
|
const state = getState(link.user_id);
|
|
@@ -500,28 +585,41 @@ async function runAgent({ bot, chatId, link, prompt }) {
|
|
|
500
585
|
return;
|
|
501
586
|
}
|
|
502
587
|
|
|
588
|
+
const provider = resolveTelegramProvider(state);
|
|
589
|
+
const model = resolveTelegramModel(state);
|
|
503
590
|
if (state.progressMode !== 'final') {
|
|
504
591
|
await send(bot, chatId, t(lang, 'control.agentStarted', {
|
|
505
|
-
provider
|
|
592
|
+
provider,
|
|
506
593
|
project: state.selectedProjectName || state.selectedProjectPath,
|
|
507
594
|
}));
|
|
508
595
|
}
|
|
509
596
|
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
597
|
+
const result = await runTelegramTool({
|
|
598
|
+
userId: link.user_id,
|
|
599
|
+
action: 'run_agent',
|
|
600
|
+
retries: 0,
|
|
601
|
+
execute: () => localApi(link.user_id, '/api/agent', {
|
|
602
|
+
method: 'POST',
|
|
603
|
+
body: {
|
|
604
|
+
projectPath: state.selectedProjectPath,
|
|
605
|
+
provider,
|
|
606
|
+
model: model || undefined,
|
|
607
|
+
message: buildTelegramAgentPrompt(prompt, state),
|
|
608
|
+
cleanup: false,
|
|
609
|
+
stream: false,
|
|
610
|
+
},
|
|
611
|
+
}),
|
|
520
612
|
});
|
|
613
|
+
if (!result.ok) {
|
|
614
|
+
await sendToolFailure({ bot, chatId, link, result });
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const response = result.data;
|
|
521
619
|
const assistantText = extractAssistantText(response);
|
|
522
620
|
const statusLine = response.success === false
|
|
523
621
|
? t(lang, 'control.agentFailed', { error: response.error || 'provider returned no answer' })
|
|
524
|
-
: t(lang, 'control.agentDone', { provider
|
|
622
|
+
: t(lang, 'control.agentDone', { provider, session: response.sessionId ? ` (${response.sessionId})` : '' });
|
|
525
623
|
await send(bot, chatId, `${statusLine}\n\n${assistantText || response.error || t(lang, 'control.noAssistantText')}`);
|
|
526
624
|
}
|
|
527
625
|
|
|
@@ -529,6 +627,10 @@ export async function runWorkflow({ bot, chatId, link, input }) {
|
|
|
529
627
|
const lang = languageFor(link);
|
|
530
628
|
const state = getState(link.user_id);
|
|
531
629
|
const workflowId = state.selectedWorkflowId;
|
|
630
|
+
if (!state.remoteControlEnabled) {
|
|
631
|
+
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
532
634
|
if (!workflowId) {
|
|
533
635
|
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'));
|
|
534
636
|
await showWorkflowMenu({ bot, chatId, link });
|
|
@@ -540,27 +642,178 @@ export async function runWorkflow({ bot, chatId, link, input }) {
|
|
|
540
642
|
return;
|
|
541
643
|
}
|
|
542
644
|
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
645
|
+
const provider = resolveTelegramProvider(state);
|
|
646
|
+
const model = resolveTelegramModel(state);
|
|
647
|
+
const result = await runTelegramTool({
|
|
648
|
+
userId: link.user_id,
|
|
649
|
+
action: 'run_workflow',
|
|
650
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/${workflowId}/runs`, {
|
|
651
|
+
method: 'POST',
|
|
652
|
+
body: {
|
|
653
|
+
input,
|
|
654
|
+
metadata: {
|
|
655
|
+
projectId: state.selectedProjectName,
|
|
656
|
+
projectName: state.selectedProjectName,
|
|
657
|
+
projectPath: state.selectedProjectPath,
|
|
658
|
+
workspaceTarget: 'selected_project',
|
|
659
|
+
telegram: true,
|
|
660
|
+
preferredProvider: provider,
|
|
661
|
+
preferredModel: model,
|
|
662
|
+
},
|
|
555
663
|
},
|
|
556
|
-
},
|
|
664
|
+
}),
|
|
557
665
|
});
|
|
666
|
+
if (!result.ok) {
|
|
667
|
+
await sendToolFailure({ bot, chatId, link, result });
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const run = result.data;
|
|
558
671
|
await send(bot, chatId, t(lang, 'control.workflowStarted', { runId: run.id, workflowId }));
|
|
559
672
|
monitorWorkflowRun({ bot, chatId, link, runId: run.id }).catch((error) => {
|
|
560
673
|
console.warn('[telegram-control] workflow monitor failed:', error?.message || error);
|
|
561
674
|
});
|
|
562
675
|
}
|
|
563
676
|
|
|
677
|
+
async function cancelRun({ bot, chatId, link, runId, editMessageId, confirmed = false }) {
|
|
678
|
+
const lang = languageFor(link);
|
|
679
|
+
if (!runId) {
|
|
680
|
+
await send(bot, chatId, t(lang, 'control.cancelUsage'), { editMessageId });
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const state = getState(link.user_id);
|
|
684
|
+
if (state.remoteControlEnabled === false) {
|
|
685
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId });
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (state.confirmationPolicy === 'strict' && !confirmed) {
|
|
689
|
+
await requestConfirmation({
|
|
690
|
+
bot,
|
|
691
|
+
chatId,
|
|
692
|
+
link,
|
|
693
|
+
action: 'run_cancel',
|
|
694
|
+
payload: { runId },
|
|
695
|
+
editMessageId,
|
|
696
|
+
});
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const result = await runTelegramTool({
|
|
701
|
+
userId: link.user_id,
|
|
702
|
+
action: 'run_cancel',
|
|
703
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
704
|
+
method: 'POST',
|
|
705
|
+
}),
|
|
706
|
+
});
|
|
707
|
+
if (!result.ok) {
|
|
708
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId });
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const run = result.data;
|
|
712
|
+
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }), { editMessageId });
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
async function executeConfirmedAction({ bot, chatId, link, confirmation, editMessageId }) {
|
|
716
|
+
if (confirmation.action === 'install_provider') {
|
|
717
|
+
await startCliInstall({
|
|
718
|
+
bot,
|
|
719
|
+
chatId,
|
|
720
|
+
link,
|
|
721
|
+
provider: confirmation.payload?.provider,
|
|
722
|
+
editMessageId,
|
|
723
|
+
confirmed: true,
|
|
724
|
+
});
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
if (confirmation.action === 'run_cancel') {
|
|
728
|
+
await cancelRun({
|
|
729
|
+
bot,
|
|
730
|
+
chatId,
|
|
731
|
+
link,
|
|
732
|
+
runId: confirmation.payload?.runId,
|
|
733
|
+
editMessageId,
|
|
734
|
+
confirmed: true,
|
|
735
|
+
});
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function findProjectByQuery(query) {
|
|
742
|
+
const projects = await listProjects();
|
|
743
|
+
const needle = String(query || '').trim().toLocaleLowerCase('tr');
|
|
744
|
+
if (!needle) return null;
|
|
745
|
+
return projects.find((project) => {
|
|
746
|
+
const candidates = [project.name, project.displayName, project.fullPath, project.path]
|
|
747
|
+
.filter(Boolean)
|
|
748
|
+
.map((value) => String(value).toLocaleLowerCase('tr'));
|
|
749
|
+
return candidates.some((candidate) => candidate === needle || candidate.includes(needle));
|
|
750
|
+
}) || null;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
async function handleRoutedIntent({ bot, chatId, link, text }) {
|
|
754
|
+
const state = getState(link.user_id);
|
|
755
|
+
const intent = classifyTelegramIntent(text, state);
|
|
756
|
+
if (!intent) return false;
|
|
757
|
+
|
|
758
|
+
if (intent.action === 'projects') {
|
|
759
|
+
await showProjectMenu({ bot, chatId, link });
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
if (intent.action === 'project_select_query') {
|
|
763
|
+
const project = await findProjectByQuery(intent.query);
|
|
764
|
+
const lang = languageFor(link);
|
|
765
|
+
if (!project) {
|
|
766
|
+
await send(bot, chatId, t(lang, 'control.projectNotFound', { query: intent.query }));
|
|
767
|
+
await showProjectMenu({ bot, chatId, link });
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
updateTelegramControlState(link.user_id, {
|
|
771
|
+
selectedProjectName: project.name,
|
|
772
|
+
selectedProjectPath: project.fullPath || project.path,
|
|
773
|
+
});
|
|
774
|
+
await showMainMenu({
|
|
775
|
+
bot,
|
|
776
|
+
chatId,
|
|
777
|
+
link,
|
|
778
|
+
notice: t(lang, 'control.projectSelected', {
|
|
779
|
+
project: project.displayName || project.name,
|
|
780
|
+
path: project.fullPath || project.path,
|
|
781
|
+
}),
|
|
782
|
+
});
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
if (intent.action === 'provider_menu') {
|
|
786
|
+
await showProviderMenu({ bot, chatId, link });
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
if (intent.action === 'provider_select') {
|
|
790
|
+
updateTelegramControlState(link.user_id, { selectedProvider: intent.provider, selectedModel: null });
|
|
791
|
+
await showModelMenu({ bot, chatId, link });
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
if (intent.action === 'model_menu') {
|
|
795
|
+
await showModelMenu({ bot, chatId, link });
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
if (intent.action === 'runs') {
|
|
799
|
+
await showRuns({ bot, chatId, link });
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
if (intent.action === 'approvals') {
|
|
803
|
+
await showApprovalQueue({ bot, chatId, link });
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
if (intent.action === 'workflows') {
|
|
807
|
+
await showWorkflowMenu({ bot, chatId, link });
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
if (intent.action === 'new_chat') {
|
|
811
|
+
await startNewChat({ bot, chatId, link });
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
|
|
564
817
|
async function fetchRun(userId, runId) {
|
|
565
818
|
return localApi(userId, `/api/orchestration/workflows/runs/${runId}`);
|
|
566
819
|
}
|
|
@@ -625,18 +878,49 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
625
878
|
}
|
|
626
879
|
}
|
|
627
880
|
|
|
628
|
-
export async function startCliInstall({ bot, chatId, link, provider }) {
|
|
881
|
+
export async function startCliInstall({ bot, chatId, link, provider, editMessageId, confirmed = false }) {
|
|
629
882
|
const lang = languageFor(link);
|
|
630
|
-
|
|
883
|
+
if (!PROVIDERS.includes(provider)) {
|
|
884
|
+
await send(bot, chatId, t(lang, 'control.providerAuthFallback'), { editMessageId });
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const state = getState(link.user_id);
|
|
888
|
+
if (state.remoteControlEnabled === false) {
|
|
889
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId });
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (state.confirmationPolicy === 'strict' && !confirmed) {
|
|
893
|
+
await requestConfirmation({
|
|
894
|
+
bot,
|
|
895
|
+
chatId,
|
|
896
|
+
link,
|
|
897
|
+
action: 'install_provider',
|
|
898
|
+
payload: { provider },
|
|
899
|
+
editMessageId,
|
|
900
|
+
});
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const result = await runTelegramTool({
|
|
905
|
+
userId: link.user_id,
|
|
906
|
+
action: 'install_provider',
|
|
907
|
+
execute: () => localApi(link.user_id, `/api/providers/${provider}/install`, { method: 'POST' }),
|
|
908
|
+
});
|
|
909
|
+
if (!result.ok) {
|
|
910
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId });
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const data = result.data;
|
|
631
915
|
if (data?.manual) {
|
|
632
|
-
await send(bot, chatId, t(lang, 'control.manualInstall', { provider, manual: data.manual }));
|
|
916
|
+
await send(bot, chatId, t(lang, 'control.manualInstall', { provider, manual: data.manual }), { editMessageId });
|
|
633
917
|
return;
|
|
634
918
|
}
|
|
635
919
|
await send(bot, chatId, t(lang, 'control.installStarted', {
|
|
636
920
|
provider,
|
|
637
921
|
jobId: data.jobId,
|
|
638
922
|
command: data.installCmd || 'internal installer',
|
|
639
|
-
}));
|
|
923
|
+
}), { editMessageId });
|
|
640
924
|
}
|
|
641
925
|
|
|
642
926
|
async function showInstallMenu({ bot, chatId, link, editMessageId }) {
|
|
@@ -818,16 +1102,13 @@ async function handleCommand({ bot, chatId, link, text }) {
|
|
|
818
1102
|
await send(bot, chatId, t(lang, 'control.cancelUsage'));
|
|
819
1103
|
return true;
|
|
820
1104
|
}
|
|
821
|
-
|
|
822
|
-
method: 'POST',
|
|
823
|
-
});
|
|
824
|
-
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }));
|
|
1105
|
+
await cancelRun({ bot, chatId, link, runId: argText });
|
|
825
1106
|
return true;
|
|
826
1107
|
}
|
|
827
1108
|
return false;
|
|
828
1109
|
}
|
|
829
1110
|
|
|
830
|
-
|
|
1111
|
+
async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
831
1112
|
const chatId = msg.chat.id;
|
|
832
1113
|
const text = String(msg.text || '').trim();
|
|
833
1114
|
if (!text) return false;
|
|
@@ -843,8 +1124,10 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
|
|
|
843
1124
|
|
|
844
1125
|
if (await handleAwaitingInput({ bot, chatId, link, text })) return true;
|
|
845
1126
|
if (await handleCommand({ bot, chatId, link, text })) return true;
|
|
1127
|
+
if (await handleRoutedIntent({ bot, chatId, link, text })) return true;
|
|
846
1128
|
|
|
847
1129
|
const state = getState(link.user_id);
|
|
1130
|
+
if (state.routerEnabled === false) return false;
|
|
848
1131
|
if (!state.remoteControlEnabled) {
|
|
849
1132
|
await send(bot, chatId, t(languageFor(link), 'control.disabled'));
|
|
850
1133
|
return true;
|
|
@@ -854,6 +1137,20 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
|
|
|
854
1137
|
return true;
|
|
855
1138
|
}
|
|
856
1139
|
|
|
1140
|
+
export async function handleTelegramControlMessage(args) {
|
|
1141
|
+
const chatId = args?.msg?.chat?.id;
|
|
1142
|
+
if (!chatId) return false;
|
|
1143
|
+
return enqueueTelegramJob(chatId, async () => {
|
|
1144
|
+
try {
|
|
1145
|
+
return await handleTelegramControlMessageInternal(args);
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
console.error('[telegram-control] message handler failed:', error);
|
|
1148
|
+
await send(args.bot, chatId, t(languageFor(args.link), 'error.generic')).catch(() => {});
|
|
1149
|
+
return true;
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
|
|
857
1154
|
async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
858
1155
|
const lang = languageFor(link);
|
|
859
1156
|
const run = await fetchRun(link.user_id, runId);
|
|
@@ -868,7 +1165,7 @@ async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
|
868
1165
|
});
|
|
869
1166
|
}
|
|
870
1167
|
|
|
871
|
-
|
|
1168
|
+
async function handleTelegramControlCallbackInternal({ bot, query, link }) {
|
|
872
1169
|
const chatId = query.message?.chat?.id;
|
|
873
1170
|
if (!chatId) return;
|
|
874
1171
|
const editMessageId = query.message?.message_id;
|
|
@@ -882,6 +1179,32 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
882
1179
|
await bot.answerCallbackQuery(query.id).catch(() => {});
|
|
883
1180
|
|
|
884
1181
|
const { action, payload } = entry;
|
|
1182
|
+
if (action === 'confirm_action') {
|
|
1183
|
+
forgetAction(entry.id);
|
|
1184
|
+
const lang = languageFor(link);
|
|
1185
|
+
const result = consumeTelegramConfirmation(link.user_id, payload.id);
|
|
1186
|
+
if (!result.ok) {
|
|
1187
|
+
await send(bot, chatId, t(lang, result.reason === 'expired'
|
|
1188
|
+
? 'control.confirmationExpired'
|
|
1189
|
+
: 'control.confirmationMissing'), { editMessageId });
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (await executeConfirmedAction({
|
|
1193
|
+
bot,
|
|
1194
|
+
chatId,
|
|
1195
|
+
link,
|
|
1196
|
+
confirmation: result.confirmation,
|
|
1197
|
+
editMessageId,
|
|
1198
|
+
})) return;
|
|
1199
|
+
await send(bot, chatId, t(lang, 'error.generic'), { editMessageId });
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
if (action === 'cancel_confirmation') {
|
|
1203
|
+
forgetAction(entry.id);
|
|
1204
|
+
clearTelegramConfirmation(link.user_id, payload.id);
|
|
1205
|
+
await send(bot, chatId, t(languageFor(link), 'control.confirmationCanceled'), { editMessageId });
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
885
1208
|
if (action === 'menu') return showMainMenu({ bot, chatId, link, editMessageId });
|
|
886
1209
|
if (action === 'projects') return showProjectMenu({ bot, chatId, link, editMessageId });
|
|
887
1210
|
if (action === 'providers') return showProviderMenu({ bot, chatId, link, editMessageId });
|
|
@@ -947,20 +1270,26 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
947
1270
|
}
|
|
948
1271
|
if (action === 'run_detail') return showRunDetail({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
949
1272
|
if (action === 'run_cancel') {
|
|
950
|
-
|
|
951
|
-
method: 'POST',
|
|
952
|
-
});
|
|
953
|
-
await send(bot, chatId, t(languageFor(link), 'control.runStatus', { runId: run.id, status: run.status }), { editMessageId });
|
|
1273
|
+
await cancelRun({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
954
1274
|
return;
|
|
955
1275
|
}
|
|
956
1276
|
if (action === 'approval_decide') {
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1277
|
+
const toolResult = await runTelegramTool({
|
|
1278
|
+
userId: link.user_id,
|
|
1279
|
+
action: 'approval_decide',
|
|
1280
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/approvals/${encodeURIComponent(payload.approvalId)}`, {
|
|
1281
|
+
method: 'POST',
|
|
1282
|
+
body: {
|
|
1283
|
+
allow: payload.allow === true,
|
|
1284
|
+
source: 'telegram',
|
|
1285
|
+
},
|
|
1286
|
+
}),
|
|
963
1287
|
});
|
|
1288
|
+
if (!toolResult.ok) {
|
|
1289
|
+
await sendToolFailure({ bot, chatId, link, result: toolResult, editMessageId });
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const result = toolResult.data;
|
|
964
1293
|
const lang = languageFor(link);
|
|
965
1294
|
await send(bot, chatId, t(lang, 'control.approvalDecided', {
|
|
966
1295
|
approvalId: payload.approvalId,
|
|
@@ -969,7 +1298,7 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
969
1298
|
}), { editMessageId });
|
|
970
1299
|
return showApprovalQueue({ bot, chatId, link });
|
|
971
1300
|
}
|
|
972
|
-
if (action === 'install_provider') return startCliInstall({ bot, chatId, link, provider: payload.provider });
|
|
1301
|
+
if (action === 'install_provider') return startCliInstall({ bot, chatId, link, provider: payload.provider, editMessageId });
|
|
973
1302
|
if (action === 'auth_provider') {
|
|
974
1303
|
await send(bot, chatId, `${payload.provider} login:\n${AUTH_HELP[payload.provider] || t(languageFor(link), 'control.providerAuthFallback')}`, { editMessageId });
|
|
975
1304
|
return;
|
|
@@ -994,3 +1323,18 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
994
1323
|
});
|
|
995
1324
|
}
|
|
996
1325
|
}
|
|
1326
|
+
|
|
1327
|
+
export async function handleTelegramControlCallback(args) {
|
|
1328
|
+
const chatId = args?.query?.message?.chat?.id;
|
|
1329
|
+
if (!chatId) return;
|
|
1330
|
+
return enqueueTelegramJob(chatId, async () => {
|
|
1331
|
+
try {
|
|
1332
|
+
await handleTelegramControlCallbackInternal(args);
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
console.error('[telegram-control] callback failed:', error);
|
|
1335
|
+
const lang = languageFor(args.link);
|
|
1336
|
+
await args.bot?.answerCallbackQuery(args.query?.id, { text: t(lang, 'error.generic') }).catch(() => {});
|
|
1337
|
+
await send(args.bot, chatId, t(lang, 'error.generic')).catch(() => {});
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
}
|