@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.
- package/dist/assets/{index-BgMvphaU.js → index-ByaiDQ05.js} +180 -185
- package/dist/assets/index-nJQ4Q6hZ.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/services/telegram/control-center.js +95 -10
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/utils/url-detection.js +1 -1
- package/dist-server/server/utils/url-detection.js.map +1 -1
- package/package.json +2 -1
- package/scripts/smoke/mobile-ux.mjs +50 -0
- package/scripts/smoke/telegram-control.mjs +41 -0
- package/server/services/telegram/control-center.js +97 -10
- package/server/utils/url-detection.js +1 -1
- package/dist/assets/index-BEyC4q_s.css +0 -32
|
@@ -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
|
|
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
|
|
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 ?
|
|
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:
|
|
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:
|
|
1012
|
+
prompt: terminalPrompt,
|
|
926
1013
|
sinceCursor,
|
|
927
1014
|
editMessageId,
|
|
928
1015
|
monitorKey,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(
|
|
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 = '') {
|