Files
hanarang-dashboard/.plans/design/api-design.md

6.1 KiB

API 설계

공통 규칙

  • Base URL: https://hanarang-api.nabomhalang.co.kr
  • 응답 형식: JSON
  • WS Namespace: /ws
  • 기본 에러 형식: { "statusCode": 400, "message": "...", "error": "Bad Request" }
  • SPRINT-017 기준으로 오피스 화면은 REST snapshot + WebSocket push 혼합 모델을 사용한다.

인증 규칙

  • 읽기 전용 상태 조회 API는 현재 공개 조회가 가능한 엔드포인트가 섞여 있어.
  • direct chat (POST /api/sisters/:name/chat) 은 JWT 필수야.
  • WebSocket 연결도 JWT 필수야. 토큰이 없거나 잘못되면 서버가 연결을 끊어.
  • 그래서 /office읽기와 쓰기의 권한 상태를 분리해서 다뤄야 해.

오피스 대시보드 핵심 소스

  • Git 구현 기준:
    • frontend/app/office/page.tsx
    • backend/src/sisters/sisters.controller.ts
    • backend/src/events/events.gateway.ts
    • backend/src/events/events.scheduler.ts
  • 관련 Sprint: ../sprints/SPRINT-017.md
  • 관련 UI 문서:
    • ./ui/office-dashboard-design.md
    • ./ui/office-chat-design.md

상태 모델

Data Mode

mode 의미 UI 원칙
live WebSocket 또는 최신 runtime 기준으로 실시간성이 유지되는 상태 가장 신뢰도 높은 상태로 표시
snapshot REST polling 기준 최신 스냅샷 live보다 약한 상태로 표시
fallback runtime 또는 status 조회 실패 시 보여주는 보정 데이터 추정치임을 숨기지 않음

Agent State

state 의미
idle 대기 중
thinking 작업 준비 / 추론 중
tool_calling 외부 작업/도구 호출 중
speaking 응답 생성 또는 대화 중
error 연결 또는 런타임 이상

Sisters (오피스 화면 기준)

Method Endpoint 인증 설명
GET /api/sisters 없음 4자매 상태 목록
GET /api/sisters/runtime 없음 4자매 runtime 스냅샷
GET /api/sisters/:name/runtime 없음 개별 자매 runtime
GET /api/sisters/:name/system 없음 개별 자매 시스템 정보
GET /api/sisters/:name/avatar 없음 자매 아바타 이미지
GET /api/sisters/:name/config 없음 openclaw 설정 조회
GET /api/sisters/:name/sessions 없음 최근 세션 목록
GET /api/sisters/:name/subagents 없음 서브에이전트 목록/현황
GET /api/sisters/:name/activity 없음 최근 활동 로그
POST /api/sisters/:name/chat JWT 필요 direct chat 전송

GET /api/sisters/runtime

오피스 메인 화면의 상단 상태와 최근 메시지, 서브에이전트 상태를 구성하는 runtime source야.

예시 필드:

[
  {
    "name": "harang",
    "gatewayConnected": true,
    "mainState": "thinking",
    "currentTask": "SPRINT-017 scope 잠금",
    "activeSessionLabel": "main",
    "activeSessionUpdatedAt": 1775640000000,
    "controlSessionKey": "agent:harang:main",
    "recentMessages": [
      {
        "id": "msg_1",
        "role": "assistant",
        "content": "scope 정리 중",
        "ts": "2026-04-08T09:20:00Z"
      }
    ],
    "subagents": [
      {
        "name": "prd-writer",
        "state": "tool_calling",
        "updatedAt": 1775640000000,
        "currentTask": "SPRINT-017 작성",
        "sessionLabel": "main"
      }
    ]
  }
]

POST /api/sisters/:name/chat

// Request
{ "message": "SPRINT-017 scope 확인해" }
// Response 200 example
{
  "ok": true,
  "queued": true,
  "sessionKey": "agent:harang:main"
}

Chat 실패 처리 원칙

  • JWT 없음 → 입력창 비활성화 또는 전송 실패 이유 명시
  • timeout → 전송은 재시도 가능 상태로 남김
  • 최근 메시지 없음 → empty state 문구 사용
  • tool 메시지와 assistant 메시지는 같은 bubble로 합치지 않음

WebSocket

연결

  • Namespace: /ws
  • 인증 방식:
    • handshake.auth.token
    • 또는 Authorization: Bearer <token>
  • 토큰 없음/검증 실패 시 disconnect

서버 이벤트

Event Payload 설명
pong { ts } ping 응답
sisters:update { sisters, ts } 4자매 상태 push
activity:new { item, ts } 새 활동 로그 push

운영 규칙

  • WS는 가장 강한 source야.
  • WS가 끊겨도 마지막 성공 시각을 보존해 stale 여부를 판단해야 해.
  • scheduler polling 값이 더 오래된 경우 live 값을 덮어쓰면 안 돼.
  • SPRINT-017에서는 reconnect / stale / snapshot downgrade 규칙을 문서와 QA 기준으로 잠근다.

Projects (프로젝트)

Method Endpoint 설명
GET /api/projects 프로젝트 목록 (Gitea API 연동)
GET /api/projects/:id 프로젝트 상세 + Sprint + Task
GET /api/projects/:id/tasks Task Ledger
GET /api/projects/:id/activity 활동 로그

Activity (활동 피드)

Method Endpoint 설명
GET /api/activity 전체 최근 활동 피드 (limit, offset)

Org (조직도)

Method Endpoint 설명
GET /api/org 조직도 데이터 (자매 + 서브에이전트 트리)

Admin (관리자)

Method Endpoint 설명
GET /api/admin/harness/:sister/:file 하네스 파일 읽기 (AGENTS.md 등)
PUT /api/admin/harness/:sister/:file 하네스 파일 수정 + SSOT push
GET /api/admin/repos Gitea repo 목록
GET /api/admin/logs/:sister 세션 로그
GET /api/admin/costs 토큰 사용량/비용

Health

Method Endpoint 설명
GET /health 서버 상태 확인

SPRINT-017 문서 기준 정리

  • 오피스 화면은 읽기 API와 쓰기 API 권한을 분리해서 다룬다
  • live / snapshot / fallback은 API 문서, UI 문서, QA 문서에서 같은 의미로 쓴다
  • direct chat, WS disconnect, stale 상태는 정상 흐름만큼 중요하게 검증한다
  • 여기까지가 API 기준 scope야.