@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
|
@@ -2,10 +2,12 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import { apiKeysDb, telegramLinksDb } from '../../database/db.js';
|
|
3
3
|
import { getProjects } from '../../projects.js';
|
|
4
4
|
import { getStaticProviderModels } from '../model-registry.js';
|
|
5
|
+
import { TELEGRAM_CONTROL_SCOPES, TELEGRAM_PROVIDERS, buildTelegramAgentPrompt, classifyTelegramIntent, clearTelegramConfirmation, consumeTelegramConfirmation, createTelegramConfirmation, enqueueTelegramJob, resolveTelegramModel, resolveTelegramProvider, retryWithBackoff, runTelegramTool, splitTelegramText, } from './telegram-gateway.js';
|
|
5
6
|
import { SUPPORTED_LANGUAGES, t } from './translations.js';
|
|
6
|
-
const PROVIDERS =
|
|
7
|
+
const PROVIDERS = TELEGRAM_PROVIDERS;
|
|
7
8
|
const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
|
|
8
9
|
const CALLBACK_TTL_MS = 10 * 60 * 1000;
|
|
10
|
+
const MAX_CALLBACK_ACTIONS = 1000;
|
|
9
11
|
const MAX_TELEGRAM_TEXT = 3600;
|
|
10
12
|
const callbackActions = new Map();
|
|
11
13
|
const runMonitors = new Map();
|
|
@@ -77,7 +79,22 @@ function getState(userId) {
|
|
|
77
79
|
export function updateTelegramControlState(userId, patch) {
|
|
78
80
|
return telegramLinksDb.updateControlState(userId, patch);
|
|
79
81
|
}
|
|
82
|
+
function pruneCallbackActions() {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
for (const [id, entry] of callbackActions.entries()) {
|
|
85
|
+
if (entry.expiresAt < now) {
|
|
86
|
+
callbackActions.delete(id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
while (callbackActions.size > MAX_CALLBACK_ACTIONS) {
|
|
90
|
+
const oldestId = callbackActions.keys().next().value;
|
|
91
|
+
if (!oldestId)
|
|
92
|
+
break;
|
|
93
|
+
callbackActions.delete(oldestId);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
80
96
|
function registerAction(action, payload = {}) {
|
|
97
|
+
pruneCallbackActions();
|
|
81
98
|
const id = crypto.randomBytes(8).toString('hex');
|
|
82
99
|
callbackActions.set(id, {
|
|
83
100
|
action,
|
|
@@ -97,7 +114,11 @@ function readAction(data) {
|
|
|
97
114
|
callbackActions.delete(id);
|
|
98
115
|
return null;
|
|
99
116
|
}
|
|
100
|
-
return entry;
|
|
117
|
+
return { id, ...entry };
|
|
118
|
+
}
|
|
119
|
+
function forgetAction(id) {
|
|
120
|
+
if (id)
|
|
121
|
+
callbackActions.delete(id);
|
|
101
122
|
}
|
|
102
123
|
function button(text, action, payload = {}) {
|
|
103
124
|
return { text, callback_data: registerAction(action, payload) };
|
|
@@ -115,30 +136,46 @@ async function send(bot, chatId, text, options = {}) {
|
|
|
115
136
|
disable_web_page_preview: true,
|
|
116
137
|
...telegramOptions,
|
|
117
138
|
};
|
|
118
|
-
const
|
|
139
|
+
const chunks = splitTelegramText(text, MAX_TELEGRAM_TEXT);
|
|
140
|
+
const [firstChunk = ''] = chunks;
|
|
141
|
+
let startIndex = 0;
|
|
119
142
|
if (editMessageId && typeof bot.editMessageText === 'function') {
|
|
120
143
|
try {
|
|
121
|
-
|
|
144
|
+
const edited = await bot.editMessageText(firstChunk, {
|
|
122
145
|
chat_id: chatId,
|
|
123
146
|
message_id: editMessageId,
|
|
124
147
|
...extra,
|
|
125
148
|
});
|
|
149
|
+
startIndex = 1;
|
|
150
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
151
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
152
|
+
}
|
|
153
|
+
return edited;
|
|
126
154
|
}
|
|
127
155
|
catch (err) {
|
|
128
156
|
const description = err?.response?.body?.description || err?.message || '';
|
|
129
|
-
if (/message is not modified/i.test(description))
|
|
157
|
+
if (/message is not modified/i.test(description)) {
|
|
158
|
+
startIndex = 1;
|
|
159
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
160
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
161
|
+
}
|
|
130
162
|
return null;
|
|
163
|
+
}
|
|
131
164
|
console.warn('[telegram-control] editMessageText failed:', description || err);
|
|
132
165
|
}
|
|
133
166
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
167
|
+
let result = null;
|
|
168
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
169
|
+
try {
|
|
170
|
+
result = await bot.sendMessage(chatId, chunk, extra);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
const fallback = { ...extra };
|
|
174
|
+
delete fallback.parse_mode;
|
|
175
|
+
result = await bot.sendMessage(chatId, chunk, fallback);
|
|
176
|
+
}
|
|
141
177
|
}
|
|
178
|
+
return result;
|
|
142
179
|
}
|
|
143
180
|
function localApiBase() {
|
|
144
181
|
const port = process.env.SERVER_PORT || process.env.PORT || '3001';
|
|
@@ -148,19 +185,34 @@ function getOrCreateTelegramApiKey(userId) {
|
|
|
148
185
|
const existing = apiKeysDb
|
|
149
186
|
.getApiKeys(userId)
|
|
150
187
|
.find((key) => key.key_name === 'Telegram Control' && Boolean(key.is_active));
|
|
151
|
-
if (existing?.api_key)
|
|
188
|
+
if (existing?.api_key) {
|
|
189
|
+
const existingScopes = apiKeysDb.normalizeScopes(existing.scopes || []);
|
|
190
|
+
const nextScopes = apiKeysDb.normalizeScopes([...existingScopes, ...TELEGRAM_CONTROL_SCOPES]);
|
|
191
|
+
const hasAllScopes = TELEGRAM_CONTROL_SCOPES.every((scope) => existingScopes.includes(scope));
|
|
192
|
+
if (!hasAllScopes || nextScopes.length !== existingScopes.length) {
|
|
193
|
+
apiKeysDb.updateApiKeyScopes(userId, existing.id, nextScopes);
|
|
194
|
+
}
|
|
152
195
|
return existing.api_key;
|
|
153
|
-
|
|
196
|
+
}
|
|
197
|
+
return apiKeysDb.createApiKey(userId, 'Telegram Control', TELEGRAM_CONTROL_SCOPES).apiKey;
|
|
154
198
|
}
|
|
155
199
|
async function localApi(userId, path, { method = 'GET', body } = {}) {
|
|
156
200
|
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
157
|
-
const response = await
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
201
|
+
const response = await retryWithBackoff(async () => {
|
|
202
|
+
const res = await fetch(`${localApiBase()}${path}`, {
|
|
203
|
+
method,
|
|
204
|
+
headers: {
|
|
205
|
+
'Content-Type': 'application/json',
|
|
206
|
+
'X-API-Key': apiKey,
|
|
207
|
+
},
|
|
208
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
209
|
+
});
|
|
210
|
+
if ([408, 429, 500, 502, 503, 504].includes(res.status)) {
|
|
211
|
+
const error = new Error(`HTTP ${res.status}`);
|
|
212
|
+
error.status = res.status;
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
return res;
|
|
164
216
|
});
|
|
165
217
|
const data = await response.json().catch(() => ({}));
|
|
166
218
|
if (!response.ok) {
|
|
@@ -412,6 +464,36 @@ function extractAssistantText(response) {
|
|
|
412
464
|
}
|
|
413
465
|
return chunks.join('\n\n').trim();
|
|
414
466
|
}
|
|
467
|
+
function confirmationLabel(lang, action, payload = {}) {
|
|
468
|
+
if (action === 'install_provider') {
|
|
469
|
+
return t(lang, 'control.confirmInstall', { provider: payload.provider || '' });
|
|
470
|
+
}
|
|
471
|
+
if (action === 'run_cancel') {
|
|
472
|
+
return t(lang, 'control.confirmCancelRun', { runId: payload.runId || '' });
|
|
473
|
+
}
|
|
474
|
+
return t(lang, 'control.confirmationRequired');
|
|
475
|
+
}
|
|
476
|
+
async function requestConfirmation({ bot, chatId, link, action, payload = {}, editMessageId }) {
|
|
477
|
+
const lang = languageFor(link);
|
|
478
|
+
const pending = createTelegramConfirmation(link.user_id, action, payload);
|
|
479
|
+
await send(bot, chatId, confirmationLabel(lang, action, payload), {
|
|
480
|
+
editMessageId,
|
|
481
|
+
reply_markup: {
|
|
482
|
+
inline_keyboard: [[
|
|
483
|
+
button(t(lang, 'control.button.confirm'), 'confirm_action', { id: pending.id }),
|
|
484
|
+
button(t(lang, 'control.button.cancel'), 'cancel_confirmation', { id: pending.id }),
|
|
485
|
+
]],
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
return { ok: false, requiresConfirmation: true };
|
|
489
|
+
}
|
|
490
|
+
async function sendToolFailure({ bot, chatId, link, result, editMessageId }) {
|
|
491
|
+
const lang = languageFor(link);
|
|
492
|
+
const message = result?.message === 'REMOTE_CONTROL_DISABLED'
|
|
493
|
+
? t(lang, 'control.disabled')
|
|
494
|
+
: (result?.message || t(lang, 'error.generic'));
|
|
495
|
+
await send(bot, chatId, message, { editMessageId });
|
|
496
|
+
}
|
|
415
497
|
async function runAgent({ bot, chatId, link, prompt }) {
|
|
416
498
|
const lang = languageFor(link);
|
|
417
499
|
const state = getState(link.user_id);
|
|
@@ -424,33 +506,49 @@ async function runAgent({ bot, chatId, link, prompt }) {
|
|
|
424
506
|
await showProjectMenu({ bot, chatId, link });
|
|
425
507
|
return;
|
|
426
508
|
}
|
|
509
|
+
const provider = resolveTelegramProvider(state);
|
|
510
|
+
const model = resolveTelegramModel(state);
|
|
427
511
|
if (state.progressMode !== 'final') {
|
|
428
512
|
await send(bot, chatId, t(lang, 'control.agentStarted', {
|
|
429
|
-
provider
|
|
513
|
+
provider,
|
|
430
514
|
project: state.selectedProjectName || state.selectedProjectPath,
|
|
431
515
|
}));
|
|
432
516
|
}
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
517
|
+
const result = await runTelegramTool({
|
|
518
|
+
userId: link.user_id,
|
|
519
|
+
action: 'run_agent',
|
|
520
|
+
retries: 0,
|
|
521
|
+
execute: () => localApi(link.user_id, '/api/agent', {
|
|
522
|
+
method: 'POST',
|
|
523
|
+
body: {
|
|
524
|
+
projectPath: state.selectedProjectPath,
|
|
525
|
+
provider,
|
|
526
|
+
model: model || undefined,
|
|
527
|
+
message: buildTelegramAgentPrompt(prompt, state),
|
|
528
|
+
cleanup: false,
|
|
529
|
+
stream: false,
|
|
530
|
+
},
|
|
531
|
+
}),
|
|
443
532
|
});
|
|
533
|
+
if (!result.ok) {
|
|
534
|
+
await sendToolFailure({ bot, chatId, link, result });
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const response = result.data;
|
|
444
538
|
const assistantText = extractAssistantText(response);
|
|
445
539
|
const statusLine = response.success === false
|
|
446
540
|
? t(lang, 'control.agentFailed', { error: response.error || 'provider returned no answer' })
|
|
447
|
-
: t(lang, 'control.agentDone', { provider
|
|
541
|
+
: t(lang, 'control.agentDone', { provider, session: response.sessionId ? ` (${response.sessionId})` : '' });
|
|
448
542
|
await send(bot, chatId, `${statusLine}\n\n${assistantText || response.error || t(lang, 'control.noAssistantText')}`);
|
|
449
543
|
}
|
|
450
544
|
export async function runWorkflow({ bot, chatId, link, input }) {
|
|
451
545
|
const lang = languageFor(link);
|
|
452
546
|
const state = getState(link.user_id);
|
|
453
547
|
const workflowId = state.selectedWorkflowId;
|
|
548
|
+
if (!state.remoteControlEnabled) {
|
|
549
|
+
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
454
552
|
if (!workflowId) {
|
|
455
553
|
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'));
|
|
456
554
|
await showWorkflowMenu({ bot, chatId, link });
|
|
@@ -461,26 +559,173 @@ export async function runWorkflow({ bot, chatId, link, input }) {
|
|
|
461
559
|
await showProjectMenu({ bot, chatId, link });
|
|
462
560
|
return;
|
|
463
561
|
}
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
562
|
+
const provider = resolveTelegramProvider(state);
|
|
563
|
+
const model = resolveTelegramModel(state);
|
|
564
|
+
const result = await runTelegramTool({
|
|
565
|
+
userId: link.user_id,
|
|
566
|
+
action: 'run_workflow',
|
|
567
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/${workflowId}/runs`, {
|
|
568
|
+
method: 'POST',
|
|
569
|
+
body: {
|
|
570
|
+
input,
|
|
571
|
+
metadata: {
|
|
572
|
+
projectId: state.selectedProjectName,
|
|
573
|
+
projectName: state.selectedProjectName,
|
|
574
|
+
projectPath: state.selectedProjectPath,
|
|
575
|
+
workspaceTarget: 'selected_project',
|
|
576
|
+
telegram: true,
|
|
577
|
+
preferredProvider: provider,
|
|
578
|
+
preferredModel: model,
|
|
579
|
+
},
|
|
476
580
|
},
|
|
477
|
-
},
|
|
581
|
+
}),
|
|
478
582
|
});
|
|
583
|
+
if (!result.ok) {
|
|
584
|
+
await sendToolFailure({ bot, chatId, link, result });
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const run = result.data;
|
|
479
588
|
await send(bot, chatId, t(lang, 'control.workflowStarted', { runId: run.id, workflowId }));
|
|
480
589
|
monitorWorkflowRun({ bot, chatId, link, runId: run.id }).catch((error) => {
|
|
481
590
|
console.warn('[telegram-control] workflow monitor failed:', error?.message || error);
|
|
482
591
|
});
|
|
483
592
|
}
|
|
593
|
+
async function cancelRun({ bot, chatId, link, runId, editMessageId, confirmed = false }) {
|
|
594
|
+
const lang = languageFor(link);
|
|
595
|
+
if (!runId) {
|
|
596
|
+
await send(bot, chatId, t(lang, 'control.cancelUsage'), { editMessageId });
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const state = getState(link.user_id);
|
|
600
|
+
if (state.remoteControlEnabled === false) {
|
|
601
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId });
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (state.confirmationPolicy === 'strict' && !confirmed) {
|
|
605
|
+
await requestConfirmation({
|
|
606
|
+
bot,
|
|
607
|
+
chatId,
|
|
608
|
+
link,
|
|
609
|
+
action: 'run_cancel',
|
|
610
|
+
payload: { runId },
|
|
611
|
+
editMessageId,
|
|
612
|
+
});
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const result = await runTelegramTool({
|
|
616
|
+
userId: link.user_id,
|
|
617
|
+
action: 'run_cancel',
|
|
618
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
619
|
+
method: 'POST',
|
|
620
|
+
}),
|
|
621
|
+
});
|
|
622
|
+
if (!result.ok) {
|
|
623
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId });
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const run = result.data;
|
|
627
|
+
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }), { editMessageId });
|
|
628
|
+
}
|
|
629
|
+
async function executeConfirmedAction({ bot, chatId, link, confirmation, editMessageId }) {
|
|
630
|
+
if (confirmation.action === 'install_provider') {
|
|
631
|
+
await startCliInstall({
|
|
632
|
+
bot,
|
|
633
|
+
chatId,
|
|
634
|
+
link,
|
|
635
|
+
provider: confirmation.payload?.provider,
|
|
636
|
+
editMessageId,
|
|
637
|
+
confirmed: true,
|
|
638
|
+
});
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
if (confirmation.action === 'run_cancel') {
|
|
642
|
+
await cancelRun({
|
|
643
|
+
bot,
|
|
644
|
+
chatId,
|
|
645
|
+
link,
|
|
646
|
+
runId: confirmation.payload?.runId,
|
|
647
|
+
editMessageId,
|
|
648
|
+
confirmed: true,
|
|
649
|
+
});
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
async function findProjectByQuery(query) {
|
|
655
|
+
const projects = await listProjects();
|
|
656
|
+
const needle = String(query || '').trim().toLocaleLowerCase('tr');
|
|
657
|
+
if (!needle)
|
|
658
|
+
return null;
|
|
659
|
+
return projects.find((project) => {
|
|
660
|
+
const candidates = [project.name, project.displayName, project.fullPath, project.path]
|
|
661
|
+
.filter(Boolean)
|
|
662
|
+
.map((value) => String(value).toLocaleLowerCase('tr'));
|
|
663
|
+
return candidates.some((candidate) => candidate === needle || candidate.includes(needle));
|
|
664
|
+
}) || null;
|
|
665
|
+
}
|
|
666
|
+
async function handleRoutedIntent({ bot, chatId, link, text }) {
|
|
667
|
+
const state = getState(link.user_id);
|
|
668
|
+
const intent = classifyTelegramIntent(text, state);
|
|
669
|
+
if (!intent)
|
|
670
|
+
return false;
|
|
671
|
+
if (intent.action === 'projects') {
|
|
672
|
+
await showProjectMenu({ bot, chatId, link });
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
if (intent.action === 'project_select_query') {
|
|
676
|
+
const project = await findProjectByQuery(intent.query);
|
|
677
|
+
const lang = languageFor(link);
|
|
678
|
+
if (!project) {
|
|
679
|
+
await send(bot, chatId, t(lang, 'control.projectNotFound', { query: intent.query }));
|
|
680
|
+
await showProjectMenu({ bot, chatId, link });
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
updateTelegramControlState(link.user_id, {
|
|
684
|
+
selectedProjectName: project.name,
|
|
685
|
+
selectedProjectPath: project.fullPath || project.path,
|
|
686
|
+
});
|
|
687
|
+
await showMainMenu({
|
|
688
|
+
bot,
|
|
689
|
+
chatId,
|
|
690
|
+
link,
|
|
691
|
+
notice: t(lang, 'control.projectSelected', {
|
|
692
|
+
project: project.displayName || project.name,
|
|
693
|
+
path: project.fullPath || project.path,
|
|
694
|
+
}),
|
|
695
|
+
});
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
if (intent.action === 'provider_menu') {
|
|
699
|
+
await showProviderMenu({ bot, chatId, link });
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
if (intent.action === 'provider_select') {
|
|
703
|
+
updateTelegramControlState(link.user_id, { selectedProvider: intent.provider, selectedModel: null });
|
|
704
|
+
await showModelMenu({ bot, chatId, link });
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
if (intent.action === 'model_menu') {
|
|
708
|
+
await showModelMenu({ bot, chatId, link });
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
if (intent.action === 'runs') {
|
|
712
|
+
await showRuns({ bot, chatId, link });
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
if (intent.action === 'approvals') {
|
|
716
|
+
await showApprovalQueue({ bot, chatId, link });
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
if (intent.action === 'workflows') {
|
|
720
|
+
await showWorkflowMenu({ bot, chatId, link });
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
if (intent.action === 'new_chat') {
|
|
724
|
+
await startNewChat({ bot, chatId, link });
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
484
729
|
async function fetchRun(userId, runId) {
|
|
485
730
|
return localApi(userId, `/api/orchestration/workflows/runs/${runId}`);
|
|
486
731
|
}
|
|
@@ -544,18 +789,47 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
544
789
|
runMonitors.delete(runId);
|
|
545
790
|
}
|
|
546
791
|
}
|
|
547
|
-
export async function startCliInstall({ bot, chatId, link, provider }) {
|
|
792
|
+
export async function startCliInstall({ bot, chatId, link, provider, editMessageId, confirmed = false }) {
|
|
548
793
|
const lang = languageFor(link);
|
|
549
|
-
|
|
794
|
+
if (!PROVIDERS.includes(provider)) {
|
|
795
|
+
await send(bot, chatId, t(lang, 'control.providerAuthFallback'), { editMessageId });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const state = getState(link.user_id);
|
|
799
|
+
if (state.remoteControlEnabled === false) {
|
|
800
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId });
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (state.confirmationPolicy === 'strict' && !confirmed) {
|
|
804
|
+
await requestConfirmation({
|
|
805
|
+
bot,
|
|
806
|
+
chatId,
|
|
807
|
+
link,
|
|
808
|
+
action: 'install_provider',
|
|
809
|
+
payload: { provider },
|
|
810
|
+
editMessageId,
|
|
811
|
+
});
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const result = await runTelegramTool({
|
|
815
|
+
userId: link.user_id,
|
|
816
|
+
action: 'install_provider',
|
|
817
|
+
execute: () => localApi(link.user_id, `/api/providers/${provider}/install`, { method: 'POST' }),
|
|
818
|
+
});
|
|
819
|
+
if (!result.ok) {
|
|
820
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId });
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const data = result.data;
|
|
550
824
|
if (data?.manual) {
|
|
551
|
-
await send(bot, chatId, t(lang, 'control.manualInstall', { provider, manual: data.manual }));
|
|
825
|
+
await send(bot, chatId, t(lang, 'control.manualInstall', { provider, manual: data.manual }), { editMessageId });
|
|
552
826
|
return;
|
|
553
827
|
}
|
|
554
828
|
await send(bot, chatId, t(lang, 'control.installStarted', {
|
|
555
829
|
provider,
|
|
556
830
|
jobId: data.jobId,
|
|
557
831
|
command: data.installCmd || 'internal installer',
|
|
558
|
-
}));
|
|
832
|
+
}), { editMessageId });
|
|
559
833
|
}
|
|
560
834
|
async function showInstallMenu({ bot, chatId, link, editMessageId }) {
|
|
561
835
|
const lang = languageFor(link);
|
|
@@ -731,15 +1005,12 @@ async function handleCommand({ bot, chatId, link, text }) {
|
|
|
731
1005
|
await send(bot, chatId, t(lang, 'control.cancelUsage'));
|
|
732
1006
|
return true;
|
|
733
1007
|
}
|
|
734
|
-
|
|
735
|
-
method: 'POST',
|
|
736
|
-
});
|
|
737
|
-
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }));
|
|
1008
|
+
await cancelRun({ bot, chatId, link, runId: argText });
|
|
738
1009
|
return true;
|
|
739
1010
|
}
|
|
740
1011
|
return false;
|
|
741
1012
|
}
|
|
742
|
-
|
|
1013
|
+
async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
743
1014
|
const chatId = msg.chat.id;
|
|
744
1015
|
const text = String(msg.text || '').trim();
|
|
745
1016
|
if (!text)
|
|
@@ -756,7 +1027,11 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
|
|
|
756
1027
|
return true;
|
|
757
1028
|
if (await handleCommand({ bot, chatId, link, text }))
|
|
758
1029
|
return true;
|
|
1030
|
+
if (await handleRoutedIntent({ bot, chatId, link, text }))
|
|
1031
|
+
return true;
|
|
759
1032
|
const state = getState(link.user_id);
|
|
1033
|
+
if (state.routerEnabled === false)
|
|
1034
|
+
return false;
|
|
760
1035
|
if (!state.remoteControlEnabled) {
|
|
761
1036
|
await send(bot, chatId, t(languageFor(link), 'control.disabled'));
|
|
762
1037
|
return true;
|
|
@@ -764,6 +1039,21 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
|
|
|
764
1039
|
await runAgent({ bot, chatId, link, prompt: text });
|
|
765
1040
|
return true;
|
|
766
1041
|
}
|
|
1042
|
+
export async function handleTelegramControlMessage(args) {
|
|
1043
|
+
const chatId = args?.msg?.chat?.id;
|
|
1044
|
+
if (!chatId)
|
|
1045
|
+
return false;
|
|
1046
|
+
return enqueueTelegramJob(chatId, async () => {
|
|
1047
|
+
try {
|
|
1048
|
+
return await handleTelegramControlMessageInternal(args);
|
|
1049
|
+
}
|
|
1050
|
+
catch (error) {
|
|
1051
|
+
console.error('[telegram-control] message handler failed:', error);
|
|
1052
|
+
await send(args.bot, chatId, t(languageFor(args.link), 'error.generic')).catch(() => { });
|
|
1053
|
+
return true;
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
767
1057
|
async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
768
1058
|
const lang = languageFor(link);
|
|
769
1059
|
const run = await fetchRun(link.user_id, runId);
|
|
@@ -777,7 +1067,7 @@ async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
|
777
1067
|
},
|
|
778
1068
|
});
|
|
779
1069
|
}
|
|
780
|
-
|
|
1070
|
+
async function handleTelegramControlCallbackInternal({ bot, query, link }) {
|
|
781
1071
|
const chatId = query.message?.chat?.id;
|
|
782
1072
|
if (!chatId)
|
|
783
1073
|
return;
|
|
@@ -791,6 +1081,33 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
791
1081
|
}
|
|
792
1082
|
await bot.answerCallbackQuery(query.id).catch(() => { });
|
|
793
1083
|
const { action, payload } = entry;
|
|
1084
|
+
if (action === 'confirm_action') {
|
|
1085
|
+
forgetAction(entry.id);
|
|
1086
|
+
const lang = languageFor(link);
|
|
1087
|
+
const result = consumeTelegramConfirmation(link.user_id, payload.id);
|
|
1088
|
+
if (!result.ok) {
|
|
1089
|
+
await send(bot, chatId, t(lang, result.reason === 'expired'
|
|
1090
|
+
? 'control.confirmationExpired'
|
|
1091
|
+
: 'control.confirmationMissing'), { editMessageId });
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (await executeConfirmedAction({
|
|
1095
|
+
bot,
|
|
1096
|
+
chatId,
|
|
1097
|
+
link,
|
|
1098
|
+
confirmation: result.confirmation,
|
|
1099
|
+
editMessageId,
|
|
1100
|
+
}))
|
|
1101
|
+
return;
|
|
1102
|
+
await send(bot, chatId, t(lang, 'error.generic'), { editMessageId });
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (action === 'cancel_confirmation') {
|
|
1106
|
+
forgetAction(entry.id);
|
|
1107
|
+
clearTelegramConfirmation(link.user_id, payload.id);
|
|
1108
|
+
await send(bot, chatId, t(languageFor(link), 'control.confirmationCanceled'), { editMessageId });
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
794
1111
|
if (action === 'menu')
|
|
795
1112
|
return showMainMenu({ bot, chatId, link, editMessageId });
|
|
796
1113
|
if (action === 'projects')
|
|
@@ -873,20 +1190,26 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
873
1190
|
if (action === 'run_detail')
|
|
874
1191
|
return showRunDetail({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
875
1192
|
if (action === 'run_cancel') {
|
|
876
|
-
|
|
877
|
-
method: 'POST',
|
|
878
|
-
});
|
|
879
|
-
await send(bot, chatId, t(languageFor(link), 'control.runStatus', { runId: run.id, status: run.status }), { editMessageId });
|
|
1193
|
+
await cancelRun({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
880
1194
|
return;
|
|
881
1195
|
}
|
|
882
1196
|
if (action === 'approval_decide') {
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1197
|
+
const toolResult = await runTelegramTool({
|
|
1198
|
+
userId: link.user_id,
|
|
1199
|
+
action: 'approval_decide',
|
|
1200
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/approvals/${encodeURIComponent(payload.approvalId)}`, {
|
|
1201
|
+
method: 'POST',
|
|
1202
|
+
body: {
|
|
1203
|
+
allow: payload.allow === true,
|
|
1204
|
+
source: 'telegram',
|
|
1205
|
+
},
|
|
1206
|
+
}),
|
|
889
1207
|
});
|
|
1208
|
+
if (!toolResult.ok) {
|
|
1209
|
+
await sendToolFailure({ bot, chatId, link, result: toolResult, editMessageId });
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const result = toolResult.data;
|
|
890
1213
|
const lang = languageFor(link);
|
|
891
1214
|
await send(bot, chatId, t(lang, 'control.approvalDecided', {
|
|
892
1215
|
approvalId: payload.approvalId,
|
|
@@ -896,7 +1219,7 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
896
1219
|
return showApprovalQueue({ bot, chatId, link });
|
|
897
1220
|
}
|
|
898
1221
|
if (action === 'install_provider')
|
|
899
|
-
return startCliInstall({ bot, chatId, link, provider: payload.provider });
|
|
1222
|
+
return startCliInstall({ bot, chatId, link, provider: payload.provider, editMessageId });
|
|
900
1223
|
if (action === 'auth_provider') {
|
|
901
1224
|
await send(bot, chatId, `${payload.provider} login:\n${AUTH_HELP[payload.provider] || t(languageFor(link), 'control.providerAuthFallback')}`, { editMessageId });
|
|
902
1225
|
return;
|
|
@@ -921,4 +1244,20 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
921
1244
|
});
|
|
922
1245
|
}
|
|
923
1246
|
}
|
|
1247
|
+
export async function handleTelegramControlCallback(args) {
|
|
1248
|
+
const chatId = args?.query?.message?.chat?.id;
|
|
1249
|
+
if (!chatId)
|
|
1250
|
+
return;
|
|
1251
|
+
return enqueueTelegramJob(chatId, async () => {
|
|
1252
|
+
try {
|
|
1253
|
+
await handleTelegramControlCallbackInternal(args);
|
|
1254
|
+
}
|
|
1255
|
+
catch (error) {
|
|
1256
|
+
console.error('[telegram-control] callback failed:', error);
|
|
1257
|
+
const lang = languageFor(args.link);
|
|
1258
|
+
await args.bot?.answerCallbackQuery(args.query?.id, { text: t(lang, 'error.generic') }).catch(() => { });
|
|
1259
|
+
await send(args.bot, chatId, t(lang, 'error.generic')).catch(() => { });
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
924
1263
|
//# sourceMappingURL=control-center.js.map
|