diff --git a/docs/ko/.i18n/README.md b/docs/ko/.i18n/README.md new file mode 100644 index 000000000..ba7175ca4 --- /dev/null +++ b/docs/ko/.i18n/README.md @@ -0,0 +1,81 @@ +--- +x-i18n: + generated_at: "2026-04-05T12:34:05Z" + model: gpt-5.4 + provider: openai + source_hash: adff26fa8858af2759b231ea48bfc01f89c110cd9b3774a8f783e282c16f77fb + source_path: .i18n/README.md + workflow: 15 +--- + +# OpenClaw 문서 i18n 자산 + +이 폴더에는 소스 문서 리포지토리용 번역 구성이 저장됩니다. + +생성된 로캘 트리와 실시간 translation memory는 이제 게시 리포지토리에 있습니다: + +- 리포지토리: `openclaw/docs` +- 로컬 체크아웃: `~/Projects/openclaw-docs` + +## 소스 오브 트루스 + +- 영어 문서는 `openclaw/openclaw`에서 작성됩니다. +- 소스 문서 트리는 `docs/` 아래에 있습니다. +- 소스 리포지토리는 더 이상 `docs/zh-CN/**`, `docs/ja-JP/**`, `docs/es/**`, `docs/pt-BR/**`, `docs/ko/**`, `docs/de/**`, `docs/fr/**`, 또는 `docs/ar/**` 같은 생성된 로캘 트리를 커밋된 상태로 유지하지 않습니다. + +## 엔드투엔드 흐름 + +1. `openclaw/openclaw`에서 영어 문서를 수정합니다. +2. `main`에 푸시합니다. +3. `openclaw/openclaw/.github/workflows/docs-sync-publish.yml`이 문서 트리를 `openclaw/docs`로 미러링합니다. +4. 동기화 스크립트는 게시 리포지토리의 `docs/docs.json`을 다시 작성하여, 소스 리포지토리에 더 이상 커밋되지 않더라도 생성된 로캘 선택기 블록이 그곳에 존재하도록 합니다. +5. `openclaw/docs/.github/workflows/translate-zh-cn.yml`은 하루에 한 번, 필요 시, 그리고 소스 리포지토리의 릴리스 디스패치 이후에 `docs/zh-CN/**`를 새로 고칩니다. +6. `openclaw/docs/.github/workflows/translate-ja-jp.yml`은 `docs/ja-JP/**`에 대해 동일하게 수행합니다. +7. `openclaw/docs/.github/workflows/translate-es.yml`, `translate-pt-br.yml`, `translate-ko.yml`, `translate-de.yml`, `translate-fr.yml`, 그리고 `translate-ar.yml`은 `docs/es/**`, `docs/pt-BR/**`, `docs/ko/**`, `docs/de/**`, `docs/fr/**`, 그리고 `docs/ar/**`에 대해 동일하게 수행합니다. + +## 분리 구조가 존재하는 이유 + +- 생성된 로캘 출력을 주 제품 리포지토리 밖에 유지하기 위해서입니다. +- Mintlify를 단일 게시 문서 트리에서 유지하기 위해서입니다. +- 게시 리포지토리가 생성된 로캘 트리를 소유하도록 하여 기본 제공 언어 전환기를 유지하기 위해서입니다. + +## 이 폴더의 파일 + +- `glossary..json` — 프롬프트 가이드로 사용되는 선호 용어 매핑. +- `ar-navigation.json`, `de-navigation.json`, `es-navigation.json`, `fr-navigation.json`, `ja-navigation.json`, `ko-navigation.json`, `pt-BR-navigation.json`, `zh-Hans-navigation.json` — 동기화 중 게시 리포지토리에 다시 삽입되는 Mintlify 로캘 선택기 블록. +- `.tm.jsonl` — 워크플로 + 모델 + 텍스트 해시를 키로 사용하는 translation memory. + +이 리포지토리에서 `docs/.i18n/zh-CN.tm.jsonl`, `docs/.i18n/ja-JP.tm.jsonl`, `docs/.i18n/es.tm.jsonl`, `docs/.i18n/pt-BR.tm.jsonl`, `docs/.i18n/ko.tm.jsonl`, `docs/.i18n/de.tm.jsonl`, `docs/.i18n/fr.tm.jsonl`, 그리고 `docs/.i18n/ar.tm.jsonl` 같은 생성된 로캘 TM 파일은 의도적으로 더 이상 커밋되지 않습니다. + +## 용어집 형식 + +`glossary..json`은 항목 배열입니다: + +```json +{ + "source": "troubleshooting", + "target": "故障排除" +} +``` + +필드: + +- `source`: 우선적으로 사용할 영어(또는 원본) 구문입니다. +- `target`: 선호되는 번역 출력입니다. + +## 번역 메커니즘 + +- `scripts/docs-i18n`은 여전히 번역 생성을 담당합니다. +- 문서 모드는 각 번역된 페이지에 `x-i18n.source_hash`를 기록합니다. +- 각 게시 워크플로는 현재 영어 소스 해시를 저장된 로캘 `x-i18n.source_hash`와 비교하여 보류 파일 목록을 미리 계산합니다. +- 보류 개수가 `0`이면 비용이 큰 번역 단계는 완전히 건너뜁니다. +- 보류 중인 파일이 있으면 워크플로는 해당 파일만 번역합니다. +- 게시 워크플로는 일시적인 모델 형식 실패를 재시도하지만, 동일한 해시 검사가 각 재시도마다 실행되므로 변경되지 않은 파일은 계속 건너뜁니다. +- 소스 리포지토리는 게시된 GitHub 릴리스 후에도 zh-CN, ja-JP, es, pt-BR, ko, de, fr, 및 ar 새로 고침을 디스패치하므로, 릴리스 문서가 일일 cron을 기다리지 않고도 따라잡을 수 있습니다. + +## 운영 참고 사항 + +- 동기화 메타데이터는 게시 리포지토리의 `.openclaw-sync/source.json`에 기록됩니다. +- 소스 리포지토리 시크릿: `OPENCLAW_DOCS_SYNC_TOKEN` +- 게시 리포지토리 시크릿: `OPENCLAW_DOCS_I18N_OPENAI_API_KEY` +- 로캘 출력이 오래된 것처럼 보이면 먼저 `openclaw/docs`에서 해당하는 `Translate ` 워크플로를 확인하세요. diff --git a/docs/ko/auth-credential-semantics.md b/docs/ko/auth-credential-semantics.md new file mode 100644 index 000000000..cb789fd33 --- /dev/null +++ b/docs/ko/auth-credential-semantics.md @@ -0,0 +1,79 @@ +--- +read_when: + - 인증 프로필 해석 또는 자격 증명 라우팅 작업 중 + - 모델 인증 실패 또는 프로필 순서를 디버깅하는 중 +summary: 인증 프로필에 대한 정식 자격 증명 적격성 및 해석 의미 체계 +title: 인증 자격 증명 의미 체계 +x-i18n: + generated_at: "2026-04-05T12:34:03Z" + model: gpt-5.4 + provider: openai + source_hash: a4cd3e16cd25eb22c5e707311d06a19df1a59747ee3261c2d32c534a245fd7fb + source_path: auth-credential-semantics.md + workflow: 15 +--- + +# 인증 자격 증명 의미 체계 + +이 문서는 다음 전반에서 사용되는 정식 자격 증명 적격성 및 해석 의미 체계를 정의합니다. + +- `resolveAuthProfileOrder` +- `resolveApiKeyForProfile` +- `models status --probe` +- `doctor-auth` + +목표는 선택 시점 동작과 런타임 동작이 일치하도록 유지하는 것입니다. + +## 안정적인 프로브 이유 코드 + +- `ok` +- `excluded_by_auth_order` +- `missing_credential` +- `invalid_expires` +- `expired` +- `unresolved_ref` +- `no_model` + +## 토큰 자격 증명 + +토큰 자격 증명(`type: "token"`)은 인라인 `token` 및/또는 `tokenRef`를 지원합니다. + +### 적격성 규칙 + +1. `token`과 `tokenRef`가 모두 없으면 토큰 프로필은 부적격입니다. +2. `expires`는 선택 사항입니다. +3. `expires`가 있으면 `0`보다 큰 유한한 숫자여야 합니다. +4. `expires`가 유효하지 않으면(`NaN`, `0`, 음수, 비유한값 또는 잘못된 타입) 프로필은 `invalid_expires`와 함께 부적격입니다. +5. `expires`가 과거 시점이면 프로필은 `expired`와 함께 부적격입니다. +6. `tokenRef`는 `expires` 검사를 우회하지 않습니다. + +### 해석 규칙 + +1. 리졸버 의미 체계는 `expires`에 대해 적격성 의미 체계와 일치합니다. +2. 적격한 프로필의 경우 토큰 자료는 인라인 값 또는 `tokenRef`에서 해석될 수 있습니다. +3. 해석할 수 없는 ref는 `models status --probe` 출력에서 `unresolved_ref`를 생성합니다. + +## 명시적 인증 순서 필터링 + +- 공급자에 대해 `auth.order.` 또는 인증 저장소 순서 재정의가 설정되면 `models status --probe`는 해당 공급자에 대해 해석된 인증 순서에 남아 있는 프로필 ID만 프로브합니다. +- 해당 공급자에 대한 저장된 프로필이 명시적 순서에서 생략된 경우 나중에 조용히 시도되지 않습니다. 프로브 출력은 이를 `reasonCode: excluded_by_auth_order` 및 세부 정보 `Excluded by auth.order for this provider.`와 함께 보고합니다. + +## 프로브 대상 해석 + +- 프로브 대상은 인증 프로필, 환경 자격 증명 또는 `models.json`에서 올 수 있습니다. +- 공급자에 자격 증명이 있지만 OpenClaw가 해당 공급자에 대해 프로브 가능한 모델 후보를 해석할 수 없으면 `models status --probe`는 `reasonCode: no_model`과 함께 `status: no_model`을 보고합니다. + +## OAuth SecretRef 정책 가드 + +- SecretRef 입력은 정적 자격 증명에만 사용됩니다. +- 프로필 자격 증명이 `type: "oauth"`이면 해당 프로필 자격 증명 자료에 대해 SecretRef 객체는 지원되지 않습니다. +- `auth.profiles..mode`가 `"oauth"`이면 해당 프로필에 대한 SecretRef 기반 `keyRef`/`tokenRef` 입력은 거부됩니다. +- 위반은 시작/리로드 인증 해석 경로에서 강한 실패로 처리됩니다. + +## 레거시 호환 메시지 + +스크립트 호환성을 위해 프로브 오류는 이 첫 번째 줄을 변경하지 않고 유지합니다. + +`Auth profile credentials are missing or expired.` + +사람이 이해하기 쉬운 세부 정보와 안정적인 이유 코드는 후속 줄에 추가될 수 있습니다. diff --git a/docs/ko/automation/auth-monitoring.md b/docs/ko/automation/auth-monitoring.md new file mode 100644 index 000000000..b41ac463a --- /dev/null +++ b/docs/ko/automation/auth-monitoring.md @@ -0,0 +1,15 @@ +--- +summary: '`/gateway/authentication`로 리디렉션' +title: 인증 모니터링 +x-i18n: + generated_at: "2026-04-05T12:33:52Z" + model: gpt-5.4 + provider: openai + source_hash: 42aa1b4aa76201b20e07d8ad77231607855e7e7421fa032830055144ccd8819a + source_path: automation/auth-monitoring.md + workflow: 15 +--- + +# 인증 모니터링 + +이 페이지는 [인증](/gateway/authentication)(으)로 이동했습니다. 인증 모니터링 문서는 [인증](/gateway/authentication)에서 확인하세요. diff --git a/docs/ko/automation/clawflow.md b/docs/ko/automation/clawflow.md new file mode 100644 index 000000000..5e602e27b --- /dev/null +++ b/docs/ko/automation/clawflow.md @@ -0,0 +1,15 @@ +--- +summary: Task Flow로 리디렉션 +title: ClawFlow +x-i18n: + generated_at: "2026-04-05T12:33:52Z" + model: gpt-5.4 + provider: openai + source_hash: e644237e0812f25b46a06ff125c8858dbc69a499aa43cfd73cf7cb37572e78e6 + source_path: automation/clawflow.md + workflow: 15 +--- + +# ClawFlow + +ClawFlow는 [Task Flow](/automation/taskflow)로 이름이 변경되었습니다. 현재 문서는 [Task Flow](/automation/taskflow)를 참조하세요. diff --git a/docs/ko/automation/cron-jobs.md b/docs/ko/automation/cron-jobs.md new file mode 100644 index 000000000..f43249cd0 --- /dev/null +++ b/docs/ko/automation/cron-jobs.md @@ -0,0 +1,386 @@ +--- +read_when: + - 백그라운드 작업 또는 깨우기 예약 + - 외부 트리거(웹훅, Gmail)를 OpenClaw에 연결 + - 예약 작업에 heartbeat와 cron 중 무엇을 사용할지 결정 +summary: Gateway 스케줄러를 위한 예약 작업, 웹훅, Gmail PubSub 트리거 +title: 예약 작업 +x-i18n: + generated_at: "2026-04-05T12:34:54Z" + model: gpt-5.4 + provider: openai + source_hash: 43b906914461aba9af327e7e8c22aa856f65802ec2da37ed0c4f872d229cfde6 + source_path: automation/cron-jobs.md + workflow: 15 +--- + +# 예약 작업 (Cron) + +Cron은 Gateway에 내장된 스케줄러입니다. 작업을 영속적으로 저장하고, 적절한 시간에 에이전트를 깨우며, 출력 결과를 채팅 채널이나 웹훅 엔드포인트로 다시 전달할 수 있습니다. + +## 빠른 시작 + +```bash +# 일회성 리마인더 추가 +openclaw cron add \ + --name "Reminder" \ + --at "2026-02-01T16:00:00Z" \ + --session main \ + --system-event "Reminder: check the cron docs draft" \ + --wake now \ + --delete-after-run + +# 작업 확인 +openclaw cron list + +# 실행 기록 보기 +openclaw cron runs --id +``` + +## cron 작동 방식 + +- Cron은 **Gateway 프로세스 내부에서** 실행됩니다(모델 내부가 아님). +- 작업은 `~/.openclaw/cron/jobs.json`에 영속 저장되므로 재시작해도 일정이 사라지지 않습니다. +- 모든 cron 실행은 [background task](/automation/tasks) 레코드를 생성합니다. +- 일회성 작업(`--at`)은 기본적으로 성공 후 자동 삭제됩니다. +- 격리된 cron 실행은 완료 시 해당 `cron:` 세션에 대해 추적된 브라우저 탭/프로세스를 최선의 노력으로 종료하므로, 분리된 브라우저 자동화가 고아 프로세스를 남기지 않습니다. +- 격리된 cron 실행은 오래된 확인 응답도 방지합니다. 첫 번째 결과가 단지 중간 상태 업데이트(`on it`, `pulling everything together` 및 유사한 힌트)이고, 최종 답변에 여전히 책임이 있는 하위 subagent 실행이 없다면, OpenClaw는 전달 전에 실제 결과를 얻기 위해 한 번 더 다시 프롬프트합니다. + +cron의 작업 조정은 런타임이 소유합니다. 오래된 하위 세션 행이 여전히 존재하더라도, 활성 cron 작업은 cron 런타임이 해당 작업을 실행 중으로 추적하는 동안 계속 활성 상태로 유지됩니다. +런타임이 더 이상 작업을 소유하지 않고 5분 유예 시간이 지나면, 유지보수에서 해당 작업을 `lost`로 표시할 수 있습니다. + +## 일정 유형 + +| 종류 | CLI 플래그 | 설명 | +| ------- | ---------- | --------------------------------------------------------- | +| `at` | `--at` | 일회성 타임스탬프(ISO 8601 또는 `20m` 같은 상대 시간) | +| `every` | `--every` | 고정 간격 | +| `cron` | `--cron` | 선택적 `--tz`와 함께 사용하는 5필드 또는 6필드 cron 표현식 | + +타임존이 없는 타임스탬프는 UTC로 처리됩니다. 로컬 벽시계 기준 예약에는 `--tz America/New_York`를 추가하세요. + +정시 반복 표현식은 부하 급증을 줄이기 위해 최대 5분까지 자동으로 분산됩니다. 정확한 타이밍을 강제하려면 `--exact`를 사용하고, 명시적인 범위를 원하면 `--stagger 30s`를 사용하세요. + +## 실행 스타일 + +| 스타일 | `--session` 값 | 실행 위치 | 적합한 용도 | +| -------------- | ------------------- | ------------------------- | ------------------------------- | +| 메인 세션 | `main` | 다음 heartbeat 턴 | 리마인더, 시스템 이벤트 | +| 격리됨 | `isolated` | 전용 `cron:` | 리포트, 백그라운드 작업 | +| 현재 세션 | `current` | 생성 시점에 바인딩됨 | 컨텍스트 인지형 반복 작업 | +| 커스텀 세션 | `session:custom-id` | 영속적인 이름 있는 세션 | 기록을 기반으로 쌓이는 워크플로 | + +**메인 세션** 작업은 시스템 이벤트를 큐에 넣고 선택적으로 heartbeat를 깨웁니다(`--wake now` 또는 `--wake next-heartbeat`). **격리됨** 작업은 새 세션으로 전용 에이전트 턴을 실행합니다. **커스텀 세션**(`session:xxx`)은 실행 간 컨텍스트를 유지하므로, 이전 요약을 바탕으로 쌓이는 일일 스탠드업 같은 워크플로를 가능하게 합니다. + +격리된 작업의 경우, 런타임 종료 단계에는 이제 해당 cron 세션에 대한 최선의 노력 기반 브라우저 정리가 포함됩니다. 정리 실패는 실제 cron 결과가 우선되도록 무시됩니다. + +격리된 cron 실행이 subagent를 조정할 때는, 전달 시에도 오래된 상위 중간 텍스트보다 최종 하위 결과를 우선합니다. 하위 실행이 아직 진행 중이면, OpenClaw는 그 부분적인 상위 업데이트를 알리는 대신 억제합니다. + +### 격리된 작업의 페이로드 옵션 + +- `--message`: 프롬프트 텍스트(격리됨에서 필수) +- `--model` / `--thinking`: 모델 및 thinking 수준 재정의 +- `--light-context`: 워크스페이스 부트스트랩 파일 주입 건너뛰기 +- `--tools exec,read`: 작업이 사용할 수 있는 도구 제한 + +`--model`은 해당 작업에 대해 선택된 허용 모델을 사용합니다. 요청한 모델이 허용되지 않으면 cron은 경고를 기록하고, 대신 작업의 agent/default 모델 선택으로 대체합니다. 구성된 fallback 체인은 계속 적용되지만, 명시적 작업별 fallback 목록이 없는 단순 모델 재정의는 더 이상 숨겨진 추가 재시도 대상으로 agent primary를 덧붙이지 않습니다. + +격리된 작업의 모델 선택 우선순위는 다음과 같습니다. + +1. Gmail 훅 모델 재정의(실행이 Gmail에서 왔고 해당 재정의가 허용된 경우) +2. 작업별 페이로드 `model` +3. 저장된 cron 세션 모델 재정의 +4. Agent/default 모델 선택 + +빠른 모드도 해결된 라이브 선택을 따릅니다. 선택된 모델 구성에 `params.fastMode`가 있으면, 격리된 cron은 기본적으로 이를 사용합니다. 저장된 세션 `fastMode` 재정의는 어느 방향에서든 여전히 구성보다 우선합니다. + +격리된 실행이 라이브 모델 전환 handoff에 도달하면, cron은 전환된 provider/model로 재시도하고 재시도 전에 해당 라이브 선택을 영속 저장합니다. 전환에 새 auth profile도 포함되어 있으면, cron은 그 auth profile 재정의도 저장합니다. 재시도는 제한됩니다. 초기 시도에 더해 전환 재시도 2회를 넘기면, cron은 무한 반복하는 대신 중단합니다. + +## 전달 및 출력 + +| 모드 | 동작 | +| ---------- | ------------------------------------------------------------ | +| `announce` | 요약을 대상 채널로 전달(격리됨의 기본값) | +| `webhook` | 완료된 이벤트 페이로드를 URL로 POST | +| `none` | 내부 전용, 전달 없음 | + +채널 전달에는 `--announce --channel telegram --to "-1001234567890"`를 사용하세요. Telegram 포럼 주제의 경우 `-1001234567890:topic:123`을 사용하세요. Slack/Discord/Mattermost 대상은 명시적 접두사(`channel:`, `user:`)를 사용해야 합니다. + +cron이 소유한 격리된 작업의 경우, 실행기가 최종 전달 경로를 소유합니다. 에이전트는 일반 텍스트 요약을 반환하도록 프롬프트되며, 그 요약은 이후 `announce`, `webhook`으로 전송되거나 `none`일 경우 내부에만 유지됩니다. `--no-deliver`는 전달을 에이전트에 되돌리지 않으며, 실행을 내부 전용으로 유지합니다. + +원래 작업이 어떤 외부 수신자에게 메시지를 보내라고 명시적으로 지시한 경우, 에이전트는 직접 전송을 시도하는 대신 그 메시지가 누구/어디로 가야 하는지를 출력에 적어야 합니다. + +실패 알림은 별도의 대상 경로를 따릅니다. + +- `cron.failureDestination`은 실패 알림의 전역 기본값을 설정합니다. +- `job.delivery.failureDestination`은 작업별로 이를 재정의합니다. +- 둘 다 설정되지 않았고 작업이 이미 `announce`를 통해 전달하는 경우, 실패 알림은 이제 기본 announce 대상으로 대체됩니다. +- `delivery.failureDestination`은 기본 전달 모드가 `webhook`인 경우를 제외하면 `sessionTarget="isolated"` 작업에서만 지원됩니다. + +## CLI 예시 + +일회성 리마인더(메인 세션): + +```bash +openclaw cron add \ + --name "Calendar check" \ + --at "20m" \ + --session main \ + --system-event "Next heartbeat: check calendar." \ + --wake now +``` + +전달이 포함된 반복 격리 작업: + +```bash +openclaw cron add \ + --name "Morning brief" \ + --cron "0 7 * * *" \ + --tz "America/Los_Angeles" \ + --session isolated \ + --message "Summarize overnight updates." \ + --announce \ + --channel slack \ + --to "channel:C1234567890" +``` + +모델 및 thinking 재정의가 포함된 격리 작업: + +```bash +openclaw cron add \ + --name "Deep analysis" \ + --cron "0 6 * * 1" \ + --tz "America/Los_Angeles" \ + --session isolated \ + --message "Weekly deep analysis of project progress." \ + --model "opus" \ + --thinking high \ + --announce +``` + +## 웹훅 + +Gateway는 외부 트리거를 위해 HTTP 웹훅 엔드포인트를 노출할 수 있습니다. config에서 활성화하세요. + +```json5 +{ + hooks: { + enabled: true, + token: "shared-secret", + path: "/hooks", + }, +} +``` + +### 인증 + +모든 요청에는 헤더를 통해 훅 토큰이 포함되어야 합니다. + +- `Authorization: Bearer `(권장) +- `x-openclaw-token: ` + +쿼리 문자열 토큰은 거부됩니다. + +### POST /hooks/wake + +메인 세션에 시스템 이벤트를 큐에 넣습니다. + +```bash +curl -X POST http://127.0.0.1:18789/hooks/wake \ + -H 'Authorization: Bearer SECRET' \ + -H 'Content-Type: application/json' \ + -d '{"text":"New email received","mode":"now"}' +``` + +- `text`(필수): 이벤트 설명 +- `mode`(선택 사항): `now`(기본값) 또는 `next-heartbeat` + +### POST /hooks/agent + +격리된 에이전트 턴을 실행합니다. + +```bash +curl -X POST http://127.0.0.1:18789/hooks/agent \ + -H 'Authorization: Bearer SECRET' \ + -H 'Content-Type: application/json' \ + -d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.4-mini"}' +``` + +필드: `message`(필수), `name`, `agentId`, `wakeMode`, `deliver`, `channel`, `to`, `model`, `thinking`, `timeoutSeconds`. + +### 매핑된 훅(POST /hooks/\) + +커스텀 훅 이름은 config의 `hooks.mappings`를 통해 해석됩니다. 매핑은 템플릿이나 코드 변환을 사용해 임의의 페이로드를 `wake` 또는 `agent` 작업으로 변환할 수 있습니다. + +### 보안 + +- 훅 엔드포인트는 loopback, tailnet 또는 신뢰할 수 있는 리버스 프록시 뒤에 두세요. +- 전용 훅 토큰을 사용하세요. gateway auth 토큰을 재사용하지 마세요. +- `hooks.path`는 전용 하위 경로로 유지하세요. `/`는 거부됩니다. +- 명시적 `agentId` 라우팅을 제한하려면 `hooks.allowedAgentIds`를 설정하세요. +- 호출자가 세션을 선택해야 하는 경우가 아니라면 `hooks.allowRequestSessionKey=false`를 유지하세요. +- `hooks.allowRequestSessionKey`를 활성화하는 경우, 허용된 세션 키 형태를 제한하기 위해 `hooks.allowedSessionKeyPrefixes`도 함께 설정하세요. +- 훅 페이로드는 기본적으로 안전 경계로 감싸집니다. + +## Gmail PubSub 통합 + +Google PubSub를 통해 Gmail 받은편지함 트리거를 OpenClaw에 연결하세요. + +**사전 요구 사항**: `gcloud` CLI, `gog`(gogcli), OpenClaw hooks 활성화, 공개 HTTPS 엔드포인트용 Tailscale. + +### 마법사 설정(권장) + +```bash +openclaw webhooks gmail setup --account openclaw@gmail.com +``` + +이 명령은 `hooks.gmail` config를 기록하고, Gmail 프리셋을 활성화하며, 푸시 엔드포인트에 Tailscale Funnel을 사용합니다. + +### Gateway 자동 시작 + +`hooks.enabled=true`이고 `hooks.gmail.account`가 설정되어 있으면, Gateway는 부팅 시 `gog gmail watch serve`를 시작하고 watch를 자동 갱신합니다. 제외하려면 `OPENCLAW_SKIP_GMAIL_WATCHER=1`을 설정하세요. + +### 수동 1회 설정 + +1. `gog`가 사용하는 OAuth 클라이언트를 소유한 GCP 프로젝트를 선택합니다. + +```bash +gcloud auth login +gcloud config set project +gcloud services enable gmail.googleapis.com pubsub.googleapis.com +``` + +2. 토픽을 만들고 Gmail 푸시 액세스 권한을 부여합니다. + +```bash +gcloud pubsub topics create gog-gmail-watch +gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \ + --member=serviceAccount:gmail-api-push@system.gserviceaccount.com \ + --role=roles/pubsub.publisher +``` + +3. watch를 시작합니다. + +```bash +gog gmail watch start \ + --account openclaw@gmail.com \ + --label INBOX \ + --topic projects//topics/gog-gmail-watch +``` + +### Gmail 모델 재정의 + +```json5 +{ + hooks: { + gmail: { + model: "openrouter/meta-llama/llama-3.3-70b-instruct:free", + thinking: "off", + }, + }, +} +``` + +## 작업 관리 + +```bash +# 모든 작업 나열 +openclaw cron list + +# 작업 편집 +openclaw cron edit --message "Updated prompt" --model "opus" + +# 지금 작업 강제 실행 +openclaw cron run + +# 기한이 지난 경우에만 실행 +openclaw cron run --due + +# 실행 기록 보기 +openclaw cron runs --id --limit 50 + +# 작업 삭제 +openclaw cron remove + +# Agent 선택(멀티 agent 설정) +openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops +openclaw cron edit --clear-agent +``` + +모델 재정의 참고: + +- `openclaw cron add|edit --model ...`은 작업의 선택된 모델을 변경합니다. +- 모델이 허용되면, 정확히 그 provider/model이 격리된 agent 실행에 전달됩니다. +- 허용되지 않으면, cron은 경고를 표시하고 작업의 agent/default 모델 선택으로 대체합니다. +- 구성된 fallback 체인은 계속 적용되지만, 명시적 작업별 fallback 목록이 없는 일반 `--model` 재정의는 더 이상 조용한 추가 재시도 대상으로 agent primary로 넘어가지 않습니다. + +## 구성 + +```json5 +{ + cron: { + enabled: true, + store: "~/.openclaw/cron/jobs.json", + maxConcurrentRuns: 1, + retry: { + maxAttempts: 3, + backoffMs: [60000, 120000, 300000], + retryOn: ["rate_limit", "overloaded", "network", "server_error"], + }, + webhookToken: "replace-with-dedicated-webhook-token", + sessionRetention: "24h", + runLog: { maxBytes: "2mb", keepLines: 2000 }, + }, +} +``` + +cron 비활성화: `cron.enabled: false` 또는 `OPENCLAW_SKIP_CRON=1`. + +**일회성 재시도**: 일시적 오류(rate limit, overload, network, server error)는 지수 백오프와 함께 최대 3회 재시도합니다. 영구 오류는 즉시 비활성화됩니다. + +**반복 재시도**: 재시도 사이에 지수 백오프(30초~60분)를 사용합니다. 다음 성공 실행 후에는 백오프가 초기화됩니다. + +**유지보수**: `cron.sessionRetention`(기본값 `24h`)은 격리된 실행 세션 항목을 정리합니다. `cron.runLog.maxBytes` / `cron.runLog.keepLines`는 실행 로그 파일을 자동 정리합니다. + +## 문제 해결 + +### 명령 순서 + +```bash +openclaw status +openclaw gateway status +openclaw cron status +openclaw cron list +openclaw cron runs --id --limit 20 +openclaw system heartbeat last +openclaw logs --follow +openclaw doctor +``` + +### Cron이 실행되지 않음 + +- `cron.enabled`와 `OPENCLAW_SKIP_CRON` 환경 변수를 확인하세요. +- Gateway가 계속 실행 중인지 확인하세요. +- `cron` 일정의 경우 타임존(`--tz`)과 호스트 타임존이 일치하는지 확인하세요. +- 실행 출력의 `reason: not-due`는 수동 실행이 `openclaw cron run --due`로 확인되었고 작업 기한이 아직 되지 않았음을 의미합니다. + +### Cron이 실행되었지만 전달이 없음 + +- 전달 모드가 `none`이면 외부 메시지는 예상되지 않습니다. +- 전달 대상이 없거나 잘못된 경우(`channel`/`to`) 아웃바운드가 건너뛰어집니다. +- 채널 인증 오류(`unauthorized`, `Forbidden`)는 자격 증명 때문에 전달이 차단되었음을 의미합니다. +- 격리된 실행이 무음 토큰(`NO_REPLY` / `no_reply`)만 반환하면, OpenClaw는 직접 아웃바운드 전달과 fallback 대기열 요약 경로도 모두 억제하므로 채팅으로 아무것도 게시되지 않습니다. +- cron이 소유한 격리된 작업의 경우, fallback으로 에이전트가 message 도구를 사용할 것이라 기대하지 마세요. 최종 전달은 실행기가 소유하며, `--no-deliver`는 직접 전송을 허용하는 대신 내부 전용으로 유지합니다. + +### 타임존 관련 주의사항 + +- `--tz`가 없는 cron은 gateway 호스트 타임존을 사용합니다. +- 타임존이 없는 `at` 일정은 UTC로 처리됩니다. +- Heartbeat `activeHours`는 구성된 타임존 해석을 사용합니다. + +## 관련 항목 + +- [Automation & Tasks](/automation) — 모든 자동화 메커니즘 한눈에 보기 +- [Background Tasks](/automation/tasks) — cron 실행을 위한 작업 원장 +- [Heartbeat](/gateway/heartbeat) — 주기적인 메인 세션 턴 +- [Timezone](/concepts/timezone) — 타임존 구성 diff --git a/docs/ko/automation/cron-vs-heartbeat.md b/docs/ko/automation/cron-vs-heartbeat.md new file mode 100644 index 000000000..67dfd85e4 --- /dev/null +++ b/docs/ko/automation/cron-vs-heartbeat.md @@ -0,0 +1,15 @@ +--- +summary: /automation으로 리디렉션 +title: Cron vs Heartbeat +x-i18n: + generated_at: "2026-04-05T12:33:52Z" + model: gpt-5.4 + provider: openai + source_hash: 579c6618108ad7e2a71f53d5d6aa6550956fa0fdb2fe64ecdb7e8a0ce1366e33 + source_path: automation/cron-vs-heartbeat.md + workflow: 15 +--- + +# Cron vs Heartbeat + +이 페이지는 [Automation & Tasks](/automation)(으)로 이동했습니다. cron과 heartbeat를 비교하는 결정 가이드는 [Automation & Tasks](/automation)를 참조하세요. diff --git a/docs/ko/automation/gmail-pubsub.md b/docs/ko/automation/gmail-pubsub.md new file mode 100644 index 000000000..1b93c6260 --- /dev/null +++ b/docs/ko/automation/gmail-pubsub.md @@ -0,0 +1,15 @@ +--- +summary: /automation/cron-jobs로 리디렉션 +title: Gmail PubSub +x-i18n: + generated_at: "2026-04-05T12:33:53Z" + model: gpt-5.4 + provider: openai + source_hash: c2e17fdc8c09d52b6dc9fb6c44053446f07d02c65a0ab725d4ea038bd035d889 + source_path: automation/gmail-pubsub.md + workflow: 15 +--- + +# Gmail PubSub + +이 페이지는 [예약된 작업](/automation/cron-jobs#gmail-pubsub-integration)(으)로 이동했습니다. Gmail PubSub 문서는 [예약된 작업](/automation/cron-jobs#gmail-pubsub-integration)을 참조하세요. diff --git a/docs/ko/automation/hooks.md b/docs/ko/automation/hooks.md new file mode 100644 index 000000000..d0df88026 --- /dev/null +++ b/docs/ko/automation/hooks.md @@ -0,0 +1,310 @@ +--- +read_when: + - /new, /reset, /stop 및 에이전트 수명 주기 이벤트에 대한 이벤트 기반 자동화를 원합니다 + - Hooks를 빌드, 설치 또는 디버그하려고 합니다 +summary: 'Hooks: 명령 및 수명 주기 이벤트를 위한 이벤트 기반 자동화' +title: Hooks +x-i18n: + generated_at: "2026-04-05T12:34:20Z" + model: gpt-5.4 + provider: openai + source_hash: 66eb75bb2b3b2ad229bf3da24fdb0fe021ed08f812fd1d13c69b3bd9df0218e5 + source_path: automation/hooks.md + workflow: 15 +--- + +# Hooks + +Hooks는 Gateway 내부에서 어떤 일이 발생할 때 실행되는 작은 스크립트입니다. 디렉터리에서 자동으로 검색되며 `openclaw hooks`로 확인할 수 있습니다. + +OpenClaw에는 두 종류의 hooks가 있습니다. + +- **내부 hooks**(이 페이지): `/new`, `/reset`, `/stop` 또는 수명 주기 이벤트처럼 에이전트 이벤트가 발생할 때 Gateway 내부에서 실행됩니다. +- **웹훅**: 다른 시스템이 OpenClaw에서 작업을 트리거할 수 있게 하는 외부 HTTP 엔드포인트입니다. [Webhooks](/automation/cron-jobs#webhooks)를 참조하세요. + +Hooks는 plugins 내부에 번들로 포함될 수도 있습니다. `openclaw hooks list`는 독립형 hooks와 plugin이 관리하는 hooks를 모두 표시합니다. + +## 빠른 시작 + +```bash +# 사용 가능한 hooks 나열 +openclaw hooks list + +# hook 활성화 +openclaw hooks enable session-memory + +# hook 상태 확인 +openclaw hooks check + +# 자세한 정보 가져오기 +openclaw hooks info session-memory +``` + +## 이벤트 유형 + +| 이벤트 | 발생 시점 | +| ------------------------ | ------------------------------------------------ | +| `command:new` | `/new` 명령이 실행될 때 | +| `command:reset` | `/reset` 명령이 실행될 때 | +| `command:stop` | `/stop` 명령이 실행될 때 | +| `command` | 모든 명령 이벤트(일반 리스너) | +| `session:compact:before` | 압축이 기록을 요약하기 전 | +| `session:compact:after` | 압축이 완료된 후 | +| `session:patch` | 세션 속성이 수정될 때 | +| `agent:bootstrap` | 워크스페이스 bootstrap 파일이 주입되기 전 | +| `gateway:startup` | 채널이 시작되고 hooks가 로드된 후 | +| `message:received` | 모든 채널의 수신 메시지 | +| `message:transcribed` | 오디오 전사가 완료된 후 | +| `message:preprocessed` | 모든 미디어 및 링크 이해가 완료된 후 | +| `message:sent` | 발신 메시지가 전달된 후 | + +## Hooks 작성하기 + +### Hook 구조 + +각 hook은 두 개의 파일이 포함된 디렉터리입니다. + +``` +my-hook/ +├── HOOK.md # 메타데이터 + 문서 +└── handler.ts # 핸들러 구현 +``` + +### HOOK.md 형식 + +```markdown +--- +name: my-hook +description: "이 hook이 수행하는 작업에 대한 짧은 설명" +metadata: + { "openclaw": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } } +--- + +# My Hook + +여기에 자세한 문서를 작성합니다. +``` + +**메타데이터 필드** (`metadata.openclaw`): + +| 필드 | 설명 | +| ---------- | ---------------------------------------------------- | +| `emoji` | CLI용 표시 이모지 | +| `events` | 수신할 이벤트 배열 | +| `export` | 사용할 이름 있는 export(기본값: `"default"`) | +| `os` | 필요한 플랫폼(예: `["darwin", "linux"]`) | +| `requires` | 필요한 `bins`, `anyBins`, `env` 또는 `config` 경로 | +| `always` | 적격성 검사 우회(boolean) | +| `install` | 설치 방법 | + +### 핸들러 구현 + +```typescript +const handler = async (event) => { + if (event.type !== "command" || event.action !== "new") { + return; + } + + console.log(`[my-hook] New command triggered`); + // 여기에 로직 작성 + + // 선택적으로 사용자에게 메시지 전송 + event.messages.push("Hook executed!"); +}; + +export default handler; +``` + +각 이벤트에는 `type`, `action`, `sessionKey`, `timestamp`, `messages`(사용자에게 보내려면 push), `context`(이벤트별 데이터)가 포함됩니다. + +### 이벤트 컨텍스트 핵심 항목 + +**명령 이벤트** (`command:new`, `command:reset`): `context.sessionEntry`, `context.previousSessionEntry`, `context.commandSource`, `context.workspaceDir`, `context.cfg`. + +**메시지 이벤트** (`message:received`): `context.from`, `context.content`, `context.channelId`, `context.metadata` (`senderId`, `senderName`, `guildId`를 포함한 provider별 데이터). + +**메시지 이벤트** (`message:sent`): `context.to`, `context.content`, `context.success`, `context.channelId`. + +**메시지 이벤트** (`message:transcribed`): `context.transcript`, `context.from`, `context.channelId`, `context.mediaPath`. + +**메시지 이벤트** (`message:preprocessed`): `context.bodyForAgent`(최종 보강된 본문), `context.from`, `context.channelId`. + +**Bootstrap 이벤트** (`agent:bootstrap`): `context.bootstrapFiles`(변경 가능한 배열), `context.agentId`. + +**세션 패치 이벤트** (`session:patch`): `context.sessionEntry`, `context.patch`(변경된 필드만), `context.cfg`. 패치 이벤트는 권한이 있는 클라이언트만 트리거할 수 있습니다. + +**압축 이벤트**: `session:compact:before`에는 `messageCount`, `tokenCount`가 포함됩니다. `session:compact:after`에는 `compactedCount`, `summaryLength`, `tokensBefore`, `tokensAfter`가 추가됩니다. + +## Hook 검색 + +Hooks는 다음 디렉터리에서 검색되며, 아래로 갈수록 재정의 우선순위가 높아집니다. + +1. **번들 hooks**: OpenClaw와 함께 제공됨 +2. **Plugin hooks**: 설치된 plugins 내부에 번들된 hooks +3. **관리형 hooks**: `~/.openclaw/hooks/` (사용자가 설치하며 워크스페이스 간 공유). `hooks.internal.load.extraDirs`의 추가 디렉터리도 이 우선순위를 공유합니다. +4. **워크스페이스 hooks**: `/hooks/` (에이전트별, 명시적으로 활성화하기 전까지 기본적으로 비활성화됨) + +워크스페이스 hooks는 새 hook 이름을 추가할 수 있지만, 같은 이름의 번들, 관리형 또는 plugin 제공 hooks를 재정의할 수는 없습니다. + +### Hook 팩 + +Hook 팩은 `package.json`의 `openclaw.hooks`를 통해 hooks를 export하는 npm 패키지입니다. 설치 방법: + +```bash +openclaw plugins install +``` + +Npm spec은 레지스트리 전용입니다(패키지 이름 + 선택적 정확한 버전 또는 dist-tag). Git/URL/file spec 및 semver 범위는 거부됩니다. + +## 번들 hooks + +| Hook | 이벤트 | 수행 작업 | +| --------------------- | ------------------------------ | ----------------------------------------------------- | +| session-memory | `command:new`, `command:reset` | 세션 컨텍스트를 `/memory/`에 저장 | +| bootstrap-extra-files | `agent:bootstrap` | glob 패턴에서 추가 bootstrap 파일 주입 | +| command-logger | `command` | 모든 명령을 `~/.openclaw/logs/commands.log`에 기록 | +| boot-md | `gateway:startup` | gateway 시작 시 `BOOT.md` 실행 | + +번들 hook 활성화: + +```bash +openclaw hooks enable +``` + +### session-memory 세부 정보 + +최근 사용자/assistant 메시지 15개를 추출하고, LLM을 통해 설명적인 파일명 슬러그를 생성한 뒤 `/memory/YYYY-MM-DD-slug.md`에 저장합니다. `workspace.dir`이 구성되어 있어야 합니다. + +### bootstrap-extra-files 구성 + +```json +{ + "hooks": { + "internal": { + "entries": { + "bootstrap-extra-files": { + "enabled": true, + "paths": ["packages/*/AGENTS.md", "packages/*/TOOLS.md"] + } + } + } + } +} +``` + +경로는 워크스페이스를 기준으로 해석됩니다. 인식된 bootstrap basename만 로드됩니다(`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `MEMORY.md`). + +## Plugin hooks + +Plugins는 더 깊은 통합을 위해 Plugin SDK를 통해 hooks를 등록할 수 있습니다. 예를 들어 도구 호출 가로채기, 프롬프트 수정, 메시지 흐름 제어 등이 가능합니다. Plugin SDK는 모델 확인, 에이전트 수명 주기, 메시지 흐름, 도구 실행, 서브에이전트 조정, gateway 수명 주기를 포괄하는 28개의 hooks를 제공합니다. + +`before_tool_call`, `before_agent_reply`, `before_install` 및 기타 모든 plugin hooks를 포함한 전체 plugin hook 참조는 [Plugin Architecture](/plugins/architecture#provider-runtime-hooks)를 참조하세요. + +## 구성 + +```json +{ + "hooks": { + "internal": { + "enabled": true, + "entries": { + "session-memory": { "enabled": true }, + "command-logger": { "enabled": false } + } + } + } +} +``` + +Hook별 환경 변수: + +```json +{ + "hooks": { + "internal": { + "entries": { + "my-hook": { + "enabled": true, + "env": { "MY_CUSTOM_VAR": "value" } + } + } + } + } +} +``` + +추가 hook 디렉터리: + +```json +{ + "hooks": { + "internal": { + "load": { + "extraDirs": ["/path/to/more/hooks"] + } + } + } +} +``` + + +레거시 `hooks.internal.handlers` 배열 구성 형식도 이전 버전과의 호환성을 위해 계속 지원되지만, 새 hooks는 검색 기반 시스템을 사용해야 합니다. + + +## CLI 참조 + +```bash +# 모든 hooks 나열(--eligible, --verbose 또는 --json 추가 가능) +openclaw hooks list + +# hook의 자세한 정보 표시 +openclaw hooks info + +# 적격성 요약 표시 +openclaw hooks check + +# 활성화/비활성화 +openclaw hooks enable +openclaw hooks disable +``` + +## 모범 사례 + +- **핸들러를 빠르게 유지하세요.** Hooks는 명령 처리 중 실행됩니다. 무거운 작업은 `void processInBackground(event)`로 실행만 시작하고 기다리지 마세요. +- **오류를 우아하게 처리하세요.** 위험한 작업은 try/catch로 감싸고, 다른 핸들러도 실행될 수 있도록 throw하지 마세요. +- **이벤트를 초기에 필터링하세요.** 이벤트 type/action이 관련 없으면 즉시 반환하세요. +- **구체적인 이벤트 키를 사용하세요.** 오버헤드를 줄이기 위해 `"events": ["command"]`보다 `"events": ["command:new"]`를 권장합니다. + +## 문제 해결 + +### Hook이 검색되지 않음 + +```bash +# 디렉터리 구조 확인 +ls -la ~/.openclaw/hooks/my-hook/ +# 표시되어야 함: HOOK.md, handler.ts + +# 검색된 모든 hooks 나열 +openclaw hooks list +``` + +### Hook이 적격하지 않음 + +```bash +openclaw hooks info my-hook +``` + +누락된 바이너리(PATH), 환경 변수, 구성 값 또는 OS 호환성을 확인하세요. + +### Hook이 실행되지 않음 + +1. hook이 활성화되어 있는지 확인합니다: `openclaw hooks list` +2. hooks가 다시 로드되도록 gateway 프로세스를 재시작합니다. +3. gateway 로그를 확인합니다: `./scripts/clawlog.sh | grep hook` + +## 관련 문서 + +- [CLI Reference: hooks](/cli/hooks) +- [Webhooks](/automation/cron-jobs#webhooks) +- [Plugin Architecture](/plugins/architecture#provider-runtime-hooks) — 전체 plugin hook 참조 +- [Configuration](/gateway/configuration-reference#hooks) diff --git a/docs/ko/automation/index.md b/docs/ko/automation/index.md new file mode 100644 index 000000000..9343f94ca --- /dev/null +++ b/docs/ko/automation/index.md @@ -0,0 +1,122 @@ +--- +read_when: + - OpenClaw로 작업을 자동화하는 방법을 결정할 때 + - heartbeat, cron, hooks, standing orders 중에서 선택할 때 + - 적절한 자동화 진입점을 찾고 있을 때 +summary: '자동화 메커니즘 개요: 작업, cron, hooks, standing orders, Task Flow' +title: 자동화 및 작업 +x-i18n: + generated_at: "2026-04-05T12:34:17Z" + model: gpt-5.4 + provider: openai + source_hash: 13cd05dcd2f38737f7bb19243ad1136978bfd727006fd65226daa3590f823afe + source_path: automation/index.md + workflow: 15 +--- + +# 자동화 및 작업 + +OpenClaw는 작업, 예약 작업, 이벤트 hooks, 상시 지시를 통해 백그라운드에서 작업을 실행합니다. 이 페이지는 적절한 메커니즘을 선택하고 이들이 어떻게 함께 작동하는지 이해하는 데 도움을 줍니다. + +## 빠른 결정 가이드 + +```mermaid +flowchart TD + START([무엇이 필요하신가요?]) --> Q1{작업을 예약하시겠습니까?} + START --> Q2{분리된 작업을 추적하시겠습니까?} + START --> Q3{여러 단계의 흐름을 오케스트레이션하시겠습니까?} + START --> Q4{수명 주기 이벤트에 반응하시겠습니까?} + START --> Q5{에이전트에 지속적인 지침을 제공하시겠습니까?} + + Q1 -->|예| Q1a{정확한 시간인가요, 아니면 유연해도 되나요?} + Q1a -->|정확함| CRON["예약 작업 (Cron)"] + Q1a -->|유연함| HEARTBEAT[Heartbeat] + + Q2 -->|예| TASKS[백그라운드 작업] + Q3 -->|예| FLOW[Task Flow] + Q4 -->|예| HOOKS[Hooks] + Q5 -->|예| SO[Standing Orders] +``` + +| 사용 사례 | 권장 항목 | 이유 | +| --------------------------------------- | ---------------------- | ------------------------------------------------ | +| 매일 오전 9시에 정확히 일일 보고서 전송 | 예약 작업 (Cron) | 정확한 시간, 격리된 실행 | +| 20분 후에 알림 보내기 | 예약 작업 (Cron) | 정밀한 시간의 일회성 작업 (`--at`) | +| 매주 심층 분석 실행 | 예약 작업 (Cron) | 독립형 작업, 다른 모델 사용 가능 | +| 30분마다 받은편지함 확인 | Heartbeat | 다른 점검과 일괄 처리, 컨텍스트 인식 | +| 예정된 이벤트가 있는지 캘린더 모니터링 | Heartbeat | 주기적 인식에 자연스럽게 적합 | +| 서브에이전트 또는 ACP 실행 상태 점검 | 백그라운드 작업 | 작업 원장이 모든 분리된 작업을 추적 | +| 무엇이 언제 실행되었는지 감사 | 백그라운드 작업 | `openclaw tasks list` 및 `openclaw tasks audit` | +| 여러 단계의 조사 후 요약 | Task Flow | 리비전 추적이 포함된 내구성 있는 오케스트레이션 | +| 세션 재설정 시 스크립트 실행 | Hooks | 이벤트 기반, 수명 주기 이벤트에서 실행됨 | +| 모든 도구 호출마다 코드 실행 | Hooks | Hooks는 이벤트 유형으로 필터링 가능 | +| 응답 전에 항상 규정 준수 확인 | Standing Orders | 모든 세션에 자동으로 주입됨 | + +### 예약 작업 (Cron)과 Heartbeat 비교 + +| 차원 | 예약 작업 (Cron) | Heartbeat | +| --------------- | ----------------------------------- | ------------------------------------- | +| 타이밍 | 정확함 (cron 표현식, 일회성) | 대략적임 (기본 30분마다) | +| 세션 컨텍스트 | 새로 시작됨 (격리됨) 또는 공유됨 | 전체 메인 세션 컨텍스트 | +| 작업 기록 | 항상 생성됨 | 생성되지 않음 | +| 전달 | 채널, webhook 또는 무음 | 메인 세션 내 인라인 | +| 가장 적합한 용도 | 보고서, 알림, 백그라운드 작업 | 받은편지함 확인, 캘린더, 알림 | + +정확한 타이밍이나 격리된 실행이 필요할 때는 예약 작업 (Cron)을 사용하세요. 전체 세션 컨텍스트의 이점을 활용할 수 있고 대략적인 타이밍이면 충분할 때는 Heartbeat를 사용하세요. + +## 핵심 개념 + +### 예약 작업 (cron) + +Cron은 정확한 타이밍을 위한 Gateway의 내장 스케줄러입니다. 작업을 지속적으로 저장하고, 적절한 시간에 에이전트를 깨우며, 출력을 채팅 채널이나 webhook 엔드포인트로 전달할 수 있습니다. 일회성 알림, 반복 표현식, 인바운드 webhook 트리거를 지원합니다. + +[예약 작업](/automation/cron-jobs)을 참조하세요. + +### 작업 + +백그라운드 작업 원장은 모든 분리된 작업을 추적합니다: ACP 실행, 서브에이전트 생성, 격리된 cron 실행, CLI 작업. 작업은 스케줄러가 아니라 기록입니다. 이를 검사하려면 `openclaw tasks list` 및 `openclaw tasks audit`를 사용하세요. + +[백그라운드 작업](/automation/tasks)을 참조하세요. + +### Task Flow + +Task Flow는 백그라운드 작업 위에 있는 흐름 오케스트레이션 기반입니다. 관리형 및 미러링 동기화 모드, 리비전 추적, 그리고 검사용 `openclaw tasks flow list|show|cancel`을 통해 내구성 있는 여러 단계의 흐름을 관리합니다. + +[Task Flow](/automation/taskflow)를 참조하세요. + +### 상시 지시 + +상시 지시는 정의된 프로그램에 대해 에이전트에 영구적인 운영 권한을 부여합니다. 이는 워크스페이스 파일(일반적으로 `AGENTS.md`)에 저장되며 모든 세션에 주입됩니다. 시간 기반 강제를 위해 cron과 함께 결합하세요. + +[Standing Orders](/automation/standing-orders)를 참조하세요. + +### Hooks + +Hooks는 에이전트 수명 주기 이벤트(` /new`, `/reset`, `/stop`), 세션 압축, gateway 시작, 메시지 흐름, 도구 호출에 의해 트리거되는 이벤트 기반 스크립트입니다. Hooks는 디렉터리에서 자동으로 검색되며 `openclaw hooks`로 관리할 수 있습니다. + +[Hooks](/automation/hooks)를 참조하세요. + +### Heartbeat + +Heartbeat는 주기적인 메인 세션 턴입니다(기본값: 30분마다). 전체 세션 컨텍스트를 사용해 여러 점검(받은편지함, 캘린더, 알림)을 하나의 에이전트 턴으로 일괄 처리합니다. Heartbeat 턴은 작업 기록을 생성하지 않습니다. 작은 체크리스트에는 `HEARTBEAT.md`를 사용하고, heartbeat 자체 내에서 기한이 된 주기적 점검만 원할 때는 `tasks:` 블록을 사용하세요. 비어 있는 heartbeat 파일은 `empty-heartbeat-file`로 건너뛰고, 기한 기반 전용 작업 모드는 `no-tasks-due`로 건너뜁니다. + +[Heartbeat](/gateway/heartbeat)를 참조하세요. + +## 함께 작동하는 방식 + +- **Cron**은 정확한 일정(일일 보고서, 주간 검토)과 일회성 알림을 처리합니다. 모든 cron 실행은 작업 기록을 생성합니다. +- **Heartbeat**는 30분마다 한 번의 일괄 턴으로 일상적인 모니터링(받은편지함, 캘린더, 알림)을 처리합니다. +- **Hooks**는 특정 이벤트(도구 호출, 세션 재설정, 압축)에 맞춰 사용자 지정 스크립트로 반응합니다. +- **상시 지시**는 에이전트에 지속적인 컨텍스트와 권한 경계를 제공합니다. +- **Task Flow**는 개별 작업 위에서 여러 단계의 흐름을 조정합니다. +- **작업**은 모든 분리된 작업을 자동으로 추적하므로 이를 검사하고 감사할 수 있습니다. + +## 관련 문서 + +- [예약 작업](/automation/cron-jobs) — 정확한 예약과 일회성 알림 +- [백그라운드 작업](/automation/tasks) — 모든 분리된 작업을 위한 작업 원장 +- [Task Flow](/automation/taskflow) — 내구성 있는 다단계 흐름 오케스트레이션 +- [Hooks](/automation/hooks) — 이벤트 기반 수명 주기 스크립트 +- [Standing Orders](/automation/standing-orders) — 지속적인 에이전트 지침 +- [Heartbeat](/gateway/heartbeat) — 주기적인 메인 세션 턴 +- [구성 참조](/gateway/configuration-reference) — 모든 구성 키 diff --git a/docs/ko/automation/poll.md b/docs/ko/automation/poll.md new file mode 100644 index 000000000..f2f3435ce --- /dev/null +++ b/docs/ko/automation/poll.md @@ -0,0 +1,15 @@ +--- +summary: /tools/message로 리디렉션 +title: 투표 +x-i18n: + generated_at: "2026-04-05T12:33:55Z" + model: gpt-5.4 + provider: openai + source_hash: 8d69432ad8ba5fd80b59f8df779a1105ce0a2ff767d7dd5dc9d8e3ac1cb1ff1f + source_path: automation/poll.md + workflow: 15 +--- + +# 투표 + +이 페이지는 [Message tool](/cli/message)(으)로 이동했습니다. 투표 문서는 [Message tool](/cli/message)를 참조하세요. diff --git a/docs/ko/automation/standing-orders.md b/docs/ko/automation/standing-orders.md new file mode 100644 index 000000000..788eeb003 --- /dev/null +++ b/docs/ko/automation/standing-orders.md @@ -0,0 +1,261 @@ +--- +read_when: + - 작업별 프롬프트 없이 실행되는 자율 에이전트 워크플로를 설정할 때 + - 에이전트가 독립적으로 수행할 수 있는 작업과 사람의 승인이 필요한 작업을 정의할 때 + - 명확한 경계와 에스컬레이션 규칙을 갖춘 다중 프로그램 에이전트를 구조화할 때 +summary: 자율 에이전트 프로그램을 위한 상시 운영 권한 정의 +title: 상시 지시 +x-i18n: + generated_at: "2026-04-05T12:34:23Z" + model: gpt-5.4 + provider: openai + source_hash: 81347d7a51a6ce20e6493277afee92073770f69a91a2e6b3bf87b99bb586d038 + source_path: automation/standing-orders.md + workflow: 15 +--- + +# 상시 지시 + +상시 지시는 에이전트에 정의된 프로그램에 대한 **영구적인 운영 권한**을 부여합니다. 매번 개별 작업 지시를 내리는 대신, 명확한 범위, 트리거, 에스컬레이션 규칙을 갖춘 프로그램을 정의하면 에이전트는 그 경계 내에서 자율적으로 실행합니다. + +이는 매주 금요일마다 비서에게 "주간 보고서를 보내"라고 말하는 것과 상시 권한을 부여하는 것의 차이입니다. 예: "주간 보고서는 네 책임이다. 매주 금요일에 작성해서 보내고, 이상해 보이는 점이 있을 때만 에스컬레이션해." + +## 왜 상시 지시가 필요한가요? + +**상시 지시가 없을 때:** + +- 모든 작업마다 에이전트에 프롬프트를 제공해야 합니다 +- 에이전트는 요청 사이에 유휴 상태로 머뭅니다 +- 반복 작업이 잊히거나 지연됩니다 +- 사용자가 병목 지점이 됩니다 + +**상시 지시가 있을 때:** + +- 에이전트가 정의된 경계 내에서 자율적으로 실행합니다 +- 반복 작업이 프롬프트 없이도 일정에 따라 수행됩니다 +- 예외와 승인에만 사용자가 개입하면 됩니다 +- 에이전트가 유휴 시간을 생산적으로 활용합니다 + +## 작동 방식 + +상시 지시는 [에이전트 워크스페이스](/concepts/agent-workspace) 파일에 정의됩니다. 권장되는 방식은 이를 `AGENTS.md`에 직접 포함하는 것입니다(`AGENTS.md`는 모든 세션에 자동 주입됨). 이렇게 하면 에이전트가 항상 이를 컨텍스트로 갖게 됩니다. 더 큰 구성에서는 `standing-orders.md` 같은 전용 파일에 두고 `AGENTS.md`에서 참조할 수도 있습니다. + +각 프로그램은 다음을 지정합니다. + +1. **범위** — 에이전트가 수행하도록 승인된 작업 +2. **트리거** — 실행 시점(일정, 이벤트 또는 조건) +3. **승인 게이트** — 작업 전에 사람의 승인이 필요한 항목 +4. **에스컬레이션 규칙** — 중단하고 도움을 요청해야 하는 시점 + +에이전트는 워크스페이스 부트스트랩 파일을 통해 매 세션마다 이러한 지침을 로드하며([Agent Workspace](/concepts/agent-workspace)에서 자동 주입 파일의 전체 목록 확인), 시간 기반 집행을 위해 [cron jobs](/automation/cron-jobs)와 결합해 이를 실행합니다. + + +상시 지시는 `AGENTS.md`에 넣어 매 세션마다 로드되도록 보장하세요. 워크스페이스 부트스트랩은 `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`, `MEMORY.md`를 자동 주입하지만, 하위 디렉터리의 임의 파일은 자동 주입하지 않습니다. + + +## 상시 지시의 구성 요소 + +```markdown +## Program: Weekly Status Report + +**Authority:** Compile data, generate report, deliver to stakeholders +**Trigger:** Every Friday at 4 PM (enforced via cron job) +**Approval gate:** None for standard reports. Flag anomalies for human review. +**Escalation:** If data source is unavailable or metrics look unusual (>2σ from norm) + +### Execution Steps + +1. Pull metrics from configured sources +2. Compare to prior week and targets +3. Generate report in Reports/weekly/YYYY-MM-DD.md +4. Deliver summary via configured channel +5. Log completion to Agent/Logs/ + +### What NOT to Do + +- Do not send reports to external parties +- Do not modify source data +- Do not skip delivery if metrics look bad — report accurately +``` + +## 상시 지시 + Cron Jobs + +상시 지시는 에이전트가 수행하도록 승인된 **무엇을** 정의합니다. [Cron jobs](/automation/cron-jobs)는 **언제** 수행할지를 정의합니다. 둘은 함께 작동합니다. + +``` +Standing Order: "You own the daily inbox triage" + ↓ +Cron Job (8 AM daily): "Execute inbox triage per standing orders" + ↓ +Agent: Reads standing orders → executes steps → reports results +``` + +cron job 프롬프트는 상시 지시를 중복해서 쓰기보다 이를 참조해야 합니다. + +```bash +openclaw cron add \ + --name daily-inbox-triage \ + --cron "0 8 * * 1-5" \ + --tz America/New_York \ + --timeout-seconds 300 \ + --announce \ + --channel bluebubbles \ + --to "+1XXXXXXXXXX" \ + --message "Execute daily inbox triage per standing orders. Check mail for new alerts. Parse, categorize, and persist each item. Report summary to owner. Escalate unknowns." +``` + +## 예시 + +### 예시 1: 콘텐츠 및 소셜 미디어(주간 주기) + +```markdown +## Program: Content & Social Media + +**Authority:** Draft content, schedule posts, compile engagement reports +**Approval gate:** All posts require owner review for first 30 days, then standing approval +**Trigger:** Weekly cycle (Monday review → mid-week drafts → Friday brief) + +### Weekly Cycle + +- **Monday:** Review platform metrics and audience engagement +- **Tuesday–Thursday:** Draft social posts, create blog content +- **Friday:** Compile weekly marketing brief → deliver to owner + +### Content Rules + +- Voice must match the brand (see SOUL.md or brand voice guide) +- Never identify as AI in public-facing content +- Include metrics when available +- Focus on value to audience, not self-promotion +``` + +### 예시 2: 재무 운영(이벤트 트리거) + +```markdown +## Program: Financial Processing + +**Authority:** Process transaction data, generate reports, send summaries +**Approval gate:** None for analysis. Recommendations require owner approval. +**Trigger:** New data file detected OR scheduled monthly cycle + +### When New Data Arrives + +1. Detect new file in designated input directory +2. Parse and categorize all transactions +3. Compare against budget targets +4. Flag: unusual items, threshold breaches, new recurring charges +5. Generate report in designated output directory +6. Deliver summary to owner via configured channel + +### Escalation Rules + +- Single item > $500: immediate alert +- Category > budget by 20%: flag in report +- Unrecognizable transaction: ask owner for categorization +- Failed processing after 2 retries: report failure, do not guess +``` + +### 예시 3: 모니터링 및 알림(연속) + +```markdown +## Program: System Monitoring + +**Authority:** Check system health, restart services, send alerts +**Approval gate:** Restart services automatically. Escalate if restart fails twice. +**Trigger:** Every heartbeat cycle + +### Checks + +- Service health endpoints responding +- Disk space above threshold +- Pending tasks not stale (>24 hours) +- Delivery channels operational + +### Response Matrix + +| Condition | Action | Escalate? | +| ---------------- | ------------------------ | ------------------------ | +| Service down | Restart automatically | Only if restart fails 2x | +| Disk space < 10% | Alert owner | Yes | +| Stale task > 24h | Remind owner | No | +| Channel offline | Log and retry next cycle | If offline > 2 hours | +``` + +## 실행-검증-보고 패턴 + +상시 지시는 엄격한 실행 규율과 결합될 때 가장 잘 작동합니다. 상시 지시의 모든 작업은 이 루프를 따라야 합니다. + +1. **실행** — 실제 작업을 수행합니다(지시를 확인만 하지 마세요) +2. **검증** — 결과가 올바른지 확인합니다(파일 존재, 메시지 전달, 데이터 파싱 등) +3. **보고** — 무엇을 수행했고 무엇을 검증했는지 소유자에게 알립니다 + +```markdown +### Execution Rules + +- Every task follows Execute-Verify-Report. No exceptions. +- "I'll do that" is not execution. Do it, then report. +- "Done" without verification is not acceptable. Prove it. +- If execution fails: retry once with adjusted approach. +- If still fails: report failure with diagnosis. Never silently fail. +- Never retry indefinitely — 3 attempts max, then escalate. +``` + +이 패턴은 가장 흔한 에이전트 실패 모드, 즉 작업을 완료하지 않은 채 작업을 확인만 하는 문제를 방지합니다. + +## 다중 프로그램 아키텍처 + +여러 관심사를 관리하는 에이전트의 경우, 명확한 경계를 가진 개별 프로그램으로 상시 지시를 구성하세요. + +```markdown +# Standing Orders + +## Program 1: [Domain A] (Weekly) + +... + +## Program 2: [Domain B] (Monthly + On-Demand) + +... + +## Program 3: [Domain C] (As-Needed) + +... + +## Escalation Rules (All Programs) + +- [Common escalation criteria] +- [Approval gates that apply across programs] +``` + +각 프로그램은 다음을 가져야 합니다. + +- 고유한 **트리거 주기**(주간, 월간, 이벤트 기반, 연속) +- 고유한 **승인 게이트**(일부 프로그램은 다른 프로그램보다 더 많은 감독이 필요함) +- 명확한 **경계**(에이전트가 어디서 한 프로그램이 끝나고 다른 프로그램이 시작되는지 알아야 함) + +## 모범 사례 + +### 권장 사항 + +- 좁은 권한으로 시작하고 신뢰가 쌓이면서 확장하세요 +- 위험도가 높은 작업에는 명시적인 승인 게이트를 정의하세요 +- "하지 말아야 할 일" 섹션을 포함하세요 — 경계는 권한만큼 중요합니다 +- 신뢰할 수 있는 시간 기반 실행을 위해 cron jobs와 결합하세요 +- 상시 지시가 잘 지켜지는지 확인하기 위해 매주 에이전트 로그를 검토하세요 +- 필요가 변화함에 따라 상시 지시를 업데이트하세요 — 이는 살아 있는 문서입니다 + +### 피해야 할 사항 + +- 첫날부터 광범위한 권한을 부여하지 마세요("최선이라고 생각하는 대로 뭐든 해") +- 에스컬레이션 규칙을 생략하지 마세요 — 모든 프로그램에는 "멈추고 물어봐야 하는 시점" 조항이 필요합니다 +- 에이전트가 구두 지시를 기억할 것이라고 가정하지 마세요 — 모든 것을 파일에 적어 두세요 +- 하나의 프로그램에 여러 관심사를 섞지 마세요 — 별개의 도메인에는 별개의 프로그램을 사용하세요 +- cron jobs로 강제하는 것을 잊지 마세요 — 트리거 없는 상시 지시는 제안 사항에 불과합니다 + +## 관련 항목 + +- [Automation & Tasks](/automation) — 모든 자동화 메커니즘 한눈에 보기 +- [Cron Jobs](/automation/cron-jobs) — 상시 지시를 위한 일정 기반 집행 +- [Hooks](/automation/hooks) — 에이전트 수명 주기 이벤트를 위한 이벤트 기반 스크립트 +- [Webhooks](/automation/cron-jobs#webhooks) — 인바운드 HTTP 이벤트 트리거 +- [Agent Workspace](/concepts/agent-workspace) — 상시 지시가 위치하는 곳이며, 자동 주입되는 부트스트랩 파일 전체 목록(AGENTS.md, SOUL.md 등)을 포함합니다 diff --git a/docs/ko/automation/taskflow.md b/docs/ko/automation/taskflow.md new file mode 100644 index 000000000..433aeeb8a --- /dev/null +++ b/docs/ko/automation/taskflow.md @@ -0,0 +1,89 @@ +--- +read_when: + - Task Flow가 백그라운드 작업과 어떤 관련이 있는지 이해하고 싶을 때 + - 릴리스 노트나 문서에서 Task Flow 또는 openclaw tasks flow를 보게 되었을 때 + - 내구성 있는 흐름 상태를 검사하거나 관리하고 싶을 때 +summary: 백그라운드 작업 위의 Task Flow 흐름 오케스트레이션 계층 +title: Task Flow +x-i18n: + generated_at: "2026-04-05T12:34:08Z" + model: gpt-5.4 + provider: openai + source_hash: 172871206b839845db807d9c627015890f7733b862e276853d5dbfbe29e03883 + source_path: automation/taskflow.md + workflow: 15 +--- + +# Task Flow + +Task Flow는 [백그라운드 작업](/automation/tasks) 위에 위치하는 흐름 오케스트레이션 기반 계층입니다. 개별 작업은 분리된 작업의 단위로 유지되는 반면, 자체 상태, 리비전 추적, 동기화 의미 체계를 갖춘 내구성 있는 다단계 흐름을 관리합니다. + +## Task Flow를 사용해야 하는 경우 + +작업이 여러 순차적 단계 또는 분기 단계에 걸쳐 있고 게이트웨이 재시작 전반에 걸쳐 내구성 있는 진행 상황 추적이 필요하다면 Task Flow를 사용하세요. 단일 백그라운드 작업의 경우 일반 [작업](/automation/tasks)이면 충분합니다. + +| 시나리오 | 사용 방식 | +| ------------------------------------- | -------------------- | +| 단일 백그라운드 작업 | 일반 작업 | +| 다단계 파이프라인 (A 다음 B 다음 C) | Task Flow (관리형) | +| 외부에서 생성된 작업 관찰 | Task Flow (미러링) | +| 일회성 리마인더 | cron 작업 | + +## 동기화 모드 + +### 관리형 모드 + +Task Flow는 수명 주기를 처음부터 끝까지 소유합니다. 흐름 단계로 작업을 생성하고, 완료될 때까지 구동하며, 흐름 상태를 자동으로 진행시킵니다. + +예: 주간 보고서 흐름이 (1) 데이터를 수집하고, (2) 보고서를 생성하고, (3) 전달합니다. Task Flow는 각 단계를 백그라운드 작업으로 생성하고, 완료를 기다린 다음, 다음 단계로 이동합니다. + +``` +Flow: weekly-report + Step 1: gather-data → task created → succeeded + Step 2: generate-report → task created → succeeded + Step 3: deliver → task created → running +``` + +### 미러링 모드 + +Task Flow는 외부에서 생성된 작업을 관찰하고 작업 생성에 대한 소유권을 갖지 않으면서 흐름 상태를 동기화된 상태로 유지합니다. 이는 작업이 cron 작업, CLI 명령 또는 기타 소스에서 시작되고, 이를 흐름으로서 진행 상황을 통합된 보기로 확인하고 싶을 때 유용합니다. + +예: 함께 "아침 운영" 루틴을 구성하는 세 개의 독립적인 cron 작업. 미러링된 흐름은 작업의 실행 시점이나 실행 방식을 제어하지 않고도 이들의 전체 진행 상황을 추적합니다. + +## 내구성 있는 상태 및 리비전 추적 + +각 흐름은 자체 상태를 유지하고 리비전을 추적하므로 게이트웨이가 재시작되어도 진행 상황이 유지됩니다. 리비전 추적은 여러 소스가 동시에 동일한 흐름을 진행시키려 할 때 충돌 감지를 가능하게 합니다. + +## 취소 동작 + +`openclaw tasks flow cancel`은 흐름에 고정 취소 의도를 설정합니다. 흐름 내의 활성 작업은 취소되며 새 단계는 시작되지 않습니다. 취소 의도는 재시작 후에도 유지되므로, 모든 하위 작업이 종료되기 전에 게이트웨이가 재시작되더라도 취소된 흐름은 계속 취소된 상태로 유지됩니다. + +## CLI 명령 + +```bash +# 활성 및 최근 흐름 나열 +openclaw tasks flow list + +# 특정 흐름의 세부 정보 표시 +openclaw tasks flow show + +# 실행 중인 흐름과 그 활성 작업 취소 +openclaw tasks flow cancel +``` + +| 명령 | 설명 | +| --------------------------------- | --------------------------------------------- | +| `openclaw tasks flow list` | 상태 및 동기화 모드와 함께 추적된 흐름 표시 | +| `openclaw tasks flow show ` | 흐름 id 또는 조회 키로 하나의 흐름 검사 | +| `openclaw tasks flow cancel ` | 실행 중인 흐름과 그 활성 작업 취소 | + +## 흐름과 작업의 관계 + +흐름은 작업을 조정하는 것이지, 작업을 대체하는 것이 아닙니다. 하나의 흐름은 수명 주기 동안 여러 백그라운드 작업을 구동할 수 있습니다. 개별 작업 레코드를 검사하려면 `openclaw tasks`를 사용하고, 오케스트레이션하는 흐름을 검사하려면 `openclaw tasks flow`를 사용하세요. + +## 관련 항목 + +- [백그라운드 작업](/automation/tasks) — 흐름이 조정하는 분리된 작업 원장 +- [CLI: tasks](/cli/index#tasks) — `openclaw tasks flow`용 CLI 명령 참조 +- [자동화 개요](/automation) — 모든 자동화 메커니즘 한눈에 보기 +- [Cron Jobs](/automation/cron-jobs) — 흐름으로 유입될 수 있는 예약된 작업 diff --git a/docs/ko/automation/tasks.md b/docs/ko/automation/tasks.md new file mode 100644 index 000000000..7644a4465 --- /dev/null +++ b/docs/ko/automation/tasks.md @@ -0,0 +1,317 @@ +--- +read_when: + - 진행 중이거나 최근 완료된 백그라운드 작업을 검사할 때 + - 분리된 에이전트 실행의 전달 실패를 디버깅할 때 + - 백그라운드 실행이 세션, cron, heartbeat와 어떤 관련이 있는지 이해할 때 +summary: ACP 실행, 하위 에이전트, 격리된 cron 작업, 그리고 CLI 작업에 대한 백그라운드 작업 추적 +title: 배경 작업 +x-i18n: + generated_at: "2026-04-05T12:34:39Z" + model: gpt-5.4 + provider: openai + source_hash: 6c95ccf4388d07e60a7bb68746b161793f4bb5ff2ba3d5ce9e51f2225dab2c4d + source_path: automation/tasks.md + workflow: 15 +--- + +# 배경 작업 + +> **스케줄링을 찾고 계신가요?** 적절한 메커니즘을 선택하려면 [Automation & Tasks](/automation)를 참조하세요. 이 페이지는 백그라운드 작업의 **추적**을 다루며, 스케줄링은 다루지 않습니다. + +배경 작업은 **기본 대화 세션 외부**에서 실행되는 작업을 추적합니다: +ACP 실행, 하위 에이전트 생성, 격리된 cron 작업 실행, 그리고 CLI로 시작된 작업입니다. + +작업은 세션, cron 작업, heartbeat를 대체하지 않습니다 — 이것은 분리된 작업에서 무엇이 일어났는지, 언제 일어났는지, 그리고 성공했는지를 기록하는 **활동 원장**입니다. + + +모든 에이전트 실행이 작업을 생성하는 것은 아닙니다. Heartbeat 턴과 일반 대화형 채팅은 생성하지 않습니다. 모든 cron 실행, ACP 생성, 하위 에이전트 생성, 그리고 CLI 에이전트 명령은 생성합니다. + + +## 요약 + +- 작업은 스케줄러가 아니라 **기록**입니다 — cron과 heartbeat가 작업 실행 _시점_ 을 결정하고, 작업은 _무슨 일이 있었는지_ 를 추적합니다. +- ACP, 하위 에이전트, 모든 cron 작업, 그리고 CLI 작업은 작업을 생성합니다. Heartbeat 턴은 생성하지 않습니다. +- 각 작업은 `queued → running → terminal`(succeeded, failed, timed_out, cancelled, 또는 lost) 단계를 거칩니다. +- Cron 작업은 cron 런타임이 여전히 해당 작업을 소유하는 동안 활성 상태를 유지합니다. 채팅 기반 CLI 작업은 소유 실행 컨텍스트가 아직 활성일 때만 활성 상태를 유지합니다. +- 완료는 푸시 방식으로 처리됩니다. 분리된 작업은 완료 시 직접 알리거나 요청자 세션/heartbeat를 깨울 수 있으므로, 상태를 폴링하는 루프는 대개 적절한 방식이 아닙니다. +- 격리된 cron 실행과 하위 에이전트 완료는 최종 정리 bookkeeping 전에 해당 하위 세션의 추적된 브라우저 탭/프로세스를 best-effort로 정리합니다. +- 격리된 cron 전달은 하위 하위 에이전트 작업이 아직 비워지는 동안 오래된 중간 부모 응답을 억제하고, 전달 전에 최종 하위 출력이 도착하면 그것을 우선합니다. +- 완료 알림은 채널로 직접 전달되거나 다음 heartbeat를 위해 대기열에 들어갑니다. +- `openclaw tasks list`는 모든 작업을 표시하고, `openclaw tasks audit`는 문제를 표시합니다. +- 종료된 기록은 7일 동안 유지된 후 자동으로 정리됩니다. + +## 빠른 시작 + +```bash +# 모든 작업 나열(최신순) +openclaw tasks list + +# 런타임 또는 상태별로 필터링 +openclaw tasks list --runtime acp +openclaw tasks list --status running + +# 특정 작업의 세부 정보 표시(ID, run ID 또는 session key로 조회) +openclaw tasks show + +# 실행 중인 작업 취소(하위 세션 종료) +openclaw tasks cancel + +# 작업의 알림 정책 변경 +openclaw tasks notify state_changes + +# 상태 감사 실행 +openclaw tasks audit + +# 유지 관리 미리보기 또는 적용 +openclaw tasks maintenance +openclaw tasks maintenance --apply + +# TaskFlow 상태 검사 +openclaw tasks flow list +openclaw tasks flow show +openclaw tasks flow cancel +``` + +## 작업을 생성하는 항목 + +| 소스 | 런타임 유형 | 작업 기록이 생성되는 시점 | 기본 알림 정책 | +| ---------------------- | ------------ | ------------------------------------------------------ | --------------------- | +| ACP 백그라운드 실행 | `acp` | 하위 ACP 세션 생성 | `done_only` | +| 하위 에이전트 오케스트레이션 | `subagent` | `sessions_spawn`을 통해 하위 에이전트를 생성할 때 | `done_only` | +| Cron 작업(모든 유형) | `cron` | 모든 cron 실행(메인 세션 및 격리형) | `silent` | +| CLI 작업 | `cli` | 게이트웨이를 통해 실행되는 `openclaw agent` 명령 | `silent` | + +메인 세션 cron 작업은 기본적으로 `silent` 알림 정책을 사용합니다 — 추적용 기록은 생성하지만 알림은 생성하지 않습니다. 격리된 cron 작업도 기본적으로 `silent`이지만 자체 세션에서 실행되므로 더 눈에 띕니다. + +**작업을 생성하지 않는 항목:** + +- Heartbeat 턴 — 메인 세션, [Heartbeat](/gateway/heartbeat) 참조 +- 일반 대화형 채팅 턴 +- 직접 `/command` 응답 + +## 작업 수명 주기 + +```mermaid +stateDiagram-v2 + [*] --> queued + queued --> running : agent starts + running --> succeeded : completes ok + running --> failed : error + running --> timed_out : timeout exceeded + running --> cancelled : operator cancels + queued --> lost : session gone > 5 min + running --> lost : session gone > 5 min +``` + +| 상태 | 의미 | +| ----------- | -------------------------------------------------------------------------- | +| `queued` | 생성되었고, 에이전트 시작을 기다리는 중 | +| `running` | 에이전트 턴이 현재 실행 중 | +| `succeeded` | 성공적으로 완료됨 | +| `failed` | 오류와 함께 완료됨 | +| `timed_out` | 구성된 제한 시간을 초과함 | +| `cancelled` | 운영자가 `openclaw tasks cancel`로 중지함 | +| `lost` | 5분 유예 기간 후 런타임이 권한 있는 백킹 상태를 잃음 | + +전이는 자동으로 발생합니다 — 연결된 에이전트 실행이 끝나면 작업 상태가 이에 맞춰 업데이트됩니다. + +`lost`는 런타임 인식 방식으로 처리됩니다: + +- ACP 작업: 백킹 ACP 하위 세션 메타데이터가 사라졌습니다. +- 하위 에이전트 작업: 대상 에이전트 저장소에서 백킹 하위 세션이 사라졌습니다. +- Cron 작업: cron 런타임이 더 이상 해당 작업을 활성 상태로 추적하지 않습니다. +- CLI 작업: 격리된 하위 세션 작업은 하위 세션을 사용하고, 채팅 기반 CLI 작업은 대신 활성 실행 컨텍스트를 사용하므로 남아 있는 채널/그룹/직접 세션 행만으로는 활성 상태가 유지되지 않습니다. + +## 전달 및 알림 + +작업이 종료 상태에 도달하면 OpenClaw가 알립니다. 전달 경로는 두 가지입니다. + +**직접 전달** — 작업에 채널 대상(`requesterOrigin`)이 있으면 완료 메시지가 해당 채널(Telegram, Discord, Slack 등)로 바로 전송됩니다. 하위 에이전트 완료의 경우 OpenClaw는 가능하면 바인딩된 스레드/토픽 라우팅도 보존하며, 직접 전달을 포기하기 전에 요청자 세션의 저장된 경로(`lastChannel` / `lastTo` / `lastAccountId`)에서 누락된 `to` / 계정을 채울 수 있습니다. + +**세션 대기열 전달** — 직접 전달에 실패했거나 origin이 설정되지 않은 경우, 업데이트는 요청자 세션의 시스템 이벤트로 대기열에 들어가며 다음 heartbeat에서 표시됩니다. + + +작업 완료는 즉시 heartbeat 깨우기를 트리거하므로 결과를 빠르게 확인할 수 있습니다 — 다음 예약된 heartbeat 틱까지 기다릴 필요가 없습니다. + + +즉, 일반적인 워크플로는 푸시 기반입니다. 분리된 작업을 한 번 시작한 뒤, 완료 시 런타임이 깨우거나 알리도록 두세요. 디버깅, 개입, 또는 명시적인 감사가 필요할 때만 작업 상태를 폴링하세요. + +### 알림 정책 + +각 작업에 대해 얼마나 많은 알림을 받을지 제어합니다. + +| 정책 | 전달되는 내용 | +| --------------------- | ----------------------------------------------------------------------- | +| `done_only` (기본값) | 종료 상태(succeeded, failed 등)만 전달 — **이 값이 기본값입니다** | +| `state_changes` | 모든 상태 전이와 진행 상황 업데이트 | +| `silent` | 아무것도 전달하지 않음 | + +작업이 실행 중일 때 정책을 변경할 수 있습니다. + +```bash +openclaw tasks notify state_changes +``` + +## CLI 참조 + +### `tasks list` + +```bash +openclaw tasks list [--runtime ] [--status ] [--json] +``` + +출력 열: 작업 ID, 종류, 상태, 전달, Run ID, 하위 세션, 요약. + +### `tasks show` + +```bash +openclaw tasks show +``` + +조회 토큰은 작업 ID, run ID, 또는 session key를 받을 수 있습니다. 타이밍, 전달 상태, 오류, 종료 요약을 포함한 전체 기록을 표시합니다. + +### `tasks cancel` + +```bash +openclaw tasks cancel +``` + +ACP 및 하위 에이전트 작업의 경우 하위 세션을 종료합니다. 상태는 `cancelled`로 전이되며 전달 알림이 전송됩니다. + +### `tasks notify` + +```bash +openclaw tasks notify +``` + +### `tasks audit` + +```bash +openclaw tasks audit [--json] +``` + +운영상 문제를 표시합니다. 문제가 감지되면 결과는 `openclaw status`에도 나타납니다. + +| 항목 | 심각도 | 트리거 | +| ------------------------- | -------- | ----------------------------------------------------- | +| `stale_queued` | warn | 10분 이상 queued 상태 | +| `stale_running` | error | 30분 이상 running 상태 | +| `lost` | error | 런타임 기반 작업 소유권이 사라짐 | +| `delivery_failed` | warn | 전달이 실패했고 알림 정책이 `silent`가 아님 | +| `missing_cleanup` | warn | cleanup 타임스탬프가 없는 종료 작업 | +| `inconsistent_timestamps` | warn | 타임라인 위반(예: 시작 전에 종료됨) | + +### `tasks maintenance` + +```bash +openclaw tasks maintenance [--json] +openclaw tasks maintenance --apply [--json] +``` + +이를 사용해 작업 및 Task Flow 상태의 조정, cleanup 스탬프 처리, 그리고 정리를 미리 보거나 적용할 수 있습니다. + +조정은 런타임 인식 방식으로 처리됩니다. + +- ACP/하위 에이전트 작업은 백킹 하위 세션을 확인합니다. +- Cron 작업은 cron 런타임이 여전히 해당 작업을 소유하는지 확인합니다. +- 채팅 기반 CLI 작업은 채팅 세션 행만이 아니라 소유하는 활성 실행 컨텍스트를 확인합니다. + +완료 cleanup도 런타임 인식 방식으로 처리됩니다. + +- 하위 에이전트 완료는 알림 cleanup이 계속되기 전에 하위 세션의 추적된 브라우저 탭/프로세스를 best-effort로 닫습니다. +- 격리된 cron 완료는 실행이 완전히 종료되기 전에 cron 세션의 추적된 브라우저 탭/프로세스를 best-effort로 닫습니다. +- 격리된 cron 전달은 필요할 때 하위 하위 에이전트 후속 작업이 끝날 때까지 기다리고, 이를 알리는 대신 오래된 부모 확인 텍스트를 억제합니다. +- 하위 에이전트 완료 전달은 가장 최신의 표시 가능한 assistant 텍스트를 우선하며, 이것이 비어 있으면 정리된 최신 tool/toolResult 텍스트로 대체하고, 타임아웃만 발생한 tool-call 실행은 짧은 부분 진행 요약으로 축약될 수 있습니다. +- Cleanup 실패가 실제 작업 결과를 가리지는 않습니다. + +### `tasks flow list|show|cancel` + +```bash +openclaw tasks flow list [--status ] [--json] +openclaw tasks flow show [--json] +openclaw tasks flow cancel +``` + +개별 백그라운드 작업 기록 하나보다 이를 오케스트레이션하는 Task Flow 자체가 더 중요할 때 사용하세요. + +## 채팅 작업 보드(`/tasks`) + +어떤 채팅 세션에서든 `/tasks`를 사용해 그 세션에 연결된 백그라운드 작업을 볼 수 있습니다. 보드에는 활성 작업과 최근 완료된 작업이 런타임, 상태, 타이밍, 그리고 진행 상황 또는 오류 세부 정보와 함께 표시됩니다. + +현재 세션에 표시 가능한 연결 작업이 없으면 `/tasks`는 에이전트 로컬 작업 수로 대체되어, 다른 세션 세부 정보를 노출하지 않고도 개요를 제공합니다. + +전체 운영자 원장을 보려면 CLI를 사용하세요: `openclaw tasks list`. + +## 상태 통합(작업 압력) + +`openclaw status`에는 한눈에 보는 작업 요약이 포함됩니다. + +``` +Tasks: 3 queued · 2 running · 1 issues +``` + +요약은 다음을 보고합니다. + +- **active** — `queued` + `running` 개수 +- **failures** — `failed` + `timed_out` + `lost` 개수 +- **byRuntime** — `acp`, `subagent`, `cron`, `cli`별 분류 + +`/status`와 `session_status` 도구는 모두 cleanup 인식 작업 스냅샷을 사용합니다. 활성 작업이 우선되고, 오래된 완료 행은 숨겨지며, 최근 실패는 활성 작업이 남아 있지 않을 때만 표시됩니다. 이렇게 하면 상태 카드가 지금 중요한 내용에 집중됩니다. + +## 저장소 및 유지 관리 + +### 작업이 저장되는 위치 + +작업 기록은 다음 SQLite 위치에 유지됩니다. + +``` +$OPENCLAW_STATE_DIR/tasks/runs.sqlite +``` + +레지스트리는 게이트웨이 시작 시 메모리에 로드되며, 재시작 간 내구성을 위해 쓰기 내용을 SQLite에 동기화합니다. + +### 자동 유지 관리 + +스위퍼는 **60초**마다 실행되며 세 가지를 처리합니다. + +1. **조정** — 활성 작업이 여전히 권한 있는 런타임 백킹을 가지고 있는지 확인합니다. ACP/하위 에이전트 작업은 하위 세션 상태를 사용하고, cron 작업은 활성 작업 소유권을 사용하며, 채팅 기반 CLI 작업은 소유하는 실행 컨텍스트를 사용합니다. 이 백킹 상태가 5분 이상 사라져 있으면 작업은 `lost`로 표시됩니다. +2. **Cleanup 스탬프 처리** — 종료 작업에 `cleanupAfter` 타임스탬프를 설정합니다(`endedAt + 7 days`). +3. **정리** — `cleanupAfter` 날짜가 지난 기록을 삭제합니다. + +**보존 기간**: 종료된 작업 기록은 **7일** 동안 유지된 후 자동으로 정리됩니다. 별도의 구성은 필요하지 않습니다. + +## 작업이 다른 시스템과 어떤 관련이 있는가 + +### 작업과 Task Flow + +[Task Flow](/automation/taskflow)는 백그라운드 작업 위에 있는 플로우 오케스트레이션 계층입니다. 단일 플로우는 관리형 또는 미러링된 동기화 모드를 사용해 수명 주기 동안 여러 작업을 조정할 수 있습니다. 개별 작업 기록을 검사하려면 `openclaw tasks`를, 오케스트레이션 플로우를 검사하려면 `openclaw tasks flow`를 사용하세요. + +자세한 내용은 [Task Flow](/automation/taskflow)를 참조하세요. + +### 작업과 cron + +Cron 작업 **정의**는 `~/.openclaw/cron/jobs.json`에 있습니다. **모든** cron 실행은 작업 기록을 생성합니다 — 메인 세션과 격리형 모두 포함됩니다. 메인 세션 cron 작업은 기본적으로 `silent` 알림 정책을 사용하므로 알림을 생성하지 않고 추적만 합니다. + +[Scheduled Tasks](/automation/cron-jobs)를 참조하세요. + +### 작업과 heartbeat + +Heartbeat 실행은 메인 세션 턴입니다 — 작업 기록을 생성하지 않습니다. 작업이 완료되면 heartbeat 깨우기를 트리거해 결과를 빠르게 확인할 수 있습니다. + +[Heartbeat](/gateway/heartbeat)를 참조하세요. + +### 작업과 세션 + +작업은 `childSessionKey`(작업이 실행되는 곳)와 `requesterSessionKey`(누가 시작했는지)를 참조할 수 있습니다. 세션은 대화 컨텍스트이고, 작업은 그 위에서 활동을 추적합니다. + +### 작업과 에이전트 실행 + +작업의 `runId`는 작업을 수행하는 에이전트 실행과 연결됩니다. 에이전트 수명 주기 이벤트(시작, 종료, 오류)는 작업 상태를 자동으로 업데이트합니다 — 수명 주기를 수동으로 관리할 필요가 없습니다. + +## 관련 항목 + +- [Automation & Tasks](/automation) — 모든 자동화 메커니즘 한눈에 보기 +- [Task Flow](/automation/taskflow) — 작업 상위의 플로우 오케스트레이션 +- [Scheduled Tasks](/automation/cron-jobs) — 백그라운드 작업 스케줄링 +- [Heartbeat](/gateway/heartbeat) — 주기적인 메인 세션 턴 +- [CLI: Tasks](/cli/index#tasks) — CLI 명령 참조 diff --git a/docs/ko/automation/troubleshooting.md b/docs/ko/automation/troubleshooting.md new file mode 100644 index 000000000..969b2cfc4 --- /dev/null +++ b/docs/ko/automation/troubleshooting.md @@ -0,0 +1,15 @@ +--- +summary: /automation/cron-jobs로 리디렉션 +title: 자동화 문제 해결 +x-i18n: + generated_at: "2026-04-05T12:34:06Z" + model: gpt-5.4 + provider: openai + source_hash: 6621342754fb56234b89e71167f73bad6c070273ff0b8d12dbb20854e32e7980 + source_path: automation/troubleshooting.md + workflow: 15 +--- + +# 자동화 문제 해결 + +이 페이지는 [예약된 작업](/automation/cron-jobs#troubleshooting)(으)로 이동되었습니다. 문제 해결 문서는 [예약된 작업](/automation/cron-jobs#troubleshooting)을(를) 참조하세요. diff --git a/docs/ko/automation/webhook.md b/docs/ko/automation/webhook.md new file mode 100644 index 000000000..105d2771b --- /dev/null +++ b/docs/ko/automation/webhook.md @@ -0,0 +1,15 @@ +--- +summary: /automation/cron-jobs로 리디렉션 +title: 웹훅 +x-i18n: + generated_at: "2026-04-05T12:34:07Z" + model: gpt-5.4 + provider: openai + source_hash: 7511f6bc28e7f13ee1d9313bb5551825afb5706019068079603a44668c4dda34 + source_path: automation/webhook.md + workflow: 15 +--- + +# 웹훅 + +이 페이지는 [예약된 작업](/automation/cron-jobs#webhooks)으로 이동했습니다. 웹훅 문서는 [예약된 작업](/automation/cron-jobs#webhooks)을 참조하세요. diff --git a/docs/ko/brave-search.md b/docs/ko/brave-search.md new file mode 100644 index 000000000..7f64e3bf6 --- /dev/null +++ b/docs/ko/brave-search.md @@ -0,0 +1,110 @@ +--- +read_when: + - web_search에 Brave Search를 사용하려는 경우 + - BRAVE_API_KEY 또는 요금제 세부 정보가 필요한 경우 +summary: web_search용 Brave Search API 설정 +title: Brave Search (레거시 경로) +x-i18n: + generated_at: "2026-04-05T12:34:22Z" + model: gpt-5.4 + provider: openai + source_hash: 7788e4cee7dc460819e55095c87df8cea29ba3a8bd3cef4c0e98ac601b45b651 + source_path: brave-search.md + workflow: 15 +--- + +# Brave Search API + +OpenClaw는 Brave Search API를 `web_search` 공급자로 지원합니다. + +## API 키 받기 + +1. [https://brave.com/search/api/](https://brave.com/search/api/)에서 Brave Search API 계정을 생성합니다. +2. 대시보드에서 **Search** 요금제를 선택하고 API 키를 생성합니다. +3. 키를 config에 저장하거나 Gateway 환경에서 `BRAVE_API_KEY`를 설정합니다. + +## config 예시 + +```json5 +{ + plugins: { + entries: { + brave: { + config: { + webSearch: { + apiKey: "BRAVE_API_KEY_HERE", + mode: "web", // or "llm-context" + }, + }, + }, + }, + }, + tools: { + web: { + search: { + provider: "brave", + maxResults: 5, + timeoutSeconds: 30, + }, + }, + }, +} +``` + +공급자별 Brave 검색 설정은 이제 `plugins.entries.brave.config.webSearch.*` 아래에 있습니다. +레거시 `tools.web.search.apiKey`도 호환성 shim을 통해 계속 로드되지만, 더 이상 정식 config 경로는 아닙니다. + +`webSearch.mode`는 Brave 전송 방식을 제어합니다. + +- `web`(기본값): 제목, URL, 스니펫이 포함된 일반 Brave 웹 검색 +- `llm-context`: 근거 제시를 위해 미리 추출된 텍스트 청크와 출처를 제공하는 Brave LLM Context API + +## 도구 매개변수 + +| 매개변수 | 설명 | +| ------------- | ------------------------------------------------------------------- | +| `query` | 검색어(필수) | +| `count` | 반환할 결과 수(1-10, 기본값: 5) | +| `country` | 2자리 ISO 국가 코드(예: "US", "DE") | +| `language` | 검색 결과용 ISO 639-1 언어 코드(예: "en", "de", "fr") | +| `search_lang` | Brave 검색 언어 코드(예: `en`, `en-gb`, `zh-hans`) | +| `ui_lang` | UI 요소용 ISO 언어 코드 | +| `freshness` | 시간 필터: `day`(24시간), `week`, `month` 또는 `year` | +| `date_after` | 이 날짜(YYYY-MM-DD) 이후에 게시된 결과만 | +| `date_before` | 이 날짜(YYYY-MM-DD) 이전에 게시된 결과만 | + +**예시:** + +```javascript +// Country and language-specific search +await web_search({ + query: "renewable energy", + country: "DE", + language: "de", +}); + +// Recent results (past week) +await web_search({ + query: "AI news", + freshness: "week", +}); + +// Date range search +await web_search({ + query: "AI developments", + date_after: "2024-01-01", + date_before: "2024-06-30", +}); +``` + +## 참고 + +- OpenClaw는 Brave **Search** 요금제를 사용합니다. 레거시 구독(예: 월 2,000회 쿼리를 제공하던 기존 Free 요금제)이 있는 경우 계속 유효하지만, LLM Context나 더 높은 속도 제한 같은 최신 기능은 포함되지 않습니다. +- 각 Brave 요금제에는 매월 갱신되는 **월 \$5의 무료 크레딧**이 포함됩니다. Search 요금제 비용은 요청 1,000건당 \$5이므로, 이 크레딧으로 월 1,000회 쿼리를 사용할 수 있습니다. 예상치 못한 요금을 방지하려면 Brave 대시보드에서 사용량 한도를 설정하세요. 현재 요금제는 [Brave API 포털](https://brave.com/search/api/)을 참조하세요. +- Search 요금제에는 LLM Context 엔드포인트와 AI 추론 권한이 포함됩니다. 모델을 학습하거나 튜닝하기 위해 결과를 저장하려면 명시적인 저장 권한이 포함된 요금제가 필요합니다. Brave [서비스 약관](https://api-dashboard.search.brave.com/terms-of-service)을 참조하세요. +- `llm-context` 모드는 일반 웹 검색 스니펫 형태 대신 근거가 있는 출처 항목을 반환합니다. +- `llm-context` 모드는 `ui_lang`, `freshness`, `date_after`, `date_before`를 지원하지 않습니다. +- `ui_lang`에는 `en-US`와 같은 지역 하위 태그가 포함되어야 합니다. +- 결과는 기본적으로 15분 동안 캐시됩니다(`cacheTtlMinutes`로 구성 가능). + +전체 web_search config는 [웹 도구](/tools/web)를 참조하세요. diff --git a/docs/ko/channels/bluebubbles.md b/docs/ko/channels/bluebubbles.md new file mode 100644 index 000000000..9d1f653a2 --- /dev/null +++ b/docs/ko/channels/bluebubbles.md @@ -0,0 +1,441 @@ +--- +read_when: + - BlueBubbles 채널 설정 시 + - 웹훅 페어링 문제 해결 시 + - macOS에서 iMessage 구성 시 +summary: BlueBubbles macOS 서버를 통한 iMessage(REST 송수신, 타이핑, 반응, 페어링, 고급 작업). +title: BlueBubbles +x-i18n: + generated_at: "2026-04-05T12:35:03Z" + model: gpt-5.4 + provider: openai + source_hash: ed8e59a165bdfb8fd794ee2ad6e4dacd44aa02d512312c5f2fd7d15f863380bb + source_path: channels/bluebubbles.md + workflow: 15 +--- + +# BlueBubbles (macOS REST) + +상태: HTTP를 통해 BlueBubbles macOS 서버와 통신하는 번들 plugin입니다. 기존 imsg 채널보다 API가 더 풍부하고 설정이 더 쉬우므로 **iMessage 통합에 권장됩니다**. + +## 번들 plugin + +현재 OpenClaw 릴리스에는 BlueBubbles가 번들로 포함되어 있으므로, 일반적인 패키지 빌드에서는 별도의 `openclaw plugins install` 단계가 필요하지 않습니다. + +## 개요 + +- BlueBubbles helper 앱([bluebubbles.app](https://bluebubbles.app))을 통해 macOS에서 실행됩니다. +- 권장/테스트 환경: macOS Sequoia (15). macOS Tahoe (26)에서도 작동하지만, 현재 Tahoe에서는 편집 기능이 깨져 있으며 그룹 아이콘 업데이트는 성공으로 표시되더라도 동기화되지 않을 수 있습니다. +- OpenClaw는 REST API(`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`)를 통해 통신합니다. +- 수신 메시지는 웹훅으로 도착하며, 발신 답장, 타이핑 표시기, 읽음 확인, 탭백은 REST 호출로 처리됩니다. +- 첨부파일과 스티커는 수신 미디어로 수집되며(가능한 경우 에이전트에 표시됨). +- 페어링/허용 목록은 다른 채널과 동일한 방식으로 작동하며(` /channels/pairing` 등), `channels.bluebubbles.allowFrom` + 페어링 코드를 사용합니다. +- 반응은 Slack/Telegram과 마찬가지로 시스템 이벤트로 표시되므로, 에이전트는 답장 전에 이를 "언급"할 수 있습니다. +- 고급 기능: 편집, 전송 취소, 답장 스레딩, 메시지 효과, 그룹 관리. + +## 빠른 시작 + +1. Mac에 BlueBubbles 서버를 설치합니다([bluebubbles.app/install](https://bluebubbles.app/install)의 안내를 따르세요). +2. BlueBubbles 설정에서 웹 API를 활성화하고 비밀번호를 설정합니다. +3. `openclaw onboard`를 실행하고 BlueBubbles를 선택하거나, 수동으로 구성합니다: + + ```json5 + { + channels: { + bluebubbles: { + enabled: true, + serverUrl: "http://192.168.1.100:1234", + password: "example-password", + webhookPath: "/bluebubbles-webhook", + }, + }, + } + ``` + +4. BlueBubbles 웹훅이 게이트웨이를 가리키도록 설정합니다(예: `https://your-gateway-host:3000/bluebubbles-webhook?password=`). +5. 게이트웨이를 시작하면 웹훅 핸들러를 등록하고 페어링을 시작합니다. + +보안 참고: + +- 항상 웹훅 비밀번호를 설정하세요. +- 웹훅 인증은 항상 필요합니다. OpenClaw는 루프백/프록시 토폴로지와 관계없이 `channels.bluebubbles.password`와 일치하는 비밀번호/guid를 포함하지 않는 BlueBubbles 웹훅 요청을 거부합니다(예: `?password=` 또는 `x-password`). +- 비밀번호 인증은 전체 웹훅 본문을 읽거나 파싱하기 전에 검사됩니다. + +## Messages.app 활성 상태 유지하기(VM / 헤드리스 설정) + +일부 macOS VM / 상시 실행 설정에서는 Messages.app이 “유휴” 상태가 되어(앱을 열거나 포그라운드로 가져올 때까지 수신 이벤트가 중지됨) 문제가 생길 수 있습니다. 간단한 해결 방법은 AppleScript + LaunchAgent를 사용해 **5분마다 Messages를 건드리는 것**입니다. + +### 1) AppleScript 저장 + +다음 경로에 저장하세요: + +- `~/Scripts/poke-messages.scpt` + +예시 스크립트(비대화형, 포커스를 빼앗지 않음): + +```applescript +try + tell application "Messages" + if not running then + launch + end if + + -- Touch the scripting interface to keep the process responsive. + set _chatCount to (count of chats) + end tell +on error + -- Ignore transient failures (first-run prompts, locked session, etc). +end try +``` + +### 2) LaunchAgent 설치 + +다음 경로에 저장하세요: + +- `~/Library/LaunchAgents/com.user.poke-messages.plist` + +```xml + + + + + Label + com.user.poke-messages + + ProgramArguments + + /bin/bash + -lc + /usr/bin/osascript "$HOME/Scripts/poke-messages.scpt" + + + RunAtLoad + + + StartInterval + 300 + + StandardOutPath + /tmp/poke-messages.log + StandardErrorPath + /tmp/poke-messages.err + + +``` + +참고: + +- 이는 **300초마다** 그리고 **로그인 시** 실행됩니다. +- 첫 실행 시 macOS **Automation** 프롬프트(`osascript` → Messages)가 나타날 수 있습니다. LaunchAgent를 실행하는 동일한 사용자 세션에서 이를 승인하세요. + +로드: + +```bash +launchctl unload ~/Library/LaunchAgents/com.user.poke-messages.plist 2>/dev/null || true +launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist +``` + +## 온보딩 + +BlueBubbles는 대화형 온보딩에서 사용할 수 있습니다: + +``` +openclaw onboard +``` + +마법사는 다음 항목을 묻습니다: + +- **Server URL** (필수): BlueBubbles 서버 주소(예: `http://192.168.1.100:1234`) +- **Password** (필수): BlueBubbles Server 설정의 API 비밀번호 +- **Webhook path** (선택 사항): 기본값은 `/bluebubbles-webhook` +- **DM policy**: pairing, allowlist, open, 또는 disabled +- **Allow list**: 전화번호, 이메일 또는 채팅 대상 + +CLI를 통해 BlueBubbles를 추가할 수도 있습니다: + +``` +openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password +``` + +## 접근 제어(DM + 그룹) + +DM: + +- 기본값: `channels.bluebubbles.dmPolicy = "pairing"`. +- 알 수 없는 발신자에게는 페어링 코드가 전송되며, 승인될 때까지 메시지는 무시됩니다(코드는 1시간 후 만료). +- 다음으로 승인: + - `openclaw pairing list bluebubbles` + - `openclaw pairing approve bluebubbles ` +- 페어링이 기본 토큰 교환 방식입니다. 자세한 내용: [페어링](/channels/pairing) + +그룹: + +- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (기본값: `allowlist`). +- `channels.bluebubbles.groupAllowFrom`은 `allowlist`가 설정된 경우 그룹에서 누가 트리거할 수 있는지 제어합니다. + +### 연락처 이름 보강(macOS, 선택 사항) + +BlueBubbles 그룹 웹훅은 종종 원시 참가자 주소만 포함합니다. 대신 `GroupMembers` 컨텍스트에 로컬 연락처 이름이 표시되도록 하려면 macOS에서 로컬 연락처 보강을 선택적으로 활성화할 수 있습니다: + +- `channels.bluebubbles.enrichGroupParticipantsFromContacts = true`는 조회를 활성화합니다. 기본값: `false`. +- 조회는 그룹 접근, 명령 권한 부여, 멘션 게이팅이 모두 메시지 통과를 허용한 후에만 실행됩니다. +- 이름이 없는 전화 참가자만 보강됩니다. +- 로컬 일치 항목을 찾지 못하면 원시 전화번호가 대체값으로 유지됩니다. + +```json5 +{ + channels: { + bluebubbles: { + enrichGroupParticipantsFromContacts: true, + }, + }, +} +``` + +### 멘션 게이팅(그룹) + +BlueBubbles는 그룹 채팅에서 멘션 게이팅을 지원하며, iMessage/WhatsApp 동작과 일치합니다: + +- 멘션 감지에는 `agents.list[].groupChat.mentionPatterns`(또는 `messages.groupChat.mentionPatterns`)를 사용합니다. +- 그룹에 대해 `requireMention`이 활성화되면, 에이전트는 멘션되었을 때만 응답합니다. +- 권한 있는 발신자의 제어 명령은 멘션 게이팅을 우회합니다. + +그룹별 구성: + +```json5 +{ + channels: { + bluebubbles: { + groupPolicy: "allowlist", + groupAllowFrom: ["+15555550123"], + groups: { + "*": { requireMention: true }, // 모든 그룹의 기본값 + "iMessage;-;chat123": { requireMention: false }, // 특정 그룹에 대한 재정의 + }, + }, + }, +} +``` + +### 명령 게이팅 + +- 제어 명령(예: `/config`, `/model`)에는 권한 부여가 필요합니다. +- 명령 권한 부여를 결정하는 데 `allowFrom`과 `groupAllowFrom`을 사용합니다. +- 권한 있는 발신자는 그룹에서 멘션 없이도 제어 명령을 실행할 수 있습니다. + +## ACP 대화 바인딩 + +BlueBubbles 채팅은 전송 계층을 변경하지 않고도 영속적인 ACP 워크스페이스로 전환할 수 있습니다. + +빠른 운영자 흐름: + +- DM 또는 허용된 그룹 채팅 안에서 `/acp spawn codex --bind here`를 실행합니다. +- 이후 같은 BlueBubbles 대화의 메시지는 생성된 ACP 세션으로 라우팅됩니다. +- `/new`와 `/reset`은 같은 바인딩된 ACP 세션을 그 자리에서 재설정합니다. +- `/acp close`는 ACP 세션을 닫고 바인딩을 제거합니다. + +구성된 영속 바인딩도 최상위 `bindings[]` 항목에서 `type: "acp"` 및 `match.channel: "bluebubbles"`를 통해 지원됩니다. + +`match.peer.id`는 지원되는 모든 BlueBubbles 대상 형식을 사용할 수 있습니다: + +- `+15555550123` 또는 `user@example.com` 같은 정규화된 DM 핸들 +- `chat_id:` +- `chat_guid:` +- `chat_identifier:` + +안정적인 그룹 바인딩에는 `chat_id:*` 또는 `chat_identifier:*`를 권장합니다. + +예시: + +```json5 +{ + agents: { + list: [ + { + id: "codex", + runtime: { + type: "acp", + acp: { agent: "codex", backend: "acpx", mode: "persistent" }, + }, + }, + ], + }, + bindings: [ + { + type: "acp", + agentId: "codex", + match: { + channel: "bluebubbles", + accountId: "default", + peer: { kind: "dm", id: "+15555550123" }, + }, + acp: { label: "codex-imessage" }, + }, + ], +} +``` + +공통 ACP 바인딩 동작은 [ACP Agents](/tools/acp-agents)를 참조하세요. + +## 타이핑 + 읽음 확인 + +- **타이핑 표시기**: 응답 생성 전과 생성 중에 자동으로 전송됩니다. +- **읽음 확인**: `channels.bluebubbles.sendReadReceipts`로 제어됩니다(기본값: `true`). +- **타이핑 표시기**: OpenClaw는 타이핑 시작 이벤트를 전송하며, BlueBubbles는 전송 또는 타임아웃 시 자동으로 타이핑을 해제합니다(DELETE를 통한 수동 중지는 신뢰할 수 없음). + +```json5 +{ + channels: { + bluebubbles: { + sendReadReceipts: false, // 읽음 확인 비활성화 + }, + }, +} +``` + +## 고급 작업 + +BlueBubbles는 구성에서 활성화하면 고급 메시지 작업을 지원합니다: + +```json5 +{ + channels: { + bluebubbles: { + actions: { + reactions: true, // 탭백 (기본값: true) + edit: true, // 보낸 메시지 편집 (macOS 13+, macOS 26 Tahoe에서는 깨짐) + unsend: true, // 메시지 전송 취소 (macOS 13+) + reply: true, // 메시지 GUID 기준 답장 스레딩 + sendWithEffect: true, // 메시지 효과 (slam, loud 등) + renameGroup: true, // 그룹 채팅 이름 변경 + setGroupIcon: true, // 그룹 채팅 아이콘/사진 설정 (macOS 26 Tahoe에서 불안정) + addParticipant: true, // 그룹에 참가자 추가 + removeParticipant: true, // 그룹에서 참가자 제거 + leaveGroup: true, // 그룹 채팅 나가기 + sendAttachment: true, // 첨부파일/미디어 전송 + }, + }, + }, +} +``` + +사용 가능한 작업: + +- **react**: 탭백 반응 추가/제거 (`messageId`, `emoji`, `remove`) +- **edit**: 보낸 메시지 편집 (`messageId`, `text`) +- **unsend**: 메시지 전송 취소 (`messageId`) +- **reply**: 특정 메시지에 답장 (`messageId`, `text`, `to`) +- **sendWithEffect**: iMessage 효과와 함께 전송 (`text`, `to`, `effectId`) +- **renameGroup**: 그룹 채팅 이름 변경 (`chatGuid`, `displayName`) +- **setGroupIcon**: 그룹 채팅의 아이콘/사진 설정 (`chatGuid`, `media`) — macOS 26 Tahoe에서 불안정(API가 성공을 반환해도 아이콘이 동기화되지 않을 수 있음). +- **addParticipant**: 그룹에 사용자 추가 (`chatGuid`, `address`) +- **removeParticipant**: 그룹에서 사용자 제거 (`chatGuid`, `address`) +- **leaveGroup**: 그룹 채팅 나가기 (`chatGuid`) +- **upload-file**: 미디어/파일 전송 (`to`, `buffer`, `filename`, `asVoice`) + - 음성 메모: **MP3** 또는 **CAF** 오디오와 함께 `asVoice: true`를 설정하면 iMessage 음성 메시지로 전송됩니다. BlueBubbles는 음성 메모 전송 시 MP3 → CAF로 변환합니다. +- 레거시 별칭: `sendAttachment`도 계속 작동하지만, 정식 작업 이름은 `upload-file`입니다. + +### 메시지 ID(짧은 형식 vs 전체 형식) + +OpenClaw는 토큰 절약을 위해 _짧은_ 메시지 ID(예: `1`, `2`)를 표시할 수 있습니다. + +- `MessageSid` / `ReplyToId`는 짧은 ID일 수 있습니다. +- `MessageSidFull` / `ReplyToIdFull`에는 provider의 전체 ID가 들어 있습니다. +- 짧은 ID는 메모리 내 값이며, 재시작 또는 캐시 제거 시 만료될 수 있습니다. +- 작업은 짧은 `messageId`와 전체 `messageId`를 모두 허용하지만, 더 이상 사용할 수 없는 짧은 ID는 오류가 발생합니다. + +지속적인 자동화와 저장에는 전체 ID를 사용하세요: + +- 템플릿: `{{MessageSidFull}}`, `{{ReplyToIdFull}}` +- 컨텍스트: 수신 페이로드의 `MessageSidFull` / `ReplyToIdFull` + +템플릿 변수는 [구성](/gateway/configuration)을 참조하세요. + +## 블록 스트리밍 + +응답을 단일 메시지로 보낼지 블록 단위로 스트리밍할지 제어합니다: + +```json5 +{ + channels: { + bluebubbles: { + blockStreaming: true, // 블록 스트리밍 활성화(기본적으로 꺼짐) + }, + }, +} +``` + +## 미디어 + 제한 + +- 수신 첨부파일은 다운로드되어 미디어 캐시에 저장됩니다. +- 수신 및 발신 미디어의 용량 제한은 `channels.bluebubbles.mediaMaxMb`로 제어합니다(기본값: 8 MB). +- 발신 텍스트는 `channels.bluebubbles.textChunkLimit`까지 청크로 분할됩니다(기본값: 4000자). + +## 구성 참조 + +전체 구성: [구성](/gateway/configuration) + +Provider 옵션: + +- `channels.bluebubbles.enabled`: 채널 활성화/비활성화. +- `channels.bluebubbles.serverUrl`: BlueBubbles REST API 기본 URL. +- `channels.bluebubbles.password`: API 비밀번호. +- `channels.bluebubbles.webhookPath`: 웹훅 엔드포인트 경로(기본값: `/bluebubbles-webhook`). +- `channels.bluebubbles.dmPolicy`: `pairing | allowlist | open | disabled` (기본값: `pairing`). +- `channels.bluebubbles.allowFrom`: DM 허용 목록(핸들, 이메일, E.164 번호, `chat_id:*`, `chat_guid:*`). +- `channels.bluebubbles.groupPolicy`: `open | allowlist | disabled` (기본값: `allowlist`). +- `channels.bluebubbles.groupAllowFrom`: 그룹 발신자 허용 목록. +- `channels.bluebubbles.enrichGroupParticipantsFromContacts`: macOS에서 게이팅 통과 후 이름 없는 그룹 참가자를 로컬 연락처로부터 선택적으로 보강합니다. 기본값: `false`. +- `channels.bluebubbles.groups`: 그룹별 구성(`requireMention` 등). +- `channels.bluebubbles.sendReadReceipts`: 읽음 확인 전송(기본값: `true`). +- `channels.bluebubbles.blockStreaming`: 블록 스트리밍 활성화(기본값: `false`; 스트리밍 답장에 필요). +- `channels.bluebubbles.textChunkLimit`: 발신 청크 크기(문자 수, 기본값: 4000). +- `channels.bluebubbles.chunkMode`: `length`(기본값)는 `textChunkLimit`를 초과할 때만 분할합니다. `newline`은 길이 기준 분할 전에 빈 줄(문단 경계)에서 분할합니다. +- `channels.bluebubbles.mediaMaxMb`: 수신/발신 미디어 용량 제한(MB, 기본값: 8). +- `channels.bluebubbles.mediaLocalRoots`: 발신 로컬 미디어 경로에 허용되는 절대 로컬 디렉터리의 명시적 허용 목록. 이를 구성하지 않으면 로컬 경로 전송은 기본적으로 거부됩니다. 계정별 재정의: `channels.bluebubbles.accounts..mediaLocalRoots`. +- `channels.bluebubbles.historyLimit`: 컨텍스트용 최대 그룹 메시지 수(0이면 비활성화). +- `channels.bluebubbles.dmHistoryLimit`: DM 기록 제한. +- `channels.bluebubbles.actions`: 특정 작업 활성화/비활성화. +- `channels.bluebubbles.accounts`: 다중 계정 구성. + +관련 전역 옵션: + +- `agents.list[].groupChat.mentionPatterns`(또는 `messages.groupChat.mentionPatterns`). +- `messages.responsePrefix`. + +## 주소 지정 / 전달 대상 + +안정적인 라우팅에는 `chat_guid`를 권장합니다: + +- `chat_guid:iMessage;-;+15555550123` (그룹에 권장) +- `chat_id:123` +- `chat_identifier:...` +- 직접 핸들: `+15555550123`, `user@example.com` + - 직접 핸들에 기존 DM 채팅이 없으면 OpenClaw가 `POST /api/v1/chat/new`를 통해 새로 생성합니다. 이 기능을 사용하려면 BlueBubbles Private API가 활성화되어 있어야 합니다. + +## 보안 + +- 웹훅 요청은 `guid`/`password` 쿼리 매개변수 또는 헤더를 `channels.bluebubbles.password`와 비교하여 인증됩니다. +- API 비밀번호와 웹훅 엔드포인트는 비밀로 유지하세요(자격 증명처럼 취급하세요). +- BlueBubbles 웹훅 인증에는 localhost 우회가 없습니다. 웹훅 트래픽을 프록시하는 경우 요청 전 구간에 걸쳐 BlueBubbles 비밀번호를 유지하세요. 여기서 `gateway.trustedProxies`는 `channels.bluebubbles.password`를 대체하지 않습니다. [게이트웨이 보안](/gateway/security#reverse-proxy-configuration)을 참조하세요. +- LAN 외부에 노출하는 경우 BlueBubbles 서버에서 HTTPS + 방화벽 규칙을 활성화하세요. + +## 문제 해결 + +- 타이핑/읽음 이벤트가 더 이상 작동하지 않으면 BlueBubbles 웹훅 로그를 확인하고 게이트웨이 경로가 `channels.bluebubbles.webhookPath`와 일치하는지 검증하세요. +- 페어링 코드는 1시간 후 만료됩니다. `openclaw pairing list bluebubbles` 및 `openclaw pairing approve bluebubbles `를 사용하세요. +- 반응 기능에는 BlueBubbles private API(`POST /api/v1/message/react`)가 필요합니다. 서버 버전이 이를 노출하는지 확인하세요. +- 편집/전송 취소는 macOS 13+ 및 호환되는 BlueBubbles 서버 버전이 필요합니다. macOS 26(Tahoe)에서는 private API 변경으로 인해 현재 편집 기능이 깨져 있습니다. +- 그룹 아이콘 업데이트는 macOS 26(Tahoe)에서 불안정할 수 있습니다. API는 성공을 반환해도 새 아이콘이 동기화되지 않을 수 있습니다. +- OpenClaw는 BlueBubbles 서버의 macOS 버전을 기준으로 알려진 문제 있는 작업을 자동으로 숨깁니다. macOS 26(Tahoe)에서 여전히 편집이 표시되면 `channels.bluebubbles.actions.edit=false`로 수동 비활성화하세요. +- 상태/헬스 정보는 `openclaw status --all` 또는 `openclaw status --deep`를 사용하세요. + +일반적인 채널 워크플로 참조는 [채널](/channels) 및 [Plugins](/tools/plugin) 가이드를 참조하세요. + +## 관련 문서 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 접근 모델 및 보안 강화 diff --git a/docs/ko/channels/broadcast-groups.md b/docs/ko/channels/broadcast-groups.md new file mode 100644 index 000000000..26cc3b3f9 --- /dev/null +++ b/docs/ko/channels/broadcast-groups.md @@ -0,0 +1,449 @@ +--- +read_when: + - 브로드캐스트 그룹을 구성할 때 + - WhatsApp에서 다중 에이전트 응답을 디버깅할 때 +status: experimental +summary: WhatsApp 메시지를 여러 에이전트에 브로드캐스트합니다 +title: 브로드캐스트 그룹 +x-i18n: + generated_at: "2026-04-05T12:34:44Z" + model: gpt-5.4 + provider: openai + source_hash: 1d117ae65ec3b63c2bd4b3c215d96f32d7eafa0f99a9cd7378e502c15e56ca56 + source_path: channels/broadcast-groups.md + workflow: 15 +--- + +# 브로드캐스트 그룹 + +**상태:** 실험적 +**버전:** 2026.1.9에 추가됨 + +## 개요 + +브로드캐스트 그룹을 사용하면 여러 에이전트가 동일한 메시지를 동시에 처리하고 응답할 수 있습니다. 이를 통해 하나의 WhatsApp 그룹 또는 DM에서 함께 작업하는 전문화된 에이전트 팀을 만들 수 있으며, 모두 하나의 전화번호를 사용합니다. + +현재 범위: **WhatsApp 전용** (web 채널). + +브로드캐스트 그룹은 채널 허용 목록과 그룹 활성화 규칙 이후에 평가됩니다. WhatsApp 그룹에서는 OpenClaw가 일반적으로 응답하는 경우(예: 그룹 설정에 따라 멘션이 있을 때) 브로드캐스트가 발생한다는 뜻입니다. + +## 사용 사례 + +### 1. 전문화된 에이전트 팀 + +원자적이고 집중된 책임을 가진 여러 에이전트를 배포합니다: + +``` +Group: "Development Team" +Agents: + - CodeReviewer (코드 스니펫 검토) + - DocumentationBot (문서 생성) + - SecurityAuditor (취약점 검사) + - TestGenerator (테스트 케이스 제안) +``` + +각 에이전트는 동일한 메시지를 처리하고 자신의 전문 관점을 제공합니다. + +### 2. 다국어 지원 + +``` +Group: "International Support" +Agents: + - Agent_EN (영어로 응답) + - Agent_DE (독일어로 응답) + - Agent_ES (스페인어로 응답) +``` + +### 3. 품질 보증 워크플로 + +``` +Group: "Customer Support" +Agents: + - SupportAgent (답변 제공) + - QAAgent (품질 검토, 문제를 발견한 경우에만 응답) +``` + +### 4. 작업 자동화 + +``` +Group: "Project Management" +Agents: + - TaskTracker (작업 데이터베이스 업데이트) + - TimeLogger (소요 시간 기록) + - ReportGenerator (요약 생성) +``` + +## 구성 + +### 기본 설정 + +최상위 `broadcast` 섹션을 추가합니다(`bindings` 옆). 키는 WhatsApp 피어 ID입니다: + +- 그룹 채팅: 그룹 JID(예: `120363403215116621@g.us`) +- DM: E.164 전화번호(예: `+15551234567`) + +```json +{ + "broadcast": { + "120363403215116621@g.us": ["alfred", "baerbel", "assistant3"] + } +} +``` + +**결과:** OpenClaw가 이 채팅에 응답해야 할 때 세 에이전트가 모두 실행됩니다. + +### 처리 전략 + +에이전트가 메시지를 처리하는 방식을 제어합니다: + +#### 병렬(기본값) + +모든 에이전트가 동시에 처리합니다: + +```json +{ + "broadcast": { + "strategy": "parallel", + "120363403215116621@g.us": ["alfred", "baerbel"] + } +} +``` + +#### 순차 + +에이전트가 순서대로 처리합니다(이전 에이전트가 끝날 때까지 다음 에이전트가 대기): + +```json +{ + "broadcast": { + "strategy": "sequential", + "120363403215116621@g.us": ["alfred", "baerbel"] + } +} +``` + +### 전체 예제 + +```json +{ + "agents": { + "list": [ + { + "id": "code-reviewer", + "name": "Code Reviewer", + "workspace": "/path/to/code-reviewer", + "sandbox": { "mode": "all" } + }, + { + "id": "security-auditor", + "name": "Security Auditor", + "workspace": "/path/to/security-auditor", + "sandbox": { "mode": "all" } + }, + { + "id": "docs-generator", + "name": "Documentation Generator", + "workspace": "/path/to/docs-generator", + "sandbox": { "mode": "all" } + } + ] + }, + "broadcast": { + "strategy": "parallel", + "120363403215116621@g.us": ["code-reviewer", "security-auditor", "docs-generator"], + "120363424282127706@g.us": ["support-en", "support-de"], + "+15555550123": ["assistant", "logger"] + } +} +``` + +## 작동 방식 + +### 메시지 흐름 + +1. **수신 메시지**가 WhatsApp 그룹에 도착합니다 +2. **브로드캐스트 확인**: 시스템이 피어 ID가 `broadcast`에 있는지 확인합니다 +3. **브로드캐스트 목록에 있으면**: + - 나열된 모든 에이전트가 메시지를 처리합니다 + - 각 에이전트는 자체 세션 키와 격리된 컨텍스트를 가집니다 + - 에이전트는 병렬(기본값) 또는 순차적으로 처리합니다 +4. **브로드캐스트 목록에 없으면**: + - 일반 라우팅이 적용됩니다(처음 일치하는 binding) + +참고: 브로드캐스트 그룹은 채널 허용 목록이나 그룹 활성화 규칙(멘션/명령 등)을 우회하지 않습니다. 메시지가 처리 대상이 될 때 _어떤 에이전트가 실행되는지_ 만 변경합니다. + +### 세션 격리 + +브로드캐스트 그룹의 각 에이전트는 다음을 완전히 분리해서 유지합니다: + +- **세션 키** (`agent:alfred:whatsapp:group:120363...` 대 `agent:baerbel:whatsapp:group:120363...`) +- **대화 기록** (에이전트는 다른 에이전트의 메시지를 보지 못함) +- **워크스페이스** (구성된 경우 별도 샌드박스) +- **도구 접근 권한** (서로 다른 허용/거부 목록) +- **메모리/컨텍스트** (별도 `IDENTITY.md`, `SOUL.md` 등) +- **그룹 컨텍스트 버퍼** (컨텍스트에 사용되는 최근 그룹 메시지)는 피어별로 공유되므로, 트리거될 때 모든 브로드캐스트 에이전트가 동일한 컨텍스트를 봅니다 + +이를 통해 각 에이전트는 다음을 가질 수 있습니다: + +- 서로 다른 성격 +- 서로 다른 도구 접근 권한(예: 읽기 전용 대 읽기-쓰기) +- 서로 다른 모델(예: opus 대 sonnet) +- 서로 다른 Skills 설치 + +### 예: 격리된 세션 + +그룹 `120363403215116621@g.us`에 에이전트 `["alfred", "baerbel"]`가 있는 경우: + +**Alfred의 컨텍스트:** + +``` +Session: agent:alfred:whatsapp:group:120363403215116621@g.us +History: [사용자 메시지, alfred의 이전 응답] +Workspace: /Users/user/openclaw-alfred/ +Tools: read, write, exec +``` + +**Bärbel의 컨텍스트:** + +``` +Session: agent:baerbel:whatsapp:group:120363403215116621@g.us +History: [사용자 메시지, baerbel의 이전 응답] +Workspace: /Users/user/openclaw-baerbel/ +Tools: read only +``` + +## 모범 사례 + +### 1. 에이전트의 역할을 집중시키기 + +각 에이전트를 하나의 명확한 책임으로 설계합니다: + +```json +{ + "broadcast": { + "DEV_GROUP": ["formatter", "linter", "tester"] + } +} +``` + +✅ **좋음:** 각 에이전트에 하나의 역할이 있음 +❌ **나쁨:** 범용 "dev-helper" 에이전트 하나 + +### 2. 설명적인 이름 사용 + +각 에이전트가 무엇을 하는지 명확하게 만드세요: + +```json +{ + "agents": { + "security-scanner": { "name": "Security Scanner" }, + "code-formatter": { "name": "Code Formatter" }, + "test-generator": { "name": "Test Generator" } + } +} +``` + +### 3. 서로 다른 도구 접근 권한 구성 + +각 에이전트에 필요한 도구만 부여하세요: + +```json +{ + "agents": { + "reviewer": { + "tools": { "allow": ["read", "exec"] } // 읽기 전용 + }, + "fixer": { + "tools": { "allow": ["read", "write", "edit", "exec"] } // 읽기-쓰기 + } + } +} +``` + +### 4. 성능 모니터링 + +에이전트가 많다면 다음을 고려하세요: + +- 속도를 위해 `"strategy": "parallel"`(기본값) 사용 +- 브로드캐스트 그룹을 5~10개 에이전트로 제한 +- 더 단순한 에이전트에는 더 빠른 모델 사용 + +### 5. 실패를 우아하게 처리 + +에이전트는 독립적으로 실패합니다. 한 에이전트의 오류가 다른 에이전트를 막지 않습니다: + +``` +Message → [Agent A ✓, Agent B ✗ error, Agent C ✓] +Result: Agent A와 C는 응답, Agent B는 오류 기록 +``` + +## 호환성 + +### 제공자 + +브로드캐스트 그룹은 현재 다음과 함께 작동합니다: + +- ✅ WhatsApp (구현됨) +- 🚧 Telegram (계획됨) +- 🚧 Discord (계획됨) +- 🚧 Slack (계획됨) + +### 라우팅 + +브로드캐스트 그룹은 기존 라우팅과 함께 작동합니다: + +```json +{ + "bindings": [ + { + "match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } }, + "agentId": "alfred" + } + ], + "broadcast": { + "GROUP_B": ["agent1", "agent2"] + } +} +``` + +- `GROUP_A`: alfred만 응답함(일반 라우팅) +- `GROUP_B`: agent1과 agent2가 모두 응답함(브로드캐스트) + +**우선순위:** `broadcast`가 `bindings`보다 우선합니다. + +## 문제 해결 + +### 에이전트가 응답하지 않음 + +**확인할 사항:** + +1. 에이전트 ID가 `agents.list`에 존재하는지 +2. 피어 ID 형식이 올바른지(예: `120363403215116621@g.us`) +3. 에이전트가 거부 목록에 없는지 + +**디버그:** + +```bash +tail -f ~/.openclaw/logs/gateway.log | grep broadcast +``` + +### 하나의 에이전트만 응답함 + +**원인:** 피어 ID가 `bindings`에는 있지만 `broadcast`에는 없을 수 있습니다. + +**해결:** 브로드캐스트 구성에 추가하거나 bindings에서 제거하세요. + +### 성능 문제 + +**에이전트가 많아 느릴 때:** + +- 그룹당 에이전트 수 줄이기 +- 더 가벼운 모델 사용(opus 대신 sonnet) +- 샌드박스 시작 시간 확인 + +## 예제 + +### 예제 1: 코드 리뷰 팀 + +```json +{ + "broadcast": { + "strategy": "parallel", + "120363403215116621@g.us": [ + "code-formatter", + "security-scanner", + "test-coverage", + "docs-checker" + ] + }, + "agents": { + "list": [ + { + "id": "code-formatter", + "workspace": "~/agents/formatter", + "tools": { "allow": ["read", "write"] } + }, + { + "id": "security-scanner", + "workspace": "~/agents/security", + "tools": { "allow": ["read", "exec"] } + }, + { + "id": "test-coverage", + "workspace": "~/agents/testing", + "tools": { "allow": ["read", "exec"] } + }, + { "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } } + ] + } +} +``` + +**사용자 전송:** 코드 스니펫 +**응답:** + +- code-formatter: "들여쓰기를 수정하고 타입 힌트를 추가했습니다" +- security-scanner: "⚠️ 12번째 줄에 SQL 인젝션 취약점이 있습니다" +- test-coverage: "커버리지는 45%이며 오류 사례에 대한 테스트가 누락되어 있습니다" +- docs-checker: "함수 `process_data`에 대한 docstring이 누락되어 있습니다" + +### 예제 2: 다국어 지원 + +```json +{ + "broadcast": { + "strategy": "sequential", + "+15555550123": ["detect-language", "translator-en", "translator-de"] + }, + "agents": { + "list": [ + { "id": "detect-language", "workspace": "~/agents/lang-detect" }, + { "id": "translator-en", "workspace": "~/agents/translate-en" }, + { "id": "translator-de", "workspace": "~/agents/translate-de" } + ] + } +} +``` + +## API 참조 + +### 구성 스키마 + +```typescript +interface OpenClawConfig { + broadcast?: { + strategy?: "parallel" | "sequential"; + [peerId: string]: string[]; + }; +} +``` + +### 필드 + +- `strategy` (선택 사항): 에이전트를 처리하는 방식 + - `"parallel"` (기본값): 모든 에이전트가 동시에 처리 + - `"sequential"`: 에이전트가 배열 순서대로 처리 +- `[peerId]`: WhatsApp 그룹 JID, E.164 번호 또는 기타 피어 ID + - 값: 메시지를 처리해야 하는 에이전트 ID 배열 + +## 제한 사항 + +1. **최대 에이전트 수:** 하드 제한은 없지만 10개 이상 에이전트는 느릴 수 있습니다 +2. **공유 컨텍스트:** 에이전트는 서로의 응답을 보지 못합니다(의도된 동작) +3. **메시지 순서:** 병렬 응답은 어떤 순서로든 도착할 수 있습니다 +4. **속도 제한:** 모든 에이전트가 WhatsApp 속도 제한에 포함됩니다 + +## 향후 개선 사항 + +계획된 기능: + +- [ ] 공유 컨텍스트 모드(에이전트가 서로의 응답을 볼 수 있음) +- [ ] 에이전트 조정(에이전트가 서로에게 신호를 보낼 수 있음) +- [ ] 동적 에이전트 선택(메시지 내용에 따라 에이전트 선택) +- [ ] 에이전트 우선순위(일부 에이전트가 다른 에이전트보다 먼저 응답) + +## 함께 보기 + +- [다중 에이전트 구성](/tools/multi-agent-sandbox-tools) +- [라우팅 구성](/channels/channel-routing) +- [세션 관리](/concepts/session) diff --git a/docs/ko/channels/channel-routing.md b/docs/ko/channels/channel-routing.md new file mode 100644 index 000000000..e8a2120cc --- /dev/null +++ b/docs/ko/channels/channel-routing.md @@ -0,0 +1,138 @@ +--- +read_when: + - 채널 라우팅 또는 받은편지함 동작을 변경할 때 +summary: 채널별 라우팅 규칙(WhatsApp, Telegram, Discord, Slack)과 공유 컨텍스트 +title: 채널 라우팅 +x-i18n: + generated_at: "2026-04-05T12:34:38Z" + model: gpt-5.4 + provider: openai + source_hash: 63916c4dd0af5fc9bbd12581a9eb15fea14a380c5ade09323ca0c237db61e537 + source_path: channels/channel-routing.md + workflow: 15 +--- + +# 채널 및 라우팅 + +OpenClaw는 응답을 **메시지가 들어온 채널로 다시 라우팅합니다**. 모델이 채널을 선택하는 것이 아니라, 라우팅은 결정적이며 호스트 구성에 의해 제어됩니다. + +## 주요 용어 + +- **채널**: `telegram`, `whatsapp`, `discord`, `irc`, `googlechat`, `slack`, `signal`, `imessage`, `line` 및 확장 채널. `webchat`은 내부 WebChat UI 채널이며 구성 가능한 아웃바운드 채널이 아닙니다. +- **AccountId**: 채널별 계정 인스턴스(지원되는 경우). +- 선택적 채널 기본 계정: `channels..defaultAccount`는 아웃바운드 경로가 `accountId`를 지정하지 않을 때 사용할 계정을 선택합니다. + - 다중 계정 구성에서는 두 개 이상의 계정이 설정되어 있을 때 명시적 기본값(`defaultAccount` 또는 `accounts.default`)을 설정하세요. 그렇지 않으면 폴백 라우팅이 첫 번째 정규화된 계정 ID를 선택할 수 있습니다. +- **AgentId**: 격리된 워크스페이스 + 세션 저장소(“브레인”). +- **SessionKey**: 컨텍스트를 저장하고 동시성을 제어하는 데 사용하는 버킷 키입니다. + +## 세션 키 형태(예시) + +다이렉트 메시지는 에이전트의 **메인** 세션으로 통합됩니다. + +- `agent::` (기본값: `agent:main:main`) + +그룹과 채널은 채널별로 계속 격리됩니다. + +- 그룹: `agent:::group:` +- 채널/룸: `agent:::channel:` + +스레드: + +- Slack/Discord 스레드는 기본 키에 `:thread:`를 추가합니다. +- Telegram 포럼 토픽은 그룹 키에 `:topic:`를 포함합니다. + +예시: + +- `agent:main:telegram:group:-1001234567890:topic:42` +- `agent:main:discord:channel:123456:thread:987654` + +## 메인 DM 경로 고정 + +`session.dmScope`가 `main`일 때 다이렉트 메시지는 하나의 메인 세션을 공유할 수 있습니다. +메인 세션의 `lastRoute`가 소유자가 아닌 DM에 의해 덮어써지는 것을 방지하기 위해, +OpenClaw는 다음 조건이 모두 참일 때 `allowFrom`에서 고정된 소유자를 추론합니다. + +- `allowFrom`에 와일드카드가 아닌 항목이 정확히 하나 있습니다. +- 해당 항목을 그 채널의 구체적인 발신자 ID로 정규화할 수 있습니다. +- 인바운드 DM 발신자가 그 고정된 소유자와 일치하지 않습니다. + +이 불일치 사례에서 OpenClaw는 여전히 인바운드 세션 메타데이터를 기록하지만, +메인 세션 `lastRoute` 업데이트는 건너뜁니다. + +## 라우팅 규칙(에이전트가 선택되는 방식) + +라우팅은 각 인바운드 메시지에 대해 **하나의 에이전트**를 선택합니다. + +1. **정확한 피어 일치** (`peer.kind` + `peer.id`가 있는 `bindings`) +2. **상위 피어 일치** (스레드 상속) +3. **길드 + 역할 일치** (Discord) via `guildId` + `roles` +4. **길드 일치** (Discord) via `guildId` +5. **팀 일치** (Slack) via `teamId` +6. **계정 일치** (채널의 `accountId`) +7. **채널 일치** (해당 채널의 모든 계정, `accountId: "*"` ) +8. **기본 에이전트** (`agents.list[].default`, 없으면 첫 번째 목록 항목, 폴백은 `main`) + +바인딩에 여러 일치 필드(`peer`, `guildId`, `teamId`, `roles`)가 포함된 경우, 해당 바인딩이 적용되려면 **제공된 모든 필드가 일치해야 합니다**. + +일치한 에이전트가 어떤 워크스페이스와 세션 저장소를 사용할지 결정합니다. + +## 브로드캐스트 그룹(여러 에이전트 실행) + +브로드캐스트 그룹을 사용하면 **OpenClaw가 일반적으로 응답하는 경우**(예: WhatsApp 그룹에서 멘션/활성화 게이팅 후) 같은 피어에 대해 **여러 에이전트**를 실행할 수 있습니다. + +구성: + +```json5 +{ + broadcast: { + strategy: "parallel", + "120363403215116621@g.us": ["alfred", "baerbel"], + "+15555550123": ["support", "logger"], + }, +} +``` + +참조: [브로드캐스트 그룹](/channels/broadcast-groups). + +## 구성 개요 + +- `agents.list`: 이름이 지정된 에이전트 정의(워크스페이스, 모델 등) +- `bindings`: 인바운드 채널/계정/피어를 에이전트에 매핑 + +예시: + +```json5 +{ + agents: { + list: [{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }], + }, + bindings: [ + { match: { channel: "slack", teamId: "T123" }, agentId: "support" }, + { match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }, + ], +} +``` + +## 세션 저장소 + +세션 저장소는 상태 디렉터리 아래에 있습니다(기본값 `~/.openclaw`). + +- `~/.openclaw/agents//sessions/sessions.json` +- JSONL 트랜스크립트는 저장소와 같은 위치에 있습니다 + +`session.store`와 `{agentId}` 템플릿을 통해 저장소 경로를 재정의할 수 있습니다. + +Gateway 및 ACP 세션 검색은 기본 `agents/` 루트 아래와 템플릿이 적용된 `session.store` 루트 아래의 디스크 기반 에이전트 저장소도 검사합니다. 검색된 저장소는 해결된 해당 에이전트 루트 내부에 있어야 하며 일반 `sessions.json` 파일을 사용해야 합니다. 심볼릭 링크와 루트 외부 경로는 무시됩니다. + +## WebChat 동작 + +WebChat은 **선택된 에이전트**에 연결되며 기본적으로 해당 에이전트의 메인 세션을 사용합니다. 이 때문에 WebChat에서는 해당 에이전트의 교차 채널 컨텍스트를 한 곳에서 볼 수 있습니다. + +## 응답 컨텍스트 + +인바운드 응답에는 다음이 포함됩니다. + +- 가능한 경우 `ReplyToId`, `ReplyToBody`, `ReplyToSender` +- 인용된 컨텍스트는 `[Replying to ...]` 블록으로 `Body`에 추가됩니다. + +이 동작은 채널 전반에 걸쳐 일관됩니다. diff --git a/docs/ko/channels/discord.md b/docs/ko/channels/discord.md new file mode 100644 index 000000000..18ce096be --- /dev/null +++ b/docs/ko/channels/discord.md @@ -0,0 +1,1259 @@ +--- +read_when: + - Discord 채널 기능 작업 중 +summary: Discord 봇 지원 상태, 기능 및 구성 +title: Discord +x-i18n: + generated_at: "2026-04-05T12:36:23Z" + model: gpt-5.4 + provider: openai + source_hash: e757d321d80d05642cd9e24b51fb47897bacaf8db19df83bd61a49a8ce51ed3a + source_path: channels/discord.md + workflow: 15 +--- + +# Discord (Bot API) + +상태: 공식 Discord gateway를 통해 DM 및 길드 채널에서 사용할 준비가 되었습니다. + + + + Discord DM은 기본적으로 페어링 모드입니다. + + + 네이티브 명령 동작 및 명령 카탈로그. + + + 채널 전반의 진단 및 복구 흐름. + + + +## 빠른 설정 + +새 애플리케이션과 봇을 만들고, 봇을 서버에 추가한 다음, OpenClaw에 페어링해야 합니다. 봇은 본인의 비공개 서버에 추가하는 것을 권장합니다. 아직 서버가 없다면 먼저 [서버를 만드세요](https://support.discord.com/hc/en-us/articles/204849977-How-do-I-create-a-server) (**Create My Own > For me and my friends** 선택). + + + + [Discord Developer Portal](https://discord.com/developers/applications)로 이동한 다음 **New Application**을 클릭하세요. 이름은 "OpenClaw"처럼 지정합니다. + + 사이드바에서 **Bot**을 클릭하세요. **Username**은 OpenClaw 에이전트를 부르는 이름으로 설정합니다. + + + + + 계속 **Bot** 페이지에서 아래로 스크롤하여 **Privileged Gateway Intents**에서 다음을 활성화하세요. + + - **Message Content Intent** (필수) + - **Server Members Intent** (권장; 역할 allowlist 및 이름-대-ID 매칭에 필요) + - **Presence Intent** (선택 사항; 상태 업데이트가 필요할 때만) + + + + + **Bot** 페이지 상단으로 다시 올라가서 **Reset Token**을 클릭하세요. + + + 이름과 달리, 이 작업은 첫 번째 토큰을 생성합니다. 실제로 "재설정"되는 것은 없습니다. + + + 토큰을 복사해 안전한 곳에 저장하세요. 이것이 **Bot Token**이며 곧 필요합니다. + + + + + 사이드바에서 **OAuth2**를 클릭하세요. 서버에 봇을 추가할 수 있도록 올바른 권한이 포함된 초대 URL을 생성합니다. + + 아래로 스크롤하여 **OAuth2 URL Generator**에서 다음을 활성화하세요. + + - `bot` + - `applications.commands` + + 아래에 **Bot Permissions** 섹션이 나타납니다. 다음을 활성화하세요. + + - View Channels + - Send Messages + - Read Message History + - Embed Links + - Attach Files + - Add Reactions (선택 사항) + + 아래쪽에 생성된 URL을 복사해 브라우저에 붙여넣고, 서버를 선택한 뒤 **Continue**를 클릭하여 연결하세요. 이제 Discord 서버에서 봇이 보여야 합니다. + + + + + Discord 앱으로 돌아가서 내부 ID를 복사할 수 있도록 Developer Mode를 활성화해야 합니다. + + 1. **User Settings**(아바타 옆 톱니바퀴 아이콘) → **Advanced** → **Developer Mode** 켜기 + 2. 사이드바에서 **서버 아이콘** 우클릭 → **Copy Server ID** + 3. **내 아바타** 우클릭 → **Copy User ID** + + **Server ID**와 **User ID**를 Bot Token과 함께 저장하세요. 다음 단계에서 이 세 가지를 모두 OpenClaw에 전달하게 됩니다. + + + + + 페어링이 작동하려면 Discord에서 봇이 나에게 DM을 보낼 수 있어야 합니다. **서버 아이콘**을 우클릭 → **Privacy Settings** → **Direct Messages**를 켜세요. + + 그러면 서버 멤버(봇 포함)가 나에게 DM을 보낼 수 있습니다. OpenClaw와 Discord DM을 사용하려면 이 설정을 켜 둬야 합니다. 길드 채널만 사용할 계획이라면 페어링 후 DM을 꺼도 됩니다. + + + + + Discord 봇 토큰은 비밀 정보입니다(비밀번호와 유사). 에이전트에 메시지를 보내기 전에 OpenClaw를 실행하는 머신에 설정하세요. + +```bash +export DISCORD_BOT_TOKEN="YOUR_BOT_TOKEN" +openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN --dry-run +openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN +openclaw config set channels.discord.enabled true --strict-json +openclaw gateway +``` + + OpenClaw가 이미 백그라운드 서비스로 실행 중이라면, OpenClaw Mac 앱을 통해 재시작하거나 `openclaw gateway run` 프로세스를 중지 후 다시 시작하세요. + + + + + + + + 기존 채널(예: Telegram)에서 OpenClaw 에이전트와 대화하며 요청하세요. Discord가 첫 번째 채널이라면 대신 CLI / config 탭을 사용하세요. + + > "이미 Discord 봇 토큰을 config에 설정했습니다. User ID ``와 Server ID ``로 Discord 설정을 마무리해 주세요." + + + 파일 기반 구성을 선호한다면 다음과 같이 설정하세요. + +```json5 +{ + channels: { + discord: { + enabled: true, + token: { + source: "env", + provider: "default", + id: "DISCORD_BOT_TOKEN", + }, + }, + }, +} +``` + + 기본 계정용 env 폴백: + +```bash +DISCORD_BOT_TOKEN=... +``` + + 일반 텍스트 `token` 값도 지원됩니다. `channels.discord.token`에는 env/file/exec provider 전반에 걸쳐 SecretRef 값도 지원됩니다. 자세한 내용은 [Secrets Management](/gateway/secrets)를 참조하세요. + + + + + + + + gateway가 실행 중일 때까지 기다린 다음, Discord에서 봇에게 DM을 보내세요. 봇이 페어링 코드를 응답합니다. + + + + 기존 채널의 에이전트에게 페어링 코드를 보내세요. + + > "이 Discord 페어링 코드를 승인해 주세요: ``" + + + +```bash +openclaw pairing list discord +openclaw pairing approve discord +``` + + + + + 페어링 코드는 1시간 후 만료됩니다. + + 이제 Discord에서 DM을 통해 에이전트와 대화할 수 있어야 합니다. + + + + + +토큰 확인은 계정 인식 방식으로 이루어집니다. config의 토큰 값이 env 폴백보다 우선합니다. `DISCORD_BOT_TOKEN`은 기본 계정에만 사용됩니다. +고급 아웃바운드 호출(메시지 도구/채널 작업)의 경우, 명시적 호출별 `token`이 해당 호출에 사용됩니다. 이는 전송 및 읽기/프로브 스타일 작업(예: read/search/fetch/thread/pins/permissions)에 적용됩니다. 계정 정책/재시도 설정은 여전히 활성 런타임 스냅샷에서 선택된 계정에서 가져옵니다. + + +## 권장: 길드 워크스페이스 설정 + +DM이 작동하면 Discord 서버를 전체 워크스페이스로 설정할 수 있습니다. 이렇게 하면 각 채널이 자체 컨텍스트를 가진 독립적인 에이전트 세션을 갖게 됩니다. 나와 봇만 있는 비공개 서버에 권장됩니다. + + + + 이렇게 하면 에이전트가 DM뿐 아니라 서버의 모든 채널에서 응답할 수 있습니다. + + + + > "내 Discord Server ID ``를 guild allowlist에 추가해 주세요" + + + +```json5 +{ + channels: { + discord: { + groupPolicy: "allowlist", + guilds: { + YOUR_SERVER_ID: { + requireMention: true, + users: ["YOUR_USER_ID"], + }, + }, + }, + }, +} +``` + + + + + + + + 기본적으로 에이전트는 길드 채널에서 @mention되었을 때만 응답합니다. 비공개 서버라면 모든 메시지에 응답하도록 하는 것이 좋습니다. + + + + > "이 서버에서는 @mentioned되지 않아도 내 에이전트가 응답하도록 해 주세요" + + + 길드 config에서 `requireMention: false`를 설정하세요. + +```json5 +{ + channels: { + discord: { + guilds: { + YOUR_SERVER_ID: { + requireMention: false, + }, + }, + }, + }, +} +``` + + + + + + + + 기본적으로 장기 메모리(`MEMORY.md`)는 DM 세션에서만 로드됩니다. 길드 채널에서는 `MEMORY.md`가 자동으로 로드되지 않습니다. + + + + > "Discord 채널에서 질문할 때 `MEMORY.md`의 장기 컨텍스트가 필요하면 memory_search 또는 memory_get을 사용해 주세요." + + + 모든 채널에서 공유 컨텍스트가 필요하다면 안정적인 지침은 `AGENTS.md` 또는 `USER.md`에 넣으세요(모든 세션에 주입됨). 장기 메모는 `MEMORY.md`에 보관하고, 필요할 때 메모리 도구로 접근하세요. + + + + + + +이제 Discord 서버에 몇 개의 채널을 만들고 대화를 시작하세요. 에이전트는 채널 이름을 볼 수 있으며, 각 채널은 자체적으로 격리된 세션을 갖습니다. 따라서 워크플로에 맞게 `#coding`, `#home`, `#research` 등을 설정할 수 있습니다. + +## 런타임 모델 + +- Gateway가 Discord 연결을 소유합니다. +- 응답 라우팅은 결정적입니다. Discord 수신 응답은 다시 Discord로 돌아갑니다. +- 기본값(`session.dmScope=main`)에서는 직접 채팅이 에이전트 메인 세션(`agent:main:main`)을 공유합니다. +- 길드 채널은 격리된 세션 키(`agent::discord:channel:`)를 사용합니다. +- 그룹 DM은 기본적으로 무시됩니다(`channels.discord.dm.groupEnabled=false`). +- 네이티브 슬래시 명령은 격리된 명령 세션(`agent::discord:slash:`)에서 실행되지만, 라우팅된 대화 세션에는 여전히 `CommandTargetSessionKey`를 전달합니다. + +## 포럼 채널 + +Discord 포럼 및 미디어 채널은 스레드 게시물만 허용합니다. OpenClaw는 이를 만드는 두 가지 방법을 지원합니다. + +- 포럼 부모(`channel:`)에 메시지를 보내 스레드를 자동 생성합니다. 스레드 제목은 메시지의 첫 번째 비어 있지 않은 줄을 사용합니다. +- `openclaw message thread create`를 사용해 스레드를 직접 생성합니다. 포럼 채널에는 `--message-id`를 전달하지 마세요. + +예시: 포럼 부모로 보내 스레드 생성 + +```bash +openclaw message send --channel discord --target channel: \ + --message "Topic title\nBody of the post" +``` + +예시: 포럼 스레드를 명시적으로 생성 + +```bash +openclaw message thread create --channel discord --target channel: \ + --thread-name "Topic title" --message "Body of the post" +``` + +포럼 부모는 Discord 컴포넌트를 허용하지 않습니다. 컴포넌트가 필요하면 스레드 자체(`channel:`)로 보내세요. + +## 인터랙티브 컴포넌트 + +OpenClaw는 에이전트 메시지에 대해 Discord components v2 컨테이너를 지원합니다. `components` 페이로드와 함께 메시지 도구를 사용하세요. 상호작용 결과는 일반 수신 메시지처럼 에이전트로 다시 라우팅되며 기존 Discord `replyToMode` 설정을 따릅니다. + +지원되는 블록: + +- `text`, `section`, `separator`, `actions`, `media-gallery`, `file` +- 액션 행은 최대 5개의 버튼 또는 단일 선택 메뉴를 허용합니다 +- 선택 유형: `string`, `user`, `role`, `mentionable`, `channel` + +기본적으로 컴포넌트는 단일 사용입니다. 버튼, 선택, 폼을 만료될 때까지 여러 번 사용할 수 있도록 하려면 `components.reusable=true`를 설정하세요. + +버튼을 누를 수 있는 사용자를 제한하려면 해당 버튼에 `allowedUsers`를 설정하세요(Discord 사용자 ID, 태그 또는 `*`). 설정된 경우, 일치하지 않는 사용자는 ephemeral 거부 메시지를 받습니다. + +`/model` 및 `/models` 슬래시 명령은 provider 및 모델 드롭다운과 Submit 단계를 포함한 인터랙티브 모델 선택기를 엽니다. 선택기 응답은 ephemeral이며 호출한 사용자만 사용할 수 있습니다. + +파일 첨부: + +- `file` 블록은 첨부 참조(`attachment://`)를 가리켜야 합니다 +- 첨부 파일은 `media`/`path`/`filePath`(단일 파일)로 제공하세요. 여러 파일에는 `media-gallery`를 사용하세요 +- 업로드 이름이 첨부 참조와 일치해야 한다면 `filename`을 사용해 이름을 재정의하세요 + +모달 폼: + +- 최대 5개 필드로 `components.modal`을 추가하세요 +- 필드 유형: `text`, `checkbox`, `radio`, `select`, `role-select`, `user-select` +- OpenClaw가 자동으로 트리거 버튼을 추가합니다 + +예시: + +```json5 +{ + channel: "discord", + action: "send", + to: "channel:123456789012345678", + message: "선택적 폴백 텍스트", + components: { + reusable: true, + text: "경로를 선택하세요", + blocks: [ + { + type: "actions", + buttons: [ + { + label: "승인", + style: "success", + allowedUsers: ["123456789012345678"], + }, + { label: "거절", style: "danger" }, + ], + }, + { + type: "actions", + select: { + type: "string", + placeholder: "옵션을 선택하세요", + options: [ + { label: "옵션 A", value: "a" }, + { label: "옵션 B", value: "b" }, + ], + }, + }, + ], + modal: { + title: "세부 정보", + triggerLabel: "폼 열기", + fields: [ + { type: "text", label: "요청자" }, + { + type: "select", + label: "우선순위", + options: [ + { label: "낮음", value: "low" }, + { label: "높음", value: "high" }, + ], + }, + ], + }, + }, +} +``` + +## 액세스 제어 및 라우팅 + + + + `channels.discord.dmPolicy`는 DM 액세스를 제어합니다(레거시: `channels.discord.dm.policy`). + + - `pairing` (기본값) + - `allowlist` + - `open` (`channels.discord.allowFrom`에 `"*"` 포함 필요, 레거시: `channels.discord.dm.allowFrom`) + - `disabled` + + DM 정책이 open이 아니면, 알 수 없는 사용자는 차단됩니다(또는 `pairing` 모드에서는 페어링이 안내됩니다). + + 다중 계정 우선순위: + + - `channels.discord.accounts.default.allowFrom`은 `default` 계정에만 적용됩니다. + - 이름 있는 계정은 자체 `allowFrom`이 설정되지 않은 경우 `channels.discord.allowFrom`을 상속합니다. + - 이름 있는 계정은 `channels.discord.accounts.default.allowFrom`은 상속하지 않습니다. + + 전달용 DM 대상 형식: + + - `user:` + - `<@id>` mention + + 단순 숫자 ID는 모호하므로, 명시적인 user/channel 대상 종류가 제공되지 않으면 거부됩니다. + + + + + 길드 처리는 `channels.discord.groupPolicy`로 제어됩니다. + + - `open` + - `allowlist` + - `disabled` + + `channels.discord`가 존재할 때의 안전한 기준선은 `allowlist`입니다. + + `allowlist` 동작: + + - 길드는 `channels.discord.guilds`와 일치해야 합니다(`id` 권장, slug 허용) + - 선택적 발신자 allowlist: `users`(안정적인 ID 권장) 및 `roles`(역할 ID만); 둘 중 하나라도 구성되면 발신자는 `users` 또는 `roles`와 일치할 때 허용됩니다 + - 직접 이름/태그 매칭은 기본적으로 비활성화되어 있습니다. 긴급 호환성 모드로만 `channels.discord.dangerouslyAllowNameMatching: true`를 활성화하세요 + - `users`에 이름/태그를 사용할 수는 있지만 ID가 더 안전합니다. 이름/태그 항목을 사용하면 `openclaw security audit`가 경고합니다 + - 길드에 `channels`가 구성되어 있으면 목록에 없는 채널은 거부됩니다 + - 길드에 `channels` 블록이 없으면 해당 allowlist 길드의 모든 채널이 허용됩니다 + + 예시: + +```json5 +{ + channels: { + discord: { + groupPolicy: "allowlist", + guilds: { + "123456789012345678": { + requireMention: true, + ignoreOtherMentions: true, + users: ["987654321098765432"], + roles: ["123456789012345678"], + channels: { + general: { allow: true }, + help: { allow: true, requireMention: true }, + }, + }, + }, + }, + }, +} +``` + + `DISCORD_BOT_TOKEN`만 설정하고 `channels.discord` 블록을 만들지 않으면, 런타임 폴백은 `groupPolicy="allowlist"`입니다(로그에 경고 출력). 이 동작은 `channels.defaults.groupPolicy`가 `open`이어도 동일합니다. + + + + + 길드 메시지는 기본적으로 mention 게이트가 적용됩니다. + + mention 감지에는 다음이 포함됩니다. + + - 명시적인 봇 mention + - 구성된 mention 패턴(`agents.list[].groupChat.mentionPatterns`, 폴백 `messages.groupChat.mentionPatterns`) + - 지원되는 경우의 암시적 reply-to-bot 동작 + + `requireMention`은 길드/채널별로 구성됩니다(`channels.discord.guilds...`). + `ignoreOtherMentions`를 설정하면 다른 사용자/역할은 mention하지만 봇은 mention하지 않는 메시지(@everyone/@here 제외)를 선택적으로 버립니다. + + 그룹 DM: + + - 기본값: 무시(`dm.groupEnabled=false`) + - 선택적 allowlist: `dm.groupChannels` 사용(채널 ID 또는 slug) + + + + +### 역할 기반 에이전트 라우팅 + +`bindings[].match.roles`를 사용해 Discord 길드 멤버를 역할 ID별로 서로 다른 에이전트로 라우팅하세요. 역할 기반 바인딩은 역할 ID만 허용하며, peer 또는 parent-peer 바인딩 뒤, guild-only 바인딩 전에 평가됩니다. 바인딩에 다른 매치 필드(예: `peer` + `guildId` + `roles`)도 설정되어 있으면, 구성된 모든 필드가 일치해야 합니다. + +```json5 +{ + bindings: [ + { + agentId: "opus", + match: { + channel: "discord", + guildId: "123456789012345678", + roles: ["111111111111111111"], + }, + }, + { + agentId: "sonnet", + match: { + channel: "discord", + guildId: "123456789012345678", + }, + }, + ], +} +``` + +## Developer Portal 설정 + + + + + 1. Discord Developer Portal -> **Applications** -> **New Application** + 2. **Bot** -> **Add Bot** + 3. 봇 토큰 복사 + + + + + **Bot -> Privileged Gateway Intents**에서 다음을 활성화하세요. + + - Message Content Intent + - Server Members Intent (권장) + + Presence intent는 선택 사항이며 상태 업데이트를 받고 싶을 때만 필요합니다. 봇 상태를 설정하는 것(`setPresence`)에는 멤버 상태 업데이트 활성화가 필요하지 않습니다. + + + + + OAuth URL 생성기: + + - 범위: `bot`, `applications.commands` + + 일반적인 기본 권한: + + - View Channels + - Send Messages + - Read Message History + - Embed Links + - Attach Files + - Add Reactions (선택 사항) + + 명시적으로 필요한 경우가 아니면 `Administrator`는 피하세요. + + + + + Discord Developer Mode를 활성화한 다음 다음을 복사하세요. + + - 서버 ID + - 채널 ID + - 사용자 ID + + 안정적인 감사 및 프로브를 위해 OpenClaw config에서는 숫자 ID를 권장합니다. + + + + +## 네이티브 명령과 명령 인증 + +- `commands.native`의 기본값은 `"auto"`이며 Discord에서 활성화됩니다. +- 채널별 재정의: `channels.discord.commands.native`. +- `commands.native=false`는 이전에 등록된 Discord 네이티브 명령을 명시적으로 제거합니다. +- 네이티브 명령 인증은 일반 메시지 처리와 동일한 Discord allowlist/정책을 사용합니다. +- 권한이 없는 사용자에게도 명령이 Discord UI에 보일 수는 있지만, 실행 시에는 여전히 OpenClaw 인증이 적용되며 "not authorized"를 반환합니다. + +명령 카탈로그 및 동작은 [Slash commands](/tools/slash-commands)를 참조하세요. + +기본 슬래시 명령 설정: + +- `ephemeral: true` + +## 기능 세부 정보 + + + + Discord는 에이전트 출력에서 응답 태그를 지원합니다. + + - `[[reply_to_current]]` + - `[[reply_to:]]` + + 제어 설정: `channels.discord.replyToMode` + + - `off` (기본값) + - `first` + - `all` + + 참고: `off`는 암시적 응답 스레딩을 비활성화합니다. 명시적인 `[[reply_to_*]]` 태그는 여전히 적용됩니다. + + 메시지 ID는 컨텍스트/기록에 노출되므로 에이전트가 특정 메시지를 대상으로 지정할 수 있습니다. + + + + + OpenClaw는 임시 메시지를 보내고 텍스트가 도착할 때마다 수정하여 초안 응답을 스트리밍할 수 있습니다. + + - `channels.discord.streaming`은 미리보기 스트리밍을 제어합니다(`off` | `partial` | `block` | `progress`, 기본값: `off`). + - Discord 미리보기 수정은 특히 여러 봇이나 gateway가 같은 계정 또는 길드 트래픽을 공유할 때 빠르게 속도 제한에 걸릴 수 있으므로 기본값은 `off`입니다. + - `progress`는 채널 간 일관성을 위해 허용되며 Discord에서는 `partial`로 매핑됩니다. + - `channels.discord.streamMode`는 레거시 별칭이며 자동으로 마이그레이션됩니다. + - `partial`은 토큰이 도착할 때 단일 미리보기 메시지를 수정합니다. + - `block`은 초안 크기의 청크를 출력합니다(크기와 분할 기준은 `draftChunk`로 조정). + + 예시: + +```json5 +{ + channels: { + discord: { + streaming: "partial", + }, + }, +} +``` + + `block` 모드 청크 기본값(`channels.discord.textChunkLimit`에 맞춰 제한됨): + +```json5 +{ + channels: { + discord: { + streaming: "block", + draftChunk: { + minChars: 200, + maxChars: 800, + breakPreference: "paragraph", + }, + }, + }, +} +``` + + 미리보기 스트리밍은 텍스트 전용이며, 미디어 응답은 일반 전달 방식으로 폴백됩니다. + + 참고: 미리보기 스트리밍은 block streaming과 별개입니다. Discord에서 block streaming이 명시적으로 + 활성화되면 OpenClaw는 이중 스트리밍을 피하기 위해 미리보기 스트림을 건너뜁니다. + + + + + 길드 기록 컨텍스트: + + - `channels.discord.historyLimit` 기본값 `20` + - 폴백: `messages.groupChat.historyLimit` + - `0`은 비활성화 + + DM 기록 제어: + + - `channels.discord.dmHistoryLimit` + - `channels.discord.dms[""].historyLimit` + + 스레드 동작: + + - Discord 스레드는 채널 세션으로 라우팅됩니다 + - 부모 스레드 메타데이터는 부모 세션 연결에 사용할 수 있습니다 + - 스레드별 항목이 없으면 스레드 config는 부모 채널 config를 상속합니다 + + 채널 주제는 **신뢰되지 않은** 컨텍스트로 주입됩니다(시스템 프롬프트 아님). + 응답 및 인용 메시지 컨텍스트는 현재 받은 그대로 유지됩니다. + Discord allowlist는 주로 누가 에이전트를 트리거할 수 있는지를 제어하며, 추가 컨텍스트 전체를 가리는 경계는 아닙니다. + + + + + Discord는 스레드를 세션 대상에 바인딩할 수 있어, 해당 스레드의 후속 메시지가 동일한 세션(서브에이전트 세션 포함)으로 계속 라우팅되도록 할 수 있습니다. + + 명령: + + - `/focus ` 현재/새 스레드를 서브에이전트/세션 대상에 바인딩 + - `/unfocus` 현재 스레드 바인딩 제거 + - `/agents` 활성 실행 및 바인딩 상태 표시 + - `/session idle ` 포커스된 바인딩의 비활동 자동 unfocus 검사/업데이트 + - `/session max-age ` 포커스된 바인딩의 하드 최대 수명 검사/업데이트 + + 구성: + +```json5 +{ + session: { + threadBindings: { + enabled: true, + idleHours: 24, + maxAgeHours: 0, + }, + }, + channels: { + discord: { + threadBindings: { + enabled: true, + idleHours: 24, + maxAgeHours: 0, + spawnSubagentSessions: false, // 선택적 활성화 + }, + }, + }, +} +``` + + 참고: + + - `session.threadBindings.*`는 전역 기본값을 설정합니다. + - `channels.discord.threadBindings.*`는 Discord 동작을 재정의합니다. + - `sessions_spawn({ thread: true })`에 대해 자동 생성/바인딩하려면 `spawnSubagentSessions`가 true여야 합니다. + - ACP(` /acp spawn ... --thread ...` 또는 `sessions_spawn({ runtime: "acp", thread: true })`)에 대해 자동 생성/바인딩하려면 `spawnAcpSessions`가 true여야 합니다. + - 계정에서 스레드 바인딩이 비활성화되어 있으면 `/focus` 및 관련 스레드 바인딩 작업을 사용할 수 없습니다. + + 자세한 내용은 [Sub-agents](/tools/subagents), [ACP Agents](/tools/acp-agents), [Configuration Reference](/gateway/configuration-reference)를 참조하세요. + + + + + 안정적인 "항상 켜짐" ACP 워크스페이스의 경우, Discord 대화를 대상으로 하는 최상위 타입 지정 ACP 바인딩을 구성하세요. + + config 경로: + + - `type: "acp"` 및 `match.channel: "discord"`가 있는 `bindings[]` + + 예시: + +```json5 +{ + agents: { + list: [ + { + id: "codex", + runtime: { + type: "acp", + acp: { + agent: "codex", + backend: "acpx", + mode: "persistent", + cwd: "/workspace/openclaw", + }, + }, + }, + ], + }, + bindings: [ + { + type: "acp", + agentId: "codex", + match: { + channel: "discord", + accountId: "default", + peer: { kind: "channel", id: "222222222222222222" }, + }, + acp: { label: "codex-main" }, + }, + ], + channels: { + discord: { + guilds: { + "111111111111111111": { + channels: { + "222222222222222222": { + requireMention: false, + }, + }, + }, + }, + }, + }, +} +``` + + 참고: + + - `/acp spawn codex --bind here`는 현재 Discord 채널 또는 스레드를 그 자리에 바인딩하며, 이후 메시지도 동일한 ACP 세션으로 계속 라우팅합니다. + - 이는 여전히 "새 Codex ACP 세션 시작"을 의미할 수 있지만, 자체적으로 새 Discord 스레드를 생성하지는 않습니다. 기존 채널이 채팅 표면으로 유지됩니다. + - Codex는 여전히 자체 `cwd` 또는 디스크상의 백엔드 워크스페이스에서 실행될 수 있습니다. 그 워크스페이스는 Discord 스레드가 아니라 런타임 상태입니다. + - 스레드 메시지는 부모 채널 ACP 바인딩을 상속할 수 있습니다. + - 바인딩된 채널 또는 스레드에서 `/new`와 `/reset`은 동일한 ACP 세션을 그 자리에서 재설정합니다. + - 임시 스레드 바인딩도 여전히 작동하며, 활성 상태에서는 대상 확인을 재정의할 수 있습니다. + - `spawnAcpSessions`는 OpenClaw가 `--thread auto|here`를 통해 자식 스레드를 생성/바인딩해야 할 때만 필요합니다. 현재 채널에서 `/acp spawn ... --bind here`에는 필요하지 않습니다. + + 바인딩 동작 세부 정보는 [ACP Agents](/tools/acp-agents)를 참조하세요. + + + + + 길드별 리액션 알림 모드: + + - `off` + - `own` (기본값) + - `all` + - `allowlist` (`guilds..users` 사용) + + 리액션 이벤트는 시스템 이벤트로 변환되어 라우팅된 Discord 세션에 첨부됩니다. + + + + + `ackReaction`은 OpenClaw가 수신 메시지를 처리하는 동안 확인 이모지를 보냅니다. + + 확인 순서: + + - `channels.discord.accounts..ackReaction` + - `channels.discord.ackReaction` + - `messages.ackReaction` + - 에이전트 identity 이모지 폴백(`agents.list[].identity.emoji`, 없으면 "👀") + + 참고: + + - Discord는 유니코드 이모지 또는 커스텀 이모지 이름을 허용합니다. + - 채널 또는 계정에서 리액션을 비활성화하려면 `""`를 사용하세요. + + + + + 채널에서 시작한 config 쓰기는 기본적으로 활성화되어 있습니다. + + 이는 `/config set|unset` 흐름에 영향을 줍니다(명령 기능이 활성화된 경우). + + 비활성화: + +```json5 +{ + channels: { + discord: { + configWrites: false, + }, + }, +} +``` + + + + + `channels.discord.proxy`를 사용해 Discord gateway WebSocket 트래픽 및 시작 REST 조회(애플리케이션 ID + allowlist 확인)를 HTTP(S) 프록시를 통해 라우팅하세요. + +```json5 +{ + channels: { + discord: { + proxy: "http://proxy.example:8080", + }, + }, +} +``` + + 계정별 재정의: + +```json5 +{ + channels: { + discord: { + accounts: { + primary: { + proxy: "http://proxy.example:8080", + }, + }, + }, + }, +} +``` + + + + + 프록시된 메시지를 시스템 멤버 identity에 매핑하도록 PluralKit 확인을 활성화합니다. + +```json5 +{ + channels: { + discord: { + pluralkit: { + enabled: true, + token: "pk_live_...", // 선택 사항; 비공개 시스템에 필요 + }, + }, + }, +} +``` + + 참고: + + - allowlist는 `pk:`를 사용할 수 있습니다 + - 멤버 표시 이름은 `channels.discord.dangerouslyAllowNameMatching: true`일 때만 이름/slug 기준으로 매칭됩니다 + - 조회는 원본 메시지 ID를 사용하며 시간 창 제약이 있습니다 + - 조회가 실패하면 프록시된 메시지는 봇 메시지로 처리되어, `allowBots=true`가 아닌 한 버려집니다 + + + + + 상태 업데이트는 status 또는 activity 필드를 설정하거나 자동 상태를 활성화할 때 적용됩니다. + + status만 설정하는 예시: + +```json5 +{ + channels: { + discord: { + status: "idle", + }, + }, +} +``` + + activity 예시(커스텀 status가 기본 activity 유형): + +```json5 +{ + channels: { + discord: { + activity: "집중 시간", + activityType: 4, + }, + }, +} +``` + + 스트리밍 예시: + +```json5 +{ + channels: { + discord: { + activity: "라이브 코딩", + activityType: 1, + activityUrl: "https://twitch.tv/openclaw", + }, + }, +} +``` + + activity 유형 맵: + + - 0: Playing + - 1: Streaming (`activityUrl` 필요) + - 2: Listening + - 3: Watching + - 4: Custom (activity 텍스트를 상태 상태값으로 사용하며, 이모지는 선택 사항) + - 5: Competing + + 자동 상태 예시(런타임 상태 신호): + +```json5 +{ + channels: { + discord: { + autoPresence: { + enabled: true, + intervalMs: 30000, + minUpdateIntervalMs: 15000, + exhaustedText: "토큰 소진", + }, + }, + }, +} +``` + + 자동 상태는 런타임 가용성을 Discord 상태로 매핑합니다: 정상 => online, 저하 또는 알 수 없음 => idle, 소진 또는 사용 불가 => dnd. 선택적 텍스트 재정의: + + - `autoPresence.healthyText` + - `autoPresence.degradedText` + - `autoPresence.exhaustedText` (`{reason}` 플레이스홀더 지원) + + + + + Discord는 DM에서 버튼 기반 승인 처리를 지원하며, 선택적으로 원래 채널에 승인 프롬프트를 게시할 수 있습니다. + + config 경로: + + - `channels.discord.execApprovals.enabled` + - `channels.discord.execApprovals.approvers` (선택 사항; 가능할 경우 `commands.ownerAllowFrom`으로 폴백) + - `channels.discord.execApprovals.target` (`dm` | `channel` | `both`, 기본값: `dm`) + - `agentFilter`, `sessionFilter`, `cleanupAfterResolve` + + `enabled`가 설정되지 않았거나 `"auto"`이고, `execApprovals.approvers` 또는 `commands.ownerAllowFrom` 중 하나에서 최소 한 명의 승인자를 확인할 수 있으면 Discord는 네이티브 exec 승인을 자동 활성화합니다. Discord는 채널 `allowFrom`, 레거시 `dm.allowFrom`, 또는 직접 메시지 `defaultTo`에서 exec 승인자를 추론하지 않습니다. Discord를 네이티브 승인 클라이언트로 명시적으로 비활성화하려면 `enabled: false`를 설정하세요. + + `target`이 `channel` 또는 `both`이면 승인 프롬프트가 채널에 표시됩니다. 확인된 승인자만 버튼을 사용할 수 있으며, 다른 사용자는 ephemeral 거부 메시지를 받습니다. 승인 프롬프트에는 명령 텍스트가 포함되므로 신뢰할 수 있는 채널에서만 채널 전달을 활성화하세요. 세션 키에서 채널 ID를 도출할 수 없으면 OpenClaw는 DM 전달로 폴백합니다. + + Discord는 다른 채팅 채널에서 사용하는 공유 승인 버튼도 렌더링합니다. 네이티브 Discord 어댑터는 주로 승인자 DM 라우팅과 채널 팬아웃을 추가합니다. + 이러한 버튼이 मौजूद할 때는 그것이 기본 승인 UX이며, OpenClaw는 + 도구 결과가 채팅 승인을 사용할 수 없다고 말하거나 수동 승인이 유일한 경로일 때만 수동 `/approve` 명령을 포함해야 합니다. + + 이 핸들러에 대한 Gateway 인증은 다른 Gateway 클라이언트와 동일한 공유 자격 증명 확인 계약을 사용합니다. + + - env 우선 로컬 인증 (`OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD` 다음 `gateway.auth.*`) + - 로컬 모드에서는 `gateway.auth.*`가 설정되지 않은 경우에만 `gateway.remote.*`를 폴백으로 사용할 수 있으며, 구성되었지만 확인되지 않은 로컬 SecretRef는 fail closed 처리됩니다 + - 해당하는 경우 `gateway.remote.*`를 통한 원격 모드 지원 + - URL 재정의는 재정의 안전성을 가집니다. CLI 재정의는 암시적 자격 증명을 재사용하지 않으며, env 재정의는 env 자격 증명만 사용합니다 + + 승인 확인 동작: + + - `plugin:` 접두사가 있는 ID는 `plugin.approval.resolve`를 통해 확인됩니다. + - 다른 ID는 `exec.approval.resolve`를 통해 확인됩니다. + - Discord는 여기서 추가적인 exec-to-plugin 폴백 홉을 수행하지 않으며, ID + 접두사가 호출할 gateway 메서드를 결정합니다. + + Exec 승인은 기본적으로 30분 후 만료됩니다. 승인에 + 알 수 없는 승인 ID 오류가 발생하면, 승인자 확인, 기능 활성화 여부, + 그리고 전달된 승인 ID 종류가 대기 중인 요청과 일치하는지 확인하세요. + + 관련 문서: [Exec approvals](/tools/exec-approvals) + + + + +## 도구 및 작업 게이트 + +Discord 메시지 작업에는 메시징, 채널 관리, moderation, presence, 메타데이터 작업이 포함됩니다. + +핵심 예시: + +- 메시징: `sendMessage`, `readMessages`, `editMessage`, `deleteMessage`, `threadReply` +- 리액션: `react`, `reactions`, `emojiList` +- moderation: `timeout`, `kick`, `ban` +- presence: `setPresence` + +작업 게이트는 `channels.discord.actions.*` 아래에 있습니다. + +기본 게이트 동작: + +| 작업 그룹 | 기본값 | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| reactions, messages, threads, pins, polls, search, memberInfo, roleInfo, channelInfo, channels, voiceStatus, events, stickers, emojiUploads, stickerUploads, permissions | 활성화됨 | +| roles | 비활성화 | +| moderation | 비활성화 | +| presence | 비활성화 | + +## Components v2 UI + +OpenClaw는 exec 승인 및 교차 컨텍스트 마커에 Discord components v2를 사용합니다. Discord 메시지 작업은 커스텀 UI용 `components`도 받을 수 있습니다(고급 기능; discord 도구를 통해 컴포넌트 페이로드를 구성해야 함). 레거시 `embeds`도 여전히 사용할 수 있지만 권장하지 않습니다. + +- `channels.discord.ui.components.accentColor`는 Discord 컴포넌트 컨테이너에 사용되는 강조 색상을 설정합니다(hex). +- 계정별 설정: `channels.discord.accounts..ui.components.accentColor`. +- components v2가 있으면 `embeds`는 무시됩니다. + +예시: + +```json5 +{ + channels: { + discord: { + ui: { + components: { + accentColor: "#5865F2", + }, + }, + }, + }, +} +``` + +## 음성 채널 + +OpenClaw는 실시간 연속 대화를 위해 Discord 음성 채널에 참여할 수 있습니다. 이는 음성 메시지 첨부와는 별개의 기능입니다. + +요구 사항: + +- 네이티브 명령을 활성화하세요(`commands.native` 또는 `channels.discord.commands.native`). +- `channels.discord.voice`를 구성하세요. +- 봇은 대상 음성 채널에서 Connect + Speak 권한이 필요합니다. + +Discord 전용 네이티브 명령 `/vc join|leave|status`를 사용해 세션을 제어하세요. 이 명령은 계정 기본 에이전트를 사용하며 다른 Discord 명령과 동일한 allowlist 및 그룹 정책 규칙을 따릅니다. + +자동 참여 예시: + +```json5 +{ + channels: { + discord: { + voice: { + enabled: true, + autoJoin: [ + { + guildId: "123456789012345678", + channelId: "234567890123456789", + }, + ], + daveEncryption: true, + decryptionFailureTolerance: 24, + tts: { + provider: "openai", + openai: { voice: "alloy" }, + }, + }, + }, + }, +} +``` + +참고: + +- `voice.tts`는 음성 재생에 대해서만 `messages.tts`를 재정의합니다. +- 음성 전사 턴은 Discord `allowFrom`(또는 `dm.allowFrom`)에서 소유자 상태를 파생합니다. 소유자가 아닌 화자는 소유자 전용 도구(예: `gateway`, `cron`)에 접근할 수 없습니다. +- 음성은 기본적으로 활성화되어 있습니다. 비활성화하려면 `channels.discord.voice.enabled=false`를 설정하세요. +- `voice.daveEncryption` 및 `voice.decryptionFailureTolerance`는 `@discordjs/voice` join 옵션에 그대로 전달됩니다. +- `@discordjs/voice` 기본값은 설정되지 않은 경우 `daveEncryption=true` 및 `decryptionFailureTolerance=24`입니다. +- OpenClaw는 수신 복호화 실패도 감시하며, 짧은 시간 안에 반복 실패가 발생하면 음성 채널에서 나갔다가 다시 참여하여 자동 복구합니다. +- 수신 로그에 `DecryptionFailed(UnencryptedWhenPassthroughDisabled)`가 반복해서 표시되면, 이는 [discord.js #11419](https://github.com/discordjs/discord.js/issues/11419)에 추적된 상위 `@discordjs/voice` 수신 버그일 수 있습니다. + +## 음성 메시지 + +Discord 음성 메시지는 파형 미리보기를 표시하며 OGG/Opus 오디오와 메타데이터가 필요합니다. OpenClaw는 파형을 자동 생성하지만, gateway 호스트에서 오디오 파일을 검사하고 변환하려면 `ffmpeg`와 `ffprobe`를 사용할 수 있어야 합니다. + +요구 사항 및 제약: + +- **로컬 파일 경로**를 제공해야 합니다(URL은 거부됨). +- 텍스트 콘텐츠는 생략해야 합니다(Discord는 동일한 페이로드에서 텍스트 + 음성 메시지를 허용하지 않음). +- 어떤 오디오 형식이든 허용되며, 필요하면 OpenClaw가 OGG/Opus로 변환합니다. + +예시: + +```bash +message(action="send", channel="discord", target="channel:123", path="/path/to/audio.mp3", asVoice=true) +``` + +## 문제 해결 + + + + + - Message Content Intent를 활성화하세요 + - 사용자/멤버 확인에 의존한다면 Server Members Intent를 활성화하세요 + - 인텐트 변경 후 gateway를 재시작하세요 + + + + + + - `groupPolicy`를 확인하세요 + - `channels.discord.guilds` 아래 길드 allowlist를 확인하세요 + - 길드 `channels` 맵이 있으면 목록에 있는 채널만 허용됩니다 + - `requireMention` 동작과 mention 패턴을 확인하세요 + + 유용한 확인 명령: + +```bash +openclaw doctor +openclaw channels status --probe +openclaw logs --follow +``` + + + + + 일반적인 원인: + + - 일치하는 길드/채널 allowlist 없이 `groupPolicy="allowlist"` + - 잘못된 위치에 `requireMention`이 구성됨(`channels.discord.guilds` 또는 채널 항목 아래여야 함) + - 발신자가 길드/채널 `users` allowlist에 의해 차단됨 + + + + + + 일반적인 로그: + + - `Listener DiscordMessageListener timed out after 30000ms for event MESSAGE_CREATE` + - `Slow listener detected ...` + - `discord inbound worker timed out after ...` + + 리스너 예산 설정: + + - 단일 계정: `channels.discord.eventQueue.listenerTimeout` + - 다중 계정: `channels.discord.accounts..eventQueue.listenerTimeout` + + 워커 실행 시간 초과 설정: + + - 단일 계정: `channels.discord.inboundWorker.runTimeoutMs` + - 다중 계정: `channels.discord.accounts..inboundWorker.runTimeoutMs` + - 기본값: `1800000` (30분); 비활성화하려면 `0` 설정 + + 권장 기준선: + +```json5 +{ + channels: { + discord: { + accounts: { + default: { + eventQueue: { + listenerTimeout: 120000, + }, + inboundWorker: { + runTimeoutMs: 1800000, + }, + }, + }, + }, + }, +} +``` + + 느린 리스너 설정에는 `eventQueue.listenerTimeout`을 사용하고, 대기열에 있는 에이전트 턴에 대한 별도의 안전장치가 필요할 때만 `inboundWorker.runTimeoutMs`를 사용하세요. + + + + + `channels status --probe` 권한 검사는 숫자 채널 ID에서만 작동합니다. + + slug 키를 사용하면 런타임 매칭은 여전히 동작할 수 있지만, 프로브는 권한을 완전히 확인할 수 없습니다. + + + + + + - DM 비활성화: `channels.discord.dm.enabled=false` + - DM 정책 비활성화: `channels.discord.dmPolicy="disabled"` (레거시: `channels.discord.dm.policy`) + - `pairing` 모드에서 페어링 승인 대기 중 + + + + + 기본적으로 봇이 작성한 메시지는 무시됩니다. + + `channels.discord.allowBots=true`를 설정한다면 루프 동작을 피하기 위해 엄격한 mention 및 allowlist 규칙을 사용하세요. + 봇을 mention하는 봇 메시지만 허용하려면 `channels.discord.allowBots="mentions"`를 권장합니다. + + + + + + - Discord 음성 수신 복구 로직이 포함되도록 OpenClaw를 최신 상태로 유지하세요(`openclaw update`) + - `channels.discord.voice.daveEncryption=true`인지 확인하세요(기본값) + - `channels.discord.voice.decryptionFailureTolerance=24`(상위 기본값)에서 시작하고 필요한 경우에만 조정하세요 + - 다음 로그를 확인하세요: + - `discord voice: DAVE decrypt failures detected` + - `discord voice: repeated decrypt failures; attempting rejoin` + - 자동 재참여 후에도 실패가 계속되면 로그를 수집하고 [discord.js #11419](https://github.com/discordjs/discord.js/issues/11419)와 비교하세요 + + + + +## 구성 참조 포인터 + +기본 참조: + +- [Configuration reference - Discord](/gateway/configuration-reference#discord) + +신호가 강한 Discord 필드: + +- 시작/인증: `enabled`, `token`, `accounts.*`, `allowBots` +- 정책: `groupPolicy`, `dm.*`, `guilds.*`, `guilds.*.channels.*` +- 명령: `commands.native`, `commands.useAccessGroups`, `configWrites`, `slashCommand.*` +- 이벤트 큐: `eventQueue.listenerTimeout` (리스너 예산), `eventQueue.maxQueueSize`, `eventQueue.maxConcurrency` +- 인바운드 워커: `inboundWorker.runTimeoutMs` +- 응답/기록: `replyToMode`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit` +- 전달: `textChunkLimit`, `chunkMode`, `maxLinesPerMessage` +- 스트리밍: `streaming` (레거시 별칭: `streamMode`), `draftChunk`, `blockStreaming`, `blockStreamingCoalesce` +- 미디어/재시도: `mediaMaxMb`, `retry` + - `mediaMaxMb`는 Discord 아웃바운드 업로드 상한을 설정합니다(기본값: `8MB`) +- 작업: `actions.*` +- 상태: `activity`, `status`, `activityType`, `activityUrl` +- UI: `ui.components.accentColor` +- 기능: `threadBindings`, 최상위 `bindings[]` (`type: "acp"`), `pluralkit`, `execApprovals`, `intents`, `agentComponents`, `heartbeat`, `responsePrefix` + +## 안전 및 운영 + +- 봇 토큰은 비밀 정보로 취급하세요(관리되는 환경에서는 `DISCORD_BOT_TOKEN` 권장). +- 최소 권한의 Discord 권한만 부여하세요. +- 명령 배포/상태가 오래되었다면 gateway를 재시작하고 `openclaw channels status --probe`로 다시 확인하세요. + +## 관련 문서 + +- [Pairing](/channels/pairing) +- [Groups](/channels/groups) +- [Channel routing](/channels/channel-routing) +- [Security](/gateway/security) +- [Multi-agent routing](/concepts/multi-agent) +- [Troubleshooting](/channels/troubleshooting) +- [Slash commands](/tools/slash-commands) diff --git a/docs/ko/channels/feishu.md b/docs/ko/channels/feishu.md new file mode 100644 index 000000000..d1918c4c0 --- /dev/null +++ b/docs/ko/channels/feishu.md @@ -0,0 +1,794 @@ +--- +read_when: + - Feishu/Lark 봇을 연결하려는 경우 + - Feishu 채널을 구성하는 경우 +summary: Feishu 봇 개요, 기능 및 구성 +title: Feishu +x-i18n: + generated_at: "2026-04-05T12:35:40Z" + model: gpt-5.4 + provider: openai + source_hash: 4e39b6dfe3a3aa4ebbdb992975e570e4f1b5e79f3b400a555fc373a0d1889952 + source_path: channels/feishu.md + workflow: 15 +--- + +# Feishu 봇 + +Feishu(Lark)는 기업에서 메시징과 협업에 사용하는 팀 채팅 플랫폼입니다. 이 plugin은 플랫폼의 WebSocket 이벤트 구독을 사용해 OpenClaw를 Feishu/Lark 봇에 연결하므로, 공개 webhook URL을 노출하지 않고도 메시지를 수신할 수 있습니다. + +--- + +## 번들 plugin + +Feishu는 현재 OpenClaw 릴리스에 번들로 포함되어 제공되므로 별도의 plugin 설치가 필요하지 않습니다. + +번들된 Feishu가 포함되지 않은 이전 빌드 또는 사용자 지정 설치를 사용 중인 경우 수동으로 설치하세요. + +```bash +openclaw plugins install @openclaw/feishu +``` + +--- + +## 빠른 시작 + +Feishu 채널을 추가하는 방법은 두 가지입니다. + +### 방법 1: 온보딩(권장) + +OpenClaw를 방금 설치했다면 온보딩을 실행하세요. + +```bash +openclaw onboard +``` + +마법사는 다음 과정을 안내합니다. + +1. Feishu 앱 생성 및 자격 증명 수집 +2. OpenClaw에서 앱 자격 증명 구성 +3. Gateway 시작 + +✅ **구성 후**, Gateway 상태를 확인하세요. + +- `openclaw gateway status` +- `openclaw logs --follow` + +### 방법 2: CLI 설정 + +이미 초기 설치를 완료했다면 CLI를 통해 채널을 추가하세요. + +```bash +openclaw channels add +``` + +**Feishu**를 선택한 다음 App ID와 App Secret을 입력하세요. + +✅ **구성 후**, Gateway를 관리하세요. + +- `openclaw gateway status` +- `openclaw gateway restart` +- `openclaw logs --follow` + +--- + +## 1단계: Feishu 앱 만들기 + +### 1. Feishu Open Platform 열기 + +[Feishu Open Platform](https://open.feishu.cn/app)에 접속해 로그인하세요. + +Lark(글로벌) 테넌트는 [https://open.larksuite.com/app](https://open.larksuite.com/app)을 사용하고 Feishu config에서 `domain: "lark"`를 설정해야 합니다. + +### 2. 앱 만들기 + +1. **Create enterprise app**을 클릭합니다. +2. 앱 이름과 설명을 입력합니다. +3. 앱 아이콘을 선택합니다. + +![Create enterprise app](/images/feishu-step2-create-app.png) + +### 3. 자격 증명 복사 + +**Credentials & Basic Info**에서 다음을 복사하세요. + +- **App ID**(형식: `cli_xxx`) +- **App Secret** + +❗ **중요:** App Secret은 비공개로 유지하세요. + +![Get credentials](/images/feishu-step3-credentials.png) + +### 4. 권한 구성 + +**Permissions**에서 **Batch import**를 클릭하고 다음을 붙여넣으세요. + +```json +{ + "scopes": { + "tenant": [ + "aily:file:read", + "aily:file:write", + "application:application.app_message_stats.overview:readonly", + "application:application:self_manage", + "application:bot.menu:write", + "cardkit:card:read", + "cardkit:card:write", + "contact:user.employee_id:readonly", + "corehr:file:download", + "event:ip_list", + "im:chat.access_event.bot_p2p_chat:read", + "im:chat.members:bot_access", + "im:message", + "im:message.group_at_msg:readonly", + "im:message.p2p_msg:readonly", + "im:message:readonly", + "im:message:send_as_bot", + "im:resource" + ], + "user": ["aily:file:read", "aily:file:write", "im:chat.access_event.bot_p2p_chat:read"] + } +} +``` + +![Configure permissions](/images/feishu-step4-permissions.png) + +### 5. 봇 기능 활성화 + +**App Capability** > **Bot**에서 다음을 수행하세요. + +1. 봇 기능을 활성화합니다. +2. 봇 이름을 설정합니다. + +![Enable bot capability](/images/feishu-step5-bot-capability.png) + +### 6. 이벤트 구독 구성 + +⚠️ **중요:** 이벤트 구독을 설정하기 전에 다음을 확인하세요. + +1. 이미 Feishu에 대해 `openclaw channels add`를 실행했는지 +2. Gateway가 실행 중인지(`openclaw gateway status`) + +**Event Subscription**에서: + +1. **Use long connection to receive events**(WebSocket)를 선택합니다. +2. 이벤트 `im.message.receive_v1`을 추가합니다. +3. (선택 사항) Drive 댓글 워크플로를 위해 `drive.notice.comment_add_v1`도 추가합니다. + +⚠️ Gateway가 실행 중이 아니면 long-connection 설정을 저장하지 못할 수 있습니다. + +![Configure event subscription](/images/feishu-step6-event-subscription.png) + +### 7. 앱 게시 + +1. **Version Management & Release**에서 버전을 생성합니다. +2. 검토를 위해 제출하고 게시합니다. +3. 관리자 승인을 기다립니다(기업 앱은 보통 자동 승인됨). + +--- + +## 2단계: OpenClaw 구성 + +### 마법사로 구성(권장) + +```bash +openclaw channels add +``` + +**Feishu**를 선택하고 App ID와 App Secret을 붙여넣으세요. + +### config 파일로 구성 + +`~/.openclaw/openclaw.json`을 편집하세요. + +```json5 +{ + channels: { + feishu: { + enabled: true, + dmPolicy: "pairing", + accounts: { + main: { + appId: "cli_xxx", + appSecret: "xxx", + name: "My AI assistant", + }, + }, + }, + }, +} +``` + +`connectionMode: "webhook"`을 사용하는 경우 `verificationToken`과 `encryptKey`를 모두 설정하세요. Feishu webhook 서버는 기본적으로 `127.0.0.1`에 바인딩됩니다. 의도적으로 다른 바인딩 주소가 필요한 경우에만 `webhookHost`를 설정하세요. + +#### Verification Token 및 Encrypt Key(webhook 모드) + +webhook 모드를 사용할 때는 config에서 `channels.feishu.verificationToken`과 `channels.feishu.encryptKey`를 모두 설정하세요. 값을 얻으려면: + +1. Feishu Open Platform에서 앱을 엽니다. +2. **Development** → **Events & Callbacks**(开发配置 → 事件与回调)로 이동합니다. +3. **Encryption** 탭(加密策略)을 엽니다. +4. **Verification Token**과 **Encrypt Key**를 복사합니다. + +아래 스크린샷은 **Verification Token** 위치를 보여줍니다. **Encrypt Key**는 같은 **Encryption** 섹션에 표시됩니다. + +![Verification Token location](/images/feishu-verification-token.png) + +### 환경 변수로 구성 + +```bash +export FEISHU_APP_ID="cli_xxx" +export FEISHU_APP_SECRET="xxx" +``` + +### Lark(글로벌) 도메인 + +테넌트가 Lark(국제 버전)에 있는 경우 도메인을 `lark`(또는 전체 도메인 문자열)로 설정하세요. `channels.feishu.domain` 또는 계정별(`channels.feishu.accounts..domain`)로 설정할 수 있습니다. + +```json5 +{ + channels: { + feishu: { + domain: "lark", + accounts: { + main: { + appId: "cli_xxx", + appSecret: "xxx", + }, + }, + }, + }, +} +``` + +### 쿼터 최적화 플래그 + +선택적 플래그 두 가지로 Feishu API 사용량을 줄일 수 있습니다. + +- `typingIndicator`(기본값 `true`): `false`이면 입력 중 반응 호출을 건너뜁니다. +- `resolveSenderNames`(기본값 `true`): `false`이면 발신자 프로필 조회 호출을 건너뜁니다. + +최상위 또는 계정별로 설정하세요. + +```json5 +{ + channels: { + feishu: { + typingIndicator: false, + resolveSenderNames: false, + accounts: { + main: { + appId: "cli_xxx", + appSecret: "xxx", + typingIndicator: true, + resolveSenderNames: false, + }, + }, + }, + }, +} +``` + +--- + +## 3단계: 시작 + 테스트 + +### 1. Gateway 시작 + +```bash +openclaw gateway +``` + +### 2. 테스트 메시지 보내기 + +Feishu에서 봇을 찾아 메시지를 보내세요. + +### 3. 페어링 승인 + +기본적으로 봇은 페어링 코드를 응답합니다. 이를 승인하세요. + +```bash +openclaw pairing approve feishu +``` + +승인 후에는 정상적으로 대화할 수 있습니다. + +--- + +## 개요 + +- **Feishu 봇 채널**: Gateway가 관리하는 Feishu 봇 +- **결정적 라우팅**: 응답은 항상 Feishu로 돌아감 +- **세션 격리**: DM은 메인 세션을 공유하고 그룹은 분리됨 +- **WebSocket 연결**: Feishu SDK를 통한 long connection, 공개 URL 불필요 + +--- + +## 액세스 제어 + +### 다이렉트 메시지 + +- **기본값**: `dmPolicy: "pairing"`(알 수 없는 사용자는 페어링 코드를 받음) +- **페어링 승인**: + + ```bash + openclaw pairing list feishu + openclaw pairing approve feishu + ``` + +- **allowlist 모드**: 허용된 Open ID로 `channels.feishu.allowFrom`을 설정 + +### 그룹 채팅 + +**1. 그룹 정책**(`channels.feishu.groupPolicy`): + +- `"open"` = 그룹의 모든 사용자 허용 +- `"allowlist"` = `groupAllowFrom`만 허용 +- `"disabled"` = 그룹 메시지 비활성화 + +기본값: `allowlist` + +**2. 멘션 요구 사항**(`channels.feishu.requireMention`, `channels.feishu.groups..requireMention`으로 재정의 가능): + +- 명시적 `true` = @mention 필요 +- 명시적 `false` = 멘션 없이 응답 +- 설정되지 않았고 `groupPolicy: "open"`인 경우 = 기본값 `false` +- 설정되지 않았고 `groupPolicy`가 `"open"`이 아닌 경우 = 기본값 `true` + +--- + +## 그룹 구성 예시 + +### 모든 그룹 허용, @mention 불필요(open 그룹의 기본값) + +```json5 +{ + channels: { + feishu: { + groupPolicy: "open", + }, + }, +} +``` + +### 모든 그룹 허용, 하지만 여전히 @mention 필요 + +```json5 +{ + channels: { + feishu: { + groupPolicy: "open", + requireMention: true, + }, + }, +} +``` + +### 특정 그룹만 허용 + +```json5 +{ + channels: { + feishu: { + groupPolicy: "allowlist", + // Feishu group IDs (chat_id) look like: oc_xxx + groupAllowFrom: ["oc_xxx", "oc_yyy"], + }, + }, +} +``` + +### 그룹에서 메시지를 보낼 수 있는 발신자 제한(sender allowlist) + +그룹 자체를 허용하는 것 외에도, 해당 그룹의 **모든 메시지**는 발신자 open_id로 제한됩니다. `groups..allowFrom`에 나열된 사용자만 메시지가 처리되며, 다른 멤버의 메시지는 무시됩니다(이는 `/reset`이나 `/new` 같은 제어 명령에만 적용되는 것이 아니라 전체 발신자 수준 게이팅입니다). + +```json5 +{ + channels: { + feishu: { + groupPolicy: "allowlist", + groupAllowFrom: ["oc_xxx"], + groups: { + oc_xxx: { + // Feishu user IDs (open_id) look like: ou_xxx + allowFrom: ["ou_user1", "ou_user2"], + }, + }, + }, + }, +} +``` + +--- + + + +## 그룹/사용자 ID 가져오기 + +### 그룹 ID(chat_id) + +그룹 ID는 `oc_xxx` 형태입니다. + +**방법 1(권장)** + +1. Gateway를 시작하고 그룹에서 봇을 @mention합니다. +2. `openclaw logs --follow`를 실행하고 `chat_id`를 찾습니다. + +**방법 2** + +Feishu API 디버거를 사용해 그룹 채팅을 나열합니다. + +### 사용자 ID(open_id) + +사용자 ID는 `ou_xxx` 형태입니다. + +**방법 1(권장)** + +1. Gateway를 시작하고 봇에 DM을 보냅니다. +2. `openclaw logs --follow`를 실행하고 `open_id`를 찾습니다. + +**방법 2** + +사용자 Open ID는 페어링 요청에서 확인합니다. + +```bash +openclaw pairing list feishu +``` + +--- + +## 일반 명령 + +| 명령 | 설명 | +| --------- | ----------------- | +| `/status` | 봇 상태 표시 | +| `/reset` | 세션 재설정 | +| `/model` | 모델 표시/전환 | + +> 참고: Feishu는 아직 기본 명령 메뉴를 지원하지 않으므로 명령은 텍스트로 보내야 합니다. + +## Gateway 관리 명령 + +| 명령 | 설명 | +| -------------------------- | ----------------------------- | +| `openclaw gateway status` | Gateway 상태 표시 | +| `openclaw gateway install` | Gateway 서비스 설치/시작 | +| `openclaw gateway stop` | Gateway 서비스 중지 | +| `openclaw gateway restart` | Gateway 서비스 재시작 | +| `openclaw logs --follow` | Gateway 로그 추적 | + +--- + +## 문제 해결 + +### 그룹 채팅에서 봇이 응답하지 않음 + +1. 봇이 그룹에 추가되어 있는지 확인합니다. +2. 봇을 @mention했는지 확인합니다(기본 동작). +3. `groupPolicy`가 `"disabled"`로 설정되지 않았는지 확인합니다. +4. 로그를 확인합니다: `openclaw logs --follow` + +### 봇이 메시지를 수신하지 않음 + +1. 앱이 게시되고 승인되었는지 확인합니다. +2. 이벤트 구독에 `im.message.receive_v1`이 포함되어 있는지 확인합니다. +3. **long connection**이 활성화되어 있는지 확인합니다. +4. 앱 권한이 완전한지 확인합니다. +5. Gateway가 실행 중인지 확인합니다: `openclaw gateway status` +6. 로그를 확인합니다: `openclaw logs --follow` + +### App Secret 유출 + +1. Feishu Open Platform에서 App Secret을 재설정합니다. +2. config에서 App Secret을 업데이트합니다. +3. Gateway를 재시작합니다. + +### 메시지 전송 실패 + +1. 앱에 `im:message:send_as_bot` 권한이 있는지 확인합니다. +2. 앱이 게시되었는지 확인합니다. +3. 자세한 오류는 로그를 확인합니다. + +--- + +## 고급 구성 + +### 여러 계정 + +```json5 +{ + channels: { + feishu: { + defaultAccount: "main", + accounts: { + main: { + appId: "cli_xxx", + appSecret: "xxx", + name: "Primary bot", + }, + backup: { + appId: "cli_yyy", + appSecret: "yyy", + name: "Backup bot", + enabled: false, + }, + }, + }, + }, +} +``` + +`defaultAccount`는 아웃바운드 API가 `accountId`를 명시적으로 지정하지 않을 때 어떤 Feishu 계정을 사용할지 제어합니다. + +### 메시지 제한 + +- `textChunkLimit`: 아웃바운드 텍스트 청크 크기(기본값: 2000자) +- `mediaMaxMb`: 미디어 업로드/다운로드 제한(기본값: 30MB) + +### 스트리밍 + +Feishu는 인터랙티브 카드를 통한 스트리밍 응답을 지원합니다. 활성화하면 봇은 텍스트를 생성하면서 카드를 업데이트합니다. + +```json5 +{ + channels: { + feishu: { + streaming: true, // enable streaming card output (default true) + blockStreaming: true, // enable block-level streaming (default true) + }, + }, +} +``` + +전체 응답이 준비된 후 전송하려면 `streaming: false`로 설정하세요. + +### ACP 세션 + +Feishu는 다음에 대해 ACP를 지원합니다. + +- DM +- 그룹 주제 대화 + +Feishu ACP는 텍스트 명령 기반으로 동작합니다. 기본 slash-command 메뉴는 없으므로 대화에서 `/acp ...` 메시지를 직접 사용하세요. + +#### 영구 ACP 바인딩 + +최상위 타입 지정 ACP 바인딩을 사용해 Feishu DM 또는 주제 대화를 영구 ACP 세션에 고정할 수 있습니다. + +```json5 +{ + agents: { + list: [ + { + id: "codex", + runtime: { + type: "acp", + acp: { + agent: "codex", + backend: "acpx", + mode: "persistent", + cwd: "/workspace/openclaw", + }, + }, + }, + ], + }, + bindings: [ + { + type: "acp", + agentId: "codex", + match: { + channel: "feishu", + accountId: "default", + peer: { kind: "direct", id: "ou_1234567890" }, + }, + }, + { + type: "acp", + agentId: "codex", + match: { + channel: "feishu", + accountId: "default", + peer: { kind: "group", id: "oc_group_chat:topic:om_topic_root" }, + }, + acp: { label: "codex-feishu-topic" }, + }, + ], +} +``` + +#### 채팅에서 스레드 바인딩 ACP 생성 + +Feishu DM 또는 주제 대화에서 ACP 세션을 그 자리에서 생성하고 바인딩할 수 있습니다. + +```text +/acp spawn codex --thread here +``` + +참고: + +- `--thread here`는 DM과 Feishu 주제에서 작동합니다. +- 바인딩된 DM/주제의 후속 메시지는 해당 ACP 세션으로 직접 라우팅됩니다. +- v1은 일반 비주제 그룹 채팅을 대상으로 하지 않습니다. + +### 멀티 에이전트 라우팅 + +`bindings`를 사용해 Feishu DM 또는 그룹을 서로 다른 agent로 라우팅하세요. + +```json5 +{ + agents: { + list: [ + { id: "main" }, + { + id: "clawd-fan", + workspace: "/home/user/clawd-fan", + agentDir: "/home/user/.openclaw/agents/clawd-fan/agent", + }, + { + id: "clawd-xi", + workspace: "/home/user/clawd-xi", + agentDir: "/home/user/.openclaw/agents/clawd-xi/agent", + }, + ], + }, + bindings: [ + { + agentId: "main", + match: { + channel: "feishu", + peer: { kind: "direct", id: "ou_xxx" }, + }, + }, + { + agentId: "clawd-fan", + match: { + channel: "feishu", + peer: { kind: "direct", id: "ou_yyy" }, + }, + }, + { + agentId: "clawd-xi", + match: { + channel: "feishu", + peer: { kind: "group", id: "oc_zzz" }, + }, + }, + ], +} +``` + +라우팅 필드: + +- `match.channel`: `"feishu"` +- `match.peer.kind`: `"direct"` 또는 `"group"` +- `match.peer.id`: 사용자 Open ID(`ou_xxx`) 또는 그룹 ID(`oc_xxx`) + +조회 팁은 [그룹/사용자 ID 가져오기](#get-groupuser-ids)를 참조하세요. + +--- + +## 구성 참조 + +전체 구성: [Gateway configuration](/gateway/configuration) + +주요 옵션: + +| 설정 | 설명 | 기본값 | +| ------------------------------------------------- | --------------------------------------- | ---------------- | +| `channels.feishu.enabled` | 채널 활성화/비활성화 | `true` | +| `channels.feishu.domain` | API 도메인(`feishu` 또는 `lark`) | `feishu` | +| `channels.feishu.connectionMode` | 이벤트 전송 모드 | `websocket` | +| `channels.feishu.defaultAccount` | 아웃바운드 라우팅용 기본 계정 ID | `default` | +| `channels.feishu.verificationToken` | webhook 모드에 필요 | - | +| `channels.feishu.encryptKey` | webhook 모드에 필요 | - | +| `channels.feishu.webhookPath` | webhook 라우트 경로 | `/feishu/events` | +| `channels.feishu.webhookHost` | webhook 바인드 호스트 | `127.0.0.1` | +| `channels.feishu.webhookPort` | webhook 바인드 포트 | `3000` | +| `channels.feishu.accounts..appId` | App ID | - | +| `channels.feishu.accounts..appSecret` | App Secret | - | +| `channels.feishu.accounts..domain` | 계정별 API 도메인 재정의 | `feishu` | +| `channels.feishu.dmPolicy` | DM 정책 | `pairing` | +| `channels.feishu.allowFrom` | DM allowlist(open_id 목록) | - | +| `channels.feishu.groupPolicy` | 그룹 정책 | `allowlist` | +| `channels.feishu.groupAllowFrom` | 그룹 allowlist | - | +| `channels.feishu.requireMention` | 기본 @mention 필요 여부 | conditional | +| `channels.feishu.groups..requireMention` | 그룹별 @mention 필요 여부 재정의 | inherited | +| `channels.feishu.groups..enabled` | 그룹 활성화 | `true` | +| `channels.feishu.textChunkLimit` | 메시지 청크 크기 | `2000` | +| `channels.feishu.mediaMaxMb` | 미디어 크기 제한 | `30` | +| `channels.feishu.streaming` | 스트리밍 카드 출력 활성화 | `true` | +| `channels.feishu.blockStreaming` | 블록 스트리밍 활성화 | `true` | + +--- + +## dmPolicy 참조 + +| 값 | 동작 | +| ------------- | --------------------------------------------------------------- | +| `"pairing"` | **기본값.** 알 수 없는 사용자는 페어링 코드를 받으며, 승인이 필요함 | +| `"allowlist"` | `allowFrom`에 있는 사용자만 대화 가능 | +| `"open"` | 모든 사용자 허용(`allowFrom`에 `"*"` 필요) | +| `"disabled"` | DM 비활성화 | + +--- + +## 지원되는 메시지 유형 + +### 수신 + +- ✅ 텍스트 +- ✅ 리치 텍스트(post) +- ✅ 이미지 +- ✅ 파일 +- ✅ 오디오 +- ✅ 비디오/미디어 +- ✅ 스티커 + +### 전송 + +- ✅ 텍스트 +- ✅ 이미지 +- ✅ 파일 +- ✅ 오디오 +- ✅ 비디오/미디어 +- ✅ 인터랙티브 카드 +- ⚠️ 리치 텍스트(post 스타일 서식 및 카드, 임의의 Feishu 작성 기능 전체는 아님) + +### 스레드 및 응답 + +- ✅ 인라인 응답 +- ✅ Feishu가 `reply_in_thread`를 노출하는 경우 주제 스레드 응답 +- ✅ 스레드/주제 메시지에 응답할 때 미디어 응답도 스레드 인식을 유지함 + +## Drive 댓글 + +사용자가 Feishu Drive 문서(Docs, Sheets 등)에 댓글을 추가하면 Feishu가 agent를 트리거할 수 있습니다. agent는 댓글 텍스트, 문서 컨텍스트, 댓글 스레드를 받아 스레드 내에서 응답하거나 문서를 수정할 수 있습니다. + +요구 사항: + +- Feishu 앱 이벤트 구독 설정에서 `drive.notice.comment_add_v1`을 구독해야 합니다 + (기존 `im.message.receive_v1`과 함께) +- Drive 도구는 기본적으로 활성화되어 있으며, `channels.feishu.tools.drive: false`로 비활성화할 수 있습니다 + +`feishu_drive` 도구는 다음 댓글 작업을 제공합니다. + +| 작업 | 설명 | +| ---------------------- | ----------------------------------- | +| `list_comments` | 문서의 댓글 목록 표시 | +| `list_comment_replies` | 댓글 스레드의 응답 목록 표시 | +| `add_comment` | 새 최상위 댓글 추가 | +| `reply_comment` | 기존 댓글 스레드에 응답 | + +agent가 Drive 댓글 이벤트를 처리할 때 다음을 받습니다. + +- 댓글 텍스트와 발신자 +- 문서 메타데이터(제목, 유형, URL) +- 스레드 내 응답을 위한 댓글 스레드 컨텍스트 + +문서를 수정한 후 agent는 댓글 작성자에게 알리기 위해 `feishu_drive.reply_comment`를 사용하고, 중복 전송을 피하기 위해 정확히 `NO_REPLY` / `no_reply` 무음 토큰을 출력하도록 안내됩니다. + +## 런타임 작업 표면 + +현재 Feishu는 다음 런타임 작업을 노출합니다. + +- `send` +- `read` +- `edit` +- `thread-reply` +- `pin` +- `list-pins` +- `unpin` +- `member-info` +- `channel-info` +- `channel-list` +- config에서 반응이 활성화된 경우 `react` 및 `reactions` +- `feishu_drive` 댓글 작업: `list_comments`, `list_comment_replies`, `add_comment`, `reply_comment` + +## 관련 문서 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지의 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 하드닝 diff --git a/docs/ko/channels/googlechat.md b/docs/ko/channels/googlechat.md new file mode 100644 index 000000000..1b27da3af --- /dev/null +++ b/docs/ko/channels/googlechat.md @@ -0,0 +1,277 @@ +--- +read_when: + - Google Chat 채널 기능을 작업할 때 +summary: Google Chat 앱 지원 상태, 기능 및 구성 +title: Google Chat +x-i18n: + generated_at: "2026-04-05T12:34:59Z" + model: gpt-5.4 + provider: openai + source_hash: 570894ed798dd0b9ba42806b050927216379a1228fcd2f96de565bc8a4ac7c2c + source_path: channels/googlechat.md + workflow: 15 +--- + +# Google Chat (Chat API) + +상태: Google Chat API 웹훅을 통한 DM 및 스페이스 지원 준비 완료(HTTP 전용). + +## 빠른 설정(초보자용) + +1. Google Cloud 프로젝트를 만들고 **Google Chat API**를 사용 설정합니다. + - 이동: [Google Chat API Credentials](https://console.cloud.google.com/apis/api/chat.googleapis.com/credentials) + - 아직 활성화되지 않았다면 API를 활성화합니다. +2. **Service Account**를 생성합니다. + - **Create Credentials** > **Service Account**를 누릅니다. + - 원하는 이름을 지정합니다(예: `openclaw-chat`). + - 권한은 비워 둡니다(**Continue** 누름). + - 액세스 권한이 있는 주체도 비워 둡니다(**Done** 누름). +3. **JSON Key**를 생성하고 다운로드합니다. + - 서비스 계정 목록에서 방금 만든 계정을 클릭합니다. + - **Keys** 탭으로 이동합니다. + - **Add Key** > **Create new key**를 클릭합니다. + - **JSON**을 선택하고 **Create**를 누릅니다. +4. 다운로드한 JSON 파일을 게이트웨이 호스트에 저장합니다(예: `~/.openclaw/googlechat-service-account.json`). +5. [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat)에서 Google Chat 앱을 생성합니다. + - **Application info**를 입력합니다. + - **App name**: (예: `OpenClaw`) + - **Avatar URL**: (예: `https://openclaw.ai/logo.png`) + - **Description**: (예: `개인 AI 비서`) + - **Interactive features**를 활성화합니다. + - **Functionality**에서 **Join spaces and group conversations**를 선택합니다. + - **Connection settings**에서 **HTTP endpoint URL**을 선택합니다. + - **Triggers**에서 **Use a common HTTP endpoint URL for all triggers**를 선택하고, 이를 게이트웨이의 공개 URL 뒤에 `/googlechat`를 붙인 값으로 설정합니다. + - _팁: 게이트웨이의 공개 URL을 찾으려면 `openclaw status`를 실행하세요._ + - **Visibility**에서 **Make this Chat app available to specific people and groups in <Your Domain>**를 선택합니다. + - 텍스트 상자에 이메일 주소를 입력합니다(예: `user@example.com`). + - 하단의 **Save**를 클릭합니다. +6. **앱 상태를 활성화합니다**. + - 저장 후 **페이지를 새로고침**합니다. + - **App status** 섹션을 찾습니다(보통 저장 후 상단 또는 하단에 표시됨). + - 상태를 **Live - available to users**로 변경합니다. + - 다시 **Save**를 클릭합니다. +7. 서비스 계정 경로와 웹훅 audience로 OpenClaw를 구성합니다. + - Env: `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json` + - 또는 config: `channels.googlechat.serviceAccountFile: "/path/to/service-account.json"`. +8. 웹훅 audience 유형과 값을 설정합니다(Chat 앱 구성과 일치해야 함). +9. 게이트웨이를 시작합니다. 그러면 Google Chat이 웹훅 경로로 POST 요청을 보냅니다. + +## Google Chat에 추가하기 + +게이트웨이가 실행 중이고 이메일이 가시성 목록에 추가되어 있다면 다음을 수행합니다. + +1. [Google Chat](https://chat.google.com/)으로 이동합니다. +2. **Direct Messages** 옆의 **+**(플러스) 아이콘을 클릭합니다. +3. 검색창(평소 사람을 추가하는 위치)에 Google Cloud Console에서 구성한 **App name**을 입력합니다. + - **참고**: 이 봇은 비공개 앱이므로 "Marketplace" 탐색 목록에 _표시되지 않습니다_. 반드시 이름으로 검색해야 합니다. +4. 결과에서 봇을 선택합니다. +5. **Add** 또는 **Chat**을 클릭해 1:1 대화를 시작합니다. +6. 비서를 트리거하려면 "Hello"를 보내세요. + +## 공개 URL(웹훅 전용) + +Google Chat 웹훅은 공개 HTTPS 엔드포인트가 필요합니다. 보안을 위해 인터넷에는 **`/googlechat` 경로만 노출**하세요. OpenClaw 대시보드와 기타 민감한 엔드포인트는 사설 네트워크에 유지하세요. + +### 옵션 A: Tailscale Funnel(권장) + +비공개 대시보드에는 Tailscale Serve를 사용하고, 공개 웹훅 경로에는 Funnel을 사용합니다. 이렇게 하면 `/`는 비공개로 유지하면서 `/googlechat`만 노출할 수 있습니다. + +1. **게이트웨이가 어떤 주소에 바인딩되어 있는지 확인합니다.** + + ```bash + ss -tlnp | grep 18789 + ``` + + IP 주소를 확인합니다(예: `127.0.0.1`, `0.0.0.0`, 또는 `100.x.x.x` 같은 Tailscale IP). + +2. **대시보드를 tailnet 전용으로 노출합니다(포트 8443).** + + ```bash + # localhost(127.0.0.1 또는 0.0.0.0)에 바인딩된 경우: + tailscale serve --bg --https 8443 http://127.0.0.1:18789 + + # Tailscale IP 전용(예: 100.106.161.80)에 바인딩된 경우: + tailscale serve --bg --https 8443 http://100.106.161.80:18789 + ``` + +3. **웹훅 경로만 공개적으로 노출합니다.** + + ```bash + # localhost(127.0.0.1 또는 0.0.0.0)에 바인딩된 경우: + tailscale funnel --bg --set-path /googlechat http://127.0.0.1:18789/googlechat + + # Tailscale IP 전용(예: 100.106.161.80)에 바인딩된 경우: + tailscale funnel --bg --set-path /googlechat http://100.106.161.80:18789/googlechat + ``` + +4. **노드에 Funnel 액세스를 승인합니다.** + 프롬프트가 표시되면 출력에 나온 승인 URL을 방문해 tailnet 정책에서 이 노드에 대한 Funnel을 활성화합니다. + +5. **구성을 확인합니다.** + + ```bash + tailscale serve status + tailscale funnel status + ``` + +공개 웹훅 URL은 다음과 같습니다. +`https://..ts.net/googlechat` + +비공개 대시보드는 tailnet 전용으로 유지됩니다. +`https://..ts.net:8443/` + +Google Chat 앱 구성에는 공개 URL(`:8443` 제외)을 사용하세요. + +> 참고: 이 구성은 재부팅 후에도 유지됩니다. 나중에 제거하려면 `tailscale funnel reset` 및 `tailscale serve reset`을 실행하세요. + +### 옵션 B: 리버스 프록시(Caddy) + +Caddy 같은 리버스 프록시를 사용하는 경우 특정 경로만 프록시하세요. + +```caddy +your-domain.com { + reverse_proxy /googlechat* localhost:18789 +} +``` + +이 구성에서는 `your-domain.com/`에 대한 요청은 무시되거나 404를 반환하고, `your-domain.com/googlechat`만 안전하게 OpenClaw로 라우팅됩니다. + +### 옵션 C: Cloudflare Tunnel + +터널의 ingress 규칙을 구성해 웹훅 경로만 라우팅하세요. + +- **Path**: `/googlechat` -> `http://localhost:18789/googlechat` +- **Default Rule**: HTTP 404 (Not Found) + +## 작동 방식 + +1. Google Chat이 게이트웨이로 웹훅 POST를 보냅니다. 각 요청에는 `Authorization: Bearer ` 헤더가 포함됩니다. + - OpenClaw는 헤더가 있을 때 전체 웹훅 본문을 읽고 파싱하기 전에 bearer 인증을 검증합니다. + - 본문에 `authorizationEventObject.systemIdToken`이 포함된 Google Workspace Add-on 요청은 더 엄격한 사전 인증 본문 예산을 통해 지원됩니다. +2. OpenClaw는 구성된 `audienceType` 및 `audience`에 대해 토큰을 검증합니다. + - `audienceType: "app-url"` → audience는 HTTPS 웹훅 URL입니다. + - `audienceType: "project-number"` → audience는 Cloud 프로젝트 번호입니다. +3. 메시지는 스페이스 기준으로 라우팅됩니다. + - DM은 세션 키 `agent::googlechat:direct:`를 사용합니다. + - 스페이스는 세션 키 `agent::googlechat:group:`를 사용합니다. +4. DM 액세스는 기본적으로 pairing입니다. 알 수 없는 발신자에게는 pairing 코드가 전송되며, 다음으로 승인합니다. + - `openclaw pairing approve googlechat ` +5. 그룹 스페이스는 기본적으로 @멘션이 필요합니다. 멘션 감지에 앱의 사용자 이름이 필요하면 `botUser`를 사용하세요. + +## 대상 + +전송 및 허용 목록에는 다음 식별자를 사용하세요. + +- 다이렉트 메시지: `users/`(권장). +- 원시 이메일 `name@example.com`은 변경 가능하며 `channels.googlechat.dangerouslyAllowNameMatching: true`일 때만 다이렉트 허용 목록 일치에 사용됩니다. +- 사용 중단 예정: `users/`은 이메일 허용 목록이 아니라 사용자 ID로 처리됩니다. +- 스페이스: `spaces/`. + +## 주요 구성 + +```json5 +{ + channels: { + googlechat: { + enabled: true, + serviceAccountFile: "/path/to/service-account.json", + // 또는 serviceAccountRef: { source: "file", provider: "filemain", id: "/channels/googlechat/serviceAccount" } + audienceType: "app-url", + audience: "https://gateway.example.com/googlechat", + webhookPath: "/googlechat", + botUser: "users/1234567890", // 선택 사항; 멘션 감지에 도움 + dm: { + policy: "pairing", + allowFrom: ["users/1234567890"], + }, + groupPolicy: "allowlist", + groups: { + "spaces/AAAA": { + allow: true, + requireMention: true, + users: ["users/1234567890"], + systemPrompt: "짧게만 답변하세요.", + }, + }, + actions: { reactions: true }, + typingIndicator: "message", + mediaMaxMb: 20, + }, + }, +} +``` + +참고: + +- 서비스 계정 자격 증명은 `serviceAccount`(JSON 문자열)로 인라인 전달할 수도 있습니다. +- `serviceAccountRef`도 지원됩니다(env/file SecretRef). `channels.googlechat.accounts..serviceAccountRef` 아래 계정별 참조도 포함됩니다. +- `webhookPath`가 설정되지 않은 경우 기본 웹훅 경로는 `/googlechat`입니다. +- `dangerouslyAllowNameMatching`은 허용 목록에 대해 변경 가능한 이메일 주체 일치를 다시 활성화합니다(비상 호환 모드). +- `actions.reactions`가 활성화되어 있으면 반응은 `reactions` 도구 및 `channels action`을 통해 사용할 수 있습니다. +- 메시지 작업은 텍스트용 `send`와 명시적 첨부파일 전송용 `upload-file`을 노출합니다. `upload-file`은 `media` / `filePath` / `path`와 선택적 `message`, `filename`, 스레드 대상을 받습니다. +- `typingIndicator`는 `none`, `message`(기본값), `reaction`을 지원합니다(`reaction`은 사용자 OAuth 필요). +- 첨부파일은 Chat API를 통해 다운로드되어 미디어 파이프라인에 저장됩니다(크기는 `mediaMaxMb`로 제한). + +시크릿 참조 세부 정보: [Secrets Management](/gateway/secrets). + +## 문제 해결 + +### 405 Method Not Allowed + +Google Cloud Logs Explorer에 다음과 같은 오류가 표시되는 경우: + +``` +status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed +``` + +이는 웹훅 핸들러가 등록되지 않았음을 의미합니다. 일반적인 원인은 다음과 같습니다. + +1. **채널이 구성되지 않음**: config에 `channels.googlechat` 섹션이 없습니다. 다음으로 확인하세요. + + ```bash + openclaw config get channels.googlechat + ``` + + "Config path not found"가 반환되면 구성을 추가하세요([주요 구성](#config-highlights) 참조). + +2. **플러그인이 활성화되지 않음**: 플러그인 상태를 확인하세요. + + ```bash + openclaw plugins list | grep googlechat + ``` + + "disabled"로 표시되면 config에 `plugins.entries.googlechat.enabled: true`를 추가하세요. + +3. **게이트웨이를 재시작하지 않음**: config를 추가한 후 게이트웨이를 재시작하세요. + + ```bash + openclaw gateway restart + ``` + +채널이 실행 중인지 확인합니다. + +```bash +openclaw channels status +# 다음이 표시되어야 함: Google Chat default: enabled, configured, ... +``` + +### 기타 문제 + +- 인증 오류 또는 누락된 audience 구성을 확인하려면 `openclaw channels status --probe`를 확인하세요. +- 메시지가 도착하지 않으면 Chat 앱의 웹훅 URL과 이벤트 구독을 확인하세요. +- 멘션 게이팅 때문에 응답이 차단되면 `botUser`를 앱의 사용자 리소스 이름으로 설정하고 `requireMention`을 확인하세요. +- 요청이 게이트웨이에 도달하는지 확인하려면 테스트 메시지를 보내면서 `openclaw logs --follow`를 사용하세요. + +관련 문서: + +- [Gateway configuration](/gateway/configuration) +- [Security](/gateway/security) +- [Reactions](/tools/reactions) + +## 관련 + +- [Channels Overview](/channels) — 지원되는 모든 채널 +- [Pairing](/channels/pairing) — DM 인증 및 pairing 흐름 +- [Groups](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [Channel Routing](/channels/channel-routing) — 메시지의 세션 라우팅 +- [Security](/gateway/security) — 액세스 모델 및 보안 강화 diff --git a/docs/ko/channels/group-messages.md b/docs/ko/channels/group-messages.md new file mode 100644 index 000000000..78261e146 --- /dev/null +++ b/docs/ko/channels/group-messages.md @@ -0,0 +1,91 @@ +--- +read_when: + - 그룹 메시지 규칙 또는 멘션을 변경할 때 +summary: WhatsApp 그룹 메시지 처리의 동작 및 구성(`mentionPatterns`는 여러 표면에서 공유됨) +title: 그룹 메시지 +x-i18n: + generated_at: "2026-04-05T12:35:00Z" + model: gpt-5.4 + provider: openai + source_hash: 2543be5bc4c6f188f955df580a6fef585ecbfc1be36ade5d34b1a9157e021bc5 + source_path: channels/group-messages.md + workflow: 15 +--- + +# 그룹 메시지 (WhatsApp web 채널) + +목표: Clawd가 WhatsApp 그룹에 참여하고, 핑을 받았을 때만 활성화되며, 해당 스레드를 개인 DM 세션과 분리해서 유지하도록 합니다. + +참고: `agents.list[].groupChat.mentionPatterns`는 이제 Telegram/Discord/Slack/iMessage에서도 사용됩니다. 이 문서는 WhatsApp 전용 동작에 중점을 둡니다. 다중 에이전트 구성에서는 에이전트별로 `agents.list[].groupChat.mentionPatterns`를 설정하세요(또는 전역 폴백으로 `messages.groupChat.mentionPatterns`를 사용하세요). + +## 현재 구현 (2025-12-03) + +- 활성화 모드: `mention`(기본값) 또는 `always`. `mention`은 핑이 필요합니다(실제 WhatsApp @멘션 via `mentionedJids`, 안전한 정규식 패턴, 또는 텍스트 내 어디에나 있는 봇의 E.164). `always`는 모든 메시지에서 에이전트를 활성화하지만, 의미 있는 가치를 더할 수 있을 때만 응답해야 하며, 그렇지 않으면 정확히 무음 토큰 `NO_REPLY` / `no_reply`를 반환합니다. 기본값은 config의 `channels.whatsapp.groups`에서 설정할 수 있고, 그룹별로 `/activation`으로 재정의할 수 있습니다. `channels.whatsapp.groups`가 설정되면 그룹 허용 목록으로도 작동합니다(모두 허용하려면 `"*"` 포함). +- 그룹 정책: `channels.whatsapp.groupPolicy`는 그룹 메시지 수락 여부를 제어합니다(`open|disabled|allowlist`). `allowlist`는 `channels.whatsapp.groupAllowFrom`를 사용합니다(폴백: 명시적 `channels.whatsapp.allowFrom`). 기본값은 `allowlist`입니다(발신자를 추가할 때까지 차단됨). +- 그룹별 세션: 세션 키는 `agent::whatsapp:group:` 형식이므로 `/verbose on` 또는 `/think high` 같은 명령(독립 실행형 메시지로 전송)이 해당 그룹 범위에만 적용됩니다. 개인 DM 상태는 영향을 받지 않습니다. 그룹 스레드에서는 Heartbeat가 건너뛰어집니다. +- 컨텍스트 주입: 실행을 트리거하지 않은 **보류 중인 전용** 그룹 메시지(기본값 50개)는 `[Chat messages since your last reply - for context]` 아래에 접두사로 추가되고, 트리거한 줄은 `[Current message - respond to this]` 아래에 들어갑니다. 이미 세션에 있는 메시지는 다시 주입되지 않습니다. +- 발신자 표시: 이제 모든 그룹 배치의 끝에는 `[from: Sender Name (+E164)]`가 추가되어 Pi가 누가 말하는지 알 수 있습니다. +- 일시적/view-once: 텍스트/멘션을 추출하기 전에 이를 언래핑하므로, 그 안의 핑도 여전히 트리거됩니다. +- 그룹 시스템 프롬프트: 그룹 세션의 첫 턴(및 `/activation`이 모드를 변경할 때마다)에 시스템 프롬프트에 다음과 같은 짧은 설명을 주입합니다. `You are replying inside the WhatsApp group "". Group members: Alice (+44...), Bob (+43...), … Activation: trigger-only … Address the specific sender noted in the message context.` 메타데이터를 사용할 수 없는 경우에도 에이전트에 그룹 채팅이라는 점은 알려줍니다. + +## 구성 예시 (WhatsApp) + +WhatsApp가 텍스트 본문에서 시각적 `@`를 제거하더라도 표시 이름 핑이 작동하도록 `~/.openclaw/openclaw.json`에 `groupChat` 블록을 추가하세요. + +```json5 +{ + channels: { + whatsapp: { + groups: { + "*": { requireMention: true }, + }, + }, + }, + agents: { + list: [ + { + id: "main", + groupChat: { + historyLimit: 50, + mentionPatterns: ["@?openclaw", "\\+?15555550123"], + }, + }, + ], + }, +} +``` + +참고: + +- 정규식은 대소문자를 구분하지 않으며, 다른 config 정규식 표면과 동일한 safe-regex 가드레일을 사용합니다. 잘못된 패턴과 안전하지 않은 중첩 반복은 무시됩니다. +- 누군가 연락처를 탭하면 WhatsApp는 여전히 `mentionedJids`를 통해 정식 멘션을 보내므로, 번호 폴백은 거의 필요하지 않지만 유용한 안전장치입니다. + +### 활성화 명령(소유자 전용) + +그룹 채팅 명령 사용: + +- `/activation mention` +- `/activation always` + +이것은 소유자 번호(`channels.whatsapp.allowFrom`에서 가져오며, 설정되지 않은 경우 봇 자신의 E.164)만 변경할 수 있습니다. 현재 활성화 모드를 보려면 그룹에서 `/status`를 독립 실행형 메시지로 보내세요. + +## 사용 방법 + +1. WhatsApp 계정(OpenClaw를 실행 중인 계정)을 그룹에 추가합니다. +2. `@openclaw …`라고 말하거나(또는 번호를 포함하거나) 하세요. `groupPolicy: "open"`으로 설정하지 않는 한 허용 목록에 있는 발신자만 이를 트리거할 수 있습니다. +3. 에이전트 프롬프트에는 최근 그룹 컨텍스트와 끝의 `[from: …]` 마커가 포함되므로 올바른 사람에게 응답할 수 있습니다. +4. 세션 수준 지시문(`/verbose on`, `/think high`, `/new` 또는 `/reset`, `/compact`)은 해당 그룹의 세션에만 적용됩니다. 등록되도록 독립 실행형 메시지로 보내세요. 개인 DM 세션은 독립적으로 유지됩니다. + +## 테스트 / 검증 + +- 수동 스모크 테스트: + - 그룹에서 `@openclaw` 핑을 보내고 발신자 이름을 참조하는 응답을 확인합니다. + - 두 번째 핑을 보내고 기록 블록이 포함되었다가 다음 턴에서 지워지는지 확인합니다. +- gateway 로그(`--verbose`로 실행)를 확인하여 `from: ` 및 `[from: …]` 접미사를 표시하는 `inbound web message` 항목을 확인합니다. + +## 알려진 고려 사항 + +- 그룹에서 불필요하게 시끄러운 브로드캐스트를 방지하기 위해 Heartbeat는 의도적으로 건너뜁니다. +- 에코 억제는 결합된 배치 문자열을 사용합니다. 멘션 없이 동일한 텍스트를 두 번 보내면 첫 번째만 응답을 받습니다. +- 세션 저장소 항목은 세션 저장소(기본값 `~/.openclaw/agents//sessions/sessions.json`)에 `agent::whatsapp:group:`로 표시됩니다. 항목이 없다는 것은 해당 그룹이 아직 실행을 트리거하지 않았다는 의미일 뿐입니다. +- 그룹의 입력 중 표시기는 `agents.defaults.typingMode`를 따릅니다(기본값: 멘션되지 않은 경우 `message`). diff --git a/docs/ko/channels/groups.md b/docs/ko/channels/groups.md new file mode 100644 index 000000000..e85b47f11 --- /dev/null +++ b/docs/ko/channels/groups.md @@ -0,0 +1,417 @@ +--- +read_when: + - 그룹 채팅 동작이나 멘션 게이팅을 변경할 때 +summary: 표면 전반의 그룹 채팅 동작(Discord/iMessage/Matrix/Microsoft Teams/Signal/Slack/Telegram/WhatsApp/Zalo) +title: 그룹 +x-i18n: + generated_at: "2026-04-05T12:35:27Z" + model: gpt-5.4 + provider: openai + source_hash: 39d066e0542b468c6f8b384b463e2316590ea09a00ecb2065053e1e2ce55bd5f + source_path: channels/groups.md + workflow: 15 +--- + +# 그룹 + +OpenClaw는 Discord, iMessage, Matrix, Microsoft Teams, Signal, Slack, Telegram, WhatsApp, Zalo 전반에서 그룹 채팅을 일관되게 처리합니다. + +## 초보자용 소개(2분) + +OpenClaw는 사용자의 자체 메시징 계정에서 “동작”합니다. 별도의 WhatsApp 봇 사용자는 없습니다. +**당신**이 그룹에 있다면, OpenClaw는 그 그룹을 볼 수 있고 այնտեղ에서 응답할 수 있습니다. + +기본 동작: + +- 그룹은 제한됩니다(`groupPolicy: "allowlist"`). +- 멘션 게이팅을 명시적으로 비활성화하지 않는 한 응답에는 멘션이 필요합니다. + +즉, allowlist에 있는 발신자는 OpenClaw를 멘션하여 트리거할 수 있습니다. + +> 요약 +> +> - **DM 접근**은 `*.allowFrom`으로 제어됩니다. +> - **그룹 접근**은 `*.groupPolicy` + allowlist(`*.groups`, `*.groupAllowFrom`)로 제어됩니다. +> - **응답 트리거링**은 멘션 게이팅(`requireMention`, `/activation`)으로 제어됩니다. + +빠른 흐름(그룹 메시지에 어떤 일이 일어나는지): + +``` +groupPolicy? disabled -> drop +groupPolicy? allowlist -> group allowed? no -> drop +requireMention? yes -> mentioned? no -> store for context only +otherwise -> reply +``` + +## 컨텍스트 가시성과 allowlist + +그룹 안전성에는 두 가지 서로 다른 제어가 관련됩니다. + +- **트리거 권한 부여**: 누가 에이전트를 트리거할 수 있는지(`groupPolicy`, `groups`, `groupAllowFrom`, 채널별 allowlist) +- **컨텍스트 가시성**: 어떤 보조 컨텍스트가 모델에 주입되는지(답장 텍스트, 인용, 스레드 기록, 전달된 메타데이터) + +기본적으로 OpenClaw는 일반적인 채팅 동작을 우선하며 컨텍스트를 대부분 수신된 그대로 유지합니다. 이는 allowlist가 주로 누가 작업을 트리거할 수 있는지를 결정하며, 인용되거나 기록에 남은 모든 조각에 대한 보편적인 리다크션 경계는 아니라는 뜻입니다. + +현재 동작은 채널별로 다릅니다. + +- 일부 채널은 특정 경로에서 이미 보조 컨텍스트에 발신자 기반 필터링을 적용합니다(예: Slack 스레드 시딩, Matrix 답장/스레드 조회). +- 다른 채널은 여전히 인용/답장/전달 컨텍스트를 수신된 그대로 전달합니다. + +하드닝 방향(계획됨): + +- `contextVisibility: "all"`(기본값)은 현재의 수신 그대로 동작을 유지합니다. +- `contextVisibility: "allowlist"`는 보조 컨텍스트를 allowlist 발신자로 필터링합니다. +- `contextVisibility: "allowlist_quote"`는 `allowlist`에 하나의 명시적인 인용/답장 예외를 추가한 것입니다. + +이 하드닝 모델이 채널 전반에 걸쳐 일관되게 구현되기 전까지는 표면별 차이가 있을 수 있습니다. + +![그룹 메시지 흐름](/images/groups-flow.svg) + +다음을 원한다면... + +| 목표 | 설정할 값 | +| -------------------------------------------- | ---------------------------------------------------------- | +| 모든 그룹을 허용하되 @멘션에서만 응답 | `groups: { "*": { requireMention: true } }` | +| 모든 그룹 응답 비활성화 | `groupPolicy: "disabled"` | +| 특정 그룹만 허용 | `groups: { "": { ... } }` (`"*"` 키 없음) | +| 그룹에서는 오직 본인만 트리거 가능 | `groupPolicy: "allowlist"`, `groupAllowFrom: ["+1555..."]` | + +## 세션 키 + +- 그룹 세션은 `agent:::group:` 세션 키를 사용합니다(룸/채널은 `agent:::channel:` 사용). +- Telegram 포럼 토픽은 각 토픽이 자체 세션을 갖도록 그룹 ID에 `:topic:`를 추가합니다. +- 직접 채팅은 메인 세션을 사용합니다(또는 구성된 경우 발신자별 세션). +- Heartbeat는 그룹 세션에서 건너뜁니다. + + + +## 패턴: 개인 DM + 공개 그룹(단일 에이전트) + +예, “개인” 트래픽이 **DM**이고 “공개” 트래픽이 **그룹**이라면 이것은 잘 동작합니다. + +이유: 단일 에이전트 모드에서 DM은 일반적으로 **메인** 세션 키(`agent:main:main`)에 도착하는 반면, 그룹은 항상 **비메인** 세션 키(`agent:main::group:`)를 사용합니다. `mode: "non-main"`으로 샌드박싱을 활성화하면, 해당 그룹 세션은 Docker에서 실행되고 메인 DM 세션은 호스트에 남습니다. + +이렇게 하면 하나의 에이전트 “브레인”(공유 워크스페이스 + 메모리)을 가지면서도 두 가지 실행 자세를 가질 수 있습니다. + +- **DM**: 전체 도구(호스트) +- **그룹**: 샌드박스 + 제한된 도구(Docker) + +> 정말로 분리된 워크스페이스/페르소나(“개인”과 “공개”가 절대 섞이면 안 됨)가 필요하다면 두 번째 에이전트 + 바인딩을 사용하세요. [Multi-Agent Routing](/concepts/multi-agent)을 참조하세요. + +예시(DM은 호스트, 그룹은 샌드박스 + 메시징 전용 도구): + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "non-main", // groups/channels are non-main -> sandboxed + scope: "session", // strongest isolation (one container per group/channel) + workspaceAccess: "none", + }, + }, + }, + tools: { + sandbox: { + tools: { + // If allow is non-empty, everything else is blocked (deny still wins). + allow: ["group:messaging", "group:sessions"], + deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"], + }, + }, + }, +} +``` + +“호스트 접근 없음” 대신 “그룹은 폴더 X만 볼 수 있음”을 원하나요? `workspaceAccess: "none"`을 유지하고 allowlist된 경로만 샌드박스에 마운트하세요. + +```json5 +{ + agents: { + defaults: { + sandbox: { + mode: "non-main", + scope: "session", + workspaceAccess: "none", + docker: { + binds: [ + // hostPath:containerPath:mode + "/home/user/FriendsShared:/data:ro", + ], + }, + }, + }, + }, +} +``` + +관련 항목: + +- 구성 키 및 기본값: [Gateway configuration](/gateway/configuration-reference#agentsdefaultssandbox) +- 도구가 차단되는 이유 디버깅: [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) +- 바인드 마운트 세부 정보: [Sandboxing](/gateway/sandboxing#custom-bind-mounts) + +## 표시 라벨 + +- UI 라벨은 사용 가능할 때 `displayName`을 사용하며 `:` 형식으로 표시됩니다. +- `#room`은 룸/채널용으로 예약되어 있으며, 그룹 채팅은 `g-`를 사용합니다(소문자, 공백은 `-`로 변환, `#@+._-`는 유지). + +## 그룹 정책 + +채널별로 그룹/룸 메시지를 어떻게 처리할지 제어합니다. + +```json5 +{ + channels: { + whatsapp: { + groupPolicy: "disabled", // "open" | "disabled" | "allowlist" + groupAllowFrom: ["+15551234567"], + }, + telegram: { + groupPolicy: "disabled", + groupAllowFrom: ["123456789"], // numeric Telegram user id (wizard can resolve @username) + }, + signal: { + groupPolicy: "disabled", + groupAllowFrom: ["+15551234567"], + }, + imessage: { + groupPolicy: "disabled", + groupAllowFrom: ["chat_id:123"], + }, + msteams: { + groupPolicy: "disabled", + groupAllowFrom: ["user@org.com"], + }, + discord: { + groupPolicy: "allowlist", + guilds: { + GUILD_ID: { channels: { help: { allow: true } } }, + }, + }, + slack: { + groupPolicy: "allowlist", + channels: { "#general": { allow: true } }, + }, + matrix: { + groupPolicy: "allowlist", + groupAllowFrom: ["@owner:example.org"], + groups: { + "!roomId:example.org": { allow: true }, + "#alias:example.org": { allow: true }, + }, + }, + }, +} +``` + +| 정책 | 동작 | +| ------------- | ------------------------------------------------------------ | +| `"open"` | 그룹이 allowlist를 우회하지만 멘션 게이팅은 계속 적용됩니다. | +| `"disabled"` | 모든 그룹 메시지를 완전히 차단합니다. | +| `"allowlist"` | 구성된 allowlist와 일치하는 그룹/룸만 허용합니다. | + +참고: + +- `groupPolicy`는 멘션 게이팅(@멘션 필요)과 별개입니다. +- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams/Zalo: `groupAllowFrom`을 사용합니다(대체: 명시적 `allowFrom`). +- DM 페어링 승인(`*-allowFrom` 저장소 항목)은 DM 접근에만 적용되며, 그룹 발신자 권한 부여는 그룹 allowlist에 대해 명시적으로 유지됩니다. +- Discord: allowlist는 `channels.discord.guilds..channels`를 사용합니다. +- Slack: allowlist는 `channels.slack.channels`를 사용합니다. +- Matrix: allowlist는 `channels.matrix.groups`를 사용합니다. 룸 ID 또는 별칭을 권장합니다. 참가한 룸 이름 조회는 best-effort이며, 해석되지 않은 이름은 런타임에서 무시됩니다. 발신자를 제한하려면 `channels.matrix.groupAllowFrom`을 사용하세요. 룸별 `users` allowlist도 지원됩니다. +- 그룹 DM은 별도로 제어됩니다(`channels.discord.dm.*`, `channels.slack.dm.*`). +- Telegram allowlist는 사용자 ID(`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) 또는 사용자 이름(`"@alice"` 또는 `"alice"`)과 일치할 수 있습니다. 접두사는 대소문자를 구분하지 않습니다. +- 기본값은 `groupPolicy: "allowlist"`이며, 그룹 allowlist가 비어 있으면 그룹 메시지는 차단됩니다. +- 런타임 안전성: 제공자 블록이 완전히 누락된 경우(`channels.` 없음), 그룹 정책은 `channels.defaults.groupPolicy`를 상속하는 대신 실패 시 닫힘 모드(일반적으로 `allowlist`)로 대체됩니다. + +빠른 멘탈 모델(그룹 메시지 평가 순서): + +1. `groupPolicy` (open/disabled/allowlist) +2. 그룹 allowlist(`*.groups`, `*.groupAllowFrom`, 채널별 allowlist) +3. 멘션 게이팅(`requireMention`, `/activation`) + +## 멘션 게이팅(기본값) + +그룹 메시지는 그룹별로 재정의하지 않는 한 멘션이 필요합니다. 기본값은 각 서브시스템 아래의 `*.groups."*"`에 있습니다. + +봇 메시지에 답장하는 것도 암묵적 멘션으로 계산됩니다(채널이 답장 메타데이터를 지원하는 경우). 이는 Telegram, WhatsApp, Slack, Discord, Microsoft Teams에 적용됩니다. + +```json5 +{ + channels: { + whatsapp: { + groups: { + "*": { requireMention: true }, + "123@g.us": { requireMention: false }, + }, + }, + telegram: { + groups: { + "*": { requireMention: true }, + "123456789": { requireMention: false }, + }, + }, + imessage: { + groups: { + "*": { requireMention: true }, + "123": { requireMention: false }, + }, + }, + }, + agents: { + list: [ + { + id: "main", + groupChat: { + mentionPatterns: ["@openclaw", "openclaw", "\\+15555550123"], + historyLimit: 50, + }, + }, + ], + }, +} +``` + +참고: + +- `mentionPatterns`는 대소문자를 구분하지 않는 안전한 정규식 패턴입니다. 잘못된 패턴과 안전하지 않은 중첩 반복 형식은 무시됩니다. +- 명시적 멘션을 제공하는 표면은 계속 통과하며, 패턴은 대체 수단입니다. +- 에이전트별 재정의: `agents.list[].groupChat.mentionPatterns`(여러 에이전트가 하나의 그룹을 공유할 때 유용). +- 멘션 게이팅은 멘션 감지가 가능한 경우에만 적용됩니다(네이티브 멘션 또는 `mentionPatterns`가 구성된 경우). +- Discord 기본값은 `channels.discord.guilds."*"`에 있습니다(guild/channel별 재정의 가능). +- 그룹 기록 컨텍스트는 채널 전반에서 일관되게 래핑되며 **pending-only**입니다(멘션 게이팅으로 건너뛴 메시지). 전역 기본값에는 `messages.groupChat.historyLimit`을, 재정의에는 `channels..historyLimit`(또는 `channels..accounts.*.historyLimit`)을 사용하세요. 비활성화하려면 `0`으로 설정하세요. + +## 그룹/채널 도구 제한(선택 사항) + +일부 채널 구성은 **특정 그룹/룸/채널 내부에서** 사용 가능한 도구를 제한하는 기능을 지원합니다. + +- `tools`: 전체 그룹에 대해 도구 허용/거부 +- `toolsBySender`: 그룹 내 발신자별 재정의 + 명시적 키 접두사를 사용합니다: + `id:`, `e164:`, `username:`, `name:`, 그리고 `"*"` 와일드카드. + 레거시 접두사 없는 키도 여전히 허용되며 `id:` 전용으로 일치합니다. + +해결 순서(가장 구체적인 항목이 우선): + +1. 그룹/채널 `toolsBySender` 일치 +2. 그룹/채널 `tools` +3. 기본값(`"*"`) `toolsBySender` 일치 +4. 기본값(`"*"`) `tools` + +예시(Telegram): + +```json5 +{ + channels: { + telegram: { + groups: { + "*": { tools: { deny: ["exec"] } }, + "-1001234567890": { + tools: { deny: ["exec", "read", "write"] }, + toolsBySender: { + "id:123456789": { alsoAllow: ["exec"] }, + }, + }, + }, + }, + }, +} +``` + +참고: + +- 그룹/채널 도구 제한은 전역/에이전트 도구 정책에 추가로 적용됩니다(deny가 여전히 우선). +- 일부 채널은 룸/채널에 다른 중첩 구조를 사용합니다(예: Discord `guilds.*.channels.*`, Slack `channels.*`, Microsoft Teams `teams.*.channels.*`). + +## 그룹 allowlist + +`channels.whatsapp.groups`, `channels.telegram.groups`, 또는 `channels.imessage.groups`가 구성되면 해당 키가 그룹 allowlist 역할을 합니다. 모든 그룹을 허용하면서 기본 멘션 동작을 계속 설정하려면 `"*"`를 사용하세요. + +자주 발생하는 혼동: DM 페어링 승인은 그룹 권한 부여와 동일하지 않습니다. +DM 페어링을 지원하는 채널에서 페어링 저장소는 DM만 잠금 해제합니다. 그룹 명령은 여전히 `groupAllowFrom` 또는 해당 채널에 문서화된 구성 대체 항목 같은 구성 allowlist를 통한 명시적인 그룹 발신자 권한 부여가 필요합니다. + +일반적인 의도(복사/붙여넣기): + +1. 모든 그룹 응답 비활성화 + +```json5 +{ + channels: { whatsapp: { groupPolicy: "disabled" } }, +} +``` + +2. 특정 그룹만 허용(WhatsApp) + +```json5 +{ + channels: { + whatsapp: { + groups: { + "123@g.us": { requireMention: true }, + "456@g.us": { requireMention: false }, + }, + }, + }, +} +``` + +3. 모든 그룹을 허용하되 멘션 요구(명시적) + +```json5 +{ + channels: { + whatsapp: { + groups: { "*": { requireMention: true } }, + }, + }, +} +``` + +4. 그룹에서는 소유자만 트리거 가능(WhatsApp) + +```json5 +{ + channels: { + whatsapp: { + groupPolicy: "allowlist", + groupAllowFrom: ["+15551234567"], + groups: { "*": { requireMention: true } }, + }, + }, +} +``` + +## Activation(소유자 전용) + +그룹 소유자는 그룹별 activation을 전환할 수 있습니다. + +- `/activation mention` +- `/activation always` + +소유자는 `channels.whatsapp.allowFrom`으로 결정됩니다(설정되지 않은 경우 봇 자신의 E.164). 명령은 독립된 메시지로 보내세요. 현재 다른 표면은 `/activation`을 무시합니다. + +## 컨텍스트 필드 + +그룹 인바운드 페이로드는 다음을 설정합니다. + +- `ChatType=group` +- `GroupSubject`(알려진 경우) +- `GroupMembers`(알려진 경우) +- `WasMentioned`(멘션 게이팅 결과) +- Telegram 포럼 토픽에는 `MessageThreadId`와 `IsForum`도 포함됩니다. + +채널별 참고 사항: + +- BlueBubbles는 `GroupMembers`를 채우기 전에 로컬 Contacts 데이터베이스에서 이름 없는 macOS 그룹 참가자 정보를 선택적으로 보강할 수 있습니다. 이는 기본적으로 꺼져 있으며 일반적인 그룹 게이팅을 통과한 뒤에만 실행됩니다. + +에이전트 시스템 프롬프트는 새 그룹 세션의 첫 턴에 그룹 소개를 포함합니다. 여기서는 인간처럼 응답하고, Markdown 표를 피하고, 리터럴 `\n` 시퀀스를 입력하지 말 것을 모델에 상기시킵니다. + +## iMessage 세부 사항 + +- 라우팅 또는 allowlist에는 `chat_id:` 사용을 권장합니다. +- 채팅 나열: `imsg chats --limit 20` +- 그룹 응답은 항상 동일한 `chat_id`로 다시 전송됩니다. + +## WhatsApp 세부 사항 + +WhatsApp 전용 동작(기록 주입, 멘션 처리 세부 정보)은 [Group messages](/channels/group-messages)를 참조하세요. diff --git a/docs/ko/channels/imessage.md b/docs/ko/channels/imessage.md new file mode 100644 index 000000000..b67b44b85 --- /dev/null +++ b/docs/ko/channels/imessage.md @@ -0,0 +1,434 @@ +--- +read_when: + - iMessage 지원을 설정할 때 + - iMessage 송수신을 디버깅할 때 +summary: imsg를 통한 레거시 iMessage 지원(stdio를 통한 JSON-RPC). 새로운 설정에서는 BlueBubbles를 사용해야 합니다. +title: iMessage +x-i18n: + generated_at: "2026-04-05T12:35:25Z" + model: gpt-5.4 + provider: openai + source_hash: 086d85bead49f75d12ae6b14ac917af52375b6afd28f6af1a0dcbbc7fcb628a0 + source_path: channels/imessage.md + workflow: 15 +--- + +# iMessage (레거시: imsg) + + +새로운 iMessage 배포에는 BlueBubbles를 사용하세요. + +`imsg` 통합은 레거시이며 향후 릴리스에서 제거될 수 있습니다. + + +상태: 레거시 외부 CLI 통합. 게이트웨이는 `imsg rpc`를 실행하고 stdio의 JSON-RPC를 통해 통신합니다(별도의 데몬/포트 없음). + + + + 새로운 설정을 위한 선호되는 iMessage 경로입니다. + + + iMessage DM은 기본적으로 페어링 모드를 사용합니다. + + + 전체 iMessage 필드 참조입니다. + + + +## 빠른 설정 + + + + + + +```bash +brew install steipete/tap/imsg +imsg rpc --help +``` + + + + + +```json5 +{ + channels: { + imessage: { + enabled: true, + cliPath: "/usr/local/bin/imsg", + dbPath: "/Users//Library/Messages/chat.db", + }, + }, +} +``` + + + + + +```bash +openclaw gateway +``` + + + + + +```bash +openclaw pairing list imessage +openclaw pairing approve imessage +``` + + 페어링 요청은 1시간 후 만료됩니다. + + + + + + + OpenClaw에는 stdio 호환 `cliPath`만 필요하므로, `cliPath`가 원격 Mac으로 SSH 접속하여 `imsg`를 실행하는 래퍼 스크립트를 가리키도록 설정할 수 있습니다. + +```bash +#!/usr/bin/env bash +exec ssh -T gateway-host imsg "$@" +``` + + 첨부 파일이 활성화된 경우의 권장 구성: + +```json5 +{ + channels: { + imessage: { + enabled: true, + cliPath: "~/.openclaw/scripts/imsg-ssh", + remoteHost: "user@gateway-host", // SCP 첨부 파일 가져오기에 사용됨 + includeAttachments: true, + // 선택 사항: 허용된 첨부 파일 루트를 재정의합니다. + // 기본값에는 /Users/*/Library/Messages/Attachments가 포함됩니다. + attachmentRoots: ["/Users/*/Library/Messages/Attachments"], + remoteAttachmentRoots: ["/Users/*/Library/Messages/Attachments"], + }, + }, +} +``` + + `remoteHost`가 설정되지 않으면 OpenClaw는 SSH 래퍼 스크립트를 파싱하여 이를 자동 감지하려고 시도합니다. + `remoteHost`는 `host` 또는 `user@host`여야 합니다(공백이나 SSH 옵션 없음). + OpenClaw는 SCP에 엄격한 호스트 키 검사를 사용하므로 릴레이 호스트 키가 이미 `~/.ssh/known_hosts`에 있어야 합니다. + 첨부 파일 경로는 허용된 루트(`attachmentRoots` / `remoteAttachmentRoots`)를 기준으로 검증됩니다. + + + + +## 요구 사항 및 권한(macOS) + +- `imsg`를 실행하는 Mac에서 Messages에 로그인되어 있어야 합니다. +- OpenClaw/`imsg`를 실행하는 프로세스 컨텍스트에는 전체 디스크 접근 권한이 필요합니다(Messages DB 접근). +- Messages.app을 통해 메시지를 보내려면 자동화 권한이 필요합니다. + + +권한은 프로세스 컨텍스트별로 부여됩니다. 게이트웨이가 헤드리스(LaunchAgent/SSH)로 실행되는 경우, 프롬프트를 트리거하기 위해 같은 컨텍스트에서 한 번 상호작용 명령을 실행하세요: + +```bash +imsg chats --limit 1 +# 또는 +imsg send "test" +``` + + + +## 액세스 제어 및 라우팅 + + + + `channels.imessage.dmPolicy`는 다이렉트 메시지를 제어합니다: + + - `pairing` (기본값) + - `allowlist` + - `open` (`allowFrom`에 `"*"`가 포함되어야 함) + - `disabled` + + 허용 목록 필드: `channels.imessage.allowFrom`. + + 허용 목록 항목은 핸들이나 채팅 대상(`chat_id:*`, `chat_guid:*`, `chat_identifier:*`)일 수 있습니다. + + + + + `channels.imessage.groupPolicy`는 그룹 처리를 제어합니다: + + - `allowlist` (구성된 경우 기본값) + - `open` + - `disabled` + + 그룹 발신자 허용 목록: `channels.imessage.groupAllowFrom`. + + 런타임 대체 동작: `groupAllowFrom`이 설정되지 않은 경우, iMessage 그룹 발신자 검사는 사용 가능할 때 `allowFrom`으로 대체됩니다. + 런타임 참고: `channels.imessage`가 완전히 누락되면 런타임은 `groupPolicy="allowlist"`로 대체하고 경고를 기록합니다(`channels.defaults.groupPolicy`가 설정되어 있어도 마찬가지). + + 그룹용 멘션 게이팅: + + - iMessage에는 기본 멘션 메타데이터가 없습니다 + - 멘션 감지는 정규식 패턴(`agents.list[].groupChat.mentionPatterns`, 대체값 `messages.groupChat.mentionPatterns`)을 사용합니다 + - 구성된 패턴이 없으면 멘션 게이팅을 강제할 수 없습니다 + + 권한 있는 발신자의 제어 명령은 그룹에서 멘션 게이팅을 우회할 수 있습니다. + + + + + - DM은 direct routing을 사용하고 그룹은 group routing을 사용합니다. + - 기본 `session.dmScope=main`에서는 iMessage DM이 에이전트 메인 세션으로 합쳐집니다. + - 그룹 세션은 격리됩니다(`agent::imessage:group:`). + - 응답은 원래 채널/대상 메타데이터를 사용해 iMessage로 다시 라우팅됩니다. + + 그룹 유사 스레드 동작: + + 일부 다중 참여자 iMessage 스레드는 `is_group=false`로 도착할 수 있습니다. + 해당 `chat_id`가 `channels.imessage.groups` 아래에 명시적으로 구성되어 있으면, OpenClaw는 이를 그룹 트래픽으로 취급합니다(그룹 게이팅 + 그룹 세션 격리). + + + + +## ACP 대화 바인딩 + +레거시 iMessage 채팅은 ACP 세션에 바인딩할 수도 있습니다. + +빠른 운영자 흐름: + +- DM 또는 허용된 그룹 채팅 안에서 `/acp spawn codex --bind here`를 실행합니다. +- 이후 같은 iMessage 대화의 메시지는 생성된 ACP 세션으로 라우팅됩니다. +- `/new`와 `/reset`은 같은 바인딩된 ACP 세션을 제자리에서 재설정합니다. +- `/acp close`는 ACP 세션을 닫고 바인딩을 제거합니다. + +구성된 영구 바인딩은 `type: "acp"` 및 `match.channel: "imessage"`가 있는 최상위 `bindings[]` 항목을 통해 지원됩니다. + +`match.peer.id`에는 다음을 사용할 수 있습니다: + +- `+15555550123` 또는 `user@example.com` 같은 정규화된 DM 핸들 +- `chat_id:` (안정적인 그룹 바인딩에 권장) +- `chat_guid:` +- `chat_identifier:` + +예: + +```json5 +{ + agents: { + list: [ + { + id: "codex", + runtime: { + type: "acp", + acp: { agent: "codex", backend: "acpx", mode: "persistent" }, + }, + }, + ], + }, + bindings: [ + { + type: "acp", + agentId: "codex", + match: { + channel: "imessage", + accountId: "default", + peer: { kind: "group", id: "chat_id:123" }, + }, + acp: { label: "codex-group" }, + }, + ], +} +``` + +공유 ACP 바인딩 동작은 [ACP 에이전트](/tools/acp-agents)를 참조하세요. + +## 배포 패턴 + + + + 봇 트래픽이 개인 Messages 프로필과 분리되도록 전용 Apple ID와 macOS 사용자를 사용하세요. + + 일반적인 흐름: + + 1. 전용 macOS 사용자를 만들고 로그인합니다. + 2. 해당 사용자에서 봇 Apple ID로 Messages에 로그인합니다. + 3. 해당 사용자에 `imsg`를 설치합니다. + 4. OpenClaw가 해당 사용자 컨텍스트에서 `imsg`를 실행할 수 있도록 SSH 래퍼를 만듭니다. + 5. `channels.imessage.accounts..cliPath`와 `.dbPath`를 해당 사용자 프로필로 지정합니다. + + 첫 실행 시 해당 봇 사용자 세션에서 GUI 승인(자동화 + 전체 디스크 접근 권한)이 필요할 수 있습니다. + + + + + 일반적인 토폴로지: + + - 게이트웨이는 Linux/VM에서 실행됨 + - iMessage + `imsg`는 tailnet의 Mac에서 실행됨 + - `cliPath` 래퍼는 SSH를 사용해 `imsg`를 실행함 + - `remoteHost`는 SCP 첨부 파일 가져오기를 활성화함 + + 예시: + +```json5 +{ + channels: { + imessage: { + enabled: true, + cliPath: "~/.openclaw/scripts/imsg-ssh", + remoteHost: "bot@mac-mini.tailnet-1234.ts.net", + includeAttachments: true, + dbPath: "/Users/bot/Library/Messages/chat.db", + }, + }, +} +``` + +```bash +#!/usr/bin/env bash +exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@" +``` + + SSH와 SCP가 모두 비대화형이 되도록 SSH 키를 사용하세요. + 먼저 호스트 키가 신뢰되는지 확인하세요(예: `ssh bot@mac-mini.tailnet-1234.ts.net`). 그러면 `known_hosts`가 채워집니다. + + + + + iMessage는 `channels.imessage.accounts` 아래에서 계정별 구성을 지원합니다. + + 각 계정은 `cliPath`, `dbPath`, `allowFrom`, `groupPolicy`, `mediaMaxMb`, 히스토리 설정, 첨부 파일 루트 허용 목록 등의 필드를 재정의할 수 있습니다. + + + + +## 미디어, 청킹 및 전달 대상 + + + + - 수신 첨부 파일 수집은 선택 사항입니다: `channels.imessage.includeAttachments` + - `remoteHost`가 설정되면 원격 첨부 파일 경로를 SCP로 가져올 수 있습니다 + - 첨부 파일 경로는 허용된 루트와 일치해야 합니다: + - `channels.imessage.attachmentRoots` (로컬) + - `channels.imessage.remoteAttachmentRoots` (원격 SCP 모드) + - 기본 루트 패턴: `/Users/*/Library/Messages/Attachments` + - SCP는 엄격한 호스트 키 검사(`StrictHostKeyChecking=yes`)를 사용합니다 + - 발신 미디어 크기는 `channels.imessage.mediaMaxMb`를 사용합니다(기본값 16 MB) + + + + - 텍스트 청크 제한: `channels.imessage.textChunkLimit` (기본값 4000) + - 청크 모드: `channels.imessage.chunkMode` + - `length` (기본값) + - `newline` (문단 우선 분할) + + + + 선호되는 명시적 대상: + + - `chat_id:123` (안정적인 라우팅에 권장) + - `chat_guid:...` + - `chat_identifier:...` + + 핸들 대상도 지원됩니다: + + - `imessage:+1555...` + - `sms:+1555...` + - `user@example.com` + +```bash +imsg chats --limit 20 +``` + + + + +## 구성 쓰기 + +iMessage는 기본적으로 채널 시작 구성 쓰기를 허용합니다(`commands.config: true`일 때 `/config set|unset`용). + +비활성화: + +```json5 +{ + channels: { + imessage: { + configWrites: false, + }, + }, +} +``` + +## 문제 해결 + + + + 바이너리와 RPC 지원을 확인하세요: + +```bash +imsg rpc --help +openclaw channels status --probe +``` + + 프로브에서 RPC 미지원으로 보고되면 `imsg`를 업데이트하세요. + + + + + 다음을 확인하세요: + + - `channels.imessage.dmPolicy` + - `channels.imessage.allowFrom` + - 페어링 승인(`openclaw pairing list imessage`) + + + + + 다음을 확인하세요: + + - `channels.imessage.groupPolicy` + - `channels.imessage.groupAllowFrom` + - `channels.imessage.groups` 허용 목록 동작 + - 멘션 패턴 구성(`agents.list[].groupChat.mentionPatterns`) + + + + + 다음을 확인하세요: + + - `channels.imessage.remoteHost` + - `channels.imessage.remoteAttachmentRoots` + - 게이트웨이 호스트에서의 SSH/SCP 키 인증 + - 게이트웨이 호스트의 `~/.ssh/known_hosts`에 호스트 키가 존재하는지 + - Messages를 실행하는 Mac에서 원격 경로를 읽을 수 있는지 + + + + + 같은 사용자/세션 컨텍스트의 상호작용 GUI 터미널에서 다시 실행하고 프롬프트를 승인하세요: + +```bash +imsg chats --limit 1 +imsg send "test" +``` + + OpenClaw/`imsg`를 실행하는 프로세스 컨텍스트에 전체 디스크 접근 권한과 자동화 권한이 부여되었는지 확인하세요. + + + + +## 구성 참조 포인터 + +- [구성 참조 - iMessage](/gateway/configuration-reference#imessage) +- [게이트웨이 구성](/gateway/configuration) +- [페어링](/channels/pairing) +- [BlueBubbles](/channels/bluebubbles) + +## 관련 항목 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/index.md b/docs/ko/channels/index.md new file mode 100644 index 000000000..e8b65cc67 --- /dev/null +++ b/docs/ko/channels/index.md @@ -0,0 +1,56 @@ +--- +read_when: + - OpenClaw용 채팅 채널을 선택하려는 경우 + - 지원되는 메시징 플랫폼의 빠른 개요가 필요한 경우 +summary: OpenClaw가 연결할 수 있는 메시징 플랫폼 +title: 채팅 채널 +x-i18n: + generated_at: "2026-04-05T12:35:11Z" + model: gpt-5.4 + provider: openai + source_hash: 246ee6f16aebe751241f00102bb435978ed21f6158385aff5d8e222e30567416 + source_path: channels/index.md + workflow: 15 +--- + +# 채팅 채널 + +OpenClaw는 이미 사용 중인 어떤 채팅 앱에서든 사용자와 대화할 수 있습니다. 각 채널은 Gateway를 통해 연결됩니다. +텍스트는 모든 곳에서 지원되며, 미디어와 반응 지원은 채널마다 다릅니다. + +## 지원되는 채널 + +- [BlueBubbles](/channels/bluebubbles) — **iMessage에 권장**; 전체 기능 지원이 포함된 BlueBubbles macOS 서버 REST API를 사용합니다(번들 plugin; 수정, 전송 취소, 효과, 반응, 그룹 관리 — 수정은 현재 macOS 26 Tahoe에서 작동하지 않음). +- [Discord](/channels/discord) — Discord Bot API + Gateway; 서버, 채널, DM을 지원합니다. +- [Feishu](/channels/feishu) — WebSocket을 통한 Feishu/Lark 봇(번들 plugin). +- [Google Chat](/channels/googlechat) — HTTP 웹훅을 통한 Google Chat API 앱. +- [iMessage (legacy)](/channels/imessage) — imsg CLI를 통한 레거시 macOS 통합(지원 중단 예정, 새 설정에는 BlueBubbles 사용). +- [IRC](/channels/irc) — 전통적인 IRC 서버; 페어링/허용 목록 제어가 포함된 채널 + DM. +- [LINE](/channels/line) — LINE Messaging API 봇(번들 plugin). +- [Matrix](/channels/matrix) — Matrix 프로토콜(번들 plugin). +- [Mattermost](/channels/mattermost) — Bot API + WebSocket; 채널, 그룹, DM(번들 plugin). +- [Microsoft Teams](/channels/msteams) — Bot Framework; 엔터프라이즈 지원(번들 plugin). +- [Nextcloud Talk](/channels/nextcloud-talk) — Nextcloud Talk를 통한 셀프 호스팅 채팅(번들 plugin). +- [Nostr](/channels/nostr) — NIP-04를 통한 분산형 DM(번들 plugin). +- [QQ Bot](/channels/qqbot) — QQ Bot API; 개인 채팅, 그룹 채팅, 리치 미디어(번들 plugin). +- [Signal](/channels/signal) — signal-cli; 개인정보 보호 중심. +- [Slack](/channels/slack) — Bolt SDK; 워크스페이스 앱. +- [Synology Chat](/channels/synology-chat) — 송신+수신 웹훅을 통한 Synology NAS Chat(번들 plugin). +- [Telegram](/channels/telegram) — grammY를 통한 Bot API; 그룹을 지원합니다. +- [Tlon](/channels/tlon) — Urbit 기반 메신저(번들 plugin). +- [Twitch](/channels/twitch) — IRC 연결을 통한 Twitch 채팅(번들 plugin). +- [Voice Call](/plugins/voice-call) — Plivo 또는 Twilio를 통한 전화 통신(plugin, 별도 설치). +- [WebChat](/web/webchat) — WebSocket을 통한 Gateway WebChat UI. +- [WeChat](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin) — QR 로그인을 통한 Tencent iLink Bot plugin; 개인 채팅만 지원. +- [WhatsApp](/channels/whatsapp) — 가장 대중적임; Baileys를 사용하며 QR 페어링이 필요합니다. +- [Zalo](/channels/zalo) — Zalo Bot API; 베트남에서 인기 있는 메신저(번들 plugin). +- [Zalo Personal](/channels/zalouser) — QR 로그인을 통한 Zalo 개인 계정(번들 plugin). + +## 참고 + +- 채널은 동시에 실행할 수 있습니다. 여러 개를 구성하면 OpenClaw가 채팅별로 라우팅합니다. +- 가장 빠른 설정은 보통 **Telegram**입니다(간단한 봇 토큰). WhatsApp는 QR 페어링이 필요하며 디스크에 더 많은 상태를 저장합니다. +- 그룹 동작은 채널마다 다릅니다. [Groups](/channels/groups)를 참조하세요. +- 안전을 위해 DM 페어링과 허용 목록이 강제 적용됩니다. [Security](/gateway/security)를 참조하세요. +- 문제 해결: [Channel troubleshooting](/channels/troubleshooting). +- 모델 provider는 별도로 문서화되어 있습니다. [Model Providers](/providers/models)를 참조하세요. diff --git a/docs/ko/channels/irc.md b/docs/ko/channels/irc.md new file mode 100644 index 000000000..fe52a14f4 --- /dev/null +++ b/docs/ko/channels/irc.md @@ -0,0 +1,259 @@ +--- +read_when: + - OpenClaw를 IRC 채널 또는 DM에 연결하려는 경우 + - IRC 허용 목록, 그룹 정책 또는 멘션 게이팅을 구성하는 경우 +summary: IRC 플러그인 설정, 액세스 제어 및 문제 해결 +title: IRC +x-i18n: + generated_at: "2026-04-05T12:35:21Z" + model: gpt-5.4 + provider: openai + source_hash: fceab2979db72116689c6c774d6736a8a2eee3559e3f3cf8969e673d317edd94 + source_path: channels/irc.md + workflow: 15 +--- + +# IRC + +OpenClaw를 전통적인 채널(`#room`) 및 다이렉트 메시지에 연결하려면 IRC를 사용하세요. +IRC는 확장 플러그인으로 제공되지만, 구성은 메인 config의 `channels.irc` 아래에서 수행합니다. + +## 빠른 시작 + +1. `~/.openclaw/openclaw.json`에서 IRC config를 활성화합니다. +2. 최소한 다음을 설정합니다. + +```json5 +{ + channels: { + irc: { + enabled: true, + host: "irc.example.com", + port: 6697, + tls: true, + nick: "openclaw-bot", + channels: ["#openclaw"], + }, + }, +} +``` + +봇 조정을 위해서는 비공개 IRC 서버를 권장합니다. 의도적으로 공개 IRC 네트워크를 사용하는 경우 일반적인 선택지는 Libera.Chat, OFTC, Snoonet입니다. 봇 또는 스웜 백채널 트래픽에는 예측 가능한 공개 채널을 피하세요. + +3. 게이트웨이를 시작하거나 재시작합니다. + +```bash +openclaw gateway run +``` + +## 기본 보안 설정 + +- `channels.irc.dmPolicy`의 기본값은 `"pairing"`입니다. +- `channels.irc.groupPolicy`의 기본값은 `"allowlist"`입니다. +- `groupPolicy="allowlist"`를 사용할 때는 허용된 채널을 정의하기 위해 `channels.irc.groups`를 설정하세요. +- 평문 전송을 의도적으로 허용하는 경우가 아니라면 TLS(`channels.irc.tls=true`)를 사용하세요. + +## 액세스 제어 + +IRC 채널에는 두 가지 별도의 “게이트”가 있습니다. + +1. **채널 액세스**(`groupPolicy` + `groups`): 봇이 해당 채널의 메시지를 아예 수락할지 여부 +2. **발신자 액세스**(`groupAllowFrom` / 채널별 `groups["#channel"].allowFrom`): 해당 채널 안에서 누가 봇을 트리거할 수 있는지 + +Config 키: + +- DM 허용 목록(DM 발신자 액세스): `channels.irc.allowFrom` +- 그룹 발신자 허용 목록(채널 발신자 액세스): `channels.irc.groupAllowFrom` +- 채널별 제어(채널 + 발신자 + 멘션 규칙): `channels.irc.groups["#channel"]` +- `channels.irc.groupPolicy="open"`은 구성되지 않은 채널도 허용합니다(**그래도 기본적으로 멘션 게이팅은 적용됩니다**) + +허용 목록 항목에는 안정적인 발신자 식별자(`nick!user@host`)를 사용해야 합니다. +닉네임만으로 매칭하는 방식은 변경 가능하며, `channels.irc.dangerouslyAllowNameMatching: true`일 때만 활성화됩니다. + +### 흔한 함정: `allowFrom`은 채널용이 아니라 DM용입니다 + +다음과 같은 로그가 보인다면: + +- `irc: drop group sender alice!ident@host (policy=allowlist)` + +이는 **그룹/채널** 메시지에 대해 발신자가 허용되지 않았다는 의미입니다. 다음 중 하나로 해결하세요. + +- `channels.irc.groupAllowFrom` 설정(모든 채널에 전역 적용), 또는 +- 채널별 발신자 허용 목록 설정: `channels.irc.groups["#channel"].allowFrom` + +예시(`#tuirc-dev`에서 누구나 봇과 대화할 수 있도록 허용): + +```json5 +{ + channels: { + irc: { + groupPolicy: "allowlist", + groups: { + "#tuirc-dev": { allowFrom: ["*"] }, + }, + }, + }, +} +``` + +## 응답 트리거(멘션) + +채널이 허용되었고(`groupPolicy` + `groups`), 발신자도 허용되었더라도, OpenClaw는 그룹 컨텍스트에서 기본적으로 **멘션 게이팅**을 사용합니다. + +즉, 메시지에 봇과 일치하는 멘션 패턴이 포함되어 있지 않으면 `drop channel … (missing-mention)` 같은 로그가 보일 수 있습니다. + +멘션 없이도 IRC 채널에서 봇이 **응답하게 하려면**, 해당 채널의 멘션 게이팅을 비활성화하세요. + +```json5 +{ + channels: { + irc: { + groupPolicy: "allowlist", + groups: { + "#tuirc-dev": { + requireMention: false, + allowFrom: ["*"], + }, + }, + }, + }, +} +``` + +또는 **모든** IRC 채널을 허용하고(채널별 허용 목록 없음), 멘션 없이도 응답하게 하려면: + +```json5 +{ + channels: { + irc: { + groupPolicy: "open", + groups: { + "*": { requireMention: false, allowFrom: ["*"] }, + }, + }, + }, +} +``` + +## 보안 참고(공개 채널 권장 설정) + +공개 채널에서 `allowFrom: ["*"]`를 허용하면 누구나 봇에 프롬프트를 보낼 수 있습니다. +위험을 줄이려면 해당 채널의 도구를 제한하세요. + +### 채널 내 모든 사용자에게 동일한 도구 적용 + +```json5 +{ + channels: { + irc: { + groups: { + "#tuirc-dev": { + allowFrom: ["*"], + tools: { + deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"], + }, + }, + }, + }, + }, +} +``` + +### 발신자별로 다른 도구 적용(소유자에게 더 많은 권한 부여) + +`toolsBySender`를 사용해 `"*"`에는 더 엄격한 정책을, 자신의 닉네임에는 더 느슨한 정책을 적용하세요. + +```json5 +{ + channels: { + irc: { + groups: { + "#tuirc-dev": { + allowFrom: ["*"], + toolsBySender: { + "*": { + deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"], + }, + "id:eigen": { + deny: ["gateway", "nodes", "cron"], + }, + }, + }, + }, + }, + }, +} +``` + +참고: + +- `toolsBySender` 키는 IRC 발신자 식별자 값에 대해 `id:`를 사용해야 합니다. + 더 강한 일치를 위해 `id:eigen` 또는 `id:eigen!~eigen@174.127.248.171` 형식을 사용하세요. +- 기존의 접두사 없는 키도 계속 허용되며 `id:`로만 매칭됩니다. +- 가장 먼저 일치하는 발신자 정책이 우선하며, `"*"`는 와일드카드 대체값입니다. + +그룹 액세스와 멘션 게이팅(그리고 이들이 상호작용하는 방식)에 대한 자세한 내용은 [/channels/groups](/channels/groups)를 참조하세요. + +## NickServ + +연결 후 NickServ로 식별하려면: + +```json5 +{ + channels: { + irc: { + nickserv: { + enabled: true, + service: "NickServ", + password: "your-nickserv-password", + }, + }, + }, +} +``` + +연결 시 선택적 1회 등록: + +```json5 +{ + channels: { + irc: { + nickserv: { + register: true, + registerEmail: "bot@example.com", + }, + }, + }, +} +``` + +닉네임이 등록된 후에는 반복적인 REGISTER 시도를 피하기 위해 `register`를 비활성화하세요. + +## 환경 변수 + +기본 계정은 다음을 지원합니다. + +- `IRC_HOST` +- `IRC_PORT` +- `IRC_TLS` +- `IRC_NICK` +- `IRC_USERNAME` +- `IRC_REALNAME` +- `IRC_PASSWORD` +- `IRC_CHANNELS`(쉼표로 구분) +- `IRC_NICKSERV_PASSWORD` +- `IRC_NICKSERV_REGISTER_EMAIL` + +## 문제 해결 + +- 봇이 연결되지만 채널에서 전혀 응답하지 않는다면 `channels.irc.groups`와 멘션 게이팅이 메시지를 드롭하고 있는지(`missing-mention`)를 **모두** 확인하세요. 핑 없이 응답하게 하려면 해당 채널에 `requireMention:false`를 설정하세요. +- 로그인이 실패하면 닉네임 사용 가능 여부와 서버 비밀번호를 확인하세요. +- 사용자 지정 네트워크에서 TLS가 실패하면 호스트/포트와 인증서 설정을 확인하세요. + +## 관련 + +- [Channels Overview](/channels) — 지원되는 모든 채널 +- [Pairing](/channels/pairing) — DM 인증 및 pairing 흐름 +- [Groups](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [Channel Routing](/channels/channel-routing) — 메시지의 세션 라우팅 +- [Security](/gateway/security) — 액세스 모델 및 보안 강화 diff --git a/docs/ko/channels/line.md b/docs/ko/channels/line.md new file mode 100644 index 000000000..1bc10049a --- /dev/null +++ b/docs/ko/channels/line.md @@ -0,0 +1,219 @@ +--- +read_when: + - OpenClaw를 LINE에 연결하려는 경우 + - LINE webhook 및 자격 증명 설정이 필요한 경우 + - LINE 전용 메시지 옵션을 사용하려는 경우 +summary: LINE Messaging API plugin 설정, 구성 및 사용법 +title: LINE +x-i18n: + generated_at: "2026-04-05T12:35:24Z" + model: gpt-5.4 + provider: openai + source_hash: b4782b2aa3e8654505d7f1fd6fc112adf125b5010fc84d655d033688ded37414 + source_path: channels/line.md + workflow: 15 +--- + +# LINE + +LINE는 LINE Messaging API를 통해 OpenClaw에 연결됩니다. 이 plugin은 gateway에서 webhook 수신기로 실행되며 인증에 채널 액세스 토큰과 채널 시크릿을 사용합니다. + +상태: 번들 plugin. 다이렉트 메시지, 그룹 채팅, 미디어, 위치, Flex 메시지, 템플릿 메시지, 빠른 답장이 지원됩니다. 반응과 스레드는 지원되지 않습니다. + +## 번들 plugin + +LINE는 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로 일반 패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드 또는 LINE가 제외된 사용자 지정 설치를 사용하는 경우 수동으로 설치하세요. + +```bash +openclaw plugins install @openclaw/line +``` + +로컬 체크아웃(git 리포지토리에서 실행하는 경우): + +```bash +openclaw plugins install ./path/to/local/line-plugin +``` + +## 설정 + +1. LINE Developers 계정을 만들고 Console을 엽니다: + [https://developers.line.biz/console/](https://developers.line.biz/console/) +2. Provider를 생성(또는 선택)하고 **Messaging API** 채널을 추가합니다. +3. 채널 설정에서 **Channel access token** 및 **Channel secret**을 복사합니다. +4. Messaging API 설정에서 **Use webhook**을 활성화합니다. +5. webhook URL을 gateway 엔드포인트로 설정합니다(HTTPS 필수). + +``` +https://gateway-host/line/webhook +``` + +gateway는 LINE의 webhook 검증(GET)과 인바운드 이벤트(POST)에 응답합니다. +사용자 지정 경로가 필요한 경우 `channels.line.webhookPath` 또는 +`channels.line.accounts..webhookPath`를 설정하고 그에 맞게 URL을 업데이트하세요. + +보안 참고: + +- LINE 서명 검증은 본문에 의존합니다(원시 본문에 대한 HMAC). 따라서 OpenClaw는 검증 전에 엄격한 사전 인증 본문 제한과 타임아웃을 적용합니다. +- OpenClaw는 검증된 원시 요청 바이트에서 webhook 이벤트를 처리합니다. 서명 무결성 보안을 위해 업스트림 미들웨어가 변환한 `req.body` 값은 무시됩니다. + +## 구성 + +최소 구성: + +```json5 +{ + channels: { + line: { + enabled: true, + channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN", + channelSecret: "LINE_CHANNEL_SECRET", + dmPolicy: "pairing", + }, + }, +} +``` + +환경 변수(기본 계정 전용): + +- `LINE_CHANNEL_ACCESS_TOKEN` +- `LINE_CHANNEL_SECRET` + +토큰/시크릿 파일: + +```json5 +{ + channels: { + line: { + tokenFile: "/path/to/line-token.txt", + secretFile: "/path/to/line-secret.txt", + }, + }, +} +``` + +`tokenFile`과 `secretFile`은 일반 파일을 가리켜야 합니다. 심볼릭 링크는 거부됩니다. + +여러 계정: + +```json5 +{ + channels: { + line: { + accounts: { + marketing: { + channelAccessToken: "...", + channelSecret: "...", + webhookPath: "/line/marketing", + }, + }, + }, + }, +} +``` + +## 액세스 제어 + +다이렉트 메시지는 기본적으로 페어링을 사용합니다. 알 수 없는 발신자는 페어링 코드를 받으며 승인될 때까지 메시지가 무시됩니다. + +```bash +openclaw pairing list line +openclaw pairing approve line +``` + +허용 목록 및 정책: + +- `channels.line.dmPolicy`: `pairing | allowlist | open | disabled` +- `channels.line.allowFrom`: DM용 허용 목록 LINE 사용자 ID +- `channels.line.groupPolicy`: `allowlist | open | disabled` +- `channels.line.groupAllowFrom`: 그룹용 허용 목록 LINE 사용자 ID +- 그룹별 재정의: `channels.line.groups..allowFrom` +- 런타임 참고: `channels.line`이 완전히 누락된 경우 런타임은 그룹 확인에 대해 `groupPolicy="allowlist"`로 폴백합니다(`channels.defaults.groupPolicy`가 설정되어 있더라도). + +LINE ID는 대소문자를 구분합니다. 유효한 ID 형식은 다음과 같습니다. + +- 사용자: `U` + 32개의 16진수 문자 +- 그룹: `C` + 32개의 16진수 문자 +- 룸: `R` + 32개의 16진수 문자 + +## 메시지 동작 + +- 텍스트는 5000자 단위로 분할됩니다. +- Markdown 서식은 제거되며, 코드 블록과 표는 가능한 경우 Flex 카드로 변환됩니다. +- 스트리밍 응답은 버퍼링되며, 에이전트가 작업하는 동안 LINE는 로딩 애니메이션과 함께 전체 청크를 받습니다. +- 미디어 다운로드는 `channels.line.mediaMaxMb`(기본값 10)로 제한됩니다. + +## 채널 데이터(리치 메시지) + +빠른 답장, 위치, Flex 카드 또는 템플릿 메시지를 보내려면 `channelData.line`을 사용하세요. + +```json5 +{ + text: "Here you go", + channelData: { + line: { + quickReplies: ["Status", "Help"], + location: { + title: "Office", + address: "123 Main St", + latitude: 35.681236, + longitude: 139.767125, + }, + flexMessage: { + altText: "Status card", + contents: { + /* Flex payload */ + }, + }, + templateMessage: { + type: "confirm", + text: "Proceed?", + confirmLabel: "Yes", + confirmData: "yes", + cancelLabel: "No", + cancelData: "no", + }, + }, + }, +} +``` + +LINE plugin에는 Flex 메시지 프리셋용 `/card` 명령도 포함되어 있습니다. + +``` +/card info "Welcome" "Thanks for joining!" +``` + +## ACP 지원 + +LINE는 ACP(Agent Communication Protocol) 대화 바인딩을 지원합니다. + +- `/acp spawn --bind here`는 하위 스레드를 만들지 않고 현재 LINE 채팅을 ACP 세션에 바인딩합니다. +- 구성된 ACP 바인딩과 활성 대화 바운드 ACP 세션은 다른 대화 채널과 마찬가지로 LINE에서 작동합니다. + +자세한 내용은 [ACP 에이전트](/tools/acp-agents)를 참조하세요. + +## 아웃바운드 미디어 + +LINE plugin은 에이전트 메시지 도구를 통해 이미지, 비디오, 오디오 파일 전송을 지원합니다. 미디어는 적절한 미리보기 및 추적 처리를 포함한 LINE 전용 전달 경로를 통해 전송됩니다. + +- **이미지**: 자동 미리보기 생성과 함께 LINE 이미지 메시지로 전송됩니다. +- **비디오**: 명시적인 미리보기 및 content-type 처리와 함께 전송됩니다. +- **오디오**: LINE 오디오 메시지로 전송됩니다. + +일반 미디어 전송은 LINE 전용 경로를 사용할 수 없을 때 기존 이미지 전용 경로로 폴백합니다. + +## 문제 해결 + +- **Webhook 검증 실패:** webhook URL이 HTTPS인지, 그리고 `channelSecret`이 LINE Console과 일치하는지 확인하세요. +- **인바운드 이벤트 없음:** webhook 경로가 `channels.line.webhookPath`와 일치하는지, 그리고 gateway가 LINE에서 도달 가능한지 확인하세요. +- **미디어 다운로드 오류:** 미디어가 기본 제한을 초과하는 경우 `channels.line.mediaMaxMb`를 늘리세요. + +## 관련 문서 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/location.md b/docs/ko/channels/location.md new file mode 100644 index 000000000..73eabc77a --- /dev/null +++ b/docs/ko/channels/location.md @@ -0,0 +1,63 @@ +--- +read_when: + - 채널 위치 파싱을 추가하거나 수정할 때 + - 에이전트 프롬프트나 도구에서 위치 컨텍스트 필드를 사용할 때 +summary: 수신 채널 위치 파싱(Telegram/WhatsApp/Matrix) 및 컨텍스트 필드 +title: 채널 위치 파싱 +x-i18n: + generated_at: "2026-04-05T12:35:11Z" + model: gpt-5.4 + provider: openai + source_hash: 10061f0c109240a9e0bcab649b17f03b674e8bdf410debf3669b7b6da8189d96 + source_path: channels/location.md + workflow: 15 +--- + +# 채널 위치 파싱 + +OpenClaw는 채팅 채널에서 공유된 위치를 다음 형식으로 정규화합니다: + +- 수신 본문에 추가되는 사람이 읽기 쉬운 텍스트, 그리고 +- 자동 응답 컨텍스트 페이로드의 구조화된 필드. + +현재 지원 항목: + +- **Telegram** (위치 핀 + 장소 + 실시간 위치) +- **WhatsApp** (`locationMessage` + `liveLocationMessage`) +- **Matrix** (`geo_uri`가 있는 `m.location`) + +## 텍스트 형식 + +위치는 대괄호 없이 읽기 쉬운 줄로 렌더링됩니다: + +- 핀: + - `📍 48.858844, 2.294351 ±12m` +- 이름 있는 장소: + - `📍 Eiffel Tower — Champ de Mars, Paris (48.858844, 2.294351 ±12m)` +- 실시간 공유: + - `🛰 실시간 위치: 48.858844, 2.294351 ±12m` + +채널에 캡션/코멘트가 포함되어 있으면 다음 줄에 추가됩니다: + +``` +📍 48.858844, 2.294351 ±12m +여기서 만나요 +``` + +## 컨텍스트 필드 + +위치가 있으면 다음 필드가 `ctx`에 추가됩니다: + +- `LocationLat` (숫자) +- `LocationLon` (숫자) +- `LocationAccuracy` (숫자, 미터; 선택 사항) +- `LocationName` (문자열; 선택 사항) +- `LocationAddress` (문자열; 선택 사항) +- `LocationSource` (`pin | place | live`) +- `LocationIsLive` (불리언) + +## 채널 참고 사항 + +- **Telegram**: 장소는 `LocationName/LocationAddress`에 매핑되며, 실시간 위치는 `live_period`를 사용합니다. +- **WhatsApp**: `locationMessage.comment`와 `liveLocationMessage.caption`은 캡션 줄에 추가됩니다. +- **Matrix**: `geo_uri`는 핀 위치로 파싱되며, 고도는 무시되고 `LocationIsLive`는 항상 false입니다. diff --git a/docs/ko/channels/matrix.md b/docs/ko/channels/matrix.md new file mode 100644 index 000000000..665bf0663 --- /dev/null +++ b/docs/ko/channels/matrix.md @@ -0,0 +1,868 @@ +--- +read_when: + - OpenClaw에서 Matrix를 설정할 때 + - Matrix E2EE 및 검증을 구성할 때 +summary: Matrix 지원 상태, 설정 방법 및 구성 예시 +title: Matrix +x-i18n: + generated_at: "2026-04-05T12:37:20Z" + model: gpt-5.4 + provider: openai + source_hash: ba5c49ad2125d97adf66b5517f8409567eff8b86e20224a32fcb940a02cb0659 + source_path: channels/matrix.md + workflow: 15 +--- + +# Matrix + +Matrix는 OpenClaw용 Matrix 번들 채널 plugin입니다. +공식 `matrix-js-sdk`를 사용하며 DM, room, thread, media, reaction, poll, location, E2EE를 지원합니다. + +## 번들 plugin + +Matrix는 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로, 일반적인 +패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드 또는 Matrix가 제외된 사용자 지정 설치를 사용 중이라면, +수동으로 설치하세요: + +npm에서 설치: + +```bash +openclaw plugins install @openclaw/matrix +``` + +로컬 체크아웃에서 설치: + +```bash +openclaw plugins install ./path/to/local/matrix-plugin +``` + +plugin 동작 및 설치 규칙은 [Plugins](/tools/plugin)를 참조하세요. + +## 설정 + +1. Matrix plugin을 사용할 수 있는지 확인합니다. + - 현재 패키지된 OpenClaw 릴리스에는 이미 포함되어 있습니다. + - 이전/사용자 지정 설치에서는 위 명령으로 수동 추가할 수 있습니다. +2. homeserver에 Matrix 계정을 만듭니다. +3. `channels.matrix`를 다음 중 하나로 구성합니다: + - `homeserver` + `accessToken`, 또는 + - `homeserver` + `userId` + `password`. +4. 게이트웨이를 다시 시작합니다. +5. 봇과 DM을 시작하거나 room에 초대합니다. + +대화형 설정 경로: + +```bash +openclaw channels add +openclaw configure --section channels +``` + +Matrix 마법사가 실제로 묻는 항목: + +- homeserver URL +- 인증 방식: 액세스 토큰 또는 비밀번호 +- 비밀번호 인증을 선택한 경우에만 user ID +- 선택적 device 이름 +- E2EE 활성화 여부 +- 지금 Matrix room 접근을 구성할지 여부 + +알아두어야 할 마법사 동작: + +- 선택한 계정에 대해 Matrix 인증 env var가 이미 존재하고, 해당 계정의 인증이 아직 config에 저장되지 않은 경우, 마법사는 env 바로가기를 제안하고 해당 계정에 `enabled: true`만 기록합니다. +- 다른 Matrix 계정을 대화형으로 추가하면, 입력한 계정 이름이 config와 env var에 사용되는 account ID로 정규화됩니다. 예를 들어 `Ops Bot`은 `ops-bot`이 됩니다. +- DM 허용 목록 프롬프트는 전체 `@user:server` 값을 즉시 받을 수 있습니다. 표시 이름은 실시간 디렉터리 조회에서 정확히 하나의 일치 항목을 찾은 경우에만 작동하며, 그렇지 않으면 마법사가 전체 Matrix ID로 다시 시도하라고 요청합니다. +- Room 허용 목록 프롬프트는 room ID와 alias를 직접 받을 수 있습니다. 참가 중인 room 이름도 실시간으로 확인할 수 있지만, 확인되지 않은 이름은 설정 중 입력된 그대로만 유지되며 이후 런타임 허용 목록 해석에서는 무시됩니다. `!room:server` 또는 `#alias:server`를 권장합니다. +- 런타임 room/세션 식별은 안정적인 Matrix room ID를 사용합니다. room에 선언된 alias는 조회 입력으로만 사용되며, 장기 세션 키나 안정적인 그룹 식별자로는 사용되지 않습니다. +- 저장 전에 room 이름을 해석하려면 `openclaw channels resolve --channel matrix "Project Room"`를 사용하세요. + +최소 토큰 기반 설정: + +```json5 +{ + channels: { + matrix: { + enabled: true, + homeserver: "https://matrix.example.org", + accessToken: "syt_xxx", + dm: { policy: "pairing" }, + }, + }, +} +``` + +비밀번호 기반 설정(로그인 후 토큰이 캐시됨): + +```json5 +{ + channels: { + matrix: { + enabled: true, + homeserver: "https://matrix.example.org", + userId: "@bot:example.org", + password: "replace-me", // pragma: allowlist secret + deviceName: "OpenClaw Gateway", + }, + }, +} +``` + +Matrix는 캐시된 자격 증명을 `~/.openclaw/credentials/matrix/`에 저장합니다. +기본 계정은 `credentials.json`을 사용하고, 이름 있는 계정은 `credentials-.json`을 사용합니다. + +환경 변수 대응 항목(config 키가 설정되지 않은 경우 사용됨): + +- `MATRIX_HOMESERVER` +- `MATRIX_ACCESS_TOKEN` +- `MATRIX_USER_ID` +- `MATRIX_PASSWORD` +- `MATRIX_DEVICE_ID` +- `MATRIX_DEVICE_NAME` + +기본이 아닌 계정에는 계정 범위 env var를 사용합니다: + +- `MATRIX__HOMESERVER` +- `MATRIX__ACCESS_TOKEN` +- `MATRIX__USER_ID` +- `MATRIX__PASSWORD` +- `MATRIX__DEVICE_ID` +- `MATRIX__DEVICE_NAME` + +계정 `ops` 예시: + +- `MATRIX_OPS_HOMESERVER` +- `MATRIX_OPS_ACCESS_TOKEN` + +정규화된 계정 ID `ops-bot`에는 다음을 사용합니다: + +- `MATRIX_OPS_X2D_BOT_HOMESERVER` +- `MATRIX_OPS_X2D_BOT_ACCESS_TOKEN` + +Matrix는 계정 ID의 구두점을 이스케이프하여 범위 지정 env var 충돌을 방지합니다. +예를 들어 `-`는 `_X2D_`가 되므로 `ops-prod`는 `MATRIX_OPS_X2D_PROD_*`에 매핑됩니다. + +대화형 마법사는 해당 인증 env var가 이미 존재하고 선택한 계정에 Matrix 인증이 아직 config에 저장되지 않은 경우에만 env-var 바로가기를 제안합니다. + +## 구성 예시 + +다음은 DM 페어링, room 허용 목록, E2EE 활성화를 포함한 실용적인 기준 구성입니다: + +```json5 +{ + channels: { + matrix: { + enabled: true, + homeserver: "https://matrix.example.org", + accessToken: "syt_xxx", + encryption: true, + + dm: { + policy: "pairing", + threadReplies: "off", + }, + + groupPolicy: "allowlist", + groupAllowFrom: ["@admin:example.org"], + groups: { + "!roomid:example.org": { + requireMention: true, + }, + }, + + autoJoin: "allowlist", + autoJoinAllowlist: ["!roomid:example.org"], + threadReplies: "inbound", + replyToMode: "off", + streaming: "partial", + }, + }, +} +``` + +## 스트리밍 미리보기 + +Matrix 답장 스트리밍은 opt-in입니다. + +모델이 텍스트를 생성하는 동안 OpenClaw가 단일 초안 답장을 보내고, +그 초안을 제자리에서 수정한 뒤, 답장이 +완료되면 최종 확정하도록 하려면 `channels.matrix.streaming`을 `"partial"`로 설정하세요: + +```json5 +{ + channels: { + matrix: { + streaming: "partial", + }, + }, +} +``` + +- `streaming: "off"`가 기본값입니다. OpenClaw는 최종 답장을 기다렸다가 한 번만 전송합니다. +- `streaming: "partial"`은 여러 개의 부분 메시지를 보내는 대신 현재 assistant 블록에 대해 수정 가능한 미리보기 메시지 하나를 생성합니다. +- `blockStreaming: true`는 별도의 Matrix 진행 메시지를 활성화합니다. `streaming: "partial"`과 함께 사용하면 Matrix는 현재 블록의 실시간 초안을 유지하고 완료된 블록은 별도 메시지로 보존합니다. +- `streaming: "partial"`이고 `blockStreaming`이 꺼져 있으면, Matrix는 실시간 초안만 수정하고 해당 블록 또는 턴이 끝나면 완료된 답장을 한 번 전송합니다. +- 미리보기가 더 이상 하나의 Matrix 이벤트에 맞지 않으면, OpenClaw는 미리보기 스트리밍을 중단하고 일반 최종 전달로 대체합니다. +- 미디어 답장은 여전히 첨부파일을 일반 방식으로 전송합니다. 오래된 미리보기를 더 이상 안전하게 재사용할 수 없으면 OpenClaw는 최종 미디어 답장을 보내기 전에 이를 redaction 처리합니다. +- 미리보기 수정에는 추가 Matrix API 호출 비용이 듭니다. 가장 보수적인 속도 제한 동작을 원하면 스트리밍을 끄세요. + +`blockStreaming`만으로는 초안 미리보기가 활성화되지 않습니다. +미리보기 수정을 위해서는 `streaming: "partial"`을 사용하고, 완료된 assistant 블록도 별도 진행 메시지로 유지하려면 그다음 `blockStreaming: true`를 추가하세요. + +## 암호화 및 검증 + +암호화된(E2EE) room에서 발신 이미지 이벤트는 `thumbnail_file`을 사용하므로 이미지 미리보기도 전체 첨부파일과 함께 암호화됩니다. 암호화되지 않은 room은 계속 일반 `thumbnail_url`을 사용합니다. 별도 구성은 필요하지 않습니다. plugin이 E2EE 상태를 자동 감지합니다. + +### 봇 간 room + +기본적으로 다른 구성된 OpenClaw Matrix 계정의 Matrix 메시지는 무시됩니다. + +에이전트 간 Matrix 트래픽을 의도적으로 허용하려면 `allowBots`를 사용하세요: + +```json5 +{ + channels: { + matrix: { + allowBots: "mentions", // true | "mentions" + groups: { + "!roomid:example.org": { + requireMention: true, + }, + }, + }, + }, +} +``` + +- `allowBots: true`는 허용된 room과 DM에서 다른 구성된 Matrix 봇 계정의 메시지를 허용합니다. +- `allowBots: "mentions"`는 room에서 해당 메시지가 이 봇을 명시적으로 멘션할 때만 허용합니다. DM은 여전히 허용됩니다. +- `groups..allowBots`는 하나의 room에 대해 계정 수준 설정을 재정의합니다. +- OpenClaw는 자기 자신에게 답장하는 루프를 방지하기 위해 동일한 Matrix user ID의 메시지는 계속 무시합니다. +- 여기서 Matrix는 기본적인 bot 플래그를 제공하지 않습니다. OpenClaw는 "봇이 작성한"을 "이 OpenClaw 게이트웨이에서 구성된 다른 Matrix 계정이 보낸" 것으로 간주합니다. + +공유 room에서 봇 간 트래픽을 활성화할 때는 엄격한 room 허용 목록과 멘션 요구 사항을 사용하세요. + +암호화 활성화: + +```json5 +{ + channels: { + matrix: { + enabled: true, + homeserver: "https://matrix.example.org", + accessToken: "syt_xxx", + encryption: true, + dm: { policy: "pairing" }, + }, + }, +} +``` + +검증 상태 확인: + +```bash +openclaw matrix verify status +``` + +자세한 상태(전체 진단): + +```bash +openclaw matrix verify status --verbose +``` + +저장된 복구 키를 기계 판독 가능한 출력에 포함: + +```bash +openclaw matrix verify status --include-recovery-key --json +``` + +교차 서명 및 검증 상태 부트스트랩: + +```bash +openclaw matrix verify bootstrap +``` + +다중 계정 지원: 계정별 자격 증명과 선택적 `name`이 포함된 `channels.matrix.accounts`를 사용하세요. 공통 패턴은 [구성 참조](/gateway/configuration-reference#multi-account-all-channels)를 참조하세요. + +자세한 부트스트랩 진단: + +```bash +openclaw matrix verify bootstrap --verbose +``` + +부트스트랩 전에 새 교차 서명 ID 재설정을 강제: + +```bash +openclaw matrix verify bootstrap --force-reset-cross-signing +``` + +복구 키로 이 device 검증: + +```bash +openclaw matrix verify device "" +``` + +자세한 device 검증 정보: + +```bash +openclaw matrix verify device "" --verbose +``` + +room-key 백업 상태 확인: + +```bash +openclaw matrix verify backup status +``` + +자세한 백업 상태 진단: + +```bash +openclaw matrix verify backup status --verbose +``` + +서버 백업에서 room key 복원: + +```bash +openclaw matrix verify backup restore +``` + +자세한 복원 진단: + +```bash +openclaw matrix verify backup restore --verbose +``` + +현재 서버 백업을 삭제하고 새 백업 기준선을 만듭니다. 저장된 +백업 키를 정상적으로 로드할 수 없으면, 이 재설정은 이후 콜드 스타트에서 +새 백업 키를 로드할 수 있도록 secret storage를 다시 생성할 수도 있습니다: + +```bash +openclaw matrix verify backup reset --yes +``` + +모든 `verify` 명령은 기본적으로 간결하며(내부 SDK의 조용한 로깅 포함), `--verbose`를 사용할 때만 자세한 진단을 표시합니다. +스크립트에서는 전체 기계 판독 가능 출력을 위해 `--json`을 사용하세요. + +다중 계정 설정에서 Matrix CLI 명령은 `--account `를 전달하지 않으면 암묵적인 Matrix 기본 계정을 사용합니다. +이름 있는 계정을 여러 개 구성한 경우 `channels.matrix.defaultAccount`를 먼저 설정하지 않으면, 이러한 암묵적 CLI 작업은 중단되고 계정을 명시적으로 선택하라고 요청합니다. +검증 또는 device 작업이 이름 있는 계정을 명시적으로 대상으로 하도록 하려면 항상 `--account`를 사용하세요: + +```bash +openclaw matrix verify status --account assistant +openclaw matrix verify backup restore --account assistant +openclaw matrix devices list --account assistant +``` + +이름 있는 계정에서 암호화가 비활성화되었거나 사용할 수 없는 경우, Matrix 경고와 검증 오류는 해당 계정의 config 키를 가리킵니다. 예: `channels.matrix.accounts.assistant.encryption`. + +### "검증됨"의 의미 + +OpenClaw는 이 Matrix device가 사용자 자신의 교차 서명 ID에 의해 검증된 경우에만 검증된 것으로 간주합니다. +실제로 `openclaw matrix verify status --verbose`는 세 가지 신뢰 신호를 표시합니다: + +- `Locally trusted`: 이 device는 현재 클라이언트에서만 신뢰됨 +- `Cross-signing verified`: SDK가 이 device를 교차 서명을 통해 검증된 것으로 보고함 +- `Signed by owner`: 이 device가 자신의 self-signing key로 서명됨 + +`Verified by owner`는 교차 서명 검증 또는 소유자 서명이 있을 때만 `yes`가 됩니다. +로컬 신뢰만으로는 OpenClaw가 이 device를 완전히 검증된 것으로 취급하기에 충분하지 않습니다. + +### bootstrap이 하는 일 + +`openclaw matrix verify bootstrap`은 암호화된 Matrix 계정의 복구 및 설정 명령입니다. +다음 작업을 순서대로 수행합니다: + +- 가능하면 기존 복구 키를 재사용하며 secret storage를 bootstrap +- 교차 서명을 bootstrap하고 누락된 공개 교차 서명 키를 업로드 +- 현재 device를 표시하고 교차 서명하려 시도 +- 기존 서버 측 room-key 백업이 없으면 새로 생성 + +homeserver가 교차 서명 키 업로드에 상호작용 인증을 요구하는 경우, OpenClaw는 먼저 인증 없이 업로드를 시도하고, 다음으로 `m.login.dummy`, 마지막으로 `channels.matrix.password`가 구성된 경우 `m.login.password`로 시도합니다. + +현재 교차 서명 ID를 버리고 새로 만들 의도가 있을 때만 `--force-reset-cross-signing`을 사용하세요. + +현재 room-key 백업을 의도적으로 버리고 향후 메시지에 대한 새 +백업 기준선을 시작하려면 `openclaw matrix verify backup reset --yes`를 사용하세요. +복구할 수 없는 이전 암호화 기록이 계속 +사용 불가능한 상태로 남을 수 있고, 현재 백업 secret을 안전하게 로드할 수 없는 경우 OpenClaw가 secret storage를 다시 만들 수 있다는 점을 받아들일 때만 수행하세요. + +### 새 백업 기준선 + +향후 암호화된 메시지는 계속 작동하도록 유지하되 복구 불가능한 이전 기록 손실을 감수할 수 있다면, 다음 명령을 순서대로 실행하세요: + +```bash +openclaw matrix verify backup reset --yes +openclaw matrix verify backup status --verbose +openclaw matrix verify status +``` + +이름 있는 Matrix 계정을 명시적으로 대상으로 하려면 각 명령에 `--account `를 추가하세요. + +### 시작 동작 + +`encryption: true`일 때 Matrix는 기본적으로 `startupVerification`을 `"if-unverified"`로 설정합니다. +시작 시 이 device가 아직 검증되지 않았다면, Matrix는 다른 Matrix 클라이언트에서 self-verification을 요청하고, +이미 보류 중인 요청이 있으면 중복 요청을 건너뛰며, 재시작 후 재시도 전에 로컬 쿨다운을 적용합니다. +실패한 요청 시도는 기본적으로 성공적으로 요청을 만든 경우보다 더 빨리 재시도합니다. +자동 시작 요청을 비활성화하려면 `startupVerification: "off"`로 설정하거나, 더 짧거나 긴 재시도 창이 필요하면 `startupVerificationCooldownHours`를 조정하세요. + +시작 시에는 보수적인 crypto bootstrap 단계도 자동으로 수행됩니다. +이 단계는 먼저 현재 secret storage와 교차 서명 ID를 재사용하려고 하며, 명시적인 bootstrap 복구 흐름을 실행하지 않는 한 교차 서명을 재설정하지 않습니다. + +시작 시 손상된 bootstrap 상태를 발견하고 `channels.matrix.password`가 구성되어 있으면, OpenClaw는 더 엄격한 복구 경로를 시도할 수 있습니다. +현재 device가 이미 owner-signed 상태라면 OpenClaw는 이를 자동 재설정하지 않고 해당 ID를 보존합니다. + +이전 공개 Matrix plugin에서 업그레이드할 때: + +- OpenClaw는 가능하면 동일한 Matrix 계정, 액세스 토큰, device ID를 자동으로 재사용합니다. +- 실행 가능한 Matrix 마이그레이션 변경이 수행되기 전에 OpenClaw는 `~/Backups/openclaw-migrations/` 아래에 복구 스냅샷을 생성하거나 재사용합니다. +- 여러 Matrix 계정을 사용하는 경우, 이전 flat-store 레이아웃에서 업그레이드하기 전에 `channels.matrix.defaultAccount`를 설정하여 어떤 계정이 해당 공유 레거시 상태를 받아야 하는지 OpenClaw가 알 수 있게 하세요. +- 이전 plugin이 Matrix room-key 백업 복호화 키를 로컬에 저장했다면, 시작 시 또는 `openclaw doctor --fix` 실행 시 이를 새 복구 키 흐름으로 자동 가져옵니다. +- 마이그레이션 준비 후 Matrix 액세스 토큰이 변경된 경우, 시작 시 자동 백업 복원을 포기하기 전에 보류 중인 레거시 복원 상태가 있는 형제 토큰 해시 저장소 루트를 검색합니다. +- 동일 계정, homeserver, user에 대해 나중에 Matrix 액세스 토큰이 변경되면, OpenClaw는 빈 Matrix 상태 디렉터리에서 시작하는 대신 가장 완전한 기존 토큰 해시 저장소 루트를 우선 재사용합니다. +- 다음 게이트웨이 시작 시 백업된 room key가 새 crypto 저장소로 자동 복원됩니다. +- 이전 plugin에 백업되지 않은 로컬 전용 room key가 있었다면, OpenClaw는 이를 명확히 경고합니다. 해당 key는 이전 rust crypto 저장소에서 자동 내보내기할 수 없으므로 일부 이전 암호화 기록은 수동 복구 전까지 계속 사용할 수 없을 수 있습니다. +- 전체 업그레이드 흐름, 제한 사항, 복구 명령, 일반적인 마이그레이션 메시지는 [Matrix migration](/install/migrating-matrix)을 참조하세요. + +암호화된 런타임 상태는 계정별, 사용자별 토큰 해시 루트 아래의 +`~/.openclaw/matrix/accounts//__//`에 구성됩니다. +이 디렉터리에는 sync 저장소(`bot-storage.json`), crypto 저장소(`crypto/`), +복구 키 파일(`recovery-key.json`), IndexedDB 스냅샷(`crypto-idb-snapshot.json`), +thread 바인딩(`thread-bindings.json`), 시작 검증 상태(`startup-verification.json`)가 +해당 기능 사용 시 포함됩니다. +토큰이 변경되더라도 계정 ID가 같으면 OpenClaw는 해당 계정/homeserver/user 튜플에 대해 가장 적합한 기존 +루트를 재사용하므로 이전 sync 상태, crypto 상태, thread 바인딩, +시작 검증 상태를 계속 확인할 수 있습니다. + +### Node crypto 저장소 모델 + +이 plugin의 Matrix E2EE는 Node에서 공식 `matrix-js-sdk` Rust crypto 경로를 사용합니다. +이 경로는 crypto 상태를 재시작 후에도 유지하려면 IndexedDB 기반 영속성을 기대합니다. + +OpenClaw는 현재 Node에서 다음 방식으로 이를 제공합니다: + +- SDK가 기대하는 IndexedDB API shim으로 `fake-indexeddb` 사용 +- `initRustCrypto` 전에 `crypto-idb-snapshot.json`에서 Rust crypto IndexedDB 내용을 복원 +- 초기화 후와 런타임 중에 업데이트된 IndexedDB 내용을 다시 `crypto-idb-snapshot.json`에 저장 +- gateway 런타임 영속성과 CLI 유지관리 작업이 같은 스냅샷 파일에서 경쟁하지 않도록 advisory 파일 잠금을 사용해 `crypto-idb-snapshot.json`에 대한 스냅샷 복원과 저장을 직렬화 + +이는 사용자 지정 crypto 구현이 아니라 호환성/저장소 플러밍입니다. +스냅샷 파일은 민감한 런타임 상태이며 제한적인 파일 권한으로 저장됩니다. +OpenClaw의 보안 모델에서 gateway host와 로컬 OpenClaw 상태 디렉터리는 이미 신뢰된 운영자 경계 내부에 있으므로, 이는 별도의 원격 신뢰 경계라기보다 주로 운영상 내구성 문제입니다. + +계획된 개선 사항: + +- 영속적인 Matrix key material에 대한 SecretRef 지원을 추가하여 복구 키와 관련 저장소 암호화 secret을 로컬 파일뿐 아니라 OpenClaw secrets provider에서도 가져올 수 있도록 함 + +## 프로필 관리 + +선택한 계정의 Matrix self-profile을 다음 명령으로 업데이트합니다: + +```bash +openclaw matrix profile set --name "OpenClaw Assistant" +openclaw matrix profile set --avatar-url https://cdn.example.org/avatar.png +``` + +이름 있는 Matrix 계정을 명시적으로 대상으로 하려면 `--account `를 추가하세요. + +Matrix는 `mxc://` 아바타 URL을 직접 받을 수 있습니다. `http://` 또는 `https://` 아바타 URL을 전달하면 OpenClaw는 먼저 이를 Matrix에 업로드하고, 해석된 `mxc://` URL을 다시 `channels.matrix.avatarUrl`(또는 선택한 계정 재정의)에 저장합니다. + +## 자동 검증 알림 + +이제 Matrix는 strict DM 검증 room에 검증 수명 주기 알림을 `m.notice` 메시지로 직접 게시합니다. +여기에는 다음이 포함됩니다: + +- 검증 요청 알림 +- 검증 준비 알림(명시적인 "Verify by emoji" 안내 포함) +- 검증 시작 및 완료 알림 +- 사용 가능한 경우 SAS 세부 정보(이모지 및 10진수) + +다른 Matrix 클라이언트에서 들어오는 검증 요청은 OpenClaw가 추적하고 자동 수락합니다. +self-verification 흐름에서는 이모지 검증을 사용할 수 있게 되면 OpenClaw도 자동으로 SAS 흐름을 시작하고 자체 측을 확인합니다. +다른 Matrix 사용자/device의 검증 요청에 대해서는 OpenClaw가 요청을 자동 수락한 뒤 SAS 흐름이 정상적으로 진행되기를 기다립니다. +검증을 완료하려면 여전히 Matrix 클라이언트에서 이모지 또는 10진수 SAS를 비교하고 그곳에서 "They match"를 확인해야 합니다. + +OpenClaw는 self-initiated duplicate flow를 무작정 자동 수락하지 않습니다. 시작 시 self-verification 요청이 이미 보류 중이면 새 요청 생성을 건너뜁니다. + +검증 프로토콜/시스템 알림은 에이전트 채팅 파이프라인으로 전달되지 않으므로 `NO_REPLY`를 생성하지 않습니다. + +### device 위생 + +오래된 OpenClaw 관리 Matrix device가 계정에 누적되면 암호화된 room의 신뢰 상태를 파악하기 어려워질 수 있습니다. +다음 명령으로 목록을 확인하세요: + +```bash +openclaw matrix devices list +``` + +오래된 OpenClaw 관리 device는 다음 명령으로 제거하세요: + +```bash +openclaw matrix devices prune-stale +``` + +### Direct Room 복구 + +direct-message 상태가 어긋나면 OpenClaw는 오래된 단일 room을 가리키는 stale `m.direct` 매핑을 가지게 될 수 있습니다. 피어의 현재 매핑을 확인하려면 다음을 사용하세요: + +```bash +openclaw matrix direct inspect --user-id @alice:example.org +``` + +다음으로 복구할 수 있습니다: + +```bash +openclaw matrix direct repair --user-id @alice:example.org +``` + +복구는 Matrix 전용 로직을 plugin 내부에 유지합니다: + +- 먼저 `m.direct`에 이미 매핑된 엄격한 1:1 DM을 우선 사용 +- 그렇지 않으면 해당 사용자와 현재 참가 중인 엄격한 1:1 DM으로 대체 +- 정상적인 DM이 없으면 새 direct room을 생성하고 `m.direct`를 다시 써서 그것을 가리키게 함 + +복구 흐름은 오래된 room을 자동 삭제하지 않습니다. 정상적인 DM을 선택하고 매핑만 업데이트하여 이후 Matrix 전송, 검증 알림 및 기타 direct-message 흐름이 다시 올바른 room을 대상으로 하게 합니다. + +## Threads + +Matrix는 자동 답장과 message-tool 전송 모두에 대해 기본 Matrix thread를 지원합니다. + +- `threadReplies: "off"`는 답장을 최상위 수준으로 유지하고 수신 thread 메시지를 부모 세션에 유지합니다. +- `threadReplies: "inbound"`는 수신 메시지가 이미 해당 thread에 있을 때만 thread 내부에서 답장합니다. +- `threadReplies: "always"`는 room 답장을 트리거 메시지를 루트로 하는 thread에 유지하고, 첫 트리거 메시지부터 일치하는 thread 범위 세션을 통해 해당 대화를 라우팅합니다. +- `dm.threadReplies`는 DM에 대해서만 최상위 설정을 재정의합니다. 예를 들어 room thread는 분리하면서 DM은 평면으로 유지할 수 있습니다. +- 수신 thread 메시지에는 추가 에이전트 컨텍스트로 thread 루트 메시지가 포함됩니다. +- 이제 message-tool 전송은 대상이 같은 room이거나 같은 DM 사용자 대상인 경우, 명시적인 `threadId`가 제공되지 않으면 현재 Matrix thread를 자동 상속합니다. +- Matrix는 런타임 thread 바인딩을 지원합니다. 이제 `/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`, thread에 바인딩된 `/acp spawn`이 Matrix room과 DM에서 작동합니다. +- `threadBindings.spawnSubagentSessions=true`일 때 최상위 Matrix room/DM의 `/focus`는 새 Matrix thread를 만들고 이를 대상 세션에 바인딩합니다. +- 기존 Matrix thread 안에서 `/focus` 또는 `/acp spawn --thread here`를 실행하면 현재 thread가 대신 바인딩됩니다. + +## ACP 대화 바인딩 + +Matrix room, DM, 기존 Matrix thread는 채팅 표면을 바꾸지 않고도 영속적인 ACP 워크스페이스로 전환할 수 있습니다. + +빠른 운영자 흐름: + +- 계속 사용할 Matrix DM, room 또는 기존 thread 안에서 `/acp spawn codex --bind here`를 실행합니다. +- 최상위 Matrix DM 또는 room에서는 현재 DM/room이 채팅 표면으로 유지되고 이후 메시지는 생성된 ACP 세션으로 라우팅됩니다. +- 기존 Matrix thread 안에서는 `--bind here`가 현재 thread를 제자리에서 바인딩합니다. +- `/new`와 `/reset`은 동일한 바인딩된 ACP 세션을 제자리에서 재설정합니다. +- `/acp close`는 ACP 세션을 닫고 바인딩을 제거합니다. + +참고: + +- `--bind here`는 하위 Matrix thread를 생성하지 않습니다. +- `threadBindings.spawnAcpSessions`는 OpenClaw가 하위 Matrix thread를 만들거나 바인딩해야 하는 `/acp spawn --thread auto|here`에만 필요합니다. + +### Thread 바인딩 구성 + +Matrix는 `session.threadBindings`의 전역 기본값을 상속하며, 채널별 재정의도 지원합니다: + +- `threadBindings.enabled` +- `threadBindings.idleHours` +- `threadBindings.maxAgeHours` +- `threadBindings.spawnSubagentSessions` +- `threadBindings.spawnAcpSessions` + +Matrix thread 바인딩된 spawn 플래그는 opt-in입니다: + +- 최상위 `/focus`가 새 Matrix thread를 만들고 바인딩하도록 허용하려면 `threadBindings.spawnSubagentSessions: true`를 설정하세요. +- `/acp spawn --thread auto|here`가 ACP 세션을 Matrix thread에 바인딩하도록 허용하려면 `threadBindings.spawnAcpSessions: true`를 설정하세요. + +## Reactions + +Matrix는 발신 reaction 작업, 수신 reaction 알림, 수신 ack reaction을 지원합니다. + +- 발신 reaction tooling은 `channels["matrix"].actions.reactions`로 게이트됩니다. +- `react`는 특정 Matrix 이벤트에 reaction을 추가합니다. +- `reactions`는 특정 Matrix 이벤트의 현재 reaction 요약을 나열합니다. +- `emoji=""`는 해당 이벤트에 대한 봇 계정 자신의 reaction을 제거합니다. +- `remove: true`는 봇 계정의 지정된 이모지 reaction만 제거합니다. + +ack reaction은 표준 OpenClaw 해석 순서를 사용합니다: + +- `channels["matrix"].accounts..ackReaction` +- `channels["matrix"].ackReaction` +- `messages.ackReaction` +- 에이전트 ID 이모지 대체값 + +ack reaction 범위는 다음 순서로 해석됩니다: + +- `channels["matrix"].accounts..ackReactionScope` +- `channels["matrix"].ackReactionScope` +- `messages.ackReactionScope` + +reaction 알림 모드는 다음 순서로 해석됩니다: + +- `channels["matrix"].accounts..reactionNotifications` +- `channels["matrix"].reactionNotifications` +- 기본값: `own` + +현재 동작: + +- `reactionNotifications: "own"`은 봇이 작성한 Matrix 메시지를 대상으로 하는 추가된 `m.reaction` 이벤트를 전달합니다. +- `reactionNotifications: "off"`는 reaction 시스템 이벤트를 비활성화합니다. +- Matrix는 제거를 독립적인 `m.reaction` 제거가 아니라 redaction으로 표시하므로, reaction 제거는 아직 시스템 이벤트로 합성되지 않습니다. + +## 기록 컨텍스트 + +- `channels.matrix.historyLimit`는 Matrix room 메시지가 에이전트를 트리거할 때 `InboundHistory`로 포함할 최근 room 메시지 수를 제어합니다. +- 기본값은 `messages.groupChat.historyLimit`를 따릅니다. 비활성화하려면 `0`으로 설정하세요. +- Matrix room 기록은 room 전용입니다. DM은 계속 일반 세션 기록을 사용합니다. +- Matrix room 기록은 pending-only입니다. OpenClaw는 아직 답장을 트리거하지 않은 room 메시지를 버퍼링한 뒤, 멘션이나 다른 트리거가 들어오면 그 구간을 스냅샷으로 저장합니다. +- 현재 트리거 메시지는 `InboundHistory`에 포함되지 않으며, 해당 턴의 메인 수신 본문에 남아 있습니다. +- 동일한 Matrix 이벤트를 재시도할 때는 새 room 메시지로 앞으로 이동하지 않고 원래 기록 스냅샷을 재사용합니다. + +## 컨텍스트 가시성 + +Matrix는 가져온 답장 텍스트, thread 루트, pending history 같은 보조 room 컨텍스트에 대해 공통 `contextVisibility` 제어를 지원합니다. + +- `contextVisibility: "all"`이 기본값입니다. 보조 컨텍스트는 받은 그대로 유지됩니다. +- `contextVisibility: "allowlist"`는 활성 room/user 허용 목록 검사에서 허용된 발신자만 보조 컨텍스트에 남깁니다. +- `contextVisibility: "allowlist_quote"`는 `allowlist`처럼 동작하지만, 명시적으로 인용된 답장 하나는 계속 유지합니다. + +이 설정은 보조 컨텍스트의 가시성에 영향을 주며, 수신 메시지 자체가 답장을 트리거할 수 있는지 여부에는 영향을 주지 않습니다. +트리거 권한은 계속 `groupPolicy`, `groups`, `groupAllowFrom`, DM 정책 설정에서 결정됩니다. + +## DM 및 room 정책 예시 + +```json5 +{ + channels: { + matrix: { + dm: { + policy: "allowlist", + allowFrom: ["@admin:example.org"], + threadReplies: "off", + }, + groupPolicy: "allowlist", + groupAllowFrom: ["@admin:example.org"], + groups: { + "!roomid:example.org": { + requireMention: true, + }, + }, + }, + }, +} +``` + +멘션 게이팅 및 허용 목록 동작은 [Groups](/channels/groups)를 참조하세요. + +Matrix DM용 페어링 예시: + +```bash +openclaw pairing list matrix +openclaw pairing approve matrix +``` + +승인 전의 Matrix 사용자가 계속 메시지를 보내면, OpenClaw는 동일한 보류 중 페어링 코드를 재사용하며 새 코드를 발급하는 대신 짧은 쿨다운 후 다시 리마인더 답장을 보낼 수 있습니다. + +공통 DM 페어링 흐름 및 저장소 레이아웃은 [페어링](/channels/pairing)을 참조하세요. + +## Exec 승인 + +Matrix는 Matrix 계정의 exec 승인 클라이언트로 동작할 수 있습니다. + +- `channels.matrix.execApprovals.enabled` +- `channels.matrix.execApprovals.approvers` (선택 사항; 기본값은 `channels.matrix.dm.allowFrom`) +- `channels.matrix.execApprovals.target` (`dm` | `channel` | `both`, 기본값: `dm`) +- `channels.matrix.execApprovals.agentFilter` +- `channels.matrix.execApprovals.sessionFilter` + +승인자는 `@owner:example.org` 같은 Matrix user ID여야 합니다. `enabled`가 설정되지 않았거나 `"auto"`이고 `execApprovals.approvers` 또는 `channels.matrix.dm.allowFrom`에서 하나 이상의 승인자를 해석할 수 있으면 Matrix는 기본 exec 승인을 자동 활성화합니다. Matrix를 기본 승인 클라이언트로 명시적으로 비활성화하려면 `enabled: false`로 설정하세요. 그렇지 않으면 승인 요청은 다른 구성된 승인 경로나 exec 승인 대체 정책으로 넘어갑니다. + +현재 Matrix 기본 라우팅은 exec 전용입니다: + +- `channels.matrix.execApprovals.*`는 exec 승인에 대해서만 기본 DM/채널 라우팅을 제어합니다. +- plugin 승인은 계속 공통 same-chat `/approve`와 구성된 `approvals.plugin` 전달을 사용합니다. +- Matrix는 승인자를 안전하게 추론할 수 있을 때 plugin 승인 권한 부여를 위해 `channels.matrix.dm.allowFrom`을 재사용할 수 있지만, 별도의 기본 plugin 승인 DM/채널 fanout 경로를 제공하지는 않습니다. + +전달 규칙: + +- `target: "dm"`은 승인 프롬프트를 승인자 DM으로 보냅니다 +- `target: "channel"`은 프롬프트를 원래 Matrix room 또는 DM으로 다시 보냅니다 +- `target: "both"`는 승인자 DM과 원래 Matrix room 또는 DM 모두로 보냅니다 + +현재 Matrix는 텍스트 승인 프롬프트를 사용합니다. 승인자는 `/approve allow-once`, `/approve allow-always`, 또는 `/approve deny`로 이를 처리합니다. + +해석된 승인자만 승인 또는 거부할 수 있습니다. 채널 전달에는 명령 텍스트가 포함되므로 신뢰할 수 있는 room에서만 `channel` 또는 `both`를 활성화하세요. + +Matrix 승인 프롬프트는 공통 코어 승인 planner를 재사용합니다. Matrix 전용 기본 표면은 exec 승인에 대해서만 전송 계층 역할을 합니다: room/DM 라우팅 및 메시지 전송/업데이트/삭제 동작. + +계정별 재정의: + +- `channels.matrix.accounts..execApprovals` + +관련 문서: [Exec approvals](/tools/exec-approvals) + +## 다중 계정 예시 + +```json5 +{ + channels: { + matrix: { + enabled: true, + defaultAccount: "assistant", + dm: { policy: "pairing" }, + accounts: { + assistant: { + homeserver: "https://matrix.example.org", + accessToken: "syt_assistant_xxx", + encryption: true, + }, + alerts: { + homeserver: "https://matrix.example.org", + accessToken: "syt_alerts_xxx", + dm: { + policy: "allowlist", + allowFrom: ["@ops:example.org"], + threadReplies: "off", + }, + }, + }, + }, + }, +} +``` + +최상위 `channels.matrix` 값은 계정이 재정의하지 않는 한 이름 있는 계정의 기본값으로 동작합니다. +상속된 room 항목을 특정 Matrix 계정 하나로 제한하려면 `groups..account`(또는 레거시 `rooms..account`)를 사용할 수 있습니다. +`account`가 없는 항목은 모든 Matrix 계정에 공통으로 유지되며, 기본 계정이 최상위 `channels.matrix.*`에 직접 구성된 경우 `account: "default"` 항목도 계속 작동합니다. +부분적인 공통 인증 기본값만으로는 별도의 암묵적 기본 계정이 생성되지 않습니다. OpenClaw는 해당 기본 계정에 새로운 인증(`homeserver` + `accessToken`, 또는 `homeserver` + `userId` + `password`)이 있을 때만 최상위 `default` 계정을 합성합니다. 이름 있는 계정은 이후 캐시된 자격 증명으로 인증을 만족할 수 있다면 `homeserver` + `userId`만으로도 계속 검색 가능할 수 있습니다. +Matrix에 이미 정확히 하나의 이름 있는 계정이 있거나 `defaultAccount`가 기존 이름 있는 계정 키를 가리키는 경우, 단일 계정에서 다중 계정으로의 복구/설정 승격은 새 `accounts.default` 항목을 만드는 대신 해당 계정을 보존합니다. Matrix 인증/bootstrap 키만 해당 승격된 계정으로 이동하며, 공통 전달 정책 키는 최상위에 남습니다. +암묵적 라우팅, 프로브, CLI 작업에 대해 OpenClaw가 특정 이름 있는 Matrix 계정을 우선 사용하도록 하려면 `defaultAccount`를 설정하세요. +여러 이름 있는 계정을 구성하는 경우, 암묵적 계정 선택에 의존하는 CLI 명령에 대해 `defaultAccount`를 설정하거나 `--account `를 전달하세요. +한 명령에 대해 이 암묵적 선택을 재정의하려면 `openclaw matrix verify ...` 및 `openclaw matrix devices ...`에 `--account `를 전달하세요. + +## 비공개/LAN homeserver + +기본적으로 OpenClaw는 SSRF 보호를 위해 private/internal Matrix homeserver를 차단하며, +계정별로 명시적으로 opt-in해야만 허용합니다. + +homeserver가 localhost, LAN/Tailscale IP 또는 내부 호스트 이름에서 실행되는 경우, +해당 Matrix 계정에 대해 `allowPrivateNetwork`를 활성화하세요: + +```json5 +{ + channels: { + matrix: { + homeserver: "http://matrix-synapse:8008", + allowPrivateNetwork: true, + accessToken: "syt_internal_xxx", + }, + }, +} +``` + +CLI 설정 예시: + +```bash +openclaw matrix account add \ + --account ops \ + --homeserver http://matrix-synapse:8008 \ + --allow-private-network \ + --access-token syt_ops_xxx +``` + +이 opt-in은 신뢰된 private/internal 대상만 허용합니다. `http://matrix.example.org:8008` 같은 +공개 평문 homeserver는 계속 차단됩니다. 가능하면 `https://`를 권장합니다. + +## Matrix 트래픽 프록시 + +Matrix 배포에 명시적인 아웃바운드 HTTP(S) 프록시가 필요하면 `channels.matrix.proxy`를 설정하세요: + +```json5 +{ + channels: { + matrix: { + homeserver: "https://matrix.example.org", + accessToken: "syt_bot_xxx", + proxy: "http://127.0.0.1:7890", + }, + }, +} +``` + +이름 있는 계정은 `channels.matrix.accounts..proxy`로 최상위 기본값을 재정의할 수 있습니다. +OpenClaw는 런타임 Matrix 트래픽과 계정 상태 프로브 모두에 동일한 프록시 설정을 사용합니다. + +## 대상 해석 + +Matrix는 OpenClaw가 room 또는 user 대상을 요청하는 모든 위치에서 다음 대상 형식을 허용합니다: + +- 사용자: `@user:server`, `user:@user:server`, 또는 `matrix:user:@user:server` +- Room: `!room:server`, `room:!room:server`, 또는 `matrix:room:!room:server` +- Alias: `#alias:server`, `channel:#alias:server`, 또는 `matrix:channel:#alias:server` + +실시간 디렉터리 조회는 로그인된 Matrix 계정을 사용합니다: + +- 사용자 조회는 해당 homeserver의 Matrix 사용자 디렉터리를 조회합니다. +- Room 조회는 명시적인 room ID와 alias를 직접 받고, 이후 해당 계정의 참가 중인 room 이름 검색으로 대체합니다. +- 참가 중 room 이름 조회는 best-effort입니다. room 이름을 ID나 alias로 해석할 수 없으면 런타임 허용 목록 해석에서 무시됩니다. + +## 구성 참조 + +- `enabled`: 채널 활성화 또는 비활성화. +- `name`: 계정의 선택적 레이블. +- `defaultAccount`: 여러 Matrix 계정이 구성된 경우 선호되는 account ID. +- `homeserver`: homeserver URL(예: `https://matrix.example.org`). +- `allowPrivateNetwork`: 이 Matrix 계정이 private/internal homeserver에 연결되도록 허용합니다. homeserver가 `localhost`, LAN/Tailscale IP 또는 `matrix-synapse` 같은 내부 호스트로 해석되는 경우 활성화하세요. +- `proxy`: Matrix 트래픽용 선택적 HTTP(S) 프록시 URL. 이름 있는 계정은 자체 `proxy`로 최상위 기본값을 재정의할 수 있습니다. +- `userId`: 전체 Matrix user ID(예: `@bot:example.org`). +- `accessToken`: 토큰 기반 인증용 액세스 토큰. 일반 텍스트 값과 SecretRef 값은 env/file/exec provider 전반에서 `channels.matrix.accessToken`과 `channels.matrix.accounts..accessToken`에 지원됩니다. [Secrets Management](/gateway/secrets)를 참조하세요. +- `password`: 비밀번호 기반 로그인용 비밀번호. 일반 텍스트 값과 SecretRef 값이 지원됩니다. +- `deviceId`: 명시적 Matrix device ID. +- `deviceName`: 비밀번호 로그인용 device 표시 이름. +- `avatarUrl`: 프로필 동기화 및 `set-profile` 업데이트용으로 저장된 self-avatar URL. +- `initialSyncLimit`: 시작 시 sync 이벤트 제한. +- `encryption`: E2EE 활성화. +- `allowlistOnly`: DM과 room에 대해 허용 목록 전용 동작 강제. +- `allowBots`: 다른 구성된 OpenClaw Matrix 계정의 메시지를 허용(`true` 또는 `"mentions"`). +- `groupPolicy`: `open`, `allowlist`, 또는 `disabled`. +- `contextVisibility`: 보조 room 컨텍스트 가시성 모드(`all`, `allowlist`, `allowlist_quote`). +- `groupAllowFrom`: room 트래픽용 user ID 허용 목록. +- `groupAllowFrom` 항목은 전체 Matrix user ID여야 합니다. 해석되지 않은 이름은 런타임에 무시됩니다. +- `historyLimit`: 그룹 기록 컨텍스트로 포함할 최대 room 메시지 수. 기본값은 `messages.groupChat.historyLimit`를 따릅니다. 비활성화하려면 `0`으로 설정하세요. +- `replyToMode`: `off`, `first`, 또는 `all`. +- `markdown`: 발신 Matrix 텍스트용 선택적 Markdown 렌더링 구성. +- `streaming`: `off`(기본값), `partial`, `true`, 또는 `false`. `partial`과 `true`는 제자리 수정 업데이트가 가능한 단일 메시지 초안 미리보기를 활성화합니다. +- `blockStreaming`: `true`는 초안 미리보기 스트리밍이 활성화된 동안 완료된 assistant 블록에 대해 별도 진행 메시지를 활성화합니다. +- `threadReplies`: `off`, `inbound`, 또는 `always`. +- `threadBindings`: thread 바인딩 세션 라우팅 및 수명 주기에 대한 채널별 재정의. +- `startupVerification`: 시작 시 자동 self-verification 요청 모드(`if-unverified`, `off`). +- `startupVerificationCooldownHours`: 자동 시작 검증 요청을 재시도하기 전 대기 시간. +- `textChunkLimit`: 발신 메시지 청크 크기. +- `chunkMode`: `length` 또는 `newline`. +- `responsePrefix`: 발신 답장용 선택적 메시지 접두사. +- `ackReaction`: 이 채널/계정용 선택적 ack reaction 재정의. +- `ackReactionScope`: 선택적 ack reaction 범위 재정의(`group-mentions`, `group-all`, `direct`, `all`, `none`, `off`). +- `reactionNotifications`: 수신 reaction 알림 모드(`own`, `off`). +- `mediaMaxMb`: Matrix 미디어 처리용 미디어 크기 제한(MB). 발신 전송 및 수신 미디어 처리 모두에 적용됩니다. +- `autoJoin`: 초대 자동 참가 정책(`always`, `allowlist`, `off`). 기본값: `off`. +- `autoJoinAllowlist`: `autoJoin`이 `allowlist`일 때 허용되는 room/alias. alias 항목은 초대 처리 중 room ID로 해석되며, OpenClaw는 초대된 room이 주장하는 alias 상태를 신뢰하지 않습니다. +- `dm`: DM 정책 블록(`enabled`, `policy`, `allowFrom`, `threadReplies`). +- `dm.allowFrom` 항목은 실시간 디렉터리 조회로 이미 해석한 경우가 아니면 전체 Matrix user ID여야 합니다. +- `dm.threadReplies`: DM 전용 thread 정책 재정의(`off`, `inbound`, `always`). DM에서 답장 배치와 세션 분리 모두에 대해 최상위 `threadReplies` 설정을 재정의합니다. +- `execApprovals`: Matrix 기본 exec 승인 전달(`enabled`, `approvers`, `target`, `agentFilter`, `sessionFilter`). +- `execApprovals.approvers`: exec 요청을 승인할 수 있는 Matrix user ID. `dm.allowFrom`이 이미 승인자를 식별하는 경우 선택 사항입니다. +- `execApprovals.target`: `dm | channel | both` (기본값: `dm`). +- `accounts`: 이름 있는 계정별 재정의. 최상위 `channels.matrix` 값이 이 항목의 기본값으로 동작합니다. +- `groups`: room별 정책 맵. room ID 또는 alias를 권장하며, 해석되지 않은 room 이름은 런타임에 무시됩니다. 세션/그룹 ID는 해석 후 안정적인 room ID를 사용하고, 사람이 읽는 레이블은 계속 room 이름에서 가져옵니다. +- `groups..account`: 다중 계정 설정에서 상속된 room 항목 하나를 특정 Matrix 계정으로 제한. +- `groups..allowBots`: 구성된 봇 발신자에 대한 room 수준 재정의(`true` 또는 `"mentions"`). +- `groups..users`: room별 발신자 허용 목록. +- `groups..tools`: room별 도구 허용/거부 재정의. +- `groups..autoReply`: room 수준 멘션 게이팅 재정의. `true`는 해당 room의 멘션 요구 사항을 비활성화하고, `false`는 다시 강제합니다. +- `groups..skills`: 선택적 room 수준 Skills 필터. +- `groups..systemPrompt`: 선택적 room 수준 시스템 프롬프트 스니펫. +- `rooms`: `groups`의 레거시 별칭. +- `actions`: 작업별 도구 게이팅(`messages`, `reactions`, `pins`, `profile`, `memberInfo`, `channelInfo`, `verification`). + +## 관련 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 접근 모델 및 보안 강화 diff --git a/docs/ko/channels/mattermost.md b/docs/ko/channels/mattermost.md new file mode 100644 index 000000000..85b4ecbee --- /dev/null +++ b/docs/ko/channels/mattermost.md @@ -0,0 +1,480 @@ +--- +read_when: + - Mattermost 설정 + - Mattermost 라우팅 디버깅 +summary: Mattermost 봇 설정 및 OpenClaw 구성 +title: Mattermost +x-i18n: + generated_at: "2026-04-05T12:36:40Z" + model: gpt-5.4 + provider: openai + source_hash: f21dc7543176fda0b38b00fab60f0daae38dffcf68fa1cf7930a9f14ec57cb5a + source_path: channels/mattermost.md + workflow: 15 +--- + +# Mattermost + +상태: 번들 plugin(봇 토큰 + WebSocket 이벤트). 채널, 그룹, DM이 지원됩니다. +Mattermost는 자체 호스팅 가능한 팀 메시징 플랫폼입니다. 제품 세부 정보와 다운로드는 공식 사이트 +[mattermost.com](https://mattermost.com)에서 확인하세요. + +## 번들 plugin + +Mattermost는 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로, 일반적인 +패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +구버전 빌드 또는 Mattermost가 제외된 커스텀 설치를 사용하는 경우, +수동으로 설치하세요. + +CLI로 설치(npm 레지스트리): + +```bash +openclaw plugins install @openclaw/mattermost +``` + +로컬 체크아웃(git 리포지토리에서 실행 중인 경우): + +```bash +openclaw plugins install ./path/to/local/mattermost-plugin +``` + +자세한 내용: [Plugins](/tools/plugin) + +## 빠른 설정 + +1. Mattermost plugin을 사용할 수 있는지 확인합니다. + - 현재 패키지된 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 구버전/커스텀 설치는 위 명령으로 수동 추가할 수 있습니다. +2. Mattermost 봇 계정을 만들고 **봇 토큰**을 복사합니다. +3. Mattermost **기본 URL**을 복사합니다(예: `https://chat.example.com`). +4. OpenClaw를 구성하고 gateway를 시작합니다. + +최소 구성: + +```json5 +{ + channels: { + mattermost: { + enabled: true, + botToken: "mm-token", + baseUrl: "https://chat.example.com", + dmPolicy: "pairing", + }, + }, +} +``` + +## 네이티브 슬래시 명령 + +네이티브 슬래시 명령은 옵트인 방식입니다. 활성화하면 OpenClaw가 Mattermost API를 통해 `oc_*` 슬래시 명령을 등록하고 +gateway HTTP 서버에서 콜백 POST를 수신합니다. + +```json5 +{ + channels: { + mattermost: { + commands: { + native: true, + nativeSkills: true, + callbackPath: "/api/channels/mattermost/command", + // Mattermost가 gateway에 직접 도달할 수 없을 때 사용합니다(리버스 프록시/공개 URL). + callbackUrl: "https://gateway.example.com/api/channels/mattermost/command", + }, + }, + }, +} +``` + +참고: + +- `native: "auto"`는 Mattermost에서 기본적으로 비활성화됩니다. 활성화하려면 `native: true`로 설정하세요. +- `callbackUrl`이 생략되면 OpenClaw는 gateway host/port + `callbackPath`에서 이를 파생합니다. +- 멀티 계정 설정에서는 `commands`를 최상위에 두거나 + `channels.mattermost.accounts..commands` 아래에 둘 수 있습니다(계정 값이 최상위 필드를 재정의함). +- 명령 콜백은 OpenClaw가 `oc_*` 명령을 등록할 때 Mattermost가 반환한 + 명령별 토큰으로 검증됩니다. +- 슬래시 콜백은 등록 실패, 부분적 시작, 또는 + 콜백 토큰이 등록된 명령 중 어느 것과도 일치하지 않으면 차단된 상태로 실패합니다. +- 도달 가능성 요구 사항: 콜백 엔드포인트는 Mattermost 서버에서 도달 가능해야 합니다. + - Mattermost가 OpenClaw와 같은 호스트/네트워크 네임스페이스에서 실행되지 않는 한 `callbackUrl`을 `localhost`로 설정하지 마세요. + - 해당 URL이 `/api/channels/mattermost/command`를 OpenClaw로 리버스 프록시하지 않는 한 `callbackUrl`을 Mattermost 기본 URL로 설정하지 마세요. + - 빠른 확인 방법은 `curl https:///api/channels/mattermost/command`입니다. GET 요청은 `404`가 아니라 OpenClaw에서 `405 Method Not Allowed`를 반환해야 합니다. +- Mattermost 송신 허용 목록 요구 사항: + - 콜백 대상이 private/tailnet/internal 주소를 가리키는 경우, Mattermost + `ServiceSettings.AllowedUntrustedInternalConnections`에 콜백 호스트/도메인을 포함하도록 설정하세요. + - 전체 URL이 아니라 호스트/도메인 항목을 사용하세요. + - 올바름: `gateway.tailnet-name.ts.net` + - 잘못됨: `https://gateway.tailnet-name.ts.net` + +## 환경 변수(기본 계정) + +환경 변수를 선호한다면 gateway 호스트에 다음을 설정하세요. + +- `MATTERMOST_BOT_TOKEN=...` +- `MATTERMOST_URL=https://chat.example.com` + +환경 변수는 **기본** 계정(`default`)에만 적용됩니다. 다른 계정은 반드시 config 값을 사용해야 합니다. + +## 채팅 모드 + +Mattermost는 DM에 자동으로 응답합니다. 채널 동작은 `chatmode`로 제어됩니다. + +- `oncall`(기본값): 채널에서 @멘션될 때만 응답합니다. +- `onmessage`: 모든 채널 메시지에 응답합니다. +- `onchar`: 메시지가 트리거 접두사로 시작할 때 응답합니다. + +구성 예시: + +```json5 +{ + channels: { + mattermost: { + chatmode: "onchar", + oncharPrefixes: [">", "!"], + }, + }, +} +``` + +참고: + +- `onchar`는 명시적인 @멘션에도 계속 응답합니다. +- `channels.mattermost.requireMention`은 레거시 구성에서 존중되지만 `chatmode` 사용이 권장됩니다. + +## 스레딩 및 세션 + +채널 및 그룹 응답을 메인 채널에 유지할지, 트리거한 게시물 아래 스레드를 시작할지 제어하려면 +`channels.mattermost.replyToMode`를 사용하세요. + +- `off`(기본값): 수신 게시물이 이미 스레드 안에 있는 경우에만 스레드에서 응답합니다. +- `first`: 최상위 채널/그룹 게시물의 경우 해당 게시물 아래에 스레드를 시작하고 + 대화를 스레드 범위 세션으로 라우팅합니다. +- `all`: 현재 Mattermost에서는 `first`와 동일하게 동작합니다. +- 다이렉트 메시지는 이 설정을 무시하고 비스레드 상태를 유지합니다. + +구성 예시: + +```json5 +{ + channels: { + mattermost: { + replyToMode: "all", + }, + }, +} +``` + +참고: + +- 스레드 범위 세션은 트리거한 게시물 ID를 스레드 루트로 사용합니다. +- `first`와 `all`은 현재 동일합니다. Mattermost에 스레드 루트가 생기면 + 후속 청크와 미디어가 모두 그 동일한 스레드에서 계속되기 때문입니다. + +## 접근 제어(DM) + +- 기본값: `channels.mattermost.dmPolicy = "pairing"`(알 수 없는 발신자는 페어링 코드를 받음). +- 승인 방법: + - `openclaw pairing list mattermost` + - `openclaw pairing approve mattermost ` +- 공개 DM: `channels.mattermost.dmPolicy="open"`과 `channels.mattermost.allowFrom=["*"]`. + +## 채널(그룹) + +- 기본값: `channels.mattermost.groupPolicy = "allowlist"`(멘션 게이트 방식). +- `channels.mattermost.groupAllowFrom`으로 발신자를 허용 목록에 추가합니다(사용자 ID 권장). +- 채널별 멘션 재정의는 `channels.mattermost.groups..requireMention` + 또는 기본값용 `channels.mattermost.groups["*"].requireMention` 아래에 있습니다. +- `@username` 일치는 변경 가능하며 `channels.mattermost.dangerouslyAllowNameMatching: true`일 때만 활성화됩니다. +- 공개 채널: `channels.mattermost.groupPolicy="open"`(멘션 게이트 방식). +- 런타임 참고: `channels.mattermost`가 완전히 없으면, 런타임은 그룹 검사에 `groupPolicy="allowlist"`로 대체됩니다(`channels.defaults.groupPolicy`가 설정되어 있어도 마찬가지임). + +예시: + +```json5 +{ + channels: { + mattermost: { + groupPolicy: "open", + groups: { + "*": { requireMention: true }, + "team-channel-id": { requireMention: false }, + }, + }, + }, +} +``` + +## 아웃바운드 전달 대상 + +`openclaw message send` 또는 cron/웹훅에서 다음 대상 형식을 사용하세요. + +- 채널에는 `channel:` +- DM에는 `user:` +- DM에는 `@username`(Mattermost API를 통해 확인됨) + +접두사 없는 불투명 ID(예: `64ifufp...`)는 Mattermost에서 **모호**합니다(사용자 ID인지 채널 ID인지 구분 안 됨). + +OpenClaw는 이를 **사용자 우선**으로 해석합니다. + +- ID가 사용자로 존재하면(`GET /api/v4/users/` 성공), OpenClaw는 `/api/v4/channels/direct`를 통해 다이렉트 채널을 확인하여 **DM**을 전송합니다. +- 그렇지 않으면 해당 ID는 **채널 ID**로 처리됩니다. + +결정적인 동작이 필요하면 항상 명시적 접두사(`user:` / `channel:`)를 사용하세요. + +## DM 채널 재시도 + +OpenClaw가 Mattermost DM 대상으로 전송할 때 먼저 다이렉트 채널을 확인해야 하는 경우, +기본적으로 일시적인 다이렉트 채널 생성 실패를 재시도합니다. + +Mattermost plugin 전체에 대해 그 동작을 전역 조정하려면 `channels.mattermost.dmChannelRetry`를 사용하고, +특정 계정 하나에 대해서만 조정하려면 `channels.mattermost.accounts..dmChannelRetry`를 사용하세요. + +```json5 +{ + channels: { + mattermost: { + dmChannelRetry: { + maxRetries: 3, + initialDelayMs: 1000, + maxDelayMs: 10000, + timeoutMs: 30000, + }, + }, + }, +} +``` + +참고: + +- 이는 모든 Mattermost API 호출이 아니라 DM 채널 생성(`/api/v4/channels/direct`)에만 적용됩니다. +- 재시도는 rate limit, 5xx 응답, 네트워크 오류 또는 타임아웃 오류 같은 일시적 실패에 적용됩니다. +- `429`를 제외한 4xx 클라이언트 오류는 영구 오류로 처리되며 재시도하지 않습니다. + +## 반응(message 도구) + +- `channel=mattermost`와 함께 `message action=react`를 사용하세요. +- `messageId`는 Mattermost 게시물 ID입니다. +- `emoji`는 `thumbsup` 또는 `:+1:` 같은 이름을 받습니다(콜론은 선택 사항). +- 반응을 제거하려면 `remove=true`(boolean)를 설정하세요. +- 반응 추가/제거 이벤트는 라우팅된 agent 세션으로 시스템 이벤트로 전달됩니다. + +예시: + +``` +message action=react channel=mattermost target=channel: messageId= emoji=thumbsup +message action=react channel=mattermost target=channel: messageId= emoji=thumbsup remove=true +``` + +구성: + +- `channels.mattermost.actions.reactions`: 반응 동작 활성화/비활성화(기본값 true). +- 계정별 재정의: `channels.mattermost.accounts..actions.reactions`. + +## 인터랙티브 버튼(message 도구) + +클릭 가능한 버튼이 포함된 메시지를 보냅니다. 사용자가 버튼을 클릭하면 에이전트가 +선택값을 받아 응답할 수 있습니다. + +채널 기능에 `inlineButtons`를 추가하여 버튼을 활성화하세요. + +```json5 +{ + channels: { + mattermost: { + capabilities: ["inlineButtons"], + }, + }, +} +``` + +`buttons` 파라미터와 함께 `message action=send`를 사용하세요. 버튼은 2차원 배열(버튼 행)입니다. + +``` +message action=send channel=mattermost target=channel: buttons=[[{"text":"Yes","callback_data":"yes"},{"text":"No","callback_data":"no"}]] +``` + +버튼 필드: + +- `text`(필수): 표시 레이블. +- `callback_data`(필수): 클릭 시 다시 전송되는 값(action ID로 사용됨). +- `style`(선택 사항): `"default"`, `"primary"`, 또는 `"danger"`. + +사용자가 버튼을 클릭하면: + +1. 모든 버튼이 확인 줄로 교체됩니다(예: "✓ **Yes** selected by @user"). +2. 에이전트는 선택 내용을 수신 메시지로 받아 응답합니다. + +참고: + +- 버튼 콜백은 HMAC-SHA256 검증을 사용합니다(자동, 별도 구성 불필요). +- Mattermost는 API 응답에서 callback data를 제거합니다(보안 기능). 따라서 클릭 시 모든 버튼이 제거되며, 일부만 제거하는 것은 불가능합니다. +- 하이픈 또는 밑줄이 포함된 action ID는 자동으로 정리됩니다 + (Mattermost 라우팅 제한). + +구성: + +- `channels.mattermost.capabilities`: 기능 문자열 배열. 에이전트 시스템 프롬프트에서 + 버튼 도구 설명을 활성화하려면 `"inlineButtons"`를 추가하세요. +- `channels.mattermost.interactions.callbackBaseUrl`: 버튼 콜백용 선택적 외부 기본 URL + (예: `https://gateway.example.com`). Mattermost가 바인드 host의 gateway에 직접 + 도달할 수 없을 때 사용하세요. +- 멀티 계정 설정에서는 동일한 필드를 + `channels.mattermost.accounts..interactions.callbackBaseUrl` 아래에도 설정할 수 있습니다. +- `interactions.callbackBaseUrl`이 생략되면 OpenClaw는 + `gateway.customBindHost` + `gateway.port`에서 콜백 URL을 파생한 다음, `http://localhost:`로 대체합니다. +- 도달 가능성 규칙: 버튼 콜백 URL은 Mattermost 서버에서 도달 가능해야 합니다. + Mattermost와 OpenClaw가 같은 호스트/네트워크 네임스페이스에서 실행될 때만 `localhost`가 작동합니다. +- 콜백 대상이 private/tailnet/internal인 경우, 해당 host/domain을 Mattermost + `ServiceSettings.AllowedUntrustedInternalConnections`에 추가하세요. + +### Direct API 통합(외부 스크립트) + +외부 스크립트와 웹훅은 에이전트의 `message` 도구를 거치지 않고 +Mattermost REST API를 통해 직접 버튼을 게시할 수 있습니다. 가능하면 extension의 `buildButtonAttachments()`를 사용하세요. +원시 JSON을 게시하는 경우 다음 규칙을 따르세요. + +**페이로드 구조:** + +```json5 +{ + channel_id: "", + message: "Choose an option:", + props: { + attachments: [ + { + actions: [ + { + id: "mybutton01", // 영숫자만 허용 — 아래 참조 + type: "button", // 필수, 없으면 클릭이 조용히 무시됨 + name: "Approve", // 표시 레이블 + style: "primary", // 선택 사항: "default", "primary", "danger" + integration: { + url: "https://gateway.example.com/mattermost/interactions/default", + context: { + action_id: "mybutton01", // 버튼 id와 일치해야 함(이름 조회용) + action: "approve", + // ... 사용자 정의 필드 ... + _token: "", // 아래 HMAC 섹션 참조 + }, + }, + }, + ], + }, + ], + }, +} +``` + +**중요 규칙:** + +1. attachment는 최상위 `attachments`가 아니라 `props.attachments`에 넣어야 합니다(그렇지 않으면 조용히 무시됨). +2. 모든 action에는 `type: "button"`이 필요합니다. 없으면 클릭이 조용히 삼켜집니다. +3. 모든 action에는 `id` 필드가 필요합니다. Mattermost는 ID가 없는 action을 무시합니다. +4. Action `id`는 **영숫자만** 허용됩니다(`[a-zA-Z0-9]`). 하이픈과 밑줄은 + Mattermost의 서버 측 action 라우팅을 깨뜨립니다(`404` 반환). 사용 전에 제거하세요. +5. 확인 메시지에 원시 ID 대신 버튼 이름(예: "Approve")이 표시되도록 + `context.action_id`는 버튼의 `id`와 일치해야 합니다. +6. `context.action_id`는 필수입니다. 없으면 interaction 핸들러가 `400`을 반환합니다. + +**HMAC 토큰 생성:** + +gateway는 HMAC-SHA256으로 버튼 클릭을 검증합니다. 외부 스크립트는 gateway의 검증 로직과 +일치하는 토큰을 생성해야 합니다. + +1. 봇 토큰에서 secret을 파생합니다: + `HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)` +2. `_token`을 제외한 모든 필드로 context 객체를 만듭니다. +3. **정렬된 키**와 **공백 없음**으로 직렬화합니다(gateway는 정렬된 키로 `JSON.stringify`를 사용하며, + 이는 압축된 출력을 생성합니다). +4. 서명: `HMAC-SHA256(key=secret, data=serializedContext)` +5. 결과 hex digest를 context의 `_token`으로 추가합니다. + +Python 예시: + +```python +import hmac, hashlib, json + +secret = hmac.new( + b"openclaw-mattermost-interactions", + bot_token.encode(), hashlib.sha256 +).hexdigest() + +ctx = {"action_id": "mybutton01", "action": "approve"} +payload = json.dumps(ctx, sort_keys=True, separators=(",", ":")) +token = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest() + +context = {**ctx, "_token": token} +``` + +일반적인 HMAC 함정: + +- Python의 `json.dumps`는 기본적으로 공백을 추가합니다(`{"key": "val"}`). JavaScript의 압축 출력(`{"key":"val"}`)과 맞추려면 + `separators=(",", ":")`를 사용하세요. +- 항상 **모든** context 필드(`_token` 제외)에 서명하세요. gateway는 `_token`을 제거한 뒤 + 남은 모든 것을 서명합니다. 일부만 서명하면 조용한 검증 실패가 발생합니다. +- `sort_keys=True`를 사용하세요. gateway는 서명 전에 키를 정렬하고, Mattermost는 + 페이로드를 저장할 때 context 필드를 재정렬할 수 있습니다. +- secret은 랜덤 바이트가 아니라 봇 토큰에서 파생하세요(결정적). secret은 + 버튼을 생성하는 프로세스와 이를 검증하는 gateway에서 동일해야 합니다. + +## 디렉터리 어댑터 + +Mattermost plugin에는 Mattermost API를 통해 채널 및 사용자 이름을 확인하는 +디렉터리 어댑터가 포함되어 있습니다. 이를 통해 +`openclaw message send` 및 cron/웹훅 전달에서 `#channel-name`과 `@username` 대상을 사용할 수 있습니다. + +별도 구성은 필요하지 않습니다. 어댑터는 계정 config의 봇 토큰을 사용합니다. + +## 멀티 계정 + +Mattermost는 `channels.mattermost.accounts` 아래에서 여러 계정을 지원합니다. + +```json5 +{ + channels: { + mattermost: { + accounts: { + default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" }, + alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" }, + }, + }, + }, +} +``` + +## 문제 해결 + +- 채널에서 응답이 없음: 봇이 채널에 들어가 있는지 확인하고 멘션하세요(oncall), 트리거 접두사를 사용하세요(onchar), 또는 `chatmode: "onmessage"`로 설정하세요. +- 인증 오류: 봇 토큰, 기본 URL, 계정 활성화 여부를 확인하세요. +- 멀티 계정 문제: 환경 변수는 `default` 계정에만 적용됩니다. +- 네이티브 슬래시 명령이 `Unauthorized: invalid command token.`을 반환함: OpenClaw가 + 콜백 토큰을 수락하지 않았습니다. 일반적인 원인: + - 슬래시 명령 등록이 실패했거나 시작 시 일부만 완료됨 + - 콜백이 잘못된 gateway/계정으로 도달함 + - Mattermost에 이전 콜백 대상을 가리키는 오래된 명령이 여전히 남아 있음 + - gateway가 슬래시 명령을 다시 활성화하지 않고 재시작됨 +- 네이티브 슬래시 명령이 작동을 멈추면 로그에서 + `mattermost: failed to register slash commands` 또는 + `mattermost: native slash commands enabled but no commands could be registered` + 를 확인하세요. +- `callbackUrl`이 생략되었고 로그에서 콜백이 + `http://127.0.0.1:18789/...`로 확인되었다는 경고가 나오면, 그 URL은 + Mattermost가 OpenClaw와 같은 호스트/네트워크 네임스페이스에서 실행될 때만 + 도달 가능할 가능성이 높습니다. 대신 명시적으로 외부에서 도달 가능한 + `commands.callbackUrl`을 설정하세요. +- 버튼이 흰색 상자로 보임: 에이전트가 잘못된 버튼 데이터를 보내고 있을 수 있습니다. 각 버튼에 `text`와 `callback_data` 필드가 모두 있는지 확인하세요. +- 버튼은 렌더링되지만 클릭해도 아무 일도 일어나지 않음: Mattermost 서버 config의 `AllowedUntrustedInternalConnections`에 `127.0.0.1 localhost`가 포함되어 있는지, 그리고 ServiceSettings에서 `EnablePostActionIntegration`이 `true`인지 확인하세요. +- 버튼 클릭 시 404 반환: 버튼 `id`에 하이픈 또는 밑줄이 포함되어 있을 가능성이 높습니다. Mattermost의 action 라우터는 영숫자가 아닌 ID에서 깨집니다. `[a-zA-Z0-9]`만 사용하세요. +- Gateway 로그에 `invalid _token`이 표시됨: HMAC 불일치입니다. 모든 context 필드(일부가 아님)에 서명하는지, 키를 정렬하는지, 압축 JSON(공백 없음)을 사용하는지 확인하세요. 위의 HMAC 섹션을 참조하세요. +- Gateway 로그에 `missing _token in context`가 표시됨: 버튼 context에 `_token` 필드가 없습니다. integration 페이로드를 만들 때 이를 포함했는지 확인하세요. +- 확인 메시지에 버튼 이름 대신 원시 ID가 표시됨: `context.action_id`가 버튼의 `id`와 일치하지 않습니다. 둘 다 동일한 정리된 값으로 설정하세요. +- 에이전트가 버튼을 모름: Mattermost 채널 config에 `capabilities: ["inlineButtons"]`를 추가하세요. + +## 관련 항목 + +- [Channels Overview](/channels) — 지원되는 모든 채널 +- [Pairing](/channels/pairing) — DM 인증 및 페어링 흐름 +- [Groups](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [Channel Routing](/channels/channel-routing) — 메시지 세션 라우팅 +- [Security](/gateway/security) — 접근 모델 및 보안 강화 diff --git a/docs/ko/channels/msteams.md b/docs/ko/channels/msteams.md new file mode 100644 index 000000000..534444907 --- /dev/null +++ b/docs/ko/channels/msteams.md @@ -0,0 +1,812 @@ +--- +read_when: + - Microsoft Teams 채널 기능을 작업할 때 +summary: Microsoft Teams 봇 지원 상태, 기능 및 구성 +title: Microsoft Teams +x-i18n: + generated_at: "2026-04-05T12:37:02Z" + model: gpt-5.4 + provider: openai + source_hash: 99fc6e136893ec65dc85d3bc0c0d92134069a2f3b8cb4fcf66c14674399b3eaf + source_path: channels/msteams.md + workflow: 15 +--- + +# Microsoft Teams + +> "이곳에 들어오는 자여, 모든 희망을 버려라." + +업데이트: 2026-01-21 + +상태: 텍스트 + DM 첨부파일이 지원되며, 채널/그룹 파일 전송에는 `sharePointSiteId` + Graph 권한이 필요합니다([그룹 채팅에서 파일 보내기](#sending-files-in-group-chats) 참조). Polls는 Adaptive Cards를 통해 전송됩니다. 메시지 작업은 파일 우선 전송을 위한 명시적 `upload-file`을 노출합니다. + +## 번들 플러그인 + +Microsoft Teams는 현재 OpenClaw 릴리스에 번들 플러그인으로 포함되어 있으므로, +일반적인 패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드이거나 번들 Teams가 제외된 사용자 지정 설치를 사용하는 경우, +수동으로 설치하세요. + +```bash +openclaw plugins install @openclaw/msteams +``` + +로컬 체크아웃(깃 리포지토리에서 실행할 때): + +```bash +openclaw plugins install ./path/to/local/msteams-plugin +``` + +자세한 내용: [Plugins](/tools/plugin) + +## 빠른 설정(초보자용) + +1. Microsoft Teams 플러그인을 사용할 수 있는지 확인합니다. + - 현재 패키지형 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/사용자 지정 설치에서는 위 명령으로 수동 추가할 수 있습니다. +2. **Azure Bot**(App ID + client secret + tenant ID)을 생성합니다. +3. 해당 자격 증명으로 OpenClaw를 구성합니다. +4. 공개 URL 또는 터널을 통해 `/api/messages`(기본 포트 3978)를 노출합니다. +5. Teams 앱 패키지를 설치하고 게이트웨이를 시작합니다. + +최소 config: + +```json5 +{ + channels: { + msteams: { + enabled: true, + appId: "", + appPassword: "", + tenantId: "", + webhook: { port: 3978, path: "/api/messages" }, + }, + }, +} +``` + +참고: 그룹 채팅은 기본적으로 차단됩니다(`channels.msteams.groupPolicy: "allowlist"`). 그룹 응답을 허용하려면 `channels.msteams.groupAllowFrom`을 설정하세요(또는 어떤 멤버든 허용하되 기본적으로 멘션 게이팅이 적용되도록 `groupPolicy: "open"`을 사용하세요). + +## 목표 + +- Teams DM, 그룹 채팅 또는 채널을 통해 OpenClaw와 대화합니다. +- 라우팅을 결정적으로 유지합니다. 응답은 항상 수신된 채널로 돌아갑니다. +- 안전한 채널 동작을 기본값으로 사용합니다(별도 구성하지 않으면 멘션 필요). + +## config 쓰기 + +기본적으로 Microsoft Teams는 `/config set|unset`에 의해 트리거된 config 업데이트 쓰기를 허용합니다(`commands.config: true` 필요). + +다음으로 비활성화할 수 있습니다. + +```json5 +{ + channels: { msteams: { configWrites: false } }, +} +``` + +## 액세스 제어(DM + 그룹) + +**DM 액세스** + +- 기본값: `channels.msteams.dmPolicy = "pairing"`입니다. 알 수 없는 발신자는 승인될 때까지 무시됩니다. +- `channels.msteams.allowFrom`에는 안정적인 AAD object ID를 사용하는 것이 좋습니다. +- UPN/표시 이름은 변경 가능하므로 직접 일치는 기본적으로 비활성화되어 있으며 `channels.msteams.dangerouslyAllowNameMatching: true`일 때만 활성화됩니다. +- 자격 증명이 허용되면 마법사가 Microsoft Graph를 통해 이름을 ID로 확인할 수 있습니다. + +**그룹 액세스** + +- 기본값: `channels.msteams.groupPolicy = "allowlist"`입니다(`groupAllowFrom`을 추가하지 않으면 차단됨). 설정되지 않았을 때 기본값을 재정의하려면 `channels.defaults.groupPolicy`를 사용하세요. +- `channels.msteams.groupAllowFrom`은 그룹 채팅/채널에서 어떤 발신자가 트리거할 수 있는지 제어합니다(`channels.msteams.allowFrom`으로 대체됨). +- 어떤 멤버든 허용하려면 `groupPolicy: "open"`을 설정하세요(그래도 기본적으로 멘션 게이팅 적용). +- **어떤 채널도 허용하지 않으려면** `channels.msteams.groupPolicy: "disabled"`를 설정하세요. + +예시: + +```json5 +{ + channels: { + msteams: { + groupPolicy: "allowlist", + groupAllowFrom: ["user@org.com"], + }, + }, +} +``` + +**Teams + 채널 허용 목록** + +- `channels.msteams.teams` 아래에 팀과 채널을 나열해 그룹/채널 응답 범위를 지정합니다. +- 키에는 안정적인 팀 ID와 채널 conversation ID를 사용해야 합니다. +- `groupPolicy="allowlist"`이고 teams 허용 목록이 있으면 나열된 팀/채널만 허용됩니다(멘션 게이팅 적용). +- 구성 마법사는 `Team/Channel` 항목을 받아 대신 저장해 줍니다. +- 시작 시 OpenClaw는 팀/채널 및 사용자 허용 목록 이름을 ID로 확인하고(Graph 권한이 허용될 때) + 매핑을 로그에 남깁니다. 확인되지 않은 팀/채널 이름은 입력된 그대로 유지되지만, `channels.msteams.dangerouslyAllowNameMatching: true`가 활성화되지 않는 한 기본적으로 라우팅에서는 무시됩니다. + +예시: + +```json5 +{ + channels: { + msteams: { + groupPolicy: "allowlist", + teams: { + "My Team": { + channels: { + General: { requireMention: true }, + }, + }, + }, + }, + }, +} +``` + +## 작동 방식 + +1. Microsoft Teams 플러그인을 사용할 수 있는지 확인합니다. + - 현재 패키지형 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/사용자 지정 설치에서는 위 명령으로 수동 추가할 수 있습니다. +2. **Azure Bot**(App ID + secret + tenant ID)을 생성합니다. +3. 봇을 참조하고 아래 RSC 권한을 포함하는 **Teams 앱 패키지**를 빌드합니다. +4. Teams 앱을 팀에 업로드/설치합니다(DM용 개인 범위도 가능). +5. `~/.openclaw/openclaw.json`(또는 env vars)에서 `msteams`를 구성하고 게이트웨이를 시작합니다. +6. 게이트웨이는 기본적으로 `/api/messages`에서 Bot Framework 웹훅 트래픽을 수신 대기합니다. + +## Azure Bot 설정(사전 요구 사항) + +OpenClaw를 구성하기 전에 Azure Bot 리소스를 생성해야 합니다. + +### 1단계: Azure Bot 생성 + +1. [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot)으로 이동합니다. +2. **Basics** 탭을 채웁니다. + + | Field | Value | + | ------------------ | -------------------------------------------------------- | + | **Bot handle** | 봇 이름(예: `openclaw-msteams`)이며 고유해야 함 | + | **Subscription** | Azure 구독 선택 | + | **Resource group** | 새로 만들거나 기존 것 사용 | + | **Pricing tier** | 개발/테스트용 **Free** | + | **Type of App** | **Single Tenant**(권장 - 아래 참고 참조) | + | **Creation type** | **Create new Microsoft App ID** | + +> **지원 중단 안내:** 새 멀티 테넌트 봇 생성은 2025-07-31 이후 지원 중단되었습니다. 새 봇에는 **Single Tenant**를 사용하세요. + +3. **Review + create** → **Create**를 클릭합니다(약 1~2분 대기) + +### 2단계: 자격 증명 가져오기 + +1. Azure Bot 리소스로 이동 → **Configuration** +2. **Microsoft App ID**를 복사 → 이것이 `appId`입니다 +3. **Manage Password**를 클릭 → App Registration으로 이동 +4. **Certificates & secrets** → **New client secret** → **Value**를 복사 → 이것이 `appPassword`입니다 +5. **Overview**로 이동 → **Directory (tenant) ID**를 복사 → 이것이 `tenantId`입니다 + +### 3단계: 메시징 엔드포인트 구성 + +1. Azure Bot → **Configuration** +2. **Messaging endpoint**를 웹훅 URL로 설정합니다. + - 프로덕션: `https://your-domain.com/api/messages` + - 로컬 개발: 터널 사용([로컬 개발](#local-development-tunneling) 참조) + +### 4단계: Teams 채널 활성화 + +1. Azure Bot → **Channels** +2. **Microsoft Teams** → Configure → Save를 클릭 +3. 서비스 약관에 동의합니다 + +## 로컬 개발(터널링) + +Teams는 `localhost`에 접근할 수 없습니다. 로컬 개발에는 터널을 사용하세요. + +**옵션 A: ngrok** + +```bash +ngrok http 3978 +# https URL을 복사합니다. 예: https://abc123.ngrok.io +# 메시징 엔드포인트를 다음으로 설정: https://abc123.ngrok.io/api/messages +``` + +**옵션 B: Tailscale Funnel** + +```bash +tailscale funnel 3978 +# Tailscale funnel URL을 메시징 엔드포인트로 사용하세요 +``` + +## Teams Developer Portal(대안) + +manifest ZIP를 수동 생성하는 대신 [Teams Developer Portal](https://dev.teams.microsoft.com/apps)을 사용할 수 있습니다. + +1. **+ New app** 클릭 +2. 기본 정보(이름, 설명, 개발자 정보) 입력 +3. **App features** → **Bot**으로 이동 +4. **Enter a bot ID manually**를 선택하고 Azure Bot App ID를 붙여넣기 +5. 범위 선택: **Personal**, **Team**, **Group Chat** +6. **Distribute** → **Download app package** 클릭 +7. Teams에서 **Apps** → **Manage your apps** → **Upload a custom app** → ZIP 선택 + +이 방식은 JSON manifest를 직접 편집하는 것보다 더 쉬운 경우가 많습니다. + +## 봇 테스트 + +**옵션 A: Azure Web Chat(먼저 웹훅 확인)** + +1. Azure Portal → Azure Bot 리소스 → **Test in Web Chat** +2. 메시지를 전송하면 응답이 보여야 합니다 +3. 이를 통해 Teams 설정 전에 웹훅 엔드포인트가 동작하는지 확인할 수 있습니다 + +**옵션 B: Teams(앱 설치 후)** + +1. Teams 앱 설치(사이드로드 또는 조직 카탈로그) +2. Teams에서 봇을 찾고 DM 전송 +3. 게이트웨이 로그에서 들어오는 activity 확인 + +## 설정(최소 텍스트 전용) + +1. **Microsoft Teams 플러그인을 사용할 수 있는지 확인** + - 현재 패키지형 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/사용자 지정 설치에서는 수동으로 추가할 수 있습니다. + - npm에서: `openclaw plugins install @openclaw/msteams` + - 로컬 체크아웃에서: `openclaw plugins install ./path/to/local/msteams-plugin` + +2. **봇 등록** + - Azure Bot을 생성하고(위 참조) 다음을 기록합니다. + - App ID + - Client secret(App password) + - Tenant ID(single-tenant) + +3. **Teams 앱 manifest** + - `botId = `인 `bot` 항목을 포함합니다. + - 범위: `personal`, `team`, `groupChat`. + - `supportsFiles: true`(개인 범위 파일 처리에 필요). + - RSC 권한(아래)을 추가합니다. + - 아이콘 생성: `outline.png` (32x32), `color.png` (192x192). + - 세 파일을 함께 zip으로 묶습니다: `manifest.json`, `outline.png`, `color.png`. + +4. **OpenClaw 구성** + + ```json5 + { + channels: { + msteams: { + enabled: true, + appId: "", + appPassword: "", + tenantId: "", + webhook: { port: 3978, path: "/api/messages" }, + }, + }, + } + ``` + + config 키 대신 환경 변수를 사용할 수도 있습니다. + - `MSTEAMS_APP_ID` + - `MSTEAMS_APP_PASSWORD` + - `MSTEAMS_TENANT_ID` + +5. **봇 엔드포인트** + - Azure Bot Messaging Endpoint를 다음으로 설정합니다. + - `https://:3978/api/messages`(또는 선택한 path/port). + +6. **게이트웨이 실행** + - 번들 또는 수동 설치된 플러그인을 사용할 수 있고 자격 증명이 포함된 `msteams` config가 존재하면 Teams 채널이 자동으로 시작됩니다. + +## 멤버 정보 작업 + +OpenClaw는 Microsoft Teams용 Graph 기반 `member-info` 작업을 노출하므로 에이전트와 자동화가 Microsoft Graph에서 직접 채널 멤버 정보(표시 이름, 이메일, 역할)를 확인할 수 있습니다. + +요구 사항: + +- `Member.Read.Group` RSC 권한(권장 manifest에 이미 포함됨) +- 팀 간 조회용: 관리자 동의가 포함된 `User.Read.All` Graph Application 권한 + +이 작업은 `channels.msteams.actions.memberInfo`로 제어됩니다(기본값: Graph 자격 증명을 사용할 수 있을 때 활성화). + +## 기록 컨텍스트 + +- `channels.msteams.historyLimit`은 프롬프트에 포함할 최근 채널/그룹 메시지 수를 제어합니다. +- `messages.groupChat.historyLimit`로 대체됩니다. `0`으로 설정하면 비활성화됩니다(기본값 50). +- 가져온 스레드 기록은 발신자 허용 목록(`allowFrom` / `groupAllowFrom`)으로 필터링되므로 스레드 컨텍스트 시드는 허용된 발신자의 메시지만 포함합니다. +- 인용된 첨부파일 컨텍스트(`ReplyTo*`에서 파생된 Teams reply HTML)는 현재 수신된 그대로 전달됩니다. +- 즉, 허용 목록은 누가 에이전트를 트리거할 수 있는지를 제어하며, 현재는 특정 보조 컨텍스트 경로만 필터링됩니다. +- DM 기록은 `channels.msteams.dmHistoryLimit`(사용자 턴 수)로 제한할 수 있습니다. 사용자별 재정의: `channels.msteams.dms[""].historyLimit`. + +## 현재 Teams RSC 권한(Manifest) + +다음은 Teams 앱 manifest의 **기존 resourceSpecific 권한**입니다. 이는 앱이 설치된 팀/채팅 내부에서만 적용됩니다. + +**채널(팀 범위)의 경우:** + +- `ChannelMessage.Read.Group` (Application) - @멘션 없이 모든 채널 메시지 수신 +- `ChannelMessage.Send.Group` (Application) +- `Member.Read.Group` (Application) +- `Owner.Read.Group` (Application) +- `ChannelSettings.Read.Group` (Application) +- `TeamMember.Read.Group` (Application) +- `TeamSettings.Read.Group` (Application) + +**그룹 채팅의 경우:** + +- `ChatMessage.Read.Chat` (Application) - @멘션 없이 모든 그룹 채팅 메시지 수신 + +## 예시 Teams Manifest(일부 가림) + +필수 필드를 포함한 최소한의 유효한 예시입니다. ID와 URL을 바꿔 사용하세요. + +```json5 +{ + $schema: "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json", + manifestVersion: "1.23", + version: "1.0.0", + id: "00000000-0000-0000-0000-000000000000", + name: { short: "OpenClaw" }, + developer: { + name: "Your Org", + websiteUrl: "https://example.com", + privacyUrl: "https://example.com/privacy", + termsOfUseUrl: "https://example.com/terms", + }, + description: { short: "OpenClaw in Teams", full: "OpenClaw in Teams" }, + icons: { outline: "outline.png", color: "color.png" }, + accentColor: "#5B6DEF", + bots: [ + { + botId: "11111111-1111-1111-1111-111111111111", + scopes: ["personal", "team", "groupChat"], + isNotificationOnly: false, + supportsCalling: false, + supportsVideo: false, + supportsFiles: true, + }, + ], + webApplicationInfo: { + id: "11111111-1111-1111-1111-111111111111", + }, + authorization: { + permissions: { + resourceSpecific: [ + { name: "ChannelMessage.Read.Group", type: "Application" }, + { name: "ChannelMessage.Send.Group", type: "Application" }, + { name: "Member.Read.Group", type: "Application" }, + { name: "Owner.Read.Group", type: "Application" }, + { name: "ChannelSettings.Read.Group", type: "Application" }, + { name: "TeamMember.Read.Group", type: "Application" }, + { name: "TeamSettings.Read.Group", type: "Application" }, + { name: "ChatMessage.Read.Chat", type: "Application" }, + ], + }, + }, +} +``` + +### Manifest 주의 사항(필수 필드) + +- `bots[].botId`는 Azure Bot App ID와 **반드시** 일치해야 합니다. +- `webApplicationInfo.id`는 Azure Bot App ID와 **반드시** 일치해야 합니다. +- `bots[].scopes`에는 사용할 표면(`personal`, `team`, `groupChat`)이 포함되어야 합니다. +- `bots[].supportsFiles: true`는 개인 범위 파일 처리에 필요합니다. +- 채널 트래픽을 원한다면 `authorization.permissions.resourceSpecific`에 채널 읽기/보내기가 포함되어야 합니다. + +### 기존 앱 업데이트 + +이미 설치된 Teams 앱을 업데이트하려면(예: RSC 권한 추가): + +1. 새 설정으로 `manifest.json`을 업데이트합니다 +2. **`version` 필드를 증가**시킵니다(예: `1.0.0` → `1.1.0`) +3. 아이콘과 함께 manifest를 **다시 zip으로 묶습니다**(`manifest.json`, `outline.png`, `color.png`) +4. 새 zip을 업로드합니다. + - **옵션 A (Teams Admin Center):** Teams Admin Center → Teams apps → Manage apps → 앱 찾기 → Upload new version + - **옵션 B (Sideload):** Teams → Apps → Manage your apps → Upload a custom app +5. **팀 채널의 경우:** 새 권한이 적용되도록 각 팀에 앱을 다시 설치합니다 +6. 캐시된 앱 메타데이터를 지우기 위해 Teams를 **완전히 종료 후 다시 실행**합니다(창만 닫지 말 것) + +## 기능: RSC 전용 vs Graph + +### **Teams RSC만 사용**하는 경우(앱 설치됨, Graph API 권한 없음) + +작동함: + +- 채널 메시지 **텍스트** 콘텐츠 읽기. +- 채널 메시지 **텍스트** 콘텐츠 보내기. +- **개인(DM)** 파일 첨부 수신. + +작동하지 않음: + +- 채널/그룹 **이미지 또는 파일 콘텐츠**(payload에는 HTML stub만 포함됨). +- SharePoint/OneDrive에 저장된 첨부파일 다운로드. +- 메시지 기록 읽기(실시간 웹훅 이벤트 범위 초과). + +### **Teams RSC + Microsoft Graph Application 권한** 사용 시 + +추가됨: + +- 호스팅된 콘텐츠 다운로드(메시지에 붙여넣은 이미지). +- SharePoint/OneDrive에 저장된 파일 첨부 다운로드. +- Graph를 통한 채널/채팅 메시지 기록 읽기. + +### RSC vs Graph API + +| Capability | RSC Permissions | Graph API | +| ----------------------- | -------------------- | ----------------------------------- | +| **실시간 메시지** | 예(웹훅을 통해) | 아니요(폴링만 가능) | +| **과거 메시지** | 아니요 | 예(기록 조회 가능) | +| **설정 복잡도** | 앱 manifest만 필요 | 관리자 동의 + 토큰 흐름 필요 | +| **오프라인 동작** | 아니요(실행 중이어야 함) | 예(언제든 조회 가능) | + +**요약:** RSC는 실시간 수신용이고, Graph API는 과거 기록 접근용입니다. 오프라인 동안 놓친 메시지를 따라잡으려면 Graph API와 `ChannelMessage.Read.All`이 필요합니다(관리자 동의 필요). + +## Graph 기반 미디어 + 기록(채널에 필요) + +**채널**에서 이미지/파일이 필요하거나 **메시지 기록**을 가져오려면 Microsoft Graph 권한을 활성화하고 관리자 동의를 받아야 합니다. + +1. Entra ID (Azure AD) **App Registration**에서 Microsoft Graph **Application permissions**를 추가합니다. + - `ChannelMessage.Read.All` (채널 첨부파일 + 기록) + - `Chat.Read.All` 또는 `ChatMessage.Read.All` (그룹 채팅) +2. 테넌트에 대해 **관리자 동의**를 부여합니다. +3. Teams 앱 **manifest 버전**을 올리고, 다시 업로드한 뒤, **Teams에 앱을 재설치**합니다. +4. 캐시된 앱 메타데이터를 지우기 위해 Teams를 **완전히 종료 후 다시 실행**합니다. + +**사용자 멘션을 위한 추가 권한:** 사용자 @멘션은 대화에 있는 사용자에 대해서는 기본적으로 작동합니다. 하지만 현재 대화에 **없는** 사용자를 동적으로 검색하고 멘션하려면 `User.Read.All` (Application) 권한을 추가하고 관리자 동의를 부여하세요. + +## 알려진 제한 사항 + +### 웹훅 시간 초과 + +Teams는 HTTP 웹훅을 통해 메시지를 전달합니다. 처리가 너무 오래 걸리면(예: 느린 LLM 응답) 다음이 발생할 수 있습니다. + +- 게이트웨이 시간 초과 +- Teams가 메시지를 재시도함(중복 발생) +- 응답 누락 + +OpenClaw는 빠르게 응답을 반환하고 적극적으로 답장을 보내 이 문제를 처리하지만, 응답이 매우 느리면 여전히 문제가 발생할 수 있습니다. + +### 서식 + +Teams 마크다운은 Slack이나 Discord보다 제한적입니다. + +- 기본 서식은 작동합니다: **굵게**, _기울임_, `code`, 링크 +- 복잡한 마크다운(표, 중첩 목록)은 올바르게 렌더링되지 않을 수 있습니다 +- Polls 및 임의 카드 전송에는 Adaptive Cards가 지원됩니다(아래 참조) + +## 구성 + +주요 설정(공유 채널 패턴은 `/gateway/configuration` 참조): + +- `channels.msteams.enabled`: 채널 활성화/비활성화. +- `channels.msteams.appId`, `channels.msteams.appPassword`, `channels.msteams.tenantId`: 봇 자격 증명. +- `channels.msteams.webhook.port` (기본값 `3978`) +- `channels.msteams.webhook.path` (기본값 `/api/messages`) +- `channels.msteams.dmPolicy`: `pairing | allowlist | open | disabled` (기본값: pairing) +- `channels.msteams.allowFrom`: DM 허용 목록(AAD object ID 권장). Graph 액세스를 사용할 수 있을 때 마법사가 설정 중 이름을 ID로 확인합니다. +- `channels.msteams.dangerouslyAllowNameMatching`: 변경 가능한 UPN/표시 이름 일치 및 직접 팀/채널 이름 라우팅을 다시 활성화하는 비상 토글. +- `channels.msteams.textChunkLimit`: 아웃바운드 텍스트 청크 크기. +- `channels.msteams.chunkMode`: `length`(기본값) 또는 `newline`으로, 길이 기준 청킹 전에 빈 줄(문단 경계)에서 분할합니다. +- `channels.msteams.mediaAllowHosts`: 인바운드 첨부파일 호스트 허용 목록(기본값은 Microsoft/Teams 도메인). +- `channels.msteams.mediaAuthAllowHosts`: 미디어 재시도 시 Authorization 헤더를 붙일 호스트 허용 목록(기본값은 Graph + Bot Framework 호스트). +- `channels.msteams.requireMention`: 채널/그룹에서 @멘션 필요 여부(기본값 true). +- `channels.msteams.replyStyle`: `thread | top-level`([응답 스타일](#reply-style-threads-vs-posts) 참조). +- `channels.msteams.teams..replyStyle`: 팀별 재정의. +- `channels.msteams.teams..requireMention`: 팀별 재정의. +- `channels.msteams.teams..tools`: 채널 재정의가 없을 때 사용하는 기본 팀별 도구 정책 재정의(`allow`/`deny`/`alsoAllow`). +- `channels.msteams.teams..toolsBySender`: 기본 팀별 발신자별 도구 정책 재정의(`"*"` 와일드카드 지원). +- `channels.msteams.teams..channels..replyStyle`: 채널별 재정의. +- `channels.msteams.teams..channels..requireMention`: 채널별 재정의. +- `channels.msteams.teams..channels..tools`: 채널별 도구 정책 재정의(`allow`/`deny`/`alsoAllow`). +- `channels.msteams.teams..channels..toolsBySender`: 채널별 발신자별 도구 정책 재정의(`"*"` 와일드카드 지원). +- `toolsBySender` 키는 명시적 접두사를 사용해야 합니다. + `id:`, `e164:`, `username:`, `name:` (기존의 접두사 없는 키는 여전히 `id:`로만 매핑됨). +- `channels.msteams.actions.memberInfo`: Graph 기반 멤버 정보 작업 활성화/비활성화(기본값: Graph 자격 증명을 사용할 수 있으면 활성화). +- `channels.msteams.sharePointSiteId`: 그룹 채팅/채널의 파일 업로드용 SharePoint site ID([그룹 채팅에서 파일 보내기](#sending-files-in-group-chats) 참조). + +## 라우팅 및 세션 + +- 세션 키는 표준 에이전트 형식을 따릅니다([/concepts/session](/concepts/session) 참조). + - 다이렉트 메시지는 기본 세션을 공유합니다(`agent::`). + - 채널/그룹 메시지는 conversation id를 사용합니다. + - `agent::msteams:channel:` + - `agent::msteams:group:` + +## 응답 스타일: 스레드 vs 게시물 + +Teams는 최근 동일한 기본 데이터 모델 위에서 두 가지 채널 UI 스타일을 도입했습니다. + +| Style | Description | 권장 `replyStyle` | +| ------------------------ | --------------------------------------------------------- | ------------------------ | +| **Posts** (클래식) | 메시지가 카드로 표시되고 그 아래에 스레드 응답이 달림 | `thread` (기본값) | +| **Threads** (Slack 유사) | 메시지가 더 Slack처럼 선형으로 흐름 | `top-level` | + +**문제:** Teams API는 채널이 어떤 UI 스타일을 사용하는지 노출하지 않습니다. 잘못된 `replyStyle`을 사용하면 다음과 같습니다. + +- Threads 스타일 채널에서 `thread` 사용 → 응답이 어색하게 중첩되어 표시됨 +- Posts 스타일 채널에서 `top-level` 사용 → 응답이 스레드 내부가 아니라 별도 최상위 게시물로 표시됨 + +**해결책:** 채널이 어떻게 설정되어 있는지에 따라 채널별로 `replyStyle`을 구성하세요. + +```json5 +{ + channels: { + msteams: { + replyStyle: "thread", + teams: { + "19:abc...@thread.tacv2": { + channels: { + "19:xyz...@thread.tacv2": { + replyStyle: "top-level", + }, + }, + }, + }, + }, + }, +} +``` + +## 첨부파일 및 이미지 + +**현재 제한 사항:** + +- **DM:** Teams 봇 파일 API를 통해 이미지와 파일 첨부가 동작합니다. +- **채널/그룹:** 첨부파일은 M365 스토리지(SharePoint/OneDrive)에 저장됩니다. 웹훅 payload에는 실제 파일 바이트가 아닌 HTML stub만 포함됩니다. 채널 첨부파일을 다운로드하려면 **Graph API 권한이 필요합니다**. +- 명시적 파일 우선 전송에는 `media` / `filePath` / `path`와 함께 `action=upload-file`을 사용하세요. 선택적 `message`는 함께 전송되는 텍스트/주석이 되며, `filename`은 업로드 이름을 재정의합니다. + +Graph 권한이 없으면 이미지가 포함된 채널 메시지는 텍스트 전용으로 수신됩니다(이미지 콘텐츠에는 봇이 접근할 수 없음). +기본적으로 OpenClaw는 Microsoft/Teams 호스트명에서만 미디어를 다운로드합니다. `channels.msteams.mediaAllowHosts`로 재정의하세요(모든 호스트를 허용하려면 `["*"]` 사용). +Authorization 헤더는 `channels.msteams.mediaAuthAllowHosts`에 포함된 호스트에만 첨부됩니다(기본값은 Graph + Bot Framework 호스트). 이 목록은 엄격하게 유지하세요(멀티 테넌트 접미사는 피함). + +## 그룹 채팅에서 파일 보내기 + +봇은 DM에서 FileConsentCard 흐름(기본 제공)을 사용해 파일을 보낼 수 있습니다. 그러나 **그룹 채팅/채널에서 파일 보내기**에는 추가 설정이 필요합니다. + +| Context | 파일 전송 방식 | 필요한 설정 | +| ------------------------ | -------------------------------------------- | ----------------------------------------------- | +| **DM** | FileConsentCard → 사용자가 수락 → 봇이 업로드 | 기본적으로 동작 | +| **그룹 채팅/채널** | SharePoint에 업로드 → 링크 공유 | `sharePointSiteId` + Graph 권한 필요 | +| **이미지(모든 컨텍스트)** | Base64 인라인 인코딩 | 기본적으로 동작 | + +### 그룹 채팅에 SharePoint가 필요한 이유 + +봇에는 개인 OneDrive 드라이브가 없습니다(`/me/drive` Graph API 엔드포인트는 애플리케이션 ID에서 동작하지 않음). 그룹 채팅/채널로 파일을 보내려면 봇이 **SharePoint 사이트**에 업로드하고 공유 링크를 생성해야 합니다. + +### 설정 + +1. Entra ID (Azure AD) → App Registration에서 **Graph API 권한**을 추가합니다. + - `Sites.ReadWrite.All` (Application) - SharePoint에 파일 업로드 + - `Chat.Read.All` (Application) - 선택 사항, 사용자별 공유 링크 활성화 + +2. 테넌트에 대해 **관리자 동의**를 부여합니다. + +3. **SharePoint site ID 가져오기:** + + ```bash + # Graph Explorer 또는 유효한 토큰이 포함된 curl 사용: + curl -H "Authorization: Bearer $TOKEN" \ + "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}" + + # 예: "contoso.sharepoint.com/sites/BotFiles" 사이트의 경우 + curl -H "Authorization: Bearer $TOKEN" \ + "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles" + + # 응답에 포함됨: "id": "contoso.sharepoint.com,guid1,guid2" + ``` + +4. **OpenClaw 구성:** + + ```json5 + { + channels: { + msteams: { + // ... other config ... + sharePointSiteId: "contoso.sharepoint.com,guid1,guid2", + }, + }, + } + ``` + +### 공유 동작 + +| Permission | 공유 동작 | +| --------------------------------------- | --------------------------------------------------------- | +| `Sites.ReadWrite.All`만 | 조직 전체 공유 링크(조직 내 누구나 접근 가능) | +| `Sites.ReadWrite.All` + `Chat.Read.All` | 사용자별 공유 링크(채팅 멤버만 접근 가능) | + +사용자별 공유는 채팅 참여자만 파일에 접근할 수 있으므로 더 안전합니다. `Chat.Read.All` 권한이 없으면 봇은 조직 전체 공유로 대체합니다. + +### 대체 동작 + +| Scenario | 결과 | +| ------------------------------------------------- | -------------------------------------------------- | +| 그룹 채팅 + 파일 + `sharePointSiteId` 구성됨 | SharePoint에 업로드, 공유 링크 전송 | +| 그룹 채팅 + 파일 + `sharePointSiteId` 없음 | OneDrive 업로드 시도(실패 가능), 텍스트만 전송 | +| 개인 채팅 + 파일 | FileConsentCard 흐름(SharePoint 없이 동작) | +| 모든 컨텍스트 + 이미지 | Base64 인라인 인코딩(SharePoint 없이 동작) | + +### 파일 저장 위치 + +업로드된 파일은 구성된 SharePoint 사이트의 기본 문서 라이브러리 내 `/OpenClawShared/` 폴더에 저장됩니다. + +## Polls (Adaptive Cards) + +OpenClaw는 Teams Polls를 Adaptive Cards로 보냅니다(Teams에는 기본 Poll API가 없음). + +- CLI: `openclaw message poll --channel msteams --target conversation: ...` +- 투표는 게이트웨이가 `~/.openclaw/msteams-polls.json`에 기록합니다. +- 투표 기록을 위해 게이트웨이는 계속 온라인 상태여야 합니다. +- Polls는 아직 결과 요약을 자동 게시하지 않습니다(필요하면 저장 파일을 확인하세요). + +## Adaptive Cards(임의) + +`message` 도구 또는 CLI를 사용해 임의의 Adaptive Card JSON을 Teams 사용자나 대화에 보낼 수 있습니다. + +`card` 매개변수는 Adaptive Card JSON 객체를 받습니다. `card`가 제공되면 메시지 텍스트는 선택 사항입니다. + +**에이전트 도구:** + +```json5 +{ + action: "send", + channel: "msteams", + target: "user:", + card: { + type: "AdaptiveCard", + version: "1.5", + body: [{ type: "TextBlock", text: "Hello!" }], + }, +} +``` + +**CLI:** + +```bash +openclaw message send --channel msteams \ + --target "conversation:19:abc...@thread.tacv2" \ + --card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}' +``` + +카드 스키마와 예시는 [Adaptive Cards documentation](https://adaptivecards.io/)을 참조하세요. 대상 형식 세부 정보는 아래 [대상 형식](#target-formats)을 참조하세요. + +## 대상 형식 + +MSTeams 대상은 사용자와 대화를 구분하기 위해 접두사를 사용합니다. + +| Target type | Format | Example | +| ------------------- | -------------------------------- | --------------------------------------------------- | +| 사용자(ID 기준) | `user:` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` | +| 사용자(이름 기준) | `user:` | `user:John Smith` (Graph API 필요) | +| 그룹/채널 | `conversation:` | `conversation:19:abc123...@thread.tacv2` | +| 그룹/채널(raw) | `` | `19:abc123...@thread.tacv2` (`@thread`가 포함된 경우) | + +**CLI 예시:** + +```bash +# ID로 사용자에게 전송 +openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello" + +# 표시 이름으로 사용자에게 전송(Graph API 조회 트리거) +openclaw message send --channel msteams --target "user:John Smith" --message "Hello" + +# 그룹 채팅 또는 채널로 전송 +openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" --message "Hello" + +# 대화에 Adaptive Card 전송 +openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" \ + --card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}' +``` + +**에이전트 도구 예시:** + +```json5 +{ + action: "send", + channel: "msteams", + target: "user:John Smith", + message: "Hello!", +} +``` + +```json5 +{ + action: "send", + channel: "msteams", + target: "conversation:19:abc...@thread.tacv2", + card: { + type: "AdaptiveCard", + version: "1.5", + body: [{ type: "TextBlock", text: "Hello" }], + }, +} +``` + +참고: `user:` 접두사가 없으면 이름은 기본적으로 그룹/팀 확인으로 처리됩니다. 표시 이름으로 사람을 지정할 때는 항상 `user:`를 사용하세요. + +## Proactive messaging + +- Proactive 메시지는 사용자가 상호작용한 **이후에만** 가능합니다. 그 시점에 대화 참조를 저장하기 때문입니다. +- `dmPolicy`와 허용 목록 게이팅은 `/gateway/configuration`을 참조하세요. + +## 팀 및 채널 ID(흔한 함정) + +Teams URL의 `groupId` 쿼리 매개변수는 구성에 사용하는 팀 ID가 **아닙니다**. 대신 URL 경로에서 ID를 추출하세요. + +**팀 URL:** + +``` +https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=... + └────────────────────────────┘ + 팀 ID(URL 디코딩 필요) +``` + +**채널 URL:** + +``` +https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=... + └─────────────────────────┘ + 채널 ID(URL 디코딩 필요) +``` + +**config용:** + +- 팀 ID = `/team/` 뒤의 경로 세그먼트(URL 디코딩됨, 예: `19:Bk4j...@thread.tacv2`) +- 채널 ID = `/channel/` 뒤의 경로 세그먼트(URL 디코딩됨) +- `groupId` 쿼리 매개변수는 **무시**하세요 + +## 비공개 채널 + +봇은 비공개 채널에서 제한적으로 지원됩니다. + +| Feature | Standard Channels | Private Channels | +| ---------------------------- | ----------------- | ---------------------- | +| 봇 설치 | 예 | 제한적 | +| 실시간 메시지(웹훅) | 예 | 동작하지 않을 수 있음 | +| RSC 권한 | 예 | 다르게 동작할 수 있음 | +| @mentions | 예 | 봇에 접근 가능할 경우 | +| Graph API 기록 | 예 | 예(권한 필요) | + +**비공개 채널이 동작하지 않을 때의 우회 방법:** + +1. 봇 상호작용에는 표준 채널 사용 +2. DM 사용 - 사용자는 항상 봇에 직접 메시지를 보낼 수 있음 +3. 과거 접근에는 Graph API 사용(`ChannelMessage.Read.All` 필요) + +## 문제 해결 + +### 일반적인 문제 + +- **채널에 이미지가 표시되지 않음:** Graph 권한 또는 관리자 동의가 누락되었습니다. Teams 앱을 재설치하고 Teams를 완전히 종료 후 다시 여세요. +- **채널에서 응답 없음:** 기본적으로 멘션이 필요합니다. `channels.msteams.requireMention=false`를 설정하거나 팀/채널별로 구성하세요. +- **버전 불일치(Teams에 이전 manifest가 계속 표시됨):** 앱을 제거 후 다시 추가하고 Teams를 완전히 종료해 새로고침하세요. +- **웹훅에서 401 Unauthorized:** Azure JWT 없이 수동 테스트할 때는 정상입니다. 엔드포인트에 도달했지만 인증에 실패했다는 뜻입니다. 올바른 테스트에는 Azure Web Chat을 사용하세요. + +### Manifest 업로드 오류 + +- **"Icon file cannot be empty":** manifest가 참조하는 아이콘 파일이 0바이트입니다. 유효한 PNG 아이콘을 만드세요(`outline.png`는 32x32, `color.png`는 192x192). +- **"webApplicationInfo.Id already in use":** 앱이 다른 팀/채팅에 아직 설치되어 있습니다. 먼저 찾아 제거하거나 전파를 위해 5~10분 기다리세요. +- **업로드 시 "Something went wrong":** 대신 [https://admin.teams.microsoft.com](https://admin.teams.microsoft.com)을 통해 업로드하고, 브라우저 DevTools(F12) → Network 탭에서 실제 응답 본문 오류를 확인하세요. +- **사이드로드 실패:** "Upload a custom app" 대신 "Upload an app to your org's app catalog"를 시도하세요. 이 방식이 사이드로드 제한을 우회하는 경우가 많습니다. + +### RSC 권한이 작동하지 않음 + +1. `webApplicationInfo.id`가 봇의 App ID와 정확히 일치하는지 확인하세요 +2. 앱을 다시 업로드하고 팀/채팅에 재설치하세요 +3. 조직 관리자가 RSC 권한을 차단했는지 확인하세요 +4. 올바른 범위를 사용 중인지 확인하세요: 팀에는 `ChannelMessage.Read.Group`, 그룹 채팅에는 `ChatMessage.Read.Chat` + +## 참고 자료 + +- [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot 설정 가이드 +- [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - Teams 앱 생성/관리 +- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) +- [Receive channel messages with RSC](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/channel-messages-with-rsc) +- [RSC permissions reference](https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/resource-specific-consent) +- [Teams bot file handling](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/bots-filesv4) (채널/그룹에는 Graph 필요) +- [Proactive messaging](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages) + +## 관련 + +- [Channels Overview](/channels) — 지원되는 모든 채널 +- [Pairing](/channels/pairing) — DM 인증 및 pairing 흐름 +- [Groups](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [Channel Routing](/channels/channel-routing) — 메시지 세션 라우팅 +- [Security](/gateway/security) — 액세스 모델 및 보안 강화 diff --git a/docs/ko/channels/nextcloud-talk.md b/docs/ko/channels/nextcloud-talk.md new file mode 100644 index 000000000..ad47e150d --- /dev/null +++ b/docs/ko/channels/nextcloud-talk.md @@ -0,0 +1,156 @@ +--- +read_when: + - Nextcloud Talk 채널 기능을 작업할 때 +summary: Nextcloud Talk 지원 상태, 기능 및 구성 +title: Nextcloud Talk +x-i18n: + generated_at: "2026-04-05T12:35:42Z" + model: gpt-5.4 + provider: openai + source_hash: 900402afe67cf3ce96103d55158eb28cffb29c9845b77248e70d7653b12ae810 + source_path: channels/nextcloud-talk.md + workflow: 15 +--- + +# Nextcloud Talk + +상태: 번들 plugin(webhook bot). 다이렉트 메시지, 룸, 반응, 마크다운 메시지가 지원됩니다. + +## 번들 plugin + +Nextcloud Talk는 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로 +일반 패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드 또는 Nextcloud Talk가 제외된 사용자 지정 설치를 사용하는 경우 +수동으로 설치하세요. + +CLI를 통해 설치(npm 레지스트리): + +```bash +openclaw plugins install @openclaw/nextcloud-talk +``` + +로컬 체크아웃(git 리포지토리에서 실행하는 경우): + +```bash +openclaw plugins install ./path/to/local/nextcloud-talk-plugin +``` + +자세한 내용: [Plugins](/tools/plugin) + +## 빠른 설정(초보자용) + +1. Nextcloud Talk plugin을 사용할 수 있는지 확인합니다. + - 현재 패키지된 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/사용자 지정 설치에서는 위 명령으로 수동 추가할 수 있습니다. +2. Nextcloud 서버에서 bot을 생성합니다. + + ```bash + ./occ talk:bot:install "OpenClaw" "" "" --feature reaction + ``` + +3. 대상 룸 설정에서 bot을 활성화합니다. +4. OpenClaw를 구성합니다. + - Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret` + - 또는 env: `NEXTCLOUD_TALK_BOT_SECRET` (기본 계정 전용) +5. gateway를 다시 시작합니다(또는 설정을 완료합니다). + +최소 구성: + +```json5 +{ + channels: { + "nextcloud-talk": { + enabled: true, + baseUrl: "https://cloud.example.com", + botSecret: "shared-secret", + dmPolicy: "pairing", + }, + }, +} +``` + +## 참고 + +- bot은 DM을 시작할 수 없습니다. 사용자가 먼저 bot에 메시지를 보내야 합니다. +- webhook URL은 Gateway에서 도달 가능해야 합니다. 프록시 뒤에 있는 경우 `webhookPublicUrl`을 설정하세요. +- bot API는 미디어 업로드를 지원하지 않으므로 미디어는 URL로 전송됩니다. +- webhook payload는 DM과 룸을 구분하지 않습니다. 룸 유형 조회를 활성화하려면 `apiUser` + `apiPassword`를 설정하세요(그렇지 않으면 DM은 룸으로 처리됩니다). + +## 액세스 제어(DM) + +- 기본값: `channels.nextcloud-talk.dmPolicy = "pairing"`. 알 수 없는 발신자는 페어링 코드를 받습니다. +- 승인 방법: + - `openclaw pairing list nextcloud-talk` + - `openclaw pairing approve nextcloud-talk ` +- 공개 DM: `channels.nextcloud-talk.dmPolicy="open"` 및 `channels.nextcloud-talk.allowFrom=["*"]`. +- `allowFrom`은 Nextcloud 사용자 ID에만 일치하며 표시 이름은 무시됩니다. + +## 룸(그룹) + +- 기본값: `channels.nextcloud-talk.groupPolicy = "allowlist"` (멘션 게이트 적용). +- `channels.nextcloud-talk.rooms`로 룸을 허용 목록에 추가합니다. + +```json5 +{ + channels: { + "nextcloud-talk": { + rooms: { + "room-token": { requireMention: true }, + }, + }, + }, +} +``` + +- 어떤 룸도 허용하지 않으려면 허용 목록을 비워 두거나 `channels.nextcloud-talk.groupPolicy="disabled"`를 설정하세요. + +## 기능 + +| 기능 | 상태 | +| --------------- | ------------- | +| 다이렉트 메시지 | 지원됨 | +| 룸 | 지원됨 | +| 스레드 | 지원되지 않음 | +| 미디어 | URL 전용 | +| 반응 | 지원됨 | +| 네이티브 명령 | 지원되지 않음 | + +## 구성 참조(Nextcloud Talk) + +전체 구성: [구성](/gateway/configuration) + +Provider 옵션: + +- `channels.nextcloud-talk.enabled`: 채널 시작 활성화/비활성화 +- `channels.nextcloud-talk.baseUrl`: Nextcloud 인스턴스 URL +- `channels.nextcloud-talk.botSecret`: bot 공유 시크릿 +- `channels.nextcloud-talk.botSecretFile`: 일반 파일 시크릿 경로. 심볼릭 링크는 거부됩니다. +- `channels.nextcloud-talk.apiUser`: 룸 조회(DM 감지)용 API 사용자 +- `channels.nextcloud-talk.apiPassword`: 룸 조회용 API/앱 비밀번호 +- `channels.nextcloud-talk.apiPasswordFile`: API 비밀번호 파일 경로 +- `channels.nextcloud-talk.webhookPort`: webhook 리스너 포트(기본값: 8788) +- `channels.nextcloud-talk.webhookHost`: webhook 호스트(기본값: 0.0.0.0) +- `channels.nextcloud-talk.webhookPath`: webhook 경로(기본값: /nextcloud-talk-webhook) +- `channels.nextcloud-talk.webhookPublicUrl`: 외부에서 도달 가능한 webhook URL +- `channels.nextcloud-talk.dmPolicy`: `pairing | allowlist | open | disabled` +- `channels.nextcloud-talk.allowFrom`: DM 허용 목록(사용자 ID). `open`에는 `"*"`가 필요합니다. +- `channels.nextcloud-talk.groupPolicy`: `allowlist | open | disabled` +- `channels.nextcloud-talk.groupAllowFrom`: 그룹 허용 목록(사용자 ID) +- `channels.nextcloud-talk.rooms`: 룸별 설정 및 허용 목록 +- `channels.nextcloud-talk.historyLimit`: 그룹 기록 제한(0이면 비활성화) +- `channels.nextcloud-talk.dmHistoryLimit`: DM 기록 제한(0이면 비활성화) +- `channels.nextcloud-talk.dms`: DM별 재정의(`historyLimit`) +- `channels.nextcloud-talk.textChunkLimit`: 아웃바운드 텍스트 청크 크기(문자 수) +- `channels.nextcloud-talk.chunkMode`: 길이 청킹 전에 빈 줄(문단 경계) 기준으로 분할하는 `length`(기본값) 또는 `newline` +- `channels.nextcloud-talk.blockStreaming`: 이 채널의 블록 스트리밍 비활성화 +- `channels.nextcloud-talk.blockStreamingCoalesce`: 블록 스트리밍 병합 조정 +- `channels.nextcloud-talk.mediaMaxMb`: 인바운드 미디어 제한(MB) + +## 관련 문서 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/nostr.md b/docs/ko/channels/nostr.md new file mode 100644 index 000000000..a5e330fea --- /dev/null +++ b/docs/ko/channels/nostr.md @@ -0,0 +1,257 @@ +--- +read_when: + - OpenClaw가 Nostr를 통해 DM을 수신하도록 하려는 경우 + - 탈중앙화 메시징을 설정하는 경우 +summary: NIP-04 암호화 메시지를 통한 Nostr DM 채널 +title: Nostr +x-i18n: + generated_at: "2026-04-05T12:35:49Z" + model: gpt-5.4 + provider: openai + source_hash: f82829ee66fbeb3367007af343797140049ea49f2e842a695fa56acea0c80728 + source_path: channels/nostr.md + workflow: 15 +--- + +# Nostr + +**상태:** 선택적 번들 plugin(구성되기 전까지 기본적으로 비활성화됨). + +Nostr는 소셜 네트워킹을 위한 탈중앙화 프로토콜입니다. 이 채널을 사용하면 OpenClaw가 NIP-04를 통해 암호화된 다이렉트 메시지(DM)를 수신하고 응답할 수 있습니다. + +## 번들 plugin + +현재 OpenClaw 릴리스는 Nostr를 번들 plugin으로 포함하므로, 일반적인 패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +### 이전/사용자 지정 설치 + +- 온보딩(`openclaw onboard`)과 `openclaw channels add`는 여전히 공유 채널 카탈로그에서 Nostr를 표시합니다. +- 빌드에 번들 Nostr가 제외되어 있다면 수동으로 설치하세요. + +```bash +openclaw plugins install @openclaw/nostr +``` + +로컬 체크아웃 사용(개발 워크플로): + +```bash +openclaw plugins install --link +``` + +plugin을 설치하거나 활성화한 후 게이트웨이를 재시작하세요. + +### 비대화형 설정 + +```bash +openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" +openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY" --relay-urls "wss://relay.damus.io,wss://relay.primal.net" +``` + +키를 config에 저장하는 대신 환경에 `NOSTR_PRIVATE_KEY`를 유지하려면 `--use-env`를 사용하세요. + +## 빠른 설정 + +1. Nostr 키 쌍을 생성합니다(필요한 경우): + +```bash +# nak 사용 +nak key generate +``` + +2. config에 추가합니다: + +```json5 +{ + channels: { + nostr: { + privateKey: "${NOSTR_PRIVATE_KEY}", + }, + }, +} +``` + +3. 키를 export합니다: + +```bash +export NOSTR_PRIVATE_KEY="nsec1..." +``` + +4. 게이트웨이를 재시작합니다. + +## 구성 참조 + +| 키 | 유형 | 기본값 | 설명 | +| ------------ | -------- | ------------------------------------------- | ----------------------------------- | +| `privateKey` | string | 필수 | `nsec` 또는 hex 형식의 비공개 키 | +| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | 릴레이 URL(WebSocket) | +| `dmPolicy` | string | `pairing` | DM 액세스 정책 | +| `allowFrom` | string[] | `[]` | 허용된 발신자 pubkey | +| `enabled` | boolean | `true` | 채널 활성화/비활성화 | +| `name` | string | - | 표시 이름 | +| `profile` | object | - | NIP-01 프로필 메타데이터 | + +## 프로필 메타데이터 + +프로필 데이터는 NIP-01 `kind:0` 이벤트로 게시됩니다. Control UI(Channels -> Nostr -> Profile)에서 관리하거나 config에서 직접 설정할 수 있습니다. + +예시: + +```json5 +{ + channels: { + nostr: { + privateKey: "${NOSTR_PRIVATE_KEY}", + profile: { + name: "openclaw", + displayName: "OpenClaw", + about: "개인 비서 DM 봇", + picture: "https://example.com/avatar.png", + banner: "https://example.com/banner.png", + website: "https://example.com", + nip05: "openclaw@example.com", + lud16: "openclaw@example.com", + }, + }, + }, +} +``` + +참고: + +- 프로필 URL은 `https://`를 사용해야 합니다. +- 릴레이에서 가져오면 필드가 병합되며 로컬 재정의는 유지됩니다. + +## 액세스 제어 + +### DM 정책 + +- **pairing** (기본값): 알 수 없는 발신자에게 페어링 코드가 전송됩니다. +- **allowlist**: `allowFrom`의 pubkey만 DM을 보낼 수 있습니다. +- **open**: 공개 수신 DM(`allowFrom: ["*"]` 필요). +- **disabled**: 수신 DM을 무시합니다. + +강제 적용 참고 사항: + +- 수신 이벤트 서명은 발신자 정책 및 NIP-04 복호화 전에 검증되므로 위조된 이벤트는 조기에 거부됩니다. +- 페어링 응답은 원래 DM 본문을 처리하지 않고 전송됩니다. +- 수신 DM은 속도 제한이 적용되며 너무 큰 페이로드는 복호화 전에 삭제됩니다. + +### 허용 목록 예시 + +```json5 +{ + channels: { + nostr: { + privateKey: "${NOSTR_PRIVATE_KEY}", + dmPolicy: "allowlist", + allowFrom: ["npub1abc...", "npub1xyz..."], + }, + }, +} +``` + +## 키 형식 + +허용되는 형식: + +- **비공개 키:** `nsec...` 또는 64자 hex +- **Pubkey (`allowFrom`):** `npub...` 또는 hex + +## 릴레이 + +기본값: `relay.damus.io` 및 `nos.lol`. + +```json5 +{ + channels: { + nostr: { + privateKey: "${NOSTR_PRIVATE_KEY}", + relays: ["wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.wine"], + }, + }, +} +``` + +팁: + +- 이중화를 위해 2~3개의 릴레이를 사용하세요. +- 너무 많은 릴레이는 피하세요(지연 시간, 중복). +- 유료 릴레이는 안정성을 높일 수 있습니다. +- 테스트에는 로컬 릴레이도 괜찮습니다(`ws://localhost:7777`). + +## 프로토콜 지원 + +| NIP | 상태 | 설명 | +| ------ | --------- | ----------------------------------- | +| NIP-01 | 지원됨 | 기본 이벤트 형식 + 프로필 메타데이터 | +| NIP-04 | 지원됨 | 암호화된 DM(`kind:4`) | +| NIP-17 | 계획됨 | Gift-wrapped DM | +| NIP-44 | 계획됨 | 버전 지정 암호화 | + +## 테스트 + +### 로컬 릴레이 + +```bash +# strfry 시작 +docker run -p 7777:7777 ghcr.io/hoytech/strfry +``` + +```json5 +{ + channels: { + nostr: { + privateKey: "${NOSTR_PRIVATE_KEY}", + relays: ["ws://localhost:7777"], + }, + }, +} +``` + +### 수동 테스트 + +1. 로그에서 봇 pubkey(npub)를 확인합니다. +2. Nostr 클라이언트(Damus, Amethyst 등)를 엽니다. +3. 봇 pubkey로 DM을 보냅니다. +4. 응답을 확인합니다. + +## 문제 해결 + +### 메시지를 수신하지 못함 + +- 비공개 키가 유효한지 확인하세요. +- 릴레이 URL에 도달할 수 있고 `wss://`를 사용하는지 확인하세요(로컬은 `ws://` 가능). +- `enabled`가 `false`가 아닌지 확인하세요. +- 게이트웨이 로그에서 릴레이 연결 오류를 확인하세요. + +### 응답을 보내지 못함 + +- 릴레이가 쓰기를 허용하는지 확인하세요. +- 아웃바운드 연결성을 확인하세요. +- 릴레이 속도 제한이 있는지 살펴보세요. + +### 중복 응답 + +- 여러 릴레이를 사용할 때는 예상되는 동작입니다. +- 메시지는 이벤트 ID로 중복 제거되며, 첫 번째 전달만 응답을 트리거합니다. + +## 보안 + +- 비공개 키를 절대 커밋하지 마세요. +- 키에는 환경 변수를 사용하세요. +- 프로덕션 봇에는 `allowlist`를 고려하세요. +- 서명은 발신자 정책 전에 검증되고, 발신자 정책은 복호화 전에 적용되므로 위조된 이벤트는 조기에 거부되며 알 수 없는 발신자가 전체 암호화 작업을 강제할 수 없습니다. + +## 제한 사항 (MVP) + +- 다이렉트 메시지만 지원(그룹 채팅 없음). +- 미디어 첨부 파일 없음. +- NIP-04만 지원(NIP-17 gift-wrap은 계획됨). + +## 관련 항목 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/pairing.md b/docs/ko/channels/pairing.md new file mode 100644 index 000000000..97ac2802a --- /dev/null +++ b/docs/ko/channels/pairing.md @@ -0,0 +1,132 @@ +--- +read_when: + - DM 접근 제어를 설정할 때 + - 새 iOS/Android 노드를 페어링할 때 + - OpenClaw 보안 상태를 검토할 때 +summary: '페어링 개요: 누가 나에게 DM을 보낼 수 있는지와 어떤 노드가 참여할 수 있는지 승인' +title: 페어링 +x-i18n: + generated_at: "2026-04-05T12:35:45Z" + model: gpt-5.4 + provider: openai + source_hash: 2bd99240b3530def23c05a26915d07cf8b730565c2822c6338437f8fb3f285c9 + source_path: channels/pairing.md + workflow: 15 +--- + +# 페어링 + +“페어링”은 OpenClaw의 명시적인 **소유자 승인** 단계입니다. +이는 두 곳에서 사용됩니다. + +1. **DM 페어링**(누가 봇과 대화할 수 있는지) +2. **노드 페어링**(어떤 장치/노드가 게이트웨이 네트워크에 참여할 수 있는지) + +보안 맥락: [Security](/gateway/security) + +## 1) DM 페어링(인바운드 채팅 접근) + +채널이 DM 정책 `pairing`으로 구성되면, 알 수 없는 발신자는 짧은 코드를 받게 되며 승인이 이루어질 때까지 해당 메시지는 **처리되지 않습니다**. + +기본 DM 정책은 다음 문서에 설명되어 있습니다: [Security](/gateway/security) + +페어링 코드: + +- 8자, 대문자, 혼동하기 쉬운 문자 없음(`0O1I`). +- **1시간 후 만료**됩니다. 봇은 새 요청이 생성될 때만 페어링 메시지를 보냅니다(발신자당 대략 1시간에 한 번). +- 대기 중인 DM 페어링 요청은 기본적으로 **채널당 3개**로 제한되며, 하나가 만료되거나 승인될 때까지 추가 요청은 무시됩니다. + +### 발신자 승인 + +```bash +openclaw pairing list telegram +openclaw pairing approve telegram +``` + +지원 채널: `bluebubbles`, `discord`, `feishu`, `googlechat`, `imessage`, `irc`, `line`, `matrix`, `mattermost`, `msteams`, `nextcloud-talk`, `nostr`, `openclaw-weixin`, `signal`, `slack`, `synology-chat`, `telegram`, `twitch`, `whatsapp`, `zalo`, `zalouser`. + +### 상태가 저장되는 위치 + +`~/.openclaw/credentials/` 아래에 저장됩니다. + +- 대기 중 요청: `-pairing.json` +- 승인된 allowlist 저장소: + - 기본 계정: `-allowFrom.json` + - 비기본 계정: `--allowFrom.json` + +계정 범위 지정 동작: + +- 비기본 계정은 자신의 범위가 지정된 allowlist 파일만 읽고 씁니다. +- 기본 계정은 채널 범위의 범위 미지정 allowlist 파일을 사용합니다. + +이 파일들은 민감한 정보로 취급하세요(assistant에 대한 접근을 제어합니다). + +중요: 이 저장소는 DM 접근용입니다. 그룹 권한 부여는 별개입니다. +DM 페어링 코드를 승인한다고 해서 해당 발신자가 자동으로 그룹 명령을 실행하거나 그룹에서 봇을 제어할 수 있게 되는 것은 아닙니다. 그룹 접근의 경우 채널의 명시적인 그룹 allowlist를 구성해야 합니다(예: `groupAllowFrom`, `groups`, 또는 채널에 따라 그룹별/토픽별 재정의). + +## 2) 노드 장치 페어링(iOS/Android/macOS/헤드리스 노드) + +노드는 `role: node`를 가진 **장치**로 게이트웨이에 연결됩니다. 게이트웨이는 승인되어야 하는 장치 페어링 요청을 생성합니다. + +### Telegram으로 페어링(iOS 권장) + +`device-pair` plugin을 사용하면 최초 장치 페어링을 전부 Telegram에서 진행할 수 있습니다. + +1. Telegram에서 봇에게 메시지를 보냅니다: `/pair` +2. 봇이 두 개의 메시지로 응답합니다. 하나는 안내 메시지이고, 다른 하나는 Telegram에서 쉽게 복사/붙여넣기할 수 있는 별도의 **설정 코드** 메시지입니다. +3. 휴대폰에서 OpenClaw iOS 앱 → Settings → Gateway를 엽니다. +4. 설정 코드를 붙여넣고 연결합니다. +5. Telegram으로 돌아가 `/pair pending`을 실행한 뒤(요청 ID, 역할, 범위를 검토), 승인합니다. + +설정 코드는 다음을 포함하는 base64 인코딩 JSON 페이로드입니다. + +- `url`: 게이트웨이 WebSocket URL(`ws://...` 또는 `wss://...`) +- `bootstrapToken`: 초기 페어링 핸드셰이크에 사용되는, 단일 장치용 단기 bootstrap 토큰 + +그 bootstrap 토큰은 내장된 페어링 bootstrap 프로필을 포함합니다. + +- 기본적으로 넘겨지는 `node` 토큰은 `scopes: []`로 유지됩니다 +- 넘겨지는 `operator` 토큰은 bootstrap allowlist로 계속 제한됩니다: + `operator.approvals`, `operator.read`, `operator.talk.secrets`, `operator.write` +- bootstrap 범위 검사는 하나의 평면 범위 풀이 아니라 역할 접두사 기반입니다. + operator 범위 항목은 operator 요청만 충족하며, 비-operator 역할도 여전히 자신의 역할 접두사 아래에서 범위를 요청해야 합니다 + +설정 코드가 유효한 동안에는 비밀번호처럼 취급하세요. + +### 노드 장치 승인 + +```bash +openclaw devices list +openclaw devices approve +openclaw devices reject +``` + +동일한 장치가 다른 인증 세부 정보(예: 다른 역할/범위/공개 키)로 다시 시도하면, 이전 대기 요청은 대체되고 새 `requestId`가 생성됩니다. + +### 노드 페어링 상태 저장소 + +`~/.openclaw/devices/` 아래에 저장됩니다. + +- `pending.json`(단기 보관; 대기 요청은 만료됨) +- `paired.json`(페어링된 장치 + 토큰) + +### 참고 + +- 레거시 `node.pair.*` API(CLI: `openclaw nodes pending|approve|reject|rename`)는 + 게이트웨이가 소유하는 별도의 페어링 저장소입니다. WS 노드는 여전히 장치 페어링이 필요합니다. +- 페어링 기록은 승인된 역할에 대한 내구성 있는 source of truth입니다. 활성 + 장치 토큰은 해당 승인된 역할 집합으로 계속 제한되며, 승인된 역할 밖에 있는 + 우발적인 토큰 항목이 새 접근 권한을 만들지는 않습니다. + +## 관련 문서 + +- 보안 모델 + 프롬프트 인젝션: [Security](/gateway/security) +- 안전한 업데이트(doctor 실행): [Updating](/install/updating) +- 채널 구성: + - Telegram: [Telegram](/channels/telegram) + - WhatsApp: [WhatsApp](/channels/whatsapp) + - Signal: [Signal](/channels/signal) + - BlueBubbles(iMessage): [BlueBubbles](/channels/bluebubbles) + - iMessage(레거시): [iMessage](/channels/imessage) + - Discord: [Discord](/channels/discord) + - Slack: [Slack](/channels/slack) diff --git a/docs/ko/channels/qqbot.md b/docs/ko/channels/qqbot.md new file mode 100644 index 000000000..7db4f7335 --- /dev/null +++ b/docs/ko/channels/qqbot.md @@ -0,0 +1,187 @@ +--- +read_when: + - OpenClaw를 QQ에 연결하려는 경우 + - QQ Bot 자격 증명 설정이 필요한 경우 + - QQ Bot 그룹 또는 개인 채팅 지원을 사용하려는 경우 +summary: QQ Bot 설정, 구성 및 사용법 +title: QQ Bot +x-i18n: + generated_at: "2026-04-05T12:36:00Z" + model: gpt-5.4 + provider: openai + source_hash: 0e58fb7b07c59ecbf80a1276368c4a007b45d84e296ed40cffe9845e0953696c + source_path: channels/qqbot.md + workflow: 15 +--- + +# QQ Bot + +QQ Bot은 공식 QQ Bot API(WebSocket gateway)를 통해 OpenClaw에 연결됩니다. 이 plugin은 풍부한 미디어(이미지, 음성, 비디오, 파일)와 함께 C2C 개인 채팅, 그룹 @메시지, 길드 채널 메시지를 지원합니다. + +상태: 번들 plugin. 다이렉트 메시지, 그룹 채팅, 길드 채널 및 미디어가 지원됩니다. 반응과 스레드는 지원되지 않습니다. + +## 번들 plugin + +현재 OpenClaw 릴리스에는 QQ Bot이 번들로 포함되어 있으므로 일반 패키지형 빌드에서는 별도의 `openclaw plugins install` 단계가 필요하지 않습니다. + +## 설정 + +1. [QQ Open Platform](https://q.qq.com/)으로 이동해 휴대폰 QQ로 QR 코드를 스캔하여 등록/로그인합니다. +2. **Create Bot**을 클릭해 새 QQ 봇을 만듭니다. +3. 봇 설정 페이지에서 **AppID**와 **AppSecret**을 찾아 복사합니다. + +> AppSecret은 평문으로 저장되지 않으므로 저장하지 않고 페이지를 나가면 새로 다시 생성해야 합니다. + +4. 채널을 추가합니다. + +```bash +openclaw channels add --channel qqbot --token "AppID:AppSecret" +``` + +5. Gateway를 재시작합니다. + +대화형 설정 경로: + +```bash +openclaw channels add +openclaw configure --section channels +``` + +## 구성 + +최소 config: + +```json5 +{ + channels: { + qqbot: { + enabled: true, + appId: "YOUR_APP_ID", + clientSecret: "YOUR_APP_SECRET", + }, + }, +} +``` + +기본 계정 환경 변수: + +- `QQBOT_APP_ID` +- `QQBOT_CLIENT_SECRET` + +파일 기반 AppSecret: + +```json5 +{ + channels: { + qqbot: { + enabled: true, + appId: "YOUR_APP_ID", + clientSecretFile: "/path/to/qqbot-secret.txt", + }, + }, +} +``` + +참고: + +- 환경 변수 대체는 기본 QQ Bot 계정에만 적용됩니다. +- `openclaw channels add --channel qqbot --token-file ...`는 AppSecret만 제공합니다. AppID는 이미 config 또는 `QQBOT_APP_ID`에 설정되어 있어야 합니다. +- `clientSecret`은 평문 문자열뿐 아니라 SecretRef 입력도 허용합니다. + +### 다중 계정 설정 + +하나의 OpenClaw 인스턴스에서 여러 QQ 봇을 실행합니다. + +```json5 +{ + channels: { + qqbot: { + enabled: true, + appId: "111111111", + clientSecret: "secret-of-bot-1", + accounts: { + bot2: { + enabled: true, + appId: "222222222", + clientSecret: "secret-of-bot-2", + }, + }, + }, + }, +} +``` + +각 계정은 자체 WebSocket 연결을 시작하며 독립적인 토큰 캐시를 유지합니다(`appId`별로 격리됨). + +CLI로 두 번째 봇 추가: + +```bash +openclaw channels add --channel qqbot --account bot2 --token "222222222:secret-of-bot-2" +``` + +### 음성(STT / TTS) + +STT와 TTS는 우선순위 대체를 포함한 2단계 구성을 지원합니다. + +| 설정 | plugin별 | 프레임워크 대체 | +| ------- | -------------------- | ----------------------------- | +| STT | `channels.qqbot.stt` | `tools.media.audio.models[0]` | +| TTS | `channels.qqbot.tts` | `messages.tts` | + +```json5 +{ + channels: { + qqbot: { + stt: { + provider: "your-provider", + model: "your-stt-model", + }, + tts: { + provider: "your-provider", + model: "your-tts-model", + voice: "your-voice", + }, + }, + }, +} +``` + +비활성화하려면 둘 중 하나에 `enabled: false`를 설정하세요. + +아웃바운드 오디오 업로드/트랜스코드 동작은 `channels.qqbot.audioFormatPolicy`로도 조정할 수 있습니다. + +- `sttDirectFormats` +- `uploadDirectFormats` +- `transcodeEnabled` + +## 대상 형식 + +| 형식 | 설명 | +| -------------------------- | ------------------ | +| `qqbot:c2c:OPENID` | 개인 채팅(C2C) | +| `qqbot:group:GROUP_OPENID` | 그룹 채팅 | +| `qqbot:channel:CHANNEL_ID` | 길드 채널 | + +> 각 봇은 자체 사용자 OpenID 집합을 가집니다. Bot A가 받은 OpenID는 Bot B를 통해 메시지를 보내는 데 **사용할 수 없습니다**. + +## 슬래시 명령 + +AI 큐보다 먼저 가로채는 내장 명령: + +| 명령 | 설명 | +| -------------- | ------------------------------------ | +| `/bot-ping` | 지연 시간 테스트 | +| `/bot-version` | OpenClaw 프레임워크 버전 표시 | +| `/bot-help` | 모든 명령 나열 | +| `/bot-upgrade` | QQBot 업그레이드 가이드 링크 표시 | +| `/bot-logs` | 최근 gateway 로그를 파일로 내보내기 | + +사용법 도움말을 보려면 아무 명령에나 `?`를 추가하세요(예: `/bot-upgrade ?`). + +## 문제 해결 + +- **봇이 "gone to Mars"라고 응답함:** 자격 증명이 구성되지 않았거나 Gateway가 시작되지 않았습니다. +- **인바운드 메시지가 없음:** `appId`와 `clientSecret`이 올바른지, 그리고 봇이 QQ Open Platform에서 활성화되어 있는지 확인하세요. +- **`--token-file`로 설정했는데도 여전히 미구성으로 표시됨:** `--token-file`은 AppSecret만 설정합니다. 여전히 config 또는 `QQBOT_APP_ID`에 `appId`가 필요합니다. +- **사전 발송 메시지가 도착하지 않음:** 사용자가 최근 상호작용하지 않았다면 QQ가 봇이 시작한 메시지를 차단할 수 있습니다. +- **음성이 전사되지 않음:** STT가 구성되어 있고 공급자에 연결할 수 있는지 확인하세요. diff --git a/docs/ko/channels/signal.md b/docs/ko/channels/signal.md new file mode 100644 index 000000000..9ad89d9fd --- /dev/null +++ b/docs/ko/channels/signal.md @@ -0,0 +1,344 @@ +--- +read_when: + - Signal 지원을 설정할 때 + - Signal 송수신 문제를 디버깅할 때 +summary: signal-cli(JSON-RPC + SSE)를 통한 Signal 지원, 설정 경로 및 번호 모델 +title: Signal +x-i18n: + generated_at: "2026-04-05T12:36:24Z" + model: gpt-5.4 + provider: openai + source_hash: cdd855eb353aca6a1c2b04d14af0e3da079349297b54fa8243562c52b29118d9 + source_path: channels/signal.md + workflow: 15 +--- + +# Signal (signal-cli) + +상태: 외부 CLI 통합. Gateway는 HTTP JSON-RPC + SSE를 통해 `signal-cli`와 통신합니다. + +## 사전 요구 사항 + +- 서버에 OpenClaw가 설치되어 있어야 합니다(아래 Linux 흐름은 Ubuntu 24에서 테스트됨). +- gateway가 실행되는 호스트에서 `signal-cli`를 사용할 수 있어야 합니다. +- 한 번의 인증 SMS를 수신할 수 있는 전화번호가 필요합니다(SMS 등록 경로용). +- 등록 중 Signal captcha(`signalcaptchas.org`)에 접근할 수 있는 브라우저가 필요합니다. + +## 빠른 설정(초보자용) + +1. 봇에 **별도의 Signal 번호**를 사용합니다(권장). +2. `signal-cli`를 설치합니다(JVM 빌드를 사용하는 경우 Java 필요). +3. 다음 설정 경로 중 하나를 선택합니다. + - **경로 A (QR 연결):** `signal-cli link -n "OpenClaw"`를 실행하고 Signal로 스캔합니다. + - **경로 B (SMS 등록):** captcha + SMS 인증으로 전용 번호를 등록합니다. +4. OpenClaw를 구성하고 gateway를 재시작합니다. +5. 첫 번째 DM을 보내고 페어링을 승인합니다(`openclaw pairing approve signal `). + +최소 구성: + +```json5 +{ + channels: { + signal: { + enabled: true, + account: "+15551234567", + cliPath: "signal-cli", + dmPolicy: "pairing", + allowFrom: ["+15557654321"], + }, + }, +} +``` + +필드 참조: + +| 필드 | 설명 | +| ----------- | ------------------------------------------------- | +| `account` | E.164 형식의 봇 전화번호 (`+15551234567`) | +| `cliPath` | `signal-cli` 경로(`PATH`에 있으면 `signal-cli`) | +| `dmPolicy` | DM 액세스 정책(`pairing` 권장) | +| `allowFrom` | DM을 허용할 전화번호 또는 `uuid:` 값 | + +## 개요 + +- Signal 채널은 `signal-cli`를 통해 동작합니다(내장 libsignal 아님). +- 결정적 라우팅: 응답은 항상 Signal로 다시 전송됩니다. +- DM은 에이전트의 메인 세션을 공유하고, 그룹은 격리됩니다(`agent::signal:group:`). + +## Config 쓰기 + +기본적으로 Signal은 `/config set|unset`에 의해 트리거되는 config 업데이트 쓰기가 허용됩니다(`commands.config: true` 필요). + +다음으로 비활성화할 수 있습니다. + +```json5 +{ + channels: { signal: { configWrites: false } }, +} +``` + +## 번호 모델(중요) + +- gateway는 **Signal 디바이스**(`signal-cli` 계정)에 연결됩니다. +- 봇을 **개인 Signal 계정**에서 실행하면, 자신의 메시지는 무시합니다(루프 방지). +- “내가 봇에 문자를 보내면 답장받는” 구성을 원하면 **별도의 봇 번호**를 사용하세요. + +## 설정 경로 A: 기존 Signal 계정 연결(QR) + +1. `signal-cli`를 설치합니다(JVM 또는 네이티브 빌드). +2. 봇 계정을 연결합니다. + - `signal-cli link -n "OpenClaw"`를 실행한 다음 Signal에서 QR을 스캔합니다. +3. Signal을 구성하고 gateway를 시작합니다. + +예시: + +```json5 +{ + channels: { + signal: { + enabled: true, + account: "+15551234567", + cliPath: "signal-cli", + dmPolicy: "pairing", + allowFrom: ["+15557654321"], + }, + }, +} +``` + +다중 계정 지원: 계정별 config와 선택적 `name`을 포함하는 `channels.signal.accounts`를 사용하세요. 공통 패턴은 [`gateway/configuration`](/gateway/configuration-reference#multi-account-all-channels)을 참조하세요. + +## 설정 경로 B: 전용 봇 번호 등록(SMS, Linux) + +기존 Signal 앱 계정을 연결하는 대신 전용 봇 번호를 원할 때 사용합니다. + +1. SMS를 받을 수 있는 번호(또는 유선전화의 경우 음성 인증)를 준비합니다. + - 계정/세션 충돌을 피하려면 전용 봇 번호를 사용하세요. +2. gateway 호스트에 `signal-cli`를 설치합니다. + +```bash +VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/AsamK/signal-cli/releases/latest | sed -e 's/^.*\/v//') +curl -L -O "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-native.tar.gz" +sudo tar xf "signal-cli-${VERSION}-Linux-native.tar.gz" -C /opt +sudo ln -sf /opt/signal-cli /usr/local/bin/ +signal-cli --version +``` + +JVM 빌드(`signal-cli-${VERSION}.tar.gz`)를 사용하는 경우 먼저 JRE 25+를 설치하세요. +`signal-cli`는 최신 상태로 유지하세요. upstream에 따르면 Signal 서버 API가 변경되면 오래된 릴리스가 동작하지 않을 수 있습니다. + +3. 번호를 등록하고 인증합니다. + +```bash +signal-cli -a + register +``` + +captcha가 필요한 경우: + +1. `https://signalcaptchas.org/registration/generate.html`를 엽니다. +2. captcha를 완료하고 “Open Signal”의 `signalcaptcha://...` 링크 대상 값을 복사합니다. +3. 가능하면 브라우저 세션과 동일한 외부 IP에서 실행합니다. +4. 즉시 다시 등록을 실행합니다(captcha 토큰은 빠르게 만료됨). + +```bash +signal-cli -a + register --captcha '' +signal-cli -a + verify +``` + +4. OpenClaw를 구성하고 gateway를 재시작한 뒤 채널을 확인합니다. + +```bash +# gateway를 사용자 systemd 서비스로 실행하는 경우: +systemctl --user restart openclaw-gateway.service + +# 그런 다음 확인: +openclaw doctor +openclaw channels status --probe +``` + +5. DM 발신자를 페어링합니다. + - 봇 번호로 아무 메시지나 보냅니다. + - 서버에서 코드를 승인합니다: `openclaw pairing approve signal `. + - “알 수 없는 연락처”를 피하려면 휴대폰에 봇 번호를 연락처로 저장하세요. + +중요: `signal-cli`로 전화번호 계정을 등록하면 해당 번호의 기본 Signal 앱 세션이 인증 해제될 수 있습니다. 기존 휴대폰 앱 구성을 유지해야 한다면 전용 봇 번호를 사용하거나 QR 연결 모드를 선호하세요. + +upstream 참고 자료: + +- `signal-cli` README: `https://github.com/AsamK/signal-cli` +- Captcha 흐름: `https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha` +- 연결 흐름: `https://github.com/AsamK/signal-cli/wiki/Linking-other-devices-(Provisioning)` + +## 외부 데몬 모드 (`httpUrl`) + +`signal-cli`를 직접 관리하려는 경우(느린 JVM 콜드 스타트, 컨테이너 초기화 또는 공유 CPU) 데몬을 별도로 실행하고 OpenClaw가 이를 가리키도록 설정하세요. + +```json5 +{ + channels: { + signal: { + httpUrl: "http://127.0.0.1:8080", + autoStart: false, + }, + }, +} +``` + +이렇게 하면 OpenClaw 내부의 자동 실행과 시작 대기를 건너뜁니다. 자동 실행 시 시작이 느린 경우 `channels.signal.startupTimeoutMs`를 설정하세요. + +## 액세스 제어(DM + 그룹) + +DM: + +- 기본값: `channels.signal.dmPolicy = "pairing"`. +- 알 수 없는 발신자는 페어링 코드를 받으며, 승인될 때까지 메시지는 무시됩니다(코드는 1시간 후 만료). +- 승인 방법: + - `openclaw pairing list signal` + - `openclaw pairing approve signal ` +- 페어링은 Signal DM의 기본 토큰 교환 방식입니다. 자세한 내용: [페어링](/channels/pairing) +- UUID 전용 발신자(`sourceUuid`에서 옴)는 `channels.signal.allowFrom`에 `uuid:`로 저장됩니다. + +그룹: + +- `channels.signal.groupPolicy = open | allowlist | disabled`. +- `channels.signal.groupAllowFrom`는 `allowlist`가 설정된 경우 그룹에서 누가 트리거할 수 있는지 제어합니다. +- `channels.signal.groups["" | "*"]`는 `requireMention`, `tools`, `toolsBySender`로 그룹 동작을 재정의할 수 있습니다. +- 다중 계정 구성에서는 계정별 재정의를 위해 `channels.signal.accounts..groups`를 사용하세요. +- 런타임 참고: `channels.signal`이 완전히 누락된 경우 런타임은 그룹 확인에 대해 `groupPolicy="allowlist"`로 폴백합니다(`channels.defaults.groupPolicy`가 설정되어 있더라도). + +## 동작 방식 + +- `signal-cli`는 데몬으로 실행되며 gateway는 SSE를 통해 이벤트를 읽습니다. +- 인바운드 메시지는 공유 채널 envelope로 정규화됩니다. +- 응답은 항상 동일한 번호 또는 그룹으로 다시 라우팅됩니다. + +## 미디어 및 제한 + +- 아웃바운드 텍스트는 `channels.signal.textChunkLimit`(기본값 4000) 기준으로 청킹됩니다. +- 선택적 줄바꿈 청킹: 길이 기준 청킹 전에 빈 줄(문단 경계)에서 분할하려면 `channels.signal.chunkMode="newline"`를 설정하세요. +- 첨부 파일이 지원됩니다(`signal-cli`에서 가져온 base64 사용). +- 기본 미디어 제한: `channels.signal.mediaMaxMb`(기본값 8). +- 미디어 다운로드를 건너뛰려면 `channels.signal.ignoreAttachments`를 사용하세요. +- 그룹 기록 컨텍스트는 `channels.signal.historyLimit`(또는 `channels.signal.accounts.*.historyLimit`)를 사용하며, 없으면 `messages.groupChat.historyLimit`로 폴백합니다. 비활성화하려면 `0`으로 설정하세요(기본값 50). + +## 입력 중 표시 및 읽음 확인 + +- **입력 중 표시**: OpenClaw는 `signal-cli sendTyping`을 통해 입력 중 신호를 보내며 응답이 실행되는 동안 이를 갱신합니다. +- **읽음 확인**: `channels.signal.sendReadReceipts`가 true이면 OpenClaw는 허용된 DM에 대한 읽음 확인을 전달합니다. +- signal-cli는 그룹에 대한 읽음 확인을 노출하지 않습니다. + +## 반응(메시지 도구) + +- `channel=signal`과 함께 `message action=react`를 사용하세요. +- 대상: 발신자의 E.164 또는 UUID(페어링 출력의 `uuid:` 사용, bare UUID도 가능). +- `messageId`는 반응할 메시지의 Signal 타임스탬프입니다. +- 그룹 반응에는 `targetAuthor` 또는 `targetAuthorUuid`가 필요합니다. + +예시: + +``` +message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥 +message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true +message action=react channel=signal target=signal:group: targetAuthor=uuid: messageId=1737630212345 emoji=✅ +``` + +Config: + +- `channels.signal.actions.reactions`: 반응 작업 활성화/비활성화(기본값 true) +- `channels.signal.reactionLevel`: `off | ack | minimal | extensive`. + - `off`/`ack`는 에이전트 반응을 비활성화합니다(메시지 도구 `react`는 오류 발생). + - `minimal`/`extensive`는 에이전트 반응을 활성화하고 안내 수준을 설정합니다. +- 계정별 재정의: `channels.signal.accounts..actions.reactions`, `channels.signal.accounts..reactionLevel`. + +## 전달 대상(CLI/cron) + +- DM: `signal:+15551234567`(또는 일반 E.164) +- UUID DM: `uuid:`(또는 bare UUID) +- 그룹: `signal:group:`. +- 사용자 이름: `username:`(Signal 계정에서 지원되는 경우) + +## 문제 해결 + +먼저 다음 순서로 실행하세요. + +```bash +openclaw status +openclaw gateway status +openclaw logs --follow +openclaw doctor +openclaw channels status --probe +``` + +그런 다음 필요하면 DM 페어링 상태를 확인합니다. + +```bash +openclaw pairing list signal +``` + +일반적인 실패 사례: + +- 데몬에 연결되지만 응답이 없음: 계정/데몬 설정(`httpUrl`, `account`)과 수신 모드를 확인하세요. +- DM이 무시됨: 발신자가 페어링 승인 대기 상태입니다. +- 그룹 메시지가 무시됨: 그룹 발신자/멘션 게이팅이 전달을 차단합니다. +- 편집 후 config 검증 오류: `openclaw doctor --fix`를 실행하세요. +- 진단에서 Signal이 보이지 않음: `channels.signal.enabled: true`인지 확인하세요. + +추가 확인: + +```bash +openclaw pairing list signal +pgrep -af signal-cli +grep -i "signal" "/tmp/openclaw/openclaw-$(date +%Y-%m-%d).log" | tail -20 +``` + +분류 흐름은 [/channels/troubleshooting](/channels/troubleshooting)을 참조하세요. + +## 보안 참고 + +- `signal-cli`는 계정 키를 로컬에 저장합니다(일반적으로 `~/.local/share/signal-cli/data/`). +- 서버 마이그레이션 또는 재구축 전에 Signal 계정 상태를 백업하세요. +- 더 넓은 DM 액세스를 명시적으로 원하지 않는 한 `channels.signal.dmPolicy: "pairing"`을 유지하세요. +- SMS 인증은 등록 또는 복구 흐름에만 필요하지만, 번호/계정 제어를 잃으면 재등록이 복잡해질 수 있습니다. + +## 구성 참조(Signal) + +전체 구성: [구성](/gateway/configuration) + +Provider 옵션: + +- `channels.signal.enabled`: 채널 시작 활성화/비활성화 +- `channels.signal.account`: 봇 계정의 E.164 +- `channels.signal.cliPath`: `signal-cli` 경로 +- `channels.signal.httpUrl`: 전체 데몬 URL(host/port보다 우선) +- `channels.signal.httpHost`, `channels.signal.httpPort`: 데몬 바인드(기본값 127.0.0.1:8080) +- `channels.signal.autoStart`: 데몬 자동 실행(`httpUrl`이 설정되지 않은 경우 기본값 true) +- `channels.signal.startupTimeoutMs`: 시작 대기 제한 시간(ms, 최대 120000) +- `channels.signal.receiveMode`: `on-start | manual` +- `channels.signal.ignoreAttachments`: 첨부 파일 다운로드 건너뛰기 +- `channels.signal.ignoreStories`: 데몬의 스토리 무시 +- `channels.signal.sendReadReceipts`: 읽음 확인 전달 +- `channels.signal.dmPolicy`: `pairing | allowlist | open | disabled`(기본값: pairing) +- `channels.signal.allowFrom`: DM 허용 목록(E.164 또는 `uuid:`). `open`에는 `"*"`가 필요합니다. Signal에는 사용자 이름이 없으므로 전화번호/UUID ID를 사용하세요. +- `channels.signal.groupPolicy`: `open | allowlist | disabled`(기본값: allowlist) +- `channels.signal.groupAllowFrom`: 그룹 발신자 허용 목록 +- `channels.signal.groups`: Signal 그룹 ID(또는 `"*"`)를 키로 하는 그룹별 재정의. 지원 필드: `requireMention`, `tools`, `toolsBySender` +- `channels.signal.accounts..groups`: 다중 계정 구성용 `channels.signal.groups`의 계정별 버전 +- `channels.signal.historyLimit`: 컨텍스트로 포함할 최대 그룹 메시지 수(0이면 비활성화) +- `channels.signal.dmHistoryLimit`: 사용자 턴 기준 DM 기록 제한. 사용자별 재정의: `channels.signal.dms[""].historyLimit` +- `channels.signal.textChunkLimit`: 아웃바운드 청크 크기(문자 수) +- `channels.signal.chunkMode`: 길이 청킹 전에 빈 줄(문단 경계) 기준으로 분할하는 `length`(기본값) 또는 `newline` +- `channels.signal.mediaMaxMb`: 인바운드/아웃바운드 미디어 제한(MB) + +관련 전역 옵션: + +- `agents.list[].groupChat.mentionPatterns` (Signal은 네이티브 멘션을 지원하지 않음) +- `messages.groupChat.mentionPatterns` (전역 폴백) +- `messages.responsePrefix`. + +## 관련 문서 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/slack.md b/docs/ko/channels/slack.md new file mode 100644 index 000000000..16a013f65 --- /dev/null +++ b/docs/ko/channels/slack.md @@ -0,0 +1,675 @@ +--- +read_when: + - Slack을 설정하거나 Slack 소켓/HTTP 모드를 디버깅할 때 +summary: Slack 설정 및 런타임 동작(Socket Mode + HTTP Events API) +title: Slack +x-i18n: + generated_at: "2026-04-05T12:36:55Z" + model: gpt-5.4 + provider: openai + source_hash: efb37e1f04e1ac8ac3786c36ffc20013dacdc654bfa61e7f6e8df89c4902d2ab + source_path: channels/slack.md + workflow: 15 +--- + +# Slack + +상태: Slack 앱 통합을 통한 DM 및 채널용 프로덕션 준비 완료. 기본 모드는 Socket Mode이며, HTTP Events API 모드도 지원됩니다. + + + + Slack DM은 기본적으로 페어링 모드를 사용합니다. + + + 네이티브 명령 동작 및 명령 카탈로그. + + + 채널 전반의 진단 및 복구 플레이북. + + + +## 빠른 설정 + + + + + + Slack 앱 설정에서 다음을 수행합니다. + + - **Socket Mode** 활성화 + - `connections:write` 권한이 있는 **App Token**(`xapp-...`) 생성 + - 앱을 설치하고 **Bot Token**(`xoxb-...`) 복사 + + + + +```json5 +{ + channels: { + slack: { + enabled: true, + mode: "socket", + appToken: "xapp-...", + botToken: "xoxb-...", + }, + }, +} +``` + + 환경 변수 대체(default 계정만 해당): + +```bash +SLACK_APP_TOKEN=xapp-... +SLACK_BOT_TOKEN=xoxb-... +``` + + + + + 다음에 대한 봇 이벤트를 구독합니다. + + - `app_mention` + - `message.channels`, `message.groups`, `message.im`, `message.mpim` + - `reaction_added`, `reaction_removed` + - `member_joined_channel`, `member_left_channel` + - `channel_rename` + - `pin_added`, `pin_removed` + + DM용으로 App Home **Messages Tab**도 활성화합니다. + + + + +```bash +openclaw gateway +``` + + + + + + + + + + + - 모드를 HTTP로 설정(`channels.slack.mode="http"`) + - Slack **Signing Secret** 복사 + - Event Subscriptions + Interactivity + Slash command Request URL을 모두 동일한 webhook 경로(기본값 `/slack/events`)로 설정 + + + + + +```json5 +{ + channels: { + slack: { + enabled: true, + mode: "http", + botToken: "xoxb-...", + signingSecret: "your-signing-secret", + webhookPath: "/slack/events", + }, + }, +} +``` + + + + + 계정별 HTTP 모드가 지원됩니다. + + 등록이 충돌하지 않도록 각 계정에 서로 다른 `webhookPath`를 지정하세요. + + + + + + +## 매니페스트 및 범위 체크리스트 + + + + +```json +{ + "display_information": { + "name": "OpenClaw", + "description": "Slack connector for OpenClaw" + }, + "features": { + "bot_user": { + "display_name": "OpenClaw", + "always_online": true + }, + "app_home": { + "messages_tab_enabled": true, + "messages_tab_read_only_enabled": false + }, + "slash_commands": [ + { + "command": "/openclaw", + "description": "Send a message to OpenClaw", + "should_escape": false + } + ] + }, + "oauth_config": { + "scopes": { + "bot": [ + "app_mentions:read", + "assistant:write", + "channels:history", + "channels:read", + "chat:write", + "commands", + "emoji:read", + "files:read", + "files:write", + "groups:history", + "groups:read", + "im:history", + "im:read", + "im:write", + "mpim:history", + "mpim:read", + "mpim:write", + "pins:read", + "pins:write", + "reactions:read", + "reactions:write", + "users:read" + ] + } + }, + "settings": { + "socket_mode_enabled": true, + "event_subscriptions": { + "bot_events": [ + "app_mention", + "channel_rename", + "member_joined_channel", + "member_left_channel", + "message.channels", + "message.groups", + "message.im", + "message.mpim", + "pin_added", + "pin_removed", + "reaction_added", + "reaction_removed" + ] + } + } +} +``` + + + + + `channels.slack.userToken`을 구성한 경우, 일반적인 읽기 범위는 다음과 같습니다. + + - `channels:history`, `groups:history`, `im:history`, `mpim:history` + - `channels:read`, `groups:read`, `im:read`, `mpim:read` + - `users:read` + - `reactions:read` + - `pins:read` + - `emoji:read` + - `search:read`(Slack 검색 읽기에 의존하는 경우) + + + + +## 토큰 모델 + +- Socket Mode에는 `botToken` + `appToken`이 필요합니다. +- HTTP 모드에는 `botToken` + `signingSecret`이 필요합니다. +- `botToken`, `appToken`, `signingSecret`, `userToken`은 일반 문자열 또는 SecretRef 객체를 받을 수 있습니다. +- 구성의 토큰이 환경 변수 대체보다 우선합니다. +- `SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` 환경 변수 대체는 default 계정에만 적용됩니다. +- `userToken`(`xoxp-...`)은 구성 전용이며(환경 변수 대체 없음), 기본값은 읽기 전용 동작(`userTokenReadOnly: true`)입니다. +- 선택 사항: 발신 메시지가 활성 에이전트 신원(사용자 지정 `username` 및 아이콘)을 사용하게 하려면 `chat:write.customize`를 추가하세요. `icon_emoji`는 `:emoji_name:` 문법을 사용합니다. + +상태 스냅샷 동작: + +- Slack 계정 검사는 자격 증명별 `*Source` 및 `*Status` 필드(`botToken`, `appToken`, `signingSecret`, `userToken`)를 추적합니다. +- 상태는 `available`, `configured_unavailable`, `missing`입니다. +- `configured_unavailable`은 계정이 SecretRef 또는 다른 비인라인 비밀 소스를 통해 구성되었지만, 현재 명령/런타임 경로에서 실제 값을 해석할 수 없었음을 의미합니다. +- HTTP 모드에서는 `signingSecretStatus`가 포함되며, Socket Mode에서는 필요한 쌍이 `botTokenStatus` + `appTokenStatus`입니다. + + +작업/디렉터리 읽기에서는 구성된 경우 사용자 토큰을 우선할 수 있습니다. 쓰기에서는 여전히 봇 토큰이 우선이며, 사용자 토큰 쓰기는 `userTokenReadOnly: false`이고 봇 토큰을 사용할 수 없을 때만 허용됩니다. + + +## 작업 및 게이트 + +Slack 작업은 `channels.slack.actions.*`로 제어됩니다. + +현재 Slack 도구에서 사용할 수 있는 작업 그룹: + +| 그룹 | 기본값 | +| ---------- | ------- | +| messages | enabled | +| reactions | enabled | +| pins | enabled | +| memberInfo | enabled | +| emojiList | enabled | + +현재 Slack 메시지 작업에는 `send`, `upload-file`, `download-file`, `read`, `edit`, `delete`, `pin`, `unpin`, `list-pins`, `member-info`, `emoji-list`가 포함됩니다. + +## 접근 제어 및 라우팅 + + + + `channels.slack.dmPolicy`가 DM 접근을 제어합니다(레거시: `channels.slack.dm.policy`). + + - `pairing`(기본값) + - `allowlist` + - `open`(`channels.slack.allowFrom`에 `"*"`가 포함되어야 함; 레거시: `channels.slack.dm.allowFrom`) + - `disabled` + + DM 플래그: + + - `dm.enabled`(기본값 true) + - `channels.slack.allowFrom`(권장) + - `dm.allowFrom`(레거시) + - `dm.groupEnabled`(그룹 DM 기본값 false) + - `dm.groupChannels`(선택적 MPIM allowlist) + + 멀티 계정 우선순위: + + - `channels.slack.accounts.default.allowFrom`은 `default` 계정에만 적용됩니다. + - 이름 있는 계정은 자체 `allowFrom`이 설정되지 않은 경우 `channels.slack.allowFrom`을 상속합니다. + - 이름 있는 계정은 `channels.slack.accounts.default.allowFrom`을 상속하지 않습니다. + + DM의 페어링은 `openclaw pairing approve slack `를 사용합니다. + + + + + `channels.slack.groupPolicy`가 채널 처리를 제어합니다. + + - `open` + - `allowlist` + - `disabled` + + 채널 allowlist는 `channels.slack.channels` 아래에 있으며, 안정적인 채널 ID를 사용해야 합니다. + + 런타임 참고: `channels.slack`이 완전히 없는 경우(환경 변수 전용 설정), 런타임은 `channels.defaults.groupPolicy`가 설정되어 있더라도 `groupPolicy="allowlist"`로 대체하고 경고를 기록합니다. + + 이름/ID 해석: + + - 채널 allowlist 항목과 DM allowlist 항목은 토큰 접근이 허용되면 시작 시 해석됩니다 + - 해석되지 않은 채널 이름 항목은 구성된 상태로 유지되지만 기본적으로 라우팅에서는 무시됩니다 + - 인바운드 권한 부여와 채널 라우팅은 기본적으로 ID 우선이며, 직접 사용자 이름/슬러그 일치를 사용하려면 `channels.slack.dangerouslyAllowNameMatching: true`가 필요합니다 + + + + + 채널 메시지는 기본적으로 멘션 게이팅이 적용됩니다. + + 멘션 소스: + + - 명시적 앱 멘션(`<@botId>`) + - 멘션 정규식 패턴(`agents.list[].groupChat.mentionPatterns`, 대체값 `messages.groupChat.mentionPatterns`) + - 암묵적인 봇 답글 스레드 동작 + + 채널별 제어(`channels.slack.channels.`; 이름은 시작 시 해석 또는 `dangerouslyAllowNameMatching`을 통해서만 가능): + + - `requireMention` + - `users`(allowlist) + - `allowBots` + - `skills` + - `systemPrompt` + - `tools`, `toolsBySender` + - `toolsBySender` 키 형식: `id:`, `e164:`, `username:`, `name:`, 또는 `"*"` 와일드카드 + (레거시 접두사 없는 키도 여전히 `id:` 전용으로 매핑됨) + + + + +## 스레딩, 세션 및 답글 태그 + +- DM은 `direct`로, 채널은 `channel`로, MPIM은 `group`으로 라우팅됩니다. +- 기본 `session.dmScope=main`에서는 Slack DM이 에이전트 메인 세션으로 합쳐집니다. +- 채널 세션: `agent::slack:channel:`. +- 스레드 답글은 해당되는 경우 스레드 세션 접미사(`:thread:`)를 만들 수 있습니다. +- `channels.slack.thread.historyScope` 기본값은 `thread`이고, `thread.inheritParent` 기본값은 `false`입니다. +- `channels.slack.thread.initialHistoryLimit`는 새 스레드 세션이 시작될 때 가져오는 기존 스레드 메시지 수를 제어합니다(기본값 `20`; 비활성화하려면 `0` 설정). + +답글 스레딩 제어: + +- `channels.slack.replyToMode`: `off|first|all`(기본값 `off`) +- `channels.slack.replyToModeByChatType`: `direct|group|channel`별 +- 직접 채팅용 레거시 대체: `channels.slack.dm.replyToMode` + +수동 답글 태그가 지원됩니다. + +- `[[reply_to_current]]` +- `[[reply_to:]]` + +참고: `replyToMode="off"`는 Slack의 **모든** 답글 스레딩을 비활성화하며, 명시적인 `[[reply_to_*]]` 태그도 포함됩니다. 이는 명시적 태그가 `"off"` 모드에서도 계속 존중되는 Telegram과 다릅니다. 이 차이는 플랫폼의 스레딩 모델을 반영합니다. Slack 스레드는 채널에서 메시지를 숨기지만, Telegram 답글은 메인 채팅 흐름에서 계속 보입니다. + +## 확인 반응 + +`ackReaction`은 OpenClaw가 인바운드 메시지를 처리하는 동안 확인 이모지를 전송합니다. + +해결 순서: + +- `channels.slack.accounts..ackReaction` +- `channels.slack.ackReaction` +- `messages.ackReaction` +- 에이전트 신원 이모지 대체값(`agents.list[].identity.emoji`, 없으면 "👀") + +참고: + +- Slack은 shortcode를 기대합니다(예: `"eyes"`). +- Slack 계정 또는 전역에서 반응을 비활성화하려면 `""`를 사용하세요. + +## 텍스트 스트리밍 + +`channels.slack.streaming`은 라이브 미리보기 동작을 제어합니다. + +- `off`: 라이브 미리보기 스트리밍 비활성화 +- `partial`(기본값): 미리보기 텍스트를 최신 부분 출력으로 교체 +- `block`: 청크 단위 미리보기 업데이트를 추가 +- `progress`: 생성 중 진행 상태 텍스트를 표시한 다음 최종 텍스트 전송 + +`channels.slack.nativeStreaming`은 `streaming`이 `partial`일 때 Slack 네이티브 텍스트 스트리밍을 제어합니다(기본값: `true`). + +- 네이티브 텍스트 스트리밍이 표시되려면 답글 스레드를 사용할 수 있어야 합니다. 스레드 선택은 여전히 `replyToMode`를 따릅니다. 스레드가 없으면 일반 초안 미리보기가 사용됩니다. +- 미디어 및 비텍스트 페이로드는 일반 전달로 대체됩니다. +- 스트리밍이 답글 도중 실패하면 OpenClaw는 남은 페이로드에 대해 일반 전달로 대체합니다. + +Slack 네이티브 텍스트 스트리밍 대신 초안 미리보기를 사용하려면: + +```json5 +{ + channels: { + slack: { + streaming: "partial", + nativeStreaming: false, + }, + }, +} +``` + +레거시 키: + +- `channels.slack.streamMode`(`replace | status_final | append`)는 자동으로 `channels.slack.streaming`으로 마이그레이션됩니다. +- 불리언 `channels.slack.streaming`은 자동으로 `channels.slack.nativeStreaming`으로 마이그레이션됩니다. + +## 타이핑 반응 대체 + +`typingReaction`은 OpenClaw가 답글을 처리하는 동안 인바운드 Slack 메시지에 임시 반응을 추가하고, 실행이 끝나면 제거합니다. 이는 기본 "is typing..." 상태 표시기를 사용하는 스레드 답글 외부에서 특히 유용합니다. + +해결 순서: + +- `channels.slack.accounts..typingReaction` +- `channels.slack.typingReaction` + +참고: + +- Slack은 shortcode를 기대합니다(예: `"hourglass_flowing_sand"`). +- 이 반응은 best-effort이며, 답글 또는 실패 경로가 완료된 뒤 정리가 자동으로 시도됩니다. + +## 미디어, 청크 분할 및 전달 + + + + Slack 파일 첨부는 Slack이 호스팅하는 비공개 URL에서 다운로드되며(토큰 인증 요청 흐름), 가져오기에 성공하고 크기 제한을 충족하면 미디어 저장소에 기록됩니다. + + 런타임 인바운드 크기 상한은 `channels.slack.mediaMaxMb`로 재정의하지 않는 한 기본적으로 `20MB`입니다. + + + + + - 텍스트 청크는 `channels.slack.textChunkLimit`을 사용합니다(기본값 4000) + - `channels.slack.chunkMode="newline"`은 문단 우선 분할을 활성화합니다 + - 파일 전송은 Slack 업로드 API를 사용하며 스레드 답글(`thread_ts`)을 포함할 수 있습니다 + - 아웃바운드 미디어 상한은 구성된 경우 `channels.slack.mediaMaxMb`를 따르며, 그렇지 않으면 채널 전송은 미디어 파이프라인의 MIME 종류 기본값을 사용합니다 + + + + 권장되는 명시적 대상: + + - DM용 `user:` + - 채널용 `channel:` + + Slack DM은 사용자 대상에 전송할 때 Slack 대화 API를 통해 열립니다. + + + + +## 명령 및 슬래시 동작 + +- Slack에서는 네이티브 명령 자동 모드가 **꺼짐**입니다(`commands.native: "auto"`는 Slack 네이티브 명령을 활성화하지 않음). +- `channels.slack.commands.native: true`(또는 전역 `commands.native: true`)로 네이티브 Slack 명령 핸들러를 활성화하세요. +- 네이티브 명령이 활성화되면, Slack에 일치하는 슬래시 명령(`/` 이름)을 등록하세요. 단, 예외가 하나 있습니다. + - 상태 명령에는 `/agentstatus`를 등록하세요(Slack은 `/status`를 예약함) +- 네이티브 명령이 활성화되지 않은 경우, `channels.slack.slashCommand`를 통해 하나의 구성된 슬래시 명령을 실행할 수 있습니다. +- 네이티브 인수 메뉴는 이제 렌더링 전략을 적응적으로 선택합니다. + - 최대 5개 옵션: 버튼 블록 + - 6-100개 옵션: 정적 선택 메뉴 + - 100개 초과 옵션: interactivity 옵션 핸들러를 사용할 수 있을 때 비동기 옵션 필터링이 있는 외부 선택 + - 인코딩된 옵션 값이 Slack 제한을 초과하면 흐름은 버튼으로 대체됩니다 +- 긴 옵션 페이로드의 경우, Slash command 인수 메뉴는 선택한 값을 디스패치하기 전에 확인 대화상자를 사용합니다. + +기본 슬래시 명령 설정: + +- `enabled: false` +- `name: "openclaw"` +- `sessionPrefix: "slack:slash"` +- `ephemeral: true` + +슬래시 세션은 격리된 키를 사용합니다. + +- `agent::slack:slash:` + +또한 명령 실행은 대상 대화 세션(`CommandTargetSessionKey`)에 대해 계속 라우팅됩니다. + +## 대화형 답글 + +Slack은 에이전트가 작성한 대화형 답글 제어를 렌더링할 수 있지만, 이 기능은 기본적으로 비활성화되어 있습니다. + +전역으로 활성화: + +```json5 +{ + channels: { + slack: { + capabilities: { + interactiveReplies: true, + }, + }, + }, +} +``` + +또는 하나의 Slack 계정에 대해서만 활성화: + +```json5 +{ + channels: { + slack: { + accounts: { + ops: { + capabilities: { + interactiveReplies: true, + }, + }, + }, + }, + }, +} +``` + +활성화되면 에이전트는 Slack 전용 답글 지시문을 출력할 수 있습니다. + +- `[[slack_buttons: Approve:approve, Reject:reject]]` +- `[[slack_select: Choose a target | Canary:canary, Production:production]]` + +이 지시문은 Slack Block Kit으로 컴파일되며, 클릭 또는 선택은 기존 Slack 상호작용 이벤트 경로를 통해 다시 라우팅됩니다. + +참고: + +- 이것은 Slack 전용 UI입니다. 다른 채널은 Slack Block Kit 지시문을 자체 버튼 시스템으로 번역하지 않습니다. +- 대화형 콜백 값은 에이전트가 작성한 원시 값이 아니라 OpenClaw가 생성한 불투명 토큰입니다. +- 생성된 대화형 블록이 Slack Block Kit 제한을 초과하면, OpenClaw는 잘못된 blocks 페이로드를 보내는 대신 원래 텍스트 답글로 대체합니다. + +## Slack의 exec 승인 + +Slack은 Web UI나 터미널로 대체하는 대신, 대화형 버튼과 상호작용을 갖춘 네이티브 승인 클라이언트로 동작할 수 있습니다. + +- Exec 승인은 네이티브 DM/채널 라우팅을 위해 `channels.slack.execApprovals.*`를 사용합니다. +- 플러그인 승인은 요청이 이미 Slack에 도착했고 승인 ID 종류가 `plugin:`일 때 동일한 Slack 네이티브 버튼 표면을 통해 계속 해석될 수 있습니다. +- 승인자 권한 부여는 여전히 강제됩니다. 승인자로 식별된 사용자만 Slack을 통해 요청을 승인하거나 거부할 수 있습니다. + +이는 다른 채널과 동일한 공유 승인 버튼 표면을 사용합니다. Slack 앱 설정에서 `interactivity`가 활성화되어 있으면, 승인 프롬프트가 대화에 직접 Block Kit 버튼으로 렌더링됩니다. +이 버튼이 있을 때는 그것이 기본 승인 UX가 되며, OpenClaw는 도구 결과에서 채팅 승인을 사용할 수 없거나 수동 승인이 유일한 경로라고 표시하는 경우에만 수동 `/approve` 명령을 포함해야 합니다. + +구성 경로: + +- `channels.slack.execApprovals.enabled` +- `channels.slack.execApprovals.approvers`(선택 사항, 가능하면 `commands.ownerAllowFrom`으로 대체) +- `channels.slack.execApprovals.target`(`dm` | `channel` | `both`, 기본값: `dm`) +- `agentFilter`, `sessionFilter` + +Slack은 `enabled`가 설정되지 않았거나 `"auto"`이고 최소 한 명의 승인자가 해석되면 네이티브 exec 승인을 자동으로 활성화합니다. Slack을 네이티브 승인 클라이언트로 명시적으로 비활성화하려면 `enabled: false`로 설정하세요. +승인자가 해석될 때 네이티브 승인을 강제로 켜려면 `enabled: true`로 설정하세요. + +명시적인 Slack exec 승인 구성이 없을 때의 기본 동작: + +```json5 +{ + commands: { + ownerAllowFrom: ["slack:U12345678"], + }, +} +``` + +명시적인 Slack 네이티브 구성은 승인자를 재정의하거나, 필터를 추가하거나, origin-chat 전달을 선택하려는 경우에만 필요합니다. + +```json5 +{ + channels: { + slack: { + execApprovals: { + enabled: true, + approvers: ["U12345678"], + target: "both", + }, + }, + }, +} +``` + +공유 `approvals.exec` 전달은 별개입니다. exec 승인 프롬프트를 다른 채팅이나 명시적인 대역 외 대상으로도 라우팅해야 할 때만 사용하세요. 공유 `approvals.plugin` 전달도 별개이며, Slack 네이티브 버튼은 해당 요청이 이미 Slack에 도착한 경우 계속 플러그인 승인을 해석할 수 있습니다. + +동일 채팅의 `/approve`도 이미 명령을 지원하는 Slack 채널과 DM에서 동작합니다. 전체 승인 전달 모델은 [Exec approvals](/tools/exec-approvals)를 참조하세요. + +## 이벤트 및 운영 동작 + +- 메시지 편집/삭제/스레드 브로드캐스트는 시스템 이벤트로 매핑됩니다. +- 반응 추가/제거 이벤트는 시스템 이벤트로 매핑됩니다. +- 멤버 참여/퇴장, 채널 생성/이름 변경, 핀 추가/제거 이벤트는 시스템 이벤트로 매핑됩니다. +- `channel_id_changed`는 `configWrites`가 활성화된 경우 채널 구성 키를 마이그레이션할 수 있습니다. +- 채널 topic/purpose 메타데이터는 신뢰할 수 없는 컨텍스트로 취급되며 라우팅 컨텍스트에 주입될 수 있습니다. +- 스레드 시작자와 초기 스레드 기록 컨텍스트 시딩은 해당되는 경우 구성된 발신자 allowlist로 필터링됩니다. +- 블록 작업 및 모달 상호작용은 풍부한 페이로드 필드와 함께 구조화된 `Slack interaction: ...` 시스템 이벤트를 발생시킵니다. + - 블록 작업: 선택된 값, 라벨, picker 값, `workflow_*` 메타데이터 + - 라우팅된 채널 메타데이터와 폼 입력이 포함된 모달 `view_submission` 및 `view_closed` 이벤트 + +## 구성 참조 포인터 + +기본 참조: + +- [구성 참조 - Slack](/gateway/configuration-reference#slack) + + 신호가 강한 Slack 필드: + - 모드/인증: `mode`, `botToken`, `appToken`, `signingSecret`, `webhookPath`, `accounts.*` + - DM 접근: `dm.enabled`, `dmPolicy`, `allowFrom`(레거시: `dm.policy`, `dm.allowFrom`), `dm.groupEnabled`, `dm.groupChannels` + - 호환성 토글: `dangerouslyAllowNameMatching`(비상용, 필요하지 않다면 꺼두기) + - 채널 접근: `groupPolicy`, `channels.*`, `channels.*.users`, `channels.*.requireMention` + - 스레딩/기록: `replyToMode`, `replyToModeByChatType`, `thread.*`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit` + - 전달: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `streaming`, `nativeStreaming` + - 운영/기능: `configWrites`, `commands.native`, `slashCommand.*`, `actions.*`, `userToken`, `userTokenReadOnly` + +## 문제 해결 + + + + 다음 순서로 확인하세요. + + - `groupPolicy` + - 채널 allowlist(`channels.slack.channels`) + - `requireMention` + - 채널별 `users` allowlist + + 유용한 명령: + +```bash +openclaw channels status --probe +openclaw logs --follow +openclaw doctor +``` + + + + + 다음을 확인하세요. + + - `channels.slack.dm.enabled` + - `channels.slack.dmPolicy`(또는 레거시 `channels.slack.dm.policy`) + - 페어링 승인 / allowlist 항목 + +```bash +openclaw pairing list slack +``` + + + + + 봇 토큰 + 앱 토큰, 그리고 Slack 앱 설정의 Socket Mode 활성화를 검증하세요. + + `openclaw channels status --probe --json`에 `botTokenStatus` 또는 + `appTokenStatus: "configured_unavailable"`가 표시되면, Slack 계정은 + 구성되어 있지만 현재 런타임이 SecretRef 기반 값을 해석할 수 없었다는 뜻입니다. + + + + + 다음을 검증하세요. + + - signing secret + - webhook 경로 + - Slack Request URL(Events + Interactivity + Slash Commands) + - HTTP 계정별 고유 `webhookPath` + + 계정 스냅샷에 `signingSecretStatus: "configured_unavailable"`가 나타나면, + HTTP 계정은 구성되어 있지만 현재 런타임이 SecretRef 기반 signing secret을 + 해석할 수 없었다는 뜻입니다. + + + + + 의도한 방식이 다음 중 어느 것인지 확인하세요. + + - Slack에 일치하는 슬래시 명령을 등록한 네이티브 명령 모드(`channels.slack.commands.native: true`) + - 또는 단일 슬래시 명령 모드(`channels.slack.slashCommand.enabled: true`) + + 또한 `commands.useAccessGroups`와 채널/사용자 allowlist도 확인하세요. + + + + +## 관련 항목 + +- [페어링](/channels/pairing) +- [그룹](/channels/groups) +- [Security](/gateway/security) +- [채널 라우팅](/channels/channel-routing) +- [문제 해결](/channels/troubleshooting) +- [구성](/gateway/configuration) +- [슬래시 명령](/tools/slash-commands) diff --git a/docs/ko/channels/synology-chat.md b/docs/ko/channels/synology-chat.md new file mode 100644 index 000000000..63d7b2265 --- /dev/null +++ b/docs/ko/channels/synology-chat.md @@ -0,0 +1,190 @@ +--- +read_when: + - OpenClaw와 함께 Synology Chat를 설정할 때 + - Synology Chat 웹훅 라우팅을 디버깅할 때 +summary: Synology Chat 웹훅 설정 및 OpenClaw 구성 +title: Synology Chat +x-i18n: + generated_at: "2026-04-05T12:36:17Z" + model: gpt-5.4 + provider: openai + source_hash: ddb25fc6b53f896f15f43b4936d69ea071a29a91838a5b662819377271e89d81 + source_path: channels/synology-chat.md + workflow: 15 +--- + +# Synology Chat + +상태: Synology Chat 웹훅을 사용하는 번들 plugin 다이렉트 메시지 채널입니다. +이 plugin은 Synology Chat 아웃고잉 웹훅에서 수신 메시지를 받아들이고 +Synology Chat 인커밍 웹훅을 통해 응답을 전송합니다. + +## 번들 plugin + +Synology Chat는 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로, 일반적인 +패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드 또는 Synology Chat가 제외된 사용자 지정 설치를 사용 중이라면, +수동으로 설치하세요: + +로컬 체크아웃에서 설치: + +```bash +openclaw plugins install ./path/to/local/synology-chat-plugin +``` + +자세한 내용: [Plugins](/tools/plugin) + +## 빠른 설정 + +1. Synology Chat plugin을 사용할 수 있는지 확인합니다. + - 현재 패키지된 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/사용자 지정 설치에서는 위 명령으로 소스 체크아웃에서 수동 추가할 수 있습니다. + - 이제 `openclaw onboard`는 `openclaw channels add`와 동일한 채널 설정 목록에 Synology Chat를 표시합니다. + - 비대화형 설정: `openclaw channels add --channel synology-chat --token --url ` +2. Synology Chat 통합에서 다음을 수행합니다: + - 인커밍 웹훅을 만들고 URL을 복사합니다. + - 비밀 토큰으로 아웃고잉 웹훅을 만듭니다. +3. 아웃고잉 웹훅 URL이 OpenClaw 게이트웨이를 가리키도록 설정합니다: + - 기본값: `https://gateway-host/webhook/synology` + - 또는 사용자 지정 `channels.synology-chat.webhookPath` +4. OpenClaw에서 설정을 완료합니다. + - 가이드 방식: `openclaw onboard` + - 직접 방식: `openclaw channels add --channel synology-chat --token --url ` +5. 게이트웨이를 재시작하고 Synology Chat 봇에 DM을 보냅니다. + +웹훅 인증 세부 정보: + +- OpenClaw는 먼저 `body.token`에서, 그다음 `?token=...`에서, 그다음 헤더에서 아웃고잉 웹훅 토큰을 받아들입니다. +- 허용되는 헤더 형식: + - `x-synology-token` + - `x-webhook-token` + - `x-openclaw-token` + - `Authorization: Bearer ` +- 비어 있거나 누락된 토큰은 fail-closed 방식으로 거부됩니다. + +최소 구성: + +```json5 +{ + channels: { + "synology-chat": { + enabled: true, + token: "synology-outgoing-token", + incomingUrl: "https://nas.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=...", + webhookPath: "/webhook/synology", + dmPolicy: "allowlist", + allowedUserIds: ["123456"], + rateLimitPerMinute: 30, + allowInsecureSsl: false, + }, + }, +} +``` + +## 환경 변수 + +기본 계정에는 다음 env var를 사용할 수 있습니다: + +- `SYNOLOGY_CHAT_TOKEN` +- `SYNOLOGY_CHAT_INCOMING_URL` +- `SYNOLOGY_NAS_HOST` +- `SYNOLOGY_ALLOWED_USER_IDS` (쉼표로 구분) +- `SYNOLOGY_RATE_LIMIT` +- `OPENCLAW_BOT_NAME` + +config 값이 env var보다 우선합니다. + +## DM 정책 및 액세스 제어 + +- `dmPolicy: "allowlist"`가 권장 기본값입니다. +- `allowedUserIds`는 Synology 사용자 ID 목록(또는 쉼표로 구분된 문자열)을 받습니다. +- `allowlist` 모드에서 빈 `allowedUserIds` 목록은 잘못된 구성으로 처리되며 웹훅 라우트가 시작되지 않습니다(모두 허용하려면 `dmPolicy: "open"` 사용). +- `dmPolicy: "open"`은 모든 발신자를 허용합니다. +- `dmPolicy: "disabled"`는 DM을 차단합니다. +- 응답 수신자 바인딩은 기본적으로 안정적인 숫자 `user_id`에 유지됩니다. `channels.synology-chat.dangerouslyAllowNameMatching: true`는 변경 가능한 사용자 이름/닉네임 조회를 통한 응답 전달을 다시 활성화하는 비상 호환 모드입니다. +- 페어링 승인은 다음과 함께 작동합니다: + - `openclaw pairing list synology-chat` + - `openclaw pairing approve synology-chat ` + +## 아웃바운드 전달 + +대상으로 숫자 Synology Chat 사용자 ID를 사용하세요. + +예시: + +```bash +openclaw message send --channel synology-chat --target 123456 --text "Hello from OpenClaw" +openclaw message send --channel synology-chat --target synology-chat:123456 --text "Hello again" +``` + +미디어 전송은 URL 기반 파일 전달로 지원됩니다. + +## 다중 계정 + +여러 Synology Chat 계정이 `channels.synology-chat.accounts` 아래에서 지원됩니다. +각 계정은 토큰, 인커밍 URL, 웹훅 경로, DM 정책 및 제한을 재정의할 수 있습니다. +다이렉트 메시지 세션은 계정 및 사용자별로 격리되므로, 서로 다른 두 Synology 계정에서 동일한 숫자 `user_id`를 사용하더라도 대화 기록 상태를 공유하지 않습니다. +활성화된 각 계정에는 고유한 `webhookPath`를 지정하세요. 이제 OpenClaw는 중복되는 정확한 경로를 거부하며, +다중 계정 설정에서 공유 웹훅 경로만 상속하는 이름 있는 계정은 시작을 거부합니다. +이름 있는 계정에 의도적으로 레거시 상속이 필요하다면 +해당 계정 또는 `channels.synology-chat`에 `dangerouslyAllowInheritedWebhookPath: true`를 설정하세요. +단, 중복되는 정확한 경로는 여전히 fail-closed 방식으로 거부됩니다. 계정별 명시적 경로를 권장합니다. + +```json5 +{ + channels: { + "synology-chat": { + enabled: true, + accounts: { + default: { + token: "token-a", + incomingUrl: "https://nas-a.example.com/...token=...", + }, + alerts: { + token: "token-b", + incomingUrl: "https://nas-b.example.com/...token=...", + webhookPath: "/webhook/synology-alerts", + dmPolicy: "allowlist", + allowedUserIds: ["987654"], + }, + }, + }, + }, +} +``` + +## 보안 참고 사항 + +- `token`은 비밀로 유지하고 유출되면 교체하세요. +- 자체 서명된 로컬 NAS 인증서를 명시적으로 신뢰하는 경우가 아니라면 `allowInsecureSsl: false`를 유지하세요. +- 수신 웹훅 요청은 토큰 검증 및 발신자별 속도 제한이 적용됩니다. +- 잘못된 토큰 검사는 상수 시간 비밀 비교를 사용하며 fail-closed 방식으로 처리됩니다. +- 프로덕션에서는 `dmPolicy: "allowlist"`를 권장합니다. +- 레거시 사용자 이름 기반 응답 전달이 명시적으로 필요한 경우가 아니라면 `dangerouslyAllowNameMatching`을 끄세요. +- 다중 계정 설정에서 공유 경로 라우팅 위험을 명시적으로 수용하는 경우가 아니라면 `dangerouslyAllowInheritedWebhookPath`를 끄세요. + +## 문제 해결 + +- `Missing required fields (token, user_id, text)`: + - 아웃고잉 웹훅 페이로드에 필수 필드 중 하나가 없습니다 + - Synology가 헤더로 토큰을 보낸다면 게이트웨이/프록시가 해당 헤더를 보존하는지 확인하세요 +- `Invalid token`: + - 아웃고잉 웹훅 비밀이 `channels.synology-chat.token`과 일치하지 않습니다 + - 요청이 잘못된 계정/웹훅 경로로 들어오고 있습니다 + - 요청이 OpenClaw에 도달하기 전에 리버스 프록시가 토큰 헤더를 제거했습니다 +- `Rate limit exceeded`: + - 동일한 소스에서 너무 많은 잘못된 토큰 시도가 있으면 해당 소스가 일시적으로 차단될 수 있습니다 + - 인증된 발신자에게도 별도의 사용자별 메시지 속도 제한이 적용됩니다 +- `Allowlist is empty. Configure allowedUserIds or use dmPolicy=open.`: + - `dmPolicy="allowlist"`가 활성화되었지만 구성된 사용자가 없습니다 +- `User not authorized`: + - 발신자의 숫자 `user_id`가 `allowedUserIds`에 없습니다 + +## 관련 항목 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/telegram.md b/docs/ko/channels/telegram.md new file mode 100644 index 000000000..a63158ee6 --- /dev/null +++ b/docs/ko/channels/telegram.md @@ -0,0 +1,1065 @@ +--- +read_when: + - Telegram 기능 또는 webhook 작업 중 +summary: Telegram 봇 지원 상태, 기능 및 구성 +title: Telegram +x-i18n: + generated_at: "2026-04-05T12:38:31Z" + model: gpt-5.4 + provider: openai + source_hash: 39fbf328375fbc5d08ec2e3eed58b19ee0afa102010ecbc02e074a310ced157e + source_path: channels/telegram.md + workflow: 15 +--- + +# Telegram (Bot API) + +상태: grammY를 통한 봇 DM 및 그룹용 프로덕션 준비 완료. 롱 폴링이 기본 모드이며 webhook 모드는 선택 사항입니다. + + + + Telegram의 기본 DM 정책은 페어링입니다. + + + 채널 전반의 진단 및 복구 플레이북입니다. + + + 전체 채널 config 패턴과 예시입니다. + + + +## 빠른 설정 + + + + Telegram을 열고 **@BotFather**와 채팅하세요(핸들이 정확히 `@BotFather`인지 확인). + + `/newbot`을 실행하고 안내를 따른 뒤 토큰을 저장하세요. + + + + + +```json5 +{ + channels: { + telegram: { + enabled: true, + botToken: "123:abc", + dmPolicy: "pairing", + groups: { "*": { requireMention: true } }, + }, + }, +} +``` + + 환경 변수 대체: `TELEGRAM_BOT_TOKEN=...` (기본 계정만). + Telegram은 `openclaw channels login telegram`을 사용하지 **않습니다**. config/env에 토큰을 구성한 뒤 gateway를 시작하세요. + + + + + +```bash +openclaw gateway +openclaw pairing list telegram +openclaw pairing approve telegram +``` + + 페어링 코드는 1시간 후 만료됩니다. + + + + + 그룹에 봇을 추가한 다음 액세스 모델에 맞게 `channels.telegram.groups`와 `groupPolicy`를 설정하세요. + + + + +토큰 해석 순서는 계정 인식을 지원합니다. 실제로는 config 값이 환경 변수 대체보다 우선하며, `TELEGRAM_BOT_TOKEN`은 기본 계정에만 적용됩니다. + + +## Telegram 측 설정 + + + + Telegram 봇은 기본적으로 **개인정보 보호 모드**가 활성화되어 있어, 수신할 수 있는 그룹 메시지가 제한됩니다. + + 봇이 모든 그룹 메시지를 봐야 한다면 다음 중 하나를 수행하세요. + + - `/setprivacy`로 개인정보 보호 모드를 비활성화하거나 + - 봇을 그룹 관리자로 설정합니다. + + 개인정보 보호 모드를 전환한 경우, Telegram이 변경 사항을 적용할 수 있도록 각 그룹에서 봇을 제거했다가 다시 추가하세요. + + + + + 관리자 상태는 Telegram 그룹 설정에서 제어됩니다. + + 관리자 봇은 모든 그룹 메시지를 수신하므로 항상 활성화된 그룹 동작에 유용합니다. + + + + + + - 그룹 추가 허용/거부용 `/setjoingroups` + - 그룹 가시성 동작용 `/setprivacy` + + + + +## 액세스 제어 및 활성화 + + + + `channels.telegram.dmPolicy`는 다이렉트 메시지 액세스를 제어합니다. + + - `pairing` (기본값) + - `allowlist` (`allowFrom`에 발신자 ID가 최소 하나 필요) + - `open` (`allowFrom`에 `"*"` 포함 필요) + - `disabled` + + `channels.telegram.allowFrom`은 숫자형 Telegram 사용자 ID를 받습니다. `telegram:` / `tg:` 접두사는 허용되며 정규화됩니다. + 빈 `allowFrom`과 함께 `dmPolicy: "allowlist"`를 사용하면 모든 DM이 차단되며 config 유효성 검사에서 거부됩니다. + 온보딩은 `@username` 입력을 받아 숫자형 ID로 해석합니다. + 업그레이드 후 config에 `@username` allowlist 항목이 있다면, 이를 해석하려면 `openclaw doctor --fix`를 실행하세요(최선 시도 방식이며 Telegram 봇 토큰이 필요합니다). + 이전에 페어링 저장소 allowlist 파일에 의존했다면, `openclaw doctor --fix`가 allowlist 흐름에서 해당 항목을 `channels.telegram.allowFrom`으로 복구할 수 있습니다(예: `dmPolicy: "allowlist"`에 아직 명시적 ID가 없는 경우). + + 단일 소유자 봇의 경우, 이전 페어링 승인에 의존하는 대신 액세스 정책을 config에 안정적으로 유지하기 위해 명시적 숫자형 `allowFrom` ID와 함께 `dmPolicy: "allowlist"`를 사용하는 것을 권장합니다. + + 흔한 혼동: DM 페어링 승인은 "이 발신자가 어디서나 인증되었다"는 의미가 아닙니다. + 페어링은 DM 액세스만 부여합니다. 그룹 발신자 인증은 여전히 명시적 config allowlist에서 옵니다. + "한 번 인증되면 DM과 그룹 명령 모두 동작"하게 하려면 숫자형 Telegram 사용자 ID를 `channels.telegram.allowFrom`에 넣으세요. + + ### 내 Telegram 사용자 ID 찾기 + + 더 안전한 방법(서드파티 봇 없음): + + 1. 봇에 DM을 보냅니다. + 2. `openclaw logs --follow`를 실행합니다. + 3. `from.id`를 읽습니다. + + 공식 Bot API 방법: + +```bash +curl "https://api.telegram.org/bot/getUpdates" +``` + + 서드파티 방법(개인정보 보호 수준 낮음): `@userinfobot` 또는 `@getidsbot`. + + + + + 두 가지 제어가 함께 적용됩니다. + + 1. **어떤 그룹이 허용되는지** (`channels.telegram.groups`) + - `groups` config가 없는 경우: + - `groupPolicy: "open"`일 때: 어떤 그룹이든 그룹 ID 검사 통과 가능 + - `groupPolicy: "allowlist"`(기본값)일 때: `groups` 항목(또는 `"*"`)을 추가할 때까지 그룹 차단 + - `groups`가 구성된 경우: allowlist 역할 수행(명시적 ID 또는 `"*"`) + + 2. **그룹에서 어떤 발신자가 허용되는지** (`channels.telegram.groupPolicy`) + - `open` + - `allowlist` (기본값) + - `disabled` + + `groupAllowFrom`은 그룹 발신자 필터링에 사용됩니다. 설정되지 않으면 Telegram은 `allowFrom`으로 대체합니다. + `groupAllowFrom` 항목은 숫자형 Telegram 사용자 ID여야 합니다(`telegram:` / `tg:` 접두사는 정규화됨). + Telegram 그룹 또는 슈퍼그룹 채팅 ID를 `groupAllowFrom`에 넣지 마세요. 음수 채팅 ID는 `channels.telegram.groups` 아래에 있어야 합니다. + 숫자가 아닌 항목은 발신자 인증에서 무시됩니다. + 보안 경계(`2026.2.25+`): 그룹 발신자 인증은 DM 페어링 저장소 승인을 **상속하지 않습니다**. + 페어링은 DM 전용으로 유지됩니다. 그룹의 경우 `groupAllowFrom` 또는 그룹별/토픽별 `allowFrom`을 설정하세요. + `groupAllowFrom`이 설정되지 않으면 Telegram은 페어링 저장소가 아니라 config의 `allowFrom`으로 대체합니다. + 단일 소유자 봇의 실용적인 패턴: 사용자 ID를 `channels.telegram.allowFrom`에 설정하고, `groupAllowFrom`은 비워 두며, 대상 그룹은 `channels.telegram.groups` 아래에서 허용하세요. + 런타임 참고: `channels.telegram`이 완전히 없으면, `channels.defaults.groupPolicy`가 명시적으로 설정되지 않은 한 런타임 기본값은 fail-closed `groupPolicy="allowlist"`입니다. + + 예시: 특정 그룹 하나에서 모든 멤버 허용: + +```json5 +{ + channels: { + telegram: { + groups: { + "-1001234567890": { + groupPolicy: "open", + requireMention: false, + }, + }, + }, + }, +} +``` + + 예시: 특정 그룹 하나에서 특정 사용자만 허용: + +```json5 +{ + channels: { + telegram: { + groups: { + "-1001234567890": { + requireMention: true, + allowFrom: ["8734062810", "745123456"], + }, + }, + }, + }, +} +``` + + + 흔한 실수: `groupAllowFrom`은 Telegram 그룹 allowlist가 아닙니다. + + - `-1001234567890` 같은 음수 Telegram 그룹 또는 슈퍼그룹 채팅 ID는 `channels.telegram.groups` 아래에 넣으세요. + - 허용된 그룹 안에서 봇을 트리거할 수 있는 사람을 제한하려면 `8734062810` 같은 Telegram 사용자 ID를 `groupAllowFrom` 아래에 넣으세요. + - 허용된 그룹의 아무 멤버나 봇과 대화할 수 있게 하려면 `groupAllowFrom: ["*"]`를 사용하세요. + + + + + + 그룹 응답은 기본적으로 멘션이 필요합니다. + + 멘션은 다음에서 올 수 있습니다. + + - 기본 `@botusername` 멘션 + - 또는 다음의 멘션 패턴: + - `agents.list[].groupChat.mentionPatterns` + - `messages.groupChat.mentionPatterns` + + 세션 수준 명령 토글: + + - `/activation always` + - `/activation mention` + + 이 명령은 세션 상태만 업데이트합니다. 영구 저장에는 config를 사용하세요. + + 영구 config 예시: + +```json5 +{ + channels: { + telegram: { + groups: { + "*": { requireMention: false }, + }, + }, + }, +} +``` + + 그룹 채팅 ID 가져오기: + + - 그룹 메시지를 `@userinfobot` / `@getidsbot`으로 전달 + - 또는 `openclaw logs --follow`에서 `chat.id` 읽기 + - 또는 Bot API `getUpdates` 검사 + + + + +## 런타임 동작 + +- Telegram은 gateway 프로세스가 소유합니다. +- 라우팅은 결정적입니다. Telegram 인바운드 메시지는 Telegram으로 응답합니다(모델이 채널을 선택하지 않음). +- 인바운드 메시지는 응답 메타데이터와 미디어 플레이스홀더를 포함하는 공유 채널 envelope로 정규화됩니다. +- 그룹 세션은 그룹 ID별로 격리됩니다. 포럼 토픽은 토픽 격리를 위해 `:topic:`를 덧붙입니다. +- DM 메시지는 `message_thread_id`를 포함할 수 있습니다. OpenClaw는 이를 스레드 인식 세션 키로 라우팅하고 응답 시 스레드 ID를 유지합니다. +- 롱 폴링은 채팅별/스레드별 순서를 보장하는 grammY runner를 사용합니다. 전체 runner sink 동시성은 `agents.defaults.maxConcurrent`를 사용합니다. +- Telegram Bot API에는 읽음 확인 지원이 없습니다(`sendReadReceipts`는 적용되지 않음). + +## 기능 참조 + + + + OpenClaw는 부분 응답을 실시간으로 스트리밍할 수 있습니다. + + - 다이렉트 채팅: 미리보기 메시지 + `editMessageText` + - 그룹/토픽: 미리보기 메시지 + `editMessageText` + + 요구 사항: + + - `channels.telegram.streaming`이 `off | partial | block | progress`여야 합니다(기본값: `partial`) + - `progress`는 Telegram에서 `partial`로 매핑됩니다(채널 간 명명 호환성용) + - 레거시 `channels.telegram.streamMode`와 불리언 `streaming` 값은 자동 매핑됩니다 + + 텍스트 전용 응답의 경우: + + - DM: OpenClaw는 동일한 미리보기 메시지를 유지하고 최종 편집을 그 자리에서 수행합니다(두 번째 메시지 없음) + - 그룹/토픽: OpenClaw는 동일한 미리보기 메시지를 유지하고 최종 편집을 그 자리에서 수행합니다(두 번째 메시지 없음) + + 복합 응답(예: 미디어 payload)의 경우, OpenClaw는 일반 최종 전송으로 대체한 후 미리보기 메시지를 정리합니다. + + 미리보기 스트리밍은 블록 스트리밍과 별개입니다. Telegram에 대해 블록 스트리밍이 명시적으로 활성화되면 OpenClaw는 이중 스트리밍을 피하기 위해 미리보기 스트림을 건너뜁니다. + + 기본 초안 전송이 사용할 수 없거나 거부되면 OpenClaw는 자동으로 `sendMessage` + `editMessageText`로 대체합니다. + + Telegram 전용 reasoning 스트림: + + - `/reasoning stream`은 생성 중 reasoning을 실시간 미리보기로 보냅니다 + - 최종 답변은 reasoning 텍스트 없이 전송됩니다 + + + + + 아웃바운드 텍스트는 Telegram `parse_mode: "HTML"`을 사용합니다. + + - Markdown 유사 텍스트는 Telegram에 안전한 HTML로 렌더링됩니다. + - 원시 모델 HTML은 Telegram 파싱 실패를 줄이기 위해 이스케이프됩니다. + - Telegram이 파싱된 HTML을 거부하면 OpenClaw는 일반 텍스트로 재시도합니다. + + 링크 미리보기는 기본적으로 활성화되어 있으며 `channels.telegram.linkPreview: false`로 비활성화할 수 있습니다. + + + + + Telegram 명령 메뉴 등록은 시작 시 `setMyCommands`로 처리됩니다. + + 기본 네이티브 명령: + + - `commands.native: "auto"`는 Telegram에 대해 기본 명령을 활성화합니다 + + 사용자 지정 명령 메뉴 항목 추가: + +```json5 +{ + channels: { + telegram: { + customCommands: [ + { command: "backup", description: "Git 백업" }, + { command: "generate", description: "이미지 생성" }, + ], + }, + }, +} +``` + + 규칙: + + - 이름은 정규화됩니다(앞의 `/` 제거, 소문자화) + - 유효 패턴: `a-z`, `0-9`, `_`, 길이 `1..32` + - 사용자 지정 명령은 기본 명령을 재정의할 수 없습니다 + - 충돌/중복은 건너뛰고 로그에 기록됩니다 + + 참고: + + - 사용자 지정 명령은 메뉴 항목일 뿐이며 동작을 자동 구현하지 않습니다 + - plugin/Skills 명령은 Telegram 메뉴에 표시되지 않아도 직접 입력하면 여전히 동작할 수 있습니다 + + 네이티브 명령이 비활성화되면 기본 명령은 제거됩니다. 사용자 지정/plugin 명령은 구성된 경우 여전히 등록될 수 있습니다. + + 일반적인 설정 실패: + + - `setMyCommands failed`와 함께 `BOT_COMMANDS_TOO_MUCH`가 표시되면, 잘라낸 후에도 Telegram 메뉴가 여전히 넘친다는 뜻입니다. plugin/skill/사용자 지정 명령을 줄이거나 `channels.telegram.commands.native`를 비활성화하세요. + - `setMyCommands failed`와 함께 네트워크/fetch 오류가 표시되면 보통 `api.telegram.org`에 대한 아웃바운드 DNS/HTTPS가 차단된 것입니다. + + ### 장치 페어링 명령 (`device-pair` plugin) + + `device-pair` plugin이 설치된 경우: + + 1. `/pair`가 설정 코드를 생성합니다 + 2. iOS 앱에 코드를 붙여넣습니다 + 3. `/pair pending`이 대기 중 요청을 나열합니다(역할/범위 포함) + 4. 요청을 승인합니다: + - 명시적 승인에는 `/pair approve ` + - 대기 중 요청이 하나뿐이면 `/pair approve` + - 가장 최근 요청에는 `/pair approve latest` + + 설정 코드는 짧은 수명의 bootstrap 토큰을 포함합니다. 내장 bootstrap handoff는 기본 노드 토큰을 `scopes: []`로 유지합니다. handoff된 operator 토큰은 `operator.approvals`, `operator.read`, `operator.talk.secrets`, `operator.write` 범위로 제한됩니다. Bootstrap 범위 검사는 역할 접두사를 사용하므로, 해당 operator allowlist는 operator 요청만 충족하며 비-operator 역할은 여전히 자체 역할 접두사 아래 범위가 필요합니다. + + 장치가 변경된 인증 세부 정보(예: 역할/범위/공개 키)로 재시도하면 이전 대기 요청은 대체되고 새 요청은 다른 `requestId`를 사용합니다. 승인 전에 `/pair pending`을 다시 실행하세요. + + 자세한 내용: [페어링](/channels/pairing#pair-via-telegram-recommended-for-ios). + + + + + 인라인 키보드 범위를 구성합니다: + +```json5 +{ + channels: { + telegram: { + capabilities: { + inlineButtons: "allowlist", + }, + }, + }, +} +``` + + 계정별 재정의: + +```json5 +{ + channels: { + telegram: { + accounts: { + main: { + capabilities: { + inlineButtons: "allowlist", + }, + }, + }, + }, + }, +} +``` + + 범위: + + - `off` + - `dm` + - `group` + - `all` + - `allowlist` (기본값) + + 레거시 `capabilities: ["inlineButtons"]`는 `inlineButtons: "all"`로 매핑됩니다. + + 메시지 작업 예시: + +```json5 +{ + action: "send", + channel: "telegram", + to: "123456789", + message: "옵션을 선택하세요:", + buttons: [ + [ + { text: "예", callback_data: "yes" }, + { text: "아니요", callback_data: "no" }, + ], + [{ text: "취소", callback_data: "cancel" }], + ], +} +``` + + 콜백 클릭은 텍스트로 agent에 전달됩니다: + `callback_data: ` + + + + + Telegram 도구 작업은 다음을 포함합니다. + + - `sendMessage` (`to`, `content`, 선택적 `mediaUrl`, `replyToMessageId`, `messageThreadId`) + - `react` (`chatId`, `messageId`, `emoji`) + - `deleteMessage` (`chatId`, `messageId`) + - `editMessage` (`chatId`, `messageId`, `content`) + - `createForumTopic` (`chatId`, `name`, 선택적 `iconColor`, `iconCustomEmojiId`) + + 채널 메시지 작업은 사용하기 쉬운 별칭을 노출합니다(`send`, `react`, `delete`, `edit`, `sticker`, `sticker-search`, `topic-create`). + + 게이팅 제어: + + - `channels.telegram.actions.sendMessage` + - `channels.telegram.actions.deleteMessage` + - `channels.telegram.actions.reactions` + - `channels.telegram.actions.sticker` (기본값: 비활성화) + + 참고: `edit`와 `topic-create`는 현재 기본적으로 활성화되어 있으며 별도의 `channels.telegram.actions.*` 토글이 없습니다. + 런타임 전송은 활성 config/secrets 스냅샷(시작/리로드 시점)을 사용하므로, 작업 경로는 전송마다 임시 SecretRef 재해석을 수행하지 않습니다. + + 반응 제거 의미 체계: [/tools/reactions](/tools/reactions) + + + + + Telegram은 생성된 출력에서 명시적 응답 스레딩 태그를 지원합니다. + + - `[[reply_to_current]]`는 트리거한 메시지에 응답합니다 + - `[[reply_to:]]`는 특정 Telegram 메시지 ID에 응답합니다 + + `channels.telegram.replyToMode`는 처리 방식을 제어합니다. + + - `off` (기본값) + - `first` + - `all` + + 참고: `off`는 암시적 응답 스레딩을 비활성화합니다. 명시적 `[[reply_to_*]]` 태그는 여전히 적용됩니다. + + + + + 포럼 슈퍼그룹: + + - 토픽 세션 키는 `:topic:`를 덧붙입니다 + - 응답과 입력 중 표시는 토픽 스레드를 대상으로 합니다 + - 토픽 config 경로: + `channels.telegram.groups..topics.` + + 일반 토픽(`threadId=1`) 특수 사례: + + - 메시지 전송은 `message_thread_id`를 생략합니다(Telegram은 `sendMessage(...thread_id=1)`을 거부함) + - 입력 중 동작에는 여전히 `message_thread_id`가 포함됩니다 + + 토픽 상속: 토픽 항목은 재정의되지 않는 한 그룹 설정을 상속합니다(`requireMention`, `allowFrom`, `skills`, `systemPrompt`, `enabled`, `groupPolicy`). + `agentId`는 토픽 전용이며 그룹 기본값에서 상속되지 않습니다. + + **토픽별 agent 라우팅**: 각 토픽은 토픽 config에서 `agentId`를 설정하여 다른 agent로 라우팅할 수 있습니다. 이를 통해 각 토픽은 자체 격리된 workspace, 메모리, 세션을 가질 수 있습니다. 예시: + + ```json5 + { + channels: { + telegram: { + groups: { + "-1001234567890": { + topics: { + "1": { agentId: "main" }, // General topic → main agent + "3": { agentId: "zu" }, // Dev topic → zu agent + "5": { agentId: "coder" } // Code review → coder agent + } + } + } + } + } + } + ``` + + 각 토픽은 다음과 같은 자체 세션 키를 갖게 됩니다: `agent:zu:telegram:group:-1001234567890:topic:3` + + **영구 ACP 토픽 바인딩**: 포럼 토픽은 최상위 타입 지정 ACP 바인딩을 통해 ACP harness 세션을 고정할 수 있습니다. + + - `type: "acp"` 및 `match.channel: "telegram"`이 있는 `bindings[]` + + 예시: + + ```json5 + { + agents: { + list: [ + { + id: "codex", + runtime: { + type: "acp", + acp: { + agent: "codex", + backend: "acpx", + mode: "persistent", + cwd: "/workspace/openclaw", + }, + }, + }, + ], + }, + bindings: [ + { + type: "acp", + agentId: "codex", + match: { + channel: "telegram", + accountId: "default", + peer: { kind: "group", id: "-1001234567890:topic:42" }, + }, + }, + ], + channels: { + telegram: { + groups: { + "-1001234567890": { + topics: { + "42": { + requireMention: false, + }, + }, + }, + }, + }, + }, + } + ``` + + 현재 이는 그룹 및 슈퍼그룹의 포럼 토픽에 한정됩니다. + + **채팅에서 스레드 바인딩 ACP 생성**: + + - `/acp spawn --thread here|auto`는 현재 Telegram 토픽을 새 ACP 세션에 바인딩할 수 있습니다. + - 후속 토픽 메시지는 바인딩된 ACP 세션으로 직접 라우팅됩니다(`/acp steer` 불필요). + - OpenClaw는 성공적으로 바인딩한 후 생성 확인 메시지를 토픽 내에 고정합니다. + - `channels.telegram.threadBindings.spawnAcpSessions=true`가 필요합니다. + + 템플릿 컨텍스트에는 다음이 포함됩니다. + + - `MessageThreadId` + - `IsForum` + + DM 스레드 동작: + + - `message_thread_id`가 있는 개인 채팅은 DM 라우팅을 유지하지만 스레드 인식 세션 키/응답 대상을 사용합니다. + + + + + ### 오디오 메시지 + + Telegram은 음성 노트와 오디오 파일을 구분합니다. + + - 기본값: 오디오 파일 동작 + - agent 응답에 `[[audio_as_voice]]` 태그를 넣으면 음성 노트 전송 강제 + + 메시지 작업 예시: + +```json5 +{ + action: "send", + channel: "telegram", + to: "123456789", + media: "https://example.com/voice.ogg", + asVoice: true, +} +``` + + ### 비디오 메시지 + + Telegram은 비디오 파일과 비디오 노트를 구분합니다. + + 메시지 작업 예시: + +```json5 +{ + action: "send", + channel: "telegram", + to: "123456789", + media: "https://example.com/video.mp4", + asVideoNote: true, +} +``` + + 비디오 노트는 캡션을 지원하지 않으므로, 제공된 메시지 텍스트는 별도로 전송됩니다. + + ### 스티커 + + 인바운드 스티커 처리: + + - 정적 WEBP: 다운로드 및 처리됨(플레이스홀더 ``) + - 애니메이션 TGS: 건너뜀 + - 비디오 WEBM: 건너뜀 + + 스티커 컨텍스트 필드: + + - `Sticker.emoji` + - `Sticker.setName` + - `Sticker.fileId` + - `Sticker.fileUniqueId` + - `Sticker.cachedDescription` + + 스티커 캐시 파일: + + - `~/.openclaw/telegram/sticker-cache.json` + + 스티커는 반복적인 vision 호출을 줄이기 위해 가능할 때 한 번 설명하고 캐시됩니다. + + 스티커 작업 활성화: + +```json5 +{ + channels: { + telegram: { + actions: { + sticker: true, + }, + }, + }, +} +``` + + 스티커 전송 작업: + +```json5 +{ + action: "sticker", + channel: "telegram", + to: "123456789", + fileId: "CAACAgIAAxkBAAI...", +} +``` + + 캐시된 스티커 검색: + +```json5 +{ + action: "sticker-search", + channel: "telegram", + query: "손 흔드는 고양이", + limit: 5, +} +``` + + + + + Telegram 반응은 `message_reaction` 업데이트로 도착합니다(메시지 payload와 별개). + + 활성화되면 OpenClaw는 다음과 같은 시스템 이벤트를 큐에 넣습니다. + + - `Telegram reaction added: 👍 by Alice (@alice) on msg 42` + + config: + + - `channels.telegram.reactionNotifications`: `off | own | all` (기본값: `own`) + - `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` (기본값: `minimal`) + + 참고: + + - `own`은 봇이 보낸 메시지에 대한 사용자 반응만 의미합니다(전송된 메시지 캐시를 통한 최선 시도). + - 반응 이벤트는 여전히 Telegram 액세스 제어(`dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`)를 따르며, 권한 없는 발신자는 버려집니다. + - Telegram은 반응 업데이트에 스레드 ID를 제공하지 않습니다. + - 비포럼 그룹은 그룹 채팅 세션으로 라우팅 + - 포럼 그룹은 정확한 원래 토픽이 아니라 그룹 일반 토픽 세션(`:topic:1`)으로 라우팅 + + 폴링/webhook용 `allowed_updates`에는 `message_reaction`이 자동으로 포함됩니다. + + + + + `ackReaction`은 OpenClaw가 인바운드 메시지를 처리하는 동안 확인 이모지를 보냅니다. + + 해석 순서: + + - `channels.telegram.accounts..ackReaction` + - `channels.telegram.ackReaction` + - `messages.ackReaction` + - agent identity 이모지 대체(`agents.list[].identity.emoji`, 없으면 "👀") + + 참고: + + - Telegram은 유니코드 이모지(예: "👀")를 기대합니다. + - 채널 또는 계정에서 반응을 비활성화하려면 `""`를 사용하세요. + + + + + 채널 config 쓰기는 기본적으로 활성화되어 있습니다(`configWrites !== false`). + + Telegram 트리거 쓰기에는 다음이 포함됩니다. + + - `channels.telegram.groups`를 업데이트하기 위한 그룹 마이그레이션 이벤트(`migrate_to_chat_id`) + - `/config set` 및 `/config unset`(명령 활성화 필요) + + 비활성화: + +```json5 +{ + channels: { + telegram: { + configWrites: false, + }, + }, +} +``` + + + + + 기본값: 롱 폴링. + + webhook 모드: + + - `channels.telegram.webhookUrl` 설정 + - `channels.telegram.webhookSecret` 설정(webhook URL이 설정된 경우 필수) + - 선택적 `channels.telegram.webhookPath` (기본값 `/telegram-webhook`) + - 선택적 `channels.telegram.webhookHost` (기본값 `127.0.0.1`) + - 선택적 `channels.telegram.webhookPort` (기본값 `8787`) + + webhook 모드의 기본 로컬 리스너는 `127.0.0.1:8787`에 바인딩됩니다. + + 공개 엔드포인트가 다르면 앞에 reverse proxy를 두고 `webhookUrl`을 공개 URL로 지정하세요. + 외부 인그레스가 의도적으로 필요한 경우 `webhookHost`(예: `0.0.0.0`)를 설정하세요. + + + + + - `channels.telegram.textChunkLimit` 기본값은 4000입니다. + - `channels.telegram.chunkMode="newline"`은 길이 기준 분할 전에 문단 경계(빈 줄)를 우선합니다. + - `channels.telegram.mediaMaxMb`(기본값 100)는 인바운드 및 아웃바운드 Telegram 미디어 크기를 제한합니다. + - `channels.telegram.timeoutSeconds`는 Telegram API 클라이언트 타임아웃을 재정의합니다(설정하지 않으면 grammY 기본값 적용). + - 그룹 컨텍스트 기록은 `channels.telegram.historyLimit` 또는 `messages.groupChat.historyLimit`(기본값 50)을 사용하며, `0`이면 비활성화됩니다. + - 응답/인용/전달 보조 컨텍스트는 현재 수신된 그대로 전달됩니다. + - Telegram allowlist는 주로 누가 agent를 트리거할 수 있는지 제어하며, 완전한 보조 컨텍스트 redaction 경계는 아닙니다. + - DM 기록 제어: + - `channels.telegram.dmHistoryLimit` + - `channels.telegram.dms[""].historyLimit` + - `channels.telegram.retry` config는 복구 가능한 아웃바운드 API 오류에 대해 Telegram 전송 헬퍼(CLI/tools/actions)에 적용됩니다. + + CLI 전송 대상은 숫자형 chat ID 또는 username일 수 있습니다: + +```bash +openclaw message send --channel telegram --target 123456789 --message "hi" +openclaw message send --channel telegram --target @name --message "hi" +``` + + Telegram 투표는 `openclaw message poll`을 사용하며 포럼 토픽을 지원합니다: + +```bash +openclaw message poll --channel telegram --target 123456789 \ + --poll-question "Ship it?" --poll-option "Yes" --poll-option "No" +openclaw message poll --channel telegram --target -1001234567890:topic:42 \ + --poll-question "Pick a time" --poll-option "10am" --poll-option "2pm" \ + --poll-duration-seconds 300 --poll-public +``` + + Telegram 전용 투표 플래그: + + - `--poll-duration-seconds` (5-600) + - `--poll-anonymous` + - `--poll-public` + - 포럼 토픽용 `--thread-id`(또는 `:topic:` 대상 사용) + + Telegram 전송은 다음도 지원합니다: + + - `channels.telegram.capabilities.inlineButtons`가 허용할 때 인라인 키보드용 `--buttons` + - 아웃바운드 이미지와 GIF를 압축 사진 또는 애니메이션 미디어 업로드 대신 문서로 보내기 위한 `--force-document` + + 작업 게이팅: + + - `channels.telegram.actions.sendMessage=false`는 투표를 포함한 아웃바운드 Telegram 메시지를 비활성화합니다 + - `channels.telegram.actions.poll=false`는 일반 전송은 유지한 채 Telegram 투표 생성을 비활성화합니다 + + + + + Telegram은 승인자 DM에서 exec 승인을 지원하며, 선택적으로 원래 채팅 또는 토픽에 승인 프롬프트를 게시할 수 있습니다. + + config 경로: + + - `channels.telegram.execApprovals.enabled` + - `channels.telegram.execApprovals.approvers` (선택 사항. 가능하면 `allowFrom` 및 direct `defaultTo`에서 추론된 숫자형 소유자 ID로 대체) + - `channels.telegram.execApprovals.target` (`dm` | `channel` | `both`, 기본값: `dm`) + - `agentFilter`, `sessionFilter` + + 승인자는 숫자형 Telegram 사용자 ID여야 합니다. `enabled`가 설정되지 않았거나 `"auto"`이고, `execApprovals.approvers` 또는 계정의 숫자형 소유자 config(`allowFrom` 및 direct-message `defaultTo`)에서 최소 하나의 승인자를 해석할 수 있으면 Telegram은 기본 exec 승인을 자동 활성화합니다. Telegram을 기본 승인 클라이언트로 명시적으로 비활성화하려면 `enabled: false`를 설정하세요. 그렇지 않으면 승인 요청은 다른 구성된 승인 경로 또는 exec 승인 대체 정책으로 폴백됩니다. + + Telegram은 다른 채팅 채널이 사용하는 공유 승인 버튼도 렌더링합니다. 기본 Telegram 어댑터는 주로 승인자 DM 라우팅, 채널/토픽 팬아웃, 전송 전 입력 중 힌트를 추가합니다. + 이러한 버튼이 있으면 그것이 기본 승인 UX이며, OpenClaw는 도구 결과가 채팅 승인을 사용할 수 없거나 수동 승인이 유일한 경로라고 말할 때만 수동 `/approve` 명령을 포함해야 합니다. + + 전송 규칙: + + - `target: "dm"`은 해석된 승인자 DM으로만 승인 프롬프트를 보냅니다 + - `target: "channel"`은 원래 Telegram 채팅/토픽으로 프롬프트를 다시 보냅니다 + - `target: "both"`는 승인자 DM과 원래 채팅/토픽 모두에 보냅니다 + + 해석된 승인자만 승인 또는 거부할 수 있습니다. 비승인자는 `/approve`를 사용할 수 없고 Telegram 승인 버튼도 사용할 수 없습니다. + + 승인 해석 동작: + + - `plugin:` 접두사가 있는 ID는 항상 plugin 승인으로 해석됩니다. + - 다른 승인 ID는 먼저 `exec.approval.resolve`를 시도합니다. + - Telegram이 plugin 승인에도 허용되어 있고 gateway가 exec 승인이 알 수 없거나 만료되었다고 말하면, Telegram은 한 번 `plugin.approval.resolve`를 통해 재시도합니다. + - 실제 exec 승인 거부/오류는 자동으로 plugin 승인 해석으로 넘어가지 않습니다. + + 채널 전송은 채팅에 명령 텍스트를 표시하므로, `channel` 또는 `both`는 신뢰할 수 있는 그룹/토픽에서만 활성화하세요. 포럼 토픽에 프롬프트가 도착하면 OpenClaw는 승인 프롬프트와 승인 후 후속 메시지 모두에 대해 해당 토픽을 유지합니다. Exec 승인의 기본 만료 시간은 30분입니다. + + 인라인 승인 버튼은 또한 `channels.telegram.capabilities.inlineButtons`가 대상 표면(`dm`, `group`, 또는 `all`)을 허용하는지에 따라 달라집니다. + + 관련 문서: [Exec 승인](/tools/exec-approvals) + + + + +## 오류 응답 제어 + +agent가 전송 또는 공급자 오류를 만나면 Telegram은 오류 텍스트로 응답하거나 이를 숨길 수 있습니다. 이 동작은 두 개의 config 키로 제어됩니다: + +| 키 | 값 | 기본값 | 설명 | +| ----------------------------------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------- | +| `channels.telegram.errorPolicy` | `reply`, `silent` | `reply` | `reply`는 채팅에 친화적인 오류 메시지를 보냅니다. `silent`는 오류 응답을 완전히 숨깁니다. | +| `channels.telegram.errorCooldownMs` | number (ms) | `60000` | 동일한 채팅에 대한 오류 응답 사이의 최소 시간입니다. 장애 중 오류 스팸을 방지합니다. | + +계정별, 그룹별, 토픽별 재정의를 지원합니다(다른 Telegram config 키와 동일한 상속 방식). + +```json5 +{ + channels: { + telegram: { + errorPolicy: "reply", + errorCooldownMs: 120000, + groups: { + "-1001234567890": { + errorPolicy: "silent", // 이 그룹에서는 오류 숨김 + }, + }, + }, + }, +} +``` + +## 문제 해결 + + + + + - `requireMention=false`인 경우 Telegram 개인정보 보호 모드가 전체 가시성을 허용해야 합니다. + - BotFather: `/setprivacy` -> 비활성화 + - 그런 다음 그룹에서 봇을 제거했다가 다시 추가 + - `openclaw channels status`는 config가 멘션 없는 그룹 메시지를 기대할 때 경고를 표시합니다. + - `openclaw channels status --probe`는 명시적 숫자형 그룹 ID를 검사할 수 있습니다. 와일드카드 `"*"`는 멤버십 probe를 할 수 없습니다. + - 빠른 세션 테스트: `/activation always`. + + + + + + - `channels.telegram.groups`가 존재하면, 해당 그룹이 목록에 있거나 `"*"`가 포함되어 있어야 합니다 + - 그룹 내 봇 멤버십 확인 + - 건너뛴 이유는 `openclaw logs --follow`에서 로그 검토 + + + + + + - 발신자 ID를 인증하세요(페어링 및/또는 숫자형 `allowFrom`) + - 그룹 정책이 `open`이더라도 명령 인증은 여전히 적용됩니다 + - `setMyCommands failed`와 함께 `BOT_COMMANDS_TOO_MUCH`가 표시되면 기본 메뉴 항목이 너무 많다는 뜻입니다. plugin/skill/사용자 지정 명령을 줄이거나 기본 메뉴를 비활성화하세요 + - `setMyCommands failed`와 함께 네트워크/fetch 오류가 표시되면 보통 `api.telegram.org`에 대한 DNS/HTTPS 도달성 문제를 의미합니다 + + + + + + - Node 22+와 사용자 지정 fetch/proxy 조합은 AbortSignal 타입이 맞지 않으면 즉시 abort 동작을 유발할 수 있습니다. + - 일부 호스트는 `api.telegram.org`를 먼저 IPv6로 해석하므로, IPv6 아웃바운드가 깨져 있으면 간헐적인 Telegram API 실패가 발생할 수 있습니다. + - 로그에 `TypeError: fetch failed` 또는 `Network request for 'getUpdates' failed!`가 포함되면, OpenClaw는 이제 이를 복구 가능한 네트워크 오류로 재시도합니다. + - 직접 아웃바운드/TLS가 불안정한 VPS 호스트에서는 Telegram API 호출을 `channels.telegram.proxy`를 통해 라우팅하세요: + +```yaml +channels: + telegram: + proxy: socks5://:@proxy-host:1080 +``` + + - Node 22+는 기본적으로 `autoSelectFamily=true`(WSL2 제외) 및 `dnsResultOrder=ipv4first`를 사용합니다. + - 호스트가 WSL2이거나 IPv4 전용 동작이 명시적으로 더 잘 맞는 경우, family selection을 강제하세요: + +```yaml +channels: + telegram: + network: + autoSelectFamily: false +``` + + - RFC 2544 벤치마크 범위 응답(`198.18.0.0/15`)은 기본적으로 Telegram 미디어 다운로드에 이미 허용되어 있습니다. 신뢰할 수 있는 가짜 IP 또는 transparent proxy가 미디어 다운로드 중 `api.telegram.org`를 다른 private/internal/special-use 주소로 재작성하는 경우, Telegram 전용 우회를 선택적으로 활성화할 수 있습니다: + +```yaml +channels: + telegram: + network: + dangerouslyAllowPrivateNetwork: true +``` + + - 동일한 선택 기능은 계정별로도 사용할 수 있습니다: + `channels.telegram.accounts..network.dangerouslyAllowPrivateNetwork`. + - proxy가 Telegram 미디어 호스트를 `198.18.x.x`로 해석하는 경우, 먼저 위험한 플래그를 끈 상태로 두세요. Telegram 미디어는 기본적으로 RFC 2544 벤치마크 범위를 이미 허용합니다. + + + `channels.telegram.network.dangerouslyAllowPrivateNetwork`는 Telegram + 미디어 SSRF 보호를 약화시킵니다. Clash, Mihomo, Surge 가짜 IP 라우팅처럼 RFC 2544 벤치마크 범위를 벗어난 private 또는 special-use 응답을 합성하는 신뢰된 운영자 제어 proxy 환경에서만 사용하세요. 일반적인 공용 인터넷 Telegram 액세스에서는 비활성화된 상태로 두세요. + + + - 환경 변수 재정의(임시): + - `OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1` + - `OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY=1` + - `OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first` + - DNS 응답 검증: + +```bash +dig +short api.telegram.org A +dig +short api.telegram.org AAAA +``` + + + + +추가 도움말: [채널 문제 해결](/channels/troubleshooting). + +## Telegram config 참조 포인터 + +기본 참조: + +- `channels.telegram.enabled`: 채널 시작 활성화/비활성화. +- `channels.telegram.botToken`: 봇 토큰(BotFather). +- `channels.telegram.tokenFile`: 일반 파일 경로에서 토큰 읽기. 심볼릭 링크는 거부됩니다. +- `channels.telegram.dmPolicy`: `pairing | allowlist | open | disabled` (기본값: pairing). +- `channels.telegram.allowFrom`: DM allowlist(숫자형 Telegram 사용자 ID). `allowlist`는 발신자 ID가 하나 이상 필요합니다. `open`은 `"*"`가 필요합니다. `openclaw doctor --fix`는 레거시 `@username` 항목을 ID로 해석할 수 있고, allowlist 마이그레이션 흐름에서 페어링 저장소 파일의 allowlist 항목을 복구할 수 있습니다. +- `channels.telegram.actions.poll`: Telegram 투표 생성 활성화 또는 비활성화(기본값: 활성화. 여전히 `sendMessage` 필요). +- `channels.telegram.defaultTo`: 명시적 `--reply-to`가 없을 때 CLI `--deliver`이 사용하는 기본 Telegram 대상. +- `channels.telegram.groupPolicy`: `open | allowlist | disabled` (기본값: allowlist). +- `channels.telegram.groupAllowFrom`: 그룹 발신자 allowlist(숫자형 Telegram 사용자 ID). `openclaw doctor --fix`는 레거시 `@username` 항목을 ID로 해석할 수 있습니다. 숫자가 아닌 항목은 인증 시 무시됩니다. 그룹 인증은 DM 페어링 저장소 대체를 사용하지 않습니다(`2026.2.25+`). +- 다중 계정 우선순위: + - 두 개 이상의 계정 ID가 구성된 경우, 기본 라우팅을 명시적으로 만들기 위해 `channels.telegram.defaultAccount`를 설정하거나 `channels.telegram.accounts.default`를 포함하세요. + - 둘 다 설정하지 않으면 OpenClaw는 첫 번째 정규화된 계정 ID로 대체하며 `openclaw doctor`가 경고합니다. + - `channels.telegram.accounts.default.allowFrom` 및 `channels.telegram.accounts.default.groupAllowFrom`은 `default` 계정에만 적용됩니다. + - 이름이 있는 계정은 계정 수준 값이 설정되지 않은 경우 `channels.telegram.allowFrom` 및 `channels.telegram.groupAllowFrom`을 상속합니다. + - 이름이 있는 계정은 `channels.telegram.accounts.default.allowFrom` / `groupAllowFrom`을 상속하지 않습니다. +- `channels.telegram.groups`: 그룹별 기본값 + allowlist(전역 기본값에는 `"*"` 사용). + - `channels.telegram.groups..groupPolicy`: 그룹별 `groupPolicy` 재정의 (`open | allowlist | disabled`). + - `channels.telegram.groups..requireMention`: 기본 멘션 게이팅. + - `channels.telegram.groups..skills`: Skills 필터(생략 = 모든 Skills, 빈값 = 없음). + - `channels.telegram.groups..allowFrom`: 그룹별 발신자 allowlist 재정의. + - `channels.telegram.groups..systemPrompt`: 그룹용 추가 시스템 프롬프트. + - `channels.telegram.groups..enabled`: `false`이면 그룹 비활성화. + - `channels.telegram.groups..topics..*`: 토픽별 재정의(그룹 필드 + 토픽 전용 `agentId`). + - `channels.telegram.groups..topics..agentId`: 이 토픽을 특정 agent로 라우팅(그룹 수준 및 바인딩 라우팅 재정의). +- `channels.telegram.groups..topics..groupPolicy`: `groupPolicy`의 토픽별 재정의 (`open | allowlist | disabled`). +- `channels.telegram.groups..topics..requireMention`: 토픽별 멘션 게이팅 재정의. +- `match.peer.id`에 정식 토픽 ID `chatId:topic:topicId`가 있는 최상위 `bindings[]` 및 `type: "acp"`: 영구 ACP 토픽 바인딩 필드([ACP Agents](/tools/acp-agents#channel-specific-settings) 참조). +- `channels.telegram.direct..topics..agentId`: DM 토픽을 특정 agent로 라우팅(포럼 토픽과 동일한 동작). +- `channels.telegram.execApprovals.enabled`: 이 계정에 대해 Telegram을 채팅 기반 exec 승인 클라이언트로 활성화. +- `channels.telegram.execApprovals.approvers`: exec 요청을 승인 또는 거부할 수 있는 Telegram 사용자 ID. `channels.telegram.allowFrom` 또는 direct `channels.telegram.defaultTo`가 이미 소유자를 식별하는 경우 선택 사항. +- `channels.telegram.execApprovals.target`: `dm | channel | both` (기본값: `dm`). `channel` 및 `both`는 존재하는 경우 원래 Telegram 토픽을 유지합니다. +- `channels.telegram.execApprovals.agentFilter`: 전달된 승인 프롬프트에 대한 선택적 agent ID 필터. +- `channels.telegram.execApprovals.sessionFilter`: 전달된 승인 프롬프트에 대한 선택적 세션 키 필터(부분 문자열 또는 regex). +- `channels.telegram.accounts..execApprovals`: Telegram exec 승인 라우팅 및 승인자 인증에 대한 계정별 재정의. +- `channels.telegram.capabilities.inlineButtons`: `off | dm | group | all | allowlist` (기본값: allowlist). +- `channels.telegram.accounts..capabilities.inlineButtons`: 계정별 재정의. +- `channels.telegram.commands.nativeSkills`: Telegram 기본 Skills 명령 활성화/비활성화. +- `channels.telegram.replyToMode`: `off | first | all` (기본값: `off`). +- `channels.telegram.textChunkLimit`: 아웃바운드 청크 크기(문자 수). +- `channels.telegram.chunkMode`: `length`(기본값) 또는 `newline`; 길이 청크 전 빈 줄(문단 경계)에서 분할. +- `channels.telegram.linkPreview`: 아웃바운드 메시지의 링크 미리보기 토글(기본값: true). +- `channels.telegram.streaming`: `off | partial | block | progress` (실시간 스트림 미리보기, 기본값: `partial`; `progress`는 `partial`로 매핑; `block`은 레거시 미리보기 모드 호환성). Telegram 미리보기 스트리밍은 그 자리에서 편집되는 단일 미리보기 메시지를 사용합니다. +- `channels.telegram.mediaMaxMb`: 인바운드/아웃바운드 Telegram 미디어 상한(MB, 기본값: 100). +- `channels.telegram.retry`: 복구 가능한 아웃바운드 API 오류에 대한 Telegram 전송 헬퍼(CLI/tools/actions) 재시도 정책(attempts, minDelayMs, maxDelayMs, jitter). +- `channels.telegram.network.autoSelectFamily`: Node autoSelectFamily 재정의(true=활성화, false=비활성화). 기본값은 Node 22+에서 활성화이며, WSL2는 기본적으로 비활성화됩니다. +- `channels.telegram.network.dnsResultOrder`: DNS 결과 순서 재정의(`ipv4first` 또는 `verbatim`). 기본값은 Node 22+에서 `ipv4first`입니다. +- `channels.telegram.network.dangerouslyAllowPrivateNetwork`: Telegram 미디어 다운로드가 기본 RFC 2544 벤치마크 범위 허용을 벗어난 private/internal/special-use 주소로 `api.telegram.org`를 해석하는 신뢰된 가짜 IP 또는 transparent-proxy 환경용 위험한 opt-in. +- `channels.telegram.proxy`: Bot API 호출용 proxy URL(SOCKS/HTTP). +- `channels.telegram.webhookUrl`: webhook 모드 활성화(`channels.telegram.webhookSecret` 필요). +- `channels.telegram.webhookSecret`: webhook secret(`webhookUrl` 설정 시 필수). +- `channels.telegram.webhookPath`: 로컬 webhook 경로(기본값 `/telegram-webhook`). +- `channels.telegram.webhookHost`: 로컬 webhook 바인딩 호스트(기본값 `127.0.0.1`). +- `channels.telegram.webhookPort`: 로컬 webhook 바인딩 포트(기본값 `8787`). +- `channels.telegram.actions.reactions`: Telegram 도구 반응 게이트. +- `channels.telegram.actions.sendMessage`: Telegram 도구 메시지 전송 게이트. +- `channels.telegram.actions.deleteMessage`: Telegram 도구 메시지 삭제 게이트. +- `channels.telegram.actions.sticker`: Telegram 스티커 작업 게이트 — 전송 및 검색(기본값: false). +- `channels.telegram.reactionNotifications`: `off | own | all` — 어떤 반응이 시스템 이벤트를 트리거할지 제어(설정되지 않은 경우 기본값: `own`). +- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — agent의 반응 기능을 제어(설정되지 않은 경우 기본값: `minimal`). +- `channels.telegram.errorPolicy`: `reply | silent` — 오류 응답 동작 제어(기본값: `reply`). 계정별/그룹별/토픽별 재정의 지원. +- `channels.telegram.errorCooldownMs`: 동일한 채팅에 대한 오류 응답 사이의 최소 ms(기본값: `60000`). 장애 중 오류 스팸 방지. + +- [구성 참조 - Telegram](/gateway/configuration-reference#telegram) + +Telegram 전용 주요 필드: + +- 시작/인증: `enabled`, `botToken`, `tokenFile`, `accounts.*` (`tokenFile`은 일반 파일을 가리켜야 하며 심볼릭 링크는 거부됨) +- 액세스 제어: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups`, `groups.*.topics.*`, 최상위 `bindings[]` (`type: "acp"`) +- exec 승인: `execApprovals`, `accounts.*.execApprovals` +- 명령/메뉴: `commands.native`, `commands.nativeSkills`, `customCommands` +- 스레딩/응답: `replyToMode` +- 스트리밍: `streaming`(미리보기), `blockStreaming` +- 서식/전송: `textChunkLimit`, `chunkMode`, `linkPreview`, `responsePrefix` +- 미디어/네트워크: `mediaMaxMb`, `timeoutSeconds`, `retry`, `network.autoSelectFamily`, `network.dangerouslyAllowPrivateNetwork`, `proxy` +- webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost` +- 작업/기능: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker` +- 반응: `reactionNotifications`, `reactionLevel` +- 오류: `errorPolicy`, `errorCooldownMs` +- 쓰기/기록: `configWrites`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit` + +## 관련 문서 + +- [페어링](/channels/pairing) +- [그룹](/channels/groups) +- [보안](/gateway/security) +- [채널 라우팅](/channels/channel-routing) +- [멀티 에이전트 라우팅](/concepts/multi-agent) +- [문제 해결](/channels/troubleshooting) diff --git a/docs/ko/channels/tlon.md b/docs/ko/channels/tlon.md new file mode 100644 index 000000000..5068fd9e3 --- /dev/null +++ b/docs/ko/channels/tlon.md @@ -0,0 +1,296 @@ +--- +read_when: + - Tlon/Urbit 채널 기능을 작업할 때 +summary: Tlon/Urbit 지원 상태, 기능 및 구성 +title: Tlon +x-i18n: + generated_at: "2026-04-05T12:36:48Z" + model: gpt-5.4 + provider: openai + source_hash: 289cffb3c1b2d450a5f41e0d67117dfb5c192cec956d82039caac9df9f07496d + source_path: channels/tlon.md + workflow: 15 +--- + +# Tlon + +Tlon은 Urbit 기반의 탈중앙화 메신저입니다. OpenClaw는 사용자의 Urbit ship에 연결하여 +DM과 그룹 채팅 메시지에 응답할 수 있습니다. 그룹 응답은 기본적으로 @ 멘션이 필요하며 +허용 목록을 통해 추가로 제한할 수 있습니다. + +상태: 번들 plugin. DM, 그룹 멘션, 스레드 응답, 리치 텍스트 서식, 이미지 업로드를 +지원합니다. 반응과 Polls는 아직 지원되지 않습니다. + +## 번들 plugin + +Tlon은 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로, 일반적인 패키지 +빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드 또는 Tlon이 제외된 사용자 지정 설치를 사용 중이라면 수동으로 +설치하세요: + +CLI를 통한 설치(npm 레지스트리): + +```bash +openclaw plugins install @openclaw/tlon +``` + +로컬 체크아웃(깃 저장소에서 실행하는 경우): + +```bash +openclaw plugins install ./path/to/local/tlon-plugin +``` + +자세한 내용: [Plugins](/tools/plugin) + +## 설정 + +1. Tlon plugin을 사용할 수 있는지 확인합니다. + - 현재 패키지된 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/사용자 지정 설치는 위 명령으로 수동 추가할 수 있습니다. +2. ship URL과 로그인 코드를 준비합니다. +3. `channels.tlon`을 구성합니다. +4. 게이트웨이를 재시작합니다. +5. 봇에 DM을 보내거나 그룹 채널에서 멘션합니다. + +최소 구성(단일 계정): + +```json5 +{ + channels: { + tlon: { + enabled: true, + ship: "~sampel-palnet", + url: "https://your-ship-host", + code: "lidlut-tabwed-pillex-ridrup", + ownerShip: "~your-main-ship", // 권장: 사용자의 ship, 항상 허용됨 + }, + }, +} +``` + +## 비공개/LAN ship + +기본적으로 OpenClaw는 SSRF 보호를 위해 비공개/내부 호스트 이름과 IP 범위를 차단합니다. +ship이 비공개 네트워크(localhost, LAN IP 또는 내부 호스트 이름)에서 실행 중인 경우, +명시적으로 허용해야 합니다: + +```json5 +{ + channels: { + tlon: { + url: "http://localhost:8080", + allowPrivateNetwork: true, + }, + }, +} +``` + +이는 다음과 같은 URL에 적용됩니다: + +- `http://localhost:8080` +- `http://192.168.x.x:8080` +- `http://my-ship.local:8080` + +⚠️ 로컬 네트워크를 신뢰하는 경우에만 이것을 활성화하세요. 이 설정은 ship URL에 대한 요청의 SSRF 보호를 +비활성화합니다. + +## 그룹 채널 + +자동 검색은 기본적으로 활성화되어 있습니다. 채널을 수동으로 고정할 수도 있습니다: + +```json5 +{ + channels: { + tlon: { + groupChannels: ["chat/~host-ship/general", "chat/~host-ship/support"], + }, + }, +} +``` + +자동 검색 비활성화: + +```json5 +{ + channels: { + tlon: { + autoDiscoverChannels: false, + }, + }, +} +``` + +## 액세스 제어 + +DM 허용 목록(비어 있으면 DM 허용 안 함, 승인 흐름에는 `ownerShip` 사용): + +```json5 +{ + channels: { + tlon: { + dmAllowlist: ["~zod", "~nec"], + }, + }, +} +``` + +그룹 권한 부여(기본적으로 제한됨): + +```json5 +{ + channels: { + tlon: { + defaultAuthorizedShips: ["~zod"], + authorization: { + channelRules: { + "chat/~host-ship/general": { + mode: "restricted", + allowedShips: ["~zod", "~nec"], + }, + "chat/~host-ship/announcements": { + mode: "open", + }, + }, + }, + }, + }, +} +``` + +## 소유자 및 승인 시스템 + +권한 없는 사용자가 상호작용하려고 할 때 승인 요청을 받을 owner ship을 설정하세요: + +```json5 +{ + channels: { + tlon: { + ownerShip: "~your-main-ship", + }, + }, +} +``` + +owner ship은 **모든 곳에서 자동으로 권한이 부여됩니다** — DM 초대는 자동 수락되며 +채널 메시지는 항상 허용됩니다. 소유자를 `dmAllowlist`나 +`defaultAuthorizedShips`에 추가할 필요가 없습니다. + +설정되면 소유자는 다음에 대한 DM 알림을 받습니다: + +- 허용 목록에 없는 ship의 DM 요청 +- 권한 없는 채널에서의 멘션 +- 그룹 초대 요청 + +## 자동 수락 설정 + +DM 초대 자동 수락(`dmAllowlist`에 있는 ship 대상): + +```json5 +{ + channels: { + tlon: { + autoAcceptDmInvites: true, + }, + }, +} +``` + +그룹 초대 자동 수락: + +```json5 +{ + channels: { + tlon: { + autoAcceptGroupInvites: true, + }, + }, +} +``` + +## 전달 대상(CLI/cron) + +`openclaw message send` 또는 cron 전달과 함께 다음을 사용하세요: + +- DM: `~sampel-palnet` 또는 `dm/~sampel-palnet` +- 그룹: `chat/~host-ship/channel` 또는 `group:~host-ship/channel` + +## 번들 Skills + +Tlon plugin에는 Tlon 작업에 대한 CLI 액세스를 제공하는 번들 Skills([`@tloncorp/tlon-skill`](https://github.com/tloncorp/tlon-skill))가 포함되어 있습니다: + +- **연락처**: 프로필 가져오기/업데이트, 연락처 목록 보기 +- **채널**: 목록 보기, 생성, 메시지 게시, 기록 가져오기 +- **그룹**: 목록 보기, 생성, 구성원 관리 +- **DM**: 메시지 보내기, 메시지에 반응하기 +- **반응**: 게시물 및 DM에 이모지 반응 추가/제거 +- **설정**: 슬래시 명령으로 plugin 권한 관리 + +plugin이 설치되면 해당 Skills는 자동으로 사용할 수 있습니다. + +## 기능 + +| 기능 | 상태 | +| --------------- | ---------------------------------------- | +| 다이렉트 메시지 | ✅ 지원됨 | +| 그룹/채널 | ✅ 지원됨 (기본적으로 멘션 게이트 적용) | +| 스레드 | ✅ 지원됨 (스레드에서 자동 응답) | +| 리치 텍스트 | ✅ Markdown이 Tlon 형식으로 변환됨 | +| 이미지 | ✅ Tlon 저장소에 업로드됨 | +| 반응 | ✅ [번들 Skills](#번들-skills) 통해 지원 | +| Polls | ❌ 아직 지원되지 않음 | +| 네이티브 명령 | ✅ 지원됨 (기본적으로 owner 전용) | + +## 문제 해결 + +먼저 다음 순서대로 실행하세요: + +```bash +openclaw status +openclaw gateway status +openclaw logs --follow +openclaw doctor +``` + +일반적인 실패 사례: + +- **DM이 무시됨**: 발신자가 `dmAllowlist`에 없고 승인 흐름용 `ownerShip`이 구성되지 않았습니다. +- **그룹 메시지가 무시됨**: 채널이 검색되지 않았거나 발신자에게 권한이 없습니다. +- **연결 오류**: ship URL에 접근 가능한지 확인하세요. 로컬 ship에는 `allowPrivateNetwork`를 활성화하세요. +- **인증 오류**: 로그인 코드가 최신인지 확인하세요(코드는 순환 변경됨). + +## 구성 참조 + +전체 구성: [Configuration](/gateway/configuration) + +제공자 옵션: + +- `channels.tlon.enabled`: 채널 시작 활성화/비활성화. +- `channels.tlon.ship`: 봇의 Urbit ship 이름(예: `~sampel-palnet`). +- `channels.tlon.url`: ship URL(예: `https://sampel-palnet.tlon.network`). +- `channels.tlon.code`: ship 로그인 코드. +- `channels.tlon.allowPrivateNetwork`: localhost/LAN URL 허용(SSRF 우회). +- `channels.tlon.ownerShip`: 승인 시스템용 owner ship(항상 권한 있음). +- `channels.tlon.dmAllowlist`: DM을 보낼 수 있는 ship(비어 있으면 없음). +- `channels.tlon.autoAcceptDmInvites`: 허용 목록에 있는 ship의 DM을 자동 수락. +- `channels.tlon.autoAcceptGroupInvites`: 모든 그룹 초대를 자동 수락. +- `channels.tlon.autoDiscoverChannels`: 그룹 채널 자동 검색(기본값: true). +- `channels.tlon.groupChannels`: 수동으로 고정된 채널 nest. +- `channels.tlon.defaultAuthorizedShips`: 모든 채널에 대해 권한이 있는 ship. +- `channels.tlon.authorization.channelRules`: 채널별 인증 규칙. +- `channels.tlon.showModelSignature`: 메시지에 모델 이름 추가. + +## 참고 + +- 그룹 응답은 응답하려면 멘션(예: `~your-bot-ship`)이 필요합니다. +- 스레드 응답: 수신 메시지가 스레드에 있으면 OpenClaw는 해당 스레드에서 응답합니다. +- 리치 텍스트: Markdown 서식(굵게, 기울임꼴, 코드, 헤더, 목록)은 Tlon의 네이티브 형식으로 변환됩니다. +- 이미지: URL은 Tlon 저장소에 업로드되어 이미지 블록으로 삽입됩니다. + +## 관련 항목 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/troubleshooting.md b/docs/ko/channels/troubleshooting.md new file mode 100644 index 000000000..f18080580 --- /dev/null +++ b/docs/ko/channels/troubleshooting.md @@ -0,0 +1,140 @@ +--- +read_when: + - 채널 전송은 연결되었다고 표시되지만 응답이 실패함 + - 더 깊은 provider 문서를 보기 전에 채널별 확인이 필요함 +summary: 채널별 실패 시그니처와 해결 방법으로 빠르게 수행하는 채널 수준 문제 해결 +title: 채널 문제 해결 +x-i18n: + generated_at: "2026-04-05T12:36:45Z" + model: gpt-5.4 + provider: openai + source_hash: d45d8220505ea420d970b20bc66e65216c2d7024b5736db1936421ffc0676e1f + source_path: channels/troubleshooting.md + workflow: 15 +--- + +# 채널 문제 해결 + +채널은 연결되지만 동작이 잘못될 때 이 페이지를 사용하세요. + +## 명령 단계 + +먼저 다음 명령을 순서대로 실행하세요. + +```bash +openclaw status +openclaw gateway status +openclaw logs --follow +openclaw doctor +openclaw channels status --probe +``` + +정상 기준선: + +- `Runtime: running` +- `RPC probe: ok` +- 채널 프로브에 전송 연결 상태가 표시되고, 지원되는 경우 `works` 또는 `audit ok`가 표시됨 + +## WhatsApp + +### WhatsApp 실패 시그니처 + +| 증상 | 가장 빠른 확인 방법 | 해결 방법 | +| ---------------------------- | ------------------------------------------------- | ---------------------------------------------------------- | +| 연결되었지만 DM 응답이 없음 | `openclaw pairing list whatsapp` | 발신자를 승인하거나 DM 정책/allowlist를 변경하세요. | +| 그룹 메시지가 무시됨 | config에서 `requireMention` + mention 패턴 확인 | 봇을 mention하거나 해당 그룹의 mention 정책을 완화하세요. | +| 무작위 연결 해제/재로그인 루프 | `openclaw channels status --probe` + 로그 | 다시 로그인하고 자격 증명 디렉터리가 정상인지 확인하세요. | + +전체 문제 해결: [/channels/whatsapp#troubleshooting](/channels/whatsapp#troubleshooting) + +## Telegram + +### Telegram 실패 시그니처 + +| 증상 | 가장 빠른 확인 방법 | 해결 방법 | +| ----------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------- | +| `/start`는 되지만 정상적인 응답 흐름이 없음 | `openclaw pairing list telegram` | 페어링을 승인하거나 DM 정책을 변경하세요. | +| 봇은 온라인인데 그룹이 조용함 | mention 요구 사항과 봇 privacy mode 확인 | 그룹 가시성을 위해 privacy mode를 비활성화하거나 봇을 mention하세요. | +| 네트워크 오류와 함께 전송 실패 | Telegram API 호출 실패 로그 확인 | `api.telegram.org`로의 DNS/IPv6/프록시 라우팅을 수정하세요. | +| 시작 시 `setMyCommands`가 거부됨 | `BOT_COMMANDS_TOO_MUCH` 로그 확인 | plugin/skill/custom Telegram 명령을 줄이거나 네이티브 메뉴를 비활성화하세요. | +| 업그레이드 후 allowlist가 나를 차단함 | `openclaw security audit` 및 config allowlist | `openclaw doctor --fix`를 실행하거나 `@username`을 숫자 발신자 ID로 바꾸세요. | + +전체 문제 해결: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting) + +## Discord + +### Discord 실패 시그니처 + +| 증상 | 가장 빠른 확인 방법 | 해결 방법 | +| ----------------------------- | -------------------------------------- | ------------------------------------------------------------- | +| 봇은 온라인인데 길드 응답이 없음 | `openclaw channels status --probe` | 길드/채널을 허용하고 message content intent를 확인하세요. | +| 그룹 메시지가 무시됨 | 로그에서 mention 게이팅 드롭 확인 | 봇을 mention하거나 길드/채널 `requireMention: false`를 설정하세요. | +| DM 응답이 누락됨 | `openclaw pairing list discord` | DM 페어링을 승인하거나 DM 정책을 조정하세요. | + +전체 문제 해결: [/channels/discord#troubleshooting](/channels/discord#troubleshooting) + +## Slack + +### Slack 실패 시그니처 + +| 증상 | 가장 빠른 확인 방법 | 해결 방법 | +| -------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Socket mode는 연결되었지만 응답이 없음 | `openclaw channels status --probe` | 앱 토큰 + 봇 토큰과 필요한 scope를 확인하세요. SecretRef 기반 설정에서는 `botTokenStatus` / `appTokenStatus = configured_unavailable`를 주의 깊게 보세요. | +| DM이 차단됨 | `openclaw pairing list slack` | 페어링을 승인하거나 DM 정책을 완화하세요. | +| 채널 메시지가 무시됨 | `groupPolicy` 및 채널 allowlist 확인 | 채널을 허용하거나 정책을 `open`으로 변경하세요. | + +전체 문제 해결: [/channels/slack#troubleshooting](/channels/slack#troubleshooting) + +## iMessage 및 BlueBubbles + +### iMessage 및 BlueBubbles 실패 시그니처 + +| 증상 | 가장 빠른 확인 방법 | 해결 방법 | +| --------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------- | +| 수신 이벤트가 없음 | webhook/server 도달 가능성 및 앱 권한 확인 | webhook URL 또는 BlueBubbles 서버 상태를 수정하세요. | +| 보낼 수는 있지만 macOS에서 수신이 안 됨 | Messages 자동화에 대한 macOS 개인정보 보호 권한 확인 | TCC 권한을 다시 부여하고 채널 프로세스를 재시작하세요. | +| DM 발신자가 차단됨 | `openclaw pairing list imessage` 또는 `openclaw pairing list bluebubbles` | 페어링을 승인하거나 allowlist를 업데이트하세요. | + +전체 문제 해결: + +- [/channels/imessage#troubleshooting](/channels/imessage#troubleshooting) +- [/channels/bluebubbles#troubleshooting](/channels/bluebubbles#troubleshooting) + +## Signal + +### Signal 실패 시그니처 + +| 증상 | 가장 빠른 확인 방법 | 해결 방법 | +| ----------------------------- | ---------------------------------------- | ------------------------------------------------------------- | +| 데몬에 도달 가능하지만 봇이 조용함 | `openclaw channels status --probe` | `signal-cli` 데몬 URL/계정 및 수신 모드를 확인하세요. | +| DM이 차단됨 | `openclaw pairing list signal` | 발신자를 승인하거나 DM 정책을 조정하세요. | +| 그룹 응답이 트리거되지 않음 | 그룹 allowlist 및 mention 패턴 확인 | 발신자/그룹을 추가하거나 게이팅을 완화하세요. | + +전체 문제 해결: [/channels/signal#troubleshooting](/channels/signal#troubleshooting) + +## QQ Bot + +### QQ Bot 실패 시그니처 + +| 증상 | 가장 빠른 확인 방법 | 해결 방법 | +| ------------------------------ | -------------------------------------------- | --------------------------------------------------------------------- | +| 봇이 "gone to Mars"라고 응답함 | config에서 `appId` 및 `clientSecret` 확인 | 자격 증명을 설정하거나 gateway를 재시작하세요. | +| 수신 메시지가 없음 | `openclaw channels status --probe` | QQ Open Platform에서 자격 증명을 확인하세요. | +| 음성이 전사되지 않음 | STT provider config 확인 | `channels.qqbot.stt` 또는 `tools.media.audio`를 구성하세요. | +| 능동적 메시지가 도착하지 않음 | QQ 플랫폼 상호작용 요구 사항 확인 | QQ는 최근 상호작용이 없으면 봇이 시작한 메시지를 차단할 수 있습니다. | + +전체 문제 해결: [/channels/qqbot#troubleshooting](/channels/qqbot#troubleshooting) + +## Matrix + +### Matrix 실패 시그니처 + +| 증상 | 가장 빠른 확인 방법 | 해결 방법 | +| ------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------- | +| 로그인했지만 룸 메시지를 무시함 | `openclaw channels status --probe` | `groupPolicy`, 룸 allowlist, mention 게이팅을 확인하세요. | +| DM이 처리되지 않음 | `openclaw pairing list matrix` | 발신자를 승인하거나 DM 정책을 조정하세요. | +| 암호화된 룸에서 실패함 | `openclaw matrix verify status` | 기기를 다시 확인한 뒤 `openclaw matrix verify backup status`를 확인하세요. | +| 백업 복원이 대기 중이거나 손상됨 | `openclaw matrix verify backup status` | `openclaw matrix verify backup restore`를 실행하거나 복구 키로 다시 실행하세요. | +| 교차 서명/bootstrap 상태가 잘못됨 | `openclaw matrix verify bootstrap` | 비밀 저장소, 교차 서명, 백업 상태를 한 번에 복구하세요. | + +전체 설정 및 구성: [Matrix](/channels/matrix) diff --git a/docs/ko/channels/twitch.md b/docs/ko/channels/twitch.md new file mode 100644 index 000000000..43aaf2960 --- /dev/null +++ b/docs/ko/channels/twitch.md @@ -0,0 +1,398 @@ +--- +read_when: + - OpenClaw용 Twitch 채팅 통합을 설정할 때 +summary: Twitch 채팅 봇 구성 및 설정 +title: Twitch +x-i18n: + generated_at: "2026-04-05T12:36:56Z" + model: gpt-5.4 + provider: openai + source_hash: 47af9fb6edb1f462c5919850ee9d05e500a1914ddd0d64a41608fbe960e77cd6 + source_path: channels/twitch.md + workflow: 15 +--- + +# Twitch + +IRC 연결을 통한 Twitch 채팅 지원입니다. OpenClaw는 Twitch 사용자(봇 계정)로 연결하여 채널에서 메시지를 수신하고 전송합니다. + +## 번들 plugin + +Twitch는 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로 일반 패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드 또는 Twitch가 제외된 사용자 지정 설치를 사용하는 경우 수동으로 설치하세요. + +CLI를 통해 설치(npm 레지스트리): + +```bash +openclaw plugins install @openclaw/twitch +``` + +로컬 체크아웃(git 리포지토리에서 실행하는 경우): + +```bash +openclaw plugins install ./path/to/local/twitch-plugin +``` + +자세한 내용: [Plugins](/tools/plugin) + +## 빠른 설정(초보자용) + +1. Twitch plugin을 사용할 수 있는지 확인합니다. + - 현재 패키지된 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/사용자 지정 설치에서는 위 명령으로 수동 추가할 수 있습니다. +2. 봇용 전용 Twitch 계정을 생성합니다(또는 기존 계정을 사용합니다). +3. 자격 증명을 생성합니다: [Twitch Token Generator](https://twitchtokengenerator.com/) + - **Bot Token**을 선택합니다 + - `chat:read` 및 `chat:write` 스코프가 선택되었는지 확인합니다 + - **Client ID** 및 **Access Token**을 복사합니다 +4. Twitch 사용자 ID를 찾습니다: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/) +5. 토큰을 구성합니다. + - Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (기본 계정 전용) + - 또는 config: `channels.twitch.accessToken` + - 둘 다 설정된 경우 config가 우선합니다(env 폴백은 기본 계정 전용). +6. gateway를 시작합니다. + +**⚠️ 중요:** 승인되지 않은 사용자가 봇을 트리거하지 못하도록 액세스 제어(`allowFrom` 또는 `allowedRoles`)를 추가하세요. `requireMention`의 기본값은 `true`입니다. + +최소 구성: + +```json5 +{ + channels: { + twitch: { + enabled: true, + username: "openclaw", // 봇의 Twitch 계정 + accessToken: "oauth:abc123...", // OAuth 액세스 토큰(또는 OPENCLAW_TWITCH_ACCESS_TOKEN env var 사용) + clientId: "xyz789...", // Token Generator의 Client ID + channel: "vevisk", // 참여할 Twitch 채널 채팅(필수) + allowFrom: ["123456789"], // (권장) 사용자 본인의 Twitch 사용자 ID만 허용 - https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ 에서 확인 + }, + }, +} +``` + +## 개요 + +- Gateway가 소유하는 Twitch 채널입니다. +- 결정적 라우팅: 응답은 항상 Twitch로 다시 전송됩니다. +- 각 계정은 격리된 세션 키 `agent::twitch:`에 매핑됩니다. +- `username`은 봇 계정(인증 주체)이고, `channel`은 참여할 채팅방입니다. + +## 설정(상세) + +### 자격 증명 생성 + +[Twitch Token Generator](https://twitchtokengenerator.com/)를 사용하세요. + +- **Bot Token**을 선택합니다 +- `chat:read` 및 `chat:write` 스코프가 선택되었는지 확인합니다 +- **Client ID** 및 **Access Token**을 복사합니다 + +수동 앱 등록은 필요하지 않습니다. 토큰은 몇 시간 후 만료됩니다. + +### 봇 구성 + +**Env var(기본 계정 전용):** + +```bash +OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123... +``` + +**또는 config:** + +```json5 +{ + channels: { + twitch: { + enabled: true, + username: "openclaw", + accessToken: "oauth:abc123...", + clientId: "xyz789...", + channel: "vevisk", + }, + }, +} +``` + +env와 config가 모두 설정된 경우 config가 우선합니다. + +### 액세스 제어(권장) + +```json5 +{ + channels: { + twitch: { + allowFrom: ["123456789"], // (권장) 사용자 본인의 Twitch 사용자 ID만 허용 + }, + }, +} +``` + +강력한 허용 목록에는 `allowFrom`을 우선 사용하세요. 역할 기반 액세스를 원하면 대신 `allowedRoles`를 사용하세요. + +**사용 가능한 역할:** `"moderator"`, `"owner"`, `"vip"`, `"subscriber"`, `"all"`. + +**왜 사용자 ID인가요?** 사용자 이름은 변경될 수 있어 사칭이 가능하지만, 사용자 ID는 영구적입니다. + +Twitch 사용자 ID 찾기: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/) (Twitch 사용자 이름을 ID로 변환) + +## 토큰 갱신(선택 사항) + +[Twitch Token Generator](https://twitchtokengenerator.com/)에서 생성한 토큰은 자동 갱신할 수 없습니다. 만료되면 다시 생성하세요. + +자동 토큰 갱신을 원하면 [Twitch Developer Console](https://dev.twitch.tv/console)에서 직접 Twitch 애플리케이션을 만들고 config에 다음을 추가하세요. + +```json5 +{ + channels: { + twitch: { + clientSecret: "your_client_secret", + refreshToken: "your_refresh_token", + }, + }, +} +``` + +봇은 만료 전에 자동으로 토큰을 갱신하고 갱신 이벤트를 로그에 기록합니다. + +## 다중 계정 지원 + +계정별 토큰을 사용하려면 `channels.twitch.accounts`를 사용하세요. 공통 패턴은 [`gateway/configuration`](/gateway/configuration)을 참조하세요. + +예시(하나의 봇 계정을 두 채널에서 사용): + +```json5 +{ + channels: { + twitch: { + accounts: { + channel1: { + username: "openclaw", + accessToken: "oauth:abc123...", + clientId: "xyz789...", + channel: "vevisk", + }, + channel2: { + username: "openclaw", + accessToken: "oauth:def456...", + clientId: "uvw012...", + channel: "secondchannel", + }, + }, + }, + }, +} +``` + +**참고:** 각 계정에는 자체 토큰이 필요합니다(채널당 하나의 토큰). + +## 액세스 제어 + +### 역할 기반 제한 + +```json5 +{ + channels: { + twitch: { + accounts: { + default: { + allowedRoles: ["moderator", "vip"], + }, + }, + }, + }, +} +``` + +### 사용자 ID별 허용 목록(가장 안전함) + +```json5 +{ + channels: { + twitch: { + accounts: { + default: { + allowFrom: ["123456789", "987654321"], + }, + }, + }, + }, +} +``` + +### 역할 기반 액세스(대안) + +`allowFrom`은 강력한 허용 목록입니다. 설정하면 해당 사용자 ID만 허용됩니다. +역할 기반 액세스를 원하면 `allowFrom`은 설정하지 말고 대신 `allowedRoles`를 구성하세요. + +```json5 +{ + channels: { + twitch: { + accounts: { + default: { + allowedRoles: ["moderator"], + }, + }, + }, + }, +} +``` + +### @mention 요구 사항 비활성화 + +기본적으로 `requireMention`은 `true`입니다. 비활성화하고 모든 메시지에 응답하려면 다음과 같이 설정하세요. + +```json5 +{ + channels: { + twitch: { + accounts: { + default: { + requireMention: false, + }, + }, + }, + }, +} +``` + +## 문제 해결 + +먼저 진단 명령을 실행하세요. + +```bash +openclaw doctor +openclaw channels status --probe +``` + +### 봇이 메시지에 응답하지 않음 + +**액세스 제어 확인:** 사용자 ID가 `allowFrom`에 포함되어 있는지 확인하거나, 테스트를 위해 일시적으로 `allowFrom`을 제거하고 `allowedRoles: ["all"]`로 설정하세요. + +**봇이 채널에 있는지 확인:** 봇은 `channel`에 지정된 채널에 참여해야 합니다. + +### 토큰 문제 + +**"Failed to connect" 또는 인증 오류:** + +- `accessToken`이 OAuth 액세스 토큰 값인지 확인하세요(일반적으로 `oauth:` 접두사로 시작) +- 토큰에 `chat:read` 및 `chat:write` 스코프가 있는지 확인하세요 +- 토큰 갱신을 사용하는 경우 `clientSecret`과 `refreshToken`이 설정되었는지 확인하세요 + +### 토큰 갱신이 작동하지 않음 + +**갱신 이벤트 로그 확인:** + +``` +Using env token source for mybot +Access token refreshed for user 123456 (expires in 14400s) +``` + +"token refresh disabled (no refresh token)"가 표시되면: + +- `clientSecret`이 제공되었는지 확인하세요 +- `refreshToken`이 제공되었는지 확인하세요 + +## Config + +**계정 config:** + +- `username` - 봇 사용자 이름 +- `accessToken` - `chat:read` 및 `chat:write` 권한이 있는 OAuth 액세스 토큰 +- `clientId` - Twitch Client ID(Token Generator 또는 자체 앱에서 획득) +- `channel` - 참여할 채널(필수) +- `enabled` - 이 계정 활성화(기본값: `true`) +- `clientSecret` - 선택 사항: 자동 토큰 갱신용 +- `refreshToken` - 선택 사항: 자동 토큰 갱신용 +- `expiresIn` - 토큰 만료 시간(초) +- `obtainmentTimestamp` - 토큰 획득 타임스탬프 +- `allowFrom` - 사용자 ID 허용 목록 +- `allowedRoles` - 역할 기반 액세스 제어(`"moderator" | "owner" | "vip" | "subscriber" | "all"`) +- `requireMention` - @mention 필요(기본값: `true`) + +**Provider 옵션:** + +- `channels.twitch.enabled` - 채널 시작 활성화/비활성화 +- `channels.twitch.username` - 봇 사용자 이름(단순화된 단일 계정 config) +- `channels.twitch.accessToken` - OAuth 액세스 토큰(단순화된 단일 계정 config) +- `channels.twitch.clientId` - Twitch Client ID(단순화된 단일 계정 config) +- `channels.twitch.channel` - 참여할 채널(단순화된 단일 계정 config) +- `channels.twitch.accounts.` - 다중 계정 config(위의 모든 계정 필드) + +전체 예시: + +```json5 +{ + channels: { + twitch: { + enabled: true, + username: "openclaw", + accessToken: "oauth:abc123...", + clientId: "xyz789...", + channel: "vevisk", + clientSecret: "secret123...", + refreshToken: "refresh456...", + allowFrom: ["123456789"], + allowedRoles: ["moderator", "vip"], + accounts: { + default: { + username: "mybot", + accessToken: "oauth:abc123...", + clientId: "xyz789...", + channel: "your_channel", + enabled: true, + clientSecret: "secret123...", + refreshToken: "refresh456...", + expiresIn: 14400, + obtainmentTimestamp: 1706092800000, + allowFrom: ["123456789", "987654321"], + allowedRoles: ["moderator"], + }, + }, + }, + }, +} +``` + +## 도구 작업 + +에이전트는 다음 작업으로 `twitch`를 호출할 수 있습니다. + +- `send` - 채널에 메시지 보내기 + +예시: + +```json5 +{ + action: "twitch", + params: { + message: "Hello Twitch!", + to: "#mychannel", + }, +} +``` + +## 안전 및 운영 + +- **토큰을 비밀번호처럼 취급하세요** - 토큰을 git에 커밋하지 마세요 +- **장기 실행 봇에는 자동 토큰 갱신을 사용하세요** +- 액세스 제어에는 사용자 이름 대신 **사용자 ID 허용 목록**을 사용하세요 +- 토큰 갱신 이벤트와 연결 상태를 위해 **로그를 모니터링하세요** +- **토큰 스코프를 최소화하세요** - `chat:read` 및 `chat:write`만 요청하세요 +- **문제가 해결되지 않으면**: 다른 프로세스가 세션을 소유하고 있지 않은지 확인한 후 gateway를 재시작하세요 + +## 제한 + +- 메시지당 **500자**(단어 경계 기준 자동 청킹) +- 청킹 전에 Markdown이 제거됩니다 +- 속도 제한 없음(Twitch의 내장 rate limit 사용) + +## 관련 문서 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/whatsapp.md b/docs/ko/channels/whatsapp.md new file mode 100644 index 000000000..65f49cc1f --- /dev/null +++ b/docs/ko/channels/whatsapp.md @@ -0,0 +1,495 @@ +--- +read_when: + - WhatsApp/웹 채널 동작 또는 받은편지함 라우팅 작업 +summary: WhatsApp 채널 지원, 접근 제어, 전달 동작 및 운영 +title: WhatsApp +x-i18n: + generated_at: "2026-04-05T12:37:36Z" + model: gpt-5.4 + provider: openai + source_hash: c16a468b3f47fdf7e4fc3fd745b5c49c7ccebb7af0e8c87c632b78b04c583e49 + source_path: channels/whatsapp.md + workflow: 15 +--- + +# WhatsApp (웹 채널) + +상태: WhatsApp Web(Baileys)을 통한 프로덕션 준비 완료. Gateway가 연결된 세션을 소유합니다. + +## 설치(필요 시) + +- `openclaw onboard`와 `openclaw channels add --channel whatsapp`는 + WhatsApp plugin을 처음 선택할 때 설치를 안내합니다. +- `openclaw channels login --channel whatsapp`도 + plugin이 아직 없으면 설치 흐름을 제공합니다. +- 개발 채널 + git 체크아웃: 기본값은 로컬 plugin 경로입니다. +- Stable/Beta: 기본값은 npm 패키지 `@openclaw/whatsapp`입니다. + +수동 설치도 계속 사용할 수 있습니다. + +```bash +openclaw plugins install @openclaw/whatsapp +``` + + + + 알 수 없는 발신자에 대한 기본 DM 정책은 페어링입니다. + + + 채널 전반의 진단 및 복구 플레이북. + + + 전체 채널 config 패턴 및 예시. + + + +## 빠른 설정 + + + + +```json5 +{ + channels: { + whatsapp: { + dmPolicy: "pairing", + allowFrom: ["+15551234567"], + groupPolicy: "allowlist", + groupAllowFrom: ["+15551234567"], + }, + }, +} +``` + + + + + +```bash +openclaw channels login --channel whatsapp +``` + + 특정 계정의 경우: + +```bash +openclaw channels login --channel whatsapp --account work +``` + + + + + +```bash +openclaw gateway +``` + + + + + +```bash +openclaw pairing list whatsapp +openclaw pairing approve whatsapp +``` + + 페어링 요청은 1시간 후 만료됩니다. 대기 중 요청은 채널당 최대 3개까지 허용됩니다. + + + + + +가능하면 OpenClaw는 WhatsApp를 별도 번호로 운영하는 것을 권장합니다. (채널 메타데이터와 설정 흐름은 그 구성을 기준으로 최적화되어 있지만, 개인 번호 설정도 지원합니다.) + + +## 배포 패턴 + + + + 가장 깔끔한 운영 모드입니다. + + - OpenClaw용 별도 WhatsApp 신원 + - 더 명확한 DM 허용 목록 및 라우팅 경계 + - 자기 자신과의 채팅 혼동 가능성 감소 + + 최소 정책 패턴: + + ```json5 + { + channels: { + whatsapp: { + dmPolicy: "allowlist", + allowFrom: ["+15551234567"], + }, + }, + } + ``` + + + + + 온보딩은 개인 번호 모드를 지원하며 self-chat 친화적인 기본 구성을 기록합니다. + + - `dmPolicy: "allowlist"` + - `allowFrom`에 개인 번호 포함 + - `selfChatMode: true` + + 런타임에서는 self-chat 보호가 연결된 자기 번호와 `allowFrom`을 기준으로 동작합니다. + + + + + 현재 OpenClaw 채널 아키텍처에서 메시징 플랫폼 채널은 WhatsApp Web 기반(`Baileys`)입니다. + + 내장 채팅 채널 레지스트리에는 별도의 Twilio WhatsApp 메시징 채널이 없습니다. + + + + +## 런타임 모델 + +- Gateway가 WhatsApp 소켓과 재연결 루프를 소유합니다. +- 아웃바운드 전송은 대상 계정에 대해 활성 WhatsApp 리스너가 있어야 합니다. +- 상태 및 브로드캐스트 채팅은 무시됩니다(`@status`, `@broadcast`). +- 다이렉트 채팅은 DM 세션 규칙을 사용합니다(`session.dmScope`; 기본값 `main`은 DM을 agent 메인 세션으로 병합함). +- 그룹 세션은 격리됩니다(`agent::whatsapp:group:`). + +## 접근 제어 및 활성화 + + + + `channels.whatsapp.dmPolicy`는 다이렉트 채팅 접근을 제어합니다. + + - `pairing`(기본값) + - `allowlist` + - `open`(`allowFrom`에 `"*"` 포함 필요) + - `disabled` + + `allowFrom`은 E.164 형식 번호를 받으며(내부적으로 정규화됨). + + 멀티 계정 재정의: 해당 계정에서는 `channels.whatsapp.accounts..dmPolicy`(및 `allowFrom`)가 채널 수준 기본값보다 우선합니다. + + 런타임 동작 세부 사항: + + - 페어링은 채널 allow-store에 영속 저장되며 구성된 `allowFrom`과 병합됩니다 + - 허용 목록이 구성되지 않았으면 연결된 자기 번호가 기본적으로 허용됩니다 + - 아웃바운드 `fromMe` DM은 자동 페어링되지 않습니다 + + + + + 그룹 접근은 두 계층으로 구성됩니다. + + 1. **그룹 멤버십 허용 목록**(`channels.whatsapp.groups`) + - `groups`가 생략되면 모든 그룹이 대상이 됩니다 + - `groups`가 있으면 그룹 허용 목록으로 동작합니다(`"*"` 허용 가능) + + 2. **그룹 발신자 정책**(`channels.whatsapp.groupPolicy` + `groupAllowFrom`) + - `open`: 발신자 허용 목록 우회 + - `allowlist`: 발신자가 `groupAllowFrom`(또는 `*`)과 일치해야 함 + - `disabled`: 모든 그룹 수신 차단 + + 발신자 허용 목록 대체 동작: + + - `groupAllowFrom`이 설정되지 않으면, 런타임은 가능할 때 `allowFrom`으로 대체합니다 + - 발신자 허용 목록은 멘션/답글 활성화보다 먼저 평가됩니다 + + 참고: `channels.whatsapp` 블록이 아예 없으면, `channels.defaults.groupPolicy`가 설정되어 있어도 런타임 그룹 정책 대체값은 `allowlist`입니다(경고 로그와 함께). + + + + + 그룹 응답은 기본적으로 멘션이 필요합니다. + + 멘션 감지에는 다음이 포함됩니다. + + - 봇 신원에 대한 명시적 WhatsApp 멘션 + - 구성된 멘션 정규식 패턴(`agents.list[].groupChat.mentionPatterns`, 대체값 `messages.groupChat.mentionPatterns`) + - 암묵적인 reply-to-bot 감지(답글 발신자가 봇 신원과 일치) + + 보안 참고: + + - 인용/답글은 멘션 게이팅만 충족하며, 발신자 권한을 부여하지는 않습니다 + - `groupPolicy: "allowlist"`에서는 허용 목록에 없는 발신자가 허용 목록 사용자의 메시지에 답글을 달더라도 여전히 차단됩니다 + + 세션 수준 활성화 명령: + + - `/activation mention` + - `/activation always` + + `activation`은 세션 상태를 업데이트하며(전역 config 아님), 소유자 게이트가 적용됩니다. + + + + +## 개인 번호 및 self-chat 동작 + +연결된 자기 번호가 `allowFrom`에도 포함되어 있으면 WhatsApp self-chat 보호가 활성화됩니다. + +- self-chat 턴에서는 읽음 확인 생략 +- 그렇지 않으면 자신에게 핑을 보낼 수 있는 mention-JID 자동 트리거 동작 무시 +- `messages.responsePrefix`가 설정되지 않은 경우, self-chat 응답의 기본값은 `[{identity.name}]` 또는 `[openclaw]` + +## 메시지 정규화 및 컨텍스트 + + + + 들어오는 WhatsApp 메시지는 공용 수신 엔벌로프로 감싸집니다. + + 인용된 답글이 있으면 다음 형식으로 컨텍스트가 추가됩니다. + + ```text + [Replying to id:] + + [/Replying] + ``` + + 답글 메타데이터 필드도 가능하면 채워집니다(`ReplyToId`, `ReplyToBody`, `ReplyToSender`, sender JID/E.164). + + + + + 미디어만 있는 수신 메시지는 다음과 같은 플레이스홀더로 정규화됩니다. + + - `` + - `` + - `` + - `` + - `` + + 위치 및 연락처 페이로드는 라우팅 전에 텍스트 컨텍스트로 정규화됩니다. + + + + + 그룹에서는 아직 처리되지 않은 메시지를 버퍼링했다가 봇이 최종적으로 트리거될 때 컨텍스트로 주입할 수 있습니다. + + - 기본 한도: `50` + - config: `channels.whatsapp.historyLimit` + - 대체값: `messages.groupChat.historyLimit` + - `0`은 비활성화 + + 주입 마커: + + - `[Chat messages since your last reply - for context]` + - `[Current message - respond to this]` + + + + + 읽음 확인은 허용된 수신 WhatsApp 메시지에 대해 기본적으로 활성화됩니다. + + 전역 비활성화: + + ```json5 + { + channels: { + whatsapp: { + sendReadReceipts: false, + }, + }, + } + ``` + + 계정별 재정의: + + ```json5 + { + channels: { + whatsapp: { + accounts: { + work: { + sendReadReceipts: false, + }, + }, + }, + }, + } + ``` + + self-chat 턴에서는 전역적으로 활성화되어 있어도 읽음 확인을 보내지 않습니다. + + + + +## 전달, 분할, 미디어 + + + + - 기본 분할 한도: `channels.whatsapp.textChunkLimit = 4000` + - `channels.whatsapp.chunkMode = "length" | "newline"` + - `newline` 모드는 단락 경계(빈 줄)를 우선하며, 이후 길이 안전 분할로 대체합니다 + + + + - 이미지, 비디오, 오디오(PTT 음성 메모), 문서 페이로드 지원 + - 음성 메모 호환성을 위해 `audio/ogg`는 `audio/ogg; codecs=opus`로 다시 작성됩니다 + - 비디오 전송 시 `gifPlayback: true`를 통해 애니메이션 GIF 재생 지원 + - 다중 미디어 응답 페이로드 전송 시 캡션은 첫 번째 미디어 항목에 적용됩니다 + - 미디어 소스는 HTTP(S), `file://`, 또는 로컬 경로일 수 있습니다 + + + + - 수신 미디어 저장 한도: `channels.whatsapp.mediaMaxMb`(기본값 `50`) + - 아웃바운드 미디어 전송 한도: `channels.whatsapp.mediaMaxMb`(기본값 `50`) + - 계정별 재정의는 `channels.whatsapp.accounts..mediaMaxMb` 사용 + - 이미지는 제한에 맞도록 자동 최적화됩니다(크기 조정/품질 스윕) + - 미디어 전송 실패 시, 첫 번째 항목 대체 동작으로 응답을 조용히 버리지 않고 텍스트 경고를 전송합니다 + + + +## 반응 수준 + +`channels.whatsapp.reactionLevel`은 WhatsApp에서 에이전트가 이모지 반응을 얼마나 넓게 사용할지 제어합니다. + +| 수준 | Ack 반응 | 에이전트 시작 반응 | 설명 | +| ------------- | -------- | ------------------ | ------------------------------------------------- | +| `"off"` | 아니요 | 아니요 | 반응을 전혀 사용하지 않음 | +| `"ack"` | 예 | 아니요 | Ack 반응만 사용(응답 전 수신 확인) | +| `"minimal"` | 예 | 예(보수적) | Ack + 보수적 가이드의 agent 반응 | +| `"extensive"` | 예 | 예(권장) | Ack + 권장 가이드의 agent 반응 | + +기본값: `"minimal"`. + +계정별 재정의는 `channels.whatsapp.accounts..reactionLevel`을 사용합니다. + +```json5 +{ + channels: { + whatsapp: { + reactionLevel: "ack", + }, + }, +} +``` + +## 확인 반응 + +WhatsApp는 `channels.whatsapp.ackReaction`을 통해 수신 즉시 Ack 반응을 지원합니다. +Ack 반응은 `reactionLevel`에 의해 제어되며, `reactionLevel`이 `"off"`이면 억제됩니다. + +```json5 +{ + channels: { + whatsapp: { + ackReaction: { + emoji: "👀", + direct: true, + group: "mentions", // always | mentions | never + }, + }, + }, +} +``` + +동작 참고: + +- 수신이 허용된 직후 즉시 전송됨(응답 전) +- 실패는 로그에 기록되지만 일반 응답 전달을 막지 않음 +- 그룹 모드 `mentions`는 멘션으로 트리거된 턴에 반응하며, 그룹 활성화 `always`는 이 검사를 우회하는 역할을 함 +- WhatsApp는 `channels.whatsapp.ackReaction`을 사용합니다(레거시 `messages.ackReaction`은 여기서 사용되지 않음) + +## 멀티 계정 및 자격 증명 + + + + - 계정 ID는 `channels.whatsapp.accounts`에서 가져옵니다 + - 기본 계정 선택: `default`가 있으면 그것을, 없으면 첫 번째로 구성된 계정 ID(정렬 기준) + - 조회를 위해 계정 ID는 내부적으로 정규화됩니다 + + + + - 현재 auth 경로: `~/.openclaw/credentials/whatsapp//creds.json` + - 백업 파일: `creds.json.bak` + - `~/.openclaw/credentials/`의 레거시 기본 auth도 기본 계정 흐름에서 계속 인식/마이그레이션됩니다 + + + + `openclaw channels logout --channel whatsapp [--account ]`는 해당 계정의 WhatsApp auth 상태를 지웁니다. + + 레거시 auth 디렉터리에서는 `oauth.json`은 보존되고 Baileys auth 파일만 제거됩니다. + + + + +## 도구, 작업, config 쓰기 + +- agent 도구 지원에는 WhatsApp 반응 작업(`react`)이 포함됩니다. +- 작업 게이트: + - `channels.whatsapp.actions.reactions` + - `channels.whatsapp.actions.polls` +- 채널 시작 config 쓰기는 기본적으로 활성화되어 있습니다(`channels.whatsapp.configWrites=false`로 비활성화). + +## 문제 해결 + + + + 증상: 채널 상태에 연결되지 않음으로 표시됨. + + 해결 방법: + + ```bash + openclaw channels login --channel whatsapp + openclaw channels status + ``` + + + + + 증상: 계정은 연결되었지만 연결 해제 또는 재연결 시도가 반복됨. + + 해결 방법: + + ```bash + openclaw doctor + openclaw logs --follow + ``` + + 필요하면 `channels login`으로 다시 연결하세요. + + + + + 대상 계정에 활성 gateway 리스너가 없으면 아웃바운드 전송은 즉시 실패합니다. + + gateway가 실행 중이고 계정이 연결되어 있는지 확인하세요. + + + + + 다음 순서로 확인하세요. + + - `groupPolicy` + - `groupAllowFrom` / `allowFrom` + - `groups` 허용 목록 항목 + - 멘션 게이팅(`requireMention` + mention 패턴) + - `openclaw.json`(JSON5)의 중복 키: 나중 항목이 앞선 항목을 덮어쓰므로 범위별로 `groupPolicy`는 하나만 유지하세요 + + + + + WhatsApp gateway 런타임은 Node를 사용해야 합니다. Bun은 안정적인 WhatsApp/Telegram gateway 운영과 호환되지 않는 것으로 표시됩니다. + + + +## 구성 참조 포인터 + +기본 참조: + +- [구성 참조 - WhatsApp](/gateway/configuration-reference#whatsapp) + +신호가 높은 WhatsApp 필드: + +- 접근: `dmPolicy`, `allowFrom`, `groupPolicy`, `groupAllowFrom`, `groups` +- 전달: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `sendReadReceipts`, `ackReaction`, `reactionLevel` +- 멀티 계정: `accounts..enabled`, `accounts..authDir`, 계정 수준 재정의 +- 운영: `configWrites`, `debounceMs`, `web.enabled`, `web.heartbeatSeconds`, `web.reconnect.*` +- 세션 동작: `session.dmScope`, `historyLimit`, `dmHistoryLimit`, `dms..historyLimit` + +## 관련 항목 + +- [페어링](/channels/pairing) +- [그룹](/channels/groups) +- [보안](/gateway/security) +- [채널 라우팅](/channels/channel-routing) +- [멀티 agent 라우팅](/concepts/multi-agent) +- [문제 해결](/channels/troubleshooting) diff --git a/docs/ko/channels/zalo.md b/docs/ko/channels/zalo.md new file mode 100644 index 000000000..922d8ab5a --- /dev/null +++ b/docs/ko/channels/zalo.md @@ -0,0 +1,259 @@ +--- +read_when: + - Zalo 기능 또는 웹훅 작업 중 +summary: Zalo 봇 지원 상태, 기능 및 구성 +title: Zalo +x-i18n: + generated_at: "2026-04-05T12:37:21Z" + model: gpt-5.4 + provider: openai + source_hash: ab94642ba28e79605b67586af8f71c18bc10e0af60343a7df508e6823b6f4119 + source_path: channels/zalo.md + workflow: 15 +--- + +# Zalo (Bot API) + +상태: 실험적입니다. DM이 지원됩니다. 아래 [Capabilities](#capabilities) 섹션은 현재 Marketplace 봇 동작을 반영합니다. + +## 번들 plugin + +Zalo는 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로, 일반적인 패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드 또는 Zalo가 제외된 커스텀 설치를 사용 중이라면 수동으로 설치하세요. + +- CLI로 설치: `openclaw plugins install @openclaw/zalo` +- 또는 소스 체크아웃에서 설치: `openclaw plugins install ./path/to/local/zalo-plugin` +- 자세한 내용: [Plugins](/tools/plugin) + +## 빠른 설정(초보자용) + +1. Zalo plugin을 사용할 수 있는지 확인하세요. + - 현재 패키지된 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/커스텀 설치에서는 위 명령으로 수동 추가할 수 있습니다. +2. 토큰을 설정하세요. + - Env: `ZALO_BOT_TOKEN=...` + - 또는 config: `channels.zalo.accounts.default.botToken: "..."`. +3. gateway를 재시작하세요(또는 설정을 완료하세요). +4. DM 액세스는 기본적으로 페어링이며, 첫 접촉 시 페어링 코드를 승인해야 합니다. + +최소 config: + +```json5 +{ + channels: { + zalo: { + enabled: true, + accounts: { + default: { + botToken: "12345689:abc-xyz", + dmPolicy: "pairing", + }, + }, + }, + }, +} +``` + +## 개요 + +Zalo는 베트남 중심의 메시징 앱이며, Bot API를 통해 Gateway가 1:1 대화용 봇을 실행할 수 있습니다. +Zalo로의 결정적 라우팅이 필요한 지원 또는 알림에 적합합니다. + +이 페이지는 **Zalo Bot Creator / Marketplace bots**에 대한 현재 OpenClaw 동작을 반영합니다. +**Zalo Official Account (OA) bots**는 다른 Zalo 제품 표면이며 동작이 다를 수 있습니다. + +- Gateway가 소유하는 Zalo Bot API 채널입니다. +- 결정적 라우팅: 응답은 다시 Zalo로 돌아가며, 모델이 채널을 선택하지 않습니다. +- DM은 에이전트의 메인 세션을 공유합니다. +- 아래 [Capabilities](#capabilities) 섹션은 현재 Marketplace 봇 지원 범위를 보여줍니다. + +## 설정(빠른 경로) + +### 1) 봇 토큰 만들기(Zalo Bot Platform) + +1. [https://bot.zaloplatforms.com](https://bot.zaloplatforms.com)으로 이동해 로그인합니다. +2. 새 봇을 만들고 설정을 구성합니다. +3. 전체 봇 토큰(보통 `numeric_id:secret`)을 복사합니다. Marketplace 봇의 경우, 실제 런타임에서 사용할 수 있는 토큰은 생성 후 봇 환영 메시지에 표시될 수 있습니다. + +### 2) 토큰 구성(env 또는 config) + +예시: + +```json5 +{ + channels: { + zalo: { + enabled: true, + accounts: { + default: { + botToken: "12345689:abc-xyz", + dmPolicy: "pairing", + }, + }, + }, + }, +} +``` + +나중에 그룹을 사용할 수 있는 Zalo 봇 표면으로 이동하면 `groupPolicy` 및 `groupAllowFrom` 같은 그룹별 config를 명시적으로 추가할 수 있습니다. 현재 Marketplace 봇 동작은 [Capabilities](#capabilities)를 참조하세요. + +Env 옵션: `ZALO_BOT_TOKEN=...` (기본 계정에서만 작동). + +다중 계정 지원: `channels.zalo.accounts`를 사용해 계정별 토큰과 선택적 `name`을 설정합니다. + +3. gateway를 재시작하세요. 토큰이 확인되면(env 또는 config) Zalo가 시작됩니다. +4. DM 액세스의 기본값은 페어링입니다. 봇에 처음 연락할 때 코드를 승인하세요. + +## 작동 방식(동작) + +- 수신 메시지는 미디어 플레이스홀더가 포함된 공유 채널 엔벌로프로 정규화됩니다. +- 응답은 항상 동일한 Zalo 채팅으로 다시 라우팅됩니다. +- 기본값은 long-polling이며, `channels.zalo.webhookUrl`을 사용하면 webhook 모드를 사용할 수 있습니다. + +## 제한 사항 + +- 발신 텍스트는 2000자로 분할됩니다(Zalo API 제한). +- 미디어 다운로드/업로드는 `channels.zalo.mediaMaxMb`(기본값 5)로 제한됩니다. +- 2000자 제한으로 인해 스트리밍의 유용성이 낮아서 기본적으로 스트리밍은 차단됩니다. + +## 액세스 제어(DM) + +### DM 액세스 + +- 기본값: `channels.zalo.dmPolicy = "pairing"`. 알 수 없는 발신자는 페어링 코드를 받으며, 승인되기 전까지 메시지는 무시됩니다(코드는 1시간 후 만료). +- 승인 방법: + - `openclaw pairing list zalo` + - `openclaw pairing approve zalo ` +- 페어링은 기본 토큰 교환 방식입니다. 자세한 내용: [Pairing](/channels/pairing) +- `channels.zalo.allowFrom`은 숫자 사용자 ID를 받습니다(사용자 이름 조회는 제공되지 않음). + +## 액세스 제어(Groups) + +**Zalo Bot Creator / Marketplace bots**의 경우, 실제로는 봇을 그룹에 아예 추가할 수 없었기 때문에 그룹 지원을 사용할 수 없었습니다. + +즉, 아래 그룹 관련 config 키는 스키마에 존재하지만 Marketplace 봇에서는 사용할 수 없었습니다. + +- `channels.zalo.groupPolicy`는 그룹 수신 처리를 제어합니다: `open | allowlist | disabled`. +- `channels.zalo.groupAllowFrom`은 그룹에서 어떤 발신자 ID가 봇을 트리거할 수 있는지 제한합니다. +- `groupAllowFrom`이 설정되지 않으면, Zalo는 발신자 검사에 `allowFrom`으로 폴백합니다. +- 런타임 참고: `channels.zalo`가 완전히 누락된 경우에도 안전을 위해 런타임은 여전히 `groupPolicy="allowlist"`로 폴백합니다. + +그룹 액세스를 봇 표면에서 사용할 수 있을 때의 그룹 정책 값은 다음과 같습니다. + +- `groupPolicy: "disabled"` — 모든 그룹 메시지를 차단합니다. +- `groupPolicy: "open"` — 모든 그룹 멤버를 허용합니다(mention 게이트 적용). +- `groupPolicy: "allowlist"` — fail-closed 기본값이며, 허용된 발신자만 수락합니다. + +다른 Zalo 봇 제품 표면을 사용 중이고 실제로 작동하는 그룹 동작을 확인했다면, Marketplace 봇 흐름과 같다고 가정하지 말고 별도로 문서화하세요. + +## Long-polling vs webhook + +- 기본값: long-polling(공개 URL 불필요). +- Webhook 모드: `channels.zalo.webhookUrl` 및 `channels.zalo.webhookSecret`를 설정하세요. + - webhook secret은 8~256자여야 합니다. + - webhook URL은 HTTPS를 사용해야 합니다. + - Zalo는 검증을 위해 `X-Bot-Api-Secret-Token` 헤더와 함께 이벤트를 보냅니다. + - Gateway HTTP는 `channels.zalo.webhookPath`에서 webhook 요청을 처리합니다(기본값: webhook URL 경로). + - 요청은 `Content-Type: application/json`(또는 `+json` 미디어 타입)을 사용해야 합니다. + - 중복 이벤트(`event_name + message_id`)는 짧은 재생 방지 창 동안 무시됩니다. + - 버스트 트래픽은 경로/소스별로 속도 제한되며 HTTP 429를 반환할 수 있습니다. + +**참고:** Zalo API 문서에 따라 getUpdates(polling)와 webhook은 동시에 사용할 수 없습니다. + +## 지원되는 메시지 유형 + +빠른 지원 현황은 [Capabilities](#capabilities)를 참조하세요. 아래 참고 사항은 동작에 추가 컨텍스트가 필요한 부분을 보완합니다. + +- **텍스트 메시지**: 2000자 분할과 함께 완전 지원. +- **텍스트 내 일반 URL**: 일반 텍스트 입력처럼 동작합니다. +- **링크 미리보기 / 리치 링크 카드**: [Capabilities](#capabilities)의 Marketplace 봇 상태를 참조하세요. 안정적으로 응답을 트리거하지 못했습니다. +- **이미지 메시지**: [Capabilities](#capabilities)의 Marketplace 봇 상태를 참조하세요. 수신 이미지 처리는 신뢰할 수 없었습니다(최종 응답 없이 타이핑 표시만 나타남). +- **스티커**: [Capabilities](#capabilities)의 Marketplace 봇 상태를 참조하세요. +- **음성 메모 / 오디오 파일 / 비디오 / 일반 파일 첨부**: [Capabilities](#capabilities)의 Marketplace 봇 상태를 참조하세요. +- **지원되지 않는 유형**: 기록됩니다(예: 보호된 사용자의 메시지). + +## Capabilities + +이 표는 OpenClaw에서의 현재 **Zalo Bot Creator / Marketplace bot** 동작을 요약합니다. + +| 기능 | 상태 | +| --------------------------- | --------------------------------------- | +| 다이렉트 메시지 | ✅ 지원됨 | +| Groups | ❌ Marketplace 봇에서는 사용 불가 | +| 미디어(수신 이미지) | ⚠️ 제한적 / 환경에서 확인 필요 | +| 미디어(발신 이미지) | ⚠️ Marketplace 봇에서 재테스트 안 됨 | +| 텍스트 내 일반 URL | ✅ 지원됨 | +| 링크 미리보기 | ⚠️ Marketplace 봇에서는 신뢰하기 어려움 | +| Reactions | ❌ 지원되지 않음 | +| 스티커 | ⚠️ Marketplace 봇에서는 에이전트 응답 없음 | +| 음성 메모 / 오디오 / 비디오 | ⚠️ Marketplace 봇에서는 에이전트 응답 없음 | +| 파일 첨부 | ⚠️ Marketplace 봇에서는 에이전트 응답 없음 | +| Threads | ❌ 지원되지 않음 | +| Polls | ❌ 지원되지 않음 | +| Native commands | ❌ 지원되지 않음 | +| Streaming | ⚠️ 차단됨(2000자 제한) | + +## 전달 대상(CLI/cron) + +- 대화 ID를 대상으로 사용하세요. +- 예시: `openclaw message send --channel zalo --target 123456789 --message "hi"`. + +## 문제 해결 + +**봇이 응답하지 않음:** + +- 토큰이 유효한지 확인하세요: `openclaw channels status --probe` +- 발신자가 승인되었는지 확인하세요(페어링 또는 allowFrom) +- gateway 로그를 확인하세요: `openclaw logs --follow` + +**Webhook이 이벤트를 수신하지 않음:** + +- webhook URL이 HTTPS를 사용하는지 확인하세요 +- secret token이 8~256자인지 확인하세요 +- 구성된 경로에서 gateway HTTP 엔드포인트에 도달할 수 있는지 확인하세요 +- getUpdates polling이 실행 중이 아닌지 확인하세요(둘은 동시에 사용할 수 없음) + +## 구성 참조(Zalo) + +전체 구성: [Configuration](/gateway/configuration) + +평면 최상위 키(`channels.zalo.botToken`, `channels.zalo.dmPolicy` 등)는 레거시 단일 계정 축약형입니다. 새 config에는 `channels.zalo.accounts..*`를 권장합니다. 두 형식 모두 스키마에 존재하므로 여기서 계속 문서화합니다. + +Provider 옵션: + +- `channels.zalo.enabled`: 채널 시작 활성화/비활성화. +- `channels.zalo.botToken`: Zalo Bot Platform의 봇 토큰. +- `channels.zalo.tokenFile`: 일반 파일 경로에서 토큰을 읽습니다. 심볼릭 링크는 거부됩니다. +- `channels.zalo.dmPolicy`: `pairing | allowlist | open | disabled` (기본값: pairing). +- `channels.zalo.allowFrom`: DM allowlist(사용자 ID). `open`에는 `"*"`가 필요합니다. 마법사는 숫자 ID를 요청합니다. +- `channels.zalo.groupPolicy`: `open | allowlist | disabled` (기본값: allowlist). config에는 존재하며, 현재 Marketplace 봇 동작은 [Capabilities](#capabilities) 및 [Access control (Groups)](#access-control-groups)를 참조하세요. +- `channels.zalo.groupAllowFrom`: 그룹 발신자 allowlist(사용자 ID). 설정되지 않으면 `allowFrom`으로 폴백합니다. +- `channels.zalo.mediaMaxMb`: 수신/발신 미디어 상한(MB, 기본값 5). +- `channels.zalo.webhookUrl`: webhook 모드 활성화(HTTPS 필수). +- `channels.zalo.webhookSecret`: webhook secret(8~256자). +- `channels.zalo.webhookPath`: gateway HTTP 서버의 webhook 경로. +- `channels.zalo.proxy`: API 요청용 프록시 URL. + +다중 계정 옵션: + +- `channels.zalo.accounts..botToken`: 계정별 토큰. +- `channels.zalo.accounts..tokenFile`: 계정별 일반 토큰 파일. 심볼릭 링크는 거부됩니다. +- `channels.zalo.accounts..name`: 표시 이름. +- `channels.zalo.accounts..enabled`: 계정 활성화/비활성화. +- `channels.zalo.accounts..dmPolicy`: 계정별 DM 정책. +- `channels.zalo.accounts..allowFrom`: 계정별 allowlist. +- `channels.zalo.accounts..groupPolicy`: 계정별 그룹 정책. config에는 존재하며, 현재 Marketplace 봇 동작은 [Capabilities](#capabilities) 및 [Access control (Groups)](#access-control-groups)를 참조하세요. +- `channels.zalo.accounts..groupAllowFrom`: 계정별 그룹 발신자 allowlist. +- `channels.zalo.accounts..webhookUrl`: 계정별 webhook URL. +- `channels.zalo.accounts..webhookSecret`: 계정별 webhook secret. +- `channels.zalo.accounts..webhookPath`: 계정별 webhook 경로. +- `channels.zalo.accounts..proxy`: 계정별 프록시 URL. + +## 관련 문서 + +- [Channels Overview](/channels) — 지원되는 모든 채널 +- [Pairing](/channels/pairing) — DM 인증 및 페어링 흐름 +- [Groups](/channels/groups) — 그룹 채팅 동작 및 mention 게이팅 +- [Channel Routing](/channels/channel-routing) — 메시지의 세션 라우팅 +- [Security](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/channels/zalouser.md b/docs/ko/channels/zalouser.md new file mode 100644 index 000000000..7e27f32bb --- /dev/null +++ b/docs/ko/channels/zalouser.md @@ -0,0 +1,202 @@ +--- +read_when: + - OpenClaw용 Zalo Personal을 설정할 때 + - Zalo Personal 로그인 또는 메시지 흐름을 디버깅할 때 +summary: native `zca-js`(QR 로그인)를 통한 Zalo 개인 계정 지원, 기능 및 구성 +title: Zalo Personal +x-i18n: + generated_at: "2026-04-05T12:37:17Z" + model: gpt-5.4 + provider: openai + source_hash: 331b95041463185472d242cb0a944972f0a8e99df8120bda6350eca86ad5963f + source_path: channels/zalouser.md + workflow: 15 +--- + +# Zalo Personal (비공식) + +상태: 실험적. 이 통합은 OpenClaw 내부에서 native `zca-js`를 통해 **개인 Zalo 계정**을 자동화합니다. + +> **경고:** 이는 비공식 통합이며 계정 정지/차단으로 이어질 수 있습니다. 본인 책임하에 사용하세요. + +## 번들 plugin + +Zalo Personal은 현재 OpenClaw 릴리스에 번들 plugin으로 포함되어 있으므로, 일반적인 +패키지 빌드에서는 별도 설치가 필요하지 않습니다. + +이전 빌드 또는 Zalo Personal이 제외된 사용자 지정 설치를 사용 중이라면 +수동으로 설치하세요: + +- CLI로 설치: `openclaw plugins install @openclaw/zalouser` +- 또는 소스 체크아웃에서 설치: `openclaw plugins install ./path/to/local/zalouser-plugin` +- 자세한 내용: [Plugins](/tools/plugin) + +외부 `zca`/`openzca` CLI 바이너리는 필요하지 않습니다. + +## 빠른 설정(초보자용) + +1. Zalo Personal plugin을 사용할 수 있는지 확인합니다. + - 현재 패키지된 OpenClaw 릴리스에는 이미 번들로 포함되어 있습니다. + - 이전/사용자 지정 설치는 위 명령으로 수동 추가할 수 있습니다. +2. 로그인합니다(QR, 게이트웨이 머신에서): + - `openclaw channels login --channel zalouser` + - Zalo 모바일 앱으로 QR 코드를 스캔합니다. +3. 채널을 활성화합니다: + +```json5 +{ + channels: { + zalouser: { + enabled: true, + dmPolicy: "pairing", + }, + }, +} +``` + +4. 게이트웨이를 재시작합니다(또는 설정을 완료합니다). +5. DM 액세스는 기본적으로 pairing이며, 첫 연락 시 페어링 코드를 승인합니다. + +## 개요 + +- 전부 `zca-js`를 통해 인프로세스로 실행됩니다. +- native 이벤트 리스너를 사용해 수신 메시지를 받습니다. +- JS API를 통해 직접 응답을 전송합니다(텍스트/미디어/링크). +- Zalo Bot API를 사용할 수 없는 “개인 계정” 사용 사례를 위해 설계되었습니다. + +## 이름 지정 + +채널 id는 **비공식적인 개인 Zalo 사용자 계정**을 자동화한다는 점을 명확히 하기 위해 `zalouser`입니다. `zalo`는 향후 공식 Zalo API 통합 가능성을 위해 예약해 둡니다. + +## ID 찾기(디렉터리) + +디렉터리 CLI를 사용해 피어/그룹과 해당 ID를 찾으세요: + +```bash +openclaw directory self --channel zalouser +openclaw directory peers list --channel zalouser --query "name" +openclaw directory groups list --channel zalouser --query "work" +``` + +## 제한 사항 + +- 발신 텍스트는 ~2000자로 청크 처리됩니다(Zalo 클라이언트 제한). +- 스트리밍은 기본적으로 차단됩니다. + +## 액세스 제어(DM) + +`channels.zalouser.dmPolicy`는 다음을 지원합니다: `pairing | allowlist | open | disabled` (기본값: `pairing`). + +`channels.zalouser.allowFrom`은 사용자 ID 또는 이름을 받습니다. 설정 중에는 plugin의 인프로세스 연락처 조회를 사용해 이름을 ID로 확인합니다. + +다음을 통해 승인합니다: + +- `openclaw pairing list zalouser` +- `openclaw pairing approve zalouser ` + +## 그룹 액세스(선택 사항) + +- 기본값: `channels.zalouser.groupPolicy = "open"` (그룹 허용). 설정되지 않은 경우 기본값을 재정의하려면 `channels.defaults.groupPolicy`를 사용하세요. +- 허용 목록으로 제한하려면: + - `channels.zalouser.groupPolicy = "allowlist"` + - `channels.zalouser.groups` (키는 안정적인 그룹 ID여야 하며, 가능하면 시작 시 이름이 ID로 확인됩니다) + - `channels.zalouser.groupAllowFrom` (허용된 그룹에서 어떤 발신자가 봇을 트리거할 수 있는지 제어) +- 모든 그룹 차단: `channels.zalouser.groupPolicy = "disabled"`. +- 구성 마법사는 그룹 허용 목록을 물어볼 수 있습니다. +- 시작 시 OpenClaw는 허용 목록의 그룹/사용자 이름을 ID로 확인하고 그 매핑을 로그에 남깁니다. +- 그룹 허용 목록 일치는 기본적으로 ID 전용입니다. `channels.zalouser.dangerouslyAllowNameMatching: true`가 활성화되지 않으면 확인되지 않은 이름은 인증에서 무시됩니다. +- `channels.zalouser.dangerouslyAllowNameMatching: true`는 변경 가능한 그룹 이름 일치를 다시 활성화하는 비상 호환 모드입니다. +- `groupAllowFrom`이 설정되지 않은 경우 런타임은 그룹 발신자 검사에 `allowFrom`으로 대체합니다. +- 발신자 검사는 일반 그룹 메시지와 제어 명령(예: `/new`, `/reset`) 모두에 적용됩니다. + +예시: + +```json5 +{ + channels: { + zalouser: { + groupPolicy: "allowlist", + groupAllowFrom: ["1471383327500481391"], + groups: { + "123456789": { allow: true }, + "Work Chat": { allow: true }, + }, + }, + }, +} +``` + +### 그룹 멘션 게이팅 + +- `channels.zalouser.groups..requireMention`은 그룹 응답에 멘션이 필요한지 제어합니다. +- 확인 순서: 정확한 그룹 id/이름 -> 정규화된 그룹 slug -> `*` -> 기본값 (`true`). +- 이는 허용 목록 그룹과 open 그룹 모드 모두에 적용됩니다. +- 권한 있는 제어 명령(예: `/new`)은 멘션 게이팅을 우회할 수 있습니다. +- 그룹 메시지가 멘션 필요 때문에 건너뛰어지면 OpenClaw는 이를 보류 중인 그룹 기록으로 저장하고 다음에 처리되는 그룹 메시지에 포함합니다. +- 그룹 기록 제한은 기본적으로 `messages.groupChat.historyLimit`(대체값 `50`)입니다. 계정별로 `channels.zalouser.historyLimit`로 재정의할 수 있습니다. + +예시: + +```json5 +{ + channels: { + zalouser: { + groupPolicy: "allowlist", + groups: { + "*": { allow: true, requireMention: true }, + "Work Chat": { allow: true, requireMention: false }, + }, + }, + }, +} +``` + +## 다중 계정 + +계정은 OpenClaw 상태의 `zalouser` 프로필에 매핑됩니다. 예시: + +```json5 +{ + channels: { + zalouser: { + enabled: true, + defaultAccount: "default", + accounts: { + work: { enabled: true, profile: "work" }, + }, + }, + }, +} +``` + +## 입력 중, 반응 및 전달 확인 + +- OpenClaw는 응답을 전송하기 전에 입력 중 이벤트를 보냅니다(best-effort). +- 메시지 반응 액션 `react`는 채널 액션에서 `zalouser`에 대해 지원됩니다. + - 메시지에서 특정 반응 이모지를 제거하려면 `remove: true`를 사용하세요. + - 반응 의미 체계: [Reactions](/tools/reactions) +- 이벤트 메타데이터가 포함된 수신 메시지에 대해서는 OpenClaw가 전달됨 + 확인함 확인 응답을 보냅니다(best-effort). + +## 문제 해결 + +**로그인이 유지되지 않음:** + +- `openclaw channels status --probe` +- 다시 로그인: `openclaw channels logout --channel zalouser && openclaw channels login --channel zalouser` + +**허용 목록/그룹 이름이 확인되지 않음:** + +- `allowFrom`/`groupAllowFrom`/`groups`에 숫자 ID를 사용하거나, 정확한 친구/그룹 이름을 사용하세요. + +**이전 CLI 기반 설정에서 업그레이드함:** + +- 기존 외부 `zca` 프로세스 가정을 제거하세요. +- 이제 이 채널은 외부 CLI 바이너리 없이 OpenClaw 내부에서 완전히 실행됩니다. + +## 관련 항목 + +- [채널 개요](/channels) — 지원되는 모든 채널 +- [페어링](/channels/pairing) — DM 인증 및 페어링 흐름 +- [그룹](/channels/groups) — 그룹 채팅 동작 및 멘션 게이팅 +- [채널 라우팅](/channels/channel-routing) — 메시지용 세션 라우팅 +- [보안](/gateway/security) — 액세스 모델 및 강화 diff --git a/docs/ko/ci.md b/docs/ko/ci.md new file mode 100644 index 000000000..4fbf91cf0 --- /dev/null +++ b/docs/ko/ci.md @@ -0,0 +1,73 @@ +--- +read_when: + - CI 작업이 실행되었거나 실행되지 않은 이유를 이해해야 할 때 + - 실패한 GitHub Actions 검사를 디버깅할 때 +summary: CI 작업 그래프, 범위 게이트, 그리고 로컬 명령 대응 항목 +title: CI 파이프라인 +x-i18n: + generated_at: "2026-04-05T12:37:09Z" + model: gpt-5.4 + provider: openai + source_hash: 5a95b6e584b4309bc249866ea436b4dfe30e0298ab8916eadbc344edae3d1194 + source_path: ci.md + workflow: 15 +--- + +# CI 파이프라인 + +CI는 `main`에 대한 모든 push와 모든 pull request에서 실행됩니다. 관련 없는 영역만 변경되었을 때 비용이 큰 작업을 건너뛰도록 스마트 범위 지정을 사용합니다. + +## 작업 개요 + +| 작업 | 목적 | 실행 시점 | +| ------------------------ | ---------------------------------------------------------------------------------------- | ----------------------------------- | +| `preflight` | 문서 전용 변경, 변경된 범위, 변경된 확장, 그리고 CI 매니페스트 빌드를 감지 | 초안이 아닌 모든 push 및 PR에서 항상 실행 | +| `security-fast` | 비공개 키 감지, `zizmor`를 통한 워크플로 감사, 프로덕션 의존성 감사 | 초안이 아닌 모든 push 및 PR에서 항상 실행 | +| `build-artifacts` | `dist/`와 Control UI를 한 번 빌드하고, 후속 작업을 위한 재사용 가능한 아티팩트를 업로드 | Node 관련 변경 | +| `checks-fast-core` | 번들/플러그인 계약/프로토콜 검사 같은 빠른 Linux 정확성 레인 | Node 관련 변경 | +| `checks-fast-extensions` | `checks-fast-extensions-shard` 완료 후 확장 샤드 레인 집계 | Node 관련 변경 | +| `extension-fast` | 변경된 번들 plugin에 대해서만 집중 테스트 실행 | 확장 변경이 감지될 때 | +| `check` | CI의 기본 로컬 게이트: `pnpm check` + `pnpm build:strict-smoke` | Node 관련 변경 | +| `check-additional` | 아키텍처 및 경계 가드와 게이트웨이 watch 회귀 하네스 | Node 관련 변경 | +| `build-smoke` | 빌드된 CLI 스모크 테스트 및 시작 메모리 스모크 | Node 관련 변경 | +| `checks` | 더 무거운 Linux Node 레인: 전체 테스트, 채널 테스트, 그리고 push 전용 Node 22 호환성 | Node 관련 변경 | +| `check-docs` | 문서 포맷팅, lint, 깨진 링크 검사 | 문서가 변경되었을 때 | +| `skills-python` | Python 기반 Skills에 대한 Ruff + pytest | Python Skills 관련 변경 | +| `checks-windows` | Windows 전용 테스트 레인 | Windows 관련 변경 | +| `macos-node` | 공유 빌드 아티팩트를 사용하는 macOS TypeScript 테스트 레인 | macOS 관련 변경 | +| `macos-swift` | macOS 앱용 Swift lint, 빌드, 테스트 | macOS 관련 변경 | +| `android` | Android 빌드 및 테스트 매트릭스 | Android 관련 변경 | + +## Fail-Fast 순서 + +작업은 비용이 큰 작업이 실행되기 전에 저렴한 검사가 먼저 실패하도록 순서가 정해져 있습니다. + +1. `preflight`가 어떤 레인이 아예 존재하는지를 결정합니다. `docs-scope`와 `changed-scope` 로직은 독립 작업이 아니라 이 작업 내부의 단계입니다. +2. `security-fast`, `check`, `check-additional`, `check-docs`, `skills-python`은 더 무거운 아티팩트 및 플랫폼 매트릭스 작업을 기다리지 않고 빠르게 실패합니다. +3. `build-artifacts`는 빠른 Linux 레인과 겹쳐 실행되므로, 후속 소비자는 공유 빌드가 준비되는 즉시 시작할 수 있습니다. +4. 그다음 더 무거운 플랫폼 및 런타임 레인이 분기됩니다: `checks-fast-core`, `checks-fast-extensions`, `extension-fast`, `checks`, `checks-windows`, `macos-node`, `macos-swift`, `android`. + +범위 로직은 `scripts/ci-changed-scope.mjs`에 있으며 `src/scripts/ci-changed-scope.test.ts`의 단위 테스트로 검증됩니다. +별도의 `install-smoke` 워크플로는 자체 `preflight` 작업을 통해 동일한 범위 스크립트를 재사용합니다. 이는 더 좁은 changed-smoke 신호에서 `run_install_smoke`를 계산하므로, Docker/install smoke는 설치, 패키징, 컨테이너 관련 변경에 대해서만 실행됩니다. + +push에서는 `checks` 매트릭스에 push 전용 `compat-node22` 레인이 추가됩니다. pull request에서는 이 레인이 건너뛰어지고, 매트릭스는 일반 테스트/채널 레인에 집중된 상태를 유지합니다. + +## 러너 + +| 러너 | 작업 | +| -------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `blacksmith-16vcpu-ubuntu-2404` | `preflight`, `security-fast`, `build-artifacts`, Linux 검사, 문서 검사, Python Skills, `android` | +| `blacksmith-32vcpu-windows-2025` | `checks-windows` | +| `macos-latest` | `macos-node`, `macos-swift` | + +## 로컬 대응 명령 + +```bash +pnpm check # types + lint + format +pnpm build:strict-smoke +pnpm test:gateway:watch-regression +pnpm test # vitest tests +pnpm test:channels +pnpm check:docs # docs format + lint + broken links +pnpm build # CI artifact/build-smoke 레인이 중요할 때 dist 빌드 +``` diff --git a/docs/ko/cli/acp.md b/docs/ko/cli/acp.md new file mode 100644 index 000000000..8dbf106df --- /dev/null +++ b/docs/ko/cli/acp.md @@ -0,0 +1,292 @@ +--- +read_when: + - ACP 기반 IDE 통합을 설정할 때 + - Gateway로의 ACP 세션 라우팅을 디버깅할 때 +summary: IDE 통합용 ACP 브리지 실행 +title: acp +x-i18n: + generated_at: "2026-04-05T12:37:39Z" + model: gpt-5.4 + provider: openai + source_hash: 2461b181e4a97dd84580581e9436ca1947a224decce8044132dbcf7fb2b7502c + source_path: cli/acp.md + workflow: 15 +--- + +# acp + +OpenClaw Gateway와 통신하는 [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) 브리지를 실행합니다. + +이 명령은 IDE를 위해 stdio를 통해 ACP를 사용하고 프롬프트를 WebSocket을 통해 Gateway로 전달합니다. ACP 세션이 Gateway 세션 키에 매핑되도록 유지합니다. + +`openclaw acp`는 Gateway 기반 ACP 브리지이며, 완전한 ACP 네이티브 편집기 런타임은 아닙니다. 세션 라우팅, 프롬프트 전달, 기본 스트리밍 업데이트에 중점을 둡니다. + +외부 MCP 클라이언트가 ACP 하니스 세션을 호스팅하는 대신 OpenClaw 채널 대화에 직접 연결되게 하려면 [`openclaw mcp serve`](/cli/mcp)를 대신 사용하세요. + +## 이것이 아닌 것 + +이 페이지는 종종 ACP 하니스 세션과 혼동됩니다. + +`openclaw acp`의 의미는 다음과 같습니다. + +- OpenClaw가 ACP 서버로 동작함 +- IDE 또는 ACP 클라이언트가 OpenClaw에 연결함 +- OpenClaw가 해당 작업을 Gateway 세션으로 전달함 + +이는 OpenClaw가 `acpx`를 통해 Codex 또는 Claude Code 같은 외부 하니스를 실행하는 [ACP 에이전트](/tools/acp-agents)와는 다릅니다. + +간단한 기준: + +- 편집기/클라이언트가 ACP로 OpenClaw와 통신하려는 경우: `openclaw acp` 사용 +- OpenClaw가 Codex/Claude/Gemini를 ACP 하니스로 실행해야 하는 경우: `/acp spawn` 및 [ACP 에이전트](/tools/acp-agents) 사용 + +## 호환성 매트릭스 + +| ACP 영역 | 상태 | 참고 | +| --------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `initialize`, `newSession`, `prompt`, `cancel` | 구현됨 | stdio에서 Gateway chat/send + abort로 이어지는 핵심 브리지 흐름입니다. | +| `listSessions`, 슬래시 명령 | 구현됨 | 세션 목록은 Gateway 세션 상태를 기준으로 동작하며, 명령은 `available_commands_update`를 통해 광고됩니다. | +| `loadSession` | 부분 지원 | ACP 세션을 Gateway 세션 키에 다시 바인딩하고 저장된 사용자/assistant 텍스트 기록을 재생합니다. 도구/시스템 기록은 아직 재구성되지 않습니다. | +| 프롬프트 콘텐츠(`text`, 내장 `resource`, 이미지) | 부분 지원 | 텍스트/리소스는 채팅 입력으로 평탄화되고, 이미지는 Gateway 첨부 파일이 됩니다. | +| 세션 모드 | 부분 지원 | `session/set_mode`가 지원되며 브리지는 thought level, tool verbosity, reasoning, usage detail, elevated actions에 대한 초기 Gateway 기반 세션 제어를 노출합니다. 더 광범위한 ACP 네이티브 모드/config 표면은 아직 범위 밖입니다. | +| 세션 정보 및 사용량 업데이트 | 부분 지원 | 브리지는 캐시된 Gateway 세션 스냅샷에서 `session_info_update` 및 최선의 노력 방식의 `usage_update` 알림을 내보냅니다. 사용량은 근사치이며 Gateway 토큰 총계가 최신으로 표시될 때만 전송됩니다. | +| 도구 스트리밍 | 부분 지원 | `tool_call` / `tool_call_update` 이벤트에는 원시 I/O, 텍스트 콘텐츠, 그리고 Gateway 도구 인자/결과가 이를 노출할 경우 최선의 노력 방식의 파일 위치가 포함됩니다. 내장 터미널과 더 풍부한 diff 네이티브 출력은 아직 노출되지 않습니다. | +| 세션별 MCP 서버(`mcpServers`) | 지원되지 않음 | 브리지 모드는 세션별 MCP 서버 요청을 거부합니다. 대신 OpenClaw gateway 또는 agent에서 MCP를 구성하세요. | +| 클라이언트 파일시스템 메서드(`fs/read_text_file`, `fs/write_text_file`) | 지원되지 않음 | 브리지는 ACP 클라이언트 파일시스템 메서드를 호출하지 않습니다. | +| 클라이언트 터미널 메서드(`terminal/*`) | 지원되지 않음 | 브리지는 ACP 클라이언트 터미널을 생성하거나 tool call을 통해 터미널 ID를 스트리밍하지 않습니다. | +| 세션 계획 / thought 스트리밍 | 지원되지 않음 | 브리지는 현재 ACP 계획 또는 thought 업데이트가 아니라 출력 텍스트와 도구 상태를 내보냅니다. | + +## 알려진 제한 사항 + +- `loadSession`은 저장된 사용자 및 assistant 텍스트 기록을 재생하지만, 과거 도구 호출, 시스템 알림 또는 더 풍부한 ACP 네이티브 이벤트 유형은 재구성하지 않습니다. +- 여러 ACP 클라이언트가 동일한 Gateway 세션 키를 공유하는 경우, 이벤트 및 취소 라우팅은 클라이언트별로 엄격히 격리되지 않고 최선의 노력 방식으로 동작합니다. 편집기 로컬 턴을 깔끔하게 유지해야 한다면 기본 격리된 `acp:` 세션을 사용하는 것이 좋습니다. +- Gateway 중지 상태는 ACP 중지 사유로 변환되지만, 이 매핑은 완전한 ACP 네이티브 런타임보다 표현력이 떨어집니다. +- 초기 세션 제어는 현재 Gateway 노브의 일부에만 집중해 노출합니다: thought level, tool verbosity, reasoning, usage detail, elevated actions. 모델 선택과 exec-host 제어는 아직 ACP config 옵션으로 노출되지 않습니다. +- `session_info_update`와 `usage_update`는 실시간 ACP 네이티브 런타임 계정이 아니라 Gateway 세션 스냅샷에서 파생됩니다. 사용량은 근사치이며 비용 데이터는 포함하지 않고, Gateway가 총 토큰 데이터를 최신으로 표시할 때만 전송됩니다. +- 도구 추적 데이터는 최선의 노력 방식입니다. 브리지는 알려진 도구 인자/결과에 나타나는 파일 경로를 노출할 수 있지만, ACP 터미널이나 구조화된 파일 diff는 아직 내보내지 않습니다. + +## 사용법 + +```bash +openclaw acp + +# 원격 Gateway +openclaw acp --url wss://gateway-host:18789 --token + +# 원격 Gateway (파일에서 토큰 읽기) +openclaw acp --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token + +# 기존 세션 키에 연결 +openclaw acp --session agent:main:main + +# 라벨로 연결(이미 존재해야 함) +openclaw acp --session-label "support inbox" + +# 첫 번째 프롬프트 전에 세션 키 재설정 +openclaw acp --session agent:main:main --reset-session +``` + +## ACP 클라이언트(디버그) + +내장 ACP 클라이언트를 사용하면 IDE 없이 브리지를 기본 점검할 수 있습니다. +이 클라이언트는 ACP 브리지를 실행하고 프롬프트를 대화형으로 입력할 수 있게 합니다. + +```bash +openclaw acp client + +# 실행된 브리지를 원격 Gateway로 연결 +openclaw acp client --server-args --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token + +# 서버 명령 재정의(기본값: openclaw) +openclaw acp client --server "node" --server-args openclaw.mjs acp --url ws://127.0.0.1:19001 +``` + +권한 모델(클라이언트 디버그 모드): + +- 자동 승인은 허용 목록 기반이며 신뢰된 핵심 도구 ID에만 적용됩니다. +- `read` 자동 승인은 현재 작업 디렉터리(`--cwd`가 설정된 경우) 범위로 제한됩니다. +- ACP는 현재 작업 디렉터리 아래의 범위 제한 `read` 호출과 읽기 전용 검색 도구(`search`, `web_search`, `memory_search`) 같은 좁은 읽기 전용 범주만 자동 승인합니다. 알 수 없거나 비핵심 도구, 범위 밖 읽기, 실행 가능한 도구, 컨트롤 플레인 도구, 변경 도구, 대화형 흐름은 항상 명시적인 프롬프트 승인이 필요합니다. +- 서버가 제공하는 `toolCall.kind`는 신뢰할 수 없는 메타데이터로 취급되며(권한 부여의 근거가 아님). +- 이 ACP 브리지 정책은 ACPX 하니스 권한과 별개입니다. OpenClaw를 `acpx` 백엔드를 통해 실행하는 경우, `plugins.entries.acpx.config.permissionMode=approve-all`이 해당 하니스 세션의 비상 시 “yolo” 스위치입니다. + +## 사용 방법 + +IDE(또는 다른 클라이언트)가 Agent Client Protocol을 사용하고, 이를 통해 OpenClaw Gateway 세션을 구동하고 싶을 때 ACP를 사용합니다. + +1. Gateway가 실행 중인지 확인합니다(로컬 또는 원격). +2. Gateway 대상(config 또는 플래그)을 구성합니다. +3. IDE가 stdio를 통해 `openclaw acp`를 실행하도록 지정합니다. + +예시 config(영구 저장): + +```bash +openclaw config set gateway.remote.url wss://gateway-host:18789 +openclaw config set gateway.remote.token +``` + +예시 직접 실행(config 쓰기 없음): + +```bash +openclaw acp --url wss://gateway-host:18789 --token +# 로컬 프로세스 안전 측면에서 권장 +openclaw acp --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token +``` + +## 에이전트 선택 + +ACP는 에이전트를 직접 선택하지 않습니다. Gateway 세션 키를 기준으로 라우팅합니다. + +특정 에이전트를 대상으로 하려면 에이전트 범위 세션 키를 사용하세요. + +```bash +openclaw acp --session agent:main:main +openclaw acp --session agent:design:main +openclaw acp --session agent:qa:bug-123 +``` + +각 ACP 세션은 하나의 Gateway 세션 키에 매핑됩니다. 하나의 에이전트는 여러 세션을 가질 수 있으며, ACP는 키나 라벨을 재정의하지 않으면 기본적으로 격리된 `acp:` 세션을 사용합니다. + +세션별 `mcpServers`는 브리지 모드에서 지원되지 않습니다. ACP 클라이언트가 `newSession` 또는 `loadSession` 중 이를 보내면, 브리지는 조용히 무시하는 대신 명확한 오류를 반환합니다. + +ACPX 기반 세션이 OpenClaw plugin 도구를 보도록 하려면, 세션별 `mcpServers`를 전달하려 하지 말고 gateway 측 ACPX plugin 브리지를 활성화하세요. 자세한 내용은 [ACP 에이전트](/tools/acp-agents#plugin-tools-mcp-bridge)를 참조하세요. + +## `acpx`에서 사용하기(Codex, Claude, 기타 ACP 클라이언트) + +Codex 또는 Claude Code 같은 코딩 에이전트가 ACP를 통해 OpenClaw bot과 통신하게 하려면, 내장 `openclaw` 대상을 갖춘 `acpx`를 사용하세요. + +일반적인 흐름: + +1. Gateway를 실행하고 ACP 브리지가 여기에 도달할 수 있는지 확인합니다. +2. `acpx openclaw`가 `openclaw acp`를 가리키도록 설정합니다. +3. 코딩 에이전트가 사용할 OpenClaw 세션 키를 대상으로 지정합니다. + +예시: + +```bash +# 기본 OpenClaw ACP 세션으로 단발성 요청 보내기 +acpx openclaw exec "활성 OpenClaw 세션 상태를 요약해 줘." + +# 후속 턴을 위한 지속적 이름 세션 +acpx openclaw sessions ensure --name codex-bridge +acpx openclaw -s codex-bridge --cwd /path/to/repo \ + "이 리포지토리와 관련된 최근 컨텍스트를 내 OpenClaw 작업 에이전트에 물어봐." +``` + +`acpx openclaw`가 항상 특정 Gateway와 세션 키를 대상으로 하도록 하려면, `~/.acpx/config.json`에서 `openclaw` agent 명령을 재정의하세요. + +```json +{ + "agents": { + "openclaw": { + "command": "env OPENCLAW_HIDE_BANNER=1 OPENCLAW_SUPPRESS_NOTES=1 openclaw acp --url ws://127.0.0.1:18789 --token-file ~/.openclaw/gateway.token --session agent:main:main" + } + } +} +``` + +리포지토리 로컬 OpenClaw 체크아웃의 경우, ACP 스트림을 깨끗하게 유지하기 위해 dev runner 대신 직접 CLI 엔트리포인트를 사용하세요. 예: + +```bash +env OPENCLAW_HIDE_BANNER=1 OPENCLAW_SUPPRESS_NOTES=1 node openclaw.mjs acp ... +``` + +이것이 Codex, Claude Code 또는 다른 ACP 지원 클라이언트가 터미널을 스크래핑하지 않고 OpenClaw 에이전트에서 컨텍스트 정보를 가져오게 하는 가장 쉬운 방법입니다. + +## Zed 편집기 설정 + +`~/.config/zed/settings.json`에 사용자 지정 ACP 에이전트를 추가하세요(또는 Zed의 Settings UI 사용): + +```json +{ + "agent_servers": { + "OpenClaw ACP": { + "type": "custom", + "command": "openclaw", + "args": ["acp"], + "env": {} + } + } +} +``` + +특정 Gateway 또는 에이전트를 대상으로 지정하려면: + +```json +{ + "agent_servers": { + "OpenClaw ACP": { + "type": "custom", + "command": "openclaw", + "args": [ + "acp", + "--url", + "wss://gateway-host:18789", + "--token", + "", + "--session", + "agent:design:main" + ], + "env": {} + } + } +} +``` + +Zed에서 Agent 패널을 열고 “OpenClaw ACP”를 선택해 스레드를 시작하세요. + +## 세션 매핑 + +기본적으로 ACP 세션은 `acp:` 접두사가 있는 격리된 Gateway 세션 키를 받습니다. +알려진 세션을 재사용하려면 세션 키 또는 라벨을 전달하세요. + +- `--session `: 특정 Gateway 세션 키 사용 +- `--session-label