# Task: group_chat.py — Telegram 메시지 수신 (getUpdates 폴링) 추가

## 작업 레벨: Lv.2

## 문제
현재 group_chat.py 데몬은 트리거 파일만 폴링한다. 유저가 단톡봇에 직접 메시지를 보내도 수신하지 못한다.
유저가 "팀 모여", "잘래" 등을 보내면 바로 반응해야 하는데 현재는 불가능.

## 변경 대상
- `/home/jay/workspace/group_chat.py` (기존 파일 수정)
- `/home/jay/workspace/tests/test_group_chat.py` (테스트 업데이트)

## 변경 내용

### 1. Telegram getUpdates 폴링 추가

데몬의 메인 루프에 Telegram Bot API `getUpdates`를 추가하여 유저 메시지를 직접 수신한다.

```python
import requests

class TelegramPoller:
    """Telegram Bot API getUpdates 폴링으로 유저 메시지 수신."""

    def __init__(self, token: str, chat_id: str):
        self.token = token
        self.chat_id = chat_id
        self.base_url = f"https://api.telegram.org/bot{token}"
        self.last_update_id = 0

    def get_updates(self) -> list:
        """새 메시지 가져오기."""
        try:
            resp = requests.get(
                f"{self.base_url}/getUpdates",
                params={"offset": self.last_update_id + 1, "timeout": 1},
                timeout=5
            )
            data = resp.json()
            if not data.get("ok"):
                return []

            messages = []
            for update in data.get("result", []):
                self.last_update_id = update["update_id"]
                msg = update.get("message", {})
                # 지정된 chat_id에서 온 메시지만 처리
                if str(msg.get("chat", {}).get("id")) == self.chat_id:
                    text = msg.get("text", "")
                    if text:
                        messages.append(text)
            return messages
        except Exception as e:
            logger.warning(f"getUpdates 오류: {e}")
            return []
```

### 2. 자연어 트리거 감지

유저 메시지를 받으면 Claude CLI로 의도를 분석한다.

```python
def detect_intent(user_message: str, session_active: bool) -> dict:
    """유저 메시지의 의도를 감지한다.

    Returns:
        {"intent": "start_chat", "topic": "...", "personas": [...]}
        {"intent": "user_input", "message": "..."}
        {"intent": "end_chat"}
        {"intent": "none"}  # 단톡과 무관한 메시지
    """
    if session_active:
        # 세션 진행 중이면: 종료 의도 or 대화 참여
        prompt = f"""유저 메시지를 분석하세요.
현재 팀 단톡 세션이 진행 중입니다.

유저 메시지: "{user_message}"

아래 중 하나를 JSON으로 응답:
1. 종료 의도 ("잘래", "빠이", "해산", "끝", "그만" 등): {{"intent": "end_chat"}}
2. 대화 참여 (질문, 의견, 지시 등): {{"intent": "user_input", "message": "{user_message}"}}

주의: "너 잘래?" 같은 질문은 종료가 아님. "나 잘래", "오늘은 여기까지" 같은 1인칭 표현만 종료.
JSON만 응답:"""
    else:
        # 세션 없으면: 시작 의도 감지
        prompt = f"""유저 메시지를 분석하세요.
현재 팀 단톡 세션이 없습니다.

유저 메시지: "{user_message}"

아래 중 하나를 JSON으로 응답:
1. 단톡 시작 의도 ("팀 모여", "회의하자", "단톡 시작", "모여봐", "전원 집합" 등):
   {{"intent": "start_chat", "topic": "추정 주제", "personas": ["hermes", "athena", "thor"]}}
   - personas: 주제에 맞는 페르소나 3~5명 선택 (전체 소집이면 전원)
2. 단톡과 무관한 메시지 ("안녕", "하이", 일반 대화 등):
   {{"intent": "none"}}

전체 페르소나 목록: hermes, vulcan, iris, athena, argos, odin, thor, freya, mimir, heimdall, ra, anubis, isis, thoth, horus, loki, maat, janus, venus
JSON만 응답:"""

    try:
        result = call_claude("", prompt, model="claude-haiku-4-5-20251001")
        # JSON 파싱
        import re
        json_match = re.search(r'\{.*\}', result, re.DOTALL)
        if json_match:
            return json.loads(json_match.group())
    except Exception as e:
        logger.warning(f"의도 감지 오류: {e}")

    return {"intent": "none"}
```

### 3. 메인 루프 변경

기존: 트리거 파일만 폴링
변경: Telegram 폴링 + 트리거 파일 폴링 병행

```python
def main():
    load_env_keys()
    token = os.environ.get("GROUP_CHAT_BOT_TOKEN")
    if not token:
        logger.error("GROUP_CHAT_BOT_TOKEN 미설정")
        sys.exit(1)

    personas_data = load_personas()
    poller = TelegramPoller(token, chat_id="6937032012")
    engine = GroupChatEngine(token, personas_data)

    logger.info("그룹챗 데몬 시작. Telegram 폴링 + 트리거 파일 감시 중...")

    while True:
        try:
            # 1. Telegram 메시지 수신
            messages = poller.get_updates()
            for msg in messages:
                intent = detect_intent(msg, engine.is_active())

                if intent["intent"] == "start_chat" and not engine.is_active():
                    engine.start_session(
                        topic=intent.get("topic", "자유 대화"),
                        personas=intent.get("personas", ["hermes", "athena", "thor"]),
                        chat_id="6937032012",
                        user_message=msg
                    )
                elif intent["intent"] == "user_input" and engine.is_active():
                    engine.add_user_input(msg)
                elif intent["intent"] == "end_chat" and engine.is_active():
                    engine.end_session(reason="user_exit")

            # 2. 트리거 파일 체크 (기존 호환)
            check_trigger_file(engine)

            # 3. 활성 세션이면 대화 루프 1턴 실행
            if engine.is_active():
                engine.run_one_turn()

            time.sleep(1)

        except KeyboardInterrupt:
            break
        except Exception as e:
            logger.error(f"메인 루프 오류: {e}")
            time.sleep(5)
```

### 4. "none" 의도 시 안내 메시지

세션이 없을 때 유저가 일반 메시지를 보내면 사용법 안내:
```python
if intent["intent"] == "none" and not engine.is_active():
    send_telegram(token, "6937032012",
        '💬 안녕하세요! 팀 단톡을 시작하려면 "팀 모여" 또는 "회의하자"라고 말씀해주세요.')
```

### 5. run_one_turn() 분리

기존 run_loop()는 무한루프였으나, 메인 루프에서 Telegram 폴링과 병행하려면 **1턴씩 실행**하는 방식으로 변경:
```python
def run_one_turn(self):
    """대화 1턴 실행 (발화자 선택 → 응답 → 전송)."""
    if not self.active:
        return
    # 타임아웃 체크
    if time.time() - self.last_activity > TIMEOUT_SECONDS:
        self.end_session(reason="timeout")
        return
    # MAX_AUTO_TURNS 체크
    if self.auto_turns >= MAX_AUTO_TURNS:
        return  # 유저 입력 대기
    # 1턴 실행
    speaker = self.select_next_speaker()
    response = self.generate_response(speaker)
    self.send_and_log(speaker, response)
```

## 검증 항목
1. 데몬 시작 → Telegram 폴링 정상 동작
2. 유저가 "팀 모여" → 세션 시작
3. 유저가 "잘래" → 세션 종료
4. 세션 중 유저 메시지 → 대화에 반영
5. 세션 없을 때 일반 메시지 → 안내 메시지
6. 트리거 파일 방식도 여전히 동작 (하위 호환)
7. 기존 테스트 + 새 테스트 전체 PASS

## 주의사항
- Telegram getUpdates와 cokacdir 폴링이 충돌하지 않는지 확인 (단톡봇은 cokacdir에 등록 안 됨, 충돌 없음)
- getUpdates timeout은 1초로 짧게 (메인 루프 블로킹 방지)
- detect_intent에 Claude CLI 호출 → 의도 감지에 ~15-20초 소요될 수 있음. 단순 키워드 매칭으로 1차 필터 후 모호한 경우만 Claude 호출하는 하이브리드 방식 권장:
  ```python
  # 1차: 키워드 매칭 (즉시)
  START_KEYWORDS = ["팀 모여", "회의하자", "단톡 시작", "모여봐", "전원 집합", "소집"]
  END_KEYWORDS = ["잘래", "빠이", "해산", "끝", "그만", "여기까지"]

  if any(kw in user_message for kw in START_KEYWORDS):
      return {"intent": "start_chat", ...}
  if any(kw in user_message for kw in END_KEYWORDS) and session_active:
      return {"intent": "end_chat"}

  # 2차: 모호한 경우만 Claude 호출
  return call_claude_for_intent(...)
  ```
- chat_id "6937032012" 하드코딩 OK (단일 유저 시스템)
- run_loop() → run_one_turn() 리팩토링 시 기존 세션 로직(입장/퇴장 시퀀스, 타임아웃, MAX_AUTO_TURNS, 히스토리, 상태 파일 덤프) 모두 보존