@pixelbyte-software/pixcode 1.53.7 → 1.53.9

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/server/index.js CHANGED
@@ -43,13 +43,19 @@ const DAEMON_COMMAND_CONTEXT = {
43
43
  };
44
44
 
45
45
  function resolveMonacoAssetsPath() {
46
+ const appParent = path.dirname(APP_ROOT);
47
+ const appGrandparent = path.dirname(appParent);
46
48
  const candidates = [
47
49
  path.join(APP_ROOT, 'node_modules', 'monaco-editor', 'min', 'vs'),
50
+ path.join(appParent, 'node_modules', 'monaco-editor', 'min', 'vs'),
51
+ path.join(appParent, 'monaco-editor', 'min', 'vs'),
52
+ path.join(appGrandparent, 'node_modules', 'monaco-editor', 'min', 'vs'),
53
+ path.join(appGrandparent, 'monaco-editor', 'min', 'vs'),
48
54
  ];
49
55
 
50
56
  try {
51
57
  const monacoPackagePath = require.resolve('monaco-editor/package.json', {
52
- paths: [APP_ROOT, __dirname],
58
+ paths: [APP_ROOT, appParent, appGrandparent, __dirname, process.cwd()],
53
59
  });
54
60
  candidates.push(path.join(path.dirname(monacoPackagePath), 'min', 'vs'));
55
61
  } catch {
@@ -1012,6 +1018,11 @@ const ptySessionsMap = new Map();
1012
1018
  const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
1013
1019
  const COMPLETED_PTY_SESSION_TTL = 5 * 60 * 1000;
1014
1020
  const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
1021
+ const SHELL_OUTPUT_FLUSH_MAX_CHARS = 128 * 1024;
1022
+ const SHELL_WS_BACKPRESSURE_LIMIT = 8 * 1024 * 1024;
1023
+ const PTY_SESSION_BUFFER_MAX_BYTES = Number.parseInt(process.env.PIXCODE_PTY_BUFFER_MAX_BYTES || '', 10) || (2 * 1024 * 1024);
1024
+ const SHELL_INPUT_CHUNK_CHARS = 4096;
1025
+ const SHELL_PENDING_INPUT_MAX_BYTES = 2 * 1024 * 1024;
1015
1026
  const SHELL_CLI_PROVIDERS = new Set(['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode']);
1016
1027
  import { stripAnsiSequences, normalizeDetectedUrl, extractUrlsFromText, shouldAutoOpenUrlFromOutput } from './utils/url-detection.js';
1017
1028
 
@@ -1156,12 +1167,23 @@ function resolveProviderTerminalState(session, provider, output) {
1156
1167
 
1157
1168
  function appendPtySessionBuffer(session, data) {
1158
1169
  if (!session) return;
1159
- if (session.buffer.length < 5000) {
1160
- session.buffer.push(data);
1161
- } else {
1162
- session.buffer.shift();
1163
- session.buffer.push(data);
1170
+ let value = String(data || '');
1171
+ if (!value) return;
1172
+
1173
+ let valueBytes = Buffer.byteLength(value, 'utf8');
1174
+ if (valueBytes > PTY_SESSION_BUFFER_MAX_BYTES) {
1175
+ value = value.slice(-PTY_SESSION_BUFFER_MAX_BYTES);
1176
+ valueBytes = Buffer.byteLength(value, 'utf8');
1177
+ }
1178
+
1179
+ session.buffer.push(value);
1180
+ session.bufferBytes = (session.bufferBytes || 0) + valueBytes;
1181
+
1182
+ while (session.buffer.length > 0 && session.bufferBytes > PTY_SESSION_BUFFER_MAX_BYTES) {
1183
+ const removed = session.buffer.shift();
1184
+ session.bufferBytes -= Buffer.byteLength(String(removed || ''), 'utf8');
1164
1185
  }
1186
+ if (session.bufferBytes < 0) session.bufferBytes = 0;
1165
1187
  }
1166
1188
 
1167
1189
  function normalizeTerminalStartupInput(input) {
@@ -1258,6 +1280,26 @@ function writeTerminalStartupInput(session, startupInput, reason, delayMs = 500)
1258
1280
  queueTerminalStartupInput(session, startupInput, reason, delayMs);
1259
1281
  }
1260
1282
 
1283
+ function splitTerminalInput(data) {
1284
+ const value = String(data || '');
1285
+ if (!value) return [];
1286
+ const chunks = [];
1287
+ for (let index = 0; index < value.length; index += SHELL_INPUT_CHUNK_CHARS) {
1288
+ chunks.push(value.slice(index, index + SHELL_INPUT_CHUNK_CHARS));
1289
+ }
1290
+ return chunks;
1291
+ }
1292
+
1293
+ function writeTerminalInputChunks(ptyProcess, data) {
1294
+ if (!ptyProcess?.write) return false;
1295
+ const chunks = splitTerminalInput(data);
1296
+ if (chunks.length === 0) return false;
1297
+ for (const chunk of chunks) {
1298
+ ptyProcess.write(chunk);
1299
+ }
1300
+ return true;
1301
+ }
1302
+
1261
1303
  function normalizeShellPermissionMode(value) {
1262
1304
  return typeof value === 'string' ? value.trim() : '';
1263
1305
  }
@@ -1541,7 +1583,7 @@ app.post('/api/shell/sessions/provider-input', authenticateToken, requireProject
1541
1583
 
1542
1584
  const data = submit ? normalizeTerminalStartupInput(input) : input;
1543
1585
  try {
1544
- matchedSession.pty.write(data);
1586
+ writeTerminalInputChunks(matchedSession.pty, data);
1545
1587
  matchedSession.updatedAt = Date.now();
1546
1588
  res.json({
1547
1589
  ok: true,
@@ -3406,8 +3448,63 @@ function handleShellConnection(ws, request) {
3406
3448
  let urlDetectionBuffer = '';
3407
3449
  let outputBuffer = '';
3408
3450
  let outputFlushTimer = null;
3451
+ let inputFlushTimer = null;
3452
+ let pendingInputBytes = 0;
3453
+ const pendingInputQueue = [];
3409
3454
  const announcedAuthUrls = new Set();
3410
3455
 
3456
+ function getActiveShellPty() {
3457
+ const session = ptySessionKey ? ptySessionsMap.get(ptySessionKey) : null;
3458
+ return session?.pty || shellProcess;
3459
+ }
3460
+
3461
+ function scheduleInputFlush(delayMs = 0) {
3462
+ if (inputFlushTimer) return;
3463
+ inputFlushTimer = setTimeout(flushPendingInput, delayMs);
3464
+ }
3465
+
3466
+ function flushPendingInput() {
3467
+ inputFlushTimer = null;
3468
+ if (pendingInputQueue.length === 0) return;
3469
+
3470
+ const activePty = getActiveShellPty();
3471
+ if (!activePty?.write) {
3472
+ scheduleInputFlush(100);
3473
+ return;
3474
+ }
3475
+
3476
+ const chunk = pendingInputQueue.shift();
3477
+ pendingInputBytes = Math.max(0, pendingInputBytes - Buffer.byteLength(chunk, 'utf8'));
3478
+ try {
3479
+ activePty.write(chunk);
3480
+ } catch (error) {
3481
+ console.error('Error writing to shell:', error);
3482
+ pendingInputQueue.length = 0;
3483
+ pendingInputBytes = 0;
3484
+ return;
3485
+ }
3486
+
3487
+ if (pendingInputQueue.length > 0) scheduleInputFlush(1);
3488
+ }
3489
+
3490
+ function enqueueShellInput(data) {
3491
+ const chunks = splitTerminalInput(data);
3492
+ if (chunks.length === 0) return;
3493
+
3494
+ for (const chunk of chunks) {
3495
+ pendingInputQueue.push(chunk);
3496
+ pendingInputBytes += Buffer.byteLength(chunk, 'utf8');
3497
+ }
3498
+
3499
+ while (pendingInputQueue.length > 0 && pendingInputBytes > SHELL_PENDING_INPUT_MAX_BYTES) {
3500
+ const dropped = pendingInputQueue.shift();
3501
+ pendingInputBytes -= Buffer.byteLength(String(dropped || ''), 'utf8');
3502
+ }
3503
+ if (pendingInputBytes < 0) pendingInputBytes = 0;
3504
+
3505
+ scheduleInputFlush();
3506
+ }
3507
+
3411
3508
  function flushOutputBuffer() {
3412
3509
  if (!outputBuffer || !ptySessionKey) {
3413
3510
  outputFlushTimer = null;
@@ -3424,7 +3521,17 @@ function handleShellConnection(ws, request) {
3424
3521
  appendPtySessionBuffer(session, rawData);
3425
3522
 
3426
3523
  if (session.ws && session.ws.readyState === WebSocket.OPEN) {
3524
+ if (session.ws.bufferedAmount > SHELL_WS_BACKPRESSURE_LIMIT) {
3525
+ session.droppedOutputBytes = (session.droppedOutputBytes || 0) + Buffer.byteLength(rawData, 'utf8');
3526
+ return;
3527
+ }
3528
+
3529
+ const droppedOutputBytes = session.droppedOutputBytes || 0;
3530
+ session.droppedOutputBytes = 0;
3427
3531
  let outputData = rawData;
3532
+ if (droppedOutputBytes > 0) {
3533
+ outputData = `\r\n\x1b[33m[Pixcode] ${droppedOutputBytes} bytes of terminal output were skipped because the browser connection was backpressured.\x1b[0m\r\n${outputData}`;
3534
+ }
3428
3535
 
3429
3536
  const cleanChunk = stripAnsiSequences(rawData);
3430
3537
  urlDetectionBuffer = `${urlDetectionBuffer}${cleanChunk}`.slice(-SHELL_URL_PARSE_BUFFER_LIMIT);
@@ -3786,6 +3893,8 @@ function handleShellConnection(ws, request) {
3786
3893
  pty: shellProcess,
3787
3894
  ws: ws,
3788
3895
  buffer: [],
3896
+ bufferBytes: 0,
3897
+ droppedOutputBytes: 0,
3789
3898
  timeoutId: null,
3790
3899
  userId: ownerUserId,
3791
3900
  projectPath,
@@ -3806,12 +3915,21 @@ function handleShellConnection(ws, request) {
3806
3915
  if (terminalStartupInput && !isPlainShell) {
3807
3916
  writeTerminalStartupInput(createdSession, terminalStartupInput, 'new provider session', 4500);
3808
3917
  }
3918
+ scheduleInputFlush();
3809
3919
 
3810
3920
  // Handle data output — batch rapid chunks so high-volume CLI
3811
3921
  // output does not flood the WebSocket with one JSON frame per
3812
3922
  // keystroke-sized chunk.
3813
3923
  shellProcess.onData((data) => {
3814
3924
  outputBuffer += data;
3925
+ if (outputBuffer.length >= SHELL_OUTPUT_FLUSH_MAX_CHARS) {
3926
+ if (outputFlushTimer) {
3927
+ clearTimeout(outputFlushTimer);
3928
+ outputFlushTimer = null;
3929
+ }
3930
+ flushOutputBuffer();
3931
+ return;
3932
+ }
3815
3933
  if (!outputFlushTimer) {
3816
3934
  outputFlushTimer = setTimeout(flushOutputBuffer, 8);
3817
3935
  }
@@ -3878,16 +3996,7 @@ function handleShellConnection(ws, request) {
3878
3996
  }
3879
3997
 
3880
3998
  } else if (data.type === 'input') {
3881
- // Send input to shell process
3882
- if (shellProcess && shellProcess.write) {
3883
- try {
3884
- shellProcess.write(data.data);
3885
- } catch (error) {
3886
- console.error('Error writing to shell:', error);
3887
- }
3888
- } else {
3889
- console.warn('No active shell process to send input to');
3890
- }
3999
+ enqueueShellInput(data.data);
3891
4000
  } else if (data.type === 'resize') {
3892
4001
  // Handle terminal resize
3893
4002
  const session = ptySessionKey ? ptySessionsMap.get(ptySessionKey) : null;
@@ -3921,6 +4030,12 @@ function handleShellConnection(ws, request) {
3921
4030
  clearTimeout(outputFlushTimer);
3922
4031
  outputFlushTimer = null;
3923
4032
  }
4033
+ if (inputFlushTimer) {
4034
+ clearTimeout(inputFlushTimer);
4035
+ inputFlushTimer = null;
4036
+ }
4037
+ pendingInputQueue.length = 0;
4038
+ pendingInputBytes = 0;
3924
4039
 
3925
4040
  if (ptySessionKey) {
3926
4041
  const session = ptySessionsMap.get(ptySessionKey);
@@ -27,11 +27,14 @@ const TERMINAL_RUN_STATES = new Set(['completed', 'failed', 'canceled']);
27
27
  const CALLBACK_TTL_MS = 10 * 60 * 1000;
28
28
  const MAX_CALLBACK_ACTIONS = 1000;
29
29
  const MAX_TELEGRAM_TEXT = 3600;
30
+ const MAX_ACTIVITY_OUTPUT_CHARS = 48_000;
31
+ const MAX_SSE_BUFFER_CHARS = 256_000;
30
32
  const ACTIVITY_EDIT_THROTTLE_MS = 1200;
31
33
  const ACTIVITY_HEARTBEAT_MS = 8000;
32
34
  const INTENT_ROUTER_TIMEOUT_MS = 45_000;
33
35
  const callbackActions = new Map();
34
36
  const runMonitors = new Map();
37
+ const activeLongTasks = new Map();
35
38
 
36
39
  const MODEL_FALLBACKS = Object.fromEntries(
37
40
  PROVIDERS.map((provider) => [provider, getStaticProviderModels(provider)]),
@@ -85,6 +88,14 @@ function truncate(text, max = MAX_TELEGRAM_TEXT) {
85
88
  return value.length > max ? `${value.slice(0, max - 20)}\n\n…truncated…` : value;
86
89
  }
87
90
 
91
+ function appendBoundedText(current, chunk, maxChars = MAX_ACTIVITY_OUTPUT_CHARS) {
92
+ const nextChunk = String(chunk || '');
93
+ if (!nextChunk) return { text: current || '', truncated: false };
94
+ const combined = `${current || ''}${nextChunk}`;
95
+ if (combined.length <= maxChars) return { text: combined, truncated: false };
96
+ return { text: combined.slice(-maxChars), truncated: true };
97
+ }
98
+
88
99
  function languageFor(link) {
89
100
  return SUPPORTED_LANGUAGES.includes(link?.language) ? link.language : 'en';
90
101
  }
@@ -313,6 +324,9 @@ async function localAgentStream(userId, body, onEvent) {
313
324
  const { done, value } = await reader.read();
314
325
  if (done) break;
315
326
  buffer += decoder.decode(value, { stream: true });
327
+ if (buffer.length > MAX_SSE_BUFFER_CHARS) {
328
+ buffer = buffer.slice(-MAX_SSE_BUFFER_CHARS);
329
+ }
316
330
  let boundary = buffer.indexOf('\n\n');
317
331
  while (boundary !== -1) {
318
332
  const block = buffer.slice(0, boundary);
@@ -360,6 +374,7 @@ function createActivityState({ lang, type = 'agent', provider, project, prompt,
360
374
  workflowId: null,
361
375
  events: [],
362
376
  output: '',
377
+ outputTruncated: false,
363
378
  error: null,
364
379
  };
365
380
  }
@@ -386,6 +401,13 @@ function trimTelegramOutput(text, max, suffix = '') {
386
401
  return `${value.slice(0, room).trim()}\n\n${ending}`;
387
402
  }
388
403
 
404
+ function appendActivityOutput(activity, chunk) {
405
+ if (!activity) return;
406
+ const next = appendBoundedText(activity.output, chunk);
407
+ activity.output = next.text;
408
+ activity.outputTruncated = Boolean(activity.outputTruncated || next.truncated);
409
+ }
410
+
389
411
  function renderActivity(activity, { finalText = null } = {}) {
390
412
  const output = finalText || activity.output;
391
413
  if (activity.type === 'agent' && output && !activity.error) {
@@ -393,7 +415,9 @@ function renderActivity(activity, { finalText = null } = {}) {
393
415
  return trimTelegramOutput(
394
416
  output,
395
417
  3400,
396
- t(activity.lang, 'control.activity.outputTooLong'),
418
+ activity.outputTruncated
419
+ ? t(activity.lang, 'control.activity.outputHistoryTrimmed')
420
+ : t(activity.lang, 'control.activity.outputTooLong'),
397
421
  );
398
422
  }
399
423
 
@@ -839,13 +863,13 @@ function applyAgentStreamEvent(activity, event) {
839
863
  if (event.kind === 'stream_delta' || event.kind === 'text') {
840
864
  activity.status = 'running';
841
865
  activity.phase = t(activity.lang, 'control.activity.responding');
842
- activity.output = `${activity.output}${extractTextFromEvent(event)}`;
866
+ appendActivityOutput(activity, extractTextFromEvent(event));
843
867
  return;
844
868
  }
845
869
  if (event.type === 'claude-response' && event.data?.type === 'assistant') {
846
870
  activity.status = 'running';
847
871
  activity.phase = t(activity.lang, 'control.activity.responding');
848
- activity.output = `${activity.output}${extractTextFromEvent(event)}`;
872
+ appendActivityOutput(activity, extractTextFromEvent(event));
849
873
  return;
850
874
  }
851
875
  if (event.kind === 'tool_use') {
@@ -975,6 +999,47 @@ async function runAgent({ bot, chatId, link, prompt, activity = null }) {
975
999
  await editTelegramActivity({ bot, chatId, activity: active, force: true });
976
1000
  }
977
1001
 
1002
+ function longTaskKey(chatId) {
1003
+ return String(chatId);
1004
+ }
1005
+
1006
+ async function launchLongTelegramTask({ bot, chatId, link, kind, activity = null, task }) {
1007
+ const key = longTaskKey(chatId);
1008
+ const existing = activeLongTasks.get(key);
1009
+ const lang = languageFor(link);
1010
+ if (existing) {
1011
+ await send(bot, chatId, t(lang, 'control.longTaskRunning', {
1012
+ kind: existing.kind,
1013
+ elapsed: formatElapsed(existing.startedAt),
1014
+ }), {
1015
+ editMessageId: activity?.messageId,
1016
+ parse_mode: undefined,
1017
+ });
1018
+ return true;
1019
+ }
1020
+
1021
+ const record = {
1022
+ kind,
1023
+ startedAt: Date.now(),
1024
+ };
1025
+ activeLongTasks.set(key, record);
1026
+
1027
+ Promise.resolve()
1028
+ .then(task)
1029
+ .catch(async (error) => {
1030
+ console.error('[telegram-control] long task failed:', error);
1031
+ await send(bot, chatId, t(lang, 'control.longTaskFailed'), {
1032
+ editMessageId: activity?.messageId,
1033
+ parse_mode: undefined,
1034
+ }).catch(() => {});
1035
+ })
1036
+ .finally(() => {
1037
+ if (activeLongTasks.get(key) === record) activeLongTasks.delete(key);
1038
+ });
1039
+
1040
+ return true;
1041
+ }
1042
+
978
1043
  export async function runWorkflow({ bot, chatId, link, input, activity = null }) {
979
1044
  const lang = languageFor(link);
980
1045
  const state = getState(link.user_id);
@@ -1188,8 +1253,14 @@ async function handleRoutedIntent({ bot, chatId, link, text, activity }) {
1188
1253
  const editMessageId = activity?.messageId;
1189
1254
 
1190
1255
  if (intent.action === 'agent_prompt') {
1191
- await runAgent({ bot, chatId, link, prompt: intent.prompt || text, activity });
1192
- return true;
1256
+ return launchLongTelegramTask({
1257
+ bot,
1258
+ chatId,
1259
+ link,
1260
+ kind: 'agent',
1261
+ activity,
1262
+ task: () => runAgent({ bot, chatId, link, prompt: intent.prompt || text, activity }),
1263
+ });
1193
1264
  }
1194
1265
  if (intent.action === 'show_menu') {
1195
1266
  await showMainMenu({ bot, chatId, link, editMessageId });
@@ -1266,8 +1337,14 @@ async function handleRoutedIntent({ bot, chatId, link, text, activity }) {
1266
1337
  return true;
1267
1338
  }
1268
1339
  if (intent.action === 'run_workflow') {
1269
- await runWorkflow({ bot, chatId, link, input: intent.workflowInput || text, activity });
1270
- return true;
1340
+ return launchLongTelegramTask({
1341
+ bot,
1342
+ chatId,
1343
+ link,
1344
+ kind: 'workflow',
1345
+ activity,
1346
+ task: () => runWorkflow({ bot, chatId, link, input: intent.workflowInput || text, activity }),
1347
+ });
1271
1348
  }
1272
1349
  if (intent.action === 'show_sessions') {
1273
1350
  await showSessions({ bot, chatId, link, editMessageId });
@@ -1277,8 +1354,14 @@ async function handleRoutedIntent({ bot, chatId, link, text, activity }) {
1277
1354
  await startNewChat({ bot, chatId, link, editMessageId });
1278
1355
  return true;
1279
1356
  }
1280
- await runAgent({ bot, chatId, link, prompt: text, activity });
1281
- return true;
1357
+ return launchLongTelegramTask({
1358
+ bot,
1359
+ chatId,
1360
+ link,
1361
+ kind: 'agent',
1362
+ activity,
1363
+ task: () => runAgent({ bot, chatId, link, prompt: text, activity }),
1364
+ });
1282
1365
  }
1283
1366
 
1284
1367
  async function fetchRun(userId, runId) {
@@ -1493,12 +1576,22 @@ async function handleAwaitingInput({ bot, chatId, link, text }) {
1493
1576
  updateTelegramControlState(link.user_id, { awaiting: null });
1494
1577
 
1495
1578
  if (awaiting.type === 'agent_prompt') {
1496
- await runAgent({ bot, chatId, link, prompt: text });
1497
- return true;
1579
+ return launchLongTelegramTask({
1580
+ bot,
1581
+ chatId,
1582
+ link,
1583
+ kind: 'agent',
1584
+ task: () => runAgent({ bot, chatId, link, prompt: text }),
1585
+ });
1498
1586
  }
1499
1587
  if (awaiting.type === 'workflow_prompt') {
1500
- await runWorkflow({ bot, chatId, link, input: text });
1501
- return true;
1588
+ return launchLongTelegramTask({
1589
+ bot,
1590
+ chatId,
1591
+ link,
1592
+ kind: 'workflow',
1593
+ task: () => runWorkflow({ bot, chatId, link, input: text }),
1594
+ });
1502
1595
  }
1503
1596
  return false;
1504
1597
  }
@@ -1592,8 +1685,13 @@ async function handleCommand({ bot, chatId, link, text }) {
1592
1685
  await send(bot, chatId, t(lang, 'control.sendAgentPrompt'));
1593
1686
  return true;
1594
1687
  }
1595
- await runAgent({ bot, chatId, link, prompt: argText });
1596
- return true;
1688
+ return launchLongTelegramTask({
1689
+ bot,
1690
+ chatId,
1691
+ link,
1692
+ kind: 'agent',
1693
+ task: () => runAgent({ bot, chatId, link, prompt: argText }),
1694
+ });
1597
1695
  }
1598
1696
  if (command === '/workflow' || command === '/orchestrate') {
1599
1697
  if (!argText) {
@@ -1601,8 +1699,13 @@ async function handleCommand({ bot, chatId, link, text }) {
1601
1699
  await send(bot, chatId, t(lang, 'control.sendWorkflowPrompt'));
1602
1700
  return true;
1603
1701
  }
1604
- await runWorkflow({ bot, chatId, link, input: argText });
1605
- return true;
1702
+ return launchLongTelegramTask({
1703
+ bot,
1704
+ chatId,
1705
+ link,
1706
+ kind: 'workflow',
1707
+ task: () => runWorkflow({ bot, chatId, link, input: argText }),
1708
+ });
1606
1709
  }
1607
1710
  if (command === '/cancel') {
1608
1711
  if (!argText) {
@@ -1661,6 +1764,10 @@ export async function handleTelegramControlMessage(args) {
1661
1764
  await send(args.bot, chatId, t(languageFor(args.link), 'error.generic')).catch(() => {});
1662
1765
  return true;
1663
1766
  }
1767
+ }).catch(async (error) => {
1768
+ console.error('[telegram-control] message job failed:', error);
1769
+ await send(args.bot, chatId, t(languageFor(args.link), 'error.generic')).catch(() => {});
1770
+ return true;
1664
1771
  });
1665
1772
  }
1666
1773
 
@@ -1849,5 +1956,10 @@ export async function handleTelegramControlCallback(args) {
1849
1956
  await args.bot?.answerCallbackQuery(args.query?.id, { text: t(lang, 'error.generic') }).catch(() => {});
1850
1957
  await send(args.bot, chatId, t(lang, 'error.generic')).catch(() => {});
1851
1958
  }
1959
+ }).catch(async (error) => {
1960
+ console.error('[telegram-control] callback job failed:', error);
1961
+ const lang = languageFor(args.link);
1962
+ await args.bot?.answerCallbackQuery(args.query?.id, { text: t(lang, 'error.generic') }).catch(() => {});
1963
+ await send(args.bot, chatId, t(lang, 'error.generic')).catch(() => {});
1852
1964
  });
1853
1965
  }
@@ -140,7 +140,10 @@ const EN = {
140
140
  'control.activity.tokens': 'Tokens',
141
141
  'control.activity.liveFooter': 'Response is still streaming · {{elapsed}}',
142
142
  'control.activity.outputShortened': 'Telegram output was shortened while the run continues.',
143
+ 'control.activity.outputHistoryTrimmed': 'Earlier response output was trimmed to protect memory. Open the Pixcode session for the full output.',
143
144
  'control.activity.outputTooLong': 'Long response shortened for Telegram. Open the session in Pixcode for the full output.',
145
+ 'control.longTaskRunning': 'A {{kind}} task is already running in this chat · {{elapsed}}. You can still use /menu, /runs, or /settings.',
146
+ 'control.longTaskFailed': 'The background Telegram task failed. Check Pixcode logs or try again.',
144
147
  'notification.taskDone': '✅ {{title}} — task completed.',
145
148
  'notification.taskFailed': '⚠️ {{title}} — task failed: {{error}}',
146
149
  'notification.actionRequired': '❗ {{title}} — action required.',
@@ -281,7 +284,10 @@ const TR = {
281
284
  'control.activity.tokens': 'Token',
282
285
  'control.activity.liveFooter': 'Yanıt devam ediyor · {{elapsed}}',
283
286
  'control.activity.outputShortened': 'Telegram çıktısı çalışma devam ederken kısaltıldı.',
287
+ 'control.activity.outputHistoryTrimmed': 'Yanıtın önceki bölümü bellek koruması için kısaltıldı. Tam çıktı için Pixcode oturumunu aç.',
284
288
  'control.activity.outputTooLong': 'Uzun yanıt Telegram için kısaltıldı. Tam çıktı için Pixcode oturumunu aç.',
289
+ 'control.longTaskRunning': 'Bu sohbette zaten bir {{kind}} işi çalışıyor · {{elapsed}}. /menu, /runs veya /settings yine çalışır.',
290
+ 'control.longTaskFailed': 'Arka plandaki Telegram işi hata verdi. Pixcode loglarını kontrol et veya tekrar dene.',
285
291
  'notification.taskDone': '✅ {{title}} — görev tamamlandı.',
286
292
  'notification.taskFailed': '⚠️ {{title}} — görev başarısız: {{error}}',
287
293
  'notification.actionRequired': '❗ {{title}} — işlem gerekli.',