@pixelbyte-software/pixcode 1.53.13 → 1.53.14

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
@@ -1237,6 +1237,7 @@ function appendPtySessionBuffer(session, data) {
1237
1237
  valueBytes = Buffer.byteLength(value, 'utf8');
1238
1238
  }
1239
1239
 
1240
+ session.totalOutputBytes = (session.totalOutputBytes || 0) + valueBytes;
1240
1241
  session.buffer.push(value);
1241
1242
  session.bufferBytes = (session.bufferBytes || 0) + valueBytes;
1242
1243
 
@@ -1247,6 +1248,31 @@ function appendPtySessionBuffer(session, data) {
1247
1248
  if (session.bufferBytes < 0) session.bufferBytes = 0;
1248
1249
  }
1249
1250
 
1251
+ function readPtySessionBufferedOutput(session, { maxChars = 12000, sinceCursor = null } = {}) {
1252
+ const rawBuffer = (session?.buffer || []).join('');
1253
+ const bufferBytes = session?.bufferBytes || Buffer.byteLength(rawBuffer, 'utf8');
1254
+ const outputCursor = session?.totalOutputBytes || bufferBytes;
1255
+ const bufferStartCursor = Math.max(0, outputCursor - bufferBytes);
1256
+ let rawOutput = rawBuffer;
1257
+
1258
+ if (Number.isFinite(sinceCursor)) {
1259
+ const skipBytes = Math.max(0, sinceCursor - bufferStartCursor);
1260
+ if (skipBytes > 0) {
1261
+ rawOutput = Buffer.from(rawBuffer, 'utf8').slice(skipBytes).toString('utf8');
1262
+ }
1263
+ }
1264
+
1265
+ if (rawOutput.length > maxChars) {
1266
+ rawOutput = rawOutput.slice(-maxChars);
1267
+ }
1268
+
1269
+ return {
1270
+ rawOutput,
1271
+ outputCursor,
1272
+ bufferStartCursor,
1273
+ };
1274
+ }
1275
+
1250
1276
  function normalizeTerminalStartupInput(input) {
1251
1277
  return `\x15${String(input || '').replace(/(?:\r\n|\r|\n)+$/u, '')}\r`;
1252
1278
  }
@@ -1627,6 +1653,8 @@ app.get('/api/shell/sessions/provider-output', authenticateToken, requireProject
1627
1653
  20000,
1628
1654
  Math.max(1000, Number.parseInt(String(req.query.maxChars || '12000'), 10) || 12000)
1629
1655
  );
1656
+ const sinceCursorRaw = Number.parseInt(String(req.query.sinceCursor ?? ''), 10);
1657
+ const sinceCursor = Number.isFinite(sinceCursorRaw) ? Math.max(0, sinceCursorRaw) : null;
1630
1658
 
1631
1659
  if (!SHELL_CLI_PROVIDERS.has(provider)) {
1632
1660
  return res.status(400).json({ error: 'Unsupported provider' });
@@ -1643,6 +1671,8 @@ app.get('/api/shell/sessions/provider-output', authenticateToken, requireProject
1643
1671
  tabId,
1644
1672
  sessionId,
1645
1673
  output: '',
1674
+ outputCursor: 0,
1675
+ bufferStartCursor: 0,
1646
1676
  message: 'Multiple provider terminal sessions match this target. Pick a specific tab.',
1647
1677
  });
1648
1678
  }
@@ -1653,14 +1683,21 @@ app.get('/api/shell/sessions/provider-output', authenticateToken, requireProject
1653
1683
  tabId,
1654
1684
  sessionId,
1655
1685
  output: '',
1686
+ outputCursor: 0,
1687
+ bufferStartCursor: 0,
1656
1688
  message: 'No active provider terminal session found for this project.',
1657
1689
  });
1658
1690
  }
1659
1691
 
1660
1692
  const matchedSession = match.session;
1661
- const rawOutput = matchedSession.buffer.join('').slice(-maxChars);
1693
+ const {
1694
+ rawOutput,
1695
+ outputCursor,
1696
+ bufferStartCursor,
1697
+ } = readPtySessionBufferedOutput(matchedSession, { maxChars, sinceCursor });
1662
1698
  const output = stripAnsiSequences(rawOutput);
1663
- const terminalState = resolveProviderTerminalState(matchedSession, provider, output);
1699
+ const fullOutput = readSessionOutputForState(matchedSession);
1700
+ const terminalState = resolveProviderTerminalState(matchedSession, provider, fullOutput);
1664
1701
  res.json({
1665
1702
  active: true,
1666
1703
  provider,
@@ -1671,6 +1708,9 @@ app.get('/api/shell/sessions/provider-output', authenticateToken, requireProject
1671
1708
  matchStatus: match.status,
1672
1709
  ...terminalState,
1673
1710
  output,
1711
+ outputCursor,
1712
+ bufferStartCursor,
1713
+ sinceCursor,
1674
1714
  });
1675
1715
  });
1676
1716
 
@@ -1723,6 +1763,7 @@ app.post('/api/shell/sessions/provider-input', authenticateToken, requireProject
1723
1763
 
1724
1764
  const matchedSession = match.session;
1725
1765
  const data = submit ? normalizeTerminalStartupInput(input) : input;
1766
+ const outputCursorBefore = matchedSession.totalOutputBytes || matchedSession.bufferBytes || 0;
1726
1767
  try {
1727
1768
  writeTerminalInputChunks(matchedSession.pty, data);
1728
1769
  matchedSession.updatedAt = Date.now();
@@ -1736,6 +1777,8 @@ app.post('/api/shell/sessions/provider-input', authenticateToken, requireProject
1736
1777
  submitted: submit,
1737
1778
  bytes: Buffer.byteLength(data),
1738
1779
  matchStatus: match.status,
1780
+ outputCursorBefore,
1781
+ outputCursorAfter: matchedSession.totalOutputBytes || matchedSession.bufferBytes || 0,
1739
1782
  });
1740
1783
  } catch (error) {
1741
1784
  res.status(500).json({
@@ -4152,6 +4195,7 @@ function handleShellConnection(ws, request) {
4152
4195
  ws: ws,
4153
4196
  buffer: [],
4154
4197
  bufferBytes: 0,
4198
+ totalOutputBytes: 0,
4155
4199
  droppedOutputBytes: 0,
4156
4200
  timeoutId: null,
4157
4201
  userId: ownerUserId,
@@ -46,9 +46,11 @@ router.use((req, res, next) => {
46
46
  });
47
47
 
48
48
  function isNotGitRepositoryMessage(message = '') {
49
- return message.includes('Not a git repository')
50
- || message.includes('not a git repository')
51
- || message.includes('Project directory is not a git repository');
49
+ const normalized = String(message || '').toLowerCase();
50
+ return normalized.includes('not a git repository')
51
+ || normalized.includes('not inside a git work tree')
52
+ || normalized.includes('does not contain a .git folder')
53
+ || normalized.includes('project directory is not a git repository');
52
54
  }
53
55
 
54
56
  function isMissingFileError(error) {
@@ -178,6 +180,35 @@ async function buildFilesystemStatus(projectPath) {
178
180
  };
179
181
  }
180
182
 
183
+ async function readFilesystemFileWithDiff(projectPath, filePath) {
184
+ const projectRoot = path.resolve(projectPath);
185
+ const resolvedFilePath = path.resolve(projectRoot, String(filePath || ''));
186
+ const relativePath = path.relative(projectRoot, resolvedFilePath);
187
+
188
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
189
+ const error = new Error('Invalid file path: path traversal detected');
190
+ error.statusCode = 400;
191
+ throw error;
192
+ }
193
+
194
+ const stats = await fs.stat(resolvedFilePath);
195
+ if (stats.isDirectory()) {
196
+ const error = new Error('Cannot show diff for directories');
197
+ error.statusCode = 400;
198
+ throw error;
199
+ }
200
+
201
+ const currentContent = await fs.readFile(resolvedFilePath, 'utf-8');
202
+ return {
203
+ currentContent,
204
+ oldContent: currentContent,
205
+ isDeleted: false,
206
+ isUntracked: false,
207
+ isGitRepository: false,
208
+ trackingMode: 'filesystem',
209
+ };
210
+ }
211
+
181
212
  function spawnAsync(command, args, options = {}) {
182
213
  return new Promise((resolve, reject) => {
183
214
  const child = spawn(command, args, {
@@ -634,6 +665,14 @@ router.get('/diff', async (req, res) => {
634
665
 
635
666
  res.json({ diff });
636
667
  } catch (error) {
668
+ if (isNotGitRepositoryMessage(getGitErrorDetails(error))) {
669
+ return res.json({
670
+ diff: '',
671
+ isGitRepository: false,
672
+ trackingMode: 'filesystem',
673
+ });
674
+ }
675
+
637
676
  console.error('Git diff error:', error);
638
677
  res.json({ error: error.message });
639
678
  }
@@ -763,6 +802,34 @@ router.get('/file-with-diff', async (req, res) => {
763
802
  isUntracked: false,
764
803
  });
765
804
  }
805
+
806
+ if (isNotGitRepositoryMessage(getGitErrorDetails(error))) {
807
+ try {
808
+ return res.json(await readFilesystemFileWithDiff(
809
+ await getActualProjectPath(project),
810
+ file,
811
+ ));
812
+ } catch (fallbackError) {
813
+ if (isMissingFileError(fallbackError)) {
814
+ return res.status(404).json({
815
+ error: 'File not found',
816
+ currentContent: '',
817
+ oldContent: '',
818
+ isDeleted: true,
819
+ isUntracked: false,
820
+ isGitRepository: false,
821
+ trackingMode: 'filesystem',
822
+ });
823
+ }
824
+
825
+ return res.status(fallbackError.statusCode || 500).json({
826
+ error: fallbackError.message || 'Failed to read file',
827
+ isGitRepository: false,
828
+ trackingMode: 'filesystem',
829
+ });
830
+ }
831
+ }
832
+
766
833
  console.error('Git file-with-diff error:', error);
767
834
  res.json({ error: error.message });
768
835
  }
@@ -32,9 +32,14 @@ const MAX_SSE_BUFFER_CHARS = 256_000;
32
32
  const ACTIVITY_EDIT_THROTTLE_MS = 1200;
33
33
  const ACTIVITY_HEARTBEAT_MS = 8000;
34
34
  const INTENT_ROUTER_TIMEOUT_MS = 45_000;
35
+ const TERMINAL_BRIDGE_TIMEOUT_MS = 5 * 60 * 1000;
36
+ const TERMINAL_BRIDGE_POLL_MS = 1500;
37
+ const TERMINAL_BRIDGE_EDIT_THROTTLE_MS = 3500;
38
+ const TERMINAL_BRIDGE_SETTLE_MS = 6000;
35
39
  const callbackActions = new Map();
36
40
  const runMonitors = new Map();
37
41
  const activeLongTasks = new Map();
42
+ const terminalBridgeMonitors = new Map();
38
43
 
39
44
  const MODEL_FALLBACKS = Object.fromEntries(
40
45
  PROVIDERS.map((provider) => [provider, getStaticProviderModels(provider)]),
@@ -587,7 +592,7 @@ function terminalProjectLabel(terminal) {
587
592
  return terminal?.projectLabel || terminal?.projectName || terminal?.projectPath || '-';
588
593
  }
589
594
 
590
- function terminalOutputUrl(terminal, maxChars = 3200) {
595
+ function terminalOutputUrl(terminal, maxChars = 3200, sinceCursor = null) {
591
596
  const params = new URLSearchParams({
592
597
  provider: terminal.provider,
593
598
  projectPath: terminal.projectPath,
@@ -595,6 +600,7 @@ function terminalOutputUrl(terminal, maxChars = 3200) {
595
600
  });
596
601
  if (terminal.tabId) params.set('tabId', terminal.tabId);
597
602
  if (terminal.sessionId) params.set('sessionId', terminal.sessionId);
603
+ if (Number.isFinite(sinceCursor)) params.set('sinceCursor', String(sinceCursor));
598
604
  return `/api/shell/sessions/provider-output?${params.toString()}`;
599
605
  }
600
606
 
@@ -621,6 +627,193 @@ function renderTerminalSnapshot(lang, terminal, data, { prefix = '', includeOutp
621
627
  return truncate(lines.join('\n'), 3400);
622
628
  }
623
629
 
630
+ function terminalBridgeMonitorKey(chatId, terminal) {
631
+ return [
632
+ chatId,
633
+ terminal.provider,
634
+ terminal.projectPath,
635
+ terminal.tabId || '-',
636
+ terminal.sessionId || '-',
637
+ ].join(':');
638
+ }
639
+
640
+ function normalizeBridgeLine(value) {
641
+ return String(value || '').replace(/\s+/g, ' ').trim();
642
+ }
643
+
644
+ function isNoisyTerminalBridgeLine(line, prompt) {
645
+ const normalized = normalizeBridgeLine(line);
646
+ if (!normalized || normalized.length <= 1) return true;
647
+ if (/^[╭╮╰╯│─═┌┐└┘├┤┬┴┼\s]+$/u.test(normalized)) return true;
648
+ if (/^[●✶✻✽✢✳⠂⠐⏵·\s0;:()\-|/\\]+$/u.test(normalized)) return true;
649
+
650
+ const lower = normalized.toLowerCase();
651
+ const promptNeedle = normalizeBridgeLine(prompt).toLowerCase();
652
+ if (promptNeedle && lower.includes(promptNeedle.slice(0, 120))) return true;
653
+ if (/^[›❯>]\s*/u.test(normalized)) return true;
654
+ if (lower.includes('welcome back')) return true;
655
+ if (lower.includes('api usage billing')) return true;
656
+ if (lower.includes('bypass permissions')) return true;
657
+ if (lower.includes('try "')) return true;
658
+ if (lower.includes('esc to interrupt') || lower.includes('press esc')) return true;
659
+ if (lower.includes('/effort')) return true;
660
+ if (lower.includes('determining')) return true;
661
+ if (/^(?:claude code|codex)\b/i.test(normalized) && normalized.length < 80) return true;
662
+ if (/^\(?\d+s\s*·\s*[↓↑]?\d*\s*tokens?\)?$/iu.test(normalized)) return true;
663
+ return false;
664
+ }
665
+
666
+ function cleanTerminalBridgeOutput(output, prompt) {
667
+ const text = String(output || '')
668
+ .replace(/\r/g, '\n')
669
+ .replace(/[\x00-\x08\x0B-\x1F\x7F]/g, '')
670
+ .replace(/\u00a0/g, ' ');
671
+ const lines = text
672
+ .split('\n')
673
+ .map((line) => line.replace(/[ \t]+/g, ' ').trim())
674
+ .filter((line) => !isNoisyTerminalBridgeLine(line, prompt));
675
+ const deduped = [];
676
+ for (const line of lines) {
677
+ if (deduped.at(-1) === line) continue;
678
+ deduped.push(line);
679
+ }
680
+ return deduped.join('\n').trim();
681
+ }
682
+
683
+ function renderTerminalBridgeProgress(lang, terminal, {
684
+ output = '',
685
+ statusKey = 'control.terminalWaiting',
686
+ startedAt = Date.now(),
687
+ final = false,
688
+ terminalState = null,
689
+ } = {}) {
690
+ const lines = [
691
+ `${final ? '✅' : '⏳'} ${t(lang, statusKey)}`,
692
+ '',
693
+ `🤖 ${t(lang, 'control.activity.provider')}: ${terminal.provider}`,
694
+ `📁 ${t(lang, 'control.activity.project')}: ${compact(terminalProjectLabel(terminal), 90)}`,
695
+ ];
696
+ if (terminal.sessionId) {
697
+ lines.push(`🧵 ${t(lang, 'control.activity.session')}: ${terminal.sessionId}`);
698
+ }
699
+ if (terminalState) {
700
+ lines.push(`📌 ${t(lang, 'control.activity.status')}: ${terminalState}`);
701
+ }
702
+ if (output) {
703
+ lines.push('', `💬 ${t(lang, 'control.activity.output')}:`);
704
+ lines.push(truncate(output, final ? 2800 : 2200));
705
+ } else {
706
+ lines.push('', t(lang, 'control.terminalWaitingHint'));
707
+ }
708
+ if (!final) {
709
+ lines.push('', `⏱ ${t(lang, 'control.activity.elapsed')}: ${formatElapsed(startedAt)}`);
710
+ }
711
+ return truncate(lines.join('\n'), 3400);
712
+ }
713
+
714
+ async function monitorTerminalBridgeResponse({
715
+ bot,
716
+ chatId,
717
+ link,
718
+ terminal,
719
+ prompt,
720
+ sinceCursor,
721
+ editMessageId,
722
+ monitorKey,
723
+ monitorToken,
724
+ }) {
725
+ const lang = languageFor(link);
726
+ const startedAt = Date.now();
727
+ let lastCleanOutput = '';
728
+ let lastOutputChangeAt = startedAt;
729
+ let lastEditAt = 0;
730
+
731
+ const isCurrent = () => terminalBridgeMonitors.get(monitorKey) === monitorToken;
732
+
733
+ try {
734
+ while (isCurrent() && Date.now() - startedAt < TERMINAL_BRIDGE_TIMEOUT_MS) {
735
+ await new Promise((resolve) => setTimeout(resolve, TERMINAL_BRIDGE_POLL_MS));
736
+ if (!isCurrent()) return;
737
+
738
+ const data = await localApi(
739
+ link.user_id,
740
+ terminalOutputUrl(terminal, 12000, sinceCursor),
741
+ { timeoutMs: 12_000 },
742
+ );
743
+ if (data?.active === false) {
744
+ await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
745
+ output: lastCleanOutput,
746
+ statusKey: 'control.terminalNotRunning',
747
+ startedAt,
748
+ final: true,
749
+ terminalState: 'not running',
750
+ }), {
751
+ editMessageId,
752
+ parse_mode: undefined,
753
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
754
+ });
755
+ return;
756
+ }
757
+ const cleanOutput = cleanTerminalBridgeOutput(data?.output, prompt);
758
+ const terminalState = data?.terminalState || data?.lifecycleState || 'unknown';
759
+ const now = Date.now();
760
+
761
+ if (cleanOutput && cleanOutput !== lastCleanOutput) {
762
+ lastCleanOutput = cleanOutput;
763
+ lastOutputChangeAt = now;
764
+ }
765
+
766
+ const finishedByState = ['idle', 'completed', 'failed', 'exited'].includes(terminalState);
767
+ const finishedByQuietOutput = Boolean(lastCleanOutput) && now - lastOutputChangeAt >= TERMINAL_BRIDGE_SETTLE_MS;
768
+ const shouldFinish = Boolean(lastCleanOutput) && (finishedByState || finishedByQuietOutput);
769
+
770
+ if (shouldFinish) {
771
+ await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
772
+ output: lastCleanOutput,
773
+ statusKey: 'control.terminalResponseReady',
774
+ startedAt,
775
+ final: true,
776
+ terminalState,
777
+ }), {
778
+ editMessageId,
779
+ parse_mode: undefined,
780
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
781
+ });
782
+ return;
783
+ }
784
+
785
+ if (now - lastEditAt >= TERMINAL_BRIDGE_EDIT_THROTTLE_MS) {
786
+ lastEditAt = now;
787
+ await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
788
+ output: lastCleanOutput,
789
+ statusKey: lastCleanOutput ? 'control.terminalResponding' : 'control.terminalWaiting',
790
+ startedAt,
791
+ terminalState,
792
+ }), {
793
+ editMessageId,
794
+ parse_mode: undefined,
795
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
796
+ });
797
+ }
798
+ }
799
+
800
+ if (!isCurrent()) return;
801
+ await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
802
+ output: lastCleanOutput,
803
+ statusKey: lastCleanOutput ? 'control.terminalStillRunning' : 'control.terminalNoReadableOutput',
804
+ startedAt,
805
+ final: Boolean(lastCleanOutput),
806
+ terminalState: 'running',
807
+ }), {
808
+ editMessageId,
809
+ parse_mode: undefined,
810
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
811
+ });
812
+ } finally {
813
+ if (isCurrent()) terminalBridgeMonitors.delete(monitorKey);
814
+ }
815
+ }
816
+
624
817
  export async function sendActiveTerminalAttachedNotice({ bot, chatId, link, terminal }) {
625
818
  const lang = languageFor(link);
626
819
  const lines = [
@@ -695,7 +888,7 @@ async function sendToActiveTerminal({ bot, chatId, link, text }) {
695
888
  const editMessageId = sent?.message_id || sent?.message?.message_id || null;
696
889
 
697
890
  try {
698
- await localApi(link.user_id, '/api/shell/sessions/provider-input', {
891
+ const inputResult = await localApi(link.user_id, '/api/shell/sessions/provider-input', {
699
892
  method: 'POST',
700
893
  timeoutMs: 15_000,
701
894
  body: {
@@ -707,15 +900,35 @@ async function sendToActiveTerminal({ bot, chatId, link, text }) {
707
900
  submit: true,
708
901
  },
709
902
  });
710
- await new Promise((resolve) => setTimeout(resolve, 700));
711
- const data = await localApi(link.user_id, terminalOutputUrl(terminal), { timeoutMs: 12_000 });
712
- await send(bot, chatId, renderTerminalSnapshot(lang, terminal, data, {
713
- prefix: t(lang, 'control.terminalSent'),
903
+ const sinceCursor = Number.isFinite(inputResult?.outputCursorBefore)
904
+ ? inputResult.outputCursorBefore
905
+ : null;
906
+ const monitorKey = terminalBridgeMonitorKey(chatId, terminal);
907
+ const monitorToken = crypto.randomUUID();
908
+ terminalBridgeMonitors.set(monitorKey, monitorToken);
909
+
910
+ await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
911
+ statusKey: 'control.terminalWaiting',
912
+ startedAt: Date.now(),
913
+ terminalState: 'running',
714
914
  }), {
715
915
  editMessageId,
716
916
  parse_mode: undefined,
717
917
  reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
718
918
  });
919
+ monitorTerminalBridgeResponse({
920
+ bot,
921
+ chatId,
922
+ link,
923
+ terminal,
924
+ prompt: text,
925
+ sinceCursor,
926
+ editMessageId,
927
+ monitorKey,
928
+ monitorToken,
929
+ }).catch((error) => {
930
+ console.warn('[telegram-control] terminal bridge monitor failed:', error?.message || error);
931
+ });
719
932
  } catch (error) {
720
933
  await send(bot, chatId, t(lang, 'control.terminalSendFailed', {
721
934
  error: error?.message || String(error),
@@ -86,6 +86,13 @@ const EN = {
86
86
  'control.terminalOutputHidden': 'Live CLI screen output is not dumped into Telegram because full-screen terminal UIs are noisy. Keep sending messages here, or open Pixcode for the live terminal.',
87
87
  'control.terminalSending': '📲 Sending to {{provider}} terminal on {{project}}...',
88
88
  'control.terminalSent': '📲 Sent to the active terminal.',
89
+ 'control.terminalSentHint': 'I will not dump the live terminal screen here because full-screen CLI UIs are noisy. Keep sending messages here, or open Pixcode for the live terminal.',
90
+ 'control.terminalWaiting': 'Message sent to the active terminal. Waiting for the CLI response...',
91
+ 'control.terminalWaitingHint': 'The same web CLI session is still attached. I will edit this message when readable output arrives.',
92
+ 'control.terminalResponding': 'CLI response is streaming.',
93
+ 'control.terminalResponseReady': 'CLI response is ready.',
94
+ 'control.terminalStillRunning': 'The CLI is still running. I am leaving this Telegram bridge attached.',
95
+ 'control.terminalNoReadableOutput': 'The message was sent, but I could not extract a clean Telegram reply yet.',
89
96
  'control.terminalSendFailed': 'Could not send to the active terminal: {{error}}',
90
97
  'control.terminalStatusFailed': 'Could not read the active terminal: {{error}}',
91
98
  'control.terminalDetached': 'Telegram is detached from the web CLI session. Plain messages will use the normal AI router again.',
@@ -246,6 +253,13 @@ const TR = {
246
253
  'control.terminalOutputHidden': 'Canlı CLI ekran çıktısı Telegrama dökülmez; tam ekran terminal arayüzleri burada gürültülü görünür. Buradan mesaj göndermeye devam et veya canlı terminal için Pixcodeu aç.',
247
254
  'control.terminalSending': '📲 {{project}} üzerinde {{provider}} terminaline gönderiliyor...',
248
255
  'control.terminalSent': '📲 Aktif terminale gönderildi.',
256
+ 'control.terminalSentHint': 'Canlı terminal ekranını buraya dökmeyeceğim; tam ekran CLI arayüzleri Telegramda bozuk görünebilir. Buradan mesaj göndermeye devam et veya canlı terminal için Pixcodeu aç.',
257
+ 'control.terminalWaiting': 'Mesaj aktif terminale gönderildi. CLI yanıtı bekleniyor...',
258
+ 'control.terminalWaitingHint': 'Aynı web CLI oturumu bağlı kalıyor. Okunabilir çıktı gelince bu mesajı düzenleyeceğim.',
259
+ 'control.terminalResponding': 'CLI yanıtı geliyor.',
260
+ 'control.terminalResponseReady': 'CLI yanıtı hazır.',
261
+ 'control.terminalStillRunning': 'CLI hâlâ çalışıyor. Telegram köprüsünü bağlı bırakıyorum.',
262
+ 'control.terminalNoReadableOutput': 'Mesaj gönderildi ama henüz temiz bir Telegram yanıtı çıkaramadım.',
249
263
  'control.terminalSendFailed': 'Aktif terminale gönderilemedi: {{error}}',
250
264
  'control.terminalStatusFailed': 'Aktif terminal okunamadı: {{error}}',
251
265
  'control.terminalDetached': 'Telegram web CLI oturumundan ayrıldı. Düz mesajlar tekrar normal AI router akışına gidecek.',