@pixelbyte-software/pixcode 1.53.14 → 1.53.16

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
@@ -1277,6 +1277,10 @@ function normalizeTerminalStartupInput(input) {
1277
1277
  return `\x15${String(input || '').replace(/(?:\r\n|\r|\n)+$/u, '')}\r`;
1278
1278
  }
1279
1279
 
1280
+ function normalizeTerminalBufferedInput(input) {
1281
+ return `\x15${String(input || '').replace(/(?:\r\n|\r|\n)+$/u, '')}`;
1282
+ }
1283
+
1280
1284
  function readSessionOutputForState(session, maxChars = 12000) {
1281
1285
  return stripAnsiSequences((session?.buffer || []).join('').slice(-maxChars));
1282
1286
  }
@@ -1723,6 +1727,7 @@ app.post('/api/shell/sessions/provider-input', authenticateToken, requireProject
1723
1727
  const sessionId = readPtyTarget(req.body?.sessionId);
1724
1728
  const input = typeof req.body?.input === 'string' ? req.body.input : '';
1725
1729
  const submit = req.body?.submit !== false;
1730
+ const submitMode = req.body?.submitMode === 'deferred-enter' ? 'deferred-enter' : 'inline';
1726
1731
 
1727
1732
  if (!SHELL_CLI_PROVIDERS.has(provider)) {
1728
1733
  return res.status(400).json({ error: 'Unsupported provider' });
@@ -1762,10 +1767,31 @@ app.post('/api/shell/sessions/provider-input', authenticateToken, requireProject
1762
1767
  }
1763
1768
 
1764
1769
  const matchedSession = match.session;
1765
- const data = submit ? normalizeTerminalStartupInput(input) : input;
1770
+ const data = submit
1771
+ ? (submitMode === 'deferred-enter' ? normalizeTerminalBufferedInput(input) : normalizeTerminalStartupInput(input))
1772
+ : input;
1766
1773
  const outputCursorBefore = matchedSession.totalOutputBytes || matchedSession.bufferBytes || 0;
1767
1774
  try {
1768
1775
  writeTerminalInputChunks(matchedSession.pty, data);
1776
+ if (submit && submitMode === 'deferred-enter') {
1777
+ setTimeout(() => {
1778
+ const currentSession = findProviderPtySession({
1779
+ provider,
1780
+ projectPath,
1781
+ user: req.user,
1782
+ tabId,
1783
+ sessionId,
1784
+ requireRunning: true,
1785
+ requirePty: true,
1786
+ }).session;
1787
+ try {
1788
+ writeTerminalInputChunks(currentSession?.pty, '\r');
1789
+ if (currentSession) currentSession.updatedAt = Date.now();
1790
+ } catch (error) {
1791
+ console.warn('Deferred terminal submit failed:', error?.message || error);
1792
+ }
1793
+ }, 120);
1794
+ }
1769
1795
  matchedSession.updatedAt = Date.now();
1770
1796
  res.json({
1771
1797
  ok: true,
@@ -1775,6 +1801,7 @@ app.post('/api/shell/sessions/provider-input', authenticateToken, requireProject
1775
1801
  tabId: matchedSession.tabId || null,
1776
1802
  wrote: true,
1777
1803
  submitted: submit,
1804
+ submitMode,
1778
1805
  bytes: Buffer.byteLength(data),
1779
1806
  matchStatus: match.status,
1780
1807
  outputCursorBefore,
@@ -36,6 +36,8 @@ const TERMINAL_BRIDGE_TIMEOUT_MS = 5 * 60 * 1000;
36
36
  const TERMINAL_BRIDGE_POLL_MS = 1500;
37
37
  const TERMINAL_BRIDGE_EDIT_THROTTLE_MS = 3500;
38
38
  const TERMINAL_BRIDGE_SETTLE_MS = 6000;
39
+ const TERMINAL_BRIDGE_FINAL_OPEN = '<PIXCODE_TELEGRAM_FINAL>';
40
+ const TERMINAL_BRIDGE_FINAL_CLOSE = '</PIXCODE_TELEGRAM_FINAL>';
39
41
  const callbackActions = new Map();
40
42
  const runMonitors = new Map();
41
43
  const activeLongTasks = new Map();
@@ -637,15 +639,89 @@ function terminalBridgeMonitorKey(chatId, terminal) {
637
639
  ].join(':');
638
640
  }
639
641
 
642
+ function escapeRegExp(value) {
643
+ return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
644
+ }
645
+
646
+ function terminalBridgeInput(text, lang) {
647
+ const prompt = String(text || '').trim();
648
+ const instruction = lang === 'tr'
649
+ ? `Pixcode Telegram senkronu: Yanıtının sonunda Telegram için okunabilir ve eksiksiz final cevabını ${TERMINAL_BRIDGE_FINAL_OPEN} ve ${TERMINAL_BRIDGE_FINAL_CLOSE} etiketleri arasına yaz. Etiketlerin içine spinner, terminal ekranı veya tekrar eden durum satırı koyma.`
650
+ : `Pixcode Telegram sync: At the end of your response, write the readable complete final answer for Telegram between ${TERMINAL_BRIDGE_FINAL_OPEN} and ${TERMINAL_BRIDGE_FINAL_CLOSE}. Do not put spinners, terminal screen text, or repeated status lines inside the tags.`;
651
+ return `${prompt}\n\n${instruction}`;
652
+ }
653
+
640
654
  function normalizeBridgeLine(value) {
641
655
  return String(value || '').replace(/\s+/g, ' ').trim();
642
656
  }
643
657
 
644
- function isNoisyTerminalBridgeLine(line, prompt) {
658
+ function terminalBridgeLabels(terminal) {
659
+ const labels = new Set();
660
+ for (const value of [
661
+ terminal?.provider,
662
+ terminal?.projectName,
663
+ terminal?.projectLabel,
664
+ terminal?.projectPath,
665
+ ]) {
666
+ const normalized = normalizeBridgeLine(value);
667
+ if (!normalized) continue;
668
+ labels.add(normalized);
669
+ const pathParts = normalized.split(/[\\/]/u).filter(Boolean);
670
+ const basename = pathParts.at(-1);
671
+ if (basename) labels.add(basename);
672
+ }
673
+ return [...labels]
674
+ .map((value) => value.trim())
675
+ .filter((value) => value.length >= 2)
676
+ .sort((a, b) => b.length - a.length);
677
+ }
678
+
679
+ function stripTerminalBridgeFragments(value, terminal) {
680
+ let text = String(value || '');
681
+ text = text.replace(/\[[0-?]{1,32}[ -/]*[@-~]/gu, ' ');
682
+ text = text.replace(/\b\d{1,3}(?:;\d{1,3}){1,8}[A-Za-z]/gu, ' ');
683
+ text = text.replace(/\b[012];[^\n\r]{0,100}[⠂⠐⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏✶✻✽✢✳][^\n\r]{0,100}/gu, ' ');
684
+
685
+ for (const label of terminalBridgeLabels(terminal)) {
686
+ const labelPattern = escapeRegExp(label).replace(/\s+/gu, '\\s+');
687
+ const titlePattern = new RegExp(
688
+ String.raw`\b(?:\d+[a-z])?[012];[⠂⠐⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏✶✻✽✢✳•*·\s-]*${labelPattern}(?:\d+;?)?`,
689
+ 'giu',
690
+ );
691
+ text = text.replace(titlePattern, ' ');
692
+ }
693
+
694
+ return text
695
+ .replace(/[⠂⠐⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏✶✻✽✢✳]+/gu, ' ')
696
+ .replace(/\b(?:Working|Determining|Thinking|Running)(?:\s*•\s*(?:Working|Determining|Thinking|Running))+\b/giu, ' ');
697
+ }
698
+
699
+ function extractTerminalBridgeFinalBlock(text) {
700
+ const blocks = [];
701
+ const blockPattern = /(?:<|\[)\s*PIXCODE_TELEGRAM_FINAL\s*(?:>|\])([\s\S]*?)(?:<|\[)\s*\/\s*PIXCODE_TELEGRAM_FINAL\s*(?:>|\])/giu;
702
+ for (const match of text.matchAll(blockPattern)) {
703
+ const block = String(match[1] || '').trim();
704
+ if (block) blocks.push(block);
705
+ }
706
+ if (blocks.length > 0) return blocks.at(-1);
707
+
708
+ const openPattern = /(?:<|\[)\s*PIXCODE_TELEGRAM_FINAL\s*(?:>|\])/giu;
709
+ let lastOpen = null;
710
+ for (const match of text.matchAll(openPattern)) lastOpen = match;
711
+ if (!lastOpen) return '';
712
+ return text.slice((lastOpen.index || 0) + lastOpen[0].length).trim();
713
+ }
714
+
715
+ function isNoisyTerminalBridgeLine(line, prompt, terminal) {
645
716
  const normalized = normalizeBridgeLine(line);
646
717
  if (!normalized || normalized.length <= 1) return true;
718
+ if (/PIXCODE_TELEGRAM_FINAL/iu.test(normalized)) return true;
719
+ if (/Pixcode Telegram (?:sync|senkronu)/iu.test(normalized)) return true;
647
720
  if (/^[╭╮╰╯│─═┌┐└┘├┤┬┴┼\s]+$/u.test(normalized)) return true;
648
721
  if (/^[●✶✻✽✢✳⠂⠐⏵·\s0;:()\-|/\\]+$/u.test(normalized)) return true;
722
+ if (/^\[[0-?]{1,32}[ -/]*[@-~]/u.test(normalized)) return true;
723
+ if (/^(?:\d+[a-z])?[012];/iu.test(normalized) && /[⠂⠐⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏✶✻✽✢✳]/u.test(normalized)) return true;
724
+ if ((normalized.match(/[⠂⠐⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏✶✻✽✢✳]/gu) || []).length >= 2) return true;
649
725
 
650
726
  const lower = normalized.toLowerCase();
651
727
  const promptNeedle = normalizeBridgeLine(prompt).toLowerCase();
@@ -660,18 +736,28 @@ function isNoisyTerminalBridgeLine(line, prompt) {
660
736
  if (lower.includes('determining')) return true;
661
737
  if (/^(?:claude code|codex)\b/i.test(normalized) && normalized.length < 80) return true;
662
738
  if (/^\(?\d+s\s*·\s*[↓↑]?\d*\s*tokens?\)?$/iu.test(normalized)) return true;
739
+ for (const label of terminalBridgeLabels(terminal)) {
740
+ if (
741
+ lower.includes(label.toLowerCase())
742
+ && /(?:^[012];|[⠂⠐⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏✶✻✽✢✳]|working|determining)/iu.test(normalized)
743
+ ) {
744
+ return true;
745
+ }
746
+ }
663
747
  return false;
664
748
  }
665
749
 
666
- function cleanTerminalBridgeOutput(output, prompt) {
667
- const text = String(output || '')
750
+ export function cleanTerminalBridgeOutput(output, prompt, terminal = null) {
751
+ const text = stripTerminalBridgeFragments(String(output || ''), terminal)
668
752
  .replace(/\r/g, '\n')
669
753
  .replace(/[\x00-\x08\x0B-\x1F\x7F]/g, '')
670
754
  .replace(/\u00a0/g, ' ');
671
- const lines = text
755
+ const finalBlock = extractTerminalBridgeFinalBlock(text);
756
+ const source = finalBlock || text;
757
+ const lines = source
672
758
  .split('\n')
673
759
  .map((line) => line.replace(/[ \t]+/g, ' ').trim())
674
- .filter((line) => !isNoisyTerminalBridgeLine(line, prompt));
760
+ .filter((line) => !isNoisyTerminalBridgeLine(line, prompt, terminal));
675
761
  const deduped = [];
676
762
  for (const line of lines) {
677
763
  if (deduped.at(-1) === line) continue;
@@ -696,12 +782,12 @@ function renderTerminalBridgeProgress(lang, terminal, {
696
782
  if (terminal.sessionId) {
697
783
  lines.push(`🧵 ${t(lang, 'control.activity.session')}: ${terminal.sessionId}`);
698
784
  }
699
- if (terminalState) {
785
+ if (terminalState && terminalState !== 'unknown') {
700
786
  lines.push(`📌 ${t(lang, 'control.activity.status')}: ${terminalState}`);
701
787
  }
702
788
  if (output) {
703
789
  lines.push('', `💬 ${t(lang, 'control.activity.output')}:`);
704
- lines.push(truncate(output, final ? 2800 : 2200));
790
+ lines.push(truncate(output, final ? 3100 : 2200));
705
791
  } else {
706
792
  lines.push('', t(lang, 'control.terminalWaitingHint'));
707
793
  }
@@ -754,7 +840,7 @@ async function monitorTerminalBridgeResponse({
754
840
  });
755
841
  return;
756
842
  }
757
- const cleanOutput = cleanTerminalBridgeOutput(data?.output, prompt);
843
+ const cleanOutput = cleanTerminalBridgeOutput(data?.output, prompt, terminal);
758
844
  const terminalState = data?.terminalState || data?.lifecycleState || 'unknown';
759
845
  const now = Date.now();
760
846
 
@@ -888,6 +974,7 @@ async function sendToActiveTerminal({ bot, chatId, link, text }) {
888
974
  const editMessageId = sent?.message_id || sent?.message?.message_id || null;
889
975
 
890
976
  try {
977
+ const terminalPrompt = terminalBridgeInput(text, lang);
891
978
  const inputResult = await localApi(link.user_id, '/api/shell/sessions/provider-input', {
892
979
  method: 'POST',
893
980
  timeoutMs: 15_000,
@@ -896,8 +983,9 @@ async function sendToActiveTerminal({ bot, chatId, link, text }) {
896
983
  projectPath: terminal.projectPath,
897
984
  tabId: terminal.tabId,
898
985
  sessionId: terminal.sessionId,
899
- input: text,
986
+ input: terminalPrompt,
900
987
  submit: true,
988
+ submitMode: 'deferred-enter',
901
989
  },
902
990
  });
903
991
  const sinceCursor = Number.isFinite(inputResult?.outputCursorBefore)
@@ -921,7 +1009,7 @@ async function sendToActiveTerminal({ bot, chatId, link, text }) {
921
1009
  chatId,
922
1010
  link,
923
1011
  terminal,
924
- prompt: text,
1012
+ prompt: terminalPrompt,
925
1013
  sinceCursor,
926
1014
  editMessageId,
927
1015
  monitorKey,
@@ -1,4 +1,4 @@
1
- const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
1
+ const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1B\r\n]*(?:\x07|\x1B\\|$)|P[\s\S]*?(?:\x1B\\|$)|\^[\s\S]*?(?:\x1B\\|$)|_[\s\S]*?(?:\x1B\\|$)|[@-Z\\-_])|\x9B[0-?]*[ -/]*[@-~]/g;
2
2
  const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
3
3
 
4
4
  function stripAnsiSequences(value = '') {