docs: 에이전트 횡단 오버사이트 타임라인 설계 spec
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
|||||||
|
# 에이전트 횡단 오버사이트 타임라인 — 설계
|
||||||
|
|
||||||
|
작성일: 2026-06-11
|
||||||
|
대상 repo: `web-ui` (프론트엔드)
|
||||||
|
연관 백엔드: ✅ 완료 (`GET /api/agent-office/activity` 필터 지원, main `2c2828c`)
|
||||||
|
|
||||||
|
## 배경 / 목적
|
||||||
|
|
||||||
|
3개 자율 에이전트(stock 보유종목·insta 발급·lotto 진화)가 모두 도는 상태에서
|
||||||
|
"팀이 무엇을·언제·왜 했나"를 **한 화면에서** 보는 에이전트 횡단 오버사이트(CEO 가시화) 기능.
|
||||||
|
|
||||||
|
현재 web-ui에는 `/lotto/evolver` 탭의 lotto 전용 `LottoActivityTimeline`만 존재.
|
||||||
|
통합 `/activity`(전 에이전트 대상)를 소비하는 횡단 뷰가 없다.
|
||||||
|
|
||||||
|
## 백엔드 응답 shape (라이브 검증 완료)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/agent-office/activity?agent_id=&type=task|log&status=&days=&limit=&offset=
|
||||||
|
→ { items: [...], total: N }
|
||||||
|
```
|
||||||
|
|
||||||
|
- **task item**: `{ type:'task', agent_id, task_id, message, created_at, task_type, status, completed_at, duration_seconds }`
|
||||||
|
- **log item**: `{ type:'log', agent_id, task_id, message, created_at, level }`
|
||||||
|
- `status`는 task 전용(`type=log`에 주면 무시). injection 안전(? 바인딩 + 브랜치 선택).
|
||||||
|
|
||||||
|
검증 메모:
|
||||||
|
- 무필터 `total`이 65,599건 → **기본 `days=7` 필터 필수**(task 기준 110건으로 감소).
|
||||||
|
- `requires_approval` 필드는 **존재하지 않음** → `status:'pending'`을 진행/대기 강조로 처리.
|
||||||
|
- `agent_id` 값이 `AGENT_META` 키(stock/music/insta/realestate/lotto)와 일치 → 색상/이미지 재사용.
|
||||||
|
|
||||||
|
## 아키텍처
|
||||||
|
|
||||||
|
AgentOffice는 단일 화면(TopBar + 3×3 AgentGrid + 우측 패널) 구조.
|
||||||
|
우측 패널은 `selectedAgent` 상태로 분기:
|
||||||
|
- `null` → (기존) `EmptyDetailPanel variant="initial"` → **`ActivityTimeline`으로 교체**
|
||||||
|
- `placeholder-N` → `EmptyDetailPanel variant="placeholder"` (유지)
|
||||||
|
- active agent id → `SidePanel` (유지)
|
||||||
|
|
||||||
|
즉 **에이전트 미선택 시 기본 우측 패널이 횡단 타임라인**이 되고, 그리드와 항상 동시 노출.
|
||||||
|
항목/그리드 클릭으로 해당 에이전트 SidePanel로 전환.
|
||||||
|
|
||||||
|
## 신규/변경 파일
|
||||||
|
|
||||||
|
| 파일 | 역할 |
|
||||||
|
|------|------|
|
||||||
|
| `src/api.js` | `agentActivity({agent_id,type,status,days,limit,offset})` 추가 — 빈 값 제외 쿼리스트링 빌드 + GET `/api/agent-office/activity` |
|
||||||
|
| `src/pages/agent-office/AgentOffice.jsx` | `selectedAgent===null` 분기를 `EmptyDetailPanel` → `ActivityTimeline`(props: `refreshTrigger`, `onSelectAgent`)로 교체 |
|
||||||
|
| `src/pages/agent-office/hooks/useActivityFeed.js` | items/offset/total/hasMore/loading/error/filters 상태 관리 |
|
||||||
|
| `src/pages/agent-office/components/ActivityTimeline.jsx` | 컨테이너: 헤더 + `ActivityFilters` + 리스트 + 무한스크롤 sentinel + 상태(loading/empty/error/end) |
|
||||||
|
| `src/pages/agent-office/components/ActivityFilters.jsx` | 필터 4종(agent 색칩 / type / status / days). `type==='log'`일 때 status 비활성 |
|
||||||
|
| `src/pages/agent-office/components/ActivityItem.jsx` | 한 행: agent 색·이미지 + message + 상태/level 뱃지 + 상대시간 + duration. 클릭 → `onSelectAgent(agent_id)` |
|
||||||
|
| `src/pages/agent-office/AgentOffice.css` | 타임라인/필터/항목 스타일 (designer 스킬로 마감) |
|
||||||
|
|
||||||
|
## 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
AgentOffice (selectedAgent===null)
|
||||||
|
└─ <ActivityTimeline refreshTrigger={refreshTrigger} onSelectAgent={handleSelectAgent} />
|
||||||
|
└─ useActivityFeed(filters)
|
||||||
|
• mount / 필터 변경 → offset=0 fetch → items 교체
|
||||||
|
• loadMore (sentinel 교차) → offset += limit → items append
|
||||||
|
• refreshTrigger 변경 → offset=0 재조회 → items 교체 (WS 실시간 연동)
|
||||||
|
└─ ActivityItem onClick → onSelectAgent(agent_id) → SidePanel로 전환
|
||||||
|
```
|
||||||
|
|
||||||
|
`handleSelectAgent`는 기존 콜백 재사용(선택 + `clearNotifications`).
|
||||||
|
|
||||||
|
## 필터 기본값
|
||||||
|
|
||||||
|
`days=7`, `type=all`, `status=all`, `agent=all`, `limit=30`(페이지당).
|
||||||
|
|
||||||
|
## 상태 / 비주얼 매핑
|
||||||
|
|
||||||
|
- task `status`: `succeeded` → 초록 ✓ / `failed` → 빨강 ✗ / `pending`·`working` → 앰버 펄스 ⏳(강조)
|
||||||
|
- log `level`: `error` → ❌ / `warning` → ⚠️ / `info` → ·
|
||||||
|
- agent 색상: `AGENT_META[agent_id].color`, 미지정 agent → 회색 `#6b7280`
|
||||||
|
- `offset >= total` → "더 이상 활동 없음" / 무한스크롤은 IntersectionObserver
|
||||||
|
|
||||||
|
## 상태 처리(엣지)
|
||||||
|
|
||||||
|
- 첫 페이지 로딩 → 스피너/스켈레톤
|
||||||
|
- 빈 결과 → "최근 N일 활동 없음"
|
||||||
|
- fetch 실패 → 인라인 에러 + 재시도 버튼
|
||||||
|
- 리스트 끝 → end-of-list 표시, sentinel 관찰 중단
|
||||||
|
|
||||||
|
## 테스트 (TDD, vitest + RTL — 기존 패턴 따름)
|
||||||
|
|
||||||
|
- `useActivityFeed`: 필터 변경 시 offset 리셋 + items 교체 / loadMore append / refreshTrigger 재조회 / `hasMore = items.length < total` 계산 (api mock)
|
||||||
|
- `ActivityItem`: task vs log 렌더 분기, status/level 뱃지 클래스, 클릭 시 `onSelectAgent(agent_id)` 호출
|
||||||
|
- `ActivityFilters`: `type==='log'`일 때 status select 비활성, 필터 변경 시 onChange 호출
|
||||||
|
|
||||||
|
## 비범위 (YAGNI)
|
||||||
|
|
||||||
|
- 별도 라우트(`/agent-office/activity`) 미생성 — 기본 우측 패널 통합으로 충분
|
||||||
|
- 기존 `getActivityFeed(limit, offset)` 헬퍼는 lotto evolver 등에서 사용 여부 확인 후 유지(신규 `agentActivity`와 공존, 무리한 통합 안 함)
|
||||||
|
- `LottoActivityTimeline`(`kind/ts/payload` shape)은 다른 엔드포인트 소비 → 건드리지 않음
|
||||||
|
- CSV/export, 검색어 필터 등 부가기능 제외
|
||||||
|
|
||||||
|
## 구현 순서
|
||||||
|
|
||||||
|
1. `agentActivity` api 헬퍼 추가
|
||||||
|
2. `useActivityFeed` 훅 (TDD)
|
||||||
|
3. `ActivityItem` / `ActivityFilters` (TDD)
|
||||||
|
4. `ActivityTimeline` 컨테이너 조립
|
||||||
|
5. `AgentOffice.jsx` 분기 교체
|
||||||
|
6. designer 스킬로 CSS 마감
|
||||||
|
7. lint + 테스트 + 빌드 검증
|
||||||
Reference in New Issue
Block a user