@pixelbyte-software/pixcode 1.53.3 → 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-CZZiJpxs.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 +15 -9
- package/dist-server/server/services/telegram/bot.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +406 -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 +91 -48
- 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 +14 -9
- package/server/services/telegram/control-center.js +427 -66
- package/server/services/telegram/telegram-gateway.js +250 -0
- package/server/services/telegram/telegram-http-client.js +95 -45
- package/server/services/telegram/translations.js +18 -0
|
@@ -4,11 +4,27 @@ 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;
|
|
27
|
+
const MAX_CALLBACK_ACTIONS = 1000;
|
|
12
28
|
const MAX_TELEGRAM_TEXT = 3600;
|
|
13
29
|
const callbackActions = new Map();
|
|
14
30
|
const runMonitors = new Map();
|
|
@@ -94,7 +110,23 @@ export function updateTelegramControlState(userId, patch) {
|
|
|
94
110
|
return telegramLinksDb.updateControlState(userId, patch);
|
|
95
111
|
}
|
|
96
112
|
|
|
113
|
+
function pruneCallbackActions() {
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
for (const [id, entry] of callbackActions.entries()) {
|
|
116
|
+
if (entry.expiresAt < now) {
|
|
117
|
+
callbackActions.delete(id);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
while (callbackActions.size > MAX_CALLBACK_ACTIONS) {
|
|
122
|
+
const oldestId = callbackActions.keys().next().value;
|
|
123
|
+
if (!oldestId) break;
|
|
124
|
+
callbackActions.delete(oldestId);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
97
128
|
function registerAction(action, payload = {}) {
|
|
129
|
+
pruneCallbackActions();
|
|
98
130
|
const id = crypto.randomBytes(8).toString('hex');
|
|
99
131
|
callbackActions.set(id, {
|
|
100
132
|
action,
|
|
@@ -113,7 +145,11 @@ function readAction(data) {
|
|
|
113
145
|
callbackActions.delete(id);
|
|
114
146
|
return null;
|
|
115
147
|
}
|
|
116
|
-
return entry;
|
|
148
|
+
return { id, ...entry };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function forgetAction(id) {
|
|
152
|
+
if (id) callbackActions.delete(id);
|
|
117
153
|
}
|
|
118
154
|
|
|
119
155
|
function button(text, action, payload = {}) {
|
|
@@ -133,27 +169,44 @@ async function send(bot, chatId, text, options = {}) {
|
|
|
133
169
|
disable_web_page_preview: true,
|
|
134
170
|
...telegramOptions,
|
|
135
171
|
};
|
|
136
|
-
const
|
|
172
|
+
const chunks = splitTelegramText(text, MAX_TELEGRAM_TEXT);
|
|
173
|
+
const [firstChunk = ''] = chunks;
|
|
174
|
+
let startIndex = 0;
|
|
137
175
|
if (editMessageId && typeof bot.editMessageText === 'function') {
|
|
138
176
|
try {
|
|
139
|
-
|
|
177
|
+
const edited = await bot.editMessageText(firstChunk, {
|
|
140
178
|
chat_id: chatId,
|
|
141
179
|
message_id: editMessageId,
|
|
142
180
|
...extra,
|
|
143
181
|
});
|
|
182
|
+
startIndex = 1;
|
|
183
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
184
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
185
|
+
}
|
|
186
|
+
return edited;
|
|
144
187
|
} catch (err) {
|
|
145
188
|
const description = err?.response?.body?.description || err?.message || '';
|
|
146
|
-
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
|
+
}
|
|
147
196
|
console.warn('[telegram-control] editMessageText failed:', description || err);
|
|
148
197
|
}
|
|
149
198
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
+
}
|
|
156
208
|
}
|
|
209
|
+
return result;
|
|
157
210
|
}
|
|
158
211
|
|
|
159
212
|
function localApiBase() {
|
|
@@ -165,19 +218,35 @@ function getOrCreateTelegramApiKey(userId) {
|
|
|
165
218
|
const existing = apiKeysDb
|
|
166
219
|
.getApiKeys(userId)
|
|
167
220
|
.find((key) => key.key_name === 'Telegram Control' && Boolean(key.is_active));
|
|
168
|
-
if (existing?.api_key)
|
|
169
|
-
|
|
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;
|
|
170
231
|
}
|
|
171
232
|
|
|
172
233
|
async function localApi(userId, path, { method = 'GET', body } = {}) {
|
|
173
234
|
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
174
|
-
const response = await
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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;
|
|
181
250
|
});
|
|
182
251
|
const data = await response.json().catch(() => ({}));
|
|
183
252
|
if (!response.ok) {
|
|
@@ -470,6 +539,39 @@ function extractAssistantText(response) {
|
|
|
470
539
|
return chunks.join('\n\n').trim();
|
|
471
540
|
}
|
|
472
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
|
+
|
|
473
575
|
async function runAgent({ bot, chatId, link, prompt }) {
|
|
474
576
|
const lang = languageFor(link);
|
|
475
577
|
const state = getState(link.user_id);
|
|
@@ -483,28 +585,41 @@ async function runAgent({ bot, chatId, link, prompt }) {
|
|
|
483
585
|
return;
|
|
484
586
|
}
|
|
485
587
|
|
|
588
|
+
const provider = resolveTelegramProvider(state);
|
|
589
|
+
const model = resolveTelegramModel(state);
|
|
486
590
|
if (state.progressMode !== 'final') {
|
|
487
591
|
await send(bot, chatId, t(lang, 'control.agentStarted', {
|
|
488
|
-
provider
|
|
592
|
+
provider,
|
|
489
593
|
project: state.selectedProjectName || state.selectedProjectPath,
|
|
490
594
|
}));
|
|
491
595
|
}
|
|
492
596
|
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
+
}),
|
|
503
612
|
});
|
|
613
|
+
if (!result.ok) {
|
|
614
|
+
await sendToolFailure({ bot, chatId, link, result });
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const response = result.data;
|
|
504
619
|
const assistantText = extractAssistantText(response);
|
|
505
620
|
const statusLine = response.success === false
|
|
506
621
|
? t(lang, 'control.agentFailed', { error: response.error || 'provider returned no answer' })
|
|
507
|
-
: t(lang, 'control.agentDone', { provider
|
|
622
|
+
: t(lang, 'control.agentDone', { provider, session: response.sessionId ? ` (${response.sessionId})` : '' });
|
|
508
623
|
await send(bot, chatId, `${statusLine}\n\n${assistantText || response.error || t(lang, 'control.noAssistantText')}`);
|
|
509
624
|
}
|
|
510
625
|
|
|
@@ -512,6 +627,10 @@ export async function runWorkflow({ bot, chatId, link, input }) {
|
|
|
512
627
|
const lang = languageFor(link);
|
|
513
628
|
const state = getState(link.user_id);
|
|
514
629
|
const workflowId = state.selectedWorkflowId;
|
|
630
|
+
if (!state.remoteControlEnabled) {
|
|
631
|
+
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
515
634
|
if (!workflowId) {
|
|
516
635
|
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'));
|
|
517
636
|
await showWorkflowMenu({ bot, chatId, link });
|
|
@@ -523,27 +642,178 @@ export async function runWorkflow({ bot, chatId, link, input }) {
|
|
|
523
642
|
return;
|
|
524
643
|
}
|
|
525
644
|
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
+
},
|
|
538
663
|
},
|
|
539
|
-
},
|
|
664
|
+
}),
|
|
540
665
|
});
|
|
666
|
+
if (!result.ok) {
|
|
667
|
+
await sendToolFailure({ bot, chatId, link, result });
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const run = result.data;
|
|
541
671
|
await send(bot, chatId, t(lang, 'control.workflowStarted', { runId: run.id, workflowId }));
|
|
542
672
|
monitorWorkflowRun({ bot, chatId, link, runId: run.id }).catch((error) => {
|
|
543
673
|
console.warn('[telegram-control] workflow monitor failed:', error?.message || error);
|
|
544
674
|
});
|
|
545
675
|
}
|
|
546
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
|
+
|
|
547
817
|
async function fetchRun(userId, runId) {
|
|
548
818
|
return localApi(userId, `/api/orchestration/workflows/runs/${runId}`);
|
|
549
819
|
}
|
|
@@ -608,18 +878,49 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
608
878
|
}
|
|
609
879
|
}
|
|
610
880
|
|
|
611
|
-
export async function startCliInstall({ bot, chatId, link, provider }) {
|
|
881
|
+
export async function startCliInstall({ bot, chatId, link, provider, editMessageId, confirmed = false }) {
|
|
612
882
|
const lang = languageFor(link);
|
|
613
|
-
|
|
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;
|
|
614
915
|
if (data?.manual) {
|
|
615
|
-
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 });
|
|
616
917
|
return;
|
|
617
918
|
}
|
|
618
919
|
await send(bot, chatId, t(lang, 'control.installStarted', {
|
|
619
920
|
provider,
|
|
620
921
|
jobId: data.jobId,
|
|
621
922
|
command: data.installCmd || 'internal installer',
|
|
622
|
-
}));
|
|
923
|
+
}), { editMessageId });
|
|
623
924
|
}
|
|
624
925
|
|
|
625
926
|
async function showInstallMenu({ bot, chatId, link, editMessageId }) {
|
|
@@ -801,16 +1102,13 @@ async function handleCommand({ bot, chatId, link, text }) {
|
|
|
801
1102
|
await send(bot, chatId, t(lang, 'control.cancelUsage'));
|
|
802
1103
|
return true;
|
|
803
1104
|
}
|
|
804
|
-
|
|
805
|
-
method: 'POST',
|
|
806
|
-
});
|
|
807
|
-
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }));
|
|
1105
|
+
await cancelRun({ bot, chatId, link, runId: argText });
|
|
808
1106
|
return true;
|
|
809
1107
|
}
|
|
810
1108
|
return false;
|
|
811
1109
|
}
|
|
812
1110
|
|
|
813
|
-
|
|
1111
|
+
async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
814
1112
|
const chatId = msg.chat.id;
|
|
815
1113
|
const text = String(msg.text || '').trim();
|
|
816
1114
|
if (!text) return false;
|
|
@@ -826,8 +1124,10 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
|
|
|
826
1124
|
|
|
827
1125
|
if (await handleAwaitingInput({ bot, chatId, link, text })) return true;
|
|
828
1126
|
if (await handleCommand({ bot, chatId, link, text })) return true;
|
|
1127
|
+
if (await handleRoutedIntent({ bot, chatId, link, text })) return true;
|
|
829
1128
|
|
|
830
1129
|
const state = getState(link.user_id);
|
|
1130
|
+
if (state.routerEnabled === false) return false;
|
|
831
1131
|
if (!state.remoteControlEnabled) {
|
|
832
1132
|
await send(bot, chatId, t(languageFor(link), 'control.disabled'));
|
|
833
1133
|
return true;
|
|
@@ -837,6 +1137,20 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
|
|
|
837
1137
|
return true;
|
|
838
1138
|
}
|
|
839
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
|
+
|
|
840
1154
|
async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
841
1155
|
const lang = languageFor(link);
|
|
842
1156
|
const run = await fetchRun(link.user_id, runId);
|
|
@@ -851,7 +1165,7 @@ async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
|
851
1165
|
});
|
|
852
1166
|
}
|
|
853
1167
|
|
|
854
|
-
|
|
1168
|
+
async function handleTelegramControlCallbackInternal({ bot, query, link }) {
|
|
855
1169
|
const chatId = query.message?.chat?.id;
|
|
856
1170
|
if (!chatId) return;
|
|
857
1171
|
const editMessageId = query.message?.message_id;
|
|
@@ -865,6 +1179,32 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
865
1179
|
await bot.answerCallbackQuery(query.id).catch(() => {});
|
|
866
1180
|
|
|
867
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
|
+
}
|
|
868
1208
|
if (action === 'menu') return showMainMenu({ bot, chatId, link, editMessageId });
|
|
869
1209
|
if (action === 'projects') return showProjectMenu({ bot, chatId, link, editMessageId });
|
|
870
1210
|
if (action === 'providers') return showProviderMenu({ bot, chatId, link, editMessageId });
|
|
@@ -930,20 +1270,26 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
930
1270
|
}
|
|
931
1271
|
if (action === 'run_detail') return showRunDetail({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
932
1272
|
if (action === 'run_cancel') {
|
|
933
|
-
|
|
934
|
-
method: 'POST',
|
|
935
|
-
});
|
|
936
|
-
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 });
|
|
937
1274
|
return;
|
|
938
1275
|
}
|
|
939
1276
|
if (action === 'approval_decide') {
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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
|
+
}),
|
|
946
1287
|
});
|
|
1288
|
+
if (!toolResult.ok) {
|
|
1289
|
+
await sendToolFailure({ bot, chatId, link, result: toolResult, editMessageId });
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const result = toolResult.data;
|
|
947
1293
|
const lang = languageFor(link);
|
|
948
1294
|
await send(bot, chatId, t(lang, 'control.approvalDecided', {
|
|
949
1295
|
approvalId: payload.approvalId,
|
|
@@ -952,7 +1298,7 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
952
1298
|
}), { editMessageId });
|
|
953
1299
|
return showApprovalQueue({ bot, chatId, link });
|
|
954
1300
|
}
|
|
955
|
-
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 });
|
|
956
1302
|
if (action === 'auth_provider') {
|
|
957
1303
|
await send(bot, chatId, `${payload.provider} login:\n${AUTH_HELP[payload.provider] || t(languageFor(link), 'control.providerAuthFallback')}`, { editMessageId });
|
|
958
1304
|
return;
|
|
@@ -977,3 +1323,18 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
977
1323
|
});
|
|
978
1324
|
}
|
|
979
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
|
+
}
|