@pixelbyte-software/pixcode 1.53.11 โ†’ 1.53.12

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.
@@ -52,6 +52,8 @@ const CONTROL_COMMANDS = new Set([
52
52
  '/workflow',
53
53
  '/orchestrate',
54
54
  '/cancel',
55
+ '/terminal',
56
+ '/detach',
55
57
  'menu',
56
58
  ]);
57
59
  function compact(text, max = 80) {
@@ -466,15 +468,25 @@ function startActivityHeartbeat({ bot, chatId, activity }) {
466
468
  }, ACTIVITY_HEARTBEAT_MS);
467
469
  }
468
470
  function stateSummary(lang, state) {
469
- return [
471
+ const lines = [
470
472
  `${t(lang, 'control.summary.project')}: ${state.selectedProjectName || t(lang, 'control.notSelected')}`,
471
473
  `${t(lang, 'control.summary.provider')}: ${state.selectedProvider}${state.selectedModel ? ` / ${state.selectedModel}` : ''}`,
472
474
  `${t(lang, 'control.summary.workflow')}: ${state.selectedWorkflowId || t(lang, 'control.notSelected')}`,
473
475
  `${t(lang, 'control.summary.progress')}: ${state.progressMode}`,
474
- ].join('\n');
476
+ ];
477
+ if (state.activeTerminal) {
478
+ lines.push(`${t(lang, 'control.summary.terminal')}: ${state.activeTerminal.provider} / ${state.activeTerminal.projectLabel || state.activeTerminal.projectName || compact(state.activeTerminal.projectPath, 50)}`);
479
+ }
480
+ return lines.join('\n');
475
481
  }
476
- function mainMenuKeyboard(lang) {
482
+ function terminalControlKeyboard(lang) {
477
483
  return [
484
+ [button(t(lang, 'control.button.terminalRefresh'), 'terminal_status'), button(t(lang, 'control.button.detachTerminal'), 'detach_terminal')],
485
+ [button(t(lang, 'control.button.mainMenu'), 'menu')],
486
+ ];
487
+ }
488
+ function mainMenuKeyboard(lang, state = null) {
489
+ const keyboard = [
478
490
  [button(t(lang, 'control.button.projects'), 'projects'), button(t(lang, 'control.button.provider'), 'providers')],
479
491
  [button(t(lang, 'control.button.models'), 'models'), button(t(lang, 'control.button.workflows'), 'workflows')],
480
492
  [button(t(lang, 'control.button.runs'), 'runs'), button(t(lang, 'control.button.approvals'), 'approvals')],
@@ -483,6 +495,10 @@ function mainMenuKeyboard(lang) {
483
495
  [button(t(lang, 'control.button.install'), 'install_menu'), button(t(lang, 'control.button.auth'), 'auth_menu')],
484
496
  [button(t(lang, 'control.button.settings'), 'settings')],
485
497
  ];
498
+ if (state?.activeTerminal) {
499
+ keyboard.splice(1, 0, [button(t(lang, 'control.button.terminal'), 'terminal_status'), button(t(lang, 'control.button.detachTerminal'), 'detach_terminal')]);
500
+ }
501
+ return keyboard;
486
502
  }
487
503
  export async function showMainMenu({ bot, chatId, link, editMessageId, notice }) {
488
504
  const lang = languageFor(link);
@@ -490,7 +506,7 @@ export async function showMainMenu({ bot, chatId, link, editMessageId, notice })
490
506
  const prefix = notice ? `${notice}\n\n` : '';
491
507
  await send(bot, chatId, `${prefix}${t(lang, 'control.menu')}\n\n${stateSummary(lang, state)}`, {
492
508
  editMessageId,
493
- reply_markup: { inline_keyboard: mainMenuKeyboard(lang) },
509
+ reply_markup: { inline_keyboard: mainMenuKeyboard(lang, state) },
494
510
  });
495
511
  }
496
512
  async function showHelp({ bot, chatId, link }) {
@@ -502,8 +518,141 @@ async function showCommandPalette({ bot, chatId, link, editMessageId, unknown =
502
518
  const prefix = unknown ? `${t(lang, 'control.unknownCommand')}\n\n` : '';
503
519
  await send(bot, chatId, `${prefix}${t(lang, 'control.help')}\n\n${t(lang, 'control.examples')}`, {
504
520
  editMessageId,
505
- reply_markup: { inline_keyboard: mainMenuKeyboard(lang) },
521
+ reply_markup: { inline_keyboard: mainMenuKeyboard(lang, getState(link.user_id)) },
522
+ });
523
+ }
524
+ function getActiveTerminal(state) {
525
+ const terminal = state?.activeTerminal;
526
+ if (!terminal ||
527
+ !PROVIDERS.includes(terminal.provider) ||
528
+ typeof terminal.projectPath !== 'string' ||
529
+ !terminal.projectPath.trim()) {
530
+ return null;
531
+ }
532
+ return terminal;
533
+ }
534
+ function terminalProjectLabel(terminal) {
535
+ return terminal?.projectLabel || terminal?.projectName || terminal?.projectPath || '-';
536
+ }
537
+ function terminalOutputUrl(terminal, maxChars = 3200) {
538
+ const params = new URLSearchParams({
539
+ provider: terminal.provider,
540
+ projectPath: terminal.projectPath,
541
+ maxChars: String(maxChars),
542
+ });
543
+ if (terminal.tabId)
544
+ params.set('tabId', terminal.tabId);
545
+ if (terminal.sessionId)
546
+ params.set('sessionId', terminal.sessionId);
547
+ return `/api/shell/sessions/provider-output?${params.toString()}`;
548
+ }
549
+ function renderTerminalSnapshot(lang, terminal, data, { prefix = '' } = {}) {
550
+ const active = data?.active !== false;
551
+ const lifecycle = data?.terminalState || data?.lifecycleState || (active ? 'running' : 'not running');
552
+ const output = String(data?.output || '').trim();
553
+ const lines = [
554
+ prefix || t(lang, active ? 'control.terminalAttached' : 'control.terminalNotRunning'),
555
+ '',
556
+ `๐Ÿค– ${t(lang, 'control.activity.provider')}: ${terminal.provider}`,
557
+ `๐Ÿ“ ${t(lang, 'control.activity.project')}: ${compact(terminalProjectLabel(terminal), 90)}`,
558
+ `๐Ÿ“Œ ${t(lang, 'control.activity.status')}: ${lifecycle}`,
559
+ ];
560
+ if (terminal.sessionId || data?.sessionId) {
561
+ lines.push(`๐Ÿงต ${t(lang, 'control.activity.session')}: ${terminal.sessionId || data.sessionId}`);
562
+ }
563
+ if (output) {
564
+ lines.push('', `๐Ÿ’ฌ ${t(lang, 'control.activity.output')}:`);
565
+ lines.push(truncate(output, 2400));
566
+ }
567
+ else {
568
+ lines.push('', t(lang, 'control.terminalNoOutput'));
569
+ }
570
+ return truncate(lines.join('\n'), 3400);
571
+ }
572
+ async function showActiveTerminalStatus({ bot, chatId, link, editMessageId }) {
573
+ const lang = languageFor(link);
574
+ const state = getState(link.user_id);
575
+ const terminal = getActiveTerminal(state);
576
+ if (!terminal) {
577
+ await send(bot, chatId, t(lang, 'control.noActiveTerminal'), {
578
+ editMessageId,
579
+ reply_markup: { inline_keyboard: [[button(t(lang, 'control.button.mainMenu'), 'menu')]] },
580
+ });
581
+ return;
582
+ }
583
+ try {
584
+ const data = await localApi(link.user_id, terminalOutputUrl(terminal), { timeoutMs: 12_000 });
585
+ await send(bot, chatId, renderTerminalSnapshot(lang, terminal, data), {
586
+ editMessageId,
587
+ parse_mode: undefined,
588
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
589
+ });
590
+ }
591
+ catch (error) {
592
+ await send(bot, chatId, t(lang, 'control.terminalStatusFailed', { error: error?.message || String(error) }), {
593
+ editMessageId,
594
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
595
+ });
596
+ }
597
+ }
598
+ async function detachActiveTerminal({ bot, chatId, link, editMessageId }) {
599
+ const lang = languageFor(link);
600
+ updateTelegramControlState(link.user_id, { activeTerminal: null });
601
+ await send(bot, chatId, t(lang, 'control.terminalDetached'), {
602
+ editMessageId,
603
+ reply_markup: { inline_keyboard: [[button(t(lang, 'control.button.mainMenu'), 'menu')]] },
604
+ });
605
+ }
606
+ async function sendToActiveTerminal({ bot, chatId, link, text }) {
607
+ const lang = languageFor(link);
608
+ const state = getState(link.user_id);
609
+ const terminal = getActiveTerminal(state);
610
+ if (!terminal)
611
+ return false;
612
+ if (state.remoteControlEnabled === false) {
613
+ await send(bot, chatId, t(lang, 'control.disabled'));
614
+ return true;
615
+ }
616
+ const sent = await send(bot, chatId, t(lang, 'control.terminalSending', {
617
+ provider: terminal.provider,
618
+ project: terminalProjectLabel(terminal),
619
+ }), {
620
+ parse_mode: undefined,
621
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
506
622
  });
623
+ const editMessageId = sent?.message_id || sent?.message?.message_id || null;
624
+ try {
625
+ await localApi(link.user_id, '/api/shell/sessions/provider-input', {
626
+ method: 'POST',
627
+ timeoutMs: 15_000,
628
+ body: {
629
+ provider: terminal.provider,
630
+ projectPath: terminal.projectPath,
631
+ tabId: terminal.tabId,
632
+ sessionId: terminal.sessionId,
633
+ input: text,
634
+ submit: true,
635
+ },
636
+ });
637
+ await new Promise((resolve) => setTimeout(resolve, 700));
638
+ const data = await localApi(link.user_id, terminalOutputUrl(terminal), { timeoutMs: 12_000 });
639
+ await send(bot, chatId, renderTerminalSnapshot(lang, terminal, data, {
640
+ prefix: t(lang, 'control.terminalSent'),
641
+ }), {
642
+ editMessageId,
643
+ parse_mode: undefined,
644
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
645
+ });
646
+ }
647
+ catch (error) {
648
+ await send(bot, chatId, t(lang, 'control.terminalSendFailed', {
649
+ error: error?.message || String(error),
650
+ }), {
651
+ editMessageId,
652
+ reply_markup: { inline_keyboard: terminalControlKeyboard(lang) },
653
+ });
654
+ }
655
+ return true;
507
656
  }
508
657
  async function listProjects() {
509
658
  const projects = await getProjects();
@@ -1541,6 +1690,14 @@ async function handleCommand({ bot, chatId, link, text }) {
1541
1690
  await showSessions({ bot, chatId, link });
1542
1691
  return true;
1543
1692
  }
1693
+ if (command === '/terminal') {
1694
+ await showActiveTerminalStatus({ bot, chatId, link });
1695
+ return true;
1696
+ }
1697
+ if (command === '/detach') {
1698
+ await detachActiveTerminal({ bot, chatId, link });
1699
+ return true;
1700
+ }
1544
1701
  if (command === '/newchat') {
1545
1702
  await startNewChat({ bot, chatId, link });
1546
1703
  return true;
@@ -1634,6 +1791,9 @@ async function handleTelegramControlMessageInternal({ bot, msg, link }) {
1634
1791
  if (await handleCommand({ bot, chatId, link, text }))
1635
1792
  return true;
1636
1793
  const state = getState(link.user_id);
1794
+ if (getActiveTerminal(state)) {
1795
+ return sendToActiveTerminal({ bot, chatId, link, text });
1796
+ }
1637
1797
  if (state.routerEnabled === false)
1638
1798
  return false;
1639
1799
  if (!state.remoteControlEnabled) {
@@ -1745,6 +1905,10 @@ async function handleTelegramControlCallbackInternal({ bot, query, link }) {
1745
1905
  return showWebhookMenu({ bot, chatId, link, editMessageId });
1746
1906
  if (action === 'sessions')
1747
1907
  return showSessions({ bot, chatId, link, editMessageId });
1908
+ if (action === 'terminal_status')
1909
+ return showActiveTerminalStatus({ bot, chatId, link, editMessageId });
1910
+ if (action === 'detach_terminal')
1911
+ return detachActiveTerminal({ bot, chatId, link, editMessageId });
1748
1912
  if (action === 'new_chat')
1749
1913
  return startNewChat({ bot, chatId, link, editMessageId });
1750
1914
  if (action === 'install_menu')