Mattermost (플러그인)

상태: 플러그인을 통해 지원됩니다(봇 토큰 + WebSocket 이벤트). 채널, 그룹, DM이 지원됩니다. Mattermost는 자체 호스팅 가능한 팀 메시징 플랫폼입니다. 제품 세부사항 및 다운로드는 mattermost.com을 참조하세요.

플러그인 필요

Mattermost는 플러그인으로 제공되며 기본 코어에 포함되지 않습니다.

CLI로 설치 (npm 레지스트리):

openclaw plugins install @openclaw/mattermost

로컬 체크아웃 (git 저장소에서 실행 시):

openclaw plugins install ./extensions/mattermost

설정/온보딩 중 Mattermost를 선택하고 git 체크아웃이 감지되면, OpenClaw가 로컬 설치 경로를 자동으로 제안합니다.

자세한 내용: Plugins

빠른 설정

  1. Mattermost 플러그인을 설치합니다.
  2. Mattermost 봇 계정을 생성하고 봇 토큰을 복사합니다.
  3. Mattermost 기본 URL을 복사합니다 (예: https://chat.example.com).
  4. OpenClaw를 설정하고 게이트웨이를 시작합니다.

최소 설정:

{
  channels: {
    mattermost: {
      enabled: true,
      botToken: "mm-token",
      baseUrl: "https://chat.example.com",
      dmPolicy: "pairing",
    },
  },
}

네이티브 슬래시 명령

네이티브 슬래시 명령은 옵트인 방식입니다. 활성화하면 OpenClaw가 Mattermost API를 통해 oc_* 슬래시 명령을 등록하고 게이트웨이 HTTP 서버에서 콜백 POST를 수신합니다.

{
  channels: {
    mattermost: {
      commands: {
        native: true,
        nativeSkills: true,
        callbackPath: "/api/channels/mattermost/command",
        // Mattermost가 게이트웨이에 직접 접근할 수 없을 때 사용 (리버스 프록시/공개 URL).
        callbackUrl: "https://gateway.example.com/api/channels/mattermost/command",
      },
    },
  },
}

참고:

  • native: "auto"는 Mattermost에서 기본적으로 비활성화됩니다. 활성화하려면 native: true를 설정합니다.
  • callbackUrl이 생략되면 OpenClaw가 게이트웨이 host/port + callbackPath에서 자동 도출합니다.
  • 다중 계정 설정에서는 최상위 또는 channels.mattermost.accounts.<id>.commandscommands를 설정할 수 있습니다(계정 값이 최상위 필드를 재정의).
  • 명령 콜백은 명령별 토큰으로 검증되며 토큰 확인 실패 시 차단됩니다.
  • 도달 가능성 요구사항: 콜백 엔드포인트는 Mattermost 서버에서 도달 가능해야 합니다.
    • Mattermost와 OpenClaw가 동일 호스트/네트워크 네임스페이스에서 실행되지 않는 한 callbackUrllocalhost로 설정하지 마세요.
    • Mattermost 기본 URL이 /api/channels/mattermost/command를 OpenClaw로 리버스 프록시하지 않는 한 callbackUrl을 Mattermost 기본 URL로 설정하지 마세요.
    • 빠른 확인: curl https://<gateway-host>/api/channels/mattermost/command; GET은 OpenClaw에서 405 Method Not Allowed를 반환해야 하며, 404가 아닙니다.
  • Mattermost 이그레스 허용 목록 요구사항:
    • 콜백이 프라이빗/tailnet/내부 주소를 대상으로 하는 경우, Mattermost ServiceSettings.AllowedUntrustedInternalConnections에 콜백 호스트/도메인을 포함하세요.
    • 전체 URL이 아닌 호스트/도메인 항목을 사용합니다.
      • 올바른 예: gateway.tailnet-name.ts.net
      • 잘못된 예: https://gateway.tailnet-name.ts.net

환경 변수 (기본 계정)

환경 변수를 선호하는 경우 게이트웨이 호스트에 다음을 설정합니다:

  • MATTERMOST_BOT_TOKEN=...
  • MATTERMOST_URL=https://chat.example.com

환경 변수는 기본 계정(default)에만 적용됩니다. 다른 계정은 설정 값을 사용해야 합니다.

채팅 모드

Mattermost는 DM에 자동으로 응답합니다. 채널 동작은 chatmode로 제어합니다:

  • oncall (기본값): 채널에서 @멘션 시에만 응답합니다.
  • onmessage: 모든 채널 메시지에 응답합니다.
  • onchar: 메시지가 트리거 접두사로 시작할 때 응답합니다.

설정 예시:

{
  channels: {
    mattermost: {
      chatmode: "onchar",
      oncharPrefixes: [">", "!"],
    },
  },
}

참고:

  • onchar도 명시적 @멘션에는 응답합니다.
  • channels.mattermost.requireMention은 레거시 설정에서 존중되지만 chatmode가 선호됩니다.

스레딩과 세션

channels.mattermost.replyToMode를 사용하여 채널 및 그룹 응답이 메인 채널에 유지될지 트리거 게시물 아래 스레드를 시작할지 제어합니다.

  • off (기본값): 인바운드 게시물이 이미 스레드에 있을 때만 스레드에서 응답합니다.
  • first: 최상위 채널/그룹 게시물의 경우 해당 게시물 아래에 스레드를 시작하고 대화를 스레드 범위 세션으로 라우팅합니다.
  • all: 현재 Mattermost에서는 first와 동일한 동작입니다.
  • 다이렉트 메시지는 이 설정을 무시하고 스레드 없이 유지됩니다.

설정 예시:

{
  channels: {
    mattermost: {
      replyToMode: "all",
    },
  },
}

참고:

  • 스레드 범위 세션은 트리거 게시물 ID를 스레드 루트로 사용합니다.
  • firstall은 현재 동등합니다. Mattermost가 스레드 루트를 갖게 되면 후속 청크와 미디어가 동일한 스레드에서 계속됩니다.

접근 제어 (DM)

  • 기본값: channels.mattermost.dmPolicy = "pairing" (알 수 없는 발신자가 페어링 코드를 받음).
  • 승인 방법:
    • openclaw pairing list mattermost
    • openclaw pairing approve mattermost <CODE>
  • 공개 DM: channels.mattermost.dmPolicy="open" + channels.mattermost.allowFrom=["*"].

채널 (그룹)

  • 기본값: channels.mattermost.groupPolicy = "allowlist" (멘션 게이팅).
  • channels.mattermost.groupAllowFrom으로 발신자 허용 목록을 설정합니다 (사용자 ID 권장).
  • @username 매칭은 변경 가능하며 channels.mattermost.dangerouslyAllowNameMatching: true일 때만 활성화됩니다.
  • 오픈 채널: channels.mattermost.groupPolicy="open" (멘션 게이팅).
  • 런타임 참고: channels.mattermost가 완전히 없으면 런타임이 그룹 확인에 groupPolicy="allowlist"로 폴백합니다(channels.defaults.groupPolicy가 설정되어 있더라도).

아웃바운드 전달 대상

openclaw message send 또는 cron/웹훅에서 다음 대상 형식을 사용합니다:

  • channel:<id> - 채널
  • user:<id> - DM
  • @username - DM (Mattermost API를 통해 변환)

순수 불투명 ID(64ifufp... 등)는 Mattermost에서 모호합니다 (사용자 ID vs 채널 ID).

OpenClaw는 사용자 우선으로 변환합니다:

  • ID가 사용자로 존재하면(GET /api/v4/users/<id> 성공), OpenClaw는 /api/v4/channels/direct를 통해 다이렉트 채널을 변환하여 DM을 보냅니다.
  • 그렇지 않으면 ID를 채널 ID로 처리합니다.

결정적 동작이 필요하면 항상 명시적 접두사(user:<id> / channel:<id>)를 사용합니다.

리액션 (메시지 도구)

  • message action=reactchannel=mattermost와 함께 사용합니다.
  • messageId는 Mattermost 게시물 ID입니다.
  • emojithumbsup 또는 :+1: 같은 이름을 받습니다 (콜론은 선택).
  • 리액션을 제거하려면 remove=true (boolean)를 설정합니다.
  • 리액션 추가/제거 이벤트는 라우팅된 에이전트 세션으로 시스템 이벤트로 전달됩니다.

예시:

message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup
message action=react channel=mattermost target=channel:<channelId> messageId=<postId> emoji=thumbsup remove=true

설정:

  • channels.mattermost.actions.reactions: 리액션 액션 활성화/비활성화 (기본값 true).
  • 계정별 재정의: channels.mattermost.accounts.<id>.actions.reactions.

인터랙티브 버튼 (메시지 도구)

클릭 가능한 버튼이 있는 메시지를 전송합니다. 사용자가 버튼을 클릭하면 에이전트가 선택을 수신하고 응답할 수 있습니다.

채널 기능에 inlineButtons를 추가하여 버튼을 활성화합니다:

{
  channels: {
    mattermost: {
      capabilities: ["inlineButtons"],
    },
  },
}

buttons 파라미터와 함께 message action=send를 사용합니다. 버튼은 2D 배열(버튼 행)입니다:

message action=send channel=mattermost target=channel:<channelId> buttons=[[{"text":"Yes","callback_data":"yes"},{"text":"No","callback_data":"no"}]]

버튼 필드:

  • text (필수): 표시 레이블.
  • callback_data (필수): 클릭 시 반환되는 값 (액션 ID로 사용).
  • style (선택): "default", "primary", 또는 "danger".

사용자가 버튼을 클릭하면:

  1. 모든 버튼이 확인 줄로 대체됩니다 (예: ”✓ Yes selected by @user”).
  2. 에이전트가 선택을 인바운드 메시지로 수신하고 응답합니다.

참고:

  • 버튼 콜백은 HMAC-SHA256 검증을 사용합니다 (자동, 설정 불필요).
  • Mattermost는 API 응답에서 콜백 데이터를 제거합니다(보안 기능), 따라서 클릭 시 모든 버튼이 제거됩니다 — 부분 제거는 불가능합니다.
  • 하이픈이나 밑줄이 포함된 액션 ID는 자동으로 정리됩니다 (Mattermost 라우팅 제한).

설정:

  • channels.mattermost.capabilities: 기능 문자열 배열. "inlineButtons"를 추가하면 에이전트 시스템 프롬프트에 버튼 도구 설명이 활성화됩니다.
  • channels.mattermost.interactions.callbackBaseUrl: 버튼 콜백을 위한 선택적 외부 기본 URL (예: https://gateway.example.com). Mattermost가 게이트웨이의 바인드 호스트에 직접 접근할 수 없을 때 사용합니다.
  • 다중 계정 설정에서는 channels.mattermost.accounts.<id>.interactions.callbackBaseUrl에도 동일한 필드를 설정할 수 있습니다.
  • interactions.callbackBaseUrl이 생략되면 OpenClaw가 gateway.customBindHost + gateway.port에서 콜백 URL을 도출하고, http://localhost:<port>로 폴백합니다.
  • 도달 가능성 규칙: 버튼 콜백 URL은 Mattermost 서버에서 도달 가능해야 합니다. localhost는 Mattermost와 OpenClaw가 동일 호스트/네트워크 네임스페이스에서 실행될 때만 작동합니다.
  • 콜백 대상이 프라이빗/tailnet/내부인 경우, Mattermost ServiceSettings.AllowedUntrustedInternalConnections에 호스트/도메인을 추가하세요.

직접 API 연동 (외부 스크립트)

외부 스크립트와 웹훅은 에이전트의 message 도구를 거치지 않고 Mattermost REST API를 통해 직접 버튼을 게시할 수 있습니다. 가능한 경우 확장의 buildButtonAttachments()를 사용하세요. 원시 JSON을 게시하는 경우 다음 규칙을 따릅니다:

페이로드 구조:

{
  channel_id: "<channelId>",
  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>", // 아래 HMAC 섹션 참조
              },
            },
          },
        ],
      },
    ],
  },
}

필수 규칙:

  1. 첨부는 props.attachments에 넣어야 합니다. 최상위 attachments가 아닙니다 (무시됨).
  2. 모든 액션에 type: "button"이 필요합니다 — 없으면 클릭이 조용히 무시됩니다.
  3. 모든 액션에 id 필드가 필요합니다 — Mattermost는 ID 없는 액션을 무시합니다.
  4. 액션 id영숫자만 ([a-zA-Z0-9])이어야 합니다. 하이픈과 밑줄은 Mattermost의 서버 측 액션 라우팅을 중단시킵니다 (404 반환).
  5. context.action_id는 버튼의 id와 일치해야 확인 메시지에 원시 ID 대신 버튼 이름(예: “Approve”)이 표시됩니다.
  6. context.action_id는 필수입니다 — 인터랙션 핸들러가 없으면 400을 반환합니다.

HMAC 토큰 생성:

게이트웨이가 HMAC-SHA256으로 버튼 클릭을 검증합니다. 외부 스크립트는 게이트웨이의 검증 로직과 일치하는 토큰을 생성해야 합니다:

  1. 봇 토큰에서 비밀을 도출합니다: HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)
  2. _token제외한 모든 필드로 컨텍스트 객체를 빌드합니다.
  3. 정렬된 키공백 없이 직렬화합니다 (게이트웨이는 정렬된 키로 JSON.stringify를 사용하여 컴팩트 출력을 생성).
  4. 서명: HMAC-SHA256(key=secret, data=serializedContext)
  5. 결과 hex digest를 컨텍스트의 _token으로 추가합니다.

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=(",", ":")를 사용합니다.
  • 항상 모든 컨텍스트 필드(_token 제외)에 서명합니다. 게이트웨이는 _token을 제거한 후 나머지 전체에 서명합니다. 일부만 서명하면 조용히 검증에 실패합니다.
  • sort_keys=True를 사용합니다 — 게이트웨이는 서명 전에 키를 정렬하며, Mattermost는 페이로드 저장 시 컨텍스트 필드를 재정렬할 수 있습니다.
  • 비밀은 봇 토큰에서 도출합니다(결정적), 랜덤 바이트가 아닙니다. 비밀은 버튼을 생성하는 프로세스와 검증하는 게이트웨이에서 동일해야 합니다.

디렉토리 어댑터

Mattermost 플러그인에는 Mattermost API를 통해 채널 및 사용자 이름을 변환하는 디렉토리 어댑터가 포함되어 있습니다. openclaw message send 및 cron/웹훅 전달에서 #channel-name@username 대상을 사용할 수 있게 합니다.

별도의 설정이 필요하지 않습니다 — 어댑터는 계정 설정의 봇 토큰을 사용합니다.

다중 계정

Mattermost는 channels.mattermost.accounts에서 다중 계정을 지원합니다:

{
  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 계정에만 적용됩니다.
  • 버튼이 흰색 상자로 표시됨: 에이전트가 잘못된 버튼 데이터를 보내고 있을 수 있습니다. 각 버튼에 textcallback_data 필드가 모두 있는지 확인합니다.
  • 버튼이 렌더링되지만 클릭해도 아무 일도 없음: Mattermost 서버 설정에서 AllowedUntrustedInternalConnections127.0.0.1 localhost가 포함되어 있는지, EnablePostActionIntegration이 ServiceSettings에서 true인지 확인합니다.
  • 버튼 클릭 시 404 반환: 버튼 id에 하이픈이나 밑줄이 포함되었을 수 있습니다. Mattermost의 액션 라우터는 영숫자가 아닌 ID에서 중단됩니다. [a-zA-Z0-9]만 사용합니다.
  • 게이트웨이 로그에 invalid _token: HMAC 불일치. 모든 컨텍스트 필드(일부가 아닌)에 서명하고, 정렬된 키를 사용하며, 컴팩트 JSON(공백 없이)을 사용하는지 확인합니다. 위의 HMAC 섹션을 참조하세요.
  • 게이트웨이 로그에 missing _token in context: 버튼의 컨텍스트에 _token 필드가 없습니다. 인터그레이션 페이로드 빌드 시 포함되어 있는지 확인합니다.
  • 확인에 버튼 이름 대신 원시 ID가 표시됨: context.action_id가 버튼의 id와 일치하지 않습니다. 둘 다 동일한 정리된 값으로 설정합니다.
  • 에이전트가 버튼을 모름: Mattermost 채널 설정에 capabilities: ["inlineButtons"]를 추가합니다.