@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/dist/assets/{index-CV_x2mrI.js → index-BgMvphaU.js} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/server/index.js +29 -1
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +96 -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 +1 -1
- package/scripts/smoke/telegram-control.mjs +41 -0
- package/server/index.js +28 -1
- package/server/services/telegram/control-center.js +98 -10
- package/server/utils/url-detection.js +1 -1
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
|
|
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
|
|
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,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:
|
|
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:
|
|
1012
|
+
prompt: terminalPrompt,
|
|
925
1013
|
sinceCursor,
|
|
926
1014
|
editMessageId,
|
|
927
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 = '') {
|