Merge remote-tracking branch 'origin/main' into fix/slack-ansi-filter
# Conflicts: # src/lazy_enter/bridge.py
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -25,18 +26,45 @@ class Bridge:
|
||||
self._running = False
|
||||
self._channel: str = self.config.allowed_channel_id
|
||||
self._last_sent_output: str = ""
|
||||
self._last_input_at = time.monotonic()
|
||||
self._last_output_at = time.monotonic()
|
||||
self._input_idle_reported = False
|
||||
self._output_idle_reported = False
|
||||
|
||||
self.slack.on_message(self._handle_message)
|
||||
self.slack.on_command(self._handle_command)
|
||||
|
||||
def _handle_message(self, text: str, channel: str) -> None:
|
||||
"""Slack 메시지를 PTY 프로세스로 전달한다."""
|
||||
if self._is_blocked_input(text):
|
||||
self.slack.send_message(
|
||||
channel, ":no_entry: 차단된 명령 패턴이 감지되었습니다."
|
||||
)
|
||||
return
|
||||
|
||||
if self.pty and self.pty.is_alive:
|
||||
self.pty.send(text)
|
||||
self._last_input_at = time.monotonic()
|
||||
self._input_idle_reported = False
|
||||
logger.info("입력 전달: %s", text)
|
||||
else:
|
||||
self.slack.send_message(channel, ":warning: 실행 중인 세션이 없습니다.")
|
||||
|
||||
@staticmethod
|
||||
def _is_blocked_input(text: str) -> bool:
|
||||
"""치명적 쉘 명령 패턴을 단순 차단한다."""
|
||||
normalized = re.sub(r"\s+", " ", text.lower()).strip()
|
||||
blocked_patterns = (
|
||||
"rm -rf /",
|
||||
"rm -rf /*",
|
||||
"mkfs",
|
||||
":(){:|:&};:",
|
||||
"shutdown -h",
|
||||
"reboot",
|
||||
"poweroff",
|
||||
)
|
||||
return any(pattern in normalized for pattern in blocked_patterns)
|
||||
|
||||
def _handle_command(self, command: str, channel: str) -> None:
|
||||
"""슬래시 커맨드를 처리한다."""
|
||||
if command == "start":
|
||||
@@ -57,6 +85,10 @@ class Bridge:
|
||||
self.pty = PtyManager(self.config.default_shell)
|
||||
self.pty.start()
|
||||
self._running = True
|
||||
self._last_input_at = time.monotonic()
|
||||
self._last_output_at = time.monotonic()
|
||||
self._input_idle_reported = False
|
||||
self._output_idle_reported = False
|
||||
self._output_thread = threading.Thread(target=self._poll_output, daemon=True)
|
||||
self._output_thread.start()
|
||||
|
||||
@@ -75,11 +107,14 @@ class Bridge:
|
||||
"""PTY 출력을 주기적으로 읽어 Slack으로 전송한다."""
|
||||
buffer = ""
|
||||
while self._running and self.pty and self.pty.is_alive:
|
||||
now = time.monotonic()
|
||||
output = self.pty.read_output(timeout=self.config.pty_read_timeout)
|
||||
if output:
|
||||
cleaned = clean_terminal_output(output)
|
||||
if cleaned:
|
||||
buffer += f"{cleaned}\n"
|
||||
self._last_output_at = now
|
||||
self._output_idle_reported = False
|
||||
|
||||
if buffer:
|
||||
# 메시지 길이 제한 적용
|
||||
@@ -91,6 +126,33 @@ class Bridge:
|
||||
self._last_sent_output = message
|
||||
buffer = ""
|
||||
|
||||
output_idle = now - self._last_output_at
|
||||
if (
|
||||
self.config.output_idle_report_seconds > 0
|
||||
and not self._output_idle_reported
|
||||
and output_idle >= self.config.output_idle_report_seconds
|
||||
):
|
||||
self.slack.send_message(
|
||||
self._channel,
|
||||
(
|
||||
f":hourglass_flowing_sand: 출력이 {int(output_idle)}초 동안 "
|
||||
"없습니다. 세션 상태를 확인해주세요."
|
||||
),
|
||||
)
|
||||
self._output_idle_reported = True
|
||||
|
||||
input_idle = now - self._last_input_at
|
||||
if (
|
||||
self.config.input_idle_report_seconds > 0
|
||||
and not self._input_idle_reported
|
||||
and input_idle >= self.config.input_idle_report_seconds
|
||||
):
|
||||
self.slack.send_message(
|
||||
self._channel,
|
||||
f":information_source: 입력이 {int(input_idle)}초 동안 없습니다.",
|
||||
)
|
||||
self._input_idle_reported = True
|
||||
|
||||
time.sleep(self.config.output_buffer_interval)
|
||||
|
||||
if not self._running:
|
||||
|
||||
Reference in New Issue
Block a user