@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
|
@@ -2,12 +2,16 @@ 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, buildTelegramIntentPrompt, clearTelegramConfirmation, consumeTelegramConfirmation, createTelegramConfirmation, enqueueTelegramJob, parseTelegramAiIntentResponse, 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;
|
|
9
10
|
const MAX_CALLBACK_ACTIONS = 1000;
|
|
10
11
|
const MAX_TELEGRAM_TEXT = 3600;
|
|
12
|
+
const ACTIVITY_EDIT_THROTTLE_MS = 1200;
|
|
13
|
+
const ACTIVITY_HEARTBEAT_MS = 8000;
|
|
14
|
+
const INTENT_ROUTER_TIMEOUT_MS = 45_000;
|
|
11
15
|
const callbackActions = new Map();
|
|
12
16
|
const runMonitors = new Map();
|
|
13
17
|
const MODEL_FALLBACKS = Object.fromEntries(PROVIDERS.map((provider) => [provider, getStaticProviderModels(provider)]));
|
|
@@ -113,7 +117,11 @@ function readAction(data) {
|
|
|
113
117
|
callbackActions.delete(id);
|
|
114
118
|
return null;
|
|
115
119
|
}
|
|
116
|
-
return entry;
|
|
120
|
+
return { id, ...entry };
|
|
121
|
+
}
|
|
122
|
+
function forgetAction(id) {
|
|
123
|
+
if (id)
|
|
124
|
+
callbackActions.delete(id);
|
|
117
125
|
}
|
|
118
126
|
function button(text, action, payload = {}) {
|
|
119
127
|
return { text, callback_data: registerAction(action, payload) };
|
|
@@ -131,30 +139,46 @@ async function send(bot, chatId, text, options = {}) {
|
|
|
131
139
|
disable_web_page_preview: true,
|
|
132
140
|
...telegramOptions,
|
|
133
141
|
};
|
|
134
|
-
const
|
|
142
|
+
const chunks = splitTelegramText(text, MAX_TELEGRAM_TEXT);
|
|
143
|
+
const [firstChunk = ''] = chunks;
|
|
144
|
+
let startIndex = 0;
|
|
135
145
|
if (editMessageId && typeof bot.editMessageText === 'function') {
|
|
136
146
|
try {
|
|
137
|
-
|
|
147
|
+
const edited = await bot.editMessageText(firstChunk, {
|
|
138
148
|
chat_id: chatId,
|
|
139
149
|
message_id: editMessageId,
|
|
140
150
|
...extra,
|
|
141
151
|
});
|
|
152
|
+
startIndex = 1;
|
|
153
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
154
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
155
|
+
}
|
|
156
|
+
return edited;
|
|
142
157
|
}
|
|
143
158
|
catch (err) {
|
|
144
159
|
const description = err?.response?.body?.description || err?.message || '';
|
|
145
|
-
if (/message is not modified/i.test(description))
|
|
160
|
+
if (/message is not modified/i.test(description)) {
|
|
161
|
+
startIndex = 1;
|
|
162
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
163
|
+
await send(bot, chatId, chunk, telegramOptions);
|
|
164
|
+
}
|
|
146
165
|
return null;
|
|
166
|
+
}
|
|
147
167
|
console.warn('[telegram-control] editMessageText failed:', description || err);
|
|
148
168
|
}
|
|
149
169
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
170
|
+
let result = null;
|
|
171
|
+
for (const chunk of chunks.slice(startIndex)) {
|
|
172
|
+
try {
|
|
173
|
+
result = await bot.sendMessage(chatId, chunk, extra);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
const fallback = { ...extra };
|
|
177
|
+
delete fallback.parse_mode;
|
|
178
|
+
result = await bot.sendMessage(chatId, chunk, fallback);
|
|
179
|
+
}
|
|
157
180
|
}
|
|
181
|
+
return result;
|
|
158
182
|
}
|
|
159
183
|
function localApiBase() {
|
|
160
184
|
const port = process.env.SERVER_PORT || process.env.PORT || '3001';
|
|
@@ -164,30 +188,242 @@ function getOrCreateTelegramApiKey(userId) {
|
|
|
164
188
|
const existing = apiKeysDb
|
|
165
189
|
.getApiKeys(userId)
|
|
166
190
|
.find((key) => key.key_name === 'Telegram Control' && Boolean(key.is_active));
|
|
167
|
-
if (existing?.api_key)
|
|
191
|
+
if (existing?.api_key) {
|
|
192
|
+
const existingScopes = apiKeysDb.normalizeScopes(existing.scopes || []);
|
|
193
|
+
const nextScopes = apiKeysDb.normalizeScopes([...existingScopes, ...TELEGRAM_CONTROL_SCOPES]);
|
|
194
|
+
const hasAllScopes = TELEGRAM_CONTROL_SCOPES.every((scope) => existingScopes.includes(scope));
|
|
195
|
+
if (!hasAllScopes || nextScopes.length !== existingScopes.length) {
|
|
196
|
+
apiKeysDb.updateApiKeyScopes(userId, existing.id, nextScopes);
|
|
197
|
+
}
|
|
168
198
|
return existing.api_key;
|
|
169
|
-
|
|
199
|
+
}
|
|
200
|
+
return apiKeysDb.createApiKey(userId, 'Telegram Control', TELEGRAM_CONTROL_SCOPES).apiKey;
|
|
170
201
|
}
|
|
171
|
-
async function localApi(userId, path, { method = 'GET', body } = {}) {
|
|
202
|
+
async function localApi(userId, path, { method = 'GET', body, timeoutMs = 0 } = {}) {
|
|
172
203
|
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
173
|
-
const response = await
|
|
174
|
-
|
|
204
|
+
const response = await retryWithBackoff(async () => {
|
|
205
|
+
const controller = timeoutMs > 0 ? new AbortController() : null;
|
|
206
|
+
const timeoutId = controller
|
|
207
|
+
? setTimeout(() => controller.abort(new Error(`HTTP request timed out after ${timeoutMs}ms`)), timeoutMs)
|
|
208
|
+
: null;
|
|
209
|
+
const res = await fetch(`${localApiBase()}${path}`, {
|
|
210
|
+
method,
|
|
211
|
+
headers: {
|
|
212
|
+
'Content-Type': 'application/json',
|
|
213
|
+
'X-API-Key': apiKey,
|
|
214
|
+
},
|
|
215
|
+
signal: controller?.signal,
|
|
216
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
217
|
+
}).finally(() => {
|
|
218
|
+
if (timeoutId)
|
|
219
|
+
clearTimeout(timeoutId);
|
|
220
|
+
});
|
|
221
|
+
if ([408, 429, 500, 502, 503, 504].includes(res.status)) {
|
|
222
|
+
const error = new Error(`HTTP ${res.status}`);
|
|
223
|
+
error.status = res.status;
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
return res;
|
|
227
|
+
});
|
|
228
|
+
const data = await response.json().catch(() => ({}));
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
const error = data?.error?.message || data?.error || `HTTP ${response.status}`;
|
|
231
|
+
throw new Error(typeof error === 'string' ? error : JSON.stringify(error));
|
|
232
|
+
}
|
|
233
|
+
return data?.data ?? data;
|
|
234
|
+
}
|
|
235
|
+
async function localAgentStream(userId, body, onEvent) {
|
|
236
|
+
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
237
|
+
const response = await fetch(`${localApiBase()}/api/agent`, {
|
|
238
|
+
method: 'POST',
|
|
175
239
|
headers: {
|
|
176
240
|
'Content-Type': 'application/json',
|
|
177
241
|
'X-API-Key': apiKey,
|
|
178
242
|
},
|
|
179
|
-
body:
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
...body,
|
|
245
|
+
stream: true,
|
|
246
|
+
}),
|
|
180
247
|
});
|
|
181
|
-
const data = await response.json().catch(() => ({}));
|
|
182
248
|
if (!response.ok) {
|
|
249
|
+
const data = await response.json().catch(() => ({}));
|
|
183
250
|
const error = data?.error?.message || data?.error || `HTTP ${response.status}`;
|
|
184
251
|
throw new Error(typeof error === 'string' ? error : JSON.stringify(error));
|
|
185
252
|
}
|
|
186
|
-
|
|
253
|
+
if (!response.body)
|
|
254
|
+
return;
|
|
255
|
+
const reader = response.body.getReader();
|
|
256
|
+
const decoder = new TextDecoder();
|
|
257
|
+
let buffer = '';
|
|
258
|
+
const consumeBlock = async (block) => {
|
|
259
|
+
const lines = String(block || '').split('\n');
|
|
260
|
+
for (const line of lines) {
|
|
261
|
+
if (!line.startsWith('data:'))
|
|
262
|
+
continue;
|
|
263
|
+
const payload = line.slice(5).trim();
|
|
264
|
+
if (!payload)
|
|
265
|
+
continue;
|
|
266
|
+
let event;
|
|
267
|
+
try {
|
|
268
|
+
event = JSON.parse(payload);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
await onEvent?.(event);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
for (;;) {
|
|
277
|
+
const { done, value } = await reader.read();
|
|
278
|
+
if (done)
|
|
279
|
+
break;
|
|
280
|
+
buffer += decoder.decode(value, { stream: true });
|
|
281
|
+
let boundary = buffer.indexOf('\n\n');
|
|
282
|
+
while (boundary !== -1) {
|
|
283
|
+
const block = buffer.slice(0, boundary);
|
|
284
|
+
buffer = buffer.slice(boundary + 2);
|
|
285
|
+
await consumeBlock(block);
|
|
286
|
+
boundary = buffer.indexOf('\n\n');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const rest = `${buffer}${decoder.decode()}`;
|
|
290
|
+
if (rest.trim())
|
|
291
|
+
await consumeBlock(rest);
|
|
187
292
|
}
|
|
188
293
|
function checked(label) {
|
|
189
294
|
return `${label} ✓`;
|
|
190
295
|
}
|
|
296
|
+
function formatElapsed(startedAt) {
|
|
297
|
+
const seconds = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
|
|
298
|
+
if (seconds < 60)
|
|
299
|
+
return `${seconds}s`;
|
|
300
|
+
const minutes = Math.floor(seconds / 60);
|
|
301
|
+
const rest = seconds % 60;
|
|
302
|
+
return `${minutes}m ${rest}s`;
|
|
303
|
+
}
|
|
304
|
+
function projectLabel(state) {
|
|
305
|
+
return state.selectedProjectName || state.selectedProjectPath || '-';
|
|
306
|
+
}
|
|
307
|
+
function createActivityState({ lang, type = 'agent', provider, project, prompt, mode = 'final' }) {
|
|
308
|
+
return {
|
|
309
|
+
lang,
|
|
310
|
+
type,
|
|
311
|
+
status: 'starting',
|
|
312
|
+
phase: t(lang, 'control.activity.starting'),
|
|
313
|
+
provider,
|
|
314
|
+
project,
|
|
315
|
+
prompt: compact(prompt, 120),
|
|
316
|
+
mode,
|
|
317
|
+
startedAt: Date.now(),
|
|
318
|
+
lastEditAt: 0,
|
|
319
|
+
messageId: null,
|
|
320
|
+
sessionId: null,
|
|
321
|
+
runId: null,
|
|
322
|
+
workflowId: null,
|
|
323
|
+
events: [],
|
|
324
|
+
output: '',
|
|
325
|
+
error: null,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function pushActivityEvent(activity, text) {
|
|
329
|
+
const value = String(text || '').trim();
|
|
330
|
+
if (!value)
|
|
331
|
+
return;
|
|
332
|
+
if (activity.events.at(-1) === value)
|
|
333
|
+
return;
|
|
334
|
+
activity.events.push(value);
|
|
335
|
+
if (activity.events.length > 8)
|
|
336
|
+
activity.events.splice(0, activity.events.length - 8);
|
|
337
|
+
}
|
|
338
|
+
function activityTitle(activity) {
|
|
339
|
+
if (activity.type === 'workflow')
|
|
340
|
+
return t(activity.lang, 'control.activity.workflowTitle');
|
|
341
|
+
if (activity.type === 'router')
|
|
342
|
+
return t(activity.lang, 'control.activity.routerTitle');
|
|
343
|
+
return t(activity.lang, 'control.activity.agentTitle');
|
|
344
|
+
}
|
|
345
|
+
function renderActivity(activity, { finalText = null } = {}) {
|
|
346
|
+
const output = finalText || activity.output;
|
|
347
|
+
const lines = [
|
|
348
|
+
`${activity.status === 'failed' ? '❌' : activity.status === 'done' ? '✅' : activity.status === 'running' ? '🔧' : '⏳'} ${activityTitle(activity)}`,
|
|
349
|
+
'',
|
|
350
|
+
`🤖 ${t(activity.lang, 'control.activity.provider')}: ${activity.provider || '-'}`,
|
|
351
|
+
`📁 ${t(activity.lang, 'control.activity.project')}: ${compact(activity.project, 90)}`,
|
|
352
|
+
];
|
|
353
|
+
if (activity.sessionId)
|
|
354
|
+
lines.push(`🧵 ${t(activity.lang, 'control.activity.session')}: ${activity.sessionId}`);
|
|
355
|
+
if (activity.runId)
|
|
356
|
+
lines.push(`🧭 ${t(activity.lang, 'control.activity.run')}: ${activity.runId}`);
|
|
357
|
+
if (activity.workflowId)
|
|
358
|
+
lines.push(`🧩 ${t(activity.lang, 'control.activity.workflow')}: ${activity.workflowId}`);
|
|
359
|
+
lines.push(`⏱ ${t(activity.lang, 'control.activity.elapsed')}: ${formatElapsed(activity.startedAt)}`);
|
|
360
|
+
lines.push(`📌 ${t(activity.lang, 'control.activity.status')}: ${activity.phase}`);
|
|
361
|
+
const visibleEvents = activity.mode === 'final'
|
|
362
|
+
? activity.events.slice(-4)
|
|
363
|
+
: activity.events;
|
|
364
|
+
if (visibleEvents.length > 0) {
|
|
365
|
+
lines.push('', `🛠 ${t(activity.lang, 'control.activity.work')}:`);
|
|
366
|
+
for (const event of visibleEvents)
|
|
367
|
+
lines.push(`• ${event}`);
|
|
368
|
+
}
|
|
369
|
+
if (activity.error) {
|
|
370
|
+
lines.push('', `⚠️ ${truncate(activity.error, 700)}`);
|
|
371
|
+
}
|
|
372
|
+
else if (output) {
|
|
373
|
+
lines.push('', `💬 ${t(activity.lang, 'control.activity.output')}:`);
|
|
374
|
+
lines.push(truncate(output, 1800));
|
|
375
|
+
}
|
|
376
|
+
else if (activity.prompt) {
|
|
377
|
+
lines.push('', `📝 ${compact(activity.prompt, 220)}`);
|
|
378
|
+
}
|
|
379
|
+
return truncate(lines.join('\n'), 3400);
|
|
380
|
+
}
|
|
381
|
+
async function createTelegramActivity({ bot, chatId, link, type = 'agent', prompt = '', phase = null }) {
|
|
382
|
+
const lang = languageFor(link);
|
|
383
|
+
const state = getState(link.user_id);
|
|
384
|
+
const activity = createActivityState({
|
|
385
|
+
lang,
|
|
386
|
+
type,
|
|
387
|
+
provider: resolveTelegramProvider(state),
|
|
388
|
+
project: projectLabel(state),
|
|
389
|
+
prompt,
|
|
390
|
+
mode: state.progressMode,
|
|
391
|
+
});
|
|
392
|
+
if (phase)
|
|
393
|
+
activity.phase = phase;
|
|
394
|
+
const sent = await send(bot, chatId, renderActivity(activity), { parse_mode: undefined });
|
|
395
|
+
activity.messageId = sent?.message_id || sent?.message?.message_id || null;
|
|
396
|
+
activity.lastEditAt = Date.now();
|
|
397
|
+
return activity;
|
|
398
|
+
}
|
|
399
|
+
async function editTelegramActivity({ bot, chatId, activity, force = false, reply_markup }) {
|
|
400
|
+
if (!activity)
|
|
401
|
+
return null;
|
|
402
|
+
const now = Date.now();
|
|
403
|
+
if (!force && now - activity.lastEditAt < ACTIVITY_EDIT_THROTTLE_MS)
|
|
404
|
+
return null;
|
|
405
|
+
const sent = await send(bot, chatId, renderActivity(activity), {
|
|
406
|
+
editMessageId: activity.messageId,
|
|
407
|
+
parse_mode: undefined,
|
|
408
|
+
reply_markup,
|
|
409
|
+
});
|
|
410
|
+
activity.messageId = sent?.message_id || sent?.message?.message_id || activity.messageId;
|
|
411
|
+
activity.lastEditAt = now;
|
|
412
|
+
return sent;
|
|
413
|
+
}
|
|
414
|
+
function startActivityHeartbeat({ bot, chatId, activity }) {
|
|
415
|
+
return setInterval(() => {
|
|
416
|
+
if (!activity || activity.status === 'done' || activity.status === 'failed')
|
|
417
|
+
return;
|
|
418
|
+
if (activity.status === 'starting') {
|
|
419
|
+
activity.status = 'running';
|
|
420
|
+
activity.phase = t(activity.lang, 'control.activity.thinking');
|
|
421
|
+
}
|
|
422
|
+
editTelegramActivity({ bot, chatId, activity }).catch((error) => {
|
|
423
|
+
console.warn('[telegram-control] activity heartbeat failed:', error?.message || error);
|
|
424
|
+
});
|
|
425
|
+
}, ACTIVITY_HEARTBEAT_MS);
|
|
426
|
+
}
|
|
191
427
|
function stateSummary(lang, state) {
|
|
192
428
|
return [
|
|
193
429
|
`${t(lang, 'control.summary.project')}: ${state.selectedProjectName || t(lang, 'control.notSelected')}`,
|
|
@@ -232,11 +468,12 @@ async function listProjects() {
|
|
|
232
468
|
const projects = await getProjects();
|
|
233
469
|
return projects.slice(0, 20);
|
|
234
470
|
}
|
|
235
|
-
async function showProjectMenu({ bot, chatId, link, editMessageId }) {
|
|
471
|
+
async function showProjectMenu({ bot, chatId, link, editMessageId, notice }) {
|
|
236
472
|
const lang = languageFor(link);
|
|
237
473
|
const projects = await listProjects();
|
|
238
474
|
if (projects.length === 0) {
|
|
239
|
-
|
|
475
|
+
const prefix = notice ? `${notice}\n\n` : '';
|
|
476
|
+
await send(bot, chatId, `${prefix}${t(lang, 'control.noProjects')}`, { editMessageId });
|
|
240
477
|
return;
|
|
241
478
|
}
|
|
242
479
|
const buttons = projects.map((project, index) => button(`${index + 1}. ${compact(project.displayName || project.name, 34)}`, 'project_select', {
|
|
@@ -244,7 +481,8 @@ async function showProjectMenu({ bot, chatId, link, editMessageId }) {
|
|
|
244
481
|
path: project.fullPath || project.path,
|
|
245
482
|
displayName: project.displayName || project.name,
|
|
246
483
|
}));
|
|
247
|
-
|
|
484
|
+
const prefix = notice ? `${notice}\n\n` : '';
|
|
485
|
+
await send(bot, chatId, `${prefix}${t(lang, 'control.pickProject')}`, {
|
|
248
486
|
editMessageId,
|
|
249
487
|
reply_markup: { inline_keyboard: rows(buttons, 1) },
|
|
250
488
|
});
|
|
@@ -428,83 +666,526 @@ function extractAssistantText(response) {
|
|
|
428
666
|
}
|
|
429
667
|
return chunks.join('\n\n').trim();
|
|
430
668
|
}
|
|
431
|
-
|
|
669
|
+
function extractTextFromEvent(event) {
|
|
670
|
+
if (!event || typeof event !== 'object')
|
|
671
|
+
return '';
|
|
672
|
+
if (typeof event.content === 'string')
|
|
673
|
+
return event.content;
|
|
674
|
+
if (Array.isArray(event.content)) {
|
|
675
|
+
return event.content
|
|
676
|
+
.map((part) => (typeof part === 'string' ? part : part?.text || ''))
|
|
677
|
+
.join('');
|
|
678
|
+
}
|
|
679
|
+
const legacyContent = event.data?.message?.content || event.message?.content;
|
|
680
|
+
if (Array.isArray(legacyContent)) {
|
|
681
|
+
return legacyContent
|
|
682
|
+
.map((part) => (typeof part === 'string' ? part : part?.text || ''))
|
|
683
|
+
.join('');
|
|
684
|
+
}
|
|
685
|
+
if (typeof legacyContent === 'string')
|
|
686
|
+
return legacyContent;
|
|
687
|
+
return '';
|
|
688
|
+
}
|
|
689
|
+
function describeToolInput(toolName, input) {
|
|
690
|
+
if (!input || typeof input !== 'object')
|
|
691
|
+
return '';
|
|
692
|
+
if (toolName === 'Bash' && typeof input.command === 'string') {
|
|
693
|
+
return compact(input.command, 110);
|
|
694
|
+
}
|
|
695
|
+
if (toolName === 'WebSearch' && typeof input.query === 'string') {
|
|
696
|
+
return compact(input.query, 110);
|
|
697
|
+
}
|
|
698
|
+
if (toolName === 'FileChanges') {
|
|
699
|
+
return compact(JSON.stringify(input), 110);
|
|
700
|
+
}
|
|
701
|
+
const keys = Object.keys(input).slice(0, 3);
|
|
702
|
+
if (keys.length === 0)
|
|
703
|
+
return '';
|
|
704
|
+
return compact(keys.map((key) => `${key}: ${String(input[key])}`).join(', '), 110);
|
|
705
|
+
}
|
|
706
|
+
function applyAgentStreamEvent(activity, event) {
|
|
707
|
+
if (!event || typeof event !== 'object')
|
|
708
|
+
return;
|
|
709
|
+
const sessionId = event.actualSessionId || event.newSessionId || event.sessionId || event.threadId;
|
|
710
|
+
if (sessionId)
|
|
711
|
+
activity.sessionId = sessionId;
|
|
712
|
+
if (event.type === 'status' && event.message) {
|
|
713
|
+
activity.status = 'running';
|
|
714
|
+
activity.phase = compact(event.message, 120);
|
|
715
|
+
pushActivityEvent(activity, `⏳ ${compact(event.message, 120)}`);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (event.type === 'done' || event.kind === 'complete' || event.kind === 'stream_end') {
|
|
719
|
+
activity.status = 'done';
|
|
720
|
+
activity.phase = t(activity.lang, 'control.activity.done');
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (event.type === 'error' || event.kind === 'error') {
|
|
724
|
+
activity.status = 'failed';
|
|
725
|
+
activity.phase = t(activity.lang, 'control.activity.failed');
|
|
726
|
+
activity.error = event.error || event.message || event.content || t(activity.lang, 'error.generic');
|
|
727
|
+
pushActivityEvent(activity, `❌ ${compact(activity.error, 120)}`);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (event.kind === 'session_created') {
|
|
731
|
+
activity.status = 'running';
|
|
732
|
+
activity.phase = t(activity.lang, 'control.activity.sessionStarted');
|
|
733
|
+
pushActivityEvent(activity, `🧵 ${t(activity.lang, 'control.activity.sessionStarted')}`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (event.kind === 'thinking') {
|
|
737
|
+
activity.status = 'running';
|
|
738
|
+
activity.phase = t(activity.lang, 'control.activity.thinking');
|
|
739
|
+
const thought = extractTextFromEvent(event);
|
|
740
|
+
if (thought)
|
|
741
|
+
pushActivityEvent(activity, `🧠 ${compact(thought, 120)}`);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (event.kind === 'stream_delta' || event.kind === 'text') {
|
|
745
|
+
activity.status = 'running';
|
|
746
|
+
activity.phase = t(activity.lang, 'control.activity.responding');
|
|
747
|
+
activity.output = `${activity.output}${extractTextFromEvent(event)}`;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (event.type === 'claude-response' && event.data?.type === 'assistant') {
|
|
751
|
+
activity.status = 'running';
|
|
752
|
+
activity.phase = t(activity.lang, 'control.activity.responding');
|
|
753
|
+
activity.output = `${activity.output}${extractTextFromEvent(event)}`;
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (event.kind === 'tool_use') {
|
|
757
|
+
activity.status = 'running';
|
|
758
|
+
activity.phase = t(activity.lang, 'control.activity.working');
|
|
759
|
+
const toolName = event.toolName || 'Tool';
|
|
760
|
+
const input = describeToolInput(toolName, event.toolInput);
|
|
761
|
+
const icon = toolName === 'Bash'
|
|
762
|
+
? '💻'
|
|
763
|
+
: toolName === 'FileChanges'
|
|
764
|
+
? '📝'
|
|
765
|
+
: toolName === 'WebSearch'
|
|
766
|
+
? '🔎'
|
|
767
|
+
: '🔧';
|
|
768
|
+
pushActivityEvent(activity, `${icon} ${toolName}${input ? `: ${input}` : ''}`);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (event.kind === 'tool_result') {
|
|
772
|
+
activity.status = 'running';
|
|
773
|
+
activity.phase = t(activity.lang, 'control.activity.working');
|
|
774
|
+
const label = event.isError
|
|
775
|
+
? t(activity.lang, 'control.activity.toolFailed')
|
|
776
|
+
: t(activity.lang, 'control.activity.toolDone');
|
|
777
|
+
pushActivityEvent(activity, `${event.isError ? '⚠️' : '✅'} ${label}`);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (event.kind === 'status' && event.text === 'token_budget') {
|
|
781
|
+
const used = event.tokenBudget?.used;
|
|
782
|
+
if (used)
|
|
783
|
+
pushActivityEvent(activity, `📊 ${t(activity.lang, 'control.activity.tokens')}: ${used}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function confirmationLabel(lang, action, payload = {}) {
|
|
787
|
+
if (action === 'install_provider') {
|
|
788
|
+
return t(lang, 'control.confirmInstall', { provider: payload.provider || '' });
|
|
789
|
+
}
|
|
790
|
+
if (action === 'run_cancel') {
|
|
791
|
+
return t(lang, 'control.confirmCancelRun', { runId: payload.runId || '' });
|
|
792
|
+
}
|
|
793
|
+
return t(lang, 'control.confirmationRequired');
|
|
794
|
+
}
|
|
795
|
+
async function requestConfirmation({ bot, chatId, link, action, payload = {}, editMessageId }) {
|
|
796
|
+
const lang = languageFor(link);
|
|
797
|
+
const pending = createTelegramConfirmation(link.user_id, action, payload);
|
|
798
|
+
await send(bot, chatId, confirmationLabel(lang, action, payload), {
|
|
799
|
+
editMessageId,
|
|
800
|
+
reply_markup: {
|
|
801
|
+
inline_keyboard: [[
|
|
802
|
+
button(t(lang, 'control.button.confirm'), 'confirm_action', { id: pending.id }),
|
|
803
|
+
button(t(lang, 'control.button.cancel'), 'cancel_confirmation', { id: pending.id }),
|
|
804
|
+
]],
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
return { ok: false, requiresConfirmation: true };
|
|
808
|
+
}
|
|
809
|
+
async function sendToolFailure({ bot, chatId, link, result, editMessageId }) {
|
|
810
|
+
const lang = languageFor(link);
|
|
811
|
+
const message = result?.message === 'REMOTE_CONTROL_DISABLED'
|
|
812
|
+
? t(lang, 'control.disabled')
|
|
813
|
+
: (result?.message || t(lang, 'error.generic'));
|
|
814
|
+
await send(bot, chatId, message, { editMessageId });
|
|
815
|
+
}
|
|
816
|
+
async function runAgent({ bot, chatId, link, prompt, activity = null }) {
|
|
432
817
|
const lang = languageFor(link);
|
|
433
818
|
const state = getState(link.user_id);
|
|
434
819
|
if (!state.remoteControlEnabled) {
|
|
435
|
-
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
820
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId: activity?.messageId });
|
|
436
821
|
return;
|
|
437
822
|
}
|
|
438
823
|
if (!state.selectedProjectPath) {
|
|
439
|
-
await
|
|
440
|
-
await showProjectMenu({ bot, chatId, link });
|
|
824
|
+
await showProjectMenu({ bot, chatId, link, editMessageId: activity?.messageId, notice: t(lang, 'control.selectProjectFirst') });
|
|
441
825
|
return;
|
|
442
826
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
827
|
+
const provider = resolveTelegramProvider(state);
|
|
828
|
+
const model = resolveTelegramModel(state);
|
|
829
|
+
const active = activity || await createTelegramActivity({
|
|
830
|
+
bot,
|
|
831
|
+
chatId,
|
|
832
|
+
link,
|
|
833
|
+
type: 'agent',
|
|
834
|
+
prompt,
|
|
835
|
+
phase: t(lang, 'control.activity.startingProvider', { provider }),
|
|
836
|
+
});
|
|
837
|
+
active.type = 'agent';
|
|
838
|
+
active.provider = provider;
|
|
839
|
+
active.project = projectLabel(state);
|
|
840
|
+
active.status = 'running';
|
|
841
|
+
active.phase = t(lang, 'control.activity.startingProvider', { provider });
|
|
842
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
843
|
+
const heartbeat = startActivityHeartbeat({ bot, chatId, activity: active });
|
|
844
|
+
let streamFailed = null;
|
|
845
|
+
try {
|
|
846
|
+
await localAgentStream(link.user_id, {
|
|
452
847
|
projectPath: state.selectedProjectPath,
|
|
453
|
-
provider
|
|
454
|
-
model:
|
|
455
|
-
message: prompt,
|
|
848
|
+
provider,
|
|
849
|
+
model: model || undefined,
|
|
850
|
+
message: buildTelegramAgentPrompt(prompt, state),
|
|
456
851
|
cleanup: false,
|
|
457
|
-
|
|
458
|
-
},
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
852
|
+
permissionMode: 'default',
|
|
853
|
+
}, async (event) => {
|
|
854
|
+
applyAgentStreamEvent(active, event);
|
|
855
|
+
await editTelegramActivity({ bot, chatId, activity: active });
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
streamFailed = error;
|
|
860
|
+
}
|
|
861
|
+
finally {
|
|
862
|
+
clearInterval(heartbeat);
|
|
863
|
+
}
|
|
864
|
+
if (streamFailed) {
|
|
865
|
+
active.status = 'failed';
|
|
866
|
+
active.phase = t(lang, 'control.activity.failed');
|
|
867
|
+
active.error = streamFailed.message || t(lang, 'error.generic');
|
|
868
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
active.status = active.error ? 'failed' : 'done';
|
|
872
|
+
active.phase = active.error ? t(lang, 'control.activity.failed') : t(lang, 'control.activity.done');
|
|
873
|
+
if (!active.output && !active.error)
|
|
874
|
+
active.output = t(lang, 'control.noAssistantText');
|
|
875
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
465
876
|
}
|
|
466
|
-
export async function runWorkflow({ bot, chatId, link, input }) {
|
|
877
|
+
export async function runWorkflow({ bot, chatId, link, input, activity = null }) {
|
|
467
878
|
const lang = languageFor(link);
|
|
468
879
|
const state = getState(link.user_id);
|
|
469
880
|
const workflowId = state.selectedWorkflowId;
|
|
881
|
+
if (!state.remoteControlEnabled) {
|
|
882
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId: activity?.messageId });
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
470
885
|
if (!workflowId) {
|
|
471
|
-
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'));
|
|
472
|
-
await showWorkflowMenu({ bot, chatId, link });
|
|
886
|
+
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'), { editMessageId: activity?.messageId });
|
|
887
|
+
await showWorkflowMenu({ bot, chatId, link, editMessageId: activity?.messageId });
|
|
473
888
|
return;
|
|
474
889
|
}
|
|
475
890
|
if (!state.selectedProjectPath) {
|
|
476
|
-
await
|
|
477
|
-
await showProjectMenu({ bot, chatId, link });
|
|
891
|
+
await showProjectMenu({ bot, chatId, link, editMessageId: activity?.messageId, notice: t(lang, 'control.selectProjectFirst') });
|
|
478
892
|
return;
|
|
479
893
|
}
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
894
|
+
const provider = resolveTelegramProvider(state);
|
|
895
|
+
const model = resolveTelegramModel(state);
|
|
896
|
+
const active = activity || await createTelegramActivity({
|
|
897
|
+
bot,
|
|
898
|
+
chatId,
|
|
899
|
+
link,
|
|
900
|
+
type: 'workflow',
|
|
901
|
+
prompt: input,
|
|
902
|
+
phase: t(lang, 'control.activity.startingWorkflow'),
|
|
903
|
+
});
|
|
904
|
+
active.type = 'workflow';
|
|
905
|
+
active.provider = provider;
|
|
906
|
+
active.project = projectLabel(state);
|
|
907
|
+
active.workflowId = workflowId;
|
|
908
|
+
active.status = 'running';
|
|
909
|
+
active.phase = t(lang, 'control.activity.startingWorkflow');
|
|
910
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
911
|
+
const result = await runTelegramTool({
|
|
912
|
+
userId: link.user_id,
|
|
913
|
+
action: 'run_workflow',
|
|
914
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/${workflowId}/runs`, {
|
|
915
|
+
method: 'POST',
|
|
916
|
+
body: {
|
|
917
|
+
input,
|
|
918
|
+
metadata: {
|
|
919
|
+
projectId: state.selectedProjectName,
|
|
920
|
+
projectName: state.selectedProjectName,
|
|
921
|
+
projectPath: state.selectedProjectPath,
|
|
922
|
+
workspaceTarget: 'selected_project',
|
|
923
|
+
telegram: true,
|
|
924
|
+
preferredProvider: provider,
|
|
925
|
+
preferredModel: model,
|
|
926
|
+
},
|
|
492
927
|
},
|
|
493
|
-
},
|
|
928
|
+
}),
|
|
494
929
|
});
|
|
495
|
-
|
|
496
|
-
|
|
930
|
+
if (!result.ok) {
|
|
931
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId: active.messageId });
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const run = result.data;
|
|
935
|
+
active.runId = run.id;
|
|
936
|
+
active.phase = t(lang, 'control.activity.workflowRunning');
|
|
937
|
+
pushActivityEvent(active, `🧭 ${t(lang, 'control.workflowStarted', { runId: run.id, workflowId }).replace('\n', ' ')}`);
|
|
938
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
939
|
+
monitorWorkflowRun({ bot, chatId, link, runId: run.id, activity: active }).catch((error) => {
|
|
497
940
|
console.warn('[telegram-control] workflow monitor failed:', error?.message || error);
|
|
498
941
|
});
|
|
499
942
|
}
|
|
943
|
+
async function cancelRun({ bot, chatId, link, runId, editMessageId, confirmed = false }) {
|
|
944
|
+
const lang = languageFor(link);
|
|
945
|
+
if (!runId) {
|
|
946
|
+
await send(bot, chatId, t(lang, 'control.cancelUsage'), { editMessageId });
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const state = getState(link.user_id);
|
|
950
|
+
if (state.remoteControlEnabled === false) {
|
|
951
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId });
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (state.confirmationPolicy === 'strict' && !confirmed) {
|
|
955
|
+
await requestConfirmation({
|
|
956
|
+
bot,
|
|
957
|
+
chatId,
|
|
958
|
+
link,
|
|
959
|
+
action: 'run_cancel',
|
|
960
|
+
payload: { runId },
|
|
961
|
+
editMessageId,
|
|
962
|
+
});
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
const result = await runTelegramTool({
|
|
966
|
+
userId: link.user_id,
|
|
967
|
+
action: 'run_cancel',
|
|
968
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
969
|
+
method: 'POST',
|
|
970
|
+
}),
|
|
971
|
+
});
|
|
972
|
+
if (!result.ok) {
|
|
973
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId });
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
const run = result.data;
|
|
977
|
+
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }), { editMessageId });
|
|
978
|
+
}
|
|
979
|
+
async function executeConfirmedAction({ bot, chatId, link, confirmation, editMessageId }) {
|
|
980
|
+
if (confirmation.action === 'install_provider') {
|
|
981
|
+
await startCliInstall({
|
|
982
|
+
bot,
|
|
983
|
+
chatId,
|
|
984
|
+
link,
|
|
985
|
+
provider: confirmation.payload?.provider,
|
|
986
|
+
editMessageId,
|
|
987
|
+
confirmed: true,
|
|
988
|
+
});
|
|
989
|
+
return true;
|
|
990
|
+
}
|
|
991
|
+
if (confirmation.action === 'run_cancel') {
|
|
992
|
+
await cancelRun({
|
|
993
|
+
bot,
|
|
994
|
+
chatId,
|
|
995
|
+
link,
|
|
996
|
+
runId: confirmation.payload?.runId,
|
|
997
|
+
editMessageId,
|
|
998
|
+
confirmed: true,
|
|
999
|
+
});
|
|
1000
|
+
return true;
|
|
1001
|
+
}
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
async function findProjectByQuery(query) {
|
|
1005
|
+
const projects = await listProjects();
|
|
1006
|
+
const needle = String(query || '').trim().toLocaleLowerCase('tr');
|
|
1007
|
+
if (!needle)
|
|
1008
|
+
return null;
|
|
1009
|
+
return projects.find((project) => {
|
|
1010
|
+
const candidates = [project.name, project.displayName, project.fullPath, project.path]
|
|
1011
|
+
.filter(Boolean)
|
|
1012
|
+
.map((value) => String(value).toLocaleLowerCase('tr'));
|
|
1013
|
+
return candidates.some((candidate) => candidate === needle || candidate.includes(needle));
|
|
1014
|
+
}) || null;
|
|
1015
|
+
}
|
|
1016
|
+
async function listWorkflowSummaries(userId) {
|
|
1017
|
+
try {
|
|
1018
|
+
const data = await localApi(userId, '/api/orchestration/workflows', { timeoutMs: 10_000 });
|
|
1019
|
+
if (Array.isArray(data))
|
|
1020
|
+
return data;
|
|
1021
|
+
if (Array.isArray(data?.workflows))
|
|
1022
|
+
return data.workflows;
|
|
1023
|
+
}
|
|
1024
|
+
catch {
|
|
1025
|
+
// Workflow context is helpful for routing but should never block chat input.
|
|
1026
|
+
}
|
|
1027
|
+
return [];
|
|
1028
|
+
}
|
|
1029
|
+
async function resolveTelegramAiIntent({ bot, chatId, link, text, activity }) {
|
|
1030
|
+
const state = getState(link.user_id);
|
|
1031
|
+
if (state.routerEnabled === false || state.routerMode !== 'hybrid') {
|
|
1032
|
+
return { action: 'agent_prompt', prompt: text, confidence: 1 };
|
|
1033
|
+
}
|
|
1034
|
+
const provider = resolveTelegramProvider(state);
|
|
1035
|
+
const model = resolveTelegramModel(state);
|
|
1036
|
+
if (activity) {
|
|
1037
|
+
activity.type = 'router';
|
|
1038
|
+
activity.provider = provider;
|
|
1039
|
+
activity.project = projectLabel(state);
|
|
1040
|
+
activity.status = 'running';
|
|
1041
|
+
activity.phase = t(activity.lang, 'control.activity.interpreting');
|
|
1042
|
+
pushActivityEvent(activity, `🧠 ${t(activity.lang, 'control.activity.interpreting')}`);
|
|
1043
|
+
await editTelegramActivity({ bot, chatId, activity, force: true });
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
const projects = await listProjects().catch(() => []);
|
|
1047
|
+
const workflows = await listWorkflowSummaries(link.user_id);
|
|
1048
|
+
const response = await localApi(link.user_id, '/api/agent', {
|
|
1049
|
+
method: 'POST',
|
|
1050
|
+
timeoutMs: INTENT_ROUTER_TIMEOUT_MS,
|
|
1051
|
+
body: {
|
|
1052
|
+
projectPath: state.selectedProjectPath || process.cwd(),
|
|
1053
|
+
provider,
|
|
1054
|
+
model: model || undefined,
|
|
1055
|
+
message: buildTelegramIntentPrompt(text, state, { projects, workflows }),
|
|
1056
|
+
cleanup: false,
|
|
1057
|
+
stream: false,
|
|
1058
|
+
permissionMode: 'plan',
|
|
1059
|
+
},
|
|
1060
|
+
});
|
|
1061
|
+
const assistantText = extractAssistantText(response);
|
|
1062
|
+
const intent = parseTelegramAiIntentResponse(assistantText, text);
|
|
1063
|
+
if (activity) {
|
|
1064
|
+
pushActivityEvent(activity, `🧭 ${intent.action} (${Math.round(intent.confidence * 100)}%)`);
|
|
1065
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1066
|
+
}
|
|
1067
|
+
return intent;
|
|
1068
|
+
}
|
|
1069
|
+
catch (error) {
|
|
1070
|
+
if (activity) {
|
|
1071
|
+
pushActivityEvent(activity, `⚠️ ${t(activity.lang, 'control.activity.routerFallback')}`);
|
|
1072
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1073
|
+
}
|
|
1074
|
+
console.warn('[telegram-control] AI intent router fallback:', error?.message || error);
|
|
1075
|
+
return { action: 'agent_prompt', prompt: text, confidence: 0 };
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
async function handleRoutedIntent({ bot, chatId, link, text, activity }) {
|
|
1079
|
+
const intent = await resolveTelegramAiIntent({ bot, chatId, link, text, activity });
|
|
1080
|
+
const editMessageId = activity?.messageId;
|
|
1081
|
+
if (intent.action === 'agent_prompt') {
|
|
1082
|
+
await runAgent({ bot, chatId, link, prompt: intent.prompt || text, activity });
|
|
1083
|
+
return true;
|
|
1084
|
+
}
|
|
1085
|
+
if (intent.action === 'show_menu') {
|
|
1086
|
+
await showMainMenu({ bot, chatId, link, editMessageId });
|
|
1087
|
+
return true;
|
|
1088
|
+
}
|
|
1089
|
+
if (intent.action === 'show_projects') {
|
|
1090
|
+
await showProjectMenu({ bot, chatId, link, editMessageId });
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
if (intent.action === 'select_project') {
|
|
1094
|
+
const query = intent.projectQuery || intent.prompt || text;
|
|
1095
|
+
const project = await findProjectByQuery(query);
|
|
1096
|
+
const lang = languageFor(link);
|
|
1097
|
+
if (!project) {
|
|
1098
|
+
await showProjectMenu({
|
|
1099
|
+
bot,
|
|
1100
|
+
chatId,
|
|
1101
|
+
link,
|
|
1102
|
+
editMessageId,
|
|
1103
|
+
notice: t(lang, 'control.projectNotFound', { query }),
|
|
1104
|
+
});
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
updateTelegramControlState(link.user_id, {
|
|
1108
|
+
selectedProjectName: project.name,
|
|
1109
|
+
selectedProjectPath: project.fullPath || project.path,
|
|
1110
|
+
});
|
|
1111
|
+
await showMainMenu({
|
|
1112
|
+
bot,
|
|
1113
|
+
chatId,
|
|
1114
|
+
link,
|
|
1115
|
+
editMessageId,
|
|
1116
|
+
notice: t(lang, 'control.projectSelected', {
|
|
1117
|
+
project: project.displayName || project.name,
|
|
1118
|
+
path: project.fullPath || project.path,
|
|
1119
|
+
}),
|
|
1120
|
+
});
|
|
1121
|
+
return true;
|
|
1122
|
+
}
|
|
1123
|
+
if (intent.action === 'show_provider_menu') {
|
|
1124
|
+
await showProviderMenu({ bot, chatId, link, editMessageId });
|
|
1125
|
+
return true;
|
|
1126
|
+
}
|
|
1127
|
+
if (intent.action === 'select_provider' && intent.provider) {
|
|
1128
|
+
updateTelegramControlState(link.user_id, { selectedProvider: intent.provider, selectedModel: null });
|
|
1129
|
+
await showModelMenu({ bot, chatId, link, editMessageId });
|
|
1130
|
+
return true;
|
|
1131
|
+
}
|
|
1132
|
+
if (intent.action === 'show_model_menu') {
|
|
1133
|
+
await showModelMenu({ bot, chatId, link, editMessageId });
|
|
1134
|
+
return true;
|
|
1135
|
+
}
|
|
1136
|
+
if (intent.action === 'select_model' && intent.model) {
|
|
1137
|
+
updateTelegramControlState(link.user_id, { selectedModel: intent.model });
|
|
1138
|
+
await showMainMenu({
|
|
1139
|
+
bot,
|
|
1140
|
+
chatId,
|
|
1141
|
+
link,
|
|
1142
|
+
editMessageId,
|
|
1143
|
+
notice: t(languageFor(link), 'control.modelSelected', { model: intent.model }),
|
|
1144
|
+
});
|
|
1145
|
+
return true;
|
|
1146
|
+
}
|
|
1147
|
+
if (intent.action === 'show_runs') {
|
|
1148
|
+
await showRuns({ bot, chatId, link, editMessageId });
|
|
1149
|
+
return true;
|
|
1150
|
+
}
|
|
1151
|
+
if (intent.action === 'show_approvals') {
|
|
1152
|
+
await showApprovalQueue({ bot, chatId, link, editMessageId });
|
|
1153
|
+
return true;
|
|
1154
|
+
}
|
|
1155
|
+
if (intent.action === 'show_workflows') {
|
|
1156
|
+
await showWorkflowMenu({ bot, chatId, link, editMessageId });
|
|
1157
|
+
return true;
|
|
1158
|
+
}
|
|
1159
|
+
if (intent.action === 'run_workflow') {
|
|
1160
|
+
await runWorkflow({ bot, chatId, link, input: intent.workflowInput || text, activity });
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
if (intent.action === 'show_sessions') {
|
|
1164
|
+
await showSessions({ bot, chatId, link, editMessageId });
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
if (intent.action === 'new_chat') {
|
|
1168
|
+
await startNewChat({ bot, chatId, link, editMessageId });
|
|
1169
|
+
return true;
|
|
1170
|
+
}
|
|
1171
|
+
await runAgent({ bot, chatId, link, prompt: text, activity });
|
|
1172
|
+
return true;
|
|
1173
|
+
}
|
|
500
1174
|
async function fetchRun(userId, runId) {
|
|
501
1175
|
return localApi(userId, `/api/orchestration/workflows/runs/${runId}`);
|
|
502
1176
|
}
|
|
503
1177
|
function summarizeRun(run, mode) {
|
|
1178
|
+
const statusIcon = run.status === 'completed'
|
|
1179
|
+
? '✅'
|
|
1180
|
+
: run.status === 'failed'
|
|
1181
|
+
? '❌'
|
|
1182
|
+
: run.status === 'canceled'
|
|
1183
|
+
? '⏹'
|
|
1184
|
+
: '🔧';
|
|
504
1185
|
const lines = [
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
1186
|
+
`${statusIcon} Run ${run.id}`,
|
|
1187
|
+
`🧩 Workflow: ${run.workflowId}`,
|
|
1188
|
+
`📌 Status: ${run.status}`,
|
|
508
1189
|
];
|
|
509
1190
|
const nodeRuns = Array.isArray(run.nodeRuns) ? run.nodeRuns : [];
|
|
510
1191
|
if (mode !== 'final') {
|
|
@@ -512,7 +1193,14 @@ function summarizeRun(run, mode) {
|
|
|
512
1193
|
? nodeRuns.filter((node) => node.error || node.status === 'failed')
|
|
513
1194
|
: nodeRuns;
|
|
514
1195
|
for (const node of visibleNodes) {
|
|
515
|
-
|
|
1196
|
+
const nodeIcon = node.status === 'completed'
|
|
1197
|
+
? '✅'
|
|
1198
|
+
: node.status === 'failed'
|
|
1199
|
+
? '❌'
|
|
1200
|
+
: node.status === 'running'
|
|
1201
|
+
? '🔧'
|
|
1202
|
+
: '⏳';
|
|
1203
|
+
lines.push(`${nodeIcon} ${node.status}: ${node.agentLabel || node.nodeId}${node.error ? ` - ${node.error}` : ''}`);
|
|
516
1204
|
}
|
|
517
1205
|
}
|
|
518
1206
|
const outputs = nodeRuns
|
|
@@ -521,7 +1209,7 @@ function summarizeRun(run, mode) {
|
|
|
521
1209
|
.map((node) => `\n${node.agentLabel || node.nodeId}:\n${node.outputText}`);
|
|
522
1210
|
return truncate(`${lines.join('\n')}${outputs.join('\n')}`);
|
|
523
1211
|
}
|
|
524
|
-
async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
1212
|
+
async function monitorWorkflowRun({ bot, chatId, link, runId, activity = null }) {
|
|
525
1213
|
if (runMonitors.has(runId))
|
|
526
1214
|
return;
|
|
527
1215
|
runMonitors.set(runId, true);
|
|
@@ -532,12 +1220,24 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
532
1220
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
533
1221
|
const run = await fetchRun(link.user_id, runId);
|
|
534
1222
|
const nodeRuns = Array.isArray(run.nodeRuns) ? run.nodeRuns : [];
|
|
1223
|
+
if (activity && !TERMINAL_RUN_STATES.has(run.status)) {
|
|
1224
|
+
activity.status = 'running';
|
|
1225
|
+
activity.phase = `${t(activity.lang, 'control.activity.workflowRunning')} (${run.status})`;
|
|
1226
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1227
|
+
}
|
|
535
1228
|
if (state.progressMode === 'all') {
|
|
536
1229
|
for (const node of nodeRuns) {
|
|
537
1230
|
const key = `${node.nodeId}:${node.status}`;
|
|
538
1231
|
if (!seenNodeStatus.has(key)) {
|
|
539
1232
|
seenNodeStatus.set(key, true);
|
|
540
|
-
|
|
1233
|
+
if (activity) {
|
|
1234
|
+
pushActivityEvent(activity, `${node.status === 'completed' ? '✅' : node.status === 'failed' ? '❌' : '🔧'} ${node.agentLabel || node.nodeId}: ${node.status}${node.error ? ` - ${node.error}` : ''}`);
|
|
1235
|
+
activity.phase = t(activity.lang, 'control.activity.workflowRunning');
|
|
1236
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1237
|
+
}
|
|
1238
|
+
else {
|
|
1239
|
+
await send(bot, chatId, `${node.agentLabel || node.nodeId}: ${node.status}${node.error ? `\n${node.error}` : ''}`);
|
|
1240
|
+
}
|
|
541
1241
|
}
|
|
542
1242
|
}
|
|
543
1243
|
}
|
|
@@ -546,12 +1246,29 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
546
1246
|
const key = `${node.nodeId}:${node.status}:${node.error || ''}`;
|
|
547
1247
|
if (!seenNodeStatus.has(key)) {
|
|
548
1248
|
seenNodeStatus.set(key, true);
|
|
549
|
-
|
|
1249
|
+
if (activity) {
|
|
1250
|
+
pushActivityEvent(activity, `❌ ${node.agentLabel || node.nodeId}: ${node.status}${node.error ? ` - ${node.error}` : ''}`);
|
|
1251
|
+
activity.phase = t(activity.lang, 'control.activity.workflowRunning');
|
|
1252
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
await send(bot, chatId, `${node.agentLabel || node.nodeId}: ${node.status}\n${node.error || ''}`);
|
|
1256
|
+
}
|
|
550
1257
|
}
|
|
551
1258
|
}
|
|
552
1259
|
}
|
|
553
1260
|
if (TERMINAL_RUN_STATES.has(run.status)) {
|
|
554
|
-
|
|
1261
|
+
if (activity) {
|
|
1262
|
+
activity.status = run.status === 'completed' ? 'done' : 'failed';
|
|
1263
|
+
activity.phase = run.status === 'completed'
|
|
1264
|
+
? t(activity.lang, 'control.activity.done')
|
|
1265
|
+
: t(activity.lang, 'control.activity.failed');
|
|
1266
|
+
activity.output = summarizeRun(run, state.progressMode);
|
|
1267
|
+
await editTelegramActivity({ bot, chatId, activity, force: true });
|
|
1268
|
+
}
|
|
1269
|
+
else {
|
|
1270
|
+
await send(bot, chatId, summarizeRun(run, state.progressMode));
|
|
1271
|
+
}
|
|
555
1272
|
return;
|
|
556
1273
|
}
|
|
557
1274
|
}
|
|
@@ -560,18 +1277,47 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
560
1277
|
runMonitors.delete(runId);
|
|
561
1278
|
}
|
|
562
1279
|
}
|
|
563
|
-
export async function startCliInstall({ bot, chatId, link, provider }) {
|
|
1280
|
+
export async function startCliInstall({ bot, chatId, link, provider, editMessageId, confirmed = false }) {
|
|
564
1281
|
const lang = languageFor(link);
|
|
565
|
-
|
|
1282
|
+
if (!PROVIDERS.includes(provider)) {
|
|
1283
|
+
await send(bot, chatId, t(lang, 'control.providerAuthFallback'), { editMessageId });
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const state = getState(link.user_id);
|
|
1287
|
+
if (state.remoteControlEnabled === false) {
|
|
1288
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId });
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
if (state.confirmationPolicy === 'strict' && !confirmed) {
|
|
1292
|
+
await requestConfirmation({
|
|
1293
|
+
bot,
|
|
1294
|
+
chatId,
|
|
1295
|
+
link,
|
|
1296
|
+
action: 'install_provider',
|
|
1297
|
+
payload: { provider },
|
|
1298
|
+
editMessageId,
|
|
1299
|
+
});
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
const result = await runTelegramTool({
|
|
1303
|
+
userId: link.user_id,
|
|
1304
|
+
action: 'install_provider',
|
|
1305
|
+
execute: () => localApi(link.user_id, `/api/providers/${provider}/install`, { method: 'POST' }),
|
|
1306
|
+
});
|
|
1307
|
+
if (!result.ok) {
|
|
1308
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId });
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const data = result.data;
|
|
566
1312
|
if (data?.manual) {
|
|
567
|
-
await send(bot, chatId, t(lang, 'control.manualInstall', { provider, manual: data.manual }));
|
|
1313
|
+
await send(bot, chatId, t(lang, 'control.manualInstall', { provider, manual: data.manual }), { editMessageId });
|
|
568
1314
|
return;
|
|
569
1315
|
}
|
|
570
1316
|
await send(bot, chatId, t(lang, 'control.installStarted', {
|
|
571
1317
|
provider,
|
|
572
1318
|
jobId: data.jobId,
|
|
573
1319
|
command: data.installCmd || 'internal installer',
|
|
574
|
-
}));
|
|
1320
|
+
}), { editMessageId });
|
|
575
1321
|
}
|
|
576
1322
|
async function showInstallMenu({ bot, chatId, link, editMessageId }) {
|
|
577
1323
|
const lang = languageFor(link);
|
|
@@ -747,15 +1493,12 @@ async function handleCommand({ bot, chatId, link, text }) {
|
|
|
747
1493
|
await send(bot, chatId, t(lang, 'control.cancelUsage'));
|
|
748
1494
|
return true;
|
|
749
1495
|
}
|
|
750
|
-
|
|
751
|
-
method: 'POST',
|
|
752
|
-
});
|
|
753
|
-
await send(bot, chatId, t(lang, 'control.runStatus', { runId: run.id, status: run.status }));
|
|
1496
|
+
await cancelRun({ bot, chatId, link, runId: argText });
|
|
754
1497
|
return true;
|
|
755
1498
|
}
|
|
756
1499
|
return false;
|
|
757
1500
|
}
|
|
758
|
-
|
|
1501
|
+
async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
759
1502
|
const chatId = msg.chat.id;
|
|
760
1503
|
const text = String(msg.text || '').trim();
|
|
761
1504
|
if (!text)
|
|
@@ -773,12 +1516,36 @@ export async function handleTelegramControlMessage({ bot, msg, link }) {
|
|
|
773
1516
|
if (await handleCommand({ bot, chatId, link, text }))
|
|
774
1517
|
return true;
|
|
775
1518
|
const state = getState(link.user_id);
|
|
1519
|
+
if (state.routerEnabled === false)
|
|
1520
|
+
return false;
|
|
776
1521
|
if (!state.remoteControlEnabled) {
|
|
777
1522
|
await send(bot, chatId, t(languageFor(link), 'control.disabled'));
|
|
778
1523
|
return true;
|
|
779
1524
|
}
|
|
780
|
-
await
|
|
781
|
-
|
|
1525
|
+
const activity = await createTelegramActivity({
|
|
1526
|
+
bot,
|
|
1527
|
+
chatId,
|
|
1528
|
+
link,
|
|
1529
|
+
type: 'router',
|
|
1530
|
+
prompt: text,
|
|
1531
|
+
phase: t(languageFor(link), 'control.activity.interpreting'),
|
|
1532
|
+
});
|
|
1533
|
+
return handleRoutedIntent({ bot, chatId, link, text, activity });
|
|
1534
|
+
}
|
|
1535
|
+
export async function handleTelegramControlMessage(args) {
|
|
1536
|
+
const chatId = args?.msg?.chat?.id;
|
|
1537
|
+
if (!chatId)
|
|
1538
|
+
return false;
|
|
1539
|
+
return enqueueTelegramJob(chatId, async () => {
|
|
1540
|
+
try {
|
|
1541
|
+
return await handleTelegramControlMessageInternal(args);
|
|
1542
|
+
}
|
|
1543
|
+
catch (error) {
|
|
1544
|
+
console.error('[telegram-control] message handler failed:', error);
|
|
1545
|
+
await send(args.bot, chatId, t(languageFor(args.link), 'error.generic')).catch(() => { });
|
|
1546
|
+
return true;
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
782
1549
|
}
|
|
783
1550
|
async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
784
1551
|
const lang = languageFor(link);
|
|
@@ -793,7 +1560,7 @@ async function showRunDetail({ bot, chatId, link, runId, editMessageId }) {
|
|
|
793
1560
|
},
|
|
794
1561
|
});
|
|
795
1562
|
}
|
|
796
|
-
|
|
1563
|
+
async function handleTelegramControlCallbackInternal({ bot, query, link }) {
|
|
797
1564
|
const chatId = query.message?.chat?.id;
|
|
798
1565
|
if (!chatId)
|
|
799
1566
|
return;
|
|
@@ -807,6 +1574,33 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
807
1574
|
}
|
|
808
1575
|
await bot.answerCallbackQuery(query.id).catch(() => { });
|
|
809
1576
|
const { action, payload } = entry;
|
|
1577
|
+
if (action === 'confirm_action') {
|
|
1578
|
+
forgetAction(entry.id);
|
|
1579
|
+
const lang = languageFor(link);
|
|
1580
|
+
const result = consumeTelegramConfirmation(link.user_id, payload.id);
|
|
1581
|
+
if (!result.ok) {
|
|
1582
|
+
await send(bot, chatId, t(lang, result.reason === 'expired'
|
|
1583
|
+
? 'control.confirmationExpired'
|
|
1584
|
+
: 'control.confirmationMissing'), { editMessageId });
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
if (await executeConfirmedAction({
|
|
1588
|
+
bot,
|
|
1589
|
+
chatId,
|
|
1590
|
+
link,
|
|
1591
|
+
confirmation: result.confirmation,
|
|
1592
|
+
editMessageId,
|
|
1593
|
+
}))
|
|
1594
|
+
return;
|
|
1595
|
+
await send(bot, chatId, t(lang, 'error.generic'), { editMessageId });
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
if (action === 'cancel_confirmation') {
|
|
1599
|
+
forgetAction(entry.id);
|
|
1600
|
+
clearTelegramConfirmation(link.user_id, payload.id);
|
|
1601
|
+
await send(bot, chatId, t(languageFor(link), 'control.confirmationCanceled'), { editMessageId });
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
810
1604
|
if (action === 'menu')
|
|
811
1605
|
return showMainMenu({ bot, chatId, link, editMessageId });
|
|
812
1606
|
if (action === 'projects')
|
|
@@ -889,20 +1683,26 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
889
1683
|
if (action === 'run_detail')
|
|
890
1684
|
return showRunDetail({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
891
1685
|
if (action === 'run_cancel') {
|
|
892
|
-
|
|
893
|
-
method: 'POST',
|
|
894
|
-
});
|
|
895
|
-
await send(bot, chatId, t(languageFor(link), 'control.runStatus', { runId: run.id, status: run.status }), { editMessageId });
|
|
1686
|
+
await cancelRun({ bot, chatId, link, runId: payload.runId, editMessageId });
|
|
896
1687
|
return;
|
|
897
1688
|
}
|
|
898
1689
|
if (action === 'approval_decide') {
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1690
|
+
const toolResult = await runTelegramTool({
|
|
1691
|
+
userId: link.user_id,
|
|
1692
|
+
action: 'approval_decide',
|
|
1693
|
+
execute: () => localApi(link.user_id, `/api/orchestration/workflows/approvals/${encodeURIComponent(payload.approvalId)}`, {
|
|
1694
|
+
method: 'POST',
|
|
1695
|
+
body: {
|
|
1696
|
+
allow: payload.allow === true,
|
|
1697
|
+
source: 'telegram',
|
|
1698
|
+
},
|
|
1699
|
+
}),
|
|
905
1700
|
});
|
|
1701
|
+
if (!toolResult.ok) {
|
|
1702
|
+
await sendToolFailure({ bot, chatId, link, result: toolResult, editMessageId });
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
const result = toolResult.data;
|
|
906
1706
|
const lang = languageFor(link);
|
|
907
1707
|
await send(bot, chatId, t(lang, 'control.approvalDecided', {
|
|
908
1708
|
approvalId: payload.approvalId,
|
|
@@ -912,7 +1712,7 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
912
1712
|
return showApprovalQueue({ bot, chatId, link });
|
|
913
1713
|
}
|
|
914
1714
|
if (action === 'install_provider')
|
|
915
|
-
return startCliInstall({ bot, chatId, link, provider: payload.provider });
|
|
1715
|
+
return startCliInstall({ bot, chatId, link, provider: payload.provider, editMessageId });
|
|
916
1716
|
if (action === 'auth_provider') {
|
|
917
1717
|
await send(bot, chatId, `${payload.provider} login:\n${AUTH_HELP[payload.provider] || t(languageFor(link), 'control.providerAuthFallback')}`, { editMessageId });
|
|
918
1718
|
return;
|
|
@@ -937,4 +1737,20 @@ export async function handleTelegramControlCallback({ bot, query, link }) {
|
|
|
937
1737
|
});
|
|
938
1738
|
}
|
|
939
1739
|
}
|
|
1740
|
+
export async function handleTelegramControlCallback(args) {
|
|
1741
|
+
const chatId = args?.query?.message?.chat?.id;
|
|
1742
|
+
if (!chatId)
|
|
1743
|
+
return;
|
|
1744
|
+
return enqueueTelegramJob(chatId, async () => {
|
|
1745
|
+
try {
|
|
1746
|
+
await handleTelegramControlCallbackInternal(args);
|
|
1747
|
+
}
|
|
1748
|
+
catch (error) {
|
|
1749
|
+
console.error('[telegram-control] callback failed:', error);
|
|
1750
|
+
const lang = languageFor(args.link);
|
|
1751
|
+
await args.bot?.answerCallbackQuery(args.query?.id, { text: t(lang, 'error.generic') }).catch(() => { });
|
|
1752
|
+
await send(args.bot, chatId, t(lang, 'error.generic')).catch(() => { });
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
940
1756
|
//# sourceMappingURL=control-center.js.map
|