@pixelbyte-software/pixcode 1.53.12 → 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/dist/assets/{index-kP_gZ-wL.js → index-CV_x2mrI.js} +173 -173
- package/dist/index.html +1 -1
- package/dist-server/server/index.js +51 -5
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/routes/auth.js +41 -1
- package/dist-server/server/routes/auth.js.map +1 -1
- package/dist-server/server/routes/git.js +60 -3
- package/dist-server/server/routes/git.js.map +1 -1
- package/dist-server/server/routes/network.js +17 -1
- package/dist-server/server/routes/network.js.map +1 -1
- package/dist-server/server/services/external-access.js +45 -21
- package/dist-server/server/services/external-access.js.map +1 -1
- package/dist-server/server/services/qr-login.js +115 -0
- package/dist-server/server/services/qr-login.js.map +1 -0
- package/dist-server/server/services/telegram/bot.js +18 -1
- package/dist-server/server/services/telegram/bot.js.map +1 -1
- package/dist-server/server/services/telegram/control-center.js +225 -9
- package/dist-server/server/services/telegram/control-center.js.map +1 -1
- package/dist-server/server/services/telegram/translations.js +22 -2
- package/dist-server/server/services/telegram/translations.js.map +1 -1
- package/package.json +1 -1
- package/server/index.js +59 -5
- package/server/routes/auth.js +47 -1
- package/server/routes/git.js +70 -3
- package/server/routes/network.js +18 -1
- package/server/services/external-access.js +49 -21
- package/server/services/qr-login.js +126 -0
- package/server/services/telegram/bot.js +15 -0
- package/server/services/telegram/control-center.js +241 -9
- package/server/services/telegram/translations.js +22 -2
|
@@ -14,9 +14,14 @@ const MAX_SSE_BUFFER_CHARS = 256_000;
|
|
|
14
14
|
const ACTIVITY_EDIT_THROTTLE_MS = 1200;
|
|
15
15
|
const ACTIVITY_HEARTBEAT_MS = 8000;
|
|
16
16
|
const INTENT_ROUTER_TIMEOUT_MS = 45_000;
|
|
17
|
+
const TERMINAL_BRIDGE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
18
|
+
const TERMINAL_BRIDGE_POLL_MS = 1500;
|
|
19
|
+
const TERMINAL_BRIDGE_EDIT_THROTTLE_MS = 3500;
|
|
20
|
+
const TERMINAL_BRIDGE_SETTLE_MS = 6000;
|
|
17
21
|
const callbackActions = new Map();
|
|
18
22
|
const runMonitors = new Map();
|
|
19
23
|
const activeLongTasks = new Map();
|
|
24
|
+
const terminalBridgeMonitors = new Map();
|
|
20
25
|
const MODEL_FALLBACKS = Object.fromEntries(PROVIDERS.map((provider) => [provider, getStaticProviderModels(provider)]));
|
|
21
26
|
const AUTH_HELP = {
|
|
22
27
|
claude: '`claude login`',
|
|
@@ -534,7 +539,7 @@ function getActiveTerminal(state) {
|
|
|
534
539
|
function terminalProjectLabel(terminal) {
|
|
535
540
|
return terminal?.projectLabel || terminal?.projectName || terminal?.projectPath || '-';
|
|
536
541
|
}
|
|
537
|
-
function terminalOutputUrl(terminal, maxChars = 3200) {
|
|
542
|
+
function terminalOutputUrl(terminal, maxChars = 3200, sinceCursor = null) {
|
|
538
543
|
const params = new URLSearchParams({
|
|
539
544
|
provider: terminal.provider,
|
|
540
545
|
projectPath: terminal.projectPath,
|
|
@@ -544,9 +549,11 @@ function terminalOutputUrl(terminal, maxChars = 3200) {
|
|
|
544
549
|
params.set('tabId', terminal.tabId);
|
|
545
550
|
if (terminal.sessionId)
|
|
546
551
|
params.set('sessionId', terminal.sessionId);
|
|
552
|
+
if (Number.isFinite(sinceCursor))
|
|
553
|
+
params.set('sinceCursor', String(sinceCursor));
|
|
547
554
|
return `/api/shell/sessions/provider-output?${params.toString()}`;
|
|
548
555
|
}
|
|
549
|
-
function renderTerminalSnapshot(lang, terminal, data, { prefix = '' } = {}) {
|
|
556
|
+
function renderTerminalSnapshot(lang, terminal, data, { prefix = '', includeOutput = false } = {}) {
|
|
550
557
|
const active = data?.active !== false;
|
|
551
558
|
const lifecycle = data?.terminalState || data?.lifecycleState || (active ? 'running' : 'not running');
|
|
552
559
|
const output = String(data?.output || '').trim();
|
|
@@ -560,15 +567,205 @@ function renderTerminalSnapshot(lang, terminal, data, { prefix = '' } = {}) {
|
|
|
560
567
|
if (terminal.sessionId || data?.sessionId) {
|
|
561
568
|
lines.push(`🧵 ${t(lang, 'control.activity.session')}: ${terminal.sessionId || data.sessionId}`);
|
|
562
569
|
}
|
|
563
|
-
if (output) {
|
|
570
|
+
if (includeOutput && output) {
|
|
564
571
|
lines.push('', `💬 ${t(lang, 'control.activity.output')}:`);
|
|
565
572
|
lines.push(truncate(output, 2400));
|
|
566
573
|
}
|
|
567
574
|
else {
|
|
568
|
-
lines.push('', t(lang, 'control.
|
|
575
|
+
lines.push('', t(lang, 'control.terminalOutputHidden'));
|
|
576
|
+
}
|
|
577
|
+
return truncate(lines.join('\n'), 3400);
|
|
578
|
+
}
|
|
579
|
+
function terminalBridgeMonitorKey(chatId, terminal) {
|
|
580
|
+
return [
|
|
581
|
+
chatId,
|
|
582
|
+
terminal.provider,
|
|
583
|
+
terminal.projectPath,
|
|
584
|
+
terminal.tabId || '-',
|
|
585
|
+
terminal.sessionId || '-',
|
|
586
|
+
].join(':');
|
|
587
|
+
}
|
|
588
|
+
function normalizeBridgeLine(value) {
|
|
589
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
590
|
+
}
|
|
591
|
+
function isNoisyTerminalBridgeLine(line, prompt) {
|
|
592
|
+
const normalized = normalizeBridgeLine(line);
|
|
593
|
+
if (!normalized || normalized.length <= 1)
|
|
594
|
+
return true;
|
|
595
|
+
if (/^[╭╮╰╯│─═┌┐└┘├┤┬┴┼\s]+$/u.test(normalized))
|
|
596
|
+
return true;
|
|
597
|
+
if (/^[●✶✻✽✢✳⠂⠐⏵·\s0;:()\-|/\\]+$/u.test(normalized))
|
|
598
|
+
return true;
|
|
599
|
+
const lower = normalized.toLowerCase();
|
|
600
|
+
const promptNeedle = normalizeBridgeLine(prompt).toLowerCase();
|
|
601
|
+
if (promptNeedle && lower.includes(promptNeedle.slice(0, 120)))
|
|
602
|
+
return true;
|
|
603
|
+
if (/^[›❯>]\s*/u.test(normalized))
|
|
604
|
+
return true;
|
|
605
|
+
if (lower.includes('welcome back'))
|
|
606
|
+
return true;
|
|
607
|
+
if (lower.includes('api usage billing'))
|
|
608
|
+
return true;
|
|
609
|
+
if (lower.includes('bypass permissions'))
|
|
610
|
+
return true;
|
|
611
|
+
if (lower.includes('try "'))
|
|
612
|
+
return true;
|
|
613
|
+
if (lower.includes('esc to interrupt') || lower.includes('press esc'))
|
|
614
|
+
return true;
|
|
615
|
+
if (lower.includes('/effort'))
|
|
616
|
+
return true;
|
|
617
|
+
if (lower.includes('determining'))
|
|
618
|
+
return true;
|
|
619
|
+
if (/^(?:claude code|codex)\b/i.test(normalized) && normalized.length < 80)
|
|
620
|
+
return true;
|
|
621
|
+
if (/^\(?\d+s\s*·\s*[↓↑]?\d*\s*tokens?\)?$/iu.test(normalized))
|
|
622
|
+
return true;
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
function cleanTerminalBridgeOutput(output, prompt) {
|
|
626
|
+
const text = String(output || '')
|
|
627
|
+
.replace(/\r/g, '\n')
|
|
628
|
+
.replace(/[\x00-\x08\x0B-\x1F\x7F]/g, '')
|
|
629
|
+
.replace(/\u00a0/g, ' ');
|
|
630
|
+
const lines = text
|
|
631
|
+
.split('\n')
|
|
632
|
+
.map((line) => line.replace(/[ \t]+/g, ' ').trim())
|
|
633
|
+
.filter((line) => !isNoisyTerminalBridgeLine(line, prompt));
|
|
634
|
+
const deduped = [];
|
|
635
|
+
for (const line of lines) {
|
|
636
|
+
if (deduped.at(-1) === line)
|
|
637
|
+
continue;
|
|
638
|
+
deduped.push(line);
|
|
639
|
+
}
|
|
640
|
+
return deduped.join('\n').trim();
|
|
641
|
+
}
|
|
642
|
+
function renderTerminalBridgeProgress(lang, terminal, { output = '', statusKey = 'control.terminalWaiting', startedAt = Date.now(), final = false, terminalState = null, } = {}) {
|
|
643
|
+
const lines = [
|
|
644
|
+
`${final ? '✅' : '⏳'} ${t(lang, statusKey)}`,
|
|
645
|
+
'',
|
|
646
|
+
`🤖 ${t(lang, 'control.activity.provider')}: ${terminal.provider}`,
|
|
647
|
+
`📁 ${t(lang, 'control.activity.project')}: ${compact(terminalProjectLabel(terminal), 90)}`,
|
|
648
|
+
];
|
|
649
|
+
if (terminal.sessionId) {
|
|
650
|
+
lines.push(`🧵 ${t(lang, 'control.activity.session')}: ${terminal.sessionId}`);
|
|
651
|
+
}
|
|
652
|
+
if (terminalState) {
|
|
653
|
+
lines.push(`📌 ${t(lang, 'control.activity.status')}: ${terminalState}`);
|
|
654
|
+
}
|
|
655
|
+
if (output) {
|
|
656
|
+
lines.push('', `💬 ${t(lang, 'control.activity.output')}:`);
|
|
657
|
+
lines.push(truncate(output, final ? 2800 : 2200));
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
lines.push('', t(lang, 'control.terminalWaitingHint'));
|
|
661
|
+
}
|
|
662
|
+
if (!final) {
|
|
663
|
+
lines.push('', `⏱ ${t(lang, 'control.activity.elapsed')}: ${formatElapsed(startedAt)}`);
|
|
569
664
|
}
|
|
570
665
|
return truncate(lines.join('\n'), 3400);
|
|
571
666
|
}
|
|
667
|
+
async function monitorTerminalBridgeResponse({ bot, chatId, link, terminal, prompt, sinceCursor, editMessageId, monitorKey, monitorToken, }) {
|
|
668
|
+
const lang = languageFor(link);
|
|
669
|
+
const startedAt = Date.now();
|
|
670
|
+
let lastCleanOutput = '';
|
|
671
|
+
let lastOutputChangeAt = startedAt;
|
|
672
|
+
let lastEditAt = 0;
|
|
673
|
+
const isCurrent = () => terminalBridgeMonitors.get(monitorKey) === monitorToken;
|
|
674
|
+
try {
|
|
675
|
+
while (isCurrent() && Date.now() - startedAt < TERMINAL_BRIDGE_TIMEOUT_MS) {
|
|
676
|
+
await new Promise((resolve) => setTimeout(resolve, TERMINAL_BRIDGE_POLL_MS));
|
|
677
|
+
if (!isCurrent())
|
|
678
|
+
return;
|
|
679
|
+
const data = await localApi(link.user_id, terminalOutputUrl(terminal, 12000, sinceCursor), { timeoutMs: 12_000 });
|
|
680
|
+
if (data?.active === false) {
|
|
681
|
+
await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
|
|
682
|
+
output: lastCleanOutput,
|
|
683
|
+
statusKey: 'control.terminalNotRunning',
|
|
684
|
+
startedAt,
|
|
685
|
+
final: true,
|
|
686
|
+
terminalState: 'not running',
|
|
687
|
+
}), {
|
|
688
|
+
editMessageId,
|
|
689
|
+
parse_mode: undefined,
|
|
690
|
+
reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
|
|
691
|
+
});
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const cleanOutput = cleanTerminalBridgeOutput(data?.output, prompt);
|
|
695
|
+
const terminalState = data?.terminalState || data?.lifecycleState || 'unknown';
|
|
696
|
+
const now = Date.now();
|
|
697
|
+
if (cleanOutput && cleanOutput !== lastCleanOutput) {
|
|
698
|
+
lastCleanOutput = cleanOutput;
|
|
699
|
+
lastOutputChangeAt = now;
|
|
700
|
+
}
|
|
701
|
+
const finishedByState = ['idle', 'completed', 'failed', 'exited'].includes(terminalState);
|
|
702
|
+
const finishedByQuietOutput = Boolean(lastCleanOutput) && now - lastOutputChangeAt >= TERMINAL_BRIDGE_SETTLE_MS;
|
|
703
|
+
const shouldFinish = Boolean(lastCleanOutput) && (finishedByState || finishedByQuietOutput);
|
|
704
|
+
if (shouldFinish) {
|
|
705
|
+
await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
|
|
706
|
+
output: lastCleanOutput,
|
|
707
|
+
statusKey: 'control.terminalResponseReady',
|
|
708
|
+
startedAt,
|
|
709
|
+
final: true,
|
|
710
|
+
terminalState,
|
|
711
|
+
}), {
|
|
712
|
+
editMessageId,
|
|
713
|
+
parse_mode: undefined,
|
|
714
|
+
reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
|
|
715
|
+
});
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (now - lastEditAt >= TERMINAL_BRIDGE_EDIT_THROTTLE_MS) {
|
|
719
|
+
lastEditAt = now;
|
|
720
|
+
await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
|
|
721
|
+
output: lastCleanOutput,
|
|
722
|
+
statusKey: lastCleanOutput ? 'control.terminalResponding' : 'control.terminalWaiting',
|
|
723
|
+
startedAt,
|
|
724
|
+
terminalState,
|
|
725
|
+
}), {
|
|
726
|
+
editMessageId,
|
|
727
|
+
parse_mode: undefined,
|
|
728
|
+
reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (!isCurrent())
|
|
733
|
+
return;
|
|
734
|
+
await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
|
|
735
|
+
output: lastCleanOutput,
|
|
736
|
+
statusKey: lastCleanOutput ? 'control.terminalStillRunning' : 'control.terminalNoReadableOutput',
|
|
737
|
+
startedAt,
|
|
738
|
+
final: Boolean(lastCleanOutput),
|
|
739
|
+
terminalState: 'running',
|
|
740
|
+
}), {
|
|
741
|
+
editMessageId,
|
|
742
|
+
parse_mode: undefined,
|
|
743
|
+
reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
finally {
|
|
747
|
+
if (isCurrent())
|
|
748
|
+
terminalBridgeMonitors.delete(monitorKey);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
export async function sendActiveTerminalAttachedNotice({ bot, chatId, link, terminal }) {
|
|
752
|
+
const lang = languageFor(link);
|
|
753
|
+
const lines = [
|
|
754
|
+
t(lang, 'control.terminalAttached'),
|
|
755
|
+
'',
|
|
756
|
+
`🤖 ${t(lang, 'control.activity.provider')}: ${terminal.provider}`,
|
|
757
|
+
`📁 ${t(lang, 'control.activity.project')}: ${compact(terminalProjectLabel(terminal), 90)}`,
|
|
758
|
+
`📌 ${t(lang, 'control.activity.status')}: ${t(lang, 'control.terminalReadyStatus')}`,
|
|
759
|
+
];
|
|
760
|
+
if (terminal.sessionId) {
|
|
761
|
+
lines.push(`🧵 ${t(lang, 'control.activity.session')}: ${terminal.sessionId}`);
|
|
762
|
+
}
|
|
763
|
+
lines.push('', t(lang, 'control.terminalReadyPrompt'));
|
|
764
|
+
await send(bot, chatId, truncate(lines.join('\n'), 3400), {
|
|
765
|
+
parse_mode: undefined,
|
|
766
|
+
reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
|
|
767
|
+
});
|
|
768
|
+
}
|
|
572
769
|
async function showActiveTerminalStatus({ bot, chatId, link, editMessageId }) {
|
|
573
770
|
const lang = languageFor(link);
|
|
574
771
|
const state = getState(link.user_id);
|
|
@@ -622,7 +819,7 @@ async function sendToActiveTerminal({ bot, chatId, link, text }) {
|
|
|
622
819
|
});
|
|
623
820
|
const editMessageId = sent?.message_id || sent?.message?.message_id || null;
|
|
624
821
|
try {
|
|
625
|
-
await localApi(link.user_id, '/api/shell/sessions/provider-input', {
|
|
822
|
+
const inputResult = await localApi(link.user_id, '/api/shell/sessions/provider-input', {
|
|
626
823
|
method: 'POST',
|
|
627
824
|
timeoutMs: 15_000,
|
|
628
825
|
body: {
|
|
@@ -634,15 +831,34 @@ async function sendToActiveTerminal({ bot, chatId, link, text }) {
|
|
|
634
831
|
submit: true,
|
|
635
832
|
},
|
|
636
833
|
});
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
834
|
+
const sinceCursor = Number.isFinite(inputResult?.outputCursorBefore)
|
|
835
|
+
? inputResult.outputCursorBefore
|
|
836
|
+
: null;
|
|
837
|
+
const monitorKey = terminalBridgeMonitorKey(chatId, terminal);
|
|
838
|
+
const monitorToken = crypto.randomUUID();
|
|
839
|
+
terminalBridgeMonitors.set(monitorKey, monitorToken);
|
|
840
|
+
await send(bot, chatId, renderTerminalBridgeProgress(lang, terminal, {
|
|
841
|
+
statusKey: 'control.terminalWaiting',
|
|
842
|
+
startedAt: Date.now(),
|
|
843
|
+
terminalState: 'running',
|
|
641
844
|
}), {
|
|
642
845
|
editMessageId,
|
|
643
846
|
parse_mode: undefined,
|
|
644
847
|
reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
|
|
645
848
|
});
|
|
849
|
+
monitorTerminalBridgeResponse({
|
|
850
|
+
bot,
|
|
851
|
+
chatId,
|
|
852
|
+
link,
|
|
853
|
+
terminal,
|
|
854
|
+
prompt: text,
|
|
855
|
+
sinceCursor,
|
|
856
|
+
editMessageId,
|
|
857
|
+
monitorKey,
|
|
858
|
+
monitorToken,
|
|
859
|
+
}).catch((error) => {
|
|
860
|
+
console.warn('[telegram-control] terminal bridge monitor failed:', error?.message || error);
|
|
861
|
+
});
|
|
646
862
|
}
|
|
647
863
|
catch (error) {
|
|
648
864
|
await send(bot, chatId, t(lang, 'control.terminalSendFailed', {
|