@pixelbyte-software/pixcode 1.53.5 → 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-CyeODv14.js → index-DP7jnMyc.js} +3 -3
- package/dist/index.html +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/services/telegram/control-center.js +575 -82
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/telegram-gateway.js +111 -45
- package/dist-server/server/services/telegram/telegram-gateway.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +54 -0
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/telegram-control.mjs +40 -1
- package/server/openai-codex.js +5 -0
- package/server/routes/agent.js +17 -7
- package/server/services/telegram/control-center.js +565 -80
- package/server/services/telegram/telegram-gateway.js +115 -51
- package/server/services/telegram/translations.js +54 -0
|
@@ -2,13 +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,
|
|
5
|
+
import { TELEGRAM_CONTROL_SCOPES, TELEGRAM_PROVIDERS, buildTelegramAgentPrompt, buildTelegramIntentPrompt, clearTelegramConfirmation, consumeTelegramConfirmation, createTelegramConfirmation, enqueueTelegramJob, parseTelegramAiIntentResponse, resolveTelegramModel, resolveTelegramProvider, retryWithBackoff, runTelegramTool, splitTelegramText, } from './telegram-gateway.js';
|
|
6
6
|
import { SUPPORTED_LANGUAGES, t } from './translations.js';
|
|
7
7
|
const PROVIDERS = TELEGRAM_PROVIDERS;
|
|
8
8
|
const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
|
|
9
9
|
const CALLBACK_TTL_MS = 10 * 60 * 1000;
|
|
10
10
|
const MAX_CALLBACK_ACTIONS = 1000;
|
|
11
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;
|
|
12
15
|
const callbackActions = new Map();
|
|
13
16
|
const runMonitors = new Map();
|
|
14
17
|
const MODEL_FALLBACKS = Object.fromEntries(PROVIDERS.map((provider) => [provider, getStaticProviderModels(provider)]));
|
|
@@ -196,16 +199,24 @@ function getOrCreateTelegramApiKey(userId) {
|
|
|
196
199
|
}
|
|
197
200
|
return apiKeysDb.createApiKey(userId, 'Telegram Control', TELEGRAM_CONTROL_SCOPES).apiKey;
|
|
198
201
|
}
|
|
199
|
-
async function localApi(userId, path, { method = 'GET', body } = {}) {
|
|
202
|
+
async function localApi(userId, path, { method = 'GET', body, timeoutMs = 0 } = {}) {
|
|
200
203
|
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
201
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;
|
|
202
209
|
const res = await fetch(`${localApiBase()}${path}`, {
|
|
203
210
|
method,
|
|
204
211
|
headers: {
|
|
205
212
|
'Content-Type': 'application/json',
|
|
206
213
|
'X-API-Key': apiKey,
|
|
207
214
|
},
|
|
215
|
+
signal: controller?.signal,
|
|
208
216
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
217
|
+
}).finally(() => {
|
|
218
|
+
if (timeoutId)
|
|
219
|
+
clearTimeout(timeoutId);
|
|
209
220
|
});
|
|
210
221
|
if ([408, 429, 500, 502, 503, 504].includes(res.status)) {
|
|
211
222
|
const error = new Error(`HTTP ${res.status}`);
|
|
@@ -221,9 +232,198 @@ async function localApi(userId, path, { method = 'GET', body } = {}) {
|
|
|
221
232
|
}
|
|
222
233
|
return data?.data ?? data;
|
|
223
234
|
}
|
|
235
|
+
async function localAgentStream(userId, body, onEvent) {
|
|
236
|
+
const apiKey = getOrCreateTelegramApiKey(userId);
|
|
237
|
+
const response = await fetch(`${localApiBase()}/api/agent`, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: {
|
|
240
|
+
'Content-Type': 'application/json',
|
|
241
|
+
'X-API-Key': apiKey,
|
|
242
|
+
},
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
...body,
|
|
245
|
+
stream: true,
|
|
246
|
+
}),
|
|
247
|
+
});
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
const data = await response.json().catch(() => ({}));
|
|
250
|
+
const error = data?.error?.message || data?.error || `HTTP ${response.status}`;
|
|
251
|
+
throw new Error(typeof error === 'string' ? error : JSON.stringify(error));
|
|
252
|
+
}
|
|
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);
|
|
292
|
+
}
|
|
224
293
|
function checked(label) {
|
|
225
294
|
return `${label} ✓`;
|
|
226
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
|
+
}
|
|
227
427
|
function stateSummary(lang, state) {
|
|
228
428
|
return [
|
|
229
429
|
`${t(lang, 'control.summary.project')}: ${state.selectedProjectName || t(lang, 'control.notSelected')}`,
|
|
@@ -268,11 +468,12 @@ async function listProjects() {
|
|
|
268
468
|
const projects = await getProjects();
|
|
269
469
|
return projects.slice(0, 20);
|
|
270
470
|
}
|
|
271
|
-
async function showProjectMenu({ bot, chatId, link, editMessageId }) {
|
|
471
|
+
async function showProjectMenu({ bot, chatId, link, editMessageId, notice }) {
|
|
272
472
|
const lang = languageFor(link);
|
|
273
473
|
const projects = await listProjects();
|
|
274
474
|
if (projects.length === 0) {
|
|
275
|
-
|
|
475
|
+
const prefix = notice ? `${notice}\n\n` : '';
|
|
476
|
+
await send(bot, chatId, `${prefix}${t(lang, 'control.noProjects')}`, { editMessageId });
|
|
276
477
|
return;
|
|
277
478
|
}
|
|
278
479
|
const buttons = projects.map((project, index) => button(`${index + 1}. ${compact(project.displayName || project.name, 34)}`, 'project_select', {
|
|
@@ -280,7 +481,8 @@ async function showProjectMenu({ bot, chatId, link, editMessageId }) {
|
|
|
280
481
|
path: project.fullPath || project.path,
|
|
281
482
|
displayName: project.displayName || project.name,
|
|
282
483
|
}));
|
|
283
|
-
|
|
484
|
+
const prefix = notice ? `${notice}\n\n` : '';
|
|
485
|
+
await send(bot, chatId, `${prefix}${t(lang, 'control.pickProject')}`, {
|
|
284
486
|
editMessageId,
|
|
285
487
|
reply_markup: { inline_keyboard: rows(buttons, 1) },
|
|
286
488
|
});
|
|
@@ -464,6 +666,123 @@ function extractAssistantText(response) {
|
|
|
464
666
|
}
|
|
465
667
|
return chunks.join('\n\n').trim();
|
|
466
668
|
}
|
|
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
|
+
}
|
|
467
786
|
function confirmationLabel(lang, action, payload = {}) {
|
|
468
787
|
if (action === 'install_provider') {
|
|
469
788
|
return t(lang, 'control.confirmInstall', { provider: payload.provider || '' });
|
|
@@ -494,73 +813,101 @@ async function sendToolFailure({ bot, chatId, link, result, editMessageId }) {
|
|
|
494
813
|
: (result?.message || t(lang, 'error.generic'));
|
|
495
814
|
await send(bot, chatId, message, { editMessageId });
|
|
496
815
|
}
|
|
497
|
-
async function runAgent({ bot, chatId, link, prompt }) {
|
|
816
|
+
async function runAgent({ bot, chatId, link, prompt, activity = null }) {
|
|
498
817
|
const lang = languageFor(link);
|
|
499
818
|
const state = getState(link.user_id);
|
|
500
819
|
if (!state.remoteControlEnabled) {
|
|
501
|
-
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
820
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId: activity?.messageId });
|
|
502
821
|
return;
|
|
503
822
|
}
|
|
504
823
|
if (!state.selectedProjectPath) {
|
|
505
|
-
await
|
|
506
|
-
await showProjectMenu({ bot, chatId, link });
|
|
824
|
+
await showProjectMenu({ bot, chatId, link, editMessageId: activity?.messageId, notice: t(lang, 'control.selectProjectFirst') });
|
|
507
825
|
return;
|
|
508
826
|
}
|
|
509
827
|
const provider = resolveTelegramProvider(state);
|
|
510
828
|
const model = resolveTelegramModel(state);
|
|
511
|
-
|
|
512
|
-
|
|
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, {
|
|
847
|
+
projectPath: state.selectedProjectPath,
|
|
513
848
|
provider,
|
|
514
|
-
|
|
515
|
-
|
|
849
|
+
model: model || undefined,
|
|
850
|
+
message: buildTelegramAgentPrompt(prompt, state),
|
|
851
|
+
cleanup: false,
|
|
852
|
+
permissionMode: 'default',
|
|
853
|
+
}, async (event) => {
|
|
854
|
+
applyAgentStreamEvent(active, event);
|
|
855
|
+
await editTelegramActivity({ bot, chatId, activity: active });
|
|
856
|
+
});
|
|
516
857
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
cleanup: false,
|
|
529
|
-
stream: false,
|
|
530
|
-
},
|
|
531
|
-
}),
|
|
532
|
-
});
|
|
533
|
-
if (!result.ok) {
|
|
534
|
-
await sendToolFailure({ bot, chatId, link, result });
|
|
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 });
|
|
535
869
|
return;
|
|
536
870
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
await send(bot, chatId, `${statusLine}\n\n${assistantText || response.error || t(lang, 'control.noAssistantText')}`);
|
|
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 });
|
|
543
876
|
}
|
|
544
|
-
export async function runWorkflow({ bot, chatId, link, input }) {
|
|
877
|
+
export async function runWorkflow({ bot, chatId, link, input, activity = null }) {
|
|
545
878
|
const lang = languageFor(link);
|
|
546
879
|
const state = getState(link.user_id);
|
|
547
880
|
const workflowId = state.selectedWorkflowId;
|
|
548
881
|
if (!state.remoteControlEnabled) {
|
|
549
|
-
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
882
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId: activity?.messageId });
|
|
550
883
|
return;
|
|
551
884
|
}
|
|
552
885
|
if (!workflowId) {
|
|
553
|
-
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'));
|
|
554
|
-
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 });
|
|
555
888
|
return;
|
|
556
889
|
}
|
|
557
890
|
if (!state.selectedProjectPath) {
|
|
558
|
-
await
|
|
559
|
-
await showProjectMenu({ bot, chatId, link });
|
|
891
|
+
await showProjectMenu({ bot, chatId, link, editMessageId: activity?.messageId, notice: t(lang, 'control.selectProjectFirst') });
|
|
560
892
|
return;
|
|
561
893
|
}
|
|
562
894
|
const provider = resolveTelegramProvider(state);
|
|
563
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 });
|
|
564
911
|
const result = await runTelegramTool({
|
|
565
912
|
userId: link.user_id,
|
|
566
913
|
action: 'run_workflow',
|
|
@@ -581,12 +928,15 @@ export async function runWorkflow({ bot, chatId, link, input }) {
|
|
|
581
928
|
}),
|
|
582
929
|
});
|
|
583
930
|
if (!result.ok) {
|
|
584
|
-
await sendToolFailure({ bot, chatId, link, result });
|
|
931
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId: active.messageId });
|
|
585
932
|
return;
|
|
586
933
|
}
|
|
587
934
|
const run = result.data;
|
|
588
|
-
|
|
589
|
-
|
|
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) => {
|
|
590
940
|
console.warn('[telegram-control] workflow monitor failed:', error?.message || error);
|
|
591
941
|
});
|
|
592
942
|
}
|
|
@@ -663,21 +1013,95 @@ async function findProjectByQuery(query) {
|
|
|
663
1013
|
return candidates.some((candidate) => candidate === needle || candidate.includes(needle));
|
|
664
1014
|
}) || null;
|
|
665
1015
|
}
|
|
666
|
-
async function
|
|
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 }) {
|
|
667
1030
|
const state = getState(link.user_id);
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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 });
|
|
673
1091
|
return true;
|
|
674
1092
|
}
|
|
675
|
-
if (intent.action === '
|
|
676
|
-
const
|
|
1093
|
+
if (intent.action === 'select_project') {
|
|
1094
|
+
const query = intent.projectQuery || intent.prompt || text;
|
|
1095
|
+
const project = await findProjectByQuery(query);
|
|
677
1096
|
const lang = languageFor(link);
|
|
678
1097
|
if (!project) {
|
|
679
|
-
await
|
|
680
|
-
|
|
1098
|
+
await showProjectMenu({
|
|
1099
|
+
bot,
|
|
1100
|
+
chatId,
|
|
1101
|
+
link,
|
|
1102
|
+
editMessageId,
|
|
1103
|
+
notice: t(lang, 'control.projectNotFound', { query }),
|
|
1104
|
+
});
|
|
681
1105
|
return true;
|
|
682
1106
|
}
|
|
683
1107
|
updateTelegramControlState(link.user_id, {
|
|
@@ -688,6 +1112,7 @@ async function handleRoutedIntent({ bot, chatId, link, text }) {
|
|
|
688
1112
|
bot,
|
|
689
1113
|
chatId,
|
|
690
1114
|
link,
|
|
1115
|
+
editMessageId,
|
|
691
1116
|
notice: t(lang, 'control.projectSelected', {
|
|
692
1117
|
project: project.displayName || project.name,
|
|
693
1118
|
path: project.fullPath || project.path,
|
|
@@ -695,45 +1120,72 @@ async function handleRoutedIntent({ bot, chatId, link, text }) {
|
|
|
695
1120
|
});
|
|
696
1121
|
return true;
|
|
697
1122
|
}
|
|
698
|
-
if (intent.action === '
|
|
699
|
-
await showProviderMenu({ bot, chatId, link });
|
|
1123
|
+
if (intent.action === 'show_provider_menu') {
|
|
1124
|
+
await showProviderMenu({ bot, chatId, link, editMessageId });
|
|
700
1125
|
return true;
|
|
701
1126
|
}
|
|
702
|
-
if (intent.action === '
|
|
1127
|
+
if (intent.action === 'select_provider' && intent.provider) {
|
|
703
1128
|
updateTelegramControlState(link.user_id, { selectedProvider: intent.provider, selectedModel: null });
|
|
704
|
-
await showModelMenu({ bot, chatId, link });
|
|
1129
|
+
await showModelMenu({ bot, chatId, link, editMessageId });
|
|
705
1130
|
return true;
|
|
706
1131
|
}
|
|
707
|
-
if (intent.action === '
|
|
708
|
-
await showModelMenu({ bot, chatId, link });
|
|
1132
|
+
if (intent.action === 'show_model_menu') {
|
|
1133
|
+
await showModelMenu({ bot, chatId, link, editMessageId });
|
|
709
1134
|
return true;
|
|
710
1135
|
}
|
|
711
|
-
if (intent.action === '
|
|
712
|
-
|
|
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
|
+
});
|
|
713
1145
|
return true;
|
|
714
1146
|
}
|
|
715
|
-
if (intent.action === '
|
|
716
|
-
await
|
|
1147
|
+
if (intent.action === 'show_runs') {
|
|
1148
|
+
await showRuns({ bot, chatId, link, editMessageId });
|
|
717
1149
|
return true;
|
|
718
1150
|
}
|
|
719
|
-
if (intent.action === '
|
|
720
|
-
await
|
|
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 });
|
|
721
1165
|
return true;
|
|
722
1166
|
}
|
|
723
1167
|
if (intent.action === 'new_chat') {
|
|
724
|
-
await startNewChat({ bot, chatId, link });
|
|
1168
|
+
await startNewChat({ bot, chatId, link, editMessageId });
|
|
725
1169
|
return true;
|
|
726
1170
|
}
|
|
727
|
-
|
|
1171
|
+
await runAgent({ bot, chatId, link, prompt: text, activity });
|
|
1172
|
+
return true;
|
|
728
1173
|
}
|
|
729
1174
|
async function fetchRun(userId, runId) {
|
|
730
1175
|
return localApi(userId, `/api/orchestration/workflows/runs/${runId}`);
|
|
731
1176
|
}
|
|
732
1177
|
function summarizeRun(run, mode) {
|
|
1178
|
+
const statusIcon = run.status === 'completed'
|
|
1179
|
+
? '✅'
|
|
1180
|
+
: run.status === 'failed'
|
|
1181
|
+
? '❌'
|
|
1182
|
+
: run.status === 'canceled'
|
|
1183
|
+
? '⏹'
|
|
1184
|
+
: '🔧';
|
|
733
1185
|
const lines = [
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1186
|
+
`${statusIcon} Run ${run.id}`,
|
|
1187
|
+
`🧩 Workflow: ${run.workflowId}`,
|
|
1188
|
+
`📌 Status: ${run.status}`,
|
|
737
1189
|
];
|
|
738
1190
|
const nodeRuns = Array.isArray(run.nodeRuns) ? run.nodeRuns : [];
|
|
739
1191
|
if (mode !== 'final') {
|
|
@@ -741,7 +1193,14 @@ function summarizeRun(run, mode) {
|
|
|
741
1193
|
? nodeRuns.filter((node) => node.error || node.status === 'failed')
|
|
742
1194
|
: nodeRuns;
|
|
743
1195
|
for (const node of visibleNodes) {
|
|
744
|
-
|
|
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}` : ''}`);
|
|
745
1204
|
}
|
|
746
1205
|
}
|
|
747
1206
|
const outputs = nodeRuns
|
|
@@ -750,7 +1209,7 @@ function summarizeRun(run, mode) {
|
|
|
750
1209
|
.map((node) => `\n${node.agentLabel || node.nodeId}:\n${node.outputText}`);
|
|
751
1210
|
return truncate(`${lines.join('\n')}${outputs.join('\n')}`);
|
|
752
1211
|
}
|
|
753
|
-
async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
1212
|
+
async function monitorWorkflowRun({ bot, chatId, link, runId, activity = null }) {
|
|
754
1213
|
if (runMonitors.has(runId))
|
|
755
1214
|
return;
|
|
756
1215
|
runMonitors.set(runId, true);
|
|
@@ -761,12 +1220,24 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
761
1220
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
762
1221
|
const run = await fetchRun(link.user_id, runId);
|
|
763
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
|
+
}
|
|
764
1228
|
if (state.progressMode === 'all') {
|
|
765
1229
|
for (const node of nodeRuns) {
|
|
766
1230
|
const key = `${node.nodeId}:${node.status}`;
|
|
767
1231
|
if (!seenNodeStatus.has(key)) {
|
|
768
1232
|
seenNodeStatus.set(key, true);
|
|
769
|
-
|
|
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
|
+
}
|
|
770
1241
|
}
|
|
771
1242
|
}
|
|
772
1243
|
}
|
|
@@ -775,12 +1246,29 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
775
1246
|
const key = `${node.nodeId}:${node.status}:${node.error || ''}`;
|
|
776
1247
|
if (!seenNodeStatus.has(key)) {
|
|
777
1248
|
seenNodeStatus.set(key, true);
|
|
778
|
-
|
|
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
|
+
}
|
|
779
1257
|
}
|
|
780
1258
|
}
|
|
781
1259
|
}
|
|
782
1260
|
if (TERMINAL_RUN_STATES.has(run.status)) {
|
|
783
|
-
|
|
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
|
+
}
|
|
784
1272
|
return;
|
|
785
1273
|
}
|
|
786
1274
|
}
|
|
@@ -1027,8 +1515,6 @@ async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
|
1027
1515
|
return true;
|
|
1028
1516
|
if (await handleCommand({ bot, chatId, link, text }))
|
|
1029
1517
|
return true;
|
|
1030
|
-
if (await handleRoutedIntent({ bot, chatId, link, text }))
|
|
1031
|
-
return true;
|
|
1032
1518
|
const state = getState(link.user_id);
|
|
1033
1519
|
if (state.routerEnabled === false)
|
|
1034
1520
|
return false;
|
|
@@ -1036,8 +1522,15 @@ async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
|
1036
1522
|
await send(bot, chatId, t(languageFor(link), 'control.disabled'));
|
|
1037
1523
|
return true;
|
|
1038
1524
|
}
|
|
1039
|
-
await
|
|
1040
|
-
|
|
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 });
|
|
1041
1534
|
}
|
|
1042
1535
|
export async function handleTelegramControlMessage(args) {
|
|
1043
1536
|
const chatId = args?.msg?.chat?.id;
|