# Task: group_chat.py — 세션 중 자연어 인원/설정 제어 기능 추가

## 작업 레벨: Lv.2

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

## 변경 내용

### 1. detect_intent() 확장 — 세션 중 제어 명령 감지

기존 의도: start_chat, end_chat, user_input, none
추가 의도: **control** (세션 중 설정 변경)

세션 진행 중 유저 메시지에서 아래 제어 의도를 감지:

```python
# 제어 명령 타입
CONTROL_TYPES = {
    "limit_personas": "인원 수 제한",        # "10명만 얘기해", "5명까지만"
    "add_persona": "인원 추가",             # "로키도 불러", "비너스 합류시켜"
    "remove_persona": "인원 퇴장",           # "로키 빠져", "아르고스 나가"
    "filter_by_role": "역할별 필터",         # "백엔드만 모여", "팀장들만"
    "filter_by_team": "팀별 필터",           # "1팀만", "개발2팀만 남아"
    "set_auto_turns": "자동 발화 수 변경",   # "3턴까지만 자동으로", "계속 얘기해"
}
```

### 2. detect_intent 세션 중 분기 확장

```python
def detect_intent(user_message: str, session_active: bool) -> dict:
    if session_active:
        # 1차: 키워드 매칭 (즉시)
        # 종료
        if any(kw in user_message for kw in END_KEYWORDS):
            return {"intent": "end_chat"}

        # 제어 명령 키워드
        # 인원 제한: "N명만", "N명까지만"
        import re
        limit_match = re.search(r'(\d+)명(?:만|까지만|까지)', user_message)
        if limit_match:
            return {
                "intent": "control",
                "type": "limit_personas",
                "count": int(limit_match.group(1))
            }

        # 인원 퇴장: "{이름} 빠져/나가/퇴장"
        for persona_id, persona_data in ALL_PERSONAS.items():
            name = persona_data.get("name", "")
            if name and name in user_message:
                if any(kw in user_message for kw in ["빠져", "나가", "퇴장", "빠지", "나가라"]):
                    return {
                        "intent": "control",
                        "type": "remove_persona",
                        "persona": persona_id
                    }
                if any(kw in user_message for kw in ["불러", "합류", "들어와", "참여"]):
                    return {
                        "intent": "control",
                        "type": "add_persona",
                        "persona": persona_id
                    }

        # 역할/팀 필터
        role_filters = {
            "백엔드": ["vulcan", "thor", "anubis"],
            "프론트": ["iris", "freya", "isis"],
            "프론트엔드": ["iris", "freya", "isis"],
            "UX": ["athena", "mimir", "thoth"],
            "테스터": ["argos", "heimdall", "horus"],
            "팀장": ["hermes", "odin", "ra"],
        }
        team_filters = {
            "1팀": ["hermes", "vulcan", "iris", "athena", "argos"],
            "개발1팀": ["hermes", "vulcan", "iris", "athena", "argos"],
            "2팀": ["odin", "thor", "freya", "mimir", "heimdall"],
            "개발2팀": ["odin", "thor", "freya", "mimir", "heimdall"],
            "3팀": ["ra", "anubis", "isis", "thoth", "horus"],
            "개발3팀": ["ra", "anubis", "isis", "thoth", "horus"],
        }

        for role_name, persona_ids in role_filters.items():
            if role_name in user_message and any(kw in user_message for kw in ["만", "만 ", "만 남아", "만 얘기"]):
                return {
                    "intent": "control",
                    "type": "filter_by_role",
                    "personas": persona_ids,
                    "role": role_name
                }

        for team_name, persona_ids in team_filters.items():
            if team_name in user_message and any(kw in user_message for kw in ["만", "만 ", "만 남아", "만 얘기"]):
                return {
                    "intent": "control",
                    "type": "filter_by_team",
                    "personas": persona_ids,
                    "team": team_name
                }

        # 자동 발화 수 변경: "N턴까지만", "계속 얘기해"
        turn_match = re.search(r'(\d+)턴', user_message)
        if turn_match:
            return {
                "intent": "control",
                "type": "set_auto_turns",
                "count": int(turn_match.group(1))
            }
        if any(kw in user_message for kw in ["계속 얘기", "계속 대화", "멈추지 마"]):
            return {
                "intent": "control",
                "type": "set_auto_turns",
                "count": 99  # 사실상 무제한
            }

        # 2차: 모호한 경우 → 그냥 user_input으로 처리 (Claude 호출 절약)
        return {"intent": "user_input", "message": user_message}
```

### 3. 제어 명령 처리 메서드 추가

GroupChatEngine(또는 GroupChatSession)에 제어 처리 메서드 추가:

```python
def handle_control(self, control: dict):
    """세션 중 제어 명령 처리."""
    ctype = control.get("type")

    if ctype == "limit_personas":
        count = control["count"]
        if count < len(self.personas):
            # 현재 참여자 중 앞에서 count명만 남기기
            removed = self.personas[count:]
            self.personas = self.personas[:count]
            removed_names = [self.get_persona(p).get("name", p) for p in removed]
            self.send_system_message(f"인원 조정: {', '.join(removed_names)} 퇴장. 현재 {count}명.")
            # 퇴장 인사 생략 (빠른 처리)

    elif ctype == "remove_persona":
        pid = control["persona"]
        if pid in self.personas:
            name = self.get_persona(pid).get("name", pid)
            self.personas.remove(pid)
            self.send_system_message(f"{name} 퇴장. 현재 {len(self.personas)}명.")

    elif ctype == "add_persona":
        pid = control["persona"]
        if pid not in self.personas:
            name = self.get_persona(pid).get("name", pid)
            self.personas.append(pid)
            self.send_system_message(f"{name} 합류. 현재 {len(self.personas)}명.")

    elif ctype == "filter_by_role":
        new_personas = [p for p in control["personas"] if p in self._all_available_personas()]
        if new_personas:
            self.personas = new_personas
            role = control.get("role", "")
            self.send_system_message(f"{role} 담당만 남았습니다. 현재 {len(self.personas)}명.")

    elif ctype == "filter_by_team":
        new_personas = [p for p in control["personas"] if p in self._all_available_personas()]
        if new_personas:
            self.personas = new_personas
            team = control.get("team", "")
            self.send_system_message(f"{team}만 남았습니다. 현재 {len(self.personas)}명.")

    elif ctype == "set_auto_turns":
        count = control["count"]
        self.max_auto_turns = count
        self.auto_turns = 0  # 리셋
        if count >= 99:
            self.send_system_message("자동 대화 모드: 계속 진행합니다.")
        else:
            self.send_system_message(f"자동 발화 {count}턴으로 설정.")

def send_system_message(self, text: str):
    """시스템 안내 메시지 전송 (페르소나 포맷 없이)."""
    send_telegram(self.token, self.chat_id, f"⚙️ {text}")
```

### 4. main() 루프에 control 처리 추가

```python
# 기존 코드에 추가
elif intent["intent"] == "control" and engine.is_active():
    engine.handle_control(intent)
```

### 5. 시작 시 자연어 인원 지정

세션 시작할 때도 자연어로 인원 지정 가능하게:
- "백엔드만 모여" → 백엔드 담당만 소집
- "1팀 모여" → 개발1팀만 소집
- "팀장들 모여" → 팀장만 소집
- "5명만 모여" → 랜덤 또는 주요 5명 소집
- "전원 집합" → 19명 전체

detect_intent의 start_chat 분기에서 키워드 기반 인원 선택:
```python
# start_chat에서 personas 결정 로직
if "전원" in user_message or "다 모여" in user_message:
    personas = ALL_PERSONA_IDS
elif any(role in user_message for role in role_filters):
    # 역할 기반
    for role_name, pids in role_filters.items():
        if role_name in user_message:
            personas = pids
            break
elif any(team in user_message for team in team_filters):
    # 팀 기반
    for team_name, pids in team_filters.items():
        if team_name in user_message:
            personas = pids
            break
else:
    # 기본: 주요 3명
    personas = ["hermes", "athena", "thor"]

limit_match = re.search(r'(\d+)명', user_message)
if limit_match:
    count = int(limit_match.group(1))
    personas = personas[:count]
```

## 검증 항목
1. "10명만 얘기해" → 현재 19명에서 10명으로 축소
2. "로키 빠져" → 로키 퇴장 메시지 + 참여자에서 제거
3. "비너스 불러" → 비너스 합류 메시지 + 참여자에 추가
4. "백엔드만 남아" → 백엔드 3명만 남음
5. "1팀만" → 개발1팀 5명만 남음
6. "3턴까지만 자동으로" → MAX_AUTO_TURNS 변경
7. "백엔드만 모여" → 시작 시 백엔드만 소집
8. "전원 집합" → 19명 소집
9. 기존 기능 유지: 입장/퇴장, 타임아웃, user_input
10. 전체 테스트 PASS

## 주의사항
- 인원 제한 시 최소 1명은 남아야 함 (0명이면 무시)
- 제어 명령 처리는 Claude CLI 호출 없이 키워드 매칭만 (즉시 반응)
- send_system_message()는 페르소나 포맷 없이 ⚙️ 아이콘으로 구분
- ALL_PERSONA_IDS 상수 필요 (조직도에서 로드된 전체 페르소나 ID 목록)
- handle_control 후에도 auto_turns 리셋하여 대화 계속 진행
