---
read_when:
    - กำลังพัฒนาฟีเจอร์ช่องทาง Discord
summary: การตั้งค่าบอต Discord, คีย์การกำหนดค่า, คอมโพเนนต์, เสียง และการแก้ไขปัญหา
title: Discord
x-i18n:
    generated_at: "2026-07-20T05:52:25Z"
    model: gpt-5.6
    postprocess_version: locale-links-v1
    prompt_version: 32
    provider: openai
    source_hash: 72bebf4de91d8f9a2c462477505ee01dc688d0cfb638fd16bda9853efe3fdc0e
    source_path: channels/discord.md
    workflow: 16
---

OpenClaw เชื่อมต่อกับ Discord ในฐานะบอตผ่าน Discord gateway อย่างเป็นทางการ รองรับ DM และช่องกิลด์

<CardGroup cols={3}>
  <Card title="การจับคู่" icon="link" href="/th/channels/pairing">
    DM ของ Discord ใช้โหมดจับคู่เป็นค่าเริ่มต้น
  </Card>
  <Card title="คำสั่งแบบสแลช" icon="terminal" href="/th/tools/slash-commands">
    ลักษณะการทำงานของคำสั่งแบบเนทีฟและแค็ตตาล็อกคำสั่ง
  </Card>
  <Card title="การแก้ไขปัญหาช่อง" icon="wrench" href="/th/channels/troubleshooting">
    ขั้นตอนการวินิจฉัยและแก้ไขปัญหาข้ามช่อง
  </Card>
</CardGroup>

## การตั้งค่าด่วน

สร้างแอปพลิเคชัน Discord ที่มีบอต เพิ่มบอตลงในเซิร์ฟเวอร์ แล้วจับคู่กับ OpenClaw หากทำได้ ให้ใช้เซิร์ฟเวอร์ส่วนตัว และหากจำเป็น ให้[สร้างเซิร์ฟเวอร์ก่อน](https://support.discord.com/hc/en-us/articles/204849977-How-do-I-create-a-server) (**Create My Own > For me and my friends**)

<Steps>
  <Step title="สร้างแอปพลิเคชัน Discord และบอต">
    ใน [Discord Developer Portal](https://discord.com/developers/applications) ให้คลิก **New Application** แล้วตั้งชื่อ (เช่น "OpenClaw")

    เปิด **Bot** ในแถบด้านข้าง แล้วตั้งค่า **Username** เป็นชื่อเอเจนต์

  </Step>

  <Step title="เปิดใช้งาน Intent ที่มีสิทธิ์พิเศษ">
    ขณะที่ยังอยู่ในหน้า **Bot** ให้เปิดใช้งานรายการต่อไปนี้ภายใต้ **Privileged Gateway Intents**:

    - **Message Content Intent** (จำเป็น)
    - **Server Members Intent** (แนะนำ และจำเป็นสำหรับรายการอนุญาตตามบทบาท การจับคู่ชื่อกับ ID และกลุ่มสิทธิ์เข้าถึงผู้ชมของช่อง)
    - **Presence Intent** (ไม่บังคับ ใช้เฉพาะสำหรับการอัปเดตสถานะการแสดงตน)

  </Step>

  <Step title="คัดลอกโทเค็นบอต">
    ในหน้า **Bot** ให้คลิก **Reset Token** แล้วคัดลอกโทเค็น

    <Note>
    แม้ชื่อจะระบุเช่นนั้น แต่การดำเนินการนี้เป็นการสร้างโทเค็นแรก ไม่ได้มีสิ่งใดถูก "รีเซ็ต"
    </Note>

  </Step>

  <Step title="สร้าง URL คำเชิญและเพิ่มบอตลงในเซิร์ฟเวอร์">
    เปิด **OAuth2** ในแถบด้านข้าง ใน **OAuth2 URL Generator** ให้เปิดใช้งานขอบเขตต่อไปนี้:

    - `bot`
    - `applications.commands`

    ในส่วน **Bot Permissions** ที่ปรากฏขึ้น ให้เปิดใช้งานอย่างน้อย:

    **General Permissions**
      - View Channels

    **Text Permissions**
      - Send Messages
      - Read Message History
      - Embed Links
      - Attach Files
      - Add Reactions (ไม่บังคับ)

    นี่คือค่าพื้นฐานสำหรับช่องข้อความทั่วไป หากบอตจะโพสต์ในเธรด รวมถึงเวิร์กโฟลว์ของช่องฟอรัมหรือสื่อที่สร้างหรือดำเนินเธรดต่อ ให้เปิดใช้งาน **Send Messages in Threads** ด้วย

    คัดลอก URL ที่สร้างขึ้น เปิดในเบราว์เซอร์ เลือกเซิร์ฟเวอร์ แล้วคลิก **Continue** ตอนนี้บอตควรปรากฏในเซิร์ฟเวอร์แล้ว

  </Step>

  <Step title="เปิดใช้งานโหมดนักพัฒนาและรวบรวม ID">
    ในแอป Discord ให้เปิดใช้งาน Developer Mode เพื่อให้คัดลอก ID ได้:

    1. **User Settings** (ไอคอนรูปเฟือง) → **Developer** → เปิด **Developer Mode**
       *(บนอุปกรณ์เคลื่อนที่: **App Settings** → **Advanced**)*
    2. คลิกขวาที่ **ไอคอนเซิร์ฟเวอร์** → **Copy Server ID**
    3. คลิกขวาที่ **อวาตาร์ของตนเอง** → **Copy User ID**

    เก็บ Server ID และ User ID ไว้พร้อมกับโทเค็นบอต เนื่องจากขั้นตอนถัดไปต้องใช้ทั้งสามรายการ

  </Step>

  <Step title="อนุญาต DM จากสมาชิกเซิร์ฟเวอร์">
    เพื่อให้การจับคู่ทำงานได้ Discord ต้องอนุญาตให้บอตส่ง DM ถึงคุณ คลิกขวาที่ **ไอคอนเซิร์ฟเวอร์** → **Privacy Settings** → เปิด **Direct Messages**

    เปิดการตั้งค่านี้ไว้หากใช้ DM ของ Discord กับ OpenClaw หากใช้เฉพาะช่องกิลด์ สามารถปิดได้หลังจากจับคู่แล้ว

  </Step>

  <Step title="ตั้งค่าโทเค็นบอตอย่างปลอดภัย (ห้ามส่งในแชต)">
    โทเค็นบอตเป็นข้อมูลลับ ให้ตั้งค่าบนเครื่องที่เรียกใช้ OpenClaw ก่อนส่งข้อความถึงเอเจนต์:

```bash
export DISCORD_BOT_TOKEN="YOUR_BOT_TOKEN"
cat > discord.patch.json5 <<'JSON5'
{
  channels: {
    discord: {
      enabled: true,
      token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
    },
  },
}
JSON5
openclaw config patch --file ./discord.patch.json5 --dry-run
openclaw config patch --file ./discord.patch.json5
openclaw gateway
```

    หาก OpenClaw ทำงานเป็นบริการเบื้องหลังอยู่แล้ว ให้รีสตาร์ตผ่านแอป OpenClaw บน Mac หรือหยุดแล้วเริ่มกระบวนการ `openclaw gateway run` ใหม่
    สำหรับการติดตั้งเป็นบริการที่มีการจัดการ ให้เรียกใช้ `openclaw gateway install` จากเชลล์ที่ตั้งค่า `DISCORD_BOT_TOKEN` ไว้ หรือจัดเก็บตัวแปรไว้ใน `~/.openclaw/.env` เพื่อให้บริการสามารถแก้ค่า env SecretRef ได้หลังรีสตาร์ต
    หากโฮสต์ถูกบล็อกหรือจำกัดอัตราโดยการค้นหาแอปพลิเคชันของ Discord ขณะเริ่มต้น ให้ตั้งค่า ID แอปพลิเคชัน/ไคลเอนต์จาก Developer Portal เพื่อให้ขั้นตอนเริ่มต้นข้ามการเรียก REST ดังกล่าวได้ โดยใช้ `channels.discord.applicationId` สำหรับบัญชีเริ่มต้น หรือ `channels.discord.accounts.<accountId>.applicationId` สำหรับบอตแต่ละตัว

  </Step>

  <Step title="กำหนดค่า OpenClaw และจับคู่">

    <Tabs>
      <Tab title="ขอให้เอเจนต์ดำเนินการ">
        แชตกับเอเจนต์ OpenClaw ผ่านช่องที่มีอยู่ (เช่น Telegram) แล้วแจ้งให้ดำเนินการ หาก Discord เป็นช่องแรก ให้ใช้แท็บ CLI / การกำหนดค่าแทน

        > "ฉันตั้งค่าโทเค็นบอต Discord ในการกำหนดค่าแล้ว โปรดตั้งค่า Discord ให้เสร็จสมบูรณ์โดยใช้ User ID `<user_id>` และ Server ID `<server_id>`"
      </Tab>
      <Tab title="CLI / การกำหนดค่า">
        การกำหนดค่าแบบใช้ไฟล์:

```json5
{
  channels: {
    discord: {
      enabled: true,
      token: {
        source: "env",
        provider: "default",
        id: "DISCORD_BOT_TOKEN",
      },
    },
  },
}
```

        env สำรองสำหรับบัญชีเริ่มต้น:

```bash
DISCORD_BOT_TOKEN=...
```

        สำหรับการตั้งค่าด้วยสคริปต์หรือจากระยะไกล ให้เขียนบล็อก JSON5 เดียวกันโดยใช้ `openclaw config patch --file ./discord.patch.json5 --dry-run` แล้วเรียกใช้อีกครั้งโดยไม่มี `--dry-run` สตริง `token` แบบข้อความธรรมดาก็ใช้งานได้เช่นกัน และรองรับค่า SecretRef สำหรับ `channels.discord.token` ผ่านผู้ให้บริการ env/file/exec ดู[การจัดการข้อมูลลับ](/th/gateway/secrets)

        สำหรับบอต Discord หลายตัว ให้เก็บโทเค็นบอตและ ID แอปพลิเคชันของแต่ละบอตไว้ภายใต้บัญชีของบอตนั้น บัญชีจะสืบทอด `channels.discord.applicationId` ระดับบนสุด ดังนั้นให้ตั้งค่าที่ระดับดังกล่าวเฉพาะเมื่อทุกบัญชีใช้ ID แอปพลิเคชันเดียวกัน

```json5
{
  channels: {
    discord: {
      enabled: true,
      accounts: {
        personal: {
          token: { source: "env", provider: "default", id: "DISCORD_PERSONAL_TOKEN" },
          applicationId: "111111111111111111",
        },
        work: {
          token: { source: "env", provider: "default", id: "DISCORD_WORK_TOKEN" },
          applicationId: "222222222222222222",
        },
      },
    },
  },
}
```

      </Tab>
    </Tabs>

  </Step>

  <Step title="อนุมัติการจับคู่ผ่าน DM ครั้งแรก">
    เมื่อ gateway ทำงานแล้ว ให้ส่ง DM ถึงบอตใน Discord บอตจะตอบกลับด้วยรหัสจับคู่

    <Tabs>
      <Tab title="ขอให้เอเจนต์ดำเนินการ">
        ส่งรหัสจับคู่ให้เอเจนต์ผ่านช่องที่มีอยู่:

        > "อนุมัติรหัสจับคู่ Discord นี้: `<CODE>`"
      </Tab>
      <Tab title="CLI">

```bash
openclaw pairing list discord
openclaw pairing approve discord <CODE>
```

      </Tab>
    </Tabs>

    รหัสจับคู่จะหมดอายุหลังจาก 1 ชั่วโมง หลังอนุมัติแล้ว ให้แชตกับเอเจนต์ผ่าน DM ของ Discord

  </Step>
</Steps>

<Note>
การแก้ค่าโทเค็นคำนึงถึงบัญชี ค่าโทเค็นในการกำหนดค่ามีลำดับความสำคัญเหนือ env สำรอง และ `DISCORD_BOT_TOKEN` จะใช้สำหรับบัญชีเริ่มต้นเท่านั้น
หากบัญชี Discord ที่เปิดใช้งานสองบัญชีแก้ค่าเป็นโทเค็นบอตเดียวกัน OpenClaw จะเริ่มตัวตรวจสอบ gateway เพียงหนึ่งตัวสำหรับโทเค็นนั้น โดยโทเค็นจากการกำหนดค่ามีลำดับความสำคัญเหนือ env สำรอง มิฉะนั้น บัญชีแรกที่เปิดใช้งานจะมีลำดับความสำคัญ และบัญชีที่ซ้ำจะถูกรายงานว่าปิดใช้งานด้วยเหตุผล `duplicate bot token`
สำหรับการเรียกขาออกขั้นสูง (เครื่องมือส่งข้อความ/การดำเนินการของช่อง) ระบบจะใช้ `token` ที่ระบุอย่างชัดเจนต่อการเรียกสำหรับการเรียกนั้น ข้อนี้ใช้กับทั้งการส่งและการดำเนินการแบบอ่าน/ตรวจสอบ (อ่าน/ค้นหา/ดึงข้อมูล/เธรด/พิน/สิทธิ์) นโยบายบัญชีและการตั้งค่าการลองใหม่ยังคงมาจากบัญชีที่เลือกในสแนปช็อตรันไทม์ที่ใช้งานอยู่
</Note>

## แนะนำ: ตั้งค่าพื้นที่ทำงานของกิลด์

เมื่อ DM ทำงานแล้ว สามารถเปลี่ยนเซิร์ฟเวอร์เป็นพื้นที่ทำงานเต็มรูปแบบ ซึ่งแต่ละช่องจะมีเซสชันเอเจนต์และบริบทของตนเอง แนะนำสำหรับเซิร์ฟเวอร์ส่วนตัวที่มีเพียงคุณและบอต

<Steps>
  <Step title="เพิ่มเซิร์ฟเวอร์ลงในรายการอนุญาตของกิลด์">
    การดำเนินการนี้ช่วยให้เอเจนต์ตอบกลับได้ในทุกช่องบนเซิร์ฟเวอร์ ไม่ใช่เฉพาะ DM

    <Tabs>
      <Tab title="ขอให้เอเจนต์ดำเนินการ">
        > "เพิ่ม Discord Server ID `<server_id>` ของฉันลงในรายการอนุญาตของกิลด์"
      </Tab>
      <Tab title="การกำหนดค่า">

```json5
{
  channels: {
    discord: {
      groupPolicy: "allowlist",
      guilds: {
        YOUR_SERVER_ID: {
          requireMention: true,
          users: ["YOUR_USER_ID"],
        },
      },
    },
  },
}
```

      </Tab>
    </Tabs>

  </Step>

  <Step title="อนุญาตให้ตอบกลับโดยไม่ต้อง @mention">
    ตามค่าเริ่มต้น เอเจนต์จะตอบกลับในช่องกิลด์เฉพาะเมื่อถูก @mention เท่านั้น สำหรับเซิร์ฟเวอร์ส่วนตัว โดยทั่วไปควรตั้งค่าให้ตอบกลับทุกข้อความ

    ในช่องกิลด์ การตอบกลับปกติจะโพสต์โดยอัตโนมัติตามค่าเริ่มต้น สำหรับห้องที่เปิดใช้งานตลอดเวลาและใช้ร่วมกัน ให้เลือกใช้ `messages.groupChat.visibleReplies: "message_tool"` เพื่อให้เอเจนต์เฝ้าดูโดยไม่ส่งข้อความ และโพสต์เฉพาะเมื่อตัดสินว่าการตอบกลับในช่องมีประโยชน์ วิธีนี้ทำงานได้ดีที่สุดกับโมเดลรุ่นล่าสุดที่ใช้เครื่องมือได้อย่างน่าเชื่อถือ เช่น GPT-5.6 Sol เหตุการณ์ห้องแบบแวดล้อมจะไม่มีการส่งข้อความ เว้นแต่เครื่องมือจะส่ง ดูการกำหนดค่าโหมดเฝ้าดูฉบับเต็มที่[เหตุการณ์ห้องแบบแวดล้อม](/th/channels/ambient-room-events)

    หาก Discord แสดงสถานะกำลังพิมพ์และบันทึกแสดงการใช้โทเค็น แต่ไม่มีข้อความถูกโพสต์ ให้ตรวจสอบว่าเทิร์นนั้นได้รับการกำหนดค่าเป็นเหตุการณ์ห้องแบบแวดล้อม หรือเลือกให้การตอบกลับที่มองเห็นได้ส่งผ่านเครื่องมือส่งข้อความหรือไม่

    <Tabs>
      <Tab title="ขอให้เอเจนต์ดำเนินการ">
        > "อนุญาตให้เอเจนต์ของฉันตอบกลับบนเซิร์ฟเวอร์นี้ได้โดยไม่ต้องถูก @mention"
      </Tab>
      <Tab title="การกำหนดค่า">
        ตั้งค่า `requireMention: false` ในการกำหนดค่ากิลด์:

```json5
{
  channels: {
    discord: {
      guilds: {
        YOUR_SERVER_ID: {
          requireMention: false,
        },
      },
    },
  },
}
```

        หากต้องการบังคับให้การตอบกลับในกลุ่ม/ช่องที่มองเห็นได้ส่งผ่านเครื่องมือส่งข้อความ ให้ตั้งค่า `messages.groupChat.visibleReplies: "message_tool"`

      </Tab>
    </Tabs>

  </Step>

  <Step title="วางแผนการใช้หน่วยความจำในช่องกิลด์">
    หน่วยความจำระยะยาว (MEMORY.md) จะโหลดโดยอัตโนมัติเฉพาะในเซสชัน DM เท่านั้น ช่องกิลด์จะไม่โหลดไฟล์นี้

    <Tabs>
      <Tab title="ขอให้เอเจนต์ดำเนินการ">
        > "เมื่อฉันถามคำถามในช่อง Discord ให้ใช้ memory_search หรือ memory_get หากต้องใช้บริบทระยะยาวจาก MEMORY.md"
      </Tab>
      <Tab title="ด้วยตนเอง">
        สำหรับบริบทที่ใช้ร่วมกันในทุกช่อง ให้วางคำสั่งที่คงที่ไว้ใน `AGENTS.md` หรือ `USER.md` (แทรกลงในทุกเซสชัน) เก็บบันทึกระยะยาวไว้ใน `MEMORY.md` และเข้าถึงเมื่อต้องการด้วยเครื่องมือหน่วยความจำ
      </Tab>
    </Tabs>

  </Step>
</Steps>

ตอนนี้ให้สร้างช่องและเริ่มแชต เอเจนต์จะเห็นชื่อช่อง และแต่ละช่องเป็นเซสชันที่แยกจากกัน โดยตั้งค่า `#coding`, `#home`, `#research` หรือรายการอื่นที่เหมาะกับเวิร์กโฟลว์

## โมเดลรันไทม์

- Gateway เป็นผู้ควบคุมการเชื่อมต่อ Discord
- การกำหนดเส้นทางการตอบกลับเป็นแบบกำหนดแน่นอน: ข้อความขาเข้าจาก Discord จะตอบกลับไปยัง Discord
- เมทาดาทาของกิลด์/ช่อง Discord จะถูกเพิ่มลงในพรอมต์ของโมเดลในฐานะบริบทที่ไม่น่าเชื่อถือ ไม่ใช่คำนำหน้าการตอบกลับที่ผู้ใช้มองเห็น หากโมเดลคัดลอกกรอบข้อมูลดังกล่าวกลับมา OpenClaw จะลบเมทาดาทาที่คัดลอกออกจากการตอบกลับขาออกและจากบริบทการเล่นซ้ำในอนาคต
- ตามค่าเริ่มต้น (`session.dmScope=main`) แชตโดยตรงจะใช้เซสชันหลักของเอเจนต์ร่วมกัน (`agent:main:main`)
- ช่องกิลด์ใช้คีย์เซสชันที่แยกจากกัน (`agent:<agentId>:discord:channel:<channelId>`)
- DM แบบกลุ่มจะถูกละเว้นตามค่าเริ่มต้น (`channels.discord.dm.groupEnabled=false`)
- คำสั่งแบบสแลชเนทีฟทำงานในเซสชันคำสั่งที่แยกจากกัน (`agent:<agentId>:discord:slash:<userId>`) ขณะเดียวกันยังส่งต่อ `CommandTargetSessionKey` ไปยังเซสชันการสนทนาที่กำหนดเส้นทางไว้
- การส่งประกาศ Cron/Heartbeat แบบข้อความล้วนไปยัง Discord จะยุบเหลือเฉพาะคำตอบสุดท้ายที่ผู้ช่วยมองเห็นและส่งเพียงครั้งเดียว เพย์โหลดสื่อและคอมโพเนนต์แบบมีโครงสร้างจะยังคงเป็นหลายข้อความเมื่อเอเจนต์สร้างเพย์โหลดที่ส่งได้หลายรายการ

## ช่องฟอรัม

ช่องฟอรัมและช่องสื่อของ Discord ยอมรับเฉพาะโพสต์ในเธรดเท่านั้น OpenClaw รองรับการสร้างโพสต์ดังกล่าว 2 วิธี:

- ส่งข้อความไปยังช่องหลักของฟอรัม (`channel:<forumId>`) เพื่อสร้างเธรดโดยอัตโนมัติ ชื่อเธรดจะเป็นบรรทัดแรกที่ไม่ว่างของข้อความ (ตัดให้ไม่เกินขีดจำกัดชื่อเธรด 100 อักขระของ Discord)
- ใช้ `openclaw message thread create` เพื่อสร้างเธรดโดยตรง อย่าส่ง `--message-id` สำหรับช่องฟอรัม

ส่งไปยังช่องหลักของฟอรัมเพื่อสร้างเธรด:

```bash
openclaw message send --channel discord --target channel:<forumId> \
  --message "ชื่อหัวข้อ\nเนื้อหาของโพสต์"
```

สร้างเธดฟอรัมอย่างชัดเจน:

```bash
openclaw message thread create --channel discord --target channel:<forumId> \
  --thread-name "ชื่อหัวข้อ" --message "เนื้อหาของโพสต์"
```

ช่องหลักของฟอรัมไม่ยอมรับคอมโพเนนต์ของ Discord หากต้องการใช้คอมโพเนนต์ ให้ส่งไปยังเธรดโดยตรง (`channel:<threadId>`)

## คอมโพเนนต์แบบโต้ตอบ

OpenClaw รองรับคอนเทนเนอร์คอมโพเนนต์ v2 ของ Discord สำหรับข้อความของเอเจนต์ ใช้เครื่องมือข้อความพร้อมเพย์โหลด `components` ผลลัพธ์จากการโต้ตอบจะถูกส่งกลับไปยังเอเจนต์ในรูปแบบข้อความขาเข้าปกติ และเป็นไปตามการตั้งค่า `replyToMode` ของ Discord ที่มีอยู่

บล็อกที่รองรับ:

- `text`, `section`, `separator`, `actions`, `media-gallery`, `file`
- แถวการดำเนินการรองรับปุ่มได้สูงสุด 5 ปุ่ม หรือเมนูเลือก 1 เมนู
- ประเภทการเลือก: `string`, `user`, `role`, `mentionable`, `channel`

โดยค่าเริ่มต้น คอมโพเนนต์ใช้ได้เพียงครั้งเดียว ตั้งค่า `components.reusable=true` เพื่ออนุญาตให้ใช้ปุ่ม รายการเลือก และฟอร์มได้หลายครั้งจนกว่าจะหมดอายุ

หากต้องการจำกัดผู้ที่คลิกปุ่มได้ ให้ตั้งค่า `allowedUsers` บนปุ่มนั้น (ID ผู้ใช้ Discord, แท็ก หรือ `*`) ผู้ใช้ที่ไม่ตรงเงื่อนไขจะได้รับข้อความปฏิเสธที่มองเห็นได้เฉพาะตน

โดยค่าเริ่มต้น คอลแบ็กของคอมโพเนนต์จะหมดอายุหลังจาก 30 นาที ตั้งค่า `channels.discord.agentComponents.ttlMs` เพื่อเปลี่ยนอายุของรีจิสทรีคอลแบ็กสำหรับบัญชีเริ่มต้น หรือ `channels.discord.accounts.<accountId>.agentComponents.ttlMs` สำหรับแต่ละบัญชี ค่านี้มีหน่วยเป็นมิลลิวินาที ต้องเป็นจำนวนเต็มบวก และจำกัดสูงสุดที่ `86400000` (24 ชั่วโมง) TTL ที่ยาวขึ้นเหมาะกับขั้นตอนการรีวิว/อนุมัติที่ต้องให้ปุ่มยังคงใช้งานได้ แต่จะขยายช่วงเวลาที่ข้อความ Discord เก่ายังสามารถทริกเกอร์การดำเนินการได้ ควรใช้ TTL ที่สั้นที่สุดซึ่งเพียงพอต่อการใช้งาน และคงค่าเริ่มต้นไว้หากคอลแบ็กที่ค้างเก่าอาจสร้างความสับสน

คำสั่งสแลช `/model` และ `/models` จะเปิดตัวเลือกโมเดลแบบโต้ตอบ ซึ่งมีรายการแบบเลื่อนลงสำหรับผู้ให้บริการ โมเดล และรันไทม์ที่เข้ากันได้ พร้อมขั้นตอน Submit ส่วน `/models add` เลิกใช้แล้ว และจะส่งข้อความแจ้งการเลิกใช้แทนการลงทะเบียนโมเดลจากแชต การตอบกลับของตัวเลือกจะมองเห็นได้เฉพาะตนและใช้งานได้เฉพาะผู้ใช้ที่เรียกใช้เท่านั้น เมนูเลือกของ Discord จำกัดไว้ที่ 25 ตัวเลือก ดังนั้นให้เพิ่มรายการ `provider/*` ใน `agents.defaults.modelPolicy.allow` เมื่อต้องการให้ตัวเลือกแสดงเฉพาะโมเดลที่ค้นพบแบบไดนามิกสำหรับผู้ให้บริการที่เลือก เช่น `openai` หรือ `vllm`

ไฟล์แนบ:

- บล็อก `file` ต้องชี้ไปยังการอ้างอิงไฟล์แนบ (`attachment://<filename>`)
- ระบุไฟล์แนบผ่าน `media`/`path`/`filePath` (ไฟล์เดียว) และใช้ `media-gallery` สำหรับหลายไฟล์
- ใช้ `filename` เพื่อเขียนทับชื่อที่อัปโหลดเมื่อชื่อนั้นควรตรงกับการอ้างอิงไฟล์แนบ

ฟอร์มโมดัล:

- เพิ่ม `components.modal` พร้อมฟิลด์ได้สูงสุด 5 ฟิลด์
- ประเภทฟิลด์: `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" },
          ],
        },
      ],
    },
  },
}
```

## การควบคุมการเข้าถึงและการกำหนดเส้นทาง

<Tabs>
  <Tab title="นโยบาย DM">
    `channels.discord.dmPolicy` ควบคุมการเข้าถึง DM ส่วน `channels.discord.allowFrom` คือรายการอนุญาต DM มาตรฐาน

    - `pairing` (ค่าเริ่มต้น)
    - `allowlist` (ต้องมีผู้ส่ง `allowFrom` อย่างน้อยหนึ่งราย)
    - `open` (กำหนดให้ `channels.discord.allowFrom` ต้องมี `"*"`)
    - `disabled`

    หากนโยบาย DM ไม่ได้เปิดไว้ ผู้ใช้ที่ไม่รู้จักจะถูกบล็อก (หรือได้รับข้อความให้จับคู่ในโหมด `pairing`)

    ลำดับความสำคัญสำหรับหลายบัญชี:

    - `channels.discord.accounts.default.allowFrom` ใช้กับบัญชี `default` เท่านั้น
    - สำหรับบัญชีหนึ่งบัญชี `allowFrom` มีลำดับความสำคัญเหนือ `dm.allowFrom` แบบเดิม
    - บัญชีที่มีชื่อจะรับช่วง `channels.discord.allowFrom` เมื่อไม่ได้ตั้งค่าทั้ง `allowFrom` ของบัญชีนั้นเองและ `dm.allowFrom` แบบเดิม
    - บัญชีที่มีชื่อจะไม่รับช่วง `channels.discord.accounts.default.allowFrom`

    ระบบยังคงอ่าน `channels.discord.dm.policy` และ `channels.discord.dm.allowFrom` แบบเดิมเพื่อความเข้ากันได้ `openclaw doctor --fix` จะย้ายค่าเหล่านี้ไปยัง `dmPolicy` และ `allowFrom` เมื่อสามารถทำได้โดยไม่เปลี่ยนแปลงการเข้าถึง

    รูปแบบเป้าหมาย DM สำหรับการส่ง:

    - `user:<id>`
    - การกล่าวถึง `<@id>`

    โดยปกติ ID ตัวเลขล้วนจะถูกตีความเป็น ID ช่องเมื่อมีค่าเริ่มต้นของช่องทำงานอยู่ แต่ ID ที่ระบุอยู่ใน `allowFrom` ของ DM ที่มีผลสำหรับบัญชีจะถูกตีความเป็นเป้าหมาย DM ของผู้ใช้เพื่อความเข้ากันได้

  </Tab>

  <Tab title="กลุ่มการเข้าถึง">
    DM ของ Discord และการให้สิทธิ์คำสั่งข้อความสามารถใช้รายการ `accessGroup:<name>` แบบไดนามิกใน `channels.discord.allowFrom`

    ชื่อกลุ่มการเข้าถึงใช้ร่วมกันในทุกช่องข้อความ ใช้ `type: "message.senders"` สำหรับกลุ่มแบบคงที่ซึ่งระบุสมาชิกด้วยไวยากรณ์ `allowFrom` ปกติของแต่ละช่อง หรือใช้ `type: "discord.channelAudience"` เมื่อต้องการให้กลุ่มผู้ชม `ViewChannel` ปัจจุบันของช่อง Discord กำหนดสมาชิกแบบไดนามิก พฤติกรรมกลุ่มการเข้าถึงที่ใช้ร่วมกัน: [กลุ่มการเข้าถึง](/th/channels/access-groups)

```json5
{
  accessGroups: {
    operators: {
      type: "message.senders",
      members: {
        "*": ["global-owner-id"],
        discord: ["discord:123456789012345678"],
        telegram: ["987654321"],
      },
    },
  },
  channels: {
    discord: {
      dmPolicy: "allowlist",
      allowFrom: ["accessGroup:operators"],
    },
  },
}
```

    ช่องข้อความ Discord ไม่มีรายชื่อสมาชิกแยกต่างหาก `type: "discord.channelAudience"` จำลองการเป็นสมาชิกดังนี้: ผู้ส่ง DM เป็นสมาชิกของกิลด์ที่กำหนดค่าไว้ และในขณะนั้นมีสิทธิ์ `ViewChannel` ที่มีผลในช่องที่กำหนดค่าไว้ หลังจากใช้การเขียนทับจากบทบาทและช่องแล้ว

    ตัวอย่าง: อนุญาตให้ทุกคนที่มองเห็น `#maintainers` ส่ง DM ถึงบอตได้ ขณะที่ยังปิด DM สำหรับบุคคลอื่นทั้งหมด

```json5
{
  accessGroups: {
    maintainers: {
      type: "discord.channelAudience",
      guildId: "1456350064065904867",
      channelId: "1456744319972282449",
      membership: "canViewChannel",
    },
  },
  channels: {
    discord: {
      dmPolicy: "allowlist",
      allowFrom: ["accessGroup:maintainers"],
    },
  },
}
```

    สามารถผสมรายการแบบไดนามิกและแบบคงที่ได้:

```json5
{
  accessGroups: {
    maintainers: {
      type: "discord.channelAudience",
      guildId: "1456350064065904867",
      channelId: "1456744319972282449",
    },
  },
  channels: {
    discord: {
      dmPolicy: "allowlist",
      allowFrom: ["accessGroup:maintainers", "discord:123456789012345678"],
    },
  },
}
```

    การค้นหาจะปฏิเสธการเข้าถึงเมื่อเกิดข้อผิดพลาด หาก Discord ส่งคืน `Missing Access` การค้นหาสมาชิกล้มเหลว หรือช่องเป็นของกิลด์อื่น ผู้ส่ง DM จะถือว่าไม่ได้รับอนุญาต

    เปิดใช้ **Server Members Intent** ใน Discord Developer Portal เมื่อใช้กลุ่มการเข้าถึงตามผู้ชมของช่อง DM ไม่มีสถานะสมาชิกกิลด์รวมอยู่ด้วย ดังนั้น OpenClaw จะค้นหาสมาชิกผ่าน Discord REST ขณะตรวจสอบสิทธิ์

  </Tab>

  <Tab title="นโยบายกิลด์">
    การจัดการกิลด์ควบคุมโดย `channels.discord.groupPolicy`:

    - `open`
    - `allowlist`
    - `disabled`

    ค่าพื้นฐานที่ปลอดภัยเมื่อมี `channels.discord` คือ `allowlist`

    พฤติกรรมของ `allowlist`:

    - กิลด์ต้องตรงกับ `channels.discord.guilds` (แนะนำให้ใช้ `id` และยอมรับ slug)
    - รายการอนุญาตผู้ส่งที่ไม่บังคับ: `users` (แนะนำ ID ที่คงที่) และ `roles` (เฉพาะ ID บทบาท) หากกำหนดค่าอย่างใดอย่างหนึ่ง ผู้ส่งจะได้รับอนุญาตเมื่อตรงกับ `users` หรือ `roles`
    - การจับคู่ชื่อ/แท็กโดยตรงถูกปิดไว้โดยค่าเริ่มต้น เปิดใช้ `channels.discord.dangerouslyAllowNameMatching: true` เฉพาะเป็นโหมดความเข้ากันได้ในกรณีฉุกเฉินเท่านั้น
    - รองรับชื่อ/แท็กสำหรับ `users` แต่ ID ปลอดภัยกว่า `openclaw security audit` จะแจ้งเตือนเมื่อมีการใช้รายการชื่อ/แท็ก
    - หากกิลด์กำหนดค่า `channels` ไว้ ช่องที่ไม่อยู่ในรายการจะถูกปฏิเสธ
    - หากกิลด์ไม่มีบล็อก `channels` ช่องทั้งหมดในกิลด์ที่อยู่ในรายการอนุญาตนั้นจะได้รับอนุญาต

    ตัวอย่าง:

```json5
{
  channels: {
    discord: {
      groupPolicy: "allowlist",
      guilds: {
        "123456789012345678": {
          requireMention: true,
          ignoreOtherMentions: true,
          users: ["987654321098765432"],
          roles: ["123456789012345678"],
          channels: {
            general: { enabled: true },
            help: { enabled: true, requireMention: true },
          },
        },
      },
    },
  },
}
```

    คีย์ `allow` ต่อช่องแบบเดิมจะถูกย้ายไปยัง `enabled` โดย `openclaw doctor --fix`

    หากตั้งค่าเฉพาะ `DISCORD_BOT_TOKEN` และไม่ได้สร้างบล็อก `channels.discord` ค่าสำรองขณะรันไทม์คือ `groupPolicy="allowlist"` (พร้อมคำเตือนในบันทึก) แม้ว่า `channels.defaults.groupPolicy` จะเป็น `open`

  </Tab>

  <Tab title="การกล่าวถึงและ DM แบบกลุ่ม">
    โดยค่าเริ่มต้น ข้อความในกิลด์ต้องมีการกล่าวถึงจึงจะผ่าน

    การตรวจจับการกล่าวถึงประกอบด้วย:

    - การกล่าวถึงบอตอย่างชัดเจน
    - รูปแบบการกล่าวถึงที่กำหนดค่าไว้ (`agents.list[].groupChat.mentionPatterns` และใช้ `messages.groupChat.mentionPatterns` เป็นค่าสำรอง)
    - พฤติกรรมตอบกลับบอตโดยนัยในกรณีที่รองรับ

    เมื่อเขียนข้อความขาออกของ Discord ให้ใช้ไวยากรณ์การกล่าวถึงมาตรฐาน: `<@USER_ID>` สำหรับผู้ใช้, `<#CHANNEL_ID>` สำหรับช่อง และ `<@&ROLE_ID>` สำหรับบทบาท อย่าใช้รูปแบบการกล่าวถึงชื่อเล่น `<@!USER_ID>` แบบเดิม

    `requireMention` กำหนดค่าต่อกิลด์/ช่อง (`channels.discord.guilds...`)
    `ignoreOtherMentions` สามารถเลือกทิ้งข้อความที่กล่าวถึงผู้ใช้/บทบาทอื่นแต่ไม่ได้กล่าวถึงบอต (ไม่รวม @everyone/@here)

    DM แบบกลุ่ม:

    - ค่าเริ่มต้น: ละเว้น (`dm.groupEnabled=false`)
    - รายการอนุญาตที่ไม่บังคับผ่าน `dm.groupChannels` (ID ช่องหรือ slug)

  </Tab>
</Tabs>

### การกำหนดเส้นทางเอเจนต์ตามบทบาท

ใช้ `bindings[].match.roles` เพื่อกำหนดเส้นทางสมาชิกกิลด์ Discord ไปยังเอเจนต์ต่างกันตาม ID บทบาท การผูกตามบทบาทยอมรับเฉพาะ ID บทบาท และจะประเมินหลังการผูกกับเพียร์หรือเพียร์หลัก แต่ก่อนการผูกเฉพาะกิลด์ หากการผูกกำหนดฟิลด์จับคู่อื่นด้วย (เช่น `peer` + `guildId` + `roles`) ฟิลด์ที่กำหนดค่าไว้ทั้งหมดต้องตรงกัน

```json5
{
  bindings: [
    {
      agentId: "opus",
      match: {
        channel: "discord",
        guildId: "123456789012345678",
        roles: ["111111111111111111"],
      },
    },
    {
      agentId: "sonnet",
      match: {
        channel: "discord",
        guildId: "123456789012345678",
      },
    },
  ],
}
```

## คำสั่งแบบเนทีฟและการตรวจสอบสิทธิ์คำสั่ง

- `commands.native` มีค่าเริ่มต้นเป็น `"auto"` และเปิดใช้งานสำหรับ Discord
- การกำหนดค่าเฉพาะช่อง: `channels.discord.commands.native`
- `commands.native=false` จะข้ามการลงทะเบียนและล้างคำสั่งแบบ slash ของ Discord ระหว่างการเริ่มต้น คำสั่งที่ลงทะเบียนไว้ก่อนหน้านี้อาจยังคงปรากฏใน Discord จนกว่าจะนำคำสั่งเหล่านั้นออกจากแอป Discord
- การตรวจสอบสิทธิ์คำสั่งแบบเนทีฟใช้รายการอนุญาต/นโยบายของ Discord ชุดเดียวกับการจัดการข้อความปกติ
- คำสั่งอาจยังคงปรากฏใน UI ของ Discord สำหรับผู้ใช้ที่ไม่ได้รับอนุญาต แต่เมื่อเรียกใช้ OpenClaw จะบังคับใช้การตรวจสอบสิทธิ์และตอบกลับว่า "ไม่ได้รับอนุญาต"
- การตั้งค่าเริ่มต้นของคำสั่งแบบ slash: `ephemeral: true` (`channels.discord.slashCommand.ephemeral`)

ดูรายการคำสั่งและลักษณะการทำงานได้ที่ [คำสั่งแบบ slash](/th/tools/slash-commands)

## รายละเอียดฟีเจอร์

<AccordionGroup>
  <Accordion title="แท็กตอบกลับและการตอบกลับแบบเนทีฟ">
    Discord รองรับแท็กตอบกลับในเอาต์พุตของเอเจนต์:

    - `[[reply_to_current]]`
    - `[[reply_to:<id>]]`

    ควบคุมโดย `channels.discord.replyToMode`:

    - `off` (ค่าเริ่มต้น): ไม่มีการจัดเธรดการตอบกลับโดยนัย แต่ยังคงใช้แท็ก `[[reply_to_*]]` ที่ระบุไว้อย่างชัดเจน
    - `first`: แนบการอ้างอิงการตอบกลับแบบเนทีฟโดยนัยกับข้อความ Discord ขาออกข้อความแรกของรอบ
    - `all`: แนบการอ้างอิงดังกล่าวกับทุกข้อความขาออก
    - `batched`: แนบเฉพาะเมื่อเหตุการณ์ขาเข้าเป็นชุดข้อความหลายข้อความที่ผ่านการหน่วงรวม เหมาะสำหรับกรณีที่ต้องการใช้การตอบกลับแบบเนทีฟเป็นหลักกับแชตที่มีข้อความจำนวนมากต่อเนื่องและตีความได้ไม่ชัดเจน ไม่ใช่ทุกๆ รอบที่มีเพียงข้อความเดียว

    รหัสข้อความจะแสดงในบริบท/ประวัติเพื่อให้เอเจนต์สามารถระบุข้อความเป้าหมายได้อย่างเจาะจง

  </Accordion>

  <Accordion title="ตัวอย่างลิงก์">
    โดยค่าเริ่มต้น Discord จะสร้างการฝังลิงก์แบบสมบูรณ์สำหรับ URL ส่วน OpenClaw จะระงับการฝังที่สร้างขึ้นดังกล่าวในข้อความ Discord ขาออกตามค่าเริ่มต้น ดังนั้น URL ที่เอเจนต์ส่งจะยังคงเป็นลิงก์ธรรมดา เว้นแต่จะเลือกเปิดใช้:

```json5
{
  channels: {
    discord: {
      suppressEmbeds: false,
    },
  },
}
```

    ตั้งค่า `channels.discord.accounts.<id>.suppressEmbeds` เพื่อกำหนดค่าทับสำหรับหนึ่งบัญชี การส่งผ่านเครื่องมือข้อความของเอเจนต์ยังสามารถส่ง `suppressEmbeds: false` สำหรับข้อความเดียวได้อีกด้วย เพย์โหลด Discord `embeds` ที่ระบุอย่างชัดเจนจะไม่ถูกระงับโดยการตั้งค่าตัวอย่างลิงก์เริ่มต้น

  </Accordion>

  <Accordion title="ตัวอย่างสตรีมสด">
    OpenClaw สามารถสตรีมฉบับร่างการตอบกลับได้โดยส่งข้อความชั่วคราวและแก้ไขข้อความเมื่อข้อความใหม่เข้ามา `channels.discord.streaming.mode` รับค่า `off` | `partial` | `block` | `progress` (ค่าเริ่มต้นเมื่อไม่ได้ตั้งค่าคีย์ `streaming`/คีย์เดิม `streamMode`) `streamMode` เป็นนามแฝงเดิม ให้เรียกใช้ `openclaw doctor --fix` เพื่อเขียนการกำหนดค่าที่บันทึกไว้ใหม่ให้อยู่ในรูปแบบซ้อนมาตรฐาน `streaming`

```json5
{
  channels: {
    discord: {
      streaming: {
        mode: "progress",
        progress: {
          maxLines: 8,
          maxLineChars: 120,
          toolProgress: false,
          commentary: false,
        },
      },
    },
  },
}
```

    - `off` ปิดใช้งานการแก้ไขตัวอย่างใน Discord
    - `partial` แก้ไขข้อความตัวอย่างเดียวเมื่อโทเค็นเข้ามา
    - `block` ส่งส่วนข้อความขนาดฉบับร่าง โดยปรับขนาดและจุดแบ่งได้ด้วย `streaming.preview.chunk` (`minChars`, `maxChars`, `breakPreference`) ซึ่งถูกจำกัดไว้ที่ `textChunkLimit` เมื่อเปิดใช้งานการสตรีมแบบบล็อกอย่างชัดเจน OpenClaw จะข้ามสตรีมตัวอย่างเพื่อหลีกเลี่ยงการสตรีมซ้ำ
    - `progress` เก็บฉบับร่างสถานะที่แก้ไขได้หนึ่งรายการไว้จนกว่าจะส่งผลลัพธ์สุดท้าย โดยค่าเริ่มต้นจะแสดงหนึ่งบรรทัดจากคำนำหรือคำบรรยายล่าสุดของเอเจนต์ โดยไม่มีป้ายกำกับที่สร้างขึ้น ช่องว่าง หรือแถวเครื่องมือ
    - ผลลัพธ์สุดท้ายที่เป็นสื่อ ข้อผิดพลาด และการตอบกลับที่ระบุอย่างชัดเจนจะยกเลิกการแก้ไขตัวอย่างที่รอดำเนินการ
    - `streaming.preview.toolProgress` มีค่าเริ่มต้นเป็น `true` ในโหมด `partial`/`block` โหมดความคืบหน้าของ Discord มีค่าเริ่มต้นไม่แสดงแถวเครื่องมือ ให้ตั้งค่า `streaming.progress.toolProgress: true` เพื่อเลือกเปิดใช้
    - ตั้งค่า `streaming.progress.toolProgress: true` เพื่อเพิ่มแถวเครื่องมือ/ความคืบหน้าแบบกระชับ เช่น `🛠️ Bash: run tests` หรือ `🔎 Web Search: for "query"` เพื่อความเข้ากันได้ การกำหนดค่า `progress.label` หรือ `progress.labels` ที่มีอยู่จะยังคงใช้ค่าเริ่มต้นของแถวเครื่องมือก่อนหน้า ตั้งค่า `toolProgress: false` เพื่อใช้ป้ายกำกับแบบกำหนดเองโดยไม่มีแถว
    - `streaming.progress.commentary` (ค่าเริ่มต้น `false`) เลือกเปิดใช้คำอธิบายดิบของผู้ช่วยในฉบับร่างความคืบหน้าชั่วคราว บรรทัดสถานะคำนำ/คำบรรยายเริ่มต้นไม่ขึ้นกับตัวเลือกนี้ คำอธิบายจะถูกล้างก่อนแสดงผล ยังคงเป็นข้อมูลชั่วคราว และไม่เปลี่ยนแปลงการส่งคำตอบสุดท้าย
    - `streaming.progress.maxLineChars` ควบคุมงบประมาณตัวอย่างความคืบหน้าต่อบรรทัด ร้อยแก้วจะถูกย่อที่ขอบเขตคำ ส่วนรายละเอียดคำสั่งและพาธจะเก็บส่วนท้ายที่เป็นประโยชน์ไว้
    - `streaming.preview.commandText` / `streaming.progress.commandText` ควบคุมรายละเอียดคำสั่ง/การดำเนินการในบรรทัดความคืบหน้าแบบกระชับ: `raw` (ค่าเริ่มต้น) หรือ `status` (เฉพาะป้ายกำกับเครื่องมือ)

    ซ่อนข้อความคำสั่ง/การดำเนินการดิบโดยยังคงแสดงบรรทัดความคืบหน้าแบบกระชับ:

    ```json
    {
      "channels": {
        "discord": {
          "streaming": {
            "mode": "progress",
            "progress": {
              "toolProgress": true,
              "commandText": "status"
            }
          }
        }
      }
    }
    ```

    การสตรีมตัวอย่างรองรับเฉพาะข้อความ การตอบกลับที่มีสื่อจะกลับไปใช้การส่งตามปกติ

  </Accordion>

  <Accordion title="ประวัติ บริบท และลักษณะการทำงานของเธรด">
    บริบทประวัติของกิลด์:

    - `channels.discord.historyLimit` ค่าเริ่มต้น `20`
    - ค่าทดแทน: `messages.groupChat.historyLimit`
    - `0` ปิดใช้งาน

    การควบคุมประวัติ DM:

    - `channels.discord.dmHistoryLimit`
    - `channels.discord.dms["<user_id>"].historyLimit`

    ลักษณะการทำงานของเธรด:

    - เธรด Discord จะกำหนดเส้นทางเป็นเซสชันช่องและรับช่วงการกำหนดค่าจากช่องแม่ เว้นแต่จะมีการกำหนดค่าทับ
    - เซสชันเธรดจะรับช่วงการเลือก `/model` ระดับเซสชันของช่องแม่เป็นค่าทดแทนเฉพาะโมเดล การเลือก `/model` ภายในเธรดมีลำดับความสำคัญสูงกว่า และจะไม่คัดลอกประวัติบันทึกการสนทนาของช่องแม่ เว้นแต่จะเปิดใช้งานการรับช่วงบันทึกการสนทนา
    - `channels.discord.thread.inheritParent` (ค่าเริ่มต้น `false`) เลือกให้เธรดใหม่ที่สร้างอัตโนมัติเริ่มต้นจากบันทึกการสนทนาของช่องแม่ การกำหนดค่าทับต่อบัญชี: `channels.discord.accounts.<id>.thread.inheritParent`
    - รีแอ็กชันจากเครื่องมือข้อความสามารถแก้ไขเป้าหมาย DM `user:<id>` ได้
    - `guilds.<guild>.channels.<channel>.requireMention: false` จะถูกเก็บรักษาไว้ระหว่างการใช้ค่าทดแทนเพื่อเปิดใช้งานขั้นตอนการตอบกลับ

    หัวข้อช่องจะถูกแทรกเป็นบริบทที่ **ไม่น่าเชื่อถือ** รายการอนุญาตจะควบคุมว่าใครสามารถทริกเกอร์เอเจนต์ได้ แต่ไม่ใช่ขอบเขตการปกปิดบริบทเสริมทั้งหมด

  </Accordion>

  <Accordion title="เซสชันที่ผูกกับเธรดสำหรับเอเจนต์ย่อย">
    Discord สามารถผูกเธรดเข้ากับเป้าหมายเซสชัน เพื่อให้ข้อความติดตามผลในเธรดนั้นยังคงกำหนดเส้นทางไปยังเซสชันเดิม (รวมถึงเซสชันของเอเจนต์ย่อย)

    คำสั่ง:

    - `/focus <target>` ผูกเธรดปัจจุบัน/เธรดใหม่เข้ากับเป้าหมายเอเจนต์ย่อย/เซสชัน
    - `/unfocus` นำการผูกเธรดปัจจุบันออก
    - `/agents` แสดงการทำงานที่กำลังใช้งานและสถานะการผูก
    - `/session idle <duration|off>` ตรวจสอบ/อัปเดตการยกเลิกโฟกัสอัตโนมัติเมื่อไม่มีการใช้งานสำหรับการผูกที่โฟกัสอยู่
    - `/session max-age <duration|off>` ตรวจสอบ/อัปเดตอายุสูงสุดแบบตายตัวสำหรับการผูกที่โฟกัสอยู่

    การกำหนดค่า:

```json5
{
  session: {
    threadBindings: {
      enabled: true,
      idleHours: 24,
      maxAgeHours: 0,
    },
  },
  channels: {
    discord: {
      threadBindings: {
        enabled: true,
        idleHours: 24,
        maxAgeHours: 0,
        spawnSessions: true,
        defaultSpawnContext: "fork",
      },
    },
  },
}
```

    หมายเหตุ:

    - `session.threadBindings.*` ตั้งค่าเริ่มต้นส่วนกลาง ส่วน `channels.discord.threadBindings.*` กำหนดค่าทับลักษณะการทำงานของ Discord
    - `spawnSessions` ควบคุมการสร้าง/ผูกเธรดอัตโนมัติสำหรับ `sessions_spawn({ thread: true })` และการสร้างเธรด ACP ค่าเริ่มต้น: `true`
    - `defaultSpawnContext` ควบคุมบริบทเอเจนต์ย่อยแบบเนทีฟสำหรับการสร้างที่ผูกกับเธรด ค่าเริ่มต้น: `"fork"`
    - คีย์ `spawnSubagentSessions`/`spawnAcpSessions` ที่เลิกใช้แล้วจะถูกย้ายข้อมูลโดย `openclaw doctor --fix`
    - หากปิดใช้งานการผูกเธรดสำหรับบัญชี `/focus` และการดำเนินการผูกเธรดที่เกี่ยวข้องจะไม่พร้อมใช้งาน

    ดู [เอเจนต์ย่อย](/th/tools/subagents), [เอเจนต์ ACP](/th/tools/acp-agents) และ [ข้อมูลอ้างอิงการกำหนดค่า](/th/gateway/configuration-reference)

  </Accordion>

  <Accordion title="ความคืบหน้าของเอเจนต์ย่อยบนข้อความต้นทาง">
    ตั้งค่า `channels.discord.subagentProgress: true` เพื่อแสดงกิจกรรมของลูกที่ทำงานอยู่เบื้องหลังบนข้อความ Discord ที่เริ่มการทำงานของแม่

```json5
{
  channels: {
    discord: {
      subagentProgress: true,
    },
  },
}
```

    ขณะที่การทำงานของลูกยังใช้งานอยู่ OpenClaw จะคงสถานะกำลังพิมพ์ของ Discord ไว้นานสูงสุดหนึ่งชั่วโมง และแทนที่รีแอ็กชันแสดงจำนวนหนึ่งรายการ (`1️⃣` ถึง `🔟`) เมื่อจำนวนการทำงานพร้อมกันเปลี่ยนแปลง โดย `🔟` ยังใช้แทนจำนวน 10 ขึ้นไปด้วย รีแอ็กชันแสดงจำนวนจะถูกนำออกหลังจากลูกตัวสุดท้ายสิ้นสุดลง ลูกที่ล้มเหลว หมดเวลา หรือถูกยุติจะทิ้งรีแอ็กชัน `🔴` ไว้

    ฟีเจอร์นี้ต้องเลือกเปิดใช้และใช้ค่าเวลาและอีโมจิเริ่มต้นภายในแบบคงที่ บอตต้องมีสิทธิ์ **Add Reactions** เพื่อแสดงผลตอบกลับด้วยรีแอ็กชัน `channels.discord.accounts.<id>.subagentProgress` ระดับบัญชีจะกำหนดค่าทับค่าระดับบนสุด

  </Accordion>

  <Accordion title="การผูกช่อง ACP แบบถาวร">
    สำหรับพื้นที่ทำงาน ACP แบบ "เปิดตลอดเวลา" ที่เสถียร ให้กำหนดค่าการผูก ACP แบบระบุชนิดระดับบนสุดซึ่งกำหนดเป้าหมายไปยังการสนทนาใน Discord

    พาธการกำหนดค่า: `bindings[]` พร้อม `type: "acp"` และ `match.channel: "discord"`

```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` ผูกช่องหรือเธรดปัจจุบันไว้กับที่ และเก็บข้อความในอนาคตไว้ในเซสชัน ACP เดิม ข้อความในเธรดจะรับช่วงการผูกจากช่องแม่
    - ในช่องหรือเธรดที่ผูกไว้ `/new` และ `/reset` จะรีเซ็ตเซสชัน ACP เดิมไว้กับที่ การผูกเธรดชั่วคราวสามารถกำหนดค่าทับการแก้ไขเป้าหมายได้ขณะที่ยังใช้งานอยู่
    - `spawnSessions` ควบคุมการสร้าง/ผูกเธรดลูกผ่าน `--thread auto|here`

    ดูรายละเอียดลักษณะการทำงานของการผูกได้ที่ [เอเจนต์ ACP](/th/tools/acp-agents)

  </Accordion>

  <Accordion title="การแจ้งเตือนรีแอ็กชัน">
    โหมดการแจ้งเตือนรีแอ็กชันต่อกิลด์ (`guilds.<id>.reactionNotifications`):

    - `off`
    - `own` (ค่าเริ่มต้น)
    - `all`
    - `allowlist` (ใช้ `guilds.<id>.users`)

    เหตุการณ์รีแอ็กชันจะถูกแปลงเป็นเหตุการณ์ระบบและแนบกับเซสชัน Discord ที่ได้รับการกำหนดเส้นทาง

  </Accordion>

  <Accordion title="เหตุการณ์สถานะออนไลน์">
    เลือกให้กิลด์ปลุกเอเจนต์ตามเส้นทางเมื่อสมาชิกที่เป็นมนุษย์เปลี่ยนสถานะจากออฟไลน์เป็นออนไลน์:

    ```json5
    {
      channels: {
        discord: {
          intents: { presence: true },
          guilds: {
            "111111111111111111": {
              presenceEvents: {
                channelId: "222222222222222222",
                users: ["333333333333333333"], // ไม่บังคับ; จำกัดผู้ชมช่องให้แคบลงอีก
                reconnectSuppressSeconds: 300, // ไม่บังคับ; ช่วงเงียบของเซสชันใหม่ (0 ปิดใช้งาน)
                burstLimit: 8, // ไม่บังคับ; จำนวนเหตุการณ์สูงสุดต่อช่วงเหตุการณ์ต่อเนื่อง
                burstWindowSeconds: 60, // ไม่บังคับ; หน้าต่างเลื่อนสำหรับตรวจจับเหตุการณ์ต่อเนื่อง
              },
            },
          },
        },
      },
    }
    ```

    `presenceEvents` ต้องเปิดใช้งาน Heartbeat สำหรับเอเจนต์ที่รับการกำหนดเส้นทาง และเปิดสิทธิ์พิเศษ **Presence Intent** ในหน้า Bot ของแอปพลิเคชันบน Discord Developer Portal OpenClaw จะตั้งต้นข้อมูลสมาชิกที่ออนไลน์อยู่ในปัจจุบันจากสแนปช็อต `GUILD_CREATE` ที่สมบูรณ์แต่ละรายการ กำหนดเส้นทางการเปลี่ยนสถานะจากออฟไลน์เป็นออนไลน์ที่ตรวจพบ และถือว่าสัญญาณออนไลน์ครั้งแรกในภายหลังของสมาชิกที่ยังไม่เคยพบเป็นการพร้อมใช้งานใหม่ด้วย สมาชิกดังกล่าวอาจเพิ่งออนไลน์หรือเข้าร่วมหลังจากสแนปช็อต ดังนั้นเหตุการณ์จึงไม่ได้ยืนยันสถานะก่อนหน้าที่แน่นอน เฉพาะผู้ใช้ที่เป็นมนุษย์และสามารถดู `channelId` เท่านั้นที่มีสิทธิ์: ช่องและเธรดสาธารณะต้องมี **View Channel** ในช่องหรือช่องแม่ ส่วนเธรดส่วนตัวยังต้องเป็นสมาชิกหรือมี **Manage Threads** เพิ่มเติม `users` สามารถจำกัดกลุ่มผู้ชมนี้ให้แคบลงอีก OpenClaw จะไม่สนใจบอตและสถานะออนไลน์ที่ไม่เปลี่ยนแปลง และคงช่วงพักต่อผู้ใช้เป็นเวลาแปดชั่วโมงไว้แม้ Gateway รีสตาร์ต เมื่อ Discord สร้างเซสชัน Gateway ใหม่และส่ง `READY` OpenClaw จะระงับเหตุการณ์ที่มาจากสถานะการแสดงตนเป็นเวลา `reconnectSuppressSeconds` (ค่าเริ่มต้น 300 โดย `0` จะปิดใช้งาน) ระหว่างสร้างสถานะการแสดงตนของกิลด์ใหม่ เพื่อไม่ให้สมาชิกที่ตรวจพบซ้ำปลุกเอเจนต์ทีละคน นอกจากนี้ ยังจำกัดอัตราเหตุการณ์ที่เข้าคิวสำเร็จต่อกิลด์ไว้ที่ `burstLimit` เหตุการณ์ (ค่าเริ่มต้น 8) ต่อหน้าต่างเลื่อน `burstWindowSeconds` (ค่าเริ่มต้น 60) โดยบันทึกแต่ละช่วงการระงับของกิลด์ลงในบันทึกเพียงครั้งเดียว เซสชันที่กลับมาทำงานต่อจะไม่ถือเป็นเซสชันใหม่ Discord จำกัดสแนปช็อตสำหรับกิลด์ที่มีสมาชิกมากกว่า 75,000 คน ในกรณีดังกล่าว OpenClaw ต้องได้รับการอัปเดตสถานะออฟไลน์อย่างชัดเจนก่อนส่งคำทักทาย เหตุการณ์ของระบบจะมี ID ผู้ใช้ กิลด์ และช่องที่เปลี่ยนแปลงไม่ได้ โดยไม่ฝังชื่อที่แสดงซึ่งเปลี่ยนแปลงได้ เอเจนต์เป็นผู้ตัดสินใจว่าจะทักทายหรือไม่และอย่างไร

  </Accordion>

  <Accordion title="รีแอ็กชันตอบรับ">
    `ackReaction` จะส่งอีโมจิตอบรับระหว่างที่ OpenClaw ประมวลผลข้อความขาเข้า

    ลำดับการตัดสินค่า:

    - `channels.discord.accounts.<accountId>.ackReaction`
    - `channels.discord.ackReaction`
    - `messages.ackReaction`
    - ใช้อีโมจิประจำตัวเอเจนต์เป็นค่าทดแทน (`agents.list[].identity.emoji` มิฉะนั้นใช้ "👀")

    หมายเหตุ:

    - Discord รองรับอีโมจิ Unicode หรือชื่ออีโมจิแบบกำหนดเอง
    - ใช้ `""` เพื่อปิดใช้งานรีแอ็กชันสำหรับช่องหรือบัญชี

    **ขอบเขต (`messages.ackReactionScope`):**

    ค่า: `"all"` (DM + กลุ่ม รวมถึงเหตุการณ์ห้องแบบแวดล้อม), `"direct"` (เฉพาะ DM), `"group-all"` (ทุกข้อความกลุ่มยกเว้นเหตุการณ์ห้องแบบแวดล้อม และไม่รวม DM), `"group-mentions"` (กลุ่มเมื่อมีการกล่าวถึงบอต; **ไม่รวม DM** และเป็นค่าเริ่มต้น), `"off"` / `"none"` (ปิดใช้งาน)

    <Note>
    ขอบเขตเริ่มต้น (`"group-mentions"`) จะไม่เรียกใช้รีแอ็กชันตอบรับในข้อความโดยตรงหรือเหตุการณ์ห้องแบบแวดล้อม หากต้องการรีแอ็กชันตอบรับสำหรับ DM ขาเข้าของ Discord และเหตุการณ์ในห้องที่ไม่มีความเคลื่อนไหว ให้ตั้งค่า `messages.ackReactionScope` เป็น `"all"`
    </Note>

  </Accordion>

  <Accordion title="การเขียนการกำหนดค่า">
    การเขียนการกำหนดค่าที่เริ่มต้นจากช่องจะเปิดใช้งานโดยค่าเริ่มต้น การตั้งค่านี้ส่งผลต่อขั้นตอน `/config set|unset` (เมื่อเปิดใช้งานคุณสมบัติคำสั่ง)

    ปิดใช้งาน:

```json5
{
  channels: {
    discord: {
      configWrites: false,
    },
  },
}
```

  </Accordion>

  <Accordion title="พร็อกซี Gateway">
    กำหนดเส้นทางทราฟฟิก WebSocket ของ Gateway Discord และการค้นหาผ่าน REST ตอนเริ่มต้น (ID แอปพลิเคชัน + การแปลงรายการอนุญาต) ผ่านพร็อกซี HTTP(S) ด้วย `channels.discord.proxy`
    การใช้พร็อกซีกับ WebSocket ของ Gateway Discord ต้องกำหนดอย่างชัดเจน การเชื่อมต่อ WebSocket จะไม่สืบทอดตัวแปรสภาพแวดล้อมพร็อกซีโดยรอบจากกระบวนการ Gateway การค้นหาผ่าน REST ตอนเริ่มต้นจะใช้พร็อกซีนี้เมื่อกำหนดค่า `channels.discord.proxy`

```json5
{
  channels: {
    discord: {
      proxy: "http://proxy.example:8080",
    },
  },
}
```

    การแทนค่าระดับบัญชี:

```json5
{
  channels: {
    discord: {
      accounts: {
        primary: {
          proxy: "http://proxy.example:8080",
        },
      },
    },
  },
}
```

  </Accordion>

  <Accordion title="การรองรับ PluralKit">
    เปิดใช้งานการแปลงค่า PluralKit เพื่อจับคู่ข้อความที่ส่งผ่านพร็อกซีกับข้อมูลประจำตัวของสมาชิกในระบบ:

```json5
{
  channels: {
    discord: {
      pluralkit: {
        enabled: true,
        token: "pk_live_...", // ไม่บังคับ; จำเป็นสำหรับระบบส่วนตัว
      },
    },
  },
}
```

    หมายเหตุ:

    - รายการอนุญาตสามารถใช้ `pk:<memberId>`
    - ชื่อที่แสดงของสมาชิกจะจับคู่ด้วยชื่อ/slug เฉพาะเมื่อ `channels.discord.dangerouslyAllowNameMatching: true`
    - การค้นหาจะเรียกใช้ API ของ PluralKit ด้วย ID ข้อความต้นฉบับ
    - หากการค้นหาล้มเหลว ข้อความที่ส่งผ่านพร็อกซีจะถือเป็นข้อความจากบอตและถูกละทิ้ง เว้นแต่ `allowBots` จะอนุญาตให้ผ่าน

  </Accordion>

  <Accordion title="นามแฝงการกล่าวถึงขาออก">
    ใช้ `mentionAliases` เมื่อเอเจนต์ต้องการการกล่าวถึงขาออกที่กำหนดผลลัพธ์ได้แน่นอนสำหรับผู้ใช้ Discord ที่รู้จัก คีย์คือแฮนเดิลที่ไม่มี `@` นำหน้า ส่วนค่าคือ ID ผู้ใช้ Discord แฮนเดิลที่ไม่รู้จัก, `@everyone`, `@here` และการกล่าวถึงภายในช่วงโค้ด Markdown จะคงไว้โดยไม่เปลี่ยนแปลง

```json5
{
  channels: {
    discord: {
      mentionAliases: {
        SupportLead: "123456789012345678",
      },
      accounts: {
        ops: {
          mentionAliases: {
            OpsLead: "234567890123456789",
          },
        },
      },
    },
  },
}
```

  </Accordion>

  <Accordion title="การกำหนดค่าสถานะการแสดงตน">
    ระบบจะใช้การอัปเดตสถานะการแสดงตนเมื่อกำหนดฟิลด์สถานะหรือกิจกรรม หรือเมื่อเปิดใช้งานสถานะการแสดงตนอัตโนมัติ

    เฉพาะสถานะ:

```json5
{
  channels: {
    discord: {
      status: "idle",
    },
  },
}
```

    กิจกรรม (สถานะแบบกำหนดเองเป็นประเภทกิจกรรมเริ่มต้นเมื่อตั้งค่า `activity`):

```json5
{
  channels: {
    discord: {
      activity: "ช่วงเวลาใช้สมาธิ",
      activityType: 4,
    },
  },
}
```

    การสตรีม:

```json5
{
  channels: {
    discord: {
      activity: "เขียนโค้ดสด",
      activityType: 1,
      activityUrl: "https://twitch.tv/openclaw",
    },
  },
}
```

    ตารางจับคู่ประเภทกิจกรรม:

    - 0: กำลังเล่น
    - 1: กำลังสตรีม (ต้องมี `activityUrl`; และ `activityUrl` ต้องมี `activityType: 1`)
    - 2: กำลังฟัง
    - 3: กำลังรับชม
    - 4: กำหนดเอง (ใช้ข้อความกิจกรรมเป็นค่าสถานะ โดยไม่จำเป็นต้องมีอีโมจิ)
    - 5: กำลังแข่งขัน

    สถานะการแสดงตนอัตโนมัติ (สัญญาณสุขภาพของรันไทม์):

```json5
{
  channels: {
    discord: {
      autoPresence: {
        enabled: true,
        intervalMs: 30000,
        minUpdateIntervalMs: 15000,
        exhaustedText: "โทเค็นหมด",
      },
    },
  },
}
```

    สถานะการแสดงตนอัตโนมัติจะจับคู่ความพร้อมใช้งานของรันไทม์กับสถานะ Discord: ปกติ => ออนไลน์, ประสิทธิภาพลดลงหรือไม่ทราบ => ไม่อยู่, ทรัพยากรหมดหรือไม่พร้อมใช้งาน => ห้ามรบกวน ค่าเริ่มต้น: `intervalMs` 30000, `minUpdateIntervalMs` 15000 (ต้องน้อยกว่าหรือเท่ากับ `intervalMs`) การแทนค่าข้อความที่ไม่บังคับ:

    - `autoPresence.healthyText`
    - `autoPresence.degradedText`
    - `autoPresence.exhaustedText` (รองรับตัวยึดตำแหน่ง `{reason}`)

  </Accordion>

  <Accordion title="การอนุมัติใน Discord">
    Discord รองรับการจัดการการอนุมัติด้วยปุ่มใน DM และสามารถเลือกโพสต์ข้อความแจ้งการอนุมัติในช่องต้นทางได้

    พาธการกำหนดค่า:

    - `channels.discord.execApprovals.enabled`
    - `channels.discord.execApprovals.approvers` (ไม่บังคับ; ใช้ `commands.ownerAllowFrom` เป็นค่าทดแทนเมื่อทำได้)
    - `channels.discord.execApprovals.target` (`dm` | `channel` | `both`, ค่าเริ่มต้น: `dm`)
    - `agentFilter`, `sessionFilter`, `cleanupAfterResolve`

    Discord จะเปิดใช้งานการอนุมัติการดำเนินการแบบเนทีฟโดยอัตโนมัติเมื่อไม่ได้ตั้งค่า `enabled` หรือเป็น `"auto"` และสามารถแปลงค่าผู้อนุมัติได้อย่างน้อยหนึ่งคน ไม่ว่าจะจาก `execApprovals.approvers` หรือจาก `commands.ownerAllowFrom` Discord จะไม่อนุมานผู้อนุมัติการดำเนินการจาก `allowFrom` ของช่อง, `dm.allowFrom` แบบเดิม หรือ `defaultTo` ของข้อความโดยตรง ตั้งค่า `enabled: false` เพื่อปิดใช้งาน Discord ในฐานะไคลเอนต์การอนุมัติแบบเนทีฟอย่างชัดเจน

    สำหรับคำสั่งกลุ่มที่ละเอียดอ่อนและจำกัดเฉพาะเจ้าของ เช่น `/diagnostics` และ `/export-trajectory` OpenClaw จะส่งข้อความแจ้งการอนุมัติและผลลัพธ์สุดท้ายแบบส่วนตัว โดยจะลองใช้ DM ของ Discord ก่อนเมื่อเจ้าของที่เรียกใช้มีเส้นทางเจ้าของใน Discord มิฉะนั้นจะใช้เส้นทางเจ้าของแรกที่พร้อมใช้งานจาก `commands.ownerAllowFrom` เป็นค่าทดแทน เช่น Telegram

    เมื่อ `target` เป็น `channel` หรือ `both` ข้อความแจ้งการอนุมัติจะมองเห็นได้ในช่อง เฉพาะผู้อนุมัติที่แปลงค่าได้เท่านั้นที่ใช้ปุ่มได้ ผู้ใช้อื่นจะได้รับข้อความปฏิเสธแบบชั่วคราว ข้อความแจ้งการอนุมัติมีข้อความคำสั่งรวมอยู่ด้วย ดังนั้นควรเปิดใช้งานการส่งไปยังช่องเฉพาะในช่องที่เชื่อถือได้ หากไม่สามารถหา ID ช่องจากคีย์เซสชันได้ OpenClaw จะใช้การส่งผ่าน DM เป็นค่าทดแทน

    Discord จะแสดงปุ่มอนุมัติร่วมที่ช่องแชตอื่นใช้ อะแดปเตอร์ Discord แบบเนทีฟมีหน้าที่หลักในการเพิ่มการกำหนดเส้นทาง DM ไปยังผู้อนุมัติและกระจายข้อความไปยังช่องต่าง ๆ เมื่อมีปุ่มเหล่านี้ ปุ่มจะเป็น UX หลักสำหรับการอนุมัติ OpenClaw ควรแสดงคำสั่ง `/approve` แบบดำเนินการด้วยตนเองเฉพาะเมื่อผลลัพธ์ของเครื่องมือระบุว่าไม่สามารถอนุมัติผ่านแชตได้ หรือการอนุมัติด้วยตนเองเป็นเส้นทางเดียว หากรันไทม์การอนุมัติแบบเนทีฟของ Discord ไม่ทำงาน OpenClaw จะคงข้อความแจ้ง `/approve <id> <decision>` แบบกำหนดผลลัพธ์ได้แน่นอนไว้ให้มองเห็น หากรันไทม์ทำงานอยู่แต่ไม่สามารถส่งการ์ดแบบเนทีฟไปยังเป้าหมายใดได้ OpenClaw จะส่งข้อความแจ้งทดแทนในแชตเดียวกัน พร้อมคำสั่ง `/approve` ที่ตรงกันจากการอนุมัติที่รอดำเนินการ

    การยืนยันตัวตนของ Gateway และการแปลงค่าการอนุมัติเป็นไปตามสัญญาไคลเอนต์ Gateway ร่วม (ID `plugin:` แปลงค่าผ่าน `plugin.approval.resolve`; ID อื่นแปลงค่าผ่าน `exec.approval.resolve`) การอนุมัติจะหมดอายุหลังจาก 30 นาทีโดยค่าเริ่มต้น

    ดู[การอนุมัติการดำเนินการ](/th/tools/exec-approvals)

  </Accordion>
</AccordionGroup>

## เครื่องมือและเกตการดำเนินการ

การดำเนินการกับข้อความ Discord ครอบคลุมการรับส่งข้อความ การดูแลช่อง การควบคุมเนื้อหา สถานะการแสดงตน และข้อมูลเมตา

ตัวอย่างหลัก:

- การรับส่งข้อความ: `sendMessage`, `readMessages`, `editMessage`, `deleteMessage`, `threadReply`
- รีแอ็กชัน: `react`, `reactions`, `emojiList`
- การควบคุมเนื้อหา: `timeout`, `kick`, `ban`
- สถานะการแสดงตน: `setPresence`

การดำเนินการ `event-create` รับพารามิเตอร์ `image` ที่ไม่บังคับ (URL หรือพาธไฟล์ภายในเครื่อง) เพื่อตั้งค่าภาพหน้าปกของกิจกรรมที่กำหนดเวลาไว้

เกตการดำเนินการอยู่ภายใต้ `channels.discord.actions.*`

ลักษณะการทำงานเริ่มต้นของเกต:

| กลุ่มการดำเนินการ                                                                                                                                                             | ค่าเริ่มต้น  |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| reactions, messages, threads, pins, polls, search, memberInfo, roleInfo, channelInfo, channels, voiceStatus, events, stickers, emojiUploads, stickerUploads, permissions | เปิดใช้งาน  |
| roles                                                                                                                                                                    | ปิดใช้งาน |
| moderation                                                                                                                                                               | ปิดใช้งาน |
| presence                                                                                                                                                                 | ปิดใช้งาน |

## UI ของ Components v2

OpenClaw ใช้คอมโพเนนต์ v2 ของ Discord สำหรับการอนุมัติการดำเนินการและเครื่องหมายข้ามบริบท การดำเนินการกับข้อความ Discord ยังสามารถรับ `components` สำหรับ UI แบบกำหนดเองได้ (ขั้นสูง ต้องสร้างเพย์โหลดคอมโพเนนต์ผ่านเครื่องมือ discord) ขณะที่ `embeds` แบบเดิมยังคงใช้งานได้ แต่ไม่แนะนำ

- `channels.discord.ui.components.accentColor` กำหนดสีเน้นที่คอนเทนเนอร์คอมโพเนนต์ของ Discord ใช้ (เลขฐานสิบหก) แยกตามบัญชี: `channels.discord.accounts.<id>.ui.components.accentColor`
- `channels.discord.agentComponents.ttlMs` ควบคุมระยะเวลาที่คอลแบ็กของคอมโพเนนต์ Discord ที่ส่งแล้วจะยังคงลงทะเบียนอยู่ (ค่าเริ่มต้น `1800000`, สูงสุด `86400000`) แยกตามบัญชี: `channels.discord.accounts.<id>.agentComponents.ttlMs`
- `embeds` จะถูกละเว้นเมื่อมีคอมโพเนนต์ v2
- โดยค่าเริ่มต้น ระบบจะระงับตัวอย่าง URL แบบธรรมดา ตั้งค่า `suppressEmbeds: false` ในการดำเนินการกับข้อความเมื่อต้องการให้ลิงก์ขาออกเพียงลิงก์เดียวแสดงตัวอย่างแบบขยาย

ตัวอย่าง:

```json5
{
  channels: {
    discord: {
      ui: {
        components: {
          accentColor: "#5865F2",
        },
      },
    },
  },
}
```

## เสียง

Discord มีพื้นผิวเสียงสองแบบที่แตกต่างกัน ได้แก่ **ช่องเสียง** แบบเรียลไทม์ (การสนทนาต่อเนื่อง) และ **ไฟล์แนบข้อความเสียง** (รูปแบบตัวอย่างคลื่นเสียง) Gateway รองรับทั้งสองแบบ

### ช่องเสียง

รายการตรวจสอบการตั้งค่า:

1. เปิดใช้งาน Message Content Intent ใน Discord Developer Portal
2. เปิดใช้งาน Server Members Intent เมื่อใช้รายการอนุญาตตามบทบาท/ผู้ใช้
3. เชิญบอตโดยใช้ขอบเขต `bot` และ `applications.commands`
4. ให้สิทธิ์ Connect, Speak, Send Messages และ Read Message History ในช่องเสียงเป้าหมาย
5. เปิดใช้งานคำสั่งเนทีฟ (`commands.native` หรือ `channels.discord.commands.native`)
6. กำหนดค่า `channels.discord.voice`

ใช้ `/vc join|leave|status` เพื่อควบคุมเซสชัน คำสั่งนี้ใช้เอเจนต์เริ่มต้นของบัญชีและปฏิบัติตามกฎรายการอนุญาตและนโยบายกลุ่มเดียวกับคำสั่ง Discord อื่นๆ

```bash
/vc join channel:<voice-channel-id>
/vc status
/vc leave
```

หากต้องการตรวจสอบสิทธิ์ที่มีผลจริงของบอตก่อนเข้าร่วม:

```bash
openclaw channels capabilities --channel discord --target channel:<voice-channel-id>
```

ตัวอย่างการเข้าร่วมอัตโนมัติ:

```json5
{
  channels: {
    discord: {
      voice: {
        enabled: true,
        model: "openai/gpt-5.6-sol",
        autoJoin: [
          {
            guildId: "123456789012345678",
            channelId: "234567890123456789",
          },
        ],
        allowedChannels: [
          {
            guildId: "123456789012345678",
            channelId: "234567890123456789",
          },
        ],
        daveEncryption: true,
        decryptionFailureTolerance: 24,
        connectTimeoutMs: 30000,
        reconnectGraceMs: 15000,
        realtime: {
          provider: "openai",
          model: "gpt-realtime-2.1",
          speakerVoice: "cedar",
        },
      },
    },
  },
}
```

หมายเหตุ:

- เสียง Discord เป็นแบบเลือกเปิดใช้สำหรับการกำหนดค่าที่รองรับเฉพาะข้อความ ให้ตั้งค่า `channels.discord.voice.enabled=true` (หรือคงบล็อก `channels.discord.voice` ที่มีอยู่ไว้) เพื่อเปิดใช้คำสั่ง `/vc` รันไทม์เสียง และ gateway intent `GuildVoiceStates` โดย `channels.discord.intents.voiceStates` สามารถเขียนทับการสมัครรับ intent อย่างชัดเจนได้ หากไม่ตั้งค่า ระบบจะทำตามสถานะการเปิดใช้เสียงที่มีผล
- `voice.mode` ควบคุมเส้นทางการสนทนา ค่าเริ่มต้นคือ `agent-proxy`: ส่วนหน้าสำหรับเสียงแบบเรียลไทม์จัดการจังหวะการผลัดกันพูด การขัดจังหวะ และการเล่นเสียง มอบหมายงานที่มีสาระให้เอเจนต์ OpenClaw ที่กำหนดเส้นทางไว้ผ่าน `openclaw_agent_consult` และถือว่าผลลัพธ์เป็นพรอมต์ Discord แบบพิมพ์จากผู้พูดคนนั้น ส่วน `stt-tts` จะคงโฟลว์ STT แบบแบตช์ร่วมกับ TTS รุ่นเก่าไว้ และ `bidi` ช่วยให้โมเดลเรียลไทม์สนทนาได้โดยตรง พร้อมเปิดให้ใช้ `openclaw_agent_consult` สำหรับสมอง OpenClaw
- `voice.agentSession` ควบคุมว่าการสนทนา OpenClaw ใดจะรับช่วงการพูดผ่านเสียง หากไม่ตั้งค่า จะใช้เซสชันของช่องเสียงเอง หรือตั้งเป็น `{ mode: "target", target: "channel:<text-channel-id>" }` เพื่อให้ช่องเสียงทำหน้าที่เป็นส่วนขยายไมโครโฟน/ลำโพงของเซสชันช่องข้อความ Discord ที่มีอยู่ เช่น `#maintainers`
- `voice.model` เขียนทับสมองเอเจนต์ OpenClaw สำหรับการตอบกลับด้วยเสียงใน Discord และการปรึกษาแบบเรียลไทม์ หากไม่ตั้งค่า จะสืบทอดโมเดลเอเจนต์ที่กำหนดเส้นทางไว้ ค่านี้แยกจาก `voice.realtime.model`
- `voice.followUsers` ช่วยให้บอทเข้าร่วม ย้าย และออกจากเสียง Discord ตามผู้ใช้ที่เลือก ดู [ติดตามผู้ใช้ในช่องเสียง](#follow-users-in-voice)
- `agent-proxy` กำหนดเส้นทางเสียงพูดผ่าน `discord-voice` ซึ่งคงการตรวจสอบสิทธิ์ของเจ้าของ/เครื่องมือตามปกติสำหรับผู้พูดและเซสชันเป้าหมาย แต่ซ่อนเครื่องมือ `tts` ของเอเจนต์ เนื่องจากเสียง Discord เป็นผู้ควบคุมการเล่นเสียง ตามค่าเริ่มต้น `agent-proxy` จะให้สิทธิ์เข้าถึงเครื่องมือแก่การปรึกษาเทียบเท่าเจ้าของอย่างสมบูรณ์สำหรับผู้พูดที่เป็นเจ้าของ (`voice.realtime.toolPolicy: "owner"`) และให้ความสำคัญอย่างมากกับการปรึกษาเอเจนต์ OpenClaw ก่อนตอบคำถามที่มีสาระ (`voice.realtime.consultPolicy: "always"`) ในโหมด `always` เริ่มต้นนี้ เลเยอร์เรียลไทม์จะไม่พูดถ้อยคำคั่นเวลาโดยอัตโนมัติก่อนคำตอบจากการปรึกษา แต่จะบันทึกและถอดเสียงคำพูด จากนั้นจึงพูดคำตอบจาก OpenClaw ที่กำหนดเส้นทางไว้ หากคำตอบจากการปรึกษาแบบบังคับหลายรายการเสร็จสิ้นในขณะที่ Discord ยังคงเล่นคำตอบแรก คำตอบแบบข้อความตรงตัวรายการหลังจะเข้าคิวจนกว่าการเล่นเสียงจะว่าง แทนที่จะแทนที่เสียงพูดกลางประโยค
- ในโหมด `stt-tts` STT ใช้ `tools.media.audio`; `voice.model` ไม่มีผลต่อการถอดเสียง
- ในโหมดเรียลไทม์ `voice.realtime.provider`, `voice.realtime.model` และ `voice.realtime.speakerVoice` ใช้กำหนดค่าเซสชันเสียงเรียลไทม์ สำหรับ OpenAI Realtime 2.1 ร่วมกับสมอง Codex ให้ใช้ `voice.realtime.model: "gpt-realtime-2.1"` และ `voice.model: "openai/gpt-5.6-sol"`
- ตามค่าเริ่มต้น โหมดเสียงเรียลไทม์จะรวมไฟล์โปรไฟล์ขนาดเล็ก `IDENTITY.md`, `USER.md` และ `SOUL.md` ไว้ในคำสั่งสำหรับผู้ให้บริการเรียลไทม์ เพื่อให้ช่วงการสนทนาโดยตรงที่รวดเร็วยังคงใช้อัตลักษณ์ การยึดโยงกับผู้ใช้ และบุคลิกเดียวกับเอเจนต์ OpenClaw ที่กำหนดเส้นทางไว้ ตั้งค่า `voice.realtime.bootstrapContextFiles` เป็นชุดย่อยเพื่อปรับแต่ง หรือใช้ `[]` เพื่อปิดใช้ รองรับเฉพาะไฟล์โปรไฟล์เหล่านั้น ส่วน `AGENTS.md` จะยังคงอยู่ในบริบทเอเจนต์ตามปกติ บริบทโปรไฟล์ที่แทรกเข้ามาไม่สามารถใช้แทน `openclaw_agent_consult` สำหรับงานในพื้นที่ทำงาน ข้อเท็จจริงปัจจุบัน การค้นหาหน่วยความจำ หรือการดำเนินการที่มีเครื่องมือรองรับ
- ในโหมดเรียลไทม์ `agent-proxy` ของ OpenAI การควบคุมด้วยชื่อเรียกปลุกจะปรับตามห้องโดยอัตโนมัติเป็นค่าเริ่มต้น กล่าวคือ เมื่อมีมนุษย์หนึ่งคนจะพูดได้อย่างเป็นธรรมชาติโดยไม่ต้องใช้ชื่อเรียกปลุก แต่เมื่อมีมนุษย์ตั้งแต่สองคนขึ้นไป ต้องเริ่มต้นหรือจบช่วงการพูดด้วยชื่อเรียกปลุกหนึ่งชื่อ บอทอื่นจะไม่ถูกนับเป็นคน ตั้งค่า `voice.realtime.requireWakeName: true` ให้ต้องใช้ชื่อเรียกปลุกเสมอ หรือ `false` เพื่อไม่ต้องใช้เลย ชื่อเรียกปลุกที่กำหนดค่าต้องมีหนึ่งหรือสองคำ หากไม่ได้ตั้งค่า `voice.realtime.wakeNames` OpenClaw จะใช้ `name` ของเอเจนต์ที่กำหนดเส้นทางไว้ร่วมกับ `OpenClaw` และหากใช้ไม่ได้จะใช้รหัสเอเจนต์ร่วมกับ `OpenClaw` เมื่อการควบคุมด้วยชื่อเรียกปลุกทำงาน ระบบจะปิดการตอบกลับอัตโนมัติของผู้ให้บริการเรียลไทม์ กำหนดเส้นทางช่วงการพูดที่ยอมรับผ่านเส้นทางการปรึกษาเอเจนต์ OpenClaw และพูดคำตอบรับสั้นๆ เมื่อระบบตรวจพบชื่อเรียกปลุกที่ต้นข้อความจากการถอดเสียงบางส่วน ก่อนที่ข้อความถอดเสียงฉบับสมบูรณ์จะมาถึง นโยบายจะปรับตามการเข้าร่วมและออกจากห้องแบบสดโดยไม่ต้องเชื่อมต่อเสียงใหม่
- ผู้ให้บริการเรียลไทม์ของ OpenAI ยอมรับชื่อเหตุการณ์ Realtime 2 ปัจจุบันและนามแฝงเดิมที่เข้ากันได้กับ Codex สำหรับเหตุการณ์เสียงเอาต์พุตและข้อความถอดเสียง ดังนั้นสแนปช็อตผู้ให้บริการที่เข้ากันได้จึงเปลี่ยนแปลงคลาดเคลื่อนได้โดยไม่ทำให้เสียงของผู้ช่วยสูญหาย
- `voice.realtime.bargeIn` ควบคุมว่าเหตุการณ์เริ่มพูดของผู้พูดใน Discord จะขัดจังหวะการเล่นเสียงเรียลไทม์ที่กำลังทำงานหรือไม่ หากไม่ตั้งค่า จะเป็นไปตามการตั้งค่าการขัดจังหวะด้วยเสียงอินพุตของผู้ให้บริการเรียลไทม์
- `voice.realtime.minBargeInAudioEndMs` ควบคุมระยะเวลาขั้นต่ำที่เสียงผู้ช่วยต้องเล่น ก่อนที่การแทรกพูดในโหมดเรียลไทม์ของ OpenAI จะตัดเสียง ค่าเริ่มต้น: `250` ตั้งค่า `0` เพื่อขัดจังหวะทันทีในห้องที่มีเสียงสะท้อนต่ำ หรือเพิ่มค่านี้สำหรับการตั้งค่าลำโพงที่มีเสียงสะท้อนมาก
- `voice.tts` เขียนทับ `messages.tts` เฉพาะสำหรับการเล่นเสียง `stt-tts`; โหมดเรียลไทม์จะใช้ `voice.realtime.speakerVoice` แทน สำหรับเสียง OpenAI ในการเล่นผ่าน Discord ให้ตั้งค่า `voice.tts.provider: "openai"` และเลือกเสียง Text-to-speech ภายใต้ `voice.tts.providers.openai.speakerVoice` โดย `cedar` เป็นตัวเลือกที่ดีซึ่งให้เสียงโทนผู้ชายบนโมเดล TTS ปัจจุบันของ OpenAI
- การเขียนทับ `systemPrompt` ของ Discord รายช่องจะมีผลกับช่วงข้อความถอดเสียงสำหรับช่องเสียงนั้น
- เมื่อ OpenClaw เข้าร่วมช่องเสียง เซสชันเอเจนต์ที่กำหนดเส้นทางไว้จะได้รับเหตุการณ์ระบบแบบเงียบพร้อมรายชื่อผู้เข้าร่วมปัจจุบัน การเข้าร่วมและออกของผู้เข้าร่วมในภายหลังจะอัปเดตเซสชันนั้นโดยไม่เรียกให้เกิดการตอบกลับด้วยเสียงที่ไม่ได้ร้องขอ และชื่อที่แสดงใน Discord จะถือเป็นป้ายกำกับที่ไม่น่าเชื่อถือ ช่วงการพูดผ่านเสียงที่ได้รับอนุญาตจะได้รับสแนปช็อตรายชื่อผู้เข้าร่วมล่าสุดด้วย
- ช่วงข้อความถอดเสียงและคำสั่ง `/vc` ใช้รายการ Discord ใน `commands.ownerAllowFrom` เพื่อกำหนดสถานะเจ้าของ เมื่อไม่ได้กำหนดเจ้าของคำสั่ง Discord ค่า `allowFrom` (หรือ `dm.allowFrom` แบบเดิม) ของบัญชี Discord ที่เลือกยังสามารถอนุญาตการเข้าถึงเสียงได้โดยไม่ให้สถานะเจ้าของ การมองเห็นเครื่องมือของเอเจนต์เป็นไปตามนโยบายเครื่องมือที่กำหนดค่าสำหรับเซสชันที่กำหนดเส้นทางไว้
- หาก `voice.autoJoin` มีหลายรายการสำหรับกิลด์เดียวกัน OpenClaw จะเข้าร่วมช่องสุดท้ายที่กำหนดค่าไว้สำหรับกิลด์นั้น
- `voice.allowedChannels` เป็นรายการอนุญาตสำหรับการคงอยู่ที่ไม่บังคับ หากไม่ตั้งค่า จะอนุญาตให้ `/vc join` เข้าสู่ช่องเสียง Discord ใดๆ ที่ได้รับอนุญาต เมื่อตั้งค่าแล้ว `/vc join` การเข้าร่วมอัตโนมัติเมื่อเริ่มต้น และการย้ายสถานะเสียงของบอทจะถูกจำกัดไว้เฉพาะรายการ `{ guildId, channelId }` ที่ระบุ ตั้งค่าเป็นอาร์เรย์ว่างเพื่อปฏิเสธการเข้าร่วมเสียง Discord ทั้งหมด หาก Discord ย้ายบอทออกนอกรายการอนุญาต OpenClaw จะออกจากช่องนั้นและกลับเข้าร่วมเป้าหมายการเข้าร่วมอัตโนมัติที่กำหนดค่าไว้ หากมีเป้าหมายดังกล่าว
- `voice.daveEncryption` และ `voice.decryptionFailureTolerance` จะถูกส่งผ่านไปยังตัวเลือกการเข้าร่วมของ `@discordjs/voice`; ค่าเริ่มต้นจากต้นทางคือ `daveEncryption=true` และ `decryptionFailureTolerance=24`
- OpenClaw ใช้ตัวแปลงสัญญาณ `libopus-wasm` ที่รวมมาให้สำหรับการรับเสียง Discord และการเล่น PCM ดิบแบบเรียลไทม์ โดยมาพร้อมบิลด์ WebAssembly ของ libopus ที่ตรึงเวอร์ชันไว้ และไม่ต้องใช้ส่วนเสริม opus แบบเนทีฟ
- `voice.connectTimeoutMs` ควบคุมการรอ Ready ครั้งแรกของ `@discordjs/voice` สำหรับ `/vc join` และความพยายามเข้าร่วมอัตโนมัติ ค่าเริ่มต้น: `30000`
- `voice.reconnectGraceMs` ควบคุมระยะเวลาที่ OpenClaw รอให้เซสชันเสียงที่ตัดการเชื่อมต่อเริ่มเชื่อมต่อใหม่ก่อนทำลายเซสชัน ค่าเริ่มต้น: `15000`
- ในโหมด `stt-tts` การเล่นเสียงจะไม่หยุดเพียงเพราะผู้ใช้อื่นเริ่มพูด เพื่อหลีกเลี่ยงวงจรป้อนกลับ OpenClaw จะไม่รับเสียงใหม่ขณะที่ TTS กำลังเล่น โปรดพูดหลังการเล่นเสียงเสร็จสิ้นสำหรับช่วงถัดไป โหมดเรียลไทม์จะส่งต่อการเริ่มพูดของผู้พูดเป็นสัญญาณแทรกพูดไปยังผู้ให้บริการเรียลไทม์
- ในโหมดเรียลไทม์ เสียงสะท้อนจากลำโพงที่เข้าสู่ไมโครโฟนซึ่งเปิดอยู่ อาจดูเหมือนการแทรกพูดและขัดจังหวะการเล่นเสียง สำหรับห้อง Discord ที่มีเสียงสะท้อนมาก ให้ตั้งค่า `voice.realtime.providers.openai.interruptResponseOnInputAudio: false` เพื่อป้องกันไม่ให้ OpenAI ขัดจังหวะอัตโนมัติเมื่อมีเสียงอินพุต เพิ่ม `voice.realtime.bargeIn: true` หากยังต้องการให้เหตุการณ์เริ่มพูดของผู้พูดใน Discord ขัดจังหวะการเล่นเสียงที่กำลังทำงาน บริดจ์เรียลไทม์ของ OpenAI จะละเว้นการตัดการเล่นเสียงที่สั้นกว่า `voice.realtime.minBargeInAudioEndMs` โดยถือว่าน่าจะเป็นเสียงสะท้อน/เสียงรบกวน และบันทึกว่าได้ข้ามแทนที่จะล้างการเล่นเสียงของ Discord
- `voice.captureSilenceGraceMs` ควบคุมระยะเวลาที่ OpenClaw รอหลังจาก Discord รายงานว่าผู้พูดหยุดพูด ก่อนสรุปส่วนเสียงนั้นเพื่อส่งให้ STT ค่าเริ่มต้น: `2000`; เพิ่มค่านี้หาก Discord แบ่งช่วงหยุดตามปกติเป็นข้อความถอดเสียงบางส่วนที่ขาดเป็นห้วงๆ
- เมื่อเลือก ElevenLabs เป็นผู้ให้บริการ TTS การเล่นเสียง Discord จะใช้ TTS แบบสตรีมและเริ่มเล่นจากสตรีมการตอบกลับของผู้ให้บริการ ผู้ให้บริการที่ไม่รองรับการสตรีมจะย้อนกลับไปใช้เส้นทางไฟล์ชั่วคราวที่สังเคราะห์แล้ว
- OpenClaw เฝ้าดูความล้มเหลวในการถอดรหัสข้อมูลที่รับ และกู้คืนอัตโนมัติโดยออกจากช่องเสียงแล้วเข้าร่วมใหม่ หลังเกิดความล้มเหลวซ้ำหลายครั้งภายในช่วงเวลาสั้นๆ
- หากบันทึกการรับแสดง `DecryptionFailed(UnencryptedWhenPassthroughDisabled)` ซ้ำๆ หลังอัปเดต ให้รวบรวมรายงานการขึ้นต่อกันและบันทึก บรรทัด `@discordjs/voice` ที่รวมมาให้มีการแก้ไขการเติมข้อมูลจากต้นทางใน discord.js PR #11449 ซึ่งปิด discord.js issue #11419
- เหตุการณ์การรับ `The operation was aborted` เป็นสิ่งที่คาดหมายเมื่อ OpenClaw สรุปส่วนเสียงของผู้พูดที่บันทึกไว้ เหตุการณ์เหล่านี้เป็นข้อมูลวินิจฉัยแบบละเอียด ไม่ใช่คำเตือน
- บันทึกเสียง Discord แบบละเอียดมีตัวอย่างข้อความถอดเสียง STT หนึ่งบรรทัดที่จำกัดความยาวสำหรับแต่ละส่วนของผู้พูดที่ยอมรับ เพื่อให้การดีบักแสดงทั้งฝั่งผู้ใช้และฝั่งคำตอบของเอเจนต์ โดยไม่ทิ้งข้อความถอดเสียงที่ไม่จำกัดความยาว
- ในโหมด `agent-proxy` กลไกสำรองของการปรึกษาแบบบังคับจะข้ามส่วนข้อความถอดเสียงที่น่าจะไม่สมบูรณ์ เช่น ข้อความที่ลงท้ายด้วย `...` หรือคำเชื่อมท้ายอย่าง "และ" รวมถึงคำปิดท้ายที่เห็นได้ชัดว่าไม่ต้องดำเนินการ เช่น "เดี๋ยวกลับมา" หรือ "ลาก่อน" บันทึกจะแสดง `forced agent consult skipped reason=...` เมื่อกลไกนี้ป้องกันคำตอบเก่าที่ค้างอยู่ในคิว

### ติดตามผู้ใช้ในช่องเสียง

ใช้ `voice.followUsers` เมื่อต้องการให้บอทเสียง Discord อยู่กับผู้ใช้ Discord ที่รู้จักอย่างน้อยหนึ่งคน แทนการเข้าร่วมช่องคงที่เมื่อเริ่มต้นหรือรอ `/vc join`

```json5
{
  channels: {
    discord: {
      voice: {
        enabled: true,
        followUsersEnabled: true,
        followUsers: ["discord:123456789012345678"],
        allowedChannels: [
          {
            guildId: "123456789012345678",
            channelId: "234567890123456789",
          },
        ],
      },
    },
  },
}
```

ลักษณะการทำงาน:

- `followUsers` รองรับ ID ผู้ใช้ Discord แบบดิบและค่า `discord:<id>` OpenClaw จะปรับทั้งสองรูปแบบให้เป็นมาตรฐานก่อนจับคู่เหตุการณ์สถานะเสียง
- `followUsersEnabled` มีค่าเริ่มต้นเป็น `true` เมื่อกำหนดค่า `followUsers` ตั้งค่าเป็น `false` เพื่อเก็บรายการที่บันทึกไว้ แต่หยุดการติดตามเสียงโดยอัตโนมัติ
- `followUsers` ควบคุมเฉพาะการคงอยู่ในช่องเสียงเท่านั้น โดยไม่ได้ให้สิทธิ์เข้าถึงในฐานะผู้พูดหรืออำนาจเจ้าของ โปรดกำหนดค่า `commands.ownerAllowFrom` รวมถึงผู้ใช้และบทบาทของกิลด์หรือช่องแยกต่างหาก
- เมื่อผู้ใช้ที่ติดตามเข้าร่วมช่องเสียงที่อนุญาต OpenClaw จะเข้าร่วมช่องนั้น เมื่อผู้ใช้ย้าย OpenClaw จะย้ายตาม และเมื่อผู้ใช้ที่กำลังติดตามอยู่ตัดการเชื่อมต่อ OpenClaw จะออกจากช่อง
- หากมีผู้ใช้ที่ติดตามหลายคนอยู่ในกิลด์เดียวกันและผู้ใช้ที่กำลังติดตามอยู่ออกจากช่อง OpenClaw จะย้ายไปยังช่องของผู้ใช้ที่ติดตามและกำลังถูกเฝ้าติดตามอีกคนก่อนออกจากกิลด์ หากผู้ใช้ที่ติดตามหลายคนย้ายพร้อมกัน เหตุการณ์สถานะเสียงที่ตรวจพบล่าสุดจะมีผล
- `allowedChannels` ยังคงมีผล ผู้ใช้ที่ติดตามซึ่งอยู่ในช่องที่ไม่อนุญาตจะถูกละเว้น และเซสชันที่เป็นของการติดตามจะย้ายไปหาผู้ใช้ที่ติดตามคนอื่นหรือออกจากช่อง
- OpenClaw จะปรับสถานะเหตุการณ์เสียงที่พลาดไปให้สอดคล้องกันเมื่อเริ่มต้นระบบและตามช่วงเวลาที่มีขอบเขตจำกัด การปรับสถานะให้สอดคล้องจะสุ่มตรวจสอบกิลด์ที่กำหนดค่าไว้และจำกัดจำนวนการค้นหาผ่าน REST ต่อการทำงานหนึ่งรอบ ดังนั้นรายการ `followUsers` ที่มีขนาดใหญ่มากอาจต้องใช้มากกว่าหนึ่งช่วงเวลากว่าจะเข้าสู่สถานะที่สอดคล้องกัน
- หาก Discord หรือผู้ดูแลระบบย้ายบอตขณะที่กำลังติดตามผู้ใช้ OpenClaw จะสร้างเซสชันเสียงใหม่และคงความเป็นเจ้าของโดยการติดตามไว้เมื่อปลายทางได้รับอนุญาต หากบอตถูกย้ายออกนอก `allowedChannels` OpenClaw จะออกและกลับเข้าร่วมเป้าหมายที่กำหนดค่าไว้เมื่อมีเป้าหมายดังกล่าว
- การกู้คืนการรับ DAVE อาจออกและกลับเข้าร่วมช่องเดิมหลังการถอดรหัสล้มเหลวซ้ำหลายครั้ง เซสชันที่เป็นของการติดตามจะคงความเป็นเจ้าของโดยการติดตามไว้ตลอดเส้นทางการกู้คืนนี้ ดังนั้นเมื่อผู้ใช้ที่ติดตามตัดการเชื่อมต่อในภายหลัง ระบบจะยังคงออกจากช่อง

เลือกระหว่างโหมดการเข้าร่วม:

- ใช้ `followUsers` สำหรับการตั้งค่าส่วนบุคคลหรือของผู้ดำเนินการ ซึ่งบอตควรอยู่ในช่องเสียงโดยอัตโนมัติเมื่อคุณอยู่ในช่องเสียง
- ใช้ `autoJoin` สำหรับบอตประจำห้องที่ควรอยู่ในช่องแม้ไม่มีผู้ใช้ที่ถูกติดตามอยู่ในช่องเสียง
- ใช้ `/vc join` สำหรับการเข้าร่วมเพียงครั้งเดียวหรือห้องที่การปรากฏตัวในช่องเสียงโดยอัตโนมัติอาจไม่เป็นไปตามที่คาดหมาย

โคเดกเสียงของ Discord:

- บันทึกการรับเสียงจะแสดง `discord voice: opus decoder: libopus-wasm`
- การเล่นแบบเรียลไทม์จะเข้ารหัส PCM สเตอริโอดิบ 48 kHz เป็น Opus ด้วยแพ็กเกจ `libopus-wasm` ที่รวมมาให้ชุดเดียวกัน ก่อนส่งแพ็กเก็ตให้ `@discordjs/voice`
- การเล่นไฟล์และสตรีมจากผู้ให้บริการจะแปลงเป็น PCM สเตอริโอดิบ 48 kHz ด้วย ffmpeg จากนั้นใช้ `libopus-wasm` สำหรับสตรีมแพ็กเก็ต Opus ที่ส่งไปยัง Discord

ไปป์ไลน์ STT และ TTS:

- การบันทึก PCM จาก Discord จะถูกแปลงเป็นไฟล์ WAV ชั่วคราว
- `tools.media.audio` จัดการ STT เช่น `openai/gpt-4o-mini-transcribe`
- บทถอดเสียงจะถูกส่งผ่านทางเข้ารับและการกำหนดเส้นทางของ Discord ขณะที่ LLM สำหรับการตอบกลับทำงานด้วยนโยบายเอาต์พุตเสียงที่ซ่อนเครื่องมือ `tts` ของเอเจนต์และขอข้อความที่ส่งกลับ เนื่องจากเสียงของ Discord เป็นผู้ควบคุมการเล่น TTS ขั้นสุดท้าย
- `voice.model` เมื่อกำหนดค่าไว้ จะแทนที่เฉพาะ LLM สำหรับการตอบกลับในรอบของช่องเสียงนี้
- `voice.tts` จะถูกผสานทับ `messages.tts` ผู้ให้บริการที่รองรับการสตรีมจะส่งข้อมูลเข้าสู่โปรแกรมเล่นโดยตรง มิฉะนั้นไฟล์เสียงที่ได้จะถูกเล่นในช่องที่เข้าร่วม

ตัวอย่างเซสชันช่องเสียงแบบพร็อกซีเอเจนต์เริ่มต้น:

```json5
{
  channels: {
    discord: {
      voice: {
        enabled: true,
        model: "openai/gpt-5.6-sol",
        followUsersEnabled: true,
        followUsers: ["123456789012345678"],
        realtime: {
          provider: "openai",
          model: "gpt-realtime-2.1",
          speakerVoice: "cedar",
        },
      },
    },
  },
}
```

เมื่อไม่มีบล็อก `voice.agentSession` ช่องเสียงแต่ละช่องจะมีเซสชัน OpenClaw ที่กำหนดเส้นทางของตัวเอง ตัวอย่างเช่น `/vc join channel:234567890123456789` จะสื่อสารกับเซสชันของช่องเสียง Discord นั้น โมเดลเรียลไทม์เป็นเพียงส่วนหน้าสำหรับเสียง ส่วนคำขอที่มีสาระสำคัญจะถูกส่งต่อไปยังเอเจนต์ OpenClaw ที่กำหนดค่าไว้ หากโมเดลเรียลไทม์สร้างบทถอดเสียงสุดท้ายโดยไม่เรียกเครื่องมือปรึกษา OpenClaw จะบังคับให้เรียกการปรึกษาเป็นทางเลือกสำรอง เพื่อให้ค่าเริ่มต้นยังคงทำงานเสมือนการสนทนากับเอเจนต์

ตัวอย่าง STT และ TTS แบบเดิม:

```json5
{
  channels: {
    discord: {
      voice: {
        enabled: true,
        mode: "stt-tts",
        model: "openai/gpt-5.4-mini",
        tts: {
          provider: "openai",
          providers: {
            openai: {
              model: "gpt-4o-mini-tts",
              speakerVoice: "cedar",
            },
          },
        },
      },
    },
  },
}
```

ตัวอย่างเรียลไทม์แบบสองทิศทาง:

```json5
{
  channels: {
    discord: {
      voice: {
        enabled: true,
        mode: "bidi",
        model: "openai/gpt-5.6-sol",
        realtime: {
          provider: "openai",
          model: "gpt-realtime-2.1",
          speakerVoice: "cedar",
          toolPolicy: "safe-read-only",
          consultPolicy: "always",
        },
      },
    },
  },
}
```

เสียงในฐานะส่วนขยายของเซสชันช่อง Discord ที่มีอยู่:

```json5
{
  channels: {
    discord: {
      voice: {
        enabled: true,
        mode: "agent-proxy",
        model: "openai/gpt-5.6-sol",
        agentSession: {
          mode: "target",
          target: "channel:123456789012345678",
        },
        realtime: {
          provider: "openai",
          model: "gpt-realtime-2.1",
          speakerVoice: "cedar",
        },
      },
    },
  },
}
```

ในโหมด `agent-proxy` บอตจะเข้าร่วมช่องเสียงที่กำหนดค่าไว้ แต่รอบการทำงานของเอเจนต์ OpenClaw จะใช้เซสชันและเอเจนต์ที่กำหนดเส้นทางตามปกติของช่องเป้าหมาย เซสชันเสียงเรียลไทม์จะพูดผลลัพธ์ที่ส่งกลับในช่องเสียง เอเจนต์ผู้ควบคุมยังคงใช้เครื่องมือส่งข้อความตามปกติได้ตามนโยบายเครื่องมือ รวมถึงส่งข้อความ Discord แยกต่างหากหากเป็นการดำเนินการที่เหมาะสม

ขณะที่การทำงาน OpenClaw ที่มอบหมายกำลังดำเนินอยู่ บทถอดเสียงใหม่จากช่องเสียง Discord จะถูกใช้เป็นการควบคุมการทำงานแบบสดก่อนเริ่มรอบการทำงานของเอเจนต์อีกครั้ง วลีอย่าง "สถานะ" "ยกเลิกสิ่งนั้น" "ใช้วิธีแก้ที่เล็กกว่า" หรือ "เมื่อเสร็จแล้วให้ตรวจสอบการทดสอบด้วย" จะถูกจำแนกเป็นข้อมูลนำเข้าสำหรับการตรวจสอบสถานะ การยกเลิก การปรับทิศทาง หรือการติดตามผลของเซสชันที่กำลังทำงาน ผลลัพธ์ของสถานะ การยกเลิก การปรับทิศทางที่ยอมรับ และการติดตามผลจะถูกพูดกลับในช่องเสียง เพื่อให้ผู้เรียกทราบว่า OpenClaw จัดการคำขอแล้วหรือไม่

รูปแบบเป้าหมายที่มีประโยชน์:

- `target: "channel:123456789012345678"` กำหนดเส้นทางผ่านเซสชันช่องข้อความ Discord
- `target: "123456789012345678"` จะถูกมองเป็นเป้าหมายช่อง
- `target: "dm:123456789012345678"` หรือ `target: "user:123456789012345678"` กำหนดเส้นทางผ่านเซสชันข้อความโดยตรงนั้น

ตัวอย่าง OpenAI Realtime สำหรับสภาพแวดล้อมที่มีเสียงสะท้อนมาก:

```json5
{
  channels: {
    discord: {
      voice: {
        enabled: true,
        mode: "bidi",
        model: "openai/gpt-5.6-sol",
        realtime: {
          provider: "openai",
          model: "gpt-realtime-2.1",
          speakerVoice: "cedar",
          bargeIn: true,
          minBargeInAudioEndMs: 500,
          consultPolicy: "always",
          providers: {
            openai: {
              interruptResponseOnInputAudio: false,
            },
          },
        },
      },
    },
  },
}
```

ใช้รูปแบบนี้เมื่อโมเดลได้ยินเสียงที่ Discord เล่นออกมาของตัวเองผ่านไมโครโฟนที่เปิดอยู่ แต่ยังต้องการให้สามารถพูดแทรกเพื่อขัดจังหวะได้ OpenClaw จะป้องกันไม่ให้ OpenAI ขัดจังหวะโดยอัตโนมัติจากเสียงอินพุตดิบ ขณะที่ `bargeIn: true` ช่วยให้เหตุการณ์เริ่มต้นของผู้พูดใน Discord และเสียงของผู้พูดที่ทำงานอยู่แล้วสามารถยกเลิกการตอบกลับแบบเรียลไทม์ที่กำลังทำงาน ก่อนที่รอบเสียงที่บันทึกถัดไปจะไปถึง OpenAI สัญญาณพูดแทรกที่เกิดเร็วมากโดยมี `audioEndMs` ต่ำกว่า `minBargeInAudioEndMs` จะถูกมองว่าอาจเป็นเสียงสะท้อนหรือสัญญาณรบกวนและถูกละเว้น เพื่อไม่ให้โมเดลหยุดพูดตั้งแต่เฟรมแรกที่เริ่มเล่น

บันทึกเสียงที่คาดว่าจะพบ:

- เมื่อเข้าร่วม: `discord voice: joining ... voiceSession=... supervisorSession=... agentSessionMode=... voiceModel=... realtimeModel=...`
- เมื่อเริ่มเรียลไทม์: `discord voice: realtime bridge starting ... autoRespond=false interruptResponse=false bargeIn=false minBargeInAudioEndMs=...`
- เมื่อมีเสียงจากผู้พูด: `discord voice: realtime speaker turn opened ...`, `discord voice: realtime input audio started ... outputAudioMs=... outputActive=...` และ `discord voice: realtime speaker turn closed ... chunks=... discordBytes=... realtimeBytes=... interruptedPlayback=...`
- เมื่อข้ามเสียงพูดที่ล้าสมัย: `discord voice: realtime forced agent consult skipped reason=incomplete-transcript ...` หรือ `reason=non-actionable-closing ...`
- เมื่อการตอบกลับแบบเรียลไทม์เสร็จสมบูรณ์: `discord voice: realtime audio playback finishing reason=response.done ... audioMs=... chunks=...`
- เมื่อหยุดหรือรีเซ็ตการเล่น: `discord voice: realtime audio playback stopped reason=... audioMs=... elapsedMs=... chunks=...`
- เมื่อปรึกษาแบบเรียลไทม์: `discord voice: realtime consult requested ... voiceSession=... supervisorSession=... question=...`
- เมื่อเอเจนต์ตอบ: `discord voice: agent turn answer ...`
- เมื่อจัดคิวเสียงพูดแบบตรงตามข้อความ: `discord voice: realtime exact speech queued ... queued=... outputAudioMs=... outputActive=...` ตามด้วย `discord voice: realtime exact speech dequeued reason=player-idle ...`
- เมื่อตรวจพบการพูดแทรก: `discord voice: realtime barge-in detected source=speaker-start ...` หรือ `discord voice: realtime barge-in detected source=active-speaker-audio ...` ตามด้วย `discord voice: realtime barge-in requested reason=... outputAudioMs=... outputActive=...`
- เมื่อขัดจังหวะแบบเรียลไทม์: `discord voice: realtime model interrupt requested client:response.cancel reason=barge-in` ตามด้วย `discord voice: realtime model audio truncated client:conversation.item.truncate reason=barge-in audioEndMs=...` หรือ `discord voice: realtime model interrupt confirmed server:response.done status=cancelled ...`
- เมื่อละเว้นเสียงสะท้อนหรือสัญญาณรบกวน: `discord voice: realtime model interrupt ignored client:conversation.item.truncate.skipped reason=barge-in audioEndMs=0 minAudioEndMs=250`
- เมื่อปิดใช้งานการพูดแทรก: `discord voice: realtime capture ignored during playback (barge-in disabled) ...`
- เมื่อการเล่นไม่มีการใช้งาน: `discord voice: realtime barge-in ignored reason=... outputActive=false ... playbackChunks=0`

หากต้องการแก้ไขข้อบกพร่องของเสียงที่ถูกตัด ให้อ่านบันทึกเสียงเรียลไทม์เป็นลำดับเวลา:

1. `realtime audio playback started` หมายความว่า Discord เริ่มเล่นเสียงของผู้ช่วยแล้ว บริดจ์จะเริ่มนับส่วนข้อมูลเอาต์พุตของผู้ช่วย จำนวนไบต์ PCM ของ Discord จำนวนไบต์เรียลไทม์ของผู้ให้บริการ และระยะเวลาเสียงที่สังเคราะห์ตั้งแต่จุดนี้
2. `realtime speaker turn opened` ระบุว่าผู้พูดใน Discord เริ่มทำงาน หากกำลังเล่นเสียงอยู่แล้วและเปิดใช้งาน `bargeIn` เหตุการณ์นี้อาจตามด้วย `barge-in detected source=speaker-start`
3. `realtime input audio started` ระบุเฟรมเสียงจริงเฟรมแรกที่ได้รับสำหรับรอบของผู้พูดนั้น การมี `outputActive=true` หรือ `outputAudioMs` ที่ไม่เป็นศูนย์ ณ จุดนี้หมายความว่าไมโครโฟนกำลังส่งอินพุตขณะที่ยังเล่นเสียงของผู้ช่วยอยู่
4. `barge-in detected source=active-speaker-audio` หมายความว่า OpenClaw ตรวจพบเสียงสดจากผู้พูดขณะที่กำลังเล่นเสียงของผู้ช่วย ซึ่งมีประโยชน์ในการแยกแยะการขัดจังหวะจริงออกจากเหตุการณ์เริ่มต้นของผู้พูดใน Discord ที่ไม่มีเสียงซึ่งใช้งานได้
5. `barge-in requested reason=...` หมายความว่า OpenClaw ขอให้ผู้ให้บริการเรียลไทม์ยกเลิกหรือตัดการตอบกลับที่กำลังทำงาน โดยมี `outputAudioMs`, `outputActive` และ `playbackChunks` รวมอยู่ด้วย เพื่อให้ดูได้ว่ามีการเล่นเสียงของผู้ช่วยจริงไปมากเพียงใดก่อนถูกขัดจังหวะ
6. `realtime audio playback stopped reason=...` คือจุดรีเซ็ตการเล่น Discord ภายในเครื่อง เหตุผลจะระบุว่าใครหยุดการเล่น: `barge-in`, `player-idle`, `provider-clear-audio`, `forced-agent-consult`, `stream-close` หรือ `session-close`
7. `realtime speaker turn closed` สรุปรอบอินพุตที่บันทึกไว้ `chunks=0` หรือ `hasAudio=false` หมายความว่ารอบของผู้พูดเริ่มขึ้น แต่ไม่มีเสียงที่ใช้งานได้ส่งถึงบริดจ์เรียลไทม์ `interruptedPlayback=true` หมายความว่ารอบอินพุตนั้นทับซ้อนกับเอาต์พุตของผู้ช่วยและกระตุ้นตรรกะการพูดแทรก

ฟิลด์ที่มีประโยชน์:

- `outputAudioMs`: ระยะเวลาเสียงของผู้ช่วยที่ผู้ให้บริการเรียลไทม์สร้างขึ้นก่อนบรรทัดบันทึกนี้
- `audioMs`: ระยะเวลาเสียงของผู้ช่วยที่ OpenClaw นับได้ก่อนหยุดการเล่น
- `elapsedMs`: เวลาตามนาฬิกาจริงระหว่างการเปิดและปิดสตรีมการเล่นหรือรอบของผู้พูด
- `discordBytes`: จำนวนไบต์ PCM สเตอริโอ 48 kHz ที่ส่งไปยังหรือรับจากเสียง Discord
- `realtimeBytes`: จำนวนไบต์ PCM ในรูปแบบของผู้ให้บริการที่ส่งไปยังหรือรับจากผู้ให้บริการเรียลไทม์
- `playbackChunks`: จำนวนส่วนข้อมูลเสียงของผู้ช่วยที่ส่งต่อไปยัง Discord สำหรับการตอบกลับที่กำลังทำงาน
- `sinceLastAudioMs`: ช่วงห่างระหว่างเฟรมเสียงล่าสุดของผู้พูดที่บันทึกได้กับการปิดรอบของผู้พูด

รูปแบบทั่วไป:

- การตัดเสียงทันทีร่วมกับ `source=active-speaker-audio` ค่า `outputAudioMs` ต่ำ และมีผู้ใช้คนเดิมอยู่ใกล้ ๆ มักบ่งชี้ว่าเสียงสะท้อนจากลำโพงเข้าสู่ไมโครโฟน ให้เพิ่ม `voice.realtime.minBargeInAudioEndMs` ลดระดับเสียงลำโพง ใช้หูฟัง หรือตั้งค่า `voice.realtime.providers.openai.interruptResponseOnInputAudio: false`
- `source=speaker-start` ตามด้วย `speaker turn closed ... hasAudio=false` หมายความว่า Discord รายงานว่าผู้พูดเริ่มพูด แต่ไม่มีเสียงส่งถึง OpenClaw สาเหตุอาจเป็นเหตุการณ์เสียงชั่วคราวของ Discord พฤติกรรมของตัวตัดสัญญาณรบกวน หรือไคลเอนต์เปิดไมโครโฟนชั่วขณะ
- `audio playback stopped reason=stream-close` ที่ไม่มีการพูดแทรกหรือ `provider-clear-audio` อยู่ใกล้เคียง หมายความว่าสตรีมการเล่นเสียง Discord ภายในเครื่องสิ้นสุดลงโดยไม่คาดคิด ให้ตรวจสอบบันทึกของผู้ให้บริการและโปรแกรมเล่น Discord ก่อนหน้า
- `capture ignored during playback (barge-in disabled)` หมายความว่า OpenClaw จงใจทิ้งอินพุตขณะที่เสียงของผู้ช่วยกำลังทำงาน เปิดใช้ `voice.realtime.bargeIn` หากต้องการให้เสียงพูดขัดจังหวะการเล่นเสียง
- `barge-in ignored ... outputActive=false` หมายความว่า Discord หรือ VAD ของผู้ให้บริการรายงานเสียงพูด แต่ OpenClaw ไม่มีการเล่นเสียงที่กำลังทำงานให้ขัดจังหวะ กรณีนี้ไม่ควรตัดเสียง

ข้อมูลรับรองจะได้รับการแก้ไขแยกตามคอมโพเนนต์ ได้แก่ การตรวจสอบสิทธิ์เส้นทาง LLM สำหรับ `voice.model` การตรวจสอบสิทธิ์ STT สำหรับ `tools.media.audio` การตรวจสอบสิทธิ์ TTS สำหรับ `messages.tts`/`voice.tts` และการตรวจสอบสิทธิ์ผู้ให้บริการแบบเรียลไทม์สำหรับ `voice.realtime.providers` หรือการกำหนดค่าการตรวจสอบสิทธิ์ปกติของผู้ให้บริการ

### ข้อความเสียง

ข้อความเสียงของ Discord จะแสดงตัวอย่างรูปคลื่นและต้องใช้เสียง OGG/Opus OpenClaw สร้างรูปคลื่นโดยอัตโนมัติ แต่ต้องมี `ffmpeg` และ `ffprobe` บนโฮสต์ Gateway เพื่อตรวจสอบและแปลงไฟล์

- ระบุ **พาธไฟล์ภายในเครื่อง** (ไม่ยอมรับ URL)
- ไม่ต้องระบุเนื้อหาข้อความ (Discord ไม่ยอมรับข้อความและข้อความเสียงในเพย์โหลดเดียวกัน)
- ยอมรับรูปแบบเสียงทุกประเภท OpenClaw จะแปลงเป็น OGG/Opus ตามความจำเป็น

```bash
message(action="send", channel="discord", target="channel:123", path="/path/to/audio.mp3", asVoice=true)
```

## การแก้ไขปัญหา

<AccordionGroup>
  <Accordion title="ใช้ Intent ที่ไม่อนุญาต หรือบอตไม่เห็นข้อความในกิลด์">

    - เปิดใช้ Message Content Intent
    - เปิดใช้ Server Members Intent เมื่อต้องใช้การระบุผู้ใช้/สมาชิก
    - รีสตาร์ต Gateway หลังเปลี่ยน Intent

  </Accordion>

  <Accordion title="ข้อความในกิลด์ถูกบล็อกโดยไม่คาดคิด">

    - ตรวจสอบ `groupPolicy`
    - ตรวจสอบรายการที่อนุญาตของกิลด์ภายใต้ `channels.discord.guilds`
    - หากมีแมป `channels` ของกิลด์ จะอนุญาตเฉพาะช่องที่ระบุไว้เท่านั้น
    - ตรวจสอบพฤติกรรมของ `requireMention` และรูปแบบการกล่าวถึง

    การตรวจสอบที่เป็นประโยชน์:

```bash
openclaw doctor
openclaw channels status --probe
openclaw logs --follow
```

  </Accordion>

  <Accordion title="ตั้งค่าไม่ให้ต้องกล่าวถึงแล้ว แต่ยังคงถูกบล็อก">
    สาเหตุที่พบบ่อย:

    - `groupPolicy="allowlist"` โดยไม่มีรายการที่อนุญาตของกิลด์/ช่องที่ตรงกัน
    - กำหนดค่า `requireMention` ผิดตำแหน่ง (ต้องอยู่ภายใต้ `channels.discord.guilds` หรือรายการของช่อง)
    - ผู้ส่งถูกบล็อกโดยรายการที่อนุญาต `users` ของกิลด์/ช่อง

  </Accordion>

  <Accordion title="รอบการทำงานของ Discord ใช้เวลานานหรือมีการตอบซ้ำ">

    บันทึกที่พบบ่อย:

    - `Slow listener detected ...`
    - `stuck session: sessionKey=agent:...:discord:... state=processing ...`

    Discord ไม่ใช้การหมดเวลาที่ช่องเป็นเจ้าของกับรอบการทำงานของเอเจนต์ที่อยู่ในคิว ตัวรับฟังข้อความจะส่งต่องานทันที และการทำงานของ Discord ที่อยู่ในคิวจะรักษาลำดับภายในแต่ละเซสชันจนกว่าวงจรชีวิตของเซสชัน/เครื่องมือ/รันไทม์จะเสร็จสมบูรณ์หรือยกเลิกงาน

  </Accordion>

  <Accordion title="คำเตือนการหมดเวลาขณะค้นหาข้อมูลเมตาของ Gateway">
    OpenClaw ดึงข้อมูลเมตา `/gateway/bot` ของ Discord ก่อนเชื่อมต่อ หากเกิดความล้มเหลวชั่วคราว ระบบจะใช้ URL Gateway เริ่มต้นของ Discord และจำกัดอัตราการบันทึก

    เวลาหมดอายุของข้อมูลเมตาเริ่มต้นอยู่ที่ 30 วินาที `OPENCLAW_DISCORD_GATEWAY_INFO_TIMEOUT_MS` สามารถแทนที่ค่านี้สำหรับสภาพแวดล้อมโฮสต์ที่ผิดปกติ

  </Accordion>

  <Accordion title="Gateway รีสตาร์ตเนื่องจากหมดเวลารอ READY">
    OpenClaw รอเหตุการณ์ `READY` ของ Gateway จาก Discord ระหว่างการเริ่มต้นและหลังจากรันไทม์เชื่อมต่อใหม่ การตั้งค่าหลายบัญชีที่หน่วงเวลาเริ่มต้นอาจต้องใช้ช่วงเวลารอ READY ระหว่างเริ่มต้นนานกว่าค่าเริ่มต้น

    การเริ่มต้นจะรอ 15 วินาที และการเชื่อมต่อใหม่ของรันไทม์จะรอ 30 วินาที `OPENCLAW_DISCORD_READY_TIMEOUT_MS` และ `OPENCLAW_DISCORD_RUNTIME_READY_TIMEOUT_MS` ยังคงใช้ได้สำหรับสภาพแวดล้อมโฮสต์ที่ผิดปกติ

  </Accordion>

  <Accordion title="ผลการตรวจสอบสิทธิ์ไม่ตรงกัน">
    การตรวจสอบสิทธิ์ `channels status --probe` ใช้ได้เฉพาะกับ ID ช่องที่เป็นตัวเลข

    หากใช้คีย์แบบ slug การจับคู่ขณะรันไทม์อาจยังทำงานได้ แต่การตรวจสอบไม่สามารถยืนยันสิทธิ์ได้อย่างสมบูรณ์

  </Accordion>

  <Accordion title="ปัญหา DM และการจับคู่">

    - ปิดใช้ DM: `channels.discord.dm.enabled=false`
    - ปิดใช้นโยบาย DM: `channels.discord.dmPolicy="disabled"` (แบบเดิม: `channels.discord.dm.policy`)
    - กำลังรอการอนุมัติการจับคู่ในโหมด `pairing`

  </Accordion>

  <Accordion title="ลูประหว่างบอต">
    โดยค่าเริ่มต้น ระบบจะละเว้นข้อความที่บอตเป็นผู้เขียน

    หากตั้งค่า `channels.discord.allowBots=true` ให้ใช้กฎการกล่าวถึงและรายการที่อนุญาตอย่างเข้มงวดเพื่อหลีกเลี่ยงพฤติกรรมวนซ้ำ
    แนะนำให้ใช้ `channels.discord.allowBots="mentions"` เพื่อยอมรับเฉพาะข้อความจากบอตที่กล่าวถึงบอตนี้เท่านั้น

    OpenClaw ยังมาพร้อมกับ [การป้องกันลูปของบอต](/th/channels/bot-loop-protection) แบบใช้ร่วมกัน เมื่อใดก็ตามที่ `allowBots` อนุญาตให้ข้อความที่บอตเป็นผู้เขียนส่งถึงการจัดส่ง Discord จะแมปเหตุการณ์ขาเข้าเป็นข้อเท็จจริง `(account, channel, bot pair)` และตัวป้องกันคู่ทั่วไปจะระงับคู่ดังกล่าวหลังจากใช้เกินงบประมาณเหตุการณ์ที่กำหนด ตัวป้องกันนี้ป้องกันลูปสองบอตที่ควบคุมไม่ได้ ซึ่งก่อนหน้านี้ต้องหยุดด้วยขีดจำกัดอัตราของ Discord โดยไม่ส่งผลต่อการปรับใช้บอตตัวเดียวหรือการตอบกลับของบอตแบบครั้งเดียวที่ยังไม่เกินงบประมาณ

    การตั้งค่าเริ่มต้น (ทำงานเมื่อตั้งค่า `allowBots`):

    - `maxEventsPerWindow: 20` -- คู่บอตสามารถแลกเปลี่ยนข้อความ 20 ข้อความภายในกรอบเวลาแบบเลื่อน
    - `windowSeconds: 60` -- ความยาวของกรอบเวลาแบบเลื่อน
    - `cooldownSeconds: 60` -- เมื่องบประมาณถึงขีดจำกัด ข้อความระหว่างบอตเพิ่มเติมทุกข้อความในทั้งสองทิศทางจะถูกทิ้งเป็นเวลาหนึ่งนาที

    กำหนดค่าเริ่มต้นแบบใช้ร่วมกันเพียงครั้งเดียวภายใต้ `channels.defaults.botLoopProtection` แล้วแทนที่ค่าของ Discord เมื่อเวิร์กโฟลว์ที่ถูกต้องต้องการขีดจำกัดที่มากขึ้น ลำดับความสำคัญคือ:

    - `channels.discord.accounts.<account>.botLoopProtection`
    - `channels.discord.botLoopProtection`
    - `channels.defaults.botLoopProtection`
    - ค่าเริ่มต้นในตัว

    Discord ใช้คีย์ทั่วไป `maxEventsPerWindow`, `windowSeconds` และ `cooldownSeconds`

```json5
{
  channels: {
    defaults: {
      botLoopProtection: {
        maxEventsPerWindow: 20,
        windowSeconds: 60,
        cooldownSeconds: 60,
      },
    },
    discord: {
      // ตัวเลือกสำหรับแทนที่ค่าทั่วทั้ง Discord บล็อกบัญชีจะแทนที่แต่ละฟิลด์
      // และสืบทอดฟิลด์ที่ไม่ได้ระบุจากที่นี่
      botLoopProtection: {
        maxEventsPerWindow: 4,
      },
      accounts: {
        alpha: {
          // Alpha รับฟังบอตอื่นเฉพาะเมื่อบอตเหล่านั้นกล่าวถึง Alpha
          allowBots: "mentions",
        },
        bravo: {
          // Bravo รับฟังข้อความ Discord ทั้งหมดที่บอตเป็นผู้เขียน
          allowBots: true,
          mentionAliases: {
            // อนุญาตให้ Bravo เขียนการกล่าวถึง Alpha บน Discord ด้วย ID ผู้ใช้ที่กำหนดค่าไว้
            Alpha: "ALPHA_DISCORD_USER_ID",
          },
          botLoopProtection: {
            // อนุญาตสูงสุดห้าข้อความต่อนาทีก่อนระงับคู่บอต
            maxEventsPerWindow: 5,
            windowSeconds: 60,
            cooldownSeconds: 90,
          },
        },
      },
    },
  },
}
```

  </Accordion>

  <Accordion title="เสียง STT หลุดพร้อม DecryptionFailed(...)">

    - อัปเดต OpenClaw ให้เป็นเวอร์ชันปัจจุบัน (`openclaw update`) เพื่อให้มีตรรกะการกู้คืนการรับเสียงของ Discord
    - ยืนยัน `channels.discord.voice.daveEncryption=true` (ค่าเริ่มต้น)
    - เริ่มจาก `channels.discord.voice.decryptionFailureTolerance=24` (ค่าเริ่มต้นจากต้นทาง) และปรับแต่งเมื่อจำเป็นเท่านั้น
    - ตรวจสอบบันทึกเพื่อหา:
      - `discord voice: DAVE decrypt failures detected`
      - `discord voice: repeated decrypt failures; attempting rejoin`
    - หากยังคงเกิดความล้มเหลวหลังจากเข้าร่วมใหม่โดยอัตโนมัติ ให้รวบรวมบันทึกและเปรียบเทียบกับประวัติการรับ DAVE จากต้นทางใน [discord.js #11419](https://github.com/discordjs/discord.js/issues/11419) และ [discord.js #11449](https://github.com/discordjs/discord.js/pull/11449)

  </Accordion>
</AccordionGroup>

## ข้อมูลอ้างอิงการกำหนดค่า

ข้อมูลอ้างอิงหลัก: [ข้อมูลอ้างอิงการกำหนดค่า - Discord](/th/gateway/config-channels#discord)

<Accordion title="ฟิลด์ Discord ที่มีข้อมูลสำคัญ">

- การเริ่มต้น/การตรวจสอบสิทธิ์: `enabled`, `token`, `applicationId`, `accounts.*`, `allowBots`
- นโยบาย: `groupPolicy`, `dmPolicy`, `allowFrom`, `dm.*`, `guilds.*`, `guilds.*.channels.*`
- คำสั่ง: `commands.native`, `commands.useAccessGroups` (ส่วนกลาง), `configWrites`, `slashCommand.ephemeral`
- Gateway: `proxy`
- การตอบกลับ/ประวัติ: `replyToMode`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
- การส่ง: `textChunkLimit` (ค่าเริ่มต้น `2000`), `maxLinesPerMessage` (ค่าเริ่มต้น `17`)
- การสตรีม: `streaming.mode`, `streaming.chunkMode`, `streaming.preview.*`, `streaming.progress.*`, `streaming.block.*` (คีย์แบบแบนเดิม `streamMode`, `draftChunk`, `blockStreaming`, `blockStreamingCoalesce`, `chunkMode` จะถูกย้ายไปยัง `streaming.*` โดย `openclaw doctor --fix`)
- สื่อ: `mediaMaxMb` (จำกัดการอัปโหลดขาออกของ Discord ค่าเริ่มต้น `100`)
- การดำเนินการ: `actions.*`
- สถานะการแสดงตน: `activity`, `status`, `activityType`, `activityUrl`, `autoPresence.*`
- UI: `ui.components.accentColor`
- ฟีเจอร์: `threadBindings`, `bindings[]` ระดับบนสุด (`type: "acp"`), `pluralkit`, `execApprovals`, `intents`, `agentComponents.enabled`, `agentComponents.ttlMs`, `activities`, `heartbeat`, `responsePrefix`

</Accordion>

### Discord Activities

ตั้งค่า `channels.discord.activities` เพื่อให้เอเจนต์โพสต์วิดเจ็ต HTML แบบสมบูรณ์ในตัวที่เปิดภายใน Discord ได้ บล็อกนี้ต้องเลือกเปิดใช้ เมื่อไม่มีบล็อกนี้ OpenClaw จะไม่ลงทะเบียนเส้นทาง Activity เครื่องมือ หรือตัวจัดการการโต้ตอบ โปรดดู [Discord Activities](/th/channels/discord-activities) สำหรับการตั้งค่า Developer Portal, ทันเนล, ความปลอดภัย และการแก้ไขปัญหา

- `activities.clientSecret`: ข้อมูลลับไคลเอนต์ OAuth2 สำหรับแอปพลิเคชัน Discord หากไม่มีจะใช้ `DISCORD_CLIENT_SECRET`
- `activities.applicationId`: ID แอปพลิเคชัน Activity ที่ระบุหรือไม่ก็ได้ ค่าเริ่มต้นคือ ID แอปพลิเคชันบอตที่ได้รับระหว่างการเริ่มต้น Gateway

## ความปลอดภัยและการปฏิบัติการ

- ถือว่าโทเค็นบอตเป็นข้อมูลลับ (แนะนำให้ใช้ `DISCORD_BOT_TOKEN` ในสภาพแวดล้อมที่มีการควบคุมดูแล)
- ให้สิทธิ์ Discord เท่าที่จำเป็น
- หากสถานะการปรับใช้/คำสั่งล้าสมัย ให้รีสตาร์ต Gateway และตรวจสอบอีกครั้งด้วย `openclaw channels status --probe`

## ที่เกี่ยวข้อง

<CardGroup cols={2}>
  <Card title="Discord Activities" icon="window" href="/th/channels/discord-activities">
    เปิดวิดเจ็ต HTML แบบโต้ตอบภายใน Discord
  </Card>
  <Card title="การจับคู่" icon="link" href="/th/channels/pairing">
    จับคู่ผู้ใช้ Discord กับ Gateway
  </Card>
  <Card title="กลุ่ม" icon="users" href="/th/channels/groups">
    พฤติกรรมการแชตกลุ่มและรายการที่อนุญาต
  </Card>
  <Card title="การกำหนดเส้นทางช่อง" icon="route" href="/th/channels/channel-routing">
    กำหนดเส้นทางข้อความขาเข้าไปยังเอเจนต์
  </Card>
  <Card title="ความปลอดภัย" icon="shield" href="/th/gateway/security">
    แบบจำลองภัยคุกคามและการเสริมความแข็งแกร่ง
  </Card>
  <Card title="การกำหนดเส้นทางหลายเอเจนต์" icon="sitemap" href="/th/concepts/multi-agent">
    แมปกิลด์และช่องไปยังเอเจนต์
  </Card>
  <Card title="คำสั่งแบบสแลช" icon="terminal" href="/th/tools/slash-commands">
    พฤติกรรมคำสั่งแบบเนทีฟ
  </Card>
</CardGroup>
