@pixelbyte-software/pixcode 1.53.15 → 1.53.17

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.
@@ -113,6 +113,7 @@ try {
113
113
  parseTelegramAiIntentResponse,
114
114
  } = await import('../../server/services/telegram/telegram-gateway.js');
115
115
  const {
116
+ cleanTerminalBridgeOutput,
116
117
  handleTelegramControlCallback,
117
118
  handleTelegramControlMessage,
118
119
  } = await import('../../server/services/telegram/control-center.js');
@@ -121,6 +122,7 @@ try {
121
122
  setTelegramBotForTesting,
122
123
  } = await import('../../server/services/telegram/bot.js');
123
124
  const { telegramLinksDb } = await import('../../server/database/db.js');
125
+ const { stripAnsiSequences } = await import('../../server/utils/url-detection.js');
124
126
 
125
127
  assert.equal(
126
128
  parseTelegramAiIntentResponse('{"action":"show_runs","confidence":0.4}', 'Selam dostum son durum nedir şimdi?').action,
@@ -137,6 +139,45 @@ try {
137
139
  'agent_prompt',
138
140
  'unparseable AI router responses should fall back to the agent',
139
141
  );
142
+ assert.equal(stripAnsiSequences('\u001b[43;35Hok'), 'ok', 'CSI escape sequences should be fully stripped');
143
+ assert.equal(stripAnsiSequences('\u001b]0;orhan\u0007ok'), 'ok', 'OSC title sequences should be fully stripped');
144
+
145
+ const terminal = {
146
+ provider: 'codex',
147
+ projectName: 'orhan',
148
+ projectLabel: 'orhan',
149
+ projectPath: '/Users/halilbilik/Desktop/orhan',
150
+ };
151
+ assert.equal(
152
+ cleanTerminalBridgeOutput(
153
+ '[43;35H40;⠸ orhan0;⠼ orhan0;⠴ orhan0;⠦ orhan0;⠧ orhan0;⠇ orhan0;⠏ orhanW0;⠋ orhanWo•Wor0;⠙ orhan•Work0;⠹ orhanWorki•Workin0;⠸ orhan5•Working',
154
+ 'pm2 kontrol',
155
+ terminal,
156
+ ),
157
+ '',
158
+ 'terminal bridge should suppress spinner/title redraw noise',
159
+ );
160
+ assert.equal(
161
+ cleanTerminalBridgeOutput(
162
+ [
163
+ '9m0;⠇ orhan0;⠏ orhan',
164
+ '- trade-bot: stopped, PID 00;⠋ orhan0;⠙ orhan0;⠹ orhan',
165
+ '<PIXCODE_TELEGRAM_FINAL>',
166
+ 'Evet, durdurmuşsun. Sunucuda PM2 durumu şu an:',
167
+ '- trade-bot: stopped, PID 0',
168
+ '- zeroclaw-gw: stopped, PID 0',
169
+ '</PIXCODE_TELEGRAM_FINAL>',
170
+ ].join('\n'),
171
+ 'pm2 kontrol',
172
+ terminal,
173
+ ),
174
+ [
175
+ 'Evet, durdurmuşsun. Sunucuda PM2 durumu şu an:',
176
+ '- trade-bot: stopped, PID 0',
177
+ '- zeroclaw-gw: stopped, PID 0',
178
+ ].join('\n'),
179
+ 'terminal bridge should prefer explicit Telegram final blocks',
180
+ );
140
181
 
141
182
  const userId = 4242;
142
183
  telegramLinksDb.unlink(userId);
@@ -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,7 +983,7 @@ 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,
901
988
  submitMode: 'deferred-enter',
902
989
  },
@@ -922,7 +1009,7 @@ async function sendToActiveTerminal({ bot, chatId, link, text }) {
922
1009
  chatId,
923
1010
  link,
924
1011
  terminal,
925
- prompt: text,
1012
+ prompt: terminalPrompt,
926
1013
  sinceCursor,
927
1014
  editMessageId,
928
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 = '') {