@pixelbyte-software/pixcode 1.53.5 → 1.53.7
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-DM-KfWAm.js} +3 -3
- package/dist/index.html +1 -1
- package/dist-server/server/claude-sdk.js +17 -15
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cursor-cli.js +14 -12
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +14 -12
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/openai-codex.js +30 -22
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +14 -12
- package/dist-server/server/opencode-cli.js.map +1 -1
- package/dist-server/server/qwen-code-cli.js +14 -12
- package/dist-server/server/qwen-code-cli.js.map +1 -1
- package/dist-server/server/routes/agent.js +24 -7
- package/dist-server/server/routes/agent.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +593 -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 +60 -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/claude-sdk.js +3 -3
- package/server/cursor-cli.js +3 -3
- package/server/gemini-cli.js +3 -3
- package/server/openai-codex.js +10 -4
- package/server/opencode-cli.js +3 -3
- package/server/qwen-code-cli.js +3 -3
- package/server/routes/agent.js +24 -7
- package/server/services/telegram/control-center.js +593 -80
- package/server/services/telegram/telegram-gateway.js +115 -51
- package/server/services/telegram/translations.js +60 -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,214 @@ 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 trimTelegramOutput(text, max, suffix = '') {
|
|
346
|
+
const value = String(text || '').trim();
|
|
347
|
+
const ending = String(suffix || '').trim();
|
|
348
|
+
if (value.length <= max)
|
|
349
|
+
return value;
|
|
350
|
+
const room = Math.max(300, max - ending.length - 4);
|
|
351
|
+
return `${value.slice(0, room).trim()}\n\n${ending}`;
|
|
352
|
+
}
|
|
353
|
+
function renderActivity(activity, { finalText = null } = {}) {
|
|
354
|
+
const output = finalText || activity.output;
|
|
355
|
+
if (activity.type === 'agent' && output && !activity.error) {
|
|
356
|
+
if (activity.status === 'done') {
|
|
357
|
+
return trimTelegramOutput(output, 3400, t(activity.lang, 'control.activity.outputTooLong'));
|
|
358
|
+
}
|
|
359
|
+
const footer = `⏳ ${t(activity.lang, 'control.activity.liveFooter', { elapsed: formatElapsed(activity.startedAt) })}`;
|
|
360
|
+
const body = trimTelegramOutput(output, 3200, t(activity.lang, 'control.activity.outputShortened'));
|
|
361
|
+
return truncate(`${body}\n\n${footer}`, 3400);
|
|
362
|
+
}
|
|
363
|
+
const lines = [
|
|
364
|
+
`${activity.status === 'failed' ? '❌' : activity.status === 'done' ? '✅' : activity.status === 'running' ? '🔧' : '⏳'} ${activityTitle(activity)}`,
|
|
365
|
+
'',
|
|
366
|
+
`🤖 ${t(activity.lang, 'control.activity.provider')}: ${activity.provider || '-'}`,
|
|
367
|
+
`📁 ${t(activity.lang, 'control.activity.project')}: ${compact(activity.project, 90)}`,
|
|
368
|
+
];
|
|
369
|
+
if (activity.sessionId)
|
|
370
|
+
lines.push(`🧵 ${t(activity.lang, 'control.activity.session')}: ${activity.sessionId}`);
|
|
371
|
+
if (activity.runId)
|
|
372
|
+
lines.push(`🧭 ${t(activity.lang, 'control.activity.run')}: ${activity.runId}`);
|
|
373
|
+
if (activity.workflowId)
|
|
374
|
+
lines.push(`🧩 ${t(activity.lang, 'control.activity.workflow')}: ${activity.workflowId}`);
|
|
375
|
+
lines.push(`⏱ ${t(activity.lang, 'control.activity.elapsed')}: ${formatElapsed(activity.startedAt)}`);
|
|
376
|
+
lines.push(`📌 ${t(activity.lang, 'control.activity.status')}: ${activity.phase}`);
|
|
377
|
+
const visibleEvents = activity.mode === 'final'
|
|
378
|
+
? activity.events.slice(-4)
|
|
379
|
+
: activity.events;
|
|
380
|
+
if (visibleEvents.length > 0) {
|
|
381
|
+
lines.push('', `🛠 ${t(activity.lang, 'control.activity.work')}:`);
|
|
382
|
+
for (const event of visibleEvents)
|
|
383
|
+
lines.push(`• ${event}`);
|
|
384
|
+
}
|
|
385
|
+
if (activity.error) {
|
|
386
|
+
lines.push('', `⚠️ ${truncate(activity.error, 700)}`);
|
|
387
|
+
}
|
|
388
|
+
else if (output) {
|
|
389
|
+
lines.push('', `💬 ${t(activity.lang, 'control.activity.output')}:`);
|
|
390
|
+
lines.push(truncate(output, 1800));
|
|
391
|
+
}
|
|
392
|
+
else if (activity.prompt) {
|
|
393
|
+
lines.push('', `📝 ${compact(activity.prompt, 220)}`);
|
|
394
|
+
}
|
|
395
|
+
return truncate(lines.join('\n'), 3400);
|
|
396
|
+
}
|
|
397
|
+
async function createTelegramActivity({ bot, chatId, link, type = 'agent', prompt = '', phase = null }) {
|
|
398
|
+
const lang = languageFor(link);
|
|
399
|
+
const state = getState(link.user_id);
|
|
400
|
+
const activity = createActivityState({
|
|
401
|
+
lang,
|
|
402
|
+
type,
|
|
403
|
+
provider: resolveTelegramProvider(state),
|
|
404
|
+
project: projectLabel(state),
|
|
405
|
+
prompt,
|
|
406
|
+
mode: state.progressMode,
|
|
407
|
+
});
|
|
408
|
+
if (phase)
|
|
409
|
+
activity.phase = phase;
|
|
410
|
+
const sent = await send(bot, chatId, renderActivity(activity), { parse_mode: undefined });
|
|
411
|
+
activity.messageId = sent?.message_id || sent?.message?.message_id || null;
|
|
412
|
+
activity.lastEditAt = Date.now();
|
|
413
|
+
return activity;
|
|
414
|
+
}
|
|
415
|
+
async function editTelegramActivity({ bot, chatId, activity, force = false, reply_markup }) {
|
|
416
|
+
if (!activity)
|
|
417
|
+
return null;
|
|
418
|
+
const now = Date.now();
|
|
419
|
+
if (!force && now - activity.lastEditAt < ACTIVITY_EDIT_THROTTLE_MS)
|
|
420
|
+
return null;
|
|
421
|
+
const sent = await send(bot, chatId, renderActivity(activity), {
|
|
422
|
+
editMessageId: activity.messageId,
|
|
423
|
+
parse_mode: undefined,
|
|
424
|
+
reply_markup,
|
|
425
|
+
});
|
|
426
|
+
activity.messageId = sent?.message_id || sent?.message?.message_id || activity.messageId;
|
|
427
|
+
activity.lastEditAt = now;
|
|
428
|
+
return sent;
|
|
429
|
+
}
|
|
430
|
+
function startActivityHeartbeat({ bot, chatId, activity }) {
|
|
431
|
+
return setInterval(() => {
|
|
432
|
+
if (!activity || activity.status === 'done' || activity.status === 'failed')
|
|
433
|
+
return;
|
|
434
|
+
if (activity.status === 'starting') {
|
|
435
|
+
activity.status = 'running';
|
|
436
|
+
activity.phase = t(activity.lang, 'control.activity.thinking');
|
|
437
|
+
}
|
|
438
|
+
editTelegramActivity({ bot, chatId, activity }).catch((error) => {
|
|
439
|
+
console.warn('[telegram-control] activity heartbeat failed:', error?.message || error);
|
|
440
|
+
});
|
|
441
|
+
}, ACTIVITY_HEARTBEAT_MS);
|
|
442
|
+
}
|
|
227
443
|
function stateSummary(lang, state) {
|
|
228
444
|
return [
|
|
229
445
|
`${t(lang, 'control.summary.project')}: ${state.selectedProjectName || t(lang, 'control.notSelected')}`,
|
|
@@ -268,11 +484,12 @@ async function listProjects() {
|
|
|
268
484
|
const projects = await getProjects();
|
|
269
485
|
return projects.slice(0, 20);
|
|
270
486
|
}
|
|
271
|
-
async function showProjectMenu({ bot, chatId, link, editMessageId }) {
|
|
487
|
+
async function showProjectMenu({ bot, chatId, link, editMessageId, notice }) {
|
|
272
488
|
const lang = languageFor(link);
|
|
273
489
|
const projects = await listProjects();
|
|
274
490
|
if (projects.length === 0) {
|
|
275
|
-
|
|
491
|
+
const prefix = notice ? `${notice}\n\n` : '';
|
|
492
|
+
await send(bot, chatId, `${prefix}${t(lang, 'control.noProjects')}`, { editMessageId });
|
|
276
493
|
return;
|
|
277
494
|
}
|
|
278
495
|
const buttons = projects.map((project, index) => button(`${index + 1}. ${compact(project.displayName || project.name, 34)}`, 'project_select', {
|
|
@@ -280,7 +497,8 @@ async function showProjectMenu({ bot, chatId, link, editMessageId }) {
|
|
|
280
497
|
path: project.fullPath || project.path,
|
|
281
498
|
displayName: project.displayName || project.name,
|
|
282
499
|
}));
|
|
283
|
-
|
|
500
|
+
const prefix = notice ? `${notice}\n\n` : '';
|
|
501
|
+
await send(bot, chatId, `${prefix}${t(lang, 'control.pickProject')}`, {
|
|
284
502
|
editMessageId,
|
|
285
503
|
reply_markup: { inline_keyboard: rows(buttons, 1) },
|
|
286
504
|
});
|
|
@@ -464,6 +682,123 @@ function extractAssistantText(response) {
|
|
|
464
682
|
}
|
|
465
683
|
return chunks.join('\n\n').trim();
|
|
466
684
|
}
|
|
685
|
+
function extractTextFromEvent(event) {
|
|
686
|
+
if (!event || typeof event !== 'object')
|
|
687
|
+
return '';
|
|
688
|
+
if (typeof event.content === 'string')
|
|
689
|
+
return event.content;
|
|
690
|
+
if (Array.isArray(event.content)) {
|
|
691
|
+
return event.content
|
|
692
|
+
.map((part) => (typeof part === 'string' ? part : part?.text || ''))
|
|
693
|
+
.join('');
|
|
694
|
+
}
|
|
695
|
+
const legacyContent = event.data?.message?.content || event.message?.content;
|
|
696
|
+
if (Array.isArray(legacyContent)) {
|
|
697
|
+
return legacyContent
|
|
698
|
+
.map((part) => (typeof part === 'string' ? part : part?.text || ''))
|
|
699
|
+
.join('');
|
|
700
|
+
}
|
|
701
|
+
if (typeof legacyContent === 'string')
|
|
702
|
+
return legacyContent;
|
|
703
|
+
return '';
|
|
704
|
+
}
|
|
705
|
+
function describeToolInput(toolName, input) {
|
|
706
|
+
if (!input || typeof input !== 'object')
|
|
707
|
+
return '';
|
|
708
|
+
if (toolName === 'Bash' && typeof input.command === 'string') {
|
|
709
|
+
return compact(input.command, 110);
|
|
710
|
+
}
|
|
711
|
+
if (toolName === 'WebSearch' && typeof input.query === 'string') {
|
|
712
|
+
return compact(input.query, 110);
|
|
713
|
+
}
|
|
714
|
+
if (toolName === 'FileChanges') {
|
|
715
|
+
return compact(JSON.stringify(input), 110);
|
|
716
|
+
}
|
|
717
|
+
const keys = Object.keys(input).slice(0, 3);
|
|
718
|
+
if (keys.length === 0)
|
|
719
|
+
return '';
|
|
720
|
+
return compact(keys.map((key) => `${key}: ${String(input[key])}`).join(', '), 110);
|
|
721
|
+
}
|
|
722
|
+
function applyAgentStreamEvent(activity, event) {
|
|
723
|
+
if (!event || typeof event !== 'object')
|
|
724
|
+
return;
|
|
725
|
+
const sessionId = event.actualSessionId || event.newSessionId || event.sessionId || event.threadId;
|
|
726
|
+
if (sessionId)
|
|
727
|
+
activity.sessionId = sessionId;
|
|
728
|
+
if (event.type === 'status' && event.message) {
|
|
729
|
+
activity.status = 'running';
|
|
730
|
+
activity.phase = compact(event.message, 120);
|
|
731
|
+
pushActivityEvent(activity, `⏳ ${compact(event.message, 120)}`);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (event.type === 'done' || event.kind === 'complete' || event.kind === 'stream_end') {
|
|
735
|
+
activity.status = 'done';
|
|
736
|
+
activity.phase = t(activity.lang, 'control.activity.done');
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (event.type === 'error' || event.kind === 'error') {
|
|
740
|
+
activity.status = 'failed';
|
|
741
|
+
activity.phase = t(activity.lang, 'control.activity.failed');
|
|
742
|
+
activity.error = event.error || event.message || event.content || t(activity.lang, 'error.generic');
|
|
743
|
+
pushActivityEvent(activity, `❌ ${compact(activity.error, 120)}`);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (event.kind === 'session_created') {
|
|
747
|
+
activity.status = 'running';
|
|
748
|
+
activity.phase = t(activity.lang, 'control.activity.sessionStarted');
|
|
749
|
+
pushActivityEvent(activity, `🧵 ${t(activity.lang, 'control.activity.sessionStarted')}`);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (event.kind === 'thinking') {
|
|
753
|
+
activity.status = 'running';
|
|
754
|
+
activity.phase = t(activity.lang, 'control.activity.thinking');
|
|
755
|
+
const thought = extractTextFromEvent(event);
|
|
756
|
+
if (thought)
|
|
757
|
+
pushActivityEvent(activity, `🧠 ${compact(thought, 120)}`);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (event.kind === 'stream_delta' || event.kind === 'text') {
|
|
761
|
+
activity.status = 'running';
|
|
762
|
+
activity.phase = t(activity.lang, 'control.activity.responding');
|
|
763
|
+
activity.output = `${activity.output}${extractTextFromEvent(event)}`;
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (event.type === 'claude-response' && event.data?.type === 'assistant') {
|
|
767
|
+
activity.status = 'running';
|
|
768
|
+
activity.phase = t(activity.lang, 'control.activity.responding');
|
|
769
|
+
activity.output = `${activity.output}${extractTextFromEvent(event)}`;
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (event.kind === 'tool_use') {
|
|
773
|
+
activity.status = 'running';
|
|
774
|
+
activity.phase = t(activity.lang, 'control.activity.working');
|
|
775
|
+
const toolName = event.toolName || 'Tool';
|
|
776
|
+
const input = describeToolInput(toolName, event.toolInput);
|
|
777
|
+
const icon = toolName === 'Bash'
|
|
778
|
+
? '💻'
|
|
779
|
+
: toolName === 'FileChanges'
|
|
780
|
+
? '📝'
|
|
781
|
+
: toolName === 'WebSearch'
|
|
782
|
+
? '🔎'
|
|
783
|
+
: '🔧';
|
|
784
|
+
pushActivityEvent(activity, `${icon} ${toolName}${input ? `: ${input}` : ''}`);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (event.kind === 'tool_result') {
|
|
788
|
+
activity.status = 'running';
|
|
789
|
+
activity.phase = t(activity.lang, 'control.activity.working');
|
|
790
|
+
const label = event.isError
|
|
791
|
+
? t(activity.lang, 'control.activity.toolFailed')
|
|
792
|
+
: t(activity.lang, 'control.activity.toolDone');
|
|
793
|
+
pushActivityEvent(activity, `${event.isError ? '⚠️' : '✅'} ${label}`);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (event.kind === 'status' && event.text === 'token_budget') {
|
|
797
|
+
const used = event.tokenBudget?.used;
|
|
798
|
+
if (used)
|
|
799
|
+
pushActivityEvent(activity, `📊 ${t(activity.lang, 'control.activity.tokens')}: ${used}`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
467
802
|
function confirmationLabel(lang, action, payload = {}) {
|
|
468
803
|
if (action === 'install_provider') {
|
|
469
804
|
return t(lang, 'control.confirmInstall', { provider: payload.provider || '' });
|
|
@@ -494,73 +829,102 @@ async function sendToolFailure({ bot, chatId, link, result, editMessageId }) {
|
|
|
494
829
|
: (result?.message || t(lang, 'error.generic'));
|
|
495
830
|
await send(bot, chatId, message, { editMessageId });
|
|
496
831
|
}
|
|
497
|
-
async function runAgent({ bot, chatId, link, prompt }) {
|
|
832
|
+
async function runAgent({ bot, chatId, link, prompt, activity = null }) {
|
|
498
833
|
const lang = languageFor(link);
|
|
499
834
|
const state = getState(link.user_id);
|
|
500
835
|
if (!state.remoteControlEnabled) {
|
|
501
|
-
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
836
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId: activity?.messageId });
|
|
502
837
|
return;
|
|
503
838
|
}
|
|
504
839
|
if (!state.selectedProjectPath) {
|
|
505
|
-
await
|
|
506
|
-
await showProjectMenu({ bot, chatId, link });
|
|
840
|
+
await showProjectMenu({ bot, chatId, link, editMessageId: activity?.messageId, notice: t(lang, 'control.selectProjectFirst') });
|
|
507
841
|
return;
|
|
508
842
|
}
|
|
509
843
|
const provider = resolveTelegramProvider(state);
|
|
510
844
|
const model = resolveTelegramModel(state);
|
|
511
|
-
|
|
512
|
-
|
|
845
|
+
const active = activity || await createTelegramActivity({
|
|
846
|
+
bot,
|
|
847
|
+
chatId,
|
|
848
|
+
link,
|
|
849
|
+
type: 'agent',
|
|
850
|
+
prompt,
|
|
851
|
+
phase: t(lang, 'control.activity.startingProvider', { provider }),
|
|
852
|
+
});
|
|
853
|
+
active.type = 'agent';
|
|
854
|
+
active.provider = provider;
|
|
855
|
+
active.project = projectLabel(state);
|
|
856
|
+
active.status = 'running';
|
|
857
|
+
active.phase = t(lang, 'control.activity.startingProvider', { provider });
|
|
858
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
859
|
+
const heartbeat = startActivityHeartbeat({ bot, chatId, activity: active });
|
|
860
|
+
let streamFailed = null;
|
|
861
|
+
try {
|
|
862
|
+
await localAgentStream(link.user_id, {
|
|
863
|
+
projectPath: state.selectedProjectPath,
|
|
513
864
|
provider,
|
|
514
|
-
|
|
515
|
-
|
|
865
|
+
model: model || undefined,
|
|
866
|
+
message: buildTelegramAgentPrompt(prompt, state),
|
|
867
|
+
cleanup: false,
|
|
868
|
+
permissionMode: 'default',
|
|
869
|
+
suppressNotifications: true,
|
|
870
|
+
}, async (event) => {
|
|
871
|
+
applyAgentStreamEvent(active, event);
|
|
872
|
+
await editTelegramActivity({ bot, chatId, activity: active });
|
|
873
|
+
});
|
|
516
874
|
}
|
|
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 });
|
|
875
|
+
catch (error) {
|
|
876
|
+
streamFailed = error;
|
|
877
|
+
}
|
|
878
|
+
finally {
|
|
879
|
+
clearInterval(heartbeat);
|
|
880
|
+
}
|
|
881
|
+
if (streamFailed) {
|
|
882
|
+
active.status = 'failed';
|
|
883
|
+
active.phase = t(lang, 'control.activity.failed');
|
|
884
|
+
active.error = streamFailed.message || t(lang, 'error.generic');
|
|
885
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
535
886
|
return;
|
|
536
887
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
await send(bot, chatId, `${statusLine}\n\n${assistantText || response.error || t(lang, 'control.noAssistantText')}`);
|
|
888
|
+
active.status = active.error ? 'failed' : 'done';
|
|
889
|
+
active.phase = active.error ? t(lang, 'control.activity.failed') : t(lang, 'control.activity.done');
|
|
890
|
+
if (!active.output && !active.error)
|
|
891
|
+
active.output = t(lang, 'control.noAssistantText');
|
|
892
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
543
893
|
}
|
|
544
|
-
export async function runWorkflow({ bot, chatId, link, input }) {
|
|
894
|
+
export async function runWorkflow({ bot, chatId, link, input, activity = null }) {
|
|
545
895
|
const lang = languageFor(link);
|
|
546
896
|
const state = getState(link.user_id);
|
|
547
897
|
const workflowId = state.selectedWorkflowId;
|
|
548
898
|
if (!state.remoteControlEnabled) {
|
|
549
|
-
await send(bot, chatId, t(lang, 'control.disabled'));
|
|
899
|
+
await send(bot, chatId, t(lang, 'control.disabled'), { editMessageId: activity?.messageId });
|
|
550
900
|
return;
|
|
551
901
|
}
|
|
552
902
|
if (!workflowId) {
|
|
553
|
-
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'));
|
|
554
|
-
await showWorkflowMenu({ bot, chatId, link });
|
|
903
|
+
await send(bot, chatId, t(lang, 'control.selectWorkflowFirst'), { editMessageId: activity?.messageId });
|
|
904
|
+
await showWorkflowMenu({ bot, chatId, link, editMessageId: activity?.messageId });
|
|
555
905
|
return;
|
|
556
906
|
}
|
|
557
907
|
if (!state.selectedProjectPath) {
|
|
558
|
-
await
|
|
559
|
-
await showProjectMenu({ bot, chatId, link });
|
|
908
|
+
await showProjectMenu({ bot, chatId, link, editMessageId: activity?.messageId, notice: t(lang, 'control.selectProjectFirst') });
|
|
560
909
|
return;
|
|
561
910
|
}
|
|
562
911
|
const provider = resolveTelegramProvider(state);
|
|
563
912
|
const model = resolveTelegramModel(state);
|
|
913
|
+
const active = activity || await createTelegramActivity({
|
|
914
|
+
bot,
|
|
915
|
+
chatId,
|
|
916
|
+
link,
|
|
917
|
+
type: 'workflow',
|
|
918
|
+
prompt: input,
|
|
919
|
+
phase: t(lang, 'control.activity.startingWorkflow'),
|
|
920
|
+
});
|
|
921
|
+
active.type = 'workflow';
|
|
922
|
+
active.provider = provider;
|
|
923
|
+
active.project = projectLabel(state);
|
|
924
|
+
active.workflowId = workflowId;
|
|
925
|
+
active.status = 'running';
|
|
926
|
+
active.phase = t(lang, 'control.activity.startingWorkflow');
|
|
927
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
564
928
|
const result = await runTelegramTool({
|
|
565
929
|
userId: link.user_id,
|
|
566
930
|
action: 'run_workflow',
|
|
@@ -581,12 +945,15 @@ export async function runWorkflow({ bot, chatId, link, input }) {
|
|
|
581
945
|
}),
|
|
582
946
|
});
|
|
583
947
|
if (!result.ok) {
|
|
584
|
-
await sendToolFailure({ bot, chatId, link, result });
|
|
948
|
+
await sendToolFailure({ bot, chatId, link, result, editMessageId: active.messageId });
|
|
585
949
|
return;
|
|
586
950
|
}
|
|
587
951
|
const run = result.data;
|
|
588
|
-
|
|
589
|
-
|
|
952
|
+
active.runId = run.id;
|
|
953
|
+
active.phase = t(lang, 'control.activity.workflowRunning');
|
|
954
|
+
pushActivityEvent(active, `🧭 ${t(lang, 'control.workflowStarted', { runId: run.id, workflowId }).replace('\n', ' ')}`);
|
|
955
|
+
await editTelegramActivity({ bot, chatId, activity: active, force: true });
|
|
956
|
+
monitorWorkflowRun({ bot, chatId, link, runId: run.id, activity: active }).catch((error) => {
|
|
590
957
|
console.warn('[telegram-control] workflow monitor failed:', error?.message || error);
|
|
591
958
|
});
|
|
592
959
|
}
|
|
@@ -663,21 +1030,96 @@ async function findProjectByQuery(query) {
|
|
|
663
1030
|
return candidates.some((candidate) => candidate === needle || candidate.includes(needle));
|
|
664
1031
|
}) || null;
|
|
665
1032
|
}
|
|
666
|
-
async function
|
|
1033
|
+
async function listWorkflowSummaries(userId) {
|
|
1034
|
+
try {
|
|
1035
|
+
const data = await localApi(userId, '/api/orchestration/workflows', { timeoutMs: 10_000 });
|
|
1036
|
+
if (Array.isArray(data))
|
|
1037
|
+
return data;
|
|
1038
|
+
if (Array.isArray(data?.workflows))
|
|
1039
|
+
return data.workflows;
|
|
1040
|
+
}
|
|
1041
|
+
catch {
|
|
1042
|
+
// Workflow context is helpful for routing but should never block chat input.
|
|
1043
|
+
}
|
|
1044
|
+
return [];
|
|
1045
|
+
}
|
|
1046
|
+
async function resolveTelegramAiIntent({ bot, chatId, link, text, activity }) {
|
|
667
1047
|
const state = getState(link.user_id);
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1048
|
+
if (state.routerEnabled === false || state.routerMode !== 'hybrid') {
|
|
1049
|
+
return { action: 'agent_prompt', prompt: text, confidence: 1 };
|
|
1050
|
+
}
|
|
1051
|
+
const provider = resolveTelegramProvider(state);
|
|
1052
|
+
const model = resolveTelegramModel(state);
|
|
1053
|
+
if (activity) {
|
|
1054
|
+
activity.type = 'router';
|
|
1055
|
+
activity.provider = provider;
|
|
1056
|
+
activity.project = projectLabel(state);
|
|
1057
|
+
activity.status = 'running';
|
|
1058
|
+
activity.phase = t(activity.lang, 'control.activity.interpreting');
|
|
1059
|
+
pushActivityEvent(activity, `🧠 ${t(activity.lang, 'control.activity.interpreting')}`);
|
|
1060
|
+
await editTelegramActivity({ bot, chatId, activity, force: true });
|
|
1061
|
+
}
|
|
1062
|
+
try {
|
|
1063
|
+
const projects = await listProjects().catch(() => []);
|
|
1064
|
+
const workflows = await listWorkflowSummaries(link.user_id);
|
|
1065
|
+
const response = await localApi(link.user_id, '/api/agent', {
|
|
1066
|
+
method: 'POST',
|
|
1067
|
+
timeoutMs: INTENT_ROUTER_TIMEOUT_MS,
|
|
1068
|
+
body: {
|
|
1069
|
+
projectPath: state.selectedProjectPath || process.cwd(),
|
|
1070
|
+
provider,
|
|
1071
|
+
model: model || undefined,
|
|
1072
|
+
message: buildTelegramIntentPrompt(text, state, { projects, workflows }),
|
|
1073
|
+
cleanup: false,
|
|
1074
|
+
stream: false,
|
|
1075
|
+
permissionMode: 'plan',
|
|
1076
|
+
suppressNotifications: true,
|
|
1077
|
+
},
|
|
1078
|
+
});
|
|
1079
|
+
const assistantText = extractAssistantText(response);
|
|
1080
|
+
const intent = parseTelegramAiIntentResponse(assistantText, text);
|
|
1081
|
+
if (activity) {
|
|
1082
|
+
pushActivityEvent(activity, `🧭 ${intent.action} (${Math.round(intent.confidence * 100)}%)`);
|
|
1083
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1084
|
+
}
|
|
1085
|
+
return intent;
|
|
1086
|
+
}
|
|
1087
|
+
catch (error) {
|
|
1088
|
+
if (activity) {
|
|
1089
|
+
pushActivityEvent(activity, `⚠️ ${t(activity.lang, 'control.activity.routerFallback')}`);
|
|
1090
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1091
|
+
}
|
|
1092
|
+
console.warn('[telegram-control] AI intent router fallback:', error?.message || error);
|
|
1093
|
+
return { action: 'agent_prompt', prompt: text, confidence: 0 };
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
async function handleRoutedIntent({ bot, chatId, link, text, activity }) {
|
|
1097
|
+
const intent = await resolveTelegramAiIntent({ bot, chatId, link, text, activity });
|
|
1098
|
+
const editMessageId = activity?.messageId;
|
|
1099
|
+
if (intent.action === 'agent_prompt') {
|
|
1100
|
+
await runAgent({ bot, chatId, link, prompt: intent.prompt || text, activity });
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
if (intent.action === 'show_menu') {
|
|
1104
|
+
await showMainMenu({ bot, chatId, link, editMessageId });
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
if (intent.action === 'show_projects') {
|
|
1108
|
+
await showProjectMenu({ bot, chatId, link, editMessageId });
|
|
673
1109
|
return true;
|
|
674
1110
|
}
|
|
675
|
-
if (intent.action === '
|
|
676
|
-
const
|
|
1111
|
+
if (intent.action === 'select_project') {
|
|
1112
|
+
const query = intent.projectQuery || intent.prompt || text;
|
|
1113
|
+
const project = await findProjectByQuery(query);
|
|
677
1114
|
const lang = languageFor(link);
|
|
678
1115
|
if (!project) {
|
|
679
|
-
await
|
|
680
|
-
|
|
1116
|
+
await showProjectMenu({
|
|
1117
|
+
bot,
|
|
1118
|
+
chatId,
|
|
1119
|
+
link,
|
|
1120
|
+
editMessageId,
|
|
1121
|
+
notice: t(lang, 'control.projectNotFound', { query }),
|
|
1122
|
+
});
|
|
681
1123
|
return true;
|
|
682
1124
|
}
|
|
683
1125
|
updateTelegramControlState(link.user_id, {
|
|
@@ -688,6 +1130,7 @@ async function handleRoutedIntent({ bot, chatId, link, text }) {
|
|
|
688
1130
|
bot,
|
|
689
1131
|
chatId,
|
|
690
1132
|
link,
|
|
1133
|
+
editMessageId,
|
|
691
1134
|
notice: t(lang, 'control.projectSelected', {
|
|
692
1135
|
project: project.displayName || project.name,
|
|
693
1136
|
path: project.fullPath || project.path,
|
|
@@ -695,45 +1138,72 @@ async function handleRoutedIntent({ bot, chatId, link, text }) {
|
|
|
695
1138
|
});
|
|
696
1139
|
return true;
|
|
697
1140
|
}
|
|
698
|
-
if (intent.action === '
|
|
699
|
-
await showProviderMenu({ bot, chatId, link });
|
|
1141
|
+
if (intent.action === 'show_provider_menu') {
|
|
1142
|
+
await showProviderMenu({ bot, chatId, link, editMessageId });
|
|
700
1143
|
return true;
|
|
701
1144
|
}
|
|
702
|
-
if (intent.action === '
|
|
1145
|
+
if (intent.action === 'select_provider' && intent.provider) {
|
|
703
1146
|
updateTelegramControlState(link.user_id, { selectedProvider: intent.provider, selectedModel: null });
|
|
704
|
-
await showModelMenu({ bot, chatId, link });
|
|
1147
|
+
await showModelMenu({ bot, chatId, link, editMessageId });
|
|
705
1148
|
return true;
|
|
706
1149
|
}
|
|
707
|
-
if (intent.action === '
|
|
708
|
-
await showModelMenu({ bot, chatId, link });
|
|
1150
|
+
if (intent.action === 'show_model_menu') {
|
|
1151
|
+
await showModelMenu({ bot, chatId, link, editMessageId });
|
|
709
1152
|
return true;
|
|
710
1153
|
}
|
|
711
|
-
if (intent.action === '
|
|
712
|
-
|
|
1154
|
+
if (intent.action === 'select_model' && intent.model) {
|
|
1155
|
+
updateTelegramControlState(link.user_id, { selectedModel: intent.model });
|
|
1156
|
+
await showMainMenu({
|
|
1157
|
+
bot,
|
|
1158
|
+
chatId,
|
|
1159
|
+
link,
|
|
1160
|
+
editMessageId,
|
|
1161
|
+
notice: t(languageFor(link), 'control.modelSelected', { model: intent.model }),
|
|
1162
|
+
});
|
|
713
1163
|
return true;
|
|
714
1164
|
}
|
|
715
|
-
if (intent.action === '
|
|
716
|
-
await
|
|
1165
|
+
if (intent.action === 'show_runs') {
|
|
1166
|
+
await showRuns({ bot, chatId, link, editMessageId });
|
|
717
1167
|
return true;
|
|
718
1168
|
}
|
|
719
|
-
if (intent.action === '
|
|
720
|
-
await
|
|
1169
|
+
if (intent.action === 'show_approvals') {
|
|
1170
|
+
await showApprovalQueue({ bot, chatId, link, editMessageId });
|
|
1171
|
+
return true;
|
|
1172
|
+
}
|
|
1173
|
+
if (intent.action === 'show_workflows') {
|
|
1174
|
+
await showWorkflowMenu({ bot, chatId, link, editMessageId });
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
1177
|
+
if (intent.action === 'run_workflow') {
|
|
1178
|
+
await runWorkflow({ bot, chatId, link, input: intent.workflowInput || text, activity });
|
|
1179
|
+
return true;
|
|
1180
|
+
}
|
|
1181
|
+
if (intent.action === 'show_sessions') {
|
|
1182
|
+
await showSessions({ bot, chatId, link, editMessageId });
|
|
721
1183
|
return true;
|
|
722
1184
|
}
|
|
723
1185
|
if (intent.action === 'new_chat') {
|
|
724
|
-
await startNewChat({ bot, chatId, link });
|
|
1186
|
+
await startNewChat({ bot, chatId, link, editMessageId });
|
|
725
1187
|
return true;
|
|
726
1188
|
}
|
|
727
|
-
|
|
1189
|
+
await runAgent({ bot, chatId, link, prompt: text, activity });
|
|
1190
|
+
return true;
|
|
728
1191
|
}
|
|
729
1192
|
async function fetchRun(userId, runId) {
|
|
730
1193
|
return localApi(userId, `/api/orchestration/workflows/runs/${runId}`);
|
|
731
1194
|
}
|
|
732
1195
|
function summarizeRun(run, mode) {
|
|
1196
|
+
const statusIcon = run.status === 'completed'
|
|
1197
|
+
? '✅'
|
|
1198
|
+
: run.status === 'failed'
|
|
1199
|
+
? '❌'
|
|
1200
|
+
: run.status === 'canceled'
|
|
1201
|
+
? '⏹'
|
|
1202
|
+
: '🔧';
|
|
733
1203
|
const lines = [
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1204
|
+
`${statusIcon} Run ${run.id}`,
|
|
1205
|
+
`🧩 Workflow: ${run.workflowId}`,
|
|
1206
|
+
`📌 Status: ${run.status}`,
|
|
737
1207
|
];
|
|
738
1208
|
const nodeRuns = Array.isArray(run.nodeRuns) ? run.nodeRuns : [];
|
|
739
1209
|
if (mode !== 'final') {
|
|
@@ -741,7 +1211,14 @@ function summarizeRun(run, mode) {
|
|
|
741
1211
|
? nodeRuns.filter((node) => node.error || node.status === 'failed')
|
|
742
1212
|
: nodeRuns;
|
|
743
1213
|
for (const node of visibleNodes) {
|
|
744
|
-
|
|
1214
|
+
const nodeIcon = node.status === 'completed'
|
|
1215
|
+
? '✅'
|
|
1216
|
+
: node.status === 'failed'
|
|
1217
|
+
? '❌'
|
|
1218
|
+
: node.status === 'running'
|
|
1219
|
+
? '🔧'
|
|
1220
|
+
: '⏳';
|
|
1221
|
+
lines.push(`${nodeIcon} ${node.status}: ${node.agentLabel || node.nodeId}${node.error ? ` - ${node.error}` : ''}`);
|
|
745
1222
|
}
|
|
746
1223
|
}
|
|
747
1224
|
const outputs = nodeRuns
|
|
@@ -750,7 +1227,7 @@ function summarizeRun(run, mode) {
|
|
|
750
1227
|
.map((node) => `\n${node.agentLabel || node.nodeId}:\n${node.outputText}`);
|
|
751
1228
|
return truncate(`${lines.join('\n')}${outputs.join('\n')}`);
|
|
752
1229
|
}
|
|
753
|
-
async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
1230
|
+
async function monitorWorkflowRun({ bot, chatId, link, runId, activity = null }) {
|
|
754
1231
|
if (runMonitors.has(runId))
|
|
755
1232
|
return;
|
|
756
1233
|
runMonitors.set(runId, true);
|
|
@@ -761,12 +1238,24 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
761
1238
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
762
1239
|
const run = await fetchRun(link.user_id, runId);
|
|
763
1240
|
const nodeRuns = Array.isArray(run.nodeRuns) ? run.nodeRuns : [];
|
|
1241
|
+
if (activity && !TERMINAL_RUN_STATES.has(run.status)) {
|
|
1242
|
+
activity.status = 'running';
|
|
1243
|
+
activity.phase = `${t(activity.lang, 'control.activity.workflowRunning')} (${run.status})`;
|
|
1244
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1245
|
+
}
|
|
764
1246
|
if (state.progressMode === 'all') {
|
|
765
1247
|
for (const node of nodeRuns) {
|
|
766
1248
|
const key = `${node.nodeId}:${node.status}`;
|
|
767
1249
|
if (!seenNodeStatus.has(key)) {
|
|
768
1250
|
seenNodeStatus.set(key, true);
|
|
769
|
-
|
|
1251
|
+
if (activity) {
|
|
1252
|
+
pushActivityEvent(activity, `${node.status === 'completed' ? '✅' : node.status === 'failed' ? '❌' : '🔧'} ${node.agentLabel || node.nodeId}: ${node.status}${node.error ? ` - ${node.error}` : ''}`);
|
|
1253
|
+
activity.phase = t(activity.lang, 'control.activity.workflowRunning');
|
|
1254
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1255
|
+
}
|
|
1256
|
+
else {
|
|
1257
|
+
await send(bot, chatId, `${node.agentLabel || node.nodeId}: ${node.status}${node.error ? `\n${node.error}` : ''}`);
|
|
1258
|
+
}
|
|
770
1259
|
}
|
|
771
1260
|
}
|
|
772
1261
|
}
|
|
@@ -775,12 +1264,29 @@ async function monitorWorkflowRun({ bot, chatId, link, runId }) {
|
|
|
775
1264
|
const key = `${node.nodeId}:${node.status}:${node.error || ''}`;
|
|
776
1265
|
if (!seenNodeStatus.has(key)) {
|
|
777
1266
|
seenNodeStatus.set(key, true);
|
|
778
|
-
|
|
1267
|
+
if (activity) {
|
|
1268
|
+
pushActivityEvent(activity, `❌ ${node.agentLabel || node.nodeId}: ${node.status}${node.error ? ` - ${node.error}` : ''}`);
|
|
1269
|
+
activity.phase = t(activity.lang, 'control.activity.workflowRunning');
|
|
1270
|
+
await editTelegramActivity({ bot, chatId, activity });
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
await send(bot, chatId, `${node.agentLabel || node.nodeId}: ${node.status}\n${node.error || ''}`);
|
|
1274
|
+
}
|
|
779
1275
|
}
|
|
780
1276
|
}
|
|
781
1277
|
}
|
|
782
1278
|
if (TERMINAL_RUN_STATES.has(run.status)) {
|
|
783
|
-
|
|
1279
|
+
if (activity) {
|
|
1280
|
+
activity.status = run.status === 'completed' ? 'done' : 'failed';
|
|
1281
|
+
activity.phase = run.status === 'completed'
|
|
1282
|
+
? t(activity.lang, 'control.activity.done')
|
|
1283
|
+
: t(activity.lang, 'control.activity.failed');
|
|
1284
|
+
activity.output = summarizeRun(run, state.progressMode);
|
|
1285
|
+
await editTelegramActivity({ bot, chatId, activity, force: true });
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
await send(bot, chatId, summarizeRun(run, state.progressMode));
|
|
1289
|
+
}
|
|
784
1290
|
return;
|
|
785
1291
|
}
|
|
786
1292
|
}
|
|
@@ -1027,8 +1533,6 @@ async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
|
1027
1533
|
return true;
|
|
1028
1534
|
if (await handleCommand({ bot, chatId, link, text }))
|
|
1029
1535
|
return true;
|
|
1030
|
-
if (await handleRoutedIntent({ bot, chatId, link, text }))
|
|
1031
|
-
return true;
|
|
1032
1536
|
const state = getState(link.user_id);
|
|
1033
1537
|
if (state.routerEnabled === false)
|
|
1034
1538
|
return false;
|
|
@@ -1036,8 +1540,15 @@ async function handleTelegramControlMessageInternal({ bot, msg, link }) {
|
|
|
1036
1540
|
await send(bot, chatId, t(languageFor(link), 'control.disabled'));
|
|
1037
1541
|
return true;
|
|
1038
1542
|
}
|
|
1039
|
-
await
|
|
1040
|
-
|
|
1543
|
+
const activity = await createTelegramActivity({
|
|
1544
|
+
bot,
|
|
1545
|
+
chatId,
|
|
1546
|
+
link,
|
|
1547
|
+
type: 'router',
|
|
1548
|
+
prompt: text,
|
|
1549
|
+
phase: t(languageFor(link), 'control.activity.interpreting'),
|
|
1550
|
+
});
|
|
1551
|
+
return handleRoutedIntent({ bot, chatId, link, text, activity });
|
|
1041
1552
|
}
|
|
1042
1553
|
export async function handleTelegramControlMessage(args) {
|
|
1043
1554
|
const chatId = args?.msg?.chat?.id;
|