@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.
@@ -2,8 +2,9 @@ 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 = ['claude', 'cursor', 'codex', 'gemini', 'qwen', 'opencode'];
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;
9
10
  const MAX_CALLBACK_ACTIONS = 1000;
@@ -113,7 +114,11 @@ function readAction(data) {
113
114
  callbackActions.delete(id);
114
115
  return null;
115
116
  }
116
- return entry;
117
+ return { id, ...entry };
118
+ }
119
+ function forgetAction(id) {
120
+ if (id)
121
+ callbackActions.delete(id);
117
122
  }
118
123
  function button(text, action, payload = {}) {
119
124
  return { text, callback_data: registerAction(action, payload) };
@@ -131,30 +136,46 @@ async function send(bot, chatId, text, options = {}) {
131
136
  disable_web_page_preview: true,
132
137
  ...telegramOptions,
133
138
  };
134
- const messageText = truncate(text);
139
+ const chunks = splitTelegramText(text, MAX_TELEGRAM_TEXT);
140
+ const [firstChunk = ''] = chunks;
141
+ let startIndex = 0;
135
142
  if (editMessageId && typeof bot.editMessageText === 'function') {
136
143
  try {
137
- return await bot.editMessageText(messageText, {
144
+ const edited = await bot.editMessageText(firstChunk, {
138
145
  chat_id: chatId,
139
146
  message_id: editMessageId,
140
147
  ...extra,
141
148
  });
149
+ startIndex = 1;
150
+ for (const chunk of chunks.slice(startIndex)) {
151
+ await send(bot, chatId, chunk, telegramOptions);
152
+ }
153
+ return edited;
142
154
  }
143
155
  catch (err) {
144
156
  const description = err?.response?.body?.description || err?.message || '';
145
- 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
+ }
146
162
  return null;
163
+ }
147
164
  console.warn('[telegram-control] editMessageText failed:', description || err);
148
165
  }
149
166
  }
150
- try {
151
- return await bot.sendMessage(chatId, messageText, extra);
152
- }
153
- catch {
154
- const fallback = { ...extra };
155
- delete fallback.parse_mode;
156
- return bot.sendMessage(chatId, messageText, fallback);
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
+ }
157
177
  }
178
+ return result;
158
179
  }
159
180
  function localApiBase() {
160
181
  const port = process.env.SERVER_PORT || process.env.PORT || '3001';
@@ -164,19 +185,34 @@ function getOrCreateTelegramApiKey(userId) {
164
185
  const existing = apiKeysDb
165
186
  .getApiKeys(userId)
166
187
  .find((key) => key.key_name === 'Telegram Control' && Boolean(key.is_active));
167
- 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
+ }
168
195
  return existing.api_key;
169
- return apiKeysDb.createApiKey(userId, 'Telegram Control').apiKey;
196
+ }
197
+ return apiKeysDb.createApiKey(userId, 'Telegram Control', TELEGRAM_CONTROL_SCOPES).apiKey;
170
198
  }
171
199
  async function localApi(userId, path, { method = 'GET', body } = {}) {
172
200
  const apiKey = getOrCreateTelegramApiKey(userId);
173
- const response = await fetch(`${localApiBase()}${path}`, {
174
- method,
175
- headers: {
176
- 'Content-Type': 'application/json',
177
- 'X-API-Key': apiKey,
178
- },
179
- body: body === undefined ? undefined : JSON.stringify(body),
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;
180
216
  });
181
217
  const data = await response.json().catch(() => ({}));
182
218
  if (!response.ok) {
@@ -428,6 +464,36 @@ function extractAssistantText(response) {
428
464
  }
429
465
  return chunks.join('\n\n').trim();
430
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
+ }
431
497
  async function runAgent({ bot, chatId, link, prompt }) {
432
498
  const lang = languageFor(link);
433
499
  const state = getState(link.user_id);
@@ -440,33 +506,49 @@ async function runAgent({ bot, chatId, link, prompt }) {
440
506
  await showProjectMenu({ bot, chatId, link });
441
507
  return;
442
508
  }
509
+ const provider = resolveTelegramProvider(state);
510
+ const model = resolveTelegramModel(state);
443
511
  if (state.progressMode !== 'final') {
444
512
  await send(bot, chatId, t(lang, 'control.agentStarted', {
445
- provider: state.selectedProvider,
513
+ provider,
446
514
  project: state.selectedProjectName || state.selectedProjectPath,
447
515
  }));
448
516
  }
449
- const response = await localApi(link.user_id, '/api/agent', {
450
- method: 'POST',
451
- body: {
452
- projectPath: state.selectedProjectPath,
453
- provider: state.selectedProvider,
454
- model: state.selectedModel || undefined,
455
- message: prompt,
456
- cleanup: false,
457
- stream: false,
458
- },
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
+ }),
459
532
  });
533
+ if (!result.ok) {
534
+ await sendToolFailure({ bot, chatId, link, result });
535
+ return;
536
+ }
537
+ const response = result.data;
460
538
  const assistantText = extractAssistantText(response);
461
539
  const statusLine = response.success === false
462
540
  ? t(lang, 'control.agentFailed', { error: response.error || 'provider returned no answer' })
463
- : t(lang, 'control.agentDone', { provider: state.selectedProvider, session: response.sessionId ? ` (${response.sessionId})` : '' });
541
+ : t(lang, 'control.agentDone', { provider, session: response.sessionId ? ` (${response.sessionId})` : '' });
464
542
  await send(bot, chatId, `${statusLine}\n\n${assistantText || response.error || t(lang, 'control.noAssistantText')}`);
465
543
  }
466
544
  export async function runWorkflow({ bot, chatId, link, input }) {
467
545
  const lang = languageFor(link);
468
546
  const state = getState(link.user_id);
469
547
  const workflowId = state.selectedWorkflowId;
548
+ if (!state.remoteControlEnabled) {
549
+ await send(bot, chatId, t(lang, 'control.disabled'));
550
+ return;
551
+ }
470
552
  if (!workflowId) {
471
553
  await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'));
472
554
  await showWorkflowMenu({ bot, chatId, link });
@@ -477,26 +559,173 @@ export async function runWorkflow({ bot, chatId, link, input }) {
477
559
  await showProjectMenu({ bot, chatId, link });
478
560
  return;
479
561
  }
480
- const run = await localApi(link.user_id, `/api/orchestration/workflows/${workflowId}/runs`, {
481
- method: 'POST',
482
- body: {
483
- input,
484
- metadata: {
485
- projectId: state.selectedProjectName,
486
- projectName: state.selectedProjectName,
487
- projectPath: state.selectedProjectPath,
488
- workspaceTarget: 'selected_project',
489
- telegram: true,
490
- preferredProvider: state.selectedProvider,
491
- preferredModel: state.selectedModel,
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
+ },
492
580
  },
493
- },
581
+ }),
494
582
  });
583
+ if (!result.ok) {
584
+ await sendToolFailure({ bot, chatId, link, result });
585
+ return;
586
+ }
587
+ const run = result.data;
495
588
  await send(bot, chatId, t(lang, 'control.workflowStarted', { runId: run.id, workflowId }));
496
589
  monitorWorkflowRun({ bot, chatId, link, runId: run.id }).catch((error) => {
497
590
  console.warn('[telegram-control] workflow monitor failed:', error?.message || error);
498
591
  });
499
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
+ }
500
729
  async function fetchRun(userId, runId) {
501
730
  return localApi(userId, `/api/orchestration/workflows/runs/${runId}`);
502
731
  }
@@ -560,18 +789,47 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
560
789
  runMonitors.delete(runId);
561
790
  }
562
791
  }
563
- export async function startCliInstall({ bot, chatId, link, provider }) {
792
+ export async function startCliInstall({ bot, chatId, link, provider, editMessageId, confirmed = false }) {
564
793
  const lang = languageFor(link);
565
- const data = await localApi(link.user_id, `/api/providers/${provider}/install`, { method: 'POST' });
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;
566
824
  if (data?.manual) {
567
- 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 });
568
826
  return;
569
827
  }
570
828
  await send(bot, chatId, t(lang, 'control.installStarted', {
571
829
  provider,
572
830
  jobId: data.jobId,
573
831
  command: data.installCmd || 'internal installer',
574
- }));
832
+ }), { editMessageId });
575
833
  }
576
834
  async function showInstallMenu({ bot, chatId, link, editMessageId }) {
577
835
  const lang = languageFor(link);
@@ -747,15 +1005,12 @@ async function handleCommand({ bot, chatId, link, text }) {
747
1005
  await send(bot, chatId, t(lang, 'control.cancelUsage'));
748
1006
  return true;
749
1007
  }
750
- const run = await localApi(link.user_id, `/api/orchestration/workflows/runs/${encodeURIComponent(argText)}/cancel`, {
751
- method: 'POST',
752
- });
753
- await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }));
1008
+ await cancelRun({ bot, chatId, link, runId: argText });
754
1009
  return true;
755
1010
  }
756
1011
  return false;
757
1012
  }
758
- export async function handleTelegramControlMessage({ bot, msg, link }) {
1013
+ async function handleTelegramControlMessageInternal({ bot, msg, link }) {
759
1014
  const chatId = msg.chat.id;
760
1015
  const text = String(msg.text || '').trim();
761
1016
  if (!text)
@@ -772,7 +1027,11 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
772
1027
  return true;
773
1028
  if (await handleCommand({ bot, chatId, link, text }))
774
1029
  return true;
1030
+ if (await handleRoutedIntent({ bot, chatId, link, text }))
1031
+ return true;
775
1032
  const state = getState(link.user_id);
1033
+ if (state.routerEnabled === false)
1034
+ return false;
776
1035
  if (!state.remoteControlEnabled) {
777
1036
  await send(bot, chatId, t(languageFor(link), 'control.disabled'));
778
1037
  return true;
@@ -780,6 +1039,21 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
780
1039
  await runAgent({ bot, chatId, link, prompt: text });
781
1040
  return true;
782
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
+ }
783
1057
  async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
784
1058
  const lang = languageFor(link);
785
1059
  const run = await fetchRun(link.user_id, runId);
@@ -793,7 +1067,7 @@ async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
793
1067
  },
794
1068
  });
795
1069
  }
796
- export async function handleTelegramControlCallback({ bot, query, link }) {
1070
+ async function handleTelegramControlCallbackInternal({ bot, query, link }) {
797
1071
  const chatId = query.message?.chat?.id;
798
1072
  if (!chatId)
799
1073
  return;
@@ -807,6 +1081,33 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
807
1081
  }
808
1082
  await bot.answerCallbackQuery(query.id).catch(() => { });
809
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
+ }
810
1111
  if (action === 'menu')
811
1112
  return showMainMenu({ bot, chatId, link, editMessageId });
812
1113
  if (action === 'projects')
@@ -889,20 +1190,26 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
889
1190
  if (action === 'run_detail')
890
1191
  return showRunDetail({ bot, chatId, link, runId: payload.runId, editMessageId });
891
1192
  if (action === 'run_cancel') {
892
- const run = await localApi(link.user_id, `/api/orchestration/workflows/runs/${encodeURIComponent(payload.runId)}/cancel`, {
893
- method: 'POST',
894
- });
895
- 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 });
896
1194
  return;
897
1195
  }
898
1196
  if (action === 'approval_decide') {
899
- const result = await localApi(link.user_id, `/api/orchestration/workflows/approvals/${encodeURIComponent(payload.approvalId)}`, {
900
- method: 'POST',
901
- body: {
902
- allow: payload.allow === true,
903
- source: 'telegram',
904
- },
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
+ }),
905
1207
  });
1208
+ if (!toolResult.ok) {
1209
+ await sendToolFailure({ bot, chatId, link, result: toolResult, editMessageId });
1210
+ return;
1211
+ }
1212
+ const result = toolResult.data;
906
1213
  const lang = languageFor(link);
907
1214
  await send(bot, chatId, t(lang, 'control.approvalDecided', {
908
1215
  approvalId: payload.approvalId,
@@ -912,7 +1219,7 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
912
1219
  return showApprovalQueue({ bot, chatId, link });
913
1220
  }
914
1221
  if (action === 'install_provider')
915
- return startCliInstall({ bot, chatId, link, provider: payload.provider });
1222
+ return startCliInstall({ bot, chatId, link, provider: payload.provider, editMessageId });
916
1223
  if (action === 'auth_provider') {
917
1224
  await send(bot, chatId, `${payload.provider} login:\n${AUTH_HELP[payload.provider] || t(languageFor(link), 'control.providerAuthFallback')}`, { editMessageId });
918
1225
  return;
@@ -937,4 +1244,20 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
937
1244
  });
938
1245
  }
939
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
+ }
940
1263
  //# sourceMappingURL=control-center.js.map