@leessju/discord-orchestrator
v0.1.6
Published
Discord 위에서 **Claude Code**와 **Codex CLI** 세션을 멀티유저 / 멀티채널로 오케스트레이션하는 봇.
Downloads
217
Readme
discord-orchestrator
Discord 위에서 Claude Code와 Codex CLI 세션을 멀티유저 / 멀티채널로 오케스트레이션하는 봇.
스레드 또는 채널 = 세션 단위. 슬래시 명령으로 시작 · 재개 · 취소 · 권한 관리 · A→B 작업 지시.
핵심 기능
- 멀티유저 채널 모드 — 한 채널에 여러 명이 봇과 동시에 대화.
composeSessionKey로 thread/channel 분리,channel:prefix로 PK 충돌 방지 - 4-tier 권한 —
owner / member / guest / public / blocked(thread > channel > global 우선순위 + blocked override) - per-(user, channel) 입장 제어 — 한 유저가 채널 슬롯 독점 못 함 (기본 active 2 / queued 5)
- A→B 작업 지시 —
/task슬래시 +omc_chains2계층 (envelope 흐름 + human task_status) - Bystander default-deny — @mention/봇 author/reply 메시지만 prompt 포함 (privacy)
- Approval privacy — 채널 출처 approval embed은 owner DM으로 라우팅
- OAuth 자동 갱신 — token-refresh-daemon이 5분 주기 만료 체크 + 4시간 전부터 자동 refresh, atomic temp+rename, exponential backoff, max-failure circuit
- Keychain 격리 —
CLAUDE_CONFIG_DIR로 봇 자체 credentials 디렉토리 사용 (사용자claude /login이 봇에 영향 없음) - stderr 401 분류 — 인증 실패 자동 감지 → daemon 깨우기 + auth_failures_total 카운터 + Discord 알림
- healthz 관측성 —
127.0.0.1:7707/healthz에서 token / queue / active session 상태 노출
0. 사전 요구사항
node --version # v22+
which claude # /opt/homebrew/bin/claude 또는 ~/.local/bin/claude
which pm2 # /opt/homebrew/bin/pm2 등없으면:
# Node 22 (mise/nvm 권장) 또는 brew install node@22
npm install -g pm2
# claude CLI: https://docs.claude.com/en/docs/claude-code
# codex CLI (선택): https://github.com/openai/codex지원 OS: macOS (Apple Silicon 권장) 또는 Linux
1. 클론 + 빌드 + 테스트
git clone https://github.com/leessju/discord-orchestrator.git
cd discord-orchestrator
npm install
npm run build
npm test # 345 unit tests 통과 확인2. Discord 봇 만들기
- https://discord.com/developers/applications → "New Application" → 봇 이름 입력
- 좌측 "Bot" 탭 → "Add Bot" → 봇 토큰 복사 (한 번만 보임, 분실 시 재생성)
- "Privileged Gateway Intents" → MESSAGE CONTENT INTENT 활성화 (필수)
- 좌측 "OAuth2 → URL Generator":
- Scopes:
bot,applications.commands - Bot Permissions:
- Send Messages
- Send Messages in Threads
- Create Public Threads
- Embed Links
- Attach Files
- Use Slash Commands
- Read Message History
- Scopes:
- 생성된 URL을 브라우저에서 열어 본인 서버에 봇 초대
3. ID 수집
Discord 클라이언트 → 설정 → 고급 → 개발자 모드 ON
| 변수 | 수집 방법 |
|---|---|
| DISCORD_GUILD_ID | 서버 우클릭 → "ID 복사" |
| OMC_OWNER_ROLE_ID | 서버 설정 → 역할 → owner 역할 우클릭 → "ID 복사" |
| OMC_OWNER_USER_ID | 본인 닉네임 우클릭 → "ID 복사" (approval DM 용) |
| OMC_ALERTS_CHANNEL_ID (선택) | 알림 채널 우클릭 → "ID 복사" |
owner 역할이 없으면 먼저 만들고 본인에게 부여하세요.
4. .env 작성
cp .env.example .env.env 편집:
# Discord (필수)
DISCORD_BOT_TOKEN=<2단계에서 복사한 봇 토큰>
DISCORD_GUILD_ID=<서버 ID>
OMC_OWNER_ROLE_ID=<owner 역할 ID>
OMC_OWNER_USER_ID=<본인 user ID>
OMC_ALERTS_CHANNEL_ID=<알림 채널 ID> # 선택
# Claude credentials 격리 (필수)
CLAUDE_CONFIG_DIR=/Users/<your-user>/.claude-bot
# 동시 세션 캡 (기본값)
OMC_MAX_ACTIVE_CLIS=6
OMC_MAX_QUEUED_SESSIONS=20
OMC_PER_USER_MAX_ACTIVE=2
OMC_PER_USER_MAX_QUEUED=5
# 채널 모드 동작 (필수)
OMC_CHANNEL_OWNER_ONLY=true
OMC_CHANNEL_INCLUDE_BYSTANDERS=false
OMC_CHANNEL_HISTORY_N=10
# 선택 — Cloudflare 우회 residential proxy (mac-server residential IP가 막히면)
# OAUTH_PROXY_URL=http://user:[email protected]:10001,http://user:[email protected]:10001⚠️
ANTHROPIC_API_KEY=sk-ant-api-*절대 설정 금지 — 정액 구독 손실 + daemon dormant 모드.sk-ant-oat로 시작하는 OAuth 토큰만 정상.
5. Claude / Codex 인증
봇은 두 CLI를 자식 프로세스로 spawn해서 사용자 본인의 구독 (Claude Pro/Max, ChatGPT Plus)을 그대로 활용합니다. API key가 아니라 OAuth 구독 모드라는 게 핵심.
5-A. Claude (필수)
Claude Code CLI는 OAuth 토큰을 macOS Keychain에 저장합니다. 봇은 PM2 프로세스라 GUI Keychain에 접근 못 하므로 격리 파일 경로($CLAUDE_CONFIG_DIR/.credentials.json)를 사용합니다.
1) 본인 Mac에서 1회 로그인
claude /login # 브라우저 열림 → Anthropic 로그인 → 구독 OAuth2) 격리 디렉토리로 시드
mkdir -p $HOME/.claude-bot && chmod 700 $HOME/.claude-bot
security find-generic-password -s "Claude Code-credentials" -w | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps({'claudeAiOauth': d.get('claudeAiOauth') or d}))" > $HOME/.claude-bot/.credentials.json.tmp
mv $HOME/.claude-bot/.credentials.json.tmp $HOME/.claude-bot/.credentials.json
chmod 600 $HOME/.claude-bot/.credentials.json3) 원격 서버 (예: mac-server) 배포 시
원격 SSH 환경에선 직접 Keychain 못 읽으므로 로컬 Mac에서 dump → ssh로 atomic write:
ssh mac-server 'mkdir -p $HOME/.claude-bot && chmod 700 $HOME/.claude-bot'
security find-generic-password -s "Claude Code-credentials" -w | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps({'claudeAiOauth': d.get('claudeAiOauth') or d}))" | \
ssh mac-server 'umask 077 && cat > $HOME/.claude-bot/.credentials.json.tmp && mv $HOME/.claude-bot/.credentials.json.tmp $HOME/.claude-bot/.credentials.json'4) 자동 갱신
봇 내장 token-refresh-daemon이 5분 주기 만료 체크 + 4시간 전부터 자동 refresh. 사용자가 손댈 필요 없음.
curl -s 127.0.0.1:7707/healthz | jq .tokenStatus
# {
# "hasToken": true,
# "remainingMin": 347,
# "daemonRunning": "running",
# "refreshFailures": 0,
# "authFailuresTotal": 0
# }⚠️ 멀티 머신 RT 충돌
같은 Anthropic 계정을 로컬 Mac과 봇 머신 양쪽에서 사용하면 refresh_token 회전 충돌 발생할 수 있음.
- 권장: 봇 머신에서만 사용 (로컬
claude /login자제) - 충돌 시: 위 2단계 시드 다시 실행 +
pm2 restart discord-orchestrator --update-env
5-B. Codex (선택, codex 사용 시만)
Codex CLI는 별도 OAuth (ChatGPT Plus 구독). 봇은 ~/.codex/auth.json을 spawn 환경에서 그대로 상속합니다 (Claude처럼 격리 안 함).
1) 봇 머신에서 1회 로그인
codex login # 브라우저 → OpenAI 로그인 → 구독 OAuth~/.codex/auth.json에 토큰 저장됨.
2) 원격 서버 배포 시
ssh mac-server 'mkdir -p $HOME/.codex'
scp ~/.codex/auth.json mac-server:~/.codex/auth.json
ssh mac-server 'chmod 600 ~/.codex/auth.json'3) Codex 안 쓰면
/session start <name> codex 안 하면 됨. claude만 쓰는 환경에선 codex 인증 생략 가능.
5-C. 인증 확인 명령
# Claude
CLAUDE_CONFIG_DIR=$HOME/.claude-bot claude -p "say only: ok"
# 출력: ok
# Codex (설치+로그인 됐다면)
codex -p "say only: ok"둘 다 "ok" 나오면 봇 spawn 시 정상 동작.
6. 실행
로컬 개발 (hot reload)
npm run dev프로덕션 (PM2)
pm2 start ecosystem.config.cjs
pm2 save
pm2 logs discord-orchestrator # Ctrl+C로 빠져나오기재부팅 생존 (macOS LaunchAgent)
pm2 startup launchd -u $USER --hp $HOME
# 출력된 sudo 명령 그대로 실행
plutil -lint ~/Library/LaunchAgents/com.user.pm2.plist # syntax 검증 (필수)
launchctl load -w ~/Library/LaunchAgents/com.user.pm2.plist7. 동작 확인
# 헬스 (daemon 상태)
curl -s 127.0.0.1:7707/healthz | jq .tokenStatus
# daemonRunning: "running" 확인
# 슬래시 등록 (20개)
pm2 logs discord-orchestrator --lines 30 --nostream | grep "slash.registered"
# updated: 20 확인Discord에서:
- 본인이 owner 역할 받았는지 확인
- 채널에서 봇 @mention 또는
/health슬래시 → 응답 오면 OK - 다른 멤버 추가는
/permission set <user> member
8. 새 멤버 운영 흐름
owner: /permission set @alice member # 권한 부여
alice: 채널에서 봇 @mention 또는 봇 메시지에 reply
→ 첫 활성화 안내 임베드 1회 발행
alice: /session start work claude # claude 세션 시작
alice: 채널에 평범하게 메시지 → 봇이 응답9. 슬래시 명령 (20개)
| 명령 | 설명 | 권한 |
|---|---|---|
| /session start <name> <runtime> | 새 세션 시작 (claude/codex) | member+ |
| /session stop | 세션 종료 | member+ |
| /session list | 활성 세션 목록 | member+ |
| /cancel | 진행 중 작업 취소 | member+ |
| /resume | 재시작 후 세션 재개 | member+ |
| /attach <thread> | 다른 스레드 attach | member+ |
| /diff | 워크트리 diff 표시 | member+ |
| /skill <name> | 등록된 스킬 호출 | member+ |
| /show-trace <chainId> | 작업 체인 추적 | member+ |
| /broadcast <text> | 모든 활성 세션에 메시지 발송 | owner |
| /health | 시스템 상태 | public |
| /wait | 큐 위치 확인 | member+ |
| /model <name> | 세션 모델 변경 | member+ |
| /cd <path> | 워크디렉토리 변경 | member+ |
| /permission set <user> <tier> | 권한 부여 | owner |
| /permission show <user> | 권한 조회 | member+ |
| /permission list | 멤버십 목록 | owner |
| /task assign | 다른 스레드에 작업 지시 | member+ |
| /task status | 내 task 상태 | member+ |
| /task accept|reject|done|cancel | task 상태 변경 | assignee |
10. 모니터링
# 토큰 상태
curl -s 127.0.0.1:7707/healthz | jq .tokenStatus
# daemonRunning: "running" | "dormant" | "stopped" | false
# remainingMin, refreshFailures, persistedFailures, authFailuresTotal
# 시스템 전체
curl -s 127.0.0.1:7707/healthz | jq .
# activeSessions, queuedCount, oldestQueuedAgeMs, accountSlotsHealth, ...
# PM2 로그 실시간
pm2 logs discord-orchestrator11. 트러블슈팅
| 증상 | 원인 | 해결 |
|---|---|---|
| 봇이 응답 안 함 | stderr 에러 가능성 | pm2 logs discord-orchestrator --lines 50 --nostream |
| Run Failed: claude exited with code 1 + 로그 Not logged in | .credentials.json 없음/만료 | 5-A 2단계 시드 다시 실행 |
| Run Failed + 로그 No conversation found with session ID | SQLite cliSessionId stale (인증 변경 후) | sqlite3 data/orchestrator.sqlite "UPDATE omc_sessions SET cli_session_id = NULL" |
| healthz daemonRunning: "dormant" | ANTHROPIC_API_KEY=sk-ant-api-* 설정됨 | .env에서 API key 빼고 OAuth 시드 |
| [token-refresh] Max failures (10) reached | RT consumed (멀티 머신 충돌) | 5-A 2단계 재시드 + restart |
| [token-refresh] OAuth refresh failed: 403 | Cloudflare 차단 (residential IP) | OAUTH_PROXY_URL 설정 또는 수동 retoken (~25일 주기) |
| state.read-corrupt log | .omc/state/token-refresh.json 손상 | 자동 복구됨 (counters reset to defaults) |
| clock-skew.refuse-start | NTP drift >30분 | sudo sntp -sS time.apple.com 후 restart |
| token.persist-disk-low 알림 | $HOME/.claude-bot FS <100MB free | logs/ 정리 + restart |
| slash.registered updated:0 | 봇 토큰/Guild ID 잘못됨 | .env 재확인 |
| Codex 응답 없음 | ~/.codex/auth.json 없음 | codex login 실행 |
| Permission denied | owner role ID 불일치 또는 권한 없음 | owner role ID 확인 + /permission set |
자세한 운영 가이드는 RUNBOOK.md 참조.
12. 아키텍처
+----------------------+
| Discord Guild |
| thread/channel = sess |
+----------+-----------+
|
v
+--------------------------------------------------------------------+
| discord-orchestrator (Node + PM2) |
| |
| Discord client → Skill registry → Gateway router |
| ↓ ↓ ↓ |
| Session-key Permission gate Admission queue |
| (thread/channel) (4-tier) (per-user/channel cap) |
| ↓ |
| Runtime adapters |
| (Claude / Codex) |
| ↓ |
| Token-refresh-daemon → spawn(claude/codex) |
| (5-min cycle, R5 persistence, |
| wakeUpDaemon non-blocking, |
| stderr 401 classifier) |
| |
| SQLite (WAL) — sessions, runs, accounts, chains, memberships |
| JSONL transcripts/<threadId>.jsonl |
| CAS attachments by sha256 |
| Outbox for inter-agent envelopes |
| Healthz on 127.0.0.1:7707 |
+--------------------------------------------------------------------+13. 테스트
npm test # 345 unit tests (default)
npm run test:integration # 실제 OAuth 사용 (INTEGRATION_REAL_OAUTH=1 필요)
npm run test:e2e # 실제 Discord 봇 사용 (RUN_E2E=1 필요)vitest config는 default npm test에서 tests/integration/**, tests/e2e/**를 자동 제외합니다.
14. 디렉토리 구조
discord/
├── src/
│ ├── index.ts # 부트스트랩
│ ├── discord/ # discord.js 클라이언트, slash, render
│ ├── gateway/ # router, queue, session-store, credential-broker
│ ├── runtime/ # Claude/Codex adapters, account-selector
│ │ └── auth/ # token-refresh-daemon, proxy
│ ├── inter-agent/ # envelope, bus, MCP, approval, outbox, tasks-store
│ ├── skills/ # registry, permissions + 20 registered skills
│ └── observability/ # logger, healthz, alerts
├── tests/
│ ├── unit/ # 345 vitest cases
│ ├── integration/ # gated by INTEGRATION_REAL_OAUTH=1
│ └── e2e/ # gated by RUN_E2E=1
├── ecosystem.config.cjs # PM2
└── RUNBOOK.md # 운영 가이드관련 문서
docs/AUTH.md— OAuth 인증 deep dive (CLI v2.1.77+ 동작, RT 회전, Multi-machine 충돌, Q4 외부 write 감지, claude_adaptation Bug 1-8 정리, 모니터링 체크포인트)RUNBOOK.md— 운영 / 장애 대응 / Auth resilience 설정 / Vacation Posture
라이선스
MIT (개인 프로젝트)
