@pixelbyte-software/pixcode 1.53.4 → 1.53.6
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-DP7jnMyc.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/openai-codex.js +5 -0
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/routes/agent.js +17 -7
- package/dist-server/server/routes/agent.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 +914 -98
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/telegram-gateway.js +286 -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 +72 -0
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/telegram-control.mjs +50 -2
- package/server/database/db.js +28 -0
- package/server/openai-codex.js +5 -0
- package/server/routes/agent.js +17 -7
- package/server/routes/telegram.js +25 -2
- package/server/services/telegram/bot.js +8 -4
- package/server/services/telegram/control-center.js +927 -98
- package/server/services/telegram/telegram-gateway.js +314 -0
- package/server/services/telegram/telegram-http-client.js +7 -5
- package/server/services/telegram/translations.js +72 -0
|
@@ -4,13 +4,32 @@ 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
|
+
buildTelegramIntentPrompt,
|
|
12
|
+
clearTelegramConfirmation,
|
|
13
|
+
consumeTelegramConfirmation,
|
|
14
|
+
createTelegramConfirmation,
|
|
15
|
+
enqueueTelegramJob,
|
|
16
|
+
parseTelegramAiIntentResponse,
|
|
17
|
+
resolveTelegramModel,
|
|
18
|
+
resolveTelegramProvider,
|
|
19
|
+
retryWithBackoff,
|
|
20
|
+
runTelegramTool,
|
|
21
|
+
splitTelegramText,
|
|
22
|
+
} from './telegram-gateway.js';
|
|
7
23
|
import { SUPPORTED_LANGUAGES, t } from './translations.js';
|
|
8
24
|
|
|
9
|
-
const PROVIDERS =
|
|
25
|
+
const PROVIDERS = TELEGRAM_PROVIDERS;
|
|
10
26
|
const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
|
|
11
27
|
const CALLBACK_TTL_MS = 10 * 60 * 1000;
|
|
12
28
|
const MAX_CALLBACK_ACTIONS = 1000;
|
|
13
29
|
const MAX_TELEGRAM_TEXT = 3600;
|
|
30
|
+
const ACTIVITY_EDIT_THROTTLE_MS = 1200;
|
|
31
|
+
const ACTIVITY_HEARTBEAT_MS = 8000;
|
|
32
|
+
const INTENT_ROUTER_TIMEOUT_MS = 45_000;
|
|
14
33
|
const callbackActions = new Map();
|
|
15
34
|
const runMonitors = new Map();
|
|
16
35
|
|
|
@@ -130,7 +149,11 @@ function readAction(data) {
|
|
|
130
149
|
callbackActions.delete(id);
|
|
131
150
|
return null;
|
|
132
151
|
}
|
|
133
|
-
return entry;
|
|
152
|
+
return { id, ...entry };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function forgetAction(id) {
|
|
156
|
+
if (id) callbackActions.delete(id);
|
|
134
157
|
}
|
|
135
158
|
|
|
136
159
|
function button(text, action, payload = {}) {
|
|
@@ -150,27 +173,44 @@ async function send(bot, chatId, text, options = {}) {
|
|
|
150
173
|
disable_web_page_preview: true,
|
|
151
174
|
...telegramOptions,
|
|
152
175
|
};
|
|
153
|
-
const
|
|
176
|
+
const chunks = splitTelegramText(text, MAX_TELEGRAM_TEXT);
|
|
177
|
+
const [firstChunk = ''] = chunks;
|
|
178
|
+
let startIndex = 0;
|
|
154
179
|
if (editMessageId && typeof bot.editMessageText === 'function') {
|
|
155
180
|
try {
|
|
156
|
-
|
|
181
|
+
const edited = await bot.editMessageText(firstChunk, {
|
|
157
182
|
chat_id: chatId,
|
|
158
183
|
message_id: editMessageId,
|
|
159
184
|
...extra,
|
|
160
185
|
});
|
|
186
|
+
startIndex = 1;
|
|
187
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
188
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
189
|
+
}
|
|
190
|
+
return edited;
|
|
161
191
|
} catch (err) {
|
|
162
192
|
const description = err?.response?.body?.description || err?.message || '';
|
|
163
|
-
if (/message is not modified/i.test(description))
|
|
193
|
+
if (/message is not modified/i.test(description)) {
|
|
194
|
+
startIndex = 1;
|
|
195
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
196
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
164
200
|
console.warn('[telegram-control] editMessageText failed:', description || err);
|
|
165
201
|
}
|
|
166
202
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
203
|
+
let result = null;
|
|
204
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
205
|
+
try {
|
|
206
|
+
result = await bot.sendMessage(chatId, chunk, extra);
|
|
207
|
+
} catch {
|
|
208
|
+
const fallback = { ...extra };
|
|
209
|
+
delete fallback.parse_mode;
|
|
210
|
+
result = await bot.sendMessage(chatId, chunk, fallback);
|
|
211
|
+
}
|
|
173
212
|
}
|
|
213
|
+
return result;
|
|
174
214
|
}
|
|
175
215
|
|
|
176
216
|
function localApiBase() {
|
|
@@ -182,32 +222,242 @@ function getOrCreateTelegramApiKey(userId) {
|
|
|
182
222
|
const existing = apiKeysDb
|
|
183
223
|
.getApiKeys(userId)
|
|
184
224
|
.find((key) => key.key_name === 'Telegram Control' && Boolean(key.is_active));
|
|
185
|
-
if (existing?.api_key)
|
|
186
|
-
|
|
225
|
+
if (existing?.api_key) {
|
|
226
|
+
const existingScopes = apiKeysDb.normalizeScopes(existing.scopes || []);
|
|
227
|
+
const nextScopes = apiKeysDb.normalizeScopes([...existingScopes, ...TELEGRAM_CONTROL_SCOPES]);
|
|
228
|
+
const hasAllScopes = TELEGRAM_CONTROL_SCOPES.every((scope) => existingScopes.includes(scope));
|
|
229
|
+
if (!hasAllScopes || nextScopes.length !== existingScopes.length) {
|
|
230
|
+
apiKeysDb.updateApiKeyScopes(userId, existing.id, nextScopes);
|
|
231
|
+
}
|
|
232
|
+
return existing.api_key;
|
|
233
|
+
}
|
|
234
|
+
return apiKeysDb.createApiKey(userId, 'Telegram Control', TELEGRAM_CONTROL_SCOPES).apiKey;
|
|
187
235
|
}
|
|
188
236
|
|
|
189
|
-
async function localApi(userId, path, { method = 'GET', body } = {}) {
|
|
237
|
+
async function localApi(userId, path, { method = 'GET', body, timeoutMs = 0 } = {}) {
|
|
190
238
|
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
191
|
-
const response = await
|
|
192
|
-
|
|
239
|
+
const response = await retryWithBackoff(async () => {
|
|
240
|
+
const controller = timeoutMs > 0 ? new AbortController() : null;
|
|
241
|
+
const timeoutId = controller
|
|
242
|
+
? setTimeout(() => controller.abort(new Error(`HTTP request timed out after ${timeoutMs}ms`)), timeoutMs)
|
|
243
|
+
: null;
|
|
244
|
+
const res = await fetch(`${localApiBase()}${path}`, {
|
|
245
|
+
method,
|
|
246
|
+
headers: {
|
|
247
|
+
'Content-Type': 'application/json',
|
|
248
|
+
'X-API-Key': apiKey,
|
|
249
|
+
},
|
|
250
|
+
signal: controller?.signal,
|
|
251
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
252
|
+
}).finally(() => {
|
|
253
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
254
|
+
});
|
|
255
|
+
if ([408, 429, 500, 502, 503, 504].includes(res.status)) {
|
|
256
|
+
const error = new Error(`HTTP ${res.status}`);
|
|
257
|
+
error.status = res.status;
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
return res;
|
|
261
|
+
});
|
|
262
|
+
const data = await response.json().catch(() => ({}));
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
const error = data?.error?.message || data?.error || `HTTP ${response.status}`;
|
|
265
|
+
throw new Error(typeof error === 'string' ? error : JSON.stringify(error));
|
|
266
|
+
}
|
|
267
|
+
return data?.data ?? data;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function localAgentStream(userId, body, onEvent) {
|
|
271
|
+
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
272
|
+
const response = await fetch(`${localApiBase()}/api/agent`, {
|
|
273
|
+
method: 'POST',
|
|
193
274
|
headers: {
|
|
194
275
|
'Content-Type': 'application/json',
|
|
195
276
|
'X-API-Key': apiKey,
|
|
196
277
|
},
|
|
197
|
-
body:
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
...body,
|
|
280
|
+
stream: true,
|
|
281
|
+
}),
|
|
198
282
|
});
|
|
199
|
-
|
|
283
|
+
|
|
200
284
|
if (!response.ok) {
|
|
285
|
+
const data = await response.json().catch(() => ({}));
|
|
201
286
|
const error = data?.error?.message || data?.error || `HTTP ${response.status}`;
|
|
202
287
|
throw new Error(typeof error === 'string' ? error : JSON.stringify(error));
|
|
203
288
|
}
|
|
204
|
-
|
|
289
|
+
|
|
290
|
+
if (!response.body) return;
|
|
291
|
+
|
|
292
|
+
const reader = response.body.getReader();
|
|
293
|
+
const decoder = new TextDecoder();
|
|
294
|
+
let buffer = '';
|
|
295
|
+
|
|
296
|
+
const consumeBlock = async (block) => {
|
|
297
|
+
const lines = String(block || '').split('\n');
|
|
298
|
+
for (const line of lines) {
|
|
299
|
+
if (!line.startsWith('data:')) continue;
|
|
300
|
+
const payload = line.slice(5).trim();
|
|
301
|
+
if (!payload) continue;
|
|
302
|
+
let event;
|
|
303
|
+
try {
|
|
304
|
+
event = JSON.parse(payload);
|
|
305
|
+
} catch {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
await onEvent?.(event);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
for (;;) {
|
|
313
|
+
const { done, value } = await reader.read();
|
|
314
|
+
if (done) break;
|
|
315
|
+
buffer += decoder.decode(value, { stream: true });
|
|
316
|
+
let boundary = buffer.indexOf('\n\n');
|
|
317
|
+
while (boundary !== -1) {
|
|
318
|
+
const block = buffer.slice(0, boundary);
|
|
319
|
+
buffer = buffer.slice(boundary + 2);
|
|
320
|
+
await consumeBlock(block);
|
|
321
|
+
boundary = buffer.indexOf('\n\n');
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const rest = `${buffer}${decoder.decode()}`;
|
|
326
|
+
if (rest.trim()) await consumeBlock(rest);
|
|
205
327
|
}
|
|
206
328
|
|
|
207
329
|
function checked(label) {
|
|
208
330
|
return `${label} ✓`;
|
|
209
331
|
}
|
|
210
332
|
|
|
333
|
+
function formatElapsed(startedAt) {
|
|
334
|
+
const seconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
|
|
335
|
+
if (seconds < 60) return `${seconds}s`;
|
|
336
|
+
const minutes = Math.floor(seconds / 60);
|
|
337
|
+
const rest = seconds % 60;
|
|
338
|
+
return `${minutes}m ${rest}s`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function projectLabel(state) {
|
|
342
|
+
return state.selectedProjectName || state.selectedProjectPath || '-';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function createActivityState({ lang, type = 'agent', provider, project, prompt, mode = 'final' }) {
|
|
346
|
+
return {
|
|
347
|
+
lang,
|
|
348
|
+
type,
|
|
349
|
+
status: 'starting',
|
|
350
|
+
phase: t(lang, 'control.activity.starting'),
|
|
351
|
+
provider,
|
|
352
|
+
project,
|
|
353
|
+
prompt: compact(prompt, 120),
|
|
354
|
+
mode,
|
|
355
|
+
startedAt: Date.now(),
|
|
356
|
+
lastEditAt: 0,
|
|
357
|
+
messageId: null,
|
|
358
|
+
sessionId: null,
|
|
359
|
+
runId: null,
|
|
360
|
+
workflowId: null,
|
|
361
|
+
events: [],
|
|
362
|
+
output: '',
|
|
363
|
+
error: null,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function pushActivityEvent(activity, text) {
|
|
368
|
+
const value = String(text || '').trim();
|
|
369
|
+
if (!value) return;
|
|
370
|
+
if (activity.events.at(-1) === value) return;
|
|
371
|
+
activity.events.push(value);
|
|
372
|
+
if (activity.events.length > 8) activity.events.splice(0, activity.events.length - 8);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function activityTitle(activity) {
|
|
376
|
+
if (activity.type === 'workflow') return t(activity.lang, 'control.activity.workflowTitle');
|
|
377
|
+
if (activity.type === 'router') return t(activity.lang, 'control.activity.routerTitle');
|
|
378
|
+
return t(activity.lang, 'control.activity.agentTitle');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function renderActivity(activity, { finalText = null } = {}) {
|
|
382
|
+
const output = finalText || activity.output;
|
|
383
|
+
const lines = [
|
|
384
|
+
`${activity.status === 'failed' ? '❌' : activity.status === 'done' ? '✅' : activity.status === 'running' ? '🔧' : '⏳'} ${activityTitle(activity)}`,
|
|
385
|
+
'',
|
|
386
|
+
`🤖 ${t(activity.lang, 'control.activity.provider')}: ${activity.provider || '-'}`,
|
|
387
|
+
`📁 ${t(activity.lang, 'control.activity.project')}: ${compact(activity.project, 90)}`,
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
if (activity.sessionId) lines.push(`🧵 ${t(activity.lang, 'control.activity.session')}: ${activity.sessionId}`);
|
|
391
|
+
if (activity.runId) lines.push(`🧭 ${t(activity.lang, 'control.activity.run')}: ${activity.runId}`);
|
|
392
|
+
if (activity.workflowId) lines.push(`🧩 ${t(activity.lang, 'control.activity.workflow')}: ${activity.workflowId}`);
|
|
393
|
+
lines.push(`⏱ ${t(activity.lang, 'control.activity.elapsed')}: ${formatElapsed(activity.startedAt)}`);
|
|
394
|
+
lines.push(`📌 ${t(activity.lang, 'control.activity.status')}: ${activity.phase}`);
|
|
395
|
+
|
|
396
|
+
const visibleEvents = activity.mode === 'final'
|
|
397
|
+
? activity.events.slice(-4)
|
|
398
|
+
: activity.events;
|
|
399
|
+
if (visibleEvents.length > 0) {
|
|
400
|
+
lines.push('', `🛠 ${t(activity.lang, 'control.activity.work')}:`);
|
|
401
|
+
for (const event of visibleEvents) lines.push(`• ${event}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (activity.error) {
|
|
405
|
+
lines.push('', `⚠️ ${truncate(activity.error, 700)}`);
|
|
406
|
+
} else if (output) {
|
|
407
|
+
lines.push('', `💬 ${t(activity.lang, 'control.activity.output')}:`);
|
|
408
|
+
lines.push(truncate(output, 1800));
|
|
409
|
+
} else if (activity.prompt) {
|
|
410
|
+
lines.push('', `📝 ${compact(activity.prompt, 220)}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return truncate(lines.join('\n'), 3400);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function createTelegramActivity({ bot, chatId, link, type = 'agent', prompt = '', phase = null }) {
|
|
417
|
+
const lang = languageFor(link);
|
|
418
|
+
const state = getState(link.user_id);
|
|
419
|
+
const activity = createActivityState({
|
|
420
|
+
lang,
|
|
421
|
+
type,
|
|
422
|
+
provider: resolveTelegramProvider(state),
|
|
423
|
+
project: projectLabel(state),
|
|
424
|
+
prompt,
|
|
425
|
+
mode: state.progressMode,
|
|
426
|
+
});
|
|
427
|
+
if (phase) activity.phase = phase;
|
|
428
|
+
const sent = await send(bot, chatId, renderActivity(activity), { parse_mode: undefined });
|
|
429
|
+
activity.messageId = sent?.message_id || sent?.message?.message_id || null;
|
|
430
|
+
activity.lastEditAt = Date.now();
|
|
431
|
+
return activity;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function editTelegramActivity({ bot, chatId, activity, force = false, reply_markup }) {
|
|
435
|
+
if (!activity) return null;
|
|
436
|
+
const now = Date.now();
|
|
437
|
+
if (!force && now - activity.lastEditAt < ACTIVITY_EDIT_THROTTLE_MS) return null;
|
|
438
|
+
const sent = await send(bot, chatId, renderActivity(activity), {
|
|
439
|
+
editMessageId: activity.messageId,
|
|
440
|
+
parse_mode: undefined,
|
|
441
|
+
reply_markup,
|
|
442
|
+
});
|
|
443
|
+
activity.messageId = sent?.message_id || sent?.message?.message_id || activity.messageId;
|
|
444
|
+
activity.lastEditAt = now;
|
|
445
|
+
return sent;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function startActivityHeartbeat({ bot, chatId, activity }) {
|
|
449
|
+
return setInterval(() => {
|
|
450
|
+
if (!activity || activity.status === 'done' || activity.status === 'failed') return;
|
|
451
|
+
if (activity.status === 'starting') {
|
|
452
|
+
activity.status = 'running';
|
|
453
|
+
activity.phase = t(activity.lang, 'control.activity.thinking');
|
|
454
|
+
}
|
|
455
|
+
editTelegramActivity({ bot, chatId, activity }).catch((error) => {
|
|
456
|
+
console.warn('[telegram-control] activity heartbeat failed:', error?.message || error);
|
|
457
|
+
});
|
|
458
|
+
}, ACTIVITY_HEARTBEAT_MS);
|
|
459
|
+
}
|
|
460
|
+
|
|
211
461
|
function stateSummary(lang, state) {
|
|
212
462
|
return [
|
|
213
463
|
`${t(lang, 'control.summary.project')}: ${state.selectedProjectName || t(lang, 'control.notSelected')}`,
|
|
@@ -258,11 +508,12 @@ async function listProjects() {
|
|
|
258
508
|
return projects.slice(0, 20);
|
|
259
509
|
}
|
|
260
510
|
|
|
261
|
-
async function showProjectMenu({ bot, chatId, link, editMessageId }) {
|
|
511
|
+
async function showProjectMenu({ bot, chatId, link, editMessageId, notice }) {
|
|
262
512
|
const lang = languageFor(link);
|
|
263
513
|
const projects = await listProjects();
|
|
264
514
|
if (projects.length === 0) {
|
|
265
|
-
|
|
515
|
+
const prefix = notice ? `${notice}\n\n` : '';
|
|
516
|
+
await send(bot, chatId, `${prefix}${t(lang, 'control.noProjects')}`, { editMessageId });
|
|
266
517
|
return;
|
|
267
518
|
}
|
|
268
519
|
|
|
@@ -275,7 +526,8 @@ async function showProjectMenu({ bot, chatId, link, editMessageId }) {
|
|
|
275
526
|
displayName: project.displayName || project.name,
|
|
276
527
|
},
|
|
277
528
|
));
|
|
278
|
-
|
|
529
|
+
const prefix = notice ? `${notice}\n\n` : '';
|
|
530
|
+
await send(bot, chatId, `${prefix}${t(lang, 'control.pickProject')}`, {
|
|
279
531
|
editMessageId,
|
|
280
532
|
reply_markup: { inline_keyboard: rows(buttons, 1) },
|
|
281
533
|
});
|
|
@@ -487,89 +739,536 @@ function extractAssistantText(response) {
|
|
|
487
739
|
return chunks.join('\n\n').trim();
|
|
488
740
|
}
|
|
489
741
|
|
|
490
|
-
|
|
742
|
+
function extractTextFromEvent(event) {
|
|
743
|
+
if (!event || typeof event !== 'object') return '';
|
|
744
|
+
if (typeof event.content === 'string') return event.content;
|
|
745
|
+
if (Array.isArray(event.content)) {
|
|
746
|
+
return event.content
|
|
747
|
+
.map((part) => (typeof part === 'string' ? part : part?.text || ''))
|
|
748
|
+
.join('');
|
|
749
|
+
}
|
|
750
|
+
const legacyContent = event.data?.message?.content || event.message?.content;
|
|
751
|
+
if (Array.isArray(legacyContent)) {
|
|
752
|
+
return legacyContent
|
|
753
|
+
.map((part) => (typeof part === 'string' ? part : part?.text || ''))
|
|
754
|
+
.join('');
|
|
755
|
+
}
|
|
756
|
+
if (typeof legacyContent === 'string') return legacyContent;
|
|
757
|
+
return '';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function describeToolInput(toolName, input) {
|
|
761
|
+
if (!input || typeof input !== 'object') return '';
|
|
762
|
+
if (toolName === 'Bash' && typeof input.command === 'string') {
|
|
763
|
+
return compact(input.command, 110);
|
|
764
|
+
}
|
|
765
|
+
if (toolName === 'WebSearch' && typeof input.query === 'string') {
|
|
766
|
+
return compact(input.query, 110);
|
|
767
|
+
}
|
|
768
|
+
if (toolName === 'FileChanges') {
|
|
769
|
+
return compact(JSON.stringify(input), 110);
|
|
770
|
+
}
|
|
771
|
+
const keys = Object.keys(input).slice(0, 3);
|
|
772
|
+
if (keys.length === 0) return '';
|
|
773
|
+
return compact(keys.map((key) => `${key}: ${String(input[key])}`).join(', '), 110);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function applyAgentStreamEvent(activity, event) {
|
|
777
|
+
if (!event || typeof event !== 'object') return;
|
|
778
|
+
|
|
779
|
+
const sessionId = event.actualSessionId || event.newSessionId || event.sessionId || event.threadId;
|
|
780
|
+
if (sessionId) activity.sessionId = sessionId;
|
|
781
|
+
|
|
782
|
+
if (event.type === 'status' && event.message) {
|
|
783
|
+
activity.status = 'running';
|
|
784
|
+
activity.phase = compact(event.message, 120);
|
|
785
|
+
pushActivityEvent(activity, `⏳ ${compact(event.message, 120)}`);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (event.type === 'done' || event.kind === 'complete' || event.kind === 'stream_end') {
|
|
789
|
+
activity.status = 'done';
|
|
790
|
+
activity.phase = t(activity.lang, 'control.activity.done');
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (event.type === 'error' || event.kind === 'error') {
|
|
794
|
+
activity.status = 'failed';
|
|
795
|
+
activity.phase = t(activity.lang, 'control.activity.failed');
|
|
796
|
+
activity.error = event.error || event.message || event.content || t(activity.lang, 'error.generic');
|
|
797
|
+
pushActivityEvent(activity, `❌ ${compact(activity.error, 120)}`);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (event.kind === 'session_created') {
|
|
801
|
+
activity.status = 'running';
|
|
802
|
+
activity.phase = t(activity.lang, 'control.activity.sessionStarted');
|
|
803
|
+
pushActivityEvent(activity, `🧵 ${t(activity.lang, 'control.activity.sessionStarted')}`);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (event.kind === 'thinking') {
|
|
807
|
+
activity.status = 'running';
|
|
808
|
+
activity.phase = t(activity.lang, 'control.activity.thinking');
|
|
809
|
+
const thought = extractTextFromEvent(event);
|
|
810
|
+
if (thought) pushActivityEvent(activity, `🧠 ${compact(thought, 120)}`);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (event.kind === 'stream_delta' || event.kind === 'text') {
|
|
814
|
+
activity.status = 'running';
|
|
815
|
+
activity.phase = t(activity.lang, 'control.activity.responding');
|
|
816
|
+
activity.output = `${activity.output}${extractTextFromEvent(event)}`;
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
if (event.type === 'claude-response' && event.data?.type === 'assistant') {
|
|
820
|
+
activity.status = 'running';
|
|
821
|
+
activity.phase = t(activity.lang, 'control.activity.responding');
|
|
822
|
+
activity.output = `${activity.output}${extractTextFromEvent(event)}`;
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (event.kind === 'tool_use') {
|
|
826
|
+
activity.status = 'running';
|
|
827
|
+
activity.phase = t(activity.lang, 'control.activity.working');
|
|
828
|
+
const toolName = event.toolName || 'Tool';
|
|
829
|
+
const input = describeToolInput(toolName, event.toolInput);
|
|
830
|
+
const icon = toolName === 'Bash'
|
|
831
|
+
? '💻'
|
|
832
|
+
: toolName === 'FileChanges'
|
|
833
|
+
? '📝'
|
|
834
|
+
: toolName === 'WebSearch'
|
|
835
|
+
? '🔎'
|
|
836
|
+
: '🔧';
|
|
837
|
+
pushActivityEvent(activity, `${icon} ${toolName}${input ? `: ${input}` : ''}`);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (event.kind === 'tool_result') {
|
|
841
|
+
activity.status = 'running';
|
|
842
|
+
activity.phase = t(activity.lang, 'control.activity.working');
|
|
843
|
+
const label = event.isError
|
|
844
|
+
? t(activity.lang, 'control.activity.toolFailed')
|
|
845
|
+
: t(activity.lang, 'control.activity.toolDone');
|
|
846
|
+
pushActivityEvent(activity, `${event.isError ? '⚠️' : '✅'} ${label}`);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (event.kind === 'status' && event.text === 'token_budget') {
|
|
850
|
+
const used = event.tokenBudget?.used;
|
|
851
|
+
if (used) pushActivityEvent(activity, `📊 ${t(activity.lang, 'control.activity.tokens')}: ${used}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function confirmationLabel(lang, action, payload = {}) {
|
|
856
|
+
if (action === 'install_provider') {
|
|
857
|
+
return t(lang, 'control.confirmInstall', { provider: payload.provider || '' });
|
|
858
|
+
}
|
|
859
|
+
if (action === 'run_cancel') {
|
|
860
|
+
return t(lang, 'control.confirmCancelRun', { runId: payload.runId || '' });
|
|
861
|
+
}
|
|
862
|
+
return t(lang, 'control.confirmationRequired');
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async function requestConfirmation({ bot, chatId, link, action, payload = {}, editMessageId }) {
|
|
866
|
+
const lang = languageFor(link);
|
|
867
|
+
const pending = createTelegramConfirmation(link.user_id, action, payload);
|
|
868
|
+
await send(bot, chatId, confirmationLabel(lang, action, payload), {
|
|
869
|
+
editMessageId,
|
|
870
|
+
reply_markup: {
|
|
871
|
+
inline_keyboard: [[
|
|
872
|
+
button(t(lang, 'control.button.confirm'), 'confirm_action', { id: pending.id }),
|
|
873
|
+
button(t(lang, 'control.button.cancel'), 'cancel_confirmation', { id: pending.id }),
|
|
874
|
+
]],
|
|
875
|
+
},
|
|
876
|
+
});
|
|
877
|
+
return { ok: false, requiresConfirmation: true };
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async function sendToolFailure({ bot, chatId, link, result, editMessageId }) {
|
|
881
|
+
const lang = languageFor(link);
|
|
882
|
+
const message = result?.message === 'REMOTE_CONTROL_DISABLED'
|
|
883
|
+
? t(lang, 'control.disabled')
|
|
884
|
+
: (result?.message || t(lang, 'error.generic'));
|
|
885
|
+
await send(bot, chatId, message, { editMessageId });
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
async function runAgent({ bot, chatId, link, prompt, activity = null }) {
|
|
491
889
|
const lang = languageFor(link);
|
|
492
890
|
const state = getState(link.user_id);
|
|
493
891
|
if (!state.remoteControlEnabled) {
|
|
494
|
-
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
892
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId: activity?.messageId });
|
|
495
893
|
return;
|
|
496
894
|
}
|
|
497
895
|
if (!state.selectedProjectPath) {
|
|
498
|
-
await
|
|
499
|
-
await showProjectMenu({ bot, chatId, link });
|
|
896
|
+
await showProjectMenu({ bot, chatId, link, editMessageId: activity?.messageId, notice: t(lang, 'control.selectProjectFirst') });
|
|
500
897
|
return;
|
|
501
898
|
}
|
|
502
899
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
900
|
+
const provider = resolveTelegramProvider(state);
|
|
901
|
+
const model = resolveTelegramModel(state);
|
|
902
|
+
const active = activity || await createTelegramActivity({
|
|
903
|
+
bot,
|
|
904
|
+
chatId,
|
|
905
|
+
link,
|
|
906
|
+
type: 'agent',
|
|
907
|
+
prompt,
|
|
908
|
+
phase: t(lang, 'control.activity.startingProvider', { provider }),
|
|
909
|
+
});
|
|
910
|
+
active.type = 'agent';
|
|
911
|
+
active.provider = provider;
|
|
912
|
+
active.project = projectLabel(state);
|
|
913
|
+
active.status = 'running';
|
|
914
|
+
active.phase = t(lang, 'control.activity.startingProvider', { provider });
|
|
915
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
916
|
+
|
|
917
|
+
const heartbeat = startActivityHeartbeat({ bot, chatId, activity: active });
|
|
918
|
+
let streamFailed = null;
|
|
919
|
+
try {
|
|
920
|
+
await localAgentStream(link.user_id, {
|
|
513
921
|
projectPath: state.selectedProjectPath,
|
|
514
|
-
provider
|
|
515
|
-
model:
|
|
516
|
-
message: prompt,
|
|
922
|
+
provider,
|
|
923
|
+
model: model || undefined,
|
|
924
|
+
message: buildTelegramAgentPrompt(prompt, state),
|
|
517
925
|
cleanup: false,
|
|
518
|
-
|
|
519
|
-
},
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
926
|
+
permissionMode: 'default',
|
|
927
|
+
}, async (event) => {
|
|
928
|
+
applyAgentStreamEvent(active, event);
|
|
929
|
+
await editTelegramActivity({ bot, chatId, activity: active });
|
|
930
|
+
});
|
|
931
|
+
} catch (error) {
|
|
932
|
+
streamFailed = error;
|
|
933
|
+
} finally {
|
|
934
|
+
clearInterval(heartbeat);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (streamFailed) {
|
|
938
|
+
active.status = 'failed';
|
|
939
|
+
active.phase = t(lang, 'control.activity.failed');
|
|
940
|
+
active.error = streamFailed.message || t(lang, 'error.generic');
|
|
941
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
active.status = active.error ? 'failed' : 'done';
|
|
946
|
+
active.phase = active.error ? t(lang, 'control.activity.failed') : t(lang, 'control.activity.done');
|
|
947
|
+
if (!active.output && !active.error) active.output = t(lang, 'control.noAssistantText');
|
|
948
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
526
949
|
}
|
|
527
950
|
|
|
528
|
-
export async function runWorkflow({ bot, chatId, link, input }) {
|
|
951
|
+
export async function runWorkflow({ bot, chatId, link, input, activity = null }) {
|
|
529
952
|
const lang = languageFor(link);
|
|
530
953
|
const state = getState(link.user_id);
|
|
531
954
|
const workflowId = state.selectedWorkflowId;
|
|
955
|
+
if (!state.remoteControlEnabled) {
|
|
956
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId: activity?.messageId });
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
532
959
|
if (!workflowId) {
|
|
533
|
-
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'));
|
|
534
|
-
await showWorkflowMenu({ bot, chatId, link });
|
|
960
|
+
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'), { editMessageId: activity?.messageId });
|
|
961
|
+
await showWorkflowMenu({ bot, chatId, link, editMessageId: activity?.messageId });
|
|
535
962
|
return;
|
|
536
963
|
}
|
|
537
964
|
if (!state.selectedProjectPath) {
|
|
538
|
-
await
|
|
539
|
-
await showProjectMenu({ bot, chatId, link });
|
|
965
|
+
await showProjectMenu({ bot, chatId, link, editMessageId: activity?.messageId, notice: t(lang, 'control.selectProjectFirst') });
|
|
540
966
|
return;
|
|
541
967
|
}
|
|
542
968
|
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
969
|
+
const provider = resolveTelegramProvider(state);
|
|
970
|
+
const model = resolveTelegramModel(state);
|
|
971
|
+
const active = activity || await createTelegramActivity({
|
|
972
|
+
bot,
|
|
973
|
+
chatId,
|
|
974
|
+
link,
|
|
975
|
+
type: 'workflow',
|
|
976
|
+
prompt: input,
|
|
977
|
+
phase: t(lang, 'control.activity.startingWorkflow'),
|
|
978
|
+
});
|
|
979
|
+
active.type = 'workflow';
|
|
980
|
+
active.provider = provider;
|
|
981
|
+
active.project = projectLabel(state);
|
|
982
|
+
active.workflowId = workflowId;
|
|
983
|
+
active.status = 'running';
|
|
984
|
+
active.phase = t(lang, 'control.activity.startingWorkflow');
|
|
985
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
986
|
+
|
|
987
|
+
const result = await runTelegramTool({
|
|
988
|
+
userId: link.user_id,
|
|
989
|
+
action: 'run_workflow',
|
|
990
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/${workflowId}/runs`, {
|
|
991
|
+
method: 'POST',
|
|
992
|
+
body: {
|
|
993
|
+
input,
|
|
994
|
+
metadata: {
|
|
995
|
+
projectId: state.selectedProjectName,
|
|
996
|
+
projectName: state.selectedProjectName,
|
|
997
|
+
projectPath: state.selectedProjectPath,
|
|
998
|
+
workspaceTarget: 'selected_project',
|
|
999
|
+
telegram: true,
|
|
1000
|
+
preferredProvider: provider,
|
|
1001
|
+
preferredModel: model,
|
|
1002
|
+
},
|
|
555
1003
|
},
|
|
556
|
-
},
|
|
1004
|
+
}),
|
|
557
1005
|
});
|
|
558
|
-
|
|
559
|
-
|
|
1006
|
+
if (!result.ok) {
|
|
1007
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId: active.messageId });
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
const run = result.data;
|
|
1011
|
+
active.runId = run.id;
|
|
1012
|
+
active.phase = t(lang, 'control.activity.workflowRunning');
|
|
1013
|
+
pushActivityEvent(active, `🧭 ${t(lang, 'control.workflowStarted', { runId: run.id, workflowId }).replace('\n', ' ')}`);
|
|
1014
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
1015
|
+
monitorWorkflowRun({ bot, chatId, link, runId: run.id, activity: active }).catch((error) => {
|
|
560
1016
|
console.warn('[telegram-control] workflow monitor failed:', error?.message || error);
|
|
561
1017
|
});
|
|
562
1018
|
}
|
|
563
1019
|
|
|
1020
|
+
async function cancelRun({ bot, chatId, link, runId, editMessageId, confirmed = false }) {
|
|
1021
|
+
const lang = languageFor(link);
|
|
1022
|
+
if (!runId) {
|
|
1023
|
+
await send(bot, chatId, t(lang, 'control.cancelUsage'), { editMessageId });
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const state = getState(link.user_id);
|
|
1027
|
+
if (state.remoteControlEnabled === false) {
|
|
1028
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId });
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
if (state.confirmationPolicy === 'strict' && !confirmed) {
|
|
1032
|
+
await requestConfirmation({
|
|
1033
|
+
bot,
|
|
1034
|
+
chatId,
|
|
1035
|
+
link,
|
|
1036
|
+
action: 'run_cancel',
|
|
1037
|
+
payload: { runId },
|
|
1038
|
+
editMessageId,
|
|
1039
|
+
});
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const result = await runTelegramTool({
|
|
1044
|
+
userId: link.user_id,
|
|
1045
|
+
action: 'run_cancel',
|
|
1046
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
1047
|
+
method: 'POST',
|
|
1048
|
+
}),
|
|
1049
|
+
});
|
|
1050
|
+
if (!result.ok) {
|
|
1051
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId });
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const run = result.data;
|
|
1055
|
+
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }), { editMessageId });
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
async function executeConfirmedAction({ bot, chatId, link, confirmation, editMessageId }) {
|
|
1059
|
+
if (confirmation.action === 'install_provider') {
|
|
1060
|
+
await startCliInstall({
|
|
1061
|
+
bot,
|
|
1062
|
+
chatId,
|
|
1063
|
+
link,
|
|
1064
|
+
provider: confirmation.payload?.provider,
|
|
1065
|
+
editMessageId,
|
|
1066
|
+
confirmed: true,
|
|
1067
|
+
});
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
if (confirmation.action === 'run_cancel') {
|
|
1071
|
+
await cancelRun({
|
|
1072
|
+
bot,
|
|
1073
|
+
chatId,
|
|
1074
|
+
link,
|
|
1075
|
+
runId: confirmation.payload?.runId,
|
|
1076
|
+
editMessageId,
|
|
1077
|
+
confirmed: true,
|
|
1078
|
+
});
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
return false;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
async function findProjectByQuery(query) {
|
|
1085
|
+
const projects = await listProjects();
|
|
1086
|
+
const needle = String(query || '').trim().toLocaleLowerCase('tr');
|
|
1087
|
+
if (!needle) return null;
|
|
1088
|
+
return projects.find((project) => {
|
|
1089
|
+
const candidates = [project.name, project.displayName, project.fullPath, project.path]
|
|
1090
|
+
.filter(Boolean)
|
|
1091
|
+
.map((value) => String(value).toLocaleLowerCase('tr'));
|
|
1092
|
+
return candidates.some((candidate) => candidate === needle || candidate.includes(needle));
|
|
1093
|
+
}) || null;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async function listWorkflowSummaries(userId) {
|
|
1097
|
+
try {
|
|
1098
|
+
const data = await localApi(userId, '/api/orchestration/workflows', { timeoutMs: 10_000 });
|
|
1099
|
+
if (Array.isArray(data)) return data;
|
|
1100
|
+
if (Array.isArray(data?.workflows)) return data.workflows;
|
|
1101
|
+
} catch {
|
|
1102
|
+
// Workflow context is helpful for routing but should never block chat input.
|
|
1103
|
+
}
|
|
1104
|
+
return [];
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
async function resolveTelegramAiIntent({ bot, chatId, link, text, activity }) {
|
|
1108
|
+
const state = getState(link.user_id);
|
|
1109
|
+
if (state.routerEnabled === false || state.routerMode !== 'hybrid') {
|
|
1110
|
+
return { action: 'agent_prompt', prompt: text, confidence: 1 };
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const provider = resolveTelegramProvider(state);
|
|
1114
|
+
const model = resolveTelegramModel(state);
|
|
1115
|
+
if (activity) {
|
|
1116
|
+
activity.type = 'router';
|
|
1117
|
+
activity.provider = provider;
|
|
1118
|
+
activity.project = projectLabel(state);
|
|
1119
|
+
activity.status = 'running';
|
|
1120
|
+
activity.phase = t(activity.lang, 'control.activity.interpreting');
|
|
1121
|
+
pushActivityEvent(activity, `🧠 ${t(activity.lang, 'control.activity.interpreting')}`);
|
|
1122
|
+
await editTelegramActivity({ bot, chatId, activity, force: true });
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
try {
|
|
1126
|
+
const projects = await listProjects().catch(() => []);
|
|
1127
|
+
const workflows = await listWorkflowSummaries(link.user_id);
|
|
1128
|
+
const response = await localApi(link.user_id, '/api/agent', {
|
|
1129
|
+
method: 'POST',
|
|
1130
|
+
timeoutMs: INTENT_ROUTER_TIMEOUT_MS,
|
|
1131
|
+
body: {
|
|
1132
|
+
projectPath: state.selectedProjectPath || process.cwd(),
|
|
1133
|
+
provider,
|
|
1134
|
+
model: model || undefined,
|
|
1135
|
+
message: buildTelegramIntentPrompt(text, state, { projects, workflows }),
|
|
1136
|
+
cleanup: false,
|
|
1137
|
+
stream: false,
|
|
1138
|
+
permissionMode: 'plan',
|
|
1139
|
+
},
|
|
1140
|
+
});
|
|
1141
|
+
const assistantText = extractAssistantText(response);
|
|
1142
|
+
const intent = parseTelegramAiIntentResponse(assistantText, text);
|
|
1143
|
+
if (activity) {
|
|
1144
|
+
pushActivityEvent(activity, `🧭 ${intent.action} (${Math.round(intent.confidence * 100)}%)`);
|
|
1145
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1146
|
+
}
|
|
1147
|
+
return intent;
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
if (activity) {
|
|
1150
|
+
pushActivityEvent(activity, `⚠️ ${t(activity.lang, 'control.activity.routerFallback')}`);
|
|
1151
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1152
|
+
}
|
|
1153
|
+
console.warn('[telegram-control] AI intent router fallback:', error?.message || error);
|
|
1154
|
+
return { action: 'agent_prompt', prompt: text, confidence: 0 };
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
async function handleRoutedIntent({ bot, chatId, link, text, activity }) {
|
|
1159
|
+
const intent = await resolveTelegramAiIntent({ bot, chatId, link, text, activity });
|
|
1160
|
+
const editMessageId = activity?.messageId;
|
|
1161
|
+
|
|
1162
|
+
if (intent.action === 'agent_prompt') {
|
|
1163
|
+
await runAgent({ bot, chatId, link, prompt: intent.prompt || text, activity });
|
|
1164
|
+
return true;
|
|
1165
|
+
}
|
|
1166
|
+
if (intent.action === 'show_menu') {
|
|
1167
|
+
await showMainMenu({ bot, chatId, link, editMessageId });
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
if (intent.action === 'show_projects') {
|
|
1171
|
+
await showProjectMenu({ bot, chatId, link, editMessageId });
|
|
1172
|
+
return true;
|
|
1173
|
+
}
|
|
1174
|
+
if (intent.action === 'select_project') {
|
|
1175
|
+
const query = intent.projectQuery || intent.prompt || text;
|
|
1176
|
+
const project = await findProjectByQuery(query);
|
|
1177
|
+
const lang = languageFor(link);
|
|
1178
|
+
if (!project) {
|
|
1179
|
+
await showProjectMenu({
|
|
1180
|
+
bot,
|
|
1181
|
+
chatId,
|
|
1182
|
+
link,
|
|
1183
|
+
editMessageId,
|
|
1184
|
+
notice: t(lang, 'control.projectNotFound', { query }),
|
|
1185
|
+
});
|
|
1186
|
+
return true;
|
|
1187
|
+
}
|
|
1188
|
+
updateTelegramControlState(link.user_id, {
|
|
1189
|
+
selectedProjectName: project.name,
|
|
1190
|
+
selectedProjectPath: project.fullPath || project.path,
|
|
1191
|
+
});
|
|
1192
|
+
await showMainMenu({
|
|
1193
|
+
bot,
|
|
1194
|
+
chatId,
|
|
1195
|
+
link,
|
|
1196
|
+
editMessageId,
|
|
1197
|
+
notice: t(lang, 'control.projectSelected', {
|
|
1198
|
+
project: project.displayName || project.name,
|
|
1199
|
+
path: project.fullPath || project.path,
|
|
1200
|
+
}),
|
|
1201
|
+
});
|
|
1202
|
+
return true;
|
|
1203
|
+
}
|
|
1204
|
+
if (intent.action === 'show_provider_menu') {
|
|
1205
|
+
await showProviderMenu({ bot, chatId, link, editMessageId });
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
if (intent.action === 'select_provider' && intent.provider) {
|
|
1209
|
+
updateTelegramControlState(link.user_id, { selectedProvider: intent.provider, selectedModel: null });
|
|
1210
|
+
await showModelMenu({ bot, chatId, link, editMessageId });
|
|
1211
|
+
return true;
|
|
1212
|
+
}
|
|
1213
|
+
if (intent.action === 'show_model_menu') {
|
|
1214
|
+
await showModelMenu({ bot, chatId, link, editMessageId });
|
|
1215
|
+
return true;
|
|
1216
|
+
}
|
|
1217
|
+
if (intent.action === 'select_model' && intent.model) {
|
|
1218
|
+
updateTelegramControlState(link.user_id, { selectedModel: intent.model });
|
|
1219
|
+
await showMainMenu({
|
|
1220
|
+
bot,
|
|
1221
|
+
chatId,
|
|
1222
|
+
link,
|
|
1223
|
+
editMessageId,
|
|
1224
|
+
notice: t(languageFor(link), 'control.modelSelected', { model: intent.model }),
|
|
1225
|
+
});
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
if (intent.action === 'show_runs') {
|
|
1229
|
+
await showRuns({ bot, chatId, link, editMessageId });
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
if (intent.action === 'show_approvals') {
|
|
1233
|
+
await showApprovalQueue({ bot, chatId, link, editMessageId });
|
|
1234
|
+
return true;
|
|
1235
|
+
}
|
|
1236
|
+
if (intent.action === 'show_workflows') {
|
|
1237
|
+
await showWorkflowMenu({ bot, chatId, link, editMessageId });
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
if (intent.action === 'run_workflow') {
|
|
1241
|
+
await runWorkflow({ bot, chatId, link, input: intent.workflowInput || text, activity });
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
if (intent.action === 'show_sessions') {
|
|
1245
|
+
await showSessions({ bot, chatId, link, editMessageId });
|
|
1246
|
+
return true;
|
|
1247
|
+
}
|
|
1248
|
+
if (intent.action === 'new_chat') {
|
|
1249
|
+
await startNewChat({ bot, chatId, link, editMessageId });
|
|
1250
|
+
return true;
|
|
1251
|
+
}
|
|
1252
|
+
await runAgent({ bot, chatId, link, prompt: text, activity });
|
|
1253
|
+
return true;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
564
1256
|
async function fetchRun(userId, runId) {
|
|
565
1257
|
return localApi(userId, `/api/orchestration/workflows/runs/${runId}`);
|
|
566
1258
|
}
|
|
567
1259
|
|
|
568
1260
|
function summarizeRun(run, mode) {
|
|
1261
|
+
const statusIcon = run.status === 'completed'
|
|
1262
|
+
? '✅'
|
|
1263
|
+
: run.status === 'failed'
|
|
1264
|
+
? '❌'
|
|
1265
|
+
: run.status === 'canceled'
|
|
1266
|
+
? '⏹'
|
|
1267
|
+
: '🔧';
|
|
569
1268
|
const lines = [
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1269
|
+
`${statusIcon} Run ${run.id}`,
|
|
1270
|
+
`🧩 Workflow: ${run.workflowId}`,
|
|
1271
|
+
`📌 Status: ${run.status}`,
|
|
573
1272
|
];
|
|
574
1273
|
const nodeRuns = Array.isArray(run.nodeRuns) ? run.nodeRuns : [];
|
|
575
1274
|
if (mode !== 'final') {
|
|
@@ -577,7 +1276,14 @@ function summarizeRun(run, mode) {
|
|
|
577
1276
|
? nodeRuns.filter((node) => node.error || node.status === 'failed')
|
|
578
1277
|
: nodeRuns;
|
|
579
1278
|
for (const node of visibleNodes) {
|
|
580
|
-
|
|
1279
|
+
const nodeIcon = node.status === 'completed'
|
|
1280
|
+
? '✅'
|
|
1281
|
+
: node.status === 'failed'
|
|
1282
|
+
? '❌'
|
|
1283
|
+
: node.status === 'running'
|
|
1284
|
+
? '🔧'
|
|
1285
|
+
: '⏳';
|
|
1286
|
+
lines.push(`${nodeIcon} ${node.status}: ${node.agentLabel || node.nodeId}${node.error ? ` - ${node.error}` : ''}`);
|
|
581
1287
|
}
|
|
582
1288
|
}
|
|
583
1289
|
const outputs = nodeRuns
|
|
@@ -587,7 +1293,7 @@ function summarizeRun(run, mode) {
|
|
|
587
1293
|
return truncate(`${lines.join('\n')}${outputs.join('\n')}`);
|
|
588
1294
|
}
|
|
589
1295
|
|
|
590
|
-
async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
1296
|
+
async function monitorWorkflowRun({ bot, chatId, link, runId, activity = null }) {
|
|
591
1297
|
if (runMonitors.has(runId)) return;
|
|
592
1298
|
runMonitors.set(runId, true);
|
|
593
1299
|
const state = getState(link.user_id);
|
|
@@ -597,12 +1303,23 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
597
1303
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
598
1304
|
const run = await fetchRun(link.user_id, runId);
|
|
599
1305
|
const nodeRuns = Array.isArray(run.nodeRuns) ? run.nodeRuns : [];
|
|
1306
|
+
if (activity && !TERMINAL_RUN_STATES.has(run.status)) {
|
|
1307
|
+
activity.status = 'running';
|
|
1308
|
+
activity.phase = `${t(activity.lang, 'control.activity.workflowRunning')} (${run.status})`;
|
|
1309
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1310
|
+
}
|
|
600
1311
|
if (state.progressMode === 'all') {
|
|
601
1312
|
for (const node of nodeRuns) {
|
|
602
1313
|
const key = `${node.nodeId}:${node.status}`;
|
|
603
1314
|
if (!seenNodeStatus.has(key)) {
|
|
604
1315
|
seenNodeStatus.set(key, true);
|
|
605
|
-
|
|
1316
|
+
if (activity) {
|
|
1317
|
+
pushActivityEvent(activity, `${node.status === 'completed' ? '✅' : node.status === 'failed' ? '❌' : '🔧'} ${node.agentLabel || node.nodeId}: ${node.status}${node.error ? ` - ${node.error}` : ''}`);
|
|
1318
|
+
activity.phase = t(activity.lang, 'control.activity.workflowRunning');
|
|
1319
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1320
|
+
} else {
|
|
1321
|
+
await send(bot, chatId, `${node.agentLabel || node.nodeId}: ${node.status}${node.error ? `\n${node.error}` : ''}`);
|
|
1322
|
+
}
|
|
606
1323
|
}
|
|
607
1324
|
}
|
|
608
1325
|
}
|
|
@@ -611,12 +1328,27 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
611
1328
|
const key = `${node.nodeId}:${node.status}:${node.error || ''}`;
|
|
612
1329
|
if (!seenNodeStatus.has(key)) {
|
|
613
1330
|
seenNodeStatus.set(key, true);
|
|
614
|
-
|
|
1331
|
+
if (activity) {
|
|
1332
|
+
pushActivityEvent(activity, `❌ ${node.agentLabel || node.nodeId}: ${node.status}${node.error ? ` - ${node.error}` : ''}`);
|
|
1333
|
+
activity.phase = t(activity.lang, 'control.activity.workflowRunning');
|
|
1334
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1335
|
+
} else {
|
|
1336
|
+
await send(bot, chatId, `${node.agentLabel || node.nodeId}: ${node.status}\n${node.error || ''}`);
|
|
1337
|
+
}
|
|
615
1338
|
}
|
|
616
1339
|
}
|
|
617
1340
|
}
|
|
618
1341
|
if (TERMINAL_RUN_STATES.has(run.status)) {
|
|
619
|
-
|
|
1342
|
+
if (activity) {
|
|
1343
|
+
activity.status = run.status === 'completed' ? 'done' : 'failed';
|
|
1344
|
+
activity.phase = run.status === 'completed'
|
|
1345
|
+
? t(activity.lang, 'control.activity.done')
|
|
1346
|
+
: t(activity.lang, 'control.activity.failed');
|
|
1347
|
+
activity.output = summarizeRun(run, state.progressMode);
|
|
1348
|
+
await editTelegramActivity({ bot, chatId, activity, force: true });
|
|
1349
|
+
} else {
|
|
1350
|
+
await send(bot, chatId, summarizeRun(run, state.progressMode));
|
|
1351
|
+
}
|
|
620
1352
|
return;
|
|
621
1353
|
}
|
|
622
1354
|
}
|
|
@@ -625,18 +1357,49 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
625
1357
|
}
|
|
626
1358
|
}
|
|
627
1359
|
|
|
628
|
-
export async function startCliInstall({ bot, chatId, link, provider }) {
|
|
1360
|
+
export async function startCliInstall({ bot, chatId, link, provider, editMessageId, confirmed = false }) {
|
|
629
1361
|
const lang = languageFor(link);
|
|
630
|
-
|
|
1362
|
+
if (!PROVIDERS.includes(provider)) {
|
|
1363
|
+
await send(bot, chatId, t(lang, 'control.providerAuthFallback'), { editMessageId });
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
const state = getState(link.user_id);
|
|
1367
|
+
if (state.remoteControlEnabled === false) {
|
|
1368
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId });
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
if (state.confirmationPolicy === 'strict' && !confirmed) {
|
|
1372
|
+
await requestConfirmation({
|
|
1373
|
+
bot,
|
|
1374
|
+
chatId,
|
|
1375
|
+
link,
|
|
1376
|
+
action: 'install_provider',
|
|
1377
|
+
payload: { provider },
|
|
1378
|
+
editMessageId,
|
|
1379
|
+
});
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const result = await runTelegramTool({
|
|
1384
|
+
userId: link.user_id,
|
|
1385
|
+
action: 'install_provider',
|
|
1386
|
+
execute: () => localApi(link.user_id, `/api/providers/${provider}/install`, { method: 'POST' }),
|
|
1387
|
+
});
|
|
1388
|
+
if (!result.ok) {
|
|
1389
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId });
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
const data = result.data;
|
|
631
1394
|
if (data?.manual) {
|
|
632
|
-
await send(bot, chatId, t(lang, 'control.manualInstall', { provider, manual: data.manual }));
|
|
1395
|
+
await send(bot, chatId, t(lang, 'control.manualInstall', { provider, manual: data.manual }), { editMessageId });
|
|
633
1396
|
return;
|
|
634
1397
|
}
|
|
635
1398
|
await send(bot, chatId, t(lang, 'control.installStarted', {
|
|
636
1399
|
provider,
|
|
637
1400
|
jobId: data.jobId,
|
|
638
1401
|
command: data.installCmd || 'internal installer',
|
|
639
|
-
}));
|
|
1402
|
+
}), { editMessageId });
|
|
640
1403
|
}
|
|
641
1404
|
|
|
642
1405
|
async function showInstallMenu({ bot, chatId, link, editMessageId }) {
|
|
@@ -818,16 +1581,13 @@ async function handleCommand({ bot, chatId, link, text }) {
|
|
|
818
1581
|
await send(bot, chatId, t(lang, 'control.cancelUsage'));
|
|
819
1582
|
return true;
|
|
820
1583
|
}
|
|
821
|
-
|
|
822
|
-
method: 'POST',
|
|
823
|
-
});
|
|
824
|
-
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }));
|
|
1584
|
+
await cancelRun({ bot, chatId, link, runId: argText });
|
|
825
1585
|
return true;
|
|
826
1586
|
}
|
|
827
1587
|
return false;
|
|
828
1588
|
}
|
|
829
1589
|
|
|
830
|
-
|
|
1590
|
+
async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
831
1591
|
const chatId = msg.chat.id;
|
|
832
1592
|
const text = String(msg.text || '').trim();
|
|
833
1593
|
if (!text) return false;
|
|
@@ -845,13 +1605,35 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
|
|
|
845
1605
|
if (await handleCommand({ bot, chatId, link, text })) return true;
|
|
846
1606
|
|
|
847
1607
|
const state = getState(link.user_id);
|
|
1608
|
+
if (state.routerEnabled === false) return false;
|
|
848
1609
|
if (!state.remoteControlEnabled) {
|
|
849
1610
|
await send(bot, chatId, t(languageFor(link), 'control.disabled'));
|
|
850
1611
|
return true;
|
|
851
1612
|
}
|
|
852
1613
|
|
|
853
|
-
await
|
|
854
|
-
|
|
1614
|
+
const activity = await createTelegramActivity({
|
|
1615
|
+
bot,
|
|
1616
|
+
chatId,
|
|
1617
|
+
link,
|
|
1618
|
+
type: 'router',
|
|
1619
|
+
prompt: text,
|
|
1620
|
+
phase: t(languageFor(link), 'control.activity.interpreting'),
|
|
1621
|
+
});
|
|
1622
|
+
return handleRoutedIntent({ bot, chatId, link, text, activity });
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
export async function handleTelegramControlMessage(args) {
|
|
1626
|
+
const chatId = args?.msg?.chat?.id;
|
|
1627
|
+
if (!chatId) return false;
|
|
1628
|
+
return enqueueTelegramJob(chatId, async () => {
|
|
1629
|
+
try {
|
|
1630
|
+
return await handleTelegramControlMessageInternal(args);
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
console.error('[telegram-control] message handler failed:', error);
|
|
1633
|
+
await send(args.bot, chatId, t(languageFor(args.link), 'error.generic')).catch(() => {});
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
855
1637
|
}
|
|
856
1638
|
|
|
857
1639
|
async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
@@ -868,7 +1650,7 @@ async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
|
868
1650
|
});
|
|
869
1651
|
}
|
|
870
1652
|
|
|
871
|
-
|
|
1653
|
+
async function handleTelegramControlCallbackInternal({ bot, query, link }) {
|
|
872
1654
|
const chatId = query.message?.chat?.id;
|
|
873
1655
|
if (!chatId) return;
|
|
874
1656
|
const editMessageId = query.message?.message_id;
|
|
@@ -882,6 +1664,32 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
882
1664
|
await bot.answerCallbackQuery(query.id).catch(() => {});
|
|
883
1665
|
|
|
884
1666
|
const { action, payload } = entry;
|
|
1667
|
+
if (action === 'confirm_action') {
|
|
1668
|
+
forgetAction(entry.id);
|
|
1669
|
+
const lang = languageFor(link);
|
|
1670
|
+
const result = consumeTelegramConfirmation(link.user_id, payload.id);
|
|
1671
|
+
if (!result.ok) {
|
|
1672
|
+
await send(bot, chatId, t(lang, result.reason === 'expired'
|
|
1673
|
+
? 'control.confirmationExpired'
|
|
1674
|
+
: 'control.confirmationMissing'), { editMessageId });
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
if (await executeConfirmedAction({
|
|
1678
|
+
bot,
|
|
1679
|
+
chatId,
|
|
1680
|
+
link,
|
|
1681
|
+
confirmation: result.confirmation,
|
|
1682
|
+
editMessageId,
|
|
1683
|
+
})) return;
|
|
1684
|
+
await send(bot, chatId, t(lang, 'error.generic'), { editMessageId });
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
if (action === 'cancel_confirmation') {
|
|
1688
|
+
forgetAction(entry.id);
|
|
1689
|
+
clearTelegramConfirmation(link.user_id, payload.id);
|
|
1690
|
+
await send(bot, chatId, t(languageFor(link), 'control.confirmationCanceled'), { editMessageId });
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
885
1693
|
if (action === 'menu') return showMainMenu({ bot, chatId, link, editMessageId });
|
|
886
1694
|
if (action === 'projects') return showProjectMenu({ bot, chatId, link, editMessageId });
|
|
887
1695
|
if (action === 'providers') return showProviderMenu({ bot, chatId, link, editMessageId });
|
|
@@ -947,20 +1755,26 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
947
1755
|
}
|
|
948
1756
|
if (action === 'run_detail') return showRunDetail({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
949
1757
|
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 });
|
|
1758
|
+
await cancelRun({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
954
1759
|
return;
|
|
955
1760
|
}
|
|
956
1761
|
if (action === 'approval_decide') {
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1762
|
+
const toolResult = await runTelegramTool({
|
|
1763
|
+
userId: link.user_id,
|
|
1764
|
+
action: 'approval_decide',
|
|
1765
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/approvals/${encodeURIComponent(payload.approvalId)}`, {
|
|
1766
|
+
method: 'POST',
|
|
1767
|
+
body: {
|
|
1768
|
+
allow: payload.allow === true,
|
|
1769
|
+
source: 'telegram',
|
|
1770
|
+
},
|
|
1771
|
+
}),
|
|
963
1772
|
});
|
|
1773
|
+
if (!toolResult.ok) {
|
|
1774
|
+
await sendToolFailure({ bot, chatId, link, result: toolResult, editMessageId });
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
const result = toolResult.data;
|
|
964
1778
|
const lang = languageFor(link);
|
|
965
1779
|
await send(bot, chatId, t(lang, 'control.approvalDecided', {
|
|
966
1780
|
approvalId: payload.approvalId,
|
|
@@ -969,7 +1783,7 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
969
1783
|
}), { editMessageId });
|
|
970
1784
|
return showApprovalQueue({ bot, chatId, link });
|
|
971
1785
|
}
|
|
972
|
-
if (action === 'install_provider') return startCliInstall({ bot, chatId, link, provider: payload.provider });
|
|
1786
|
+
if (action === 'install_provider') return startCliInstall({ bot, chatId, link, provider: payload.provider, editMessageId });
|
|
973
1787
|
if (action === 'auth_provider') {
|
|
974
1788
|
await send(bot, chatId, `${payload.provider} login:\n${AUTH_HELP[payload.provider] || t(languageFor(link), 'control.providerAuthFallback')}`, { editMessageId });
|
|
975
1789
|
return;
|
|
@@ -994,3 +1808,18 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
994
1808
|
});
|
|
995
1809
|
}
|
|
996
1810
|
}
|
|
1811
|
+
|
|
1812
|
+
export async function handleTelegramControlCallback(args) {
|
|
1813
|
+
const chatId = args?.query?.message?.chat?.id;
|
|
1814
|
+
if (!chatId) return;
|
|
1815
|
+
return enqueueTelegramJob(chatId, async () => {
|
|
1816
|
+
try {
|
|
1817
|
+
await handleTelegramControlCallbackInternal(args);
|
|
1818
|
+
} catch (error) {
|
|
1819
|
+
console.error('[telegram-control] callback failed:', error);
|
|
1820
|
+
const lang = languageFor(args.link);
|
|
1821
|
+
await args.bot?.answerCallbackQuery(args.query?.id, { text: t(lang, 'error.generic') }).catch(() => {});
|
|
1822
|
+
await send(args.bot, chatId, t(lang, 'error.generic')).catch(() => {});
|
|
1823
|
+
}
|
|
1824
|
+
});
|
|
1825
|
+
}
|