From 71e0b6f8db5ab2c919946351705ee8a53d34e437 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sat, 11 Apr 2026 08:49:43 +0900 Subject: [PATCH] feat(agent-office): BaseAgent FSM with idle/break behavior Co-Authored-By: Claude Opus 4.6 --- agent-office/app/agents/__init__.py | 17 +++++++ agent-office/app/agents/base.py | 72 +++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 agent-office/app/agents/__init__.py create mode 100644 agent-office/app/agents/base.py diff --git a/agent-office/app/agents/__init__.py b/agent-office/app/agents/__init__.py new file mode 100644 index 0000000..f941a04 --- /dev/null +++ b/agent-office/app/agents/__init__.py @@ -0,0 +1,17 @@ +from .stock import StockAgent +from .music import MusicAgent + +AGENT_REGISTRY = {} + +def init_agents(): + AGENT_REGISTRY["stock"] = StockAgent() + AGENT_REGISTRY["music"] = MusicAgent() + +def get_agent(agent_id: str): + return AGENT_REGISTRY.get(agent_id) + +def get_all_agent_states() -> list: + return [ + {"agent_id": aid, "state": agent.state, "detail": agent.state_detail} + for aid, agent in AGENT_REGISTRY.items() + ] diff --git a/agent-office/app/agents/base.py b/agent-office/app/agents/base.py new file mode 100644 index 0000000..ef9653d --- /dev/null +++ b/agent-office/app/agents/base.py @@ -0,0 +1,72 @@ +import asyncio +import random +import time +from typing import Optional + +from ..config import IDLE_BREAK_THRESHOLD, BREAK_DURATION_MIN, BREAK_DURATION_MAX +from ..db import add_log + +VALID_STATES = ("idle", "working", "waiting", "reporting", "break") + +class BaseAgent: + agent_id: str = "" + display_name: str = "" + state: str = "idle" + state_detail: str = "" + _idle_since: float = 0.0 + _break_until: float = 0.0 + _ws_manager = None + + def __init__(self): + self._idle_since = time.time() + + def set_ws_manager(self, manager): + self._ws_manager = manager + + async def transition(self, new_state: str, detail: str = "", task_id: str = None) -> None: + if new_state not in VALID_STATES: + return + old = self.state + self.state = new_state + self.state_detail = detail + + if new_state == "idle": + self._idle_since = time.time() + elif new_state == "break": + duration = random.randint(BREAK_DURATION_MIN, BREAK_DURATION_MAX) + self._break_until = time.time() + duration + + add_log(self.agent_id, f"State: {old} -> {new_state} ({detail})") + + if self._ws_manager: + await self._ws_manager.send_agent_state(self.agent_id, new_state, detail, task_id) + if new_state == "break": + await self._ws_manager.send_agent_move(self.agent_id, "break_room") + elif old == "break" and new_state == "idle": + await self._ws_manager.send_agent_move(self.agent_id, "desk") + + async def check_idle_break(self) -> None: + now = time.time() + if self.state == "idle" and (now - self._idle_since) > IDLE_BREAK_THRESHOLD: + if random.random() < 0.5: + break_type = random.choice(["커피 타임", "잠깐 산책", "졸고 있음"]) + await self.transition("break", break_type) + elif self.state == "break" and now > self._break_until: + await self.transition("idle", "휴식 완료") + + async def on_schedule(self) -> None: + raise NotImplementedError + + async def on_command(self, command: str, params: dict) -> dict: + raise NotImplementedError + + async def on_approval(self, task_id: str, approved: bool, feedback: str = "") -> None: + raise NotImplementedError + + async def get_status(self) -> dict: + return { + "agent_id": self.agent_id, + "display_name": self.display_name, + "state": self.state, + "detail": self.state_detail, + }