@pixelbyte-software/pixcode 1.53.6 → 1.53.8

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.
Files changed (33) hide show
  1. package/dist/assets/{index-DP7jnMyc.js → index-BTj30VBN.js} +1 -1
  2. package/dist/index.html +1 -1
  3. package/dist-server/server/claude-sdk.js +17 -15
  4. package/dist-server/server/claude-sdk.js.map +1 -1
  5. package/dist-server/server/cursor-cli.js +14 -12
  6. package/dist-server/server/cursor-cli.js.map +1 -1
  7. package/dist-server/server/gemini-cli.js +14 -12
  8. package/dist-server/server/gemini-cli.js.map +1 -1
  9. package/dist-server/server/index.js +38 -7
  10. package/dist-server/server/index.js.map +1 -1
  11. package/dist-server/server/openai-codex.js +25 -22
  12. package/dist-server/server/openai-codex.js.map +1 -1
  13. package/dist-server/server/opencode-cli.js +14 -12
  14. package/dist-server/server/opencode-cli.js.map +1 -1
  15. package/dist-server/server/qwen-code-cli.js +14 -12
  16. package/dist-server/server/qwen-code-cli.js.map +1 -1
  17. package/dist-server/server/routes/agent.js +11 -4
  18. package/dist-server/server/routes/agent.js.map +1 -1
  19. package/dist-server/server/services/telegram/control-center.js +44 -2
  20. package/dist-server/server/services/telegram/control-center.js.map +1 -1
  21. package/dist-server/server/services/telegram/translations.js +8 -0
  22. package/dist-server/server/services/telegram/translations.js.map +1 -1
  23. package/package.json +1 -1
  24. package/server/claude-sdk.js +3 -3
  25. package/server/cursor-cli.js +3 -3
  26. package/server/gemini-cli.js +3 -3
  27. package/server/index.js +39 -5
  28. package/server/openai-codex.js +5 -4
  29. package/server/opencode-cli.js +3 -3
  30. package/server/qwen-code-cli.js +3 -3
  31. package/server/routes/agent.js +11 -4
  32. package/server/services/telegram/control-center.js +53 -2
  33. package/server/services/telegram/translations.js +8 -0
@@ -65,7 +65,7 @@ async function ensureGeminiSettingsJson() {
65
65
  }
66
66
 
67
67
  async function spawnGemini(command, options = {}, ws) {
68
- const { sessionId, projectPath, cwd, toolsSettings, permissionMode, images, sessionSummary } = options;
68
+ const { sessionId, projectPath, cwd, toolsSettings, permissionMode, images, sessionSummary, suppressNotifications = false } = options;
69
69
  let capturedSessionId = sessionId; // Track session ID throughout the process
70
70
  let sessionCreatedSent = false; // Track if we've already sent session-created event
71
71
  let assistantBlocks = []; // Accumulate the full response blocks including tools
@@ -265,7 +265,7 @@ async function spawnGemini(command, options = {}, ws) {
265
265
 
266
266
  const finalSessionId = capturedSessionId || sessionId || processKey;
267
267
  if (code === 0 && !error) {
268
- notifyRunStopped({
268
+ if (!suppressNotifications) notifyRunStopped({
269
269
  userId: ws?.userId || null,
270
270
  provider: 'gemini',
271
271
  sessionId: finalSessionId,
@@ -275,7 +275,7 @@ async function spawnGemini(command, options = {}, ws) {
275
275
  return;
276
276
  }
277
277
 
278
- notifyRunFailed({
278
+ if (!suppressNotifications) notifyRunFailed({
279
279
  userId: ws?.userId || null,
280
280
  provider: 'gemini',
281
281
  sessionId: finalSessionId,
package/server/index.js CHANGED
@@ -1012,6 +1012,9 @@ const ptySessionsMap = new Map();
1012
1012
  const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
1013
1013
  const COMPLETED_PTY_SESSION_TTL = 5 * 60 * 1000;
1014
1014
  const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
1015
+ const SHELL_OUTPUT_FLUSH_MAX_CHARS = 128 * 1024;
1016
+ const SHELL_WS_BACKPRESSURE_LIMIT = 8 * 1024 * 1024;
1017
+ const PTY_SESSION_BUFFER_MAX_BYTES = Number.parseInt(process.env.PIXCODE_PTY_BUFFER_MAX_BYTES || '', 10) || (2 * 1024 * 1024);
1015
1018
  const SHELL_CLI_PROVIDERS = new Set(['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode']);
1016
1019
  import { stripAnsiSequences, normalizeDetectedUrl, extractUrlsFromText, shouldAutoOpenUrlFromOutput } from './utils/url-detection.js';
1017
1020
 
@@ -1156,12 +1159,23 @@ function resolveProviderTerminalState(session, provider, output) {
1156
1159
 
1157
1160
  function appendPtySessionBuffer(session, data) {
1158
1161
  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);
1162
+ let value = String(data || '');
1163
+ if (!value) return;
1164
+
1165
+ let valueBytes = Buffer.byteLength(value, 'utf8');
1166
+ if (valueBytes > PTY_SESSION_BUFFER_MAX_BYTES) {
1167
+ value = value.slice(-PTY_SESSION_BUFFER_MAX_BYTES);
1168
+ valueBytes = Buffer.byteLength(value, 'utf8');
1169
+ }
1170
+
1171
+ session.buffer.push(value);
1172
+ session.bufferBytes = (session.bufferBytes || 0) + valueBytes;
1173
+
1174
+ while (session.buffer.length > 0 && session.bufferBytes > PTY_SESSION_BUFFER_MAX_BYTES) {
1175
+ const removed = session.buffer.shift();
1176
+ session.bufferBytes -= Buffer.byteLength(String(removed || ''), 'utf8');
1164
1177
  }
1178
+ if (session.bufferBytes < 0) session.bufferBytes = 0;
1165
1179
  }
1166
1180
 
1167
1181
  function normalizeTerminalStartupInput(input) {
@@ -3424,7 +3438,17 @@ function handleShellConnection(ws, request) {
3424
3438
  appendPtySessionBuffer(session, rawData);
3425
3439
 
3426
3440
  if (session.ws && session.ws.readyState === WebSocket.OPEN) {
3441
+ if (session.ws.bufferedAmount > SHELL_WS_BACKPRESSURE_LIMIT) {
3442
+ session.droppedOutputBytes = (session.droppedOutputBytes || 0) + Buffer.byteLength(rawData, 'utf8');
3443
+ return;
3444
+ }
3445
+
3446
+ const droppedOutputBytes = session.droppedOutputBytes || 0;
3447
+ session.droppedOutputBytes = 0;
3427
3448
  let outputData = rawData;
3449
+ if (droppedOutputBytes > 0) {
3450
+ outputData = `\r\n\x1b[33m[Pixcode] ${droppedOutputBytes} bytes of terminal output were skipped because the browser connection was backpressured.\x1b[0m\r\n${outputData}`;
3451
+ }
3428
3452
 
3429
3453
  const cleanChunk = stripAnsiSequences(rawData);
3430
3454
  urlDetectionBuffer = `${urlDetectionBuffer}${cleanChunk}`.slice(-SHELL_URL_PARSE_BUFFER_LIMIT);
@@ -3786,6 +3810,8 @@ function handleShellConnection(ws, request) {
3786
3810
  pty: shellProcess,
3787
3811
  ws: ws,
3788
3812
  buffer: [],
3813
+ bufferBytes: 0,
3814
+ droppedOutputBytes: 0,
3789
3815
  timeoutId: null,
3790
3816
  userId: ownerUserId,
3791
3817
  projectPath,
@@ -3812,6 +3838,14 @@ function handleShellConnection(ws, request) {
3812
3838
  // keystroke-sized chunk.
3813
3839
  shellProcess.onData((data) => {
3814
3840
  outputBuffer += data;
3841
+ if (outputBuffer.length >= SHELL_OUTPUT_FLUSH_MAX_CHARS) {
3842
+ if (outputFlushTimer) {
3843
+ clearTimeout(outputFlushTimer);
3844
+ outputFlushTimer = null;
3845
+ }
3846
+ flushOutputBuffer();
3847
+ return;
3848
+ }
3815
3849
  if (!outputFlushTimer) {
3816
3850
  outputFlushTimer = setTimeout(flushOutputBuffer, 8);
3817
3851
  }
@@ -236,7 +236,8 @@ export async function queryCodex(command, options = {}, ws) {
236
236
  cwd,
237
237
  projectPath,
238
238
  model,
239
- permissionMode = 'default'
239
+ permissionMode = 'default',
240
+ suppressNotifications = false
240
241
  } = options;
241
242
 
242
243
  const workingDirectory = cwd || projectPath || process.cwd();
@@ -313,7 +314,7 @@ export async function queryCodex(command, options = {}, ws) {
313
314
 
314
315
  if (event.type === 'turn.failed' && !terminalFailure) {
315
316
  terminalFailure = event.error || new Error('Turn failed');
316
- notifyRunFailed({
317
+ if (!suppressNotifications) notifyRunFailed({
317
318
  userId: ws?.userId || null,
318
319
  provider: 'codex',
319
320
  sessionId: currentSessionId,
@@ -332,7 +333,7 @@ export async function queryCodex(command, options = {}, ws) {
332
333
  // Send completion event
333
334
  if (!terminalFailure) {
334
335
  sendMessage(ws, createNormalizedMessage({ kind: 'complete', actualSessionId: thread.id, sessionId: currentSessionId, provider: 'codex' }));
335
- notifyRunStopped({
336
+ if (!suppressNotifications) notifyRunStopped({
336
337
  userId: ws?.userId || null,
337
338
  provider: 'codex',
338
339
  sessionId: currentSessionId,
@@ -359,7 +360,7 @@ export async function queryCodex(command, options = {}, ws) {
359
360
 
360
361
  sendMessage(ws, createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: currentSessionId, provider: 'codex' }));
361
362
  if (!terminalFailure) {
362
- notifyRunFailed({
363
+ if (!suppressNotifications) notifyRunFailed({
363
364
  userId: ws?.userId || null,
364
365
  provider: 'codex',
365
366
  sessionId: currentSessionId,
@@ -79,7 +79,7 @@ function buildOpencodePermissionConfig(settings) {
79
79
  }
80
80
 
81
81
  async function spawnOpencode(command, options = {}, ws) {
82
- const { sessionId, projectPath, cwd, toolsSettings, permissionMode, images, sessionSummary, agent: agentOverride } = options;
82
+ const { sessionId, projectPath, cwd, toolsSettings, permissionMode, images, sessionSummary, agent: agentOverride, suppressNotifications = false } = options;
83
83
  let capturedSessionId = sessionId;
84
84
  let sessionCreatedSent = false;
85
85
  let assistantBlocks = [];
@@ -211,7 +211,7 @@ async function spawnOpencode(command, options = {}, ws) {
211
211
 
212
212
  const finalSessionId = capturedSessionId || sessionId || processKey;
213
213
  if (code === 0 && !error) {
214
- notifyRunStopped({
214
+ if (!suppressNotifications) notifyRunStopped({
215
215
  userId: ws?.userId || null,
216
216
  provider: 'opencode',
217
217
  sessionId: finalSessionId,
@@ -221,7 +221,7 @@ async function spawnOpencode(command, options = {}, ws) {
221
221
  return;
222
222
  }
223
223
 
224
- notifyRunFailed({
224
+ if (!suppressNotifications) notifyRunFailed({
225
225
  userId: ws?.userId || null,
226
226
  provider: 'opencode',
227
227
  sessionId: finalSessionId,
@@ -34,7 +34,7 @@ function readCliIdleTimeoutMs() {
34
34
  }
35
35
 
36
36
  async function spawnQwen(command, options = {}, ws) {
37
- const { sessionId, projectPath, cwd, toolsSettings, permissionMode, images, sessionSummary } = options;
37
+ const { sessionId, projectPath, cwd, toolsSettings, permissionMode, images, sessionSummary, suppressNotifications = false } = options;
38
38
  let capturedSessionId = sessionId;
39
39
  let sessionCreatedSent = false;
40
40
  let assistantBlocks = [];
@@ -154,7 +154,7 @@ async function spawnQwen(command, options = {}, ws) {
154
154
 
155
155
  const finalSessionId = capturedSessionId || sessionId || processKey;
156
156
  if (code === 0 && !error) {
157
- notifyRunStopped({
157
+ if (!suppressNotifications) notifyRunStopped({
158
158
  userId: ws?.userId || null,
159
159
  provider: 'qwen',
160
160
  sessionId: finalSessionId,
@@ -164,7 +164,7 @@ async function spawnQwen(command, options = {}, ws) {
164
164
  return;
165
165
  }
166
166
 
167
- notifyRunFailed({
167
+ if (!suppressNotifications) notifyRunFailed({
168
168
  userId: ws?.userId || null,
169
169
  provider: 'qwen',
170
170
  sessionId: finalSessionId,
@@ -968,6 +968,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
968
968
  const permissionMode = ['default', 'acceptEdits', 'bypassPermissions', 'plan', 'auto_edit', 'yolo'].includes(requestedPermissionMode)
969
969
  ? requestedPermissionMode
970
970
  : null;
971
+ const suppressNotifications = req.body.suppressNotifications === true || req.body.suppressNotifications === 'true';
971
972
 
972
973
  // Parse stream and cleanup as booleans (handle string "true"/"false" from curl)
973
974
  const stream = req.body.stream === undefined ? true : (req.body.stream === true || req.body.stream === 'true');
@@ -1079,7 +1080,8 @@ router.post('/', validateExternalApiKey, async (req, res) => {
1079
1080
  cwd: finalProjectPath,
1080
1081
  sessionId: sessionId || null,
1081
1082
  model: model,
1082
- permissionMode: permissionMode || 'bypassPermissions' // Bypass all permissions for API calls unless explicitly restricted
1083
+ permissionMode: permissionMode || 'bypassPermissions', // Bypass all permissions for API calls unless explicitly restricted
1084
+ suppressNotifications,
1083
1085
  }, writer);
1084
1086
 
1085
1087
  } else if (provider === 'cursor') {
@@ -1090,7 +1092,8 @@ router.post('/', validateExternalApiKey, async (req, res) => {
1090
1092
  cwd: finalProjectPath,
1091
1093
  sessionId: sessionId || null,
1092
1094
  model: model || undefined,
1093
- skipPermissions: permissionMode ? permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits' || permissionMode === 'yolo' : true
1095
+ skipPermissions: permissionMode ? permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits' || permissionMode === 'yolo' : true,
1096
+ suppressNotifications,
1094
1097
  }, writer);
1095
1098
  } else if (provider === 'codex') {
1096
1099
  console.log('🤖 Starting Codex SDK session');
@@ -1100,7 +1103,8 @@ router.post('/', validateExternalApiKey, async (req, res) => {
1100
1103
  cwd: finalProjectPath,
1101
1104
  sessionId: sessionId || null,
1102
1105
  model: model || getDefaultProviderModel('codex'),
1103
- permissionMode: permissionMode || 'bypassPermissions'
1106
+ permissionMode: permissionMode || 'bypassPermissions',
1107
+ suppressNotifications,
1104
1108
  }, writer);
1105
1109
  } else if (provider === 'gemini') {
1106
1110
  console.log('✨ Starting Gemini CLI session');
@@ -1111,7 +1115,8 @@ router.post('/', validateExternalApiKey, async (req, res) => {
1111
1115
  sessionId: sessionId || null,
1112
1116
  model: model,
1113
1117
  permissionMode: permissionMode || undefined,
1114
- skipPermissions: permissionMode ? permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits' || permissionMode === 'yolo' : true
1118
+ skipPermissions: permissionMode ? permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits' || permissionMode === 'yolo' : true,
1119
+ suppressNotifications,
1115
1120
  }, writer);
1116
1121
  } else if (provider === 'qwen') {
1117
1122
  console.log('🐉 Starting Qwen Code CLI session');
@@ -1123,6 +1128,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
1123
1128
  model: model,
1124
1129
  permissionMode: permissionMode || undefined,
1125
1130
  skipPermissions: permissionMode ? permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits' || permissionMode === 'yolo' : true,
1131
+ suppressNotifications,
1126
1132
  }, writer);
1127
1133
  } else if (provider === 'opencode') {
1128
1134
  console.log('🅾️ Starting OpenCode CLI session');
@@ -1138,6 +1144,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
1138
1144
  denyPatterns: [],
1139
1145
  skipPermissions: permissionMode ? permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits' || permissionMode === 'yolo' : true,
1140
1146
  },
1147
+ suppressNotifications,
1141
1148
  }, writer);
1142
1149
  }
1143
1150
 
@@ -27,6 +27,8 @@ 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;
@@ -85,6 +87,14 @@ function truncate(text, max = MAX_TELEGRAM_TEXT) {
85
87
  return value.length > max ? `${value.slice(0, max - 20)}\n\n…truncated…` : value;
86
88
  }
87
89
 
90
+ function appendBoundedText(current, chunk, maxChars = MAX_ACTIVITY_OUTPUT_CHARS) {
91
+ const nextChunk = String(chunk || '');
92
+ if (!nextChunk) return { text: current || '', truncated: false };
93
+ const combined = `${current || ''}${nextChunk}`;
94
+ if (combined.length <= maxChars) return { text: combined, truncated: false };
95
+ return { text: combined.slice(-maxChars), truncated: true };
96
+ }
97
+
88
98
  function languageFor(link) {
89
99
  return SUPPORTED_LANGUAGES.includes(link?.language) ? link.language : 'en';
90
100
  }
@@ -313,6 +323,9 @@ async function localAgentStream(userId, body, onEvent) {
313
323
  const { done, value } = await reader.read();
314
324
  if (done) break;
315
325
  buffer += decoder.decode(value, { stream: true });
326
+ if (buffer.length > MAX_SSE_BUFFER_CHARS) {
327
+ buffer = buffer.slice(-MAX_SSE_BUFFER_CHARS);
328
+ }
316
329
  let boundary = buffer.indexOf('\n\n');
317
330
  while (boundary !== -1) {
318
331
  const block = buffer.slice(0, boundary);
@@ -360,6 +373,7 @@ function createActivityState({ lang, type = 'agent', provider, project, prompt,
360
373
  workflowId: null,
361
374
  events: [],
362
375
  output: '',
376
+ outputTruncated: false,
363
377
  error: null,
364
378
  };
365
379
  }
@@ -378,8 +392,43 @@ function activityTitle(activity) {
378
392
  return t(activity.lang, 'control.activity.agentTitle');
379
393
  }
380
394
 
395
+ function trimTelegramOutput(text, max, suffix = '') {
396
+ const value = String(text || '').trim();
397
+ const ending = String(suffix || '').trim();
398
+ if (value.length <= max) return value;
399
+ const room = Math.max(300, max - ending.length - 4);
400
+ return `${value.slice(0, room).trim()}\n\n${ending}`;
401
+ }
402
+
403
+ function appendActivityOutput(activity, chunk) {
404
+ if (!activity) return;
405
+ const next = appendBoundedText(activity.output, chunk);
406
+ activity.output = next.text;
407
+ activity.outputTruncated = Boolean(activity.outputTruncated || next.truncated);
408
+ }
409
+
381
410
  function renderActivity(activity, { finalText = null } = {}) {
382
411
  const output = finalText || activity.output;
412
+ if (activity.type === 'agent' && output && !activity.error) {
413
+ if (activity.status === 'done') {
414
+ return trimTelegramOutput(
415
+ output,
416
+ 3400,
417
+ activity.outputTruncated
418
+ ? t(activity.lang, 'control.activity.outputHistoryTrimmed')
419
+ : t(activity.lang, 'control.activity.outputTooLong'),
420
+ );
421
+ }
422
+
423
+ const footer = `⏳ ${t(activity.lang, 'control.activity.liveFooter', { elapsed: formatElapsed(activity.startedAt) })}`;
424
+ const body = trimTelegramOutput(
425
+ output,
426
+ 3200,
427
+ t(activity.lang, 'control.activity.outputShortened'),
428
+ );
429
+ return truncate(`${body}\n\n${footer}`, 3400);
430
+ }
431
+
383
432
  const lines = [
384
433
  `${activity.status === 'failed' ? '❌' : activity.status === 'done' ? '✅' : activity.status === 'running' ? '🔧' : '⏳'} ${activityTitle(activity)}`,
385
434
  '',
@@ -813,13 +862,13 @@ function applyAgentStreamEvent(activity, event) {
813
862
  if (event.kind === 'stream_delta' || event.kind === 'text') {
814
863
  activity.status = 'running';
815
864
  activity.phase = t(activity.lang, 'control.activity.responding');
816
- activity.output = `${activity.output}${extractTextFromEvent(event)}`;
865
+ appendActivityOutput(activity, extractTextFromEvent(event));
817
866
  return;
818
867
  }
819
868
  if (event.type === 'claude-response' && event.data?.type === 'assistant') {
820
869
  activity.status = 'running';
821
870
  activity.phase = t(activity.lang, 'control.activity.responding');
822
- activity.output = `${activity.output}${extractTextFromEvent(event)}`;
871
+ appendActivityOutput(activity, extractTextFromEvent(event));
823
872
  return;
824
873
  }
825
874
  if (event.kind === 'tool_use') {
@@ -924,6 +973,7 @@ async function runAgent({ bot, chatId, link, prompt, activity = null }) {
924
973
  message: buildTelegramAgentPrompt(prompt, state),
925
974
  cleanup: false,
926
975
  permissionMode: 'default',
976
+ suppressNotifications: true,
927
977
  }, async (event) => {
928
978
  applyAgentStreamEvent(active, event);
929
979
  await editTelegramActivity({ bot, chatId, activity: active });
@@ -1136,6 +1186,7 @@ async function resolveTelegramAiIntent({ bot, chatId, link, text, activity }) {
1136
1186
  cleanup: false,
1137
1187
  stream: false,
1138
1188
  permissionMode: 'plan',
1189
+ suppressNotifications: true,
1139
1190
  },
1140
1191
  });
1141
1192
  const assistantText = extractAssistantText(response);
@@ -138,6 +138,10 @@ const EN = {
138
138
  'control.activity.toolDone': 'Tool completed',
139
139
  'control.activity.toolFailed': 'Tool failed',
140
140
  'control.activity.tokens': 'Tokens',
141
+ 'control.activity.liveFooter': 'Response is still streaming · {{elapsed}}',
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.',
144
+ 'control.activity.outputTooLong': 'Long response shortened for Telegram. Open the session in Pixcode for the full output.',
141
145
  'notification.taskDone': '✅ {{title}} — task completed.',
142
146
  'notification.taskFailed': '⚠️ {{title}} — task failed: {{error}}',
143
147
  'notification.actionRequired': '❗ {{title}} — action required.',
@@ -276,6 +280,10 @@ const TR = {
276
280
  'control.activity.toolDone': 'Tool tamamlandı',
277
281
  'control.activity.toolFailed': 'Tool başarısız',
278
282
  'control.activity.tokens': 'Token',
283
+ 'control.activity.liveFooter': 'Yanıt devam ediyor · {{elapsed}}',
284
+ 'control.activity.outputShortened': 'Telegram çıktısı çalışma devam ederken kısaltıldı.',
285
+ 'control.activity.outputHistoryTrimmed': 'Yanıtın önceki bölümü bellek koruması için kısaltıldı. Tam çıktı için Pixcode oturumunu aç.',
286
+ 'control.activity.outputTooLong': 'Uzun yanıt Telegram için kısaltıldı. Tam çıktı için Pixcode oturumunu aç.',
279
287
  'notification.taskDone': '✅ {{title}} — görev tamamlandı.',
280
288
  'notification.taskFailed': '⚠️ {{title}} — görev başarısız: {{error}}',
281
289
  'notification.actionRequired': '❗ {{title}} — işlem gerekli.',