---
read_when:
    - การพัฒนาหรือรีวิวฟีเจอร์แดชบอร์ดเซสชัน (บอร์ด)
    - การเปลี่ยนโฮสติ้งวิดเจ็ต บริดจ์วิดเจ็ต หรือพื้นที่จัดเก็บบอร์ด
summary: 'แดชบอร์ดเซสชัน: สถาปัตยกรรมและแผนการนำไปใช้งาน (การออกแบบทางเทคนิค ก่อน GA)'
title: สถาปัตยกรรมแดชบอร์ด
x-i18n:
    generated_at: "2026-07-21T15:27:06Z"
    model: gpt-5.6
    postprocess_version: locale-links-v1
    prompt_version: 32
    provider: openai
    source_hash: a7c5da94ec19add55c6b7b530f0c17509a027e97fb301469ce48f520b325c169
    source_path: web/dashboard-architecture.md
    workflow: 16
---

<Note>
เอกสารการออกแบบทางเทคนิคสำหรับฟีเจอร์แดชบอร์ดเซสชัน ซึ่งเขียนขึ้นก่อนและ
ระหว่างการนำไปใช้ เอกสารนี้เป็นแหล่งข้อมูลอ้างอิงหลักสำหรับการพัฒนา เมื่อ
ฟีเจอร์เผยแพร่แล้ว `/web/dashboard` จะกลายเป็นหน้าสำหรับผู้ใช้ และหน้านี้จะยังคง
เป็นเอกสารอ้างอิงด้านสถาปัตยกรรม
</Note>

## วิสัยทัศน์

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

หลักการ:

- **บอร์ดเป็นมุมมองหนึ่งของเซสชัน ไม่ใช่ออบเจ็กต์ใหม่** ทุกเซสชัน (เธรด)
  มีสองมุมมอง: บันทึกการสนทนาและบอร์ด เซสชันที่ไม่มีวิดเจ็ตที่ปักหมุดไว้
  จะเป็นแชตธรรมดา เมื่อปักหมุดวิดเจ็ตหนึ่งรายการ บอร์ดก็จะเกิดขึ้น บอร์ดสืบทอด
  ข้อมูลระบุตัวตน ความเป็นเจ้าของโดยเอเจนต์ การตั้งชื่อ การปักหมุด และวงจรชีวิตของ
  เซสชัน ไม่มี `dashboard_create` ไม่มีรีจิสทรีของบอร์ด และไม่มีโมเดล ACL แยกต่างหาก
- **ความเท่าเทียมของเอเจนต์** ทุกสิ่งที่ผู้ใช้ทำบนบอร์ดได้ เอเจนต์ก็ทำได้
  ด้วยเครื่องมือ: เพิ่ม/อัปเดต/ลบวิดเจ็ต จัดเรียงวิดเจ็ต จัดการแท็บ สลับ
  แท็บที่มองเห็น และเชื่อมติดหรือซ่อนแชต
- **เป็นเนทีฟ ไม่ใช่แบบฝัง** บอร์ดประกอบด้วยคอมโพเนนต์ Lit ในเชลล์ Control UI
  (ระบบออกแบบเดียวกับส่วนอื่นของแอป) เฉพาะ_เนื้อหา_ของวิดเจ็ตเท่านั้นที่
  ถูกแซนด์บ็อกซ์ใน iframe ไม่มีแถบ URL และไม่มีส่วนควบคุมของเบราว์เซอร์
- **พื้นผิวสำหรับเอเจนต์ขนาดเล็ก** วิดเจ็ตอ้างอิงด้วยชื่อที่เสถียรและอัปเดต
  ในตำแหน่งเดิม เลย์เอาต์เป็นกริดแบบยืดหยุ่นที่จัดพื้นที่ให้กระชับโดยอัตโนมัติ เอเจนต์ระบุขนาดและ
  จุดยึดเท่านั้น ไม่ระบุพิกเซลหรือพิกัด
- **ความสามารถสำคัญกว่าความไว้วางใจ** โค้ดวิดเจ็ตคือ HTML/JS ใดๆ ที่เอเจนต์เขียน
  และทำงานในแซนด์บ็อกซ์ที่เข้มงวด การเข้าถึง (ข้อมูล Gateway การดำเนินการ เครือข่าย) มีได้เฉพาะผ่าน
  รายการความสามารถที่ประกาศไว้และได้รับอนุญาตจากผู้ดำเนินการเท่านั้น

## แนวคิด

| แนวคิด             | คำจำกัดความ                                                                                                                                                        |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| เซสชัน (เธรด)    | เซสชัน Gateway ที่มีอยู่ ซึ่งใช้ `sessionKey` ที่เสถียรเป็นคีย์ และมีเอเจนต์เป็นเจ้าของ                                                                                        |
| บอร์ด               | มุมมองวิดเจ็ตของหนึ่งเซสชัน มีอยู่ก็ต่อเมื่อเซสชันมีวิดเจ็ต/แท็บ ยังคงอยู่หลัง `/new`/`/reset` (ผูกกับ `sessionKey` ไม่ใช่บันทึกการสนทนา)                 |
| แท็บ                 | หน้านำเสนอของบอร์ด: วิดเจ็ตที่แสดง การจัดเรียง และสถานะการเชื่อมติดแชต (`left`/`right`/`bottom`/`hidden`) บอร์ดเริ่มต้นด้วยแท็บโดยนัยหนึ่งแท็บ |
| วิดเจ็ต              | โปรแกรม HTML/JS ที่มีชื่อและทำงานในแซนด์บ็อกซ์ ซึ่งเซสชันเป็นเจ้าของ อ้างอิงด้วย `sessionKey` + `name` และอัปเดตในตำแหน่งเดิมตามชื่อ                                              |
| รายการความสามารถ | การประกาศขอบเขตการเข้าถึงของแต่ละวิดเจ็ต: `data` (การผูกข้อมูลแบบอ่าน), `actions` (กริยาที่อยู่ในรายการอนุญาต), `prompt` (ส่งไปยังเซสชัน), `net` (ต้นทางที่อนุญาต)                      |
| ปักหมุด (วิดเจ็ต)        | การย้ายวิดเจ็ตจากบันทึกการสนทนาไปยังบอร์ดของเซสชัน (ผ่านส่วนควบคุมสำหรับผู้ใช้หรืออาร์กิวเมนต์เครื่องมือของเอเจนต์) การถอนหมุดจะนำวิดเจ็ตออกจากบอร์ด                                         |
| ปักหมุด (เซสชัน)       | การปักหมุดเซสชันในแถบด้านข้างที่มีอยู่ เซสชันที่ปักหมุดและมีบอร์ดจะเปิดด้วยมุมมองบอร์ด                                                                      |

## ขั้นตอนการใช้งาน UX

- **การยกระดับ:** เอเจนต์เรียก `show_widget` ในแชตใดๆ → วิดเจ็ตแสดงแบบอินไลน์
  ในบันทึกการสนทนาเหมือนในปัจจุบันทุกประการ → เมื่อวางเมาส์เหนือจะแสดง **ปักหมุดไปยังแดชบอร์ด** → วิดเจ็ต
  ปรากฏบนบอร์ดของเซสชัน เอเจนต์สามารถส่ง `pin: true` เพื่อทำสิ่งเดียวกันได้
- **มุมมองบอร์ด:** เซสชันที่มีบอร์ดจะมีตัวสลับมุมมอง (แชต / แดชบอร์ด)
  มุมมองบอร์ด = แถบแท็บ (เฉพาะเมื่อมีมากกว่า 1 แท็บ) + กริดแบบยืดหยุ่น + บานหน้าต่างแชตที่เชื่อมติดไว้
  ส่วนเชื่อมติดแชตสามารถปรับขนาด ย้ายตำแหน่ง (ซ้าย/ขวา/ล่าง) และยุบได้เหมือน
  แถบด้านข้างทุกประการ ระบบจะจดจำสถานะส่วนเชื่อมติดของแต่ละแท็บ
- **การลาก:** ผู้ใช้ลากวิดเจ็ต กริดจะจัดพื้นที่ให้กระชับโดยอัตโนมัติ (วิดเจ็ตเลื่อนขึ้นและวิดเจ็ตข้างเคียง
  จัดเรียงใหม่) การปรับขนาดด้วยจุดจับจะยึดตามระดับขนาดที่กำหนด ไม่มีการวางตำแหน่งเป็นพิกเซล —
  สำหรับทุกฝ่าย
- **คำเตือนการรีเซ็ต:** `/new` / `/reset` ในเซสชันที่มีบอร์ดจะขอ
  การยืนยันในเว็บ UI ("บริบทจะถูกรีเซ็ต แต่แดชบอร์ดยังคงอยู่") และคง
  บอร์ดไว้
- **แถบด้านข้าง:** เซสชันที่ปักหมุดจะแสดงมุมมองบอร์ดเมื่อมีบอร์ด
  บอร์ดของเซสชันหน้าแรกคือ "แดชบอร์ดเอเจนต์" เริ่มต้น
- **การโต้ตอบ** (สามระดับ ดูด้านล่าง): เหตุการณ์สถานะแบบเงียบ การส่ง
  พรอมต์ที่มองเห็นได้ และทริกเกอร์ระบบอัตโนมัติ

## ระดับการโต้ตอบ

1. **เหตุการณ์สถานะ (ค่าเริ่มต้น)** การโต้ตอบกับ UI ของวิดเจ็ตที่โมเดลควรรับรู้
   แต่ไม่ต้องตอบสนอง `bridge.emitState({...})` จะเพิ่มประกาศของ
   เซสชันแบบมีโครงสร้าง (ใช้กลไกเดียวกับประกาศกิจกรรมกลุ่ม) โดยไม่เริ่มรอบการทำงานของ
   เอเจนต์ โมเดลจะเห็นประกาศที่สะสมไว้ในการทำงานครั้งถัดไป
2. **พรอมต์ (การสนทนาโดยชัดแจ้ง)** `bridge.sendPrompt(text)` — ต้องมีการ
   กระตุ้นโดยผู้ใช้ ส่งข้อความผู้ใช้ที่มองเห็นได้ไปยังเซสชัน (แชตที่เชื่อมติดไว้
   จะแสดงข้อความนั้น) มีการจำกัดอัตรา และการส่งแต่ละครั้งต้องได้รับการยืนยันจากผู้ใช้ เว้นแต่ว่าวิดเจ็ตจะได้รับ
   สิทธิ์ความสามารถ `prompt`
3. **ระบบอัตโนมัติ** `bridge.runAction(name, args)` — เรียกใช้การดำเนินการที่ประกาศไว้ในรายการ
   ความสามารถ ชุดกริยาเริ่มต้น: `cron.trigger` (เรียกใช้งาน Cron ที่มีอยู่ทันที) และ
   `binding.refresh` งาน Cron ทำงานในเซสชันการทำงานแบบแยกที่มองเห็นได้อยู่แล้ว
   และสามารถใช้โมเดลที่มีต้นทุนต่ำกว่าได้ ซึ่งเป็นแนวทาง "โมเดลขนาดเล็กขับเคลื่อนวิดเจ็ต"
   ไม่มีเซสชันที่ซ่อนอยู่ไม่ว่าที่ใด

## โมเดลและการโฮสต์วิดเจ็ต

HTML/JS ของวิดเจ็ตเขียนโดยเอเจนต์ (โดยทั่วไปผ่าน `show_widget`) ถูกครอบ
ด้วยเชลล์เอกสารมาตรฐาน (เมตา CSP ตัวรายงานขนาด และตัวเริ่มต้นบริดจ์) และ
แสดงใน `<iframe sandbox="allow-scripts">` (ไม่ใช้ `allow-same-origin` เด็ดขาด)

- **วิดเจ็ตแบบอินไลน์ (บันทึกการสนทนา)** ยังคงใช้ไปป์ไลน์เอกสารแคนวาสในปัจจุบัน:
  เขียนไว้ใต้ไดเรกทอรีสถานะ ให้บริการโดย Gateway ตัดทิ้งตามขอบเขต และไม่ต้อง
  ขออนุมัติ (โดยโครงสร้างแล้วไม่มีความสามารถ — การส่งพรอมต์ต้องได้รับการยืนยันจากผู้ใช้)
- **วิดเจ็ตบนบอร์ด** เป็นสถานะเซสชัน: ไบต์อยู่ในฐานข้อมูล SQLite
  ของเอเจนต์เจ้าของ (`board_widgets`) และให้บริการผ่านเส้นทาง Gateway หลัก
  (`/__openclaw__/board/<agentId>/<sessionKey>/<name>/`) ที่อ่านฐานข้อมูล
  การปักหมุดวิดเจ็ตจากบันทึกการสนทนาจะคัดลอกไบต์ ขีดจำกัด: 256 KB ต่อวิดเจ็ต
  และ 48 วิดเจ็ตต่อบอร์ด
- **อัปเดตในตำแหน่งเดิม:** การส่งวิดเจ็ตที่มี `name` เดิมอีกครั้งจะแทนที่
  ไบต์ เพิ่ม `revision` กระจาย `board.changed` และมุมมองที่เปิดอยู่จะโหลดใหม่
  เฉพาะ iframe นั้น
- **การตรึงไบต์:** ความสามารถที่ได้รับอนุญาตจะผูกกับ sha256 ของไบต์
  วิดเจ็ต การเปลี่ยนไบต์จะคงสิทธิ์ `data`/`net`/`actions` ไว้เฉพาะเมื่อ
  รีวิชันใหม่ประกาศรายการความสามารถที่เป็นเซตย่อยของรายการที่ได้รับอนุญาต รายการที่ขยายกว้างขึ้น
  จะขออนุมัติจากผู้ดำเนินการอีกครั้ง

### วิดเจ็ตโฮสต์เนื้อหา ส่วนแอป MCP เป็นเนื้อหาประเภทหนึ่ง

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

- `html` — เอเจนต์เขียนผ่าน `show_widget` โดยไบต์อยู่ในพื้นที่จัดเก็บของบอร์ด
- `mcp-app` — มุมมองแอป MCP ของบุคคลที่สาม (ทรัพยากร `ui://` จากเซิร์ฟเวอร์ที่กำหนดค่าไว้)
  ซึ่งโฮสต์อยู่ภายในเซลล์วิดเจ็ต

แอป MCP ไม่ได้กำหนดโมเดลวิดเจ็ต แต่วิดเจ็ตได้รับความสามารถในการโฮสต์
แอปเหล่านั้น ข้อมูลระบุตัวตน ตำแหน่ง การปักหมุด สิทธิ์ และ API สำหรับผู้เขียนยังคง
เป็นของ OpenClaw ดังนั้นโค้ด `show_widget` จึงยังคงสั้นเหมือนในปัจจุบัน และไม่จำเป็นต้อง
รู้ว่าข้อกำหนด MCP Apps มีอยู่

โครงสร้างพื้นฐานที่ใช้ร่วมกันด้านล่าง (นี่คือจุดที่เกิดการทำให้ง่ายขึ้น):

- **โฮสต์แซนด์บ็อกซ์หนึ่งเดียว** วิดเจ็ต `html` แสดงผลผ่านไปป์ไลน์ที่เสริมความแข็งแกร่ง
  เดียวกับที่แอป MCP ใช้เมื่อเผยแพร่ (iframe สองชั้นบนต้นทางแซนด์บ็อกซ์เฉพาะ
  โดยประกาศ CSP ต่อวิดเจ็ตและถอดรหัสแบบปิดกั้นเมื่อผิดพลาด) แทนที่จะใช้โฮสต์ iframe
  เฉพาะกิจชุดที่สอง พร็อกซีรับ HTML เป็นค่า ดังนั้นเนื้อหาในเครื่องจึงเป็น
  กรณีใช้งานตามธรรมชาติ
- **โมเดลการอนุญาตหนึ่งเดียว** ขอบเขตการเข้าถึงของวิดเจ็ตคือรายการอนุญาตที่ได้รับสิทธิ์
  ไม่ว่าจะเป็นชนิดใด: สำหรับวิดเจ็ต `html` คือเครื่องมือโฮสต์ ส่วนวิดเจ็ต `mcp-app`
  คือเครื่องมือที่แอปมองเห็นจากเซิร์ฟเวอร์ (ผ่านกลไก `allowedAppToolNames`
  ที่มีอยู่ โดยทำให้คงทนต่อวิดเจ็ตแทนที่จะเป็นต่อรอบที่สร้างวิดเจ็ต)
- **เครื่องมือโฮสต์สำหรับวิดเจ็ต `html`** (เปิดให้ใช้ผ่านบริดจ์วิดเจ็ตและตรวจสอบ
  เทียบกับสิทธิ์):
  - `openclaw.prompt.send` — ระดับ 2 ส่งผ่านตัวเขียนข้อความที่มองเห็นได้
    และต้องได้รับการยืนยันจากผู้ใช้ เว้นแต่ได้รับสิทธิ์แล้ว
  - `openclaw.state.emit` — ประกาศเซสชันระดับ 1 (รวมรายการและจำกัดขนาด)
  - `openclaw.data.read` — การผูกข้อมูลแบบอ่านอย่างเดียวที่กำหนดพารามิเตอร์ได้ (ชุด RPC
    แบบอ่านที่อยู่ในรายการอนุญาตเดิม) โดยแก้ค่าที่ฝั่ง Gateway
  - `openclaw.cron.trigger` — ระบบอัตโนมัติระดับ 3
- **`net` = CSP** การเข้าถึงเครือข่ายใช้การประกาศ CSP ต่อวิดเจ็ต
  ที่เผยแพร่แล้ว (`connect-src` ต้นทาง) วิดเจ็ตสภาพอากาศที่อัปเดตตัวเอง
  จะดึงข้อมูลจาก API โดยตรงจากแซนด์บ็อกซ์ โดยไม่เกี่ยวข้องกับ Gateway
- **สิทธิ์** วิดเจ็ตที่ไม่ประกาศสิ่งใดจะแสดงผลทันที (อยู่ในแซนด์บ็อกซ์
  `default-src 'none'` และการส่งพรอมต์แต่ละครั้งต้องได้รับการยืนยัน) ซึ่งมีระดับความไว้วางใจเดียวกับ
  วิดเจ็ตแชตแบบอินไลน์ในปัจจุบัน เครื่องมือ/ต้นทางที่ประกาศไว้จะทำให้วิดเจ็ตอยู่ใน
  `pending` บนบอร์ด: การ์ดตัวยึดตำแหน่งจะแสดงรายการเหล่านั้นเป็นภาษาที่อ่านเข้าใจได้ พร้อม
  **อนุญาต**/**ปฏิเสธ** ด้วยการแตะครั้งเดียว สิทธิ์กำหนดตามชื่อวิดเจ็ต สำหรับวิดเจ็ต `html`
  สิทธิ์จะถูกตรึงตามไบต์ (sha256) และไบต์ที่เปลี่ยนแปลงจะคงสิทธิ์ไว้เฉพาะเมื่อ
  การประกาศมีขอบเขตแคบลง
- **ชิมสำหรับผู้เขียน** ตัวครอบเอกสารจะแทรก `window.openclaw.prompt`,
  `window.openclaw.state`, `window.openclaw.data` และ `window.openclaw.cron`
  เป็น API สำหรับผู้เขียนที่เสถียร การเรียกใช้แดชบอร์ดใช้ช่องทางคำขอ
  ที่ผูกกับทิกเก็ตมุมมองร่วมกันหนึ่งช่อง ส่วนการรายงานขนาดและโทเค็นธีมยังคงเป็นการแจ้งเตือน
  จากโฮสต์ที่แยกต่างหาก

### การประกาศความสามารถของ Plugin

Plugin ที่เปิดใช้งานสามารถขยายโฮสต์วิดเจ็ตผ่าน `dashboard.dataBindings`
และ `dashboard.actionVerbs` ใน `openclaw.plugin.json` ID ภายใน Plugin จะกลายเป็น
ชื่อสิทธิ์ที่นำหน้าด้วย ID ของ Plugin เช่น `workboard.cards.list` และ
`workboard.dispatch`; `%` และ `.` ในส่วน ID ของ Plugin จะถูกเอสเคป เพื่อไม่ให้
การแบ่ง Plugin/ID ภายในแบบอื่นสืบทอดสิทธิ์ที่จัดเก็บไว้เดียวกัน ระหว่าง
การลงทะเบียน Plugin OpenClaw จะตรวจสอบว่าการผูกข้อมูลทุกรายการกำหนดเป้าหมายไปยัง RPC
ที่ลงทะเบียนโดย Plugin เดียวกันด้วย `operator.read` และการดำเนินการทุกรายการกำหนดเป้าหมายไปยัง RPC
ที่ลงทะเบียนด้วย `operator.write`; การประกาศที่ไม่ถูกต้องจะทำให้การโหลด Plugin ล้มเหลว รีจิสทรีที่ผ่านการตรวจสอบ
จะสร้างใหม่เฉพาะเมื่อวงจรชีวิตของ Plugin เปลี่ยนแปลง ขณะที่สิทธิ์ของวิดเจ็ต
ยังคงกำหนดต่อวิดเจ็ตและผูกกับไบต์และรีวิชัน

### ส่วนตกค้างที่อยู่ในแบบจำลอง: ช่องทางข้อมูล WebRTC

CSP ของแซนด์บ็อกซ์จะส่งไดเรกทีฟ `webrtc 'block'` ที่เสนอไว้ แต่
[ชุดไดเรกทีฟ CSP ปัจจุบันของ Chromium](https://chromium.googlesource.com/chromium/src/+/main/services/network/public/mojom/content_security_policy.mojom#95)
ยังไม่รองรับ วิดเจ็ตที่เรียกใช้สคริปต์ได้จึงสามารถใช้ช่องทางข้อมูล WebRTC
เพื่อส่งข้อมูลออกใน Chromium ปัจจุบันได้ ส่วนตกค้างเดียวกันนี้เผยแพร่อยู่แล้วสำหรับ
วิดเจ็ตแชตแบบอินไลน์และโฮสต์ MCP Apps บน `main`

**ข้อแลกเปลี่ยนที่ยอมรับ:** OpenClaw ไม่ได้ใช้ค่าคงเหลือนี้เป็นเงื่อนไขกั้นวิดเจ็ตที่เขียนสคริปต์ได้ เนื้อหาวิดเจ็ตจะเข้าถึงข้อมูลสำคัญของ OpenClaw ได้เฉพาะผ่านความสามารถ `data:read` ที่ผู้ดำเนินการอนุมัติและตรึงตามไบต์ไว้เท่านั้น และ Permissions Policy ของแซนด์บ็อกซ์จะบล็อกการเข้าถึงกล้องและไมโครโฟน ตัวป้องกัน DOM API เป็นการป้องกันเชิงลึกแบบพยายามอย่างเต็มที่ ไม่ใช่ขอบเขตความปลอดภัย และควรอยู่ในงานเสริมความแข็งแกร่งที่ทำต่อไป

### การแสดงผลบทสนทนา: การ์ดวิดเจ็ตหนึ่งใบ

การแสดงผลแบบอินไลน์รวมเป็นหนึ่งเดียวบนองค์ประกอบพื้นฐานของวิดเจ็ต เมื่อผลลัพธ์ของเครื่องมือมี UI — เอาต์พุต `show_widget` หรือผลลัพธ์เครื่องมือ MCP ที่มีทรัพยากรแอป — ระบบจะสร้าง **วิดเจ็ตชั่วคราวที่ตั้งชื่อให้อัตโนมัติ** (จำกัดขอบเขตตามเซสชันและถูกล้างออก) และบทสนทนาจะแสดงการ์ดวิดเจ็ตเพียงใบเดียวซึ่งเลือกการทำงานตามชนิดเนื้อหา การแสดงแอป MCP อัตโนมัติยังคงเป็นไปตามที่ข้อกำหนดคาดไว้อย่างสมบูรณ์ (ไม่มีงานเพิ่มเติมจากโมเดล) เพียงแต่เบื้องหลัง _คือ_ วิดเจ็ต วิธีนี้ลบกรณีพิเศษ `mcpApp` แบบคู่ขนานในการเรนเดอร์แชต (การกั้นตามพื้นผิวและการขจัดรายการซ้ำแยกต่างหาก) ทำให้ UI แบบอินไลน์ทั้งหมดมีตัวเลือกปักหมุดแบบเดียวกัน และทำให้รีจิสทรีวิดเจ็ตเป็นเส้นทางหลักสำหรับเปิดอีกครั้ง (การสร้างใหม่ด้วยการสแกนบทสนทนายังคงเป็นทางเลือกสำรองสำหรับประวัติที่ไม่เคยปักหมุด) โฮสต์แบบสแตนด์อโลนที่อ่านได้อย่างเดียวและผูกกับตั๋วมีส่วนทับซ้อนกับบอร์ดในฐานะพื้นผิวถาวรสำหรับเปิดอีกครั้ง — เป็นตัวเลือกสำหรับการรวมระบบที่ต้องประเมินใน T6 ไม่ใช่ข้อสรุปที่ตั้งสมมติฐานไว้

การจัดองค์ประกอบ: v1 ใช้การวางติดกันในกริด (วิดเจ็ตกรอบควบคุมของเอเจนต์อยู่ข้างวิดเจ็ตแอปในแท็บเดียวกัน) v2 เพิ่ม **สล็อตแอปที่โฮสต์จัดการ** — HTML ของวิดเจ็ตเอเจนต์ประกาศบริเวณสล็อต และโฮสต์ประกอบมุมมองแอปจริงเป็นแซนด์บ็อกซ์ระดับพี่น้อง แอปจะไม่เรนเดอร์ภายใน iframe ของเอเจนต์ เพราะการซ้อนกันจะทำลายอัตลักษณ์ของบริดจ์และทำให้สามารถวางโอเวอร์เลย์/ทำ clickjacking กับ UI ของแอปที่ได้รับสิทธิ์ได้ ดังนั้นสล็อตจึงเป็นสัญญาการจัดวาง ไม่ใช่การฝัง

### วิดเจ็ตที่มาจากเซิร์ฟเวอร์ (แอป MCP ที่ปักหมุด)

ด้วยโฮสต์แบบรวมศูนย์ การปักหมุดแอป MCP ของบุคคลที่สามเป็นเพียงวิดเจ็ตที่ดึงเนื้อหาจากเซิร์ฟเวอร์แทนการจัดเก็บ: `board_widgets` เก็บตัวบรรยาย (`serverName`, `toolName`, `uiResourceUri`, `toolCallId` + `sessionKey` ต้นทาง) แทนไบต์ HTML และบอร์ดจะออกสัญญาเช่ามุมมองใหม่เมื่อพ้น TTL 10 นาทีของรอบแชต (ดึงทรัพยากร `ui://` อีกครั้งเมื่อข้อมูลเก่า) มุมมองแอป MCP แบบอินไลน์ในแชตจะมีตัวเลือก **ปักหมุดไปยังแดชบอร์ด** เช่นเดียวกับวิดเจ็ตเอเจนต์ ปัจจุบันมุมมองที่เปิดอีกครั้งเป็นแบบอ่านอย่างเดียวโดยตั้งใจ แอปที่ปักหมุดและควรโต้ตอบได้ต่อไปจะได้รับสิทธิ์ถาวรเหนือเครื่องมือที่เซิร์ฟเวอร์เปิดให้แอปเห็น (แสดงรายการอนุญาตอย่างชัดเจนแก่ผู้ดำเนินการขณะปักหมุด) โดยแยกออกจากรันที่ออกสิทธิ์ แอปที่ปักหมุดแต่ไม่ได้รับสิทธิ์จะยังคงอ่านได้อย่างเดียว ซึ่งยังมีประโยชน์สำหรับแดชบอร์ดแสดงผล v1 ปักหมุดไปยังบอร์ดของเซสชันต้นทาง การปักหมุดข้ามเซสชันต้องใช้ตัวกลางจัดการสัญญาเช่าและต้องรอ ประสานงานกับ pull request ที่เปิดอยู่ #109807 (การกำหนดเส้นทางคอมโพสเซอร์ `ui/message` และการส่งต่อธีม/ขนาด)

### การผสานรวม WorkBoard

โปรแกรมการผสานรวม WorkBoard ทำให้การ์ดและบอร์ดยังคงเป็นของ Plugin พร้อมทั้งเชื่อมการ์ดที่ส่งงานกลับไปยังบอร์ดของเซสชันผ่าน `sessionKey` และ `runId` ที่มีอยู่ เปิดเผยฟีดและการส่งงานของ WorkBoard ผ่านการผูกและการดำเนินการที่ Plugin ประกาศ และประกอบผลลัพธ์เหล่านั้นเข้ากับชนิดวิดเจ็ต `html` และ `mcp-app` ที่มีอยู่ แทนการเพิ่มชนิดวิดเจ็ตเฉพาะสำหรับ WorkBoard

## การจัดวาง: กริดแบบยืดหยุ่น

12 คอลัมน์ ความสูงแถวคงที่ **กระชับอัตโนมัติ** (แรงโน้มถ่วงขึ้น ดันหลบเมื่อ
ลาก — ใช้ความหมายเชิงพฤติกรรมแบบ gridstack แต่ติดตั้งใช้งานแบบเนทีฟ; การคำนวณกริดยังคงบริสุทธิ์และ
ไม่ขึ้นกับ DOM) สถานะการจัดวางวิดเจ็ตต่อแท็บ: `{ name, w (1-12), h (rows) }` รวมถึง
ลำดับ คำศัพท์สำหรับเอเจนต์:

- `size`: `sm` (3×3) · `md` (6×4) · `lg` (8×6) · `xl` (12×8) · `full`
  (แท็บวิดเจ็ตเดียว)
- `after: <widgetName>` จุดยึดการจัดลำดับที่เลือกใช้ได้; หากละไว้ = ต่อท้าย
- ผู้ใช้ลาก/ปรับขนาดได้อย่างอิสระ; โมเดลลำดับ+ขนาดเดียวกันสามารถไปกลับได้โดยไม่สูญเสียข้อมูล

## โมเดลข้อมูล (ฐานข้อมูลต่อเอเจนต์)

ตารางใหม่ใน `agents/<agentId>/agent/openclaw-agent.sqlite`
(**ต้องเพิ่มเวอร์ชันสคีมาของฐานข้อมูลเอเจนต์ — ต้องได้รับการอนุมัติจากผู้ดำเนินการ
ก่อนนำการเปลี่ยนแปลงนี้เข้า**):

```sql
CREATE TABLE board_tabs (
  session_key TEXT NOT NULL,
  tab_id      TEXT NOT NULL,           -- slug
  title       TEXT NOT NULL,
  position    INTEGER NOT NULL,
  chat_dock   TEXT NOT NULL DEFAULT 'right',  -- left|right|bottom|hidden
  created_by  TEXT NOT NULL,           -- 'user' | 'agent'
  PRIMARY KEY (session_key, tab_id)
) STRICT;

CREATE TABLE board_widgets (
  session_key  TEXT NOT NULL,
  name         TEXT NOT NULL,          -- stable widget name
  tab_id       TEXT NOT NULL,
  title        TEXT,
  html         BLOB NOT NULL,          -- wrapped document source
  sha256       TEXT NOT NULL,
  revision     INTEGER NOT NULL,
  size_w       INTEGER NOT NULL,
  size_h       INTEGER NOT NULL,
  position     INTEGER NOT NULL,       -- order within tab (auto-compact input)
  manifest     TEXT NOT NULL DEFAULT '{}',  -- capability manifest JSON
  grant_state  TEXT NOT NULL DEFAULT 'none', -- none|pending|granted|rejected
  granted_sha  TEXT,                   -- byte-frozen grant
  created_by   TEXT NOT NULL,
  created_at   INTEGER NOT NULL,
  updated_at   INTEGER NOT NULL,
  PRIMARY KEY (session_key, name)
) STRICT;
```

การมีอยู่ของบอร์ด = มีแถวใดก็ตามสำหรับ `sessionKey` การลบเซสชันจะลบแถวของบอร์ดนั้น `/new`/`/reset` จะไม่แตะต้องแถวเหล่านี้

## พื้นผิวโปรโตคอล

RPC (ตารางเมธอดหลัก สคีมา typebox ใน `gateway-protocol`):

- `board.get { sessionKey }` → แท็บ + เมทาดาทาวิดเจ็ต (ไม่มีไบต์) — `operator.read`
- `board.update { sessionKey, ops[] }` — CRUD/จัดลำดับแท็บใหม่, ย้าย/ปรับขนาด/
  ลบ/เลิกปักหมุดวิดเจ็ต, สถานะด็อก, โฟกัสแท็บ — `operator.write`
- `board.widget.put { sessionKey, name, html, manifest, placement }` —
  `operator.write` (เส้นทางเครื่องมือเอเจนต์และเส้นทางปักหมุด)
- `board.widget.grant { sessionKey, name, decision }` — `operator.approvals`
- `board.event { ticket, payload }` — รับเหตุการณ์สถานะระดับ 1 ที่ผูกกับตั๋ว;
  รูปแบบโฮสต์ที่เชื่อถือแบบเดิม `{ sessionKey, widget, payload }` ยังคงอยู่ —
  `operator.write`
- `board.prompt.authorize { ticket }` — คืนค่าว่าการส่งพรอมต์ที่มองเห็นได้
  ยังคงต้องยืนยันทุกครั้งที่คลิกหรือไม่ — `operator.read`
- `board.data.read { ticket, bindingId, params? }` — การแก้ไขการผูกสำหรับอ่าน
  ของคอร์หรือ Plugin ที่ใช้งานอยู่ซึ่งอยู่ในรายการอนุญาตฝั่ง Gateway — `operator.read`
- `board.action { ticket, action, ... }` — ส่งงานระบบอัตโนมัติตามสิทธิ์ที่ตรงกันทุกประการ
  ผ่านเส้นทางเรียกใช้ทันทีของ Cron ที่มีอยู่ หรือกริยาการดำเนินการที่ผ่านการตรวจสอบของ Plugin ที่ใช้งานอยู่
  — `operator.write`

เหตุการณ์ (ใน `EVENT_SCOPE_GUARDS`, ขอบเขตการอ่าน):

- `board.changed { sessionKey, revision, widget? }` — สถานะถาวรเปลี่ยนแปลง;
  UI ดึงข้อมูลใหม่ (และโหลด iframe หนึ่งรายการใหม่เมื่อมี `widget`)
- `board.command { sessionKey, command }` — การควบคุม UI ชั่วคราว (เอเจนต์สลับ
  แท็บที่มองเห็น สลับด็อกแชต) — รูปแบบ `ui.command`

ไบต์ของวิดเจ็ตให้บริการผ่านพื้นผิว HTTP ที่ผ่านการตรวจสอบสิทธิ์ ไม่ใช่ซ็อกเก็ต

## เครื่องมือเอเจนต์

รวมทั้งหมดสามเครื่องมือ (อยู่ในคอร์ ลงทะเบียนเสมอ; การเรนเดอร์ถูกกั้นด้วยความสามารถไคลเอนต์
`inline-widgets` เช่นเดียวกับปัจจุบัน):

- `show_widget { title, widget_code, name?, pin?, size?, tab?, after?,
capabilities? }` — สร้าง/อัปเดตตามชื่อ; `pin` วางลงบนบอร์ด
  หากไม่มี `name`/`pin` จะทำงานเหมือนปัจจุบันทุกประการ (อินไลน์ ชั่วคราว)
- `dashboard { action, ... }` — กริยาจัดการบอร์ด: `read`, `tab_create`,
  `tab_update`, `tab_delete`, `tabs_reorder`, `widget_move`, `widget_remove`,
  `unpin`, `focus_tab`, `set_chat_dock`
- เครื่องมือ `cron` ที่มีอยู่ครอบคลุมระดับระบบอัตโนมัติแล้ว ไม่ต้องมีเครื่องมือใหม่

คำอธิบายเครื่องมือสอนคำศัพท์ด้านขนาด/จุดยึดและโมเดลระดับต่าง ๆ เอเจนต์จะได้รับแจ้งเหตุการณ์ระดับ 1 ของผู้ใช้ผ่านประกาศในเซสชัน เช่น
`[dashboard] user clicked "Refresh" on widget weather (tab main)`

## สิ่งที่ระบบนี้แทนที่

- **ลบ `extensions/workspaces`** เป็นฟีเจอร์ทดลอง, `enabledByDefault:
false`, ไม่เคยอยู่ในรุ่นเสถียร (ปรากฏครั้งแรกในรุ่นเบตา 2026.7.2) ไม่มี
  การย้ายข้อมูล; กฎ doctor จะลบ `<stateDir>/workspaces/` ที่ตกค้างหากมี
  แนวคิดที่นำมาใช้: การคำนวณกริดแบบบริสุทธิ์, โมเดลความปลอดภัยของบริดจ์ (การเริ่มต้นพอร์ต,
  การกั้นการผูก, การจำกัดอัตรา), การอนุมัติที่ตรึงตามไบต์
- **การโฮสต์วิดเจ็ตย้ายจาก `extensions/canvas` ไปยังคอร์** คลังเอกสาร canvas,
  ตัวครอบเอกสาร, การให้บริการ HTTP และเครื่องมือ `show_widget` จะย้ายเป็นส่วนหนึ่งของคอร์
  (`src/canvas/`); Plugin ยังคงเก็บเครื่องมือควบคุม node-canvas (`canvas`) และ
  A2UI การประกาศ `pluginSurfaceUrls["canvas"]` และ
  เส้นทาง `/__openclaw__/canvas` เป็นสัญญาไคลเอนต์เนทีฟที่เผยแพร่แล้วและยังคง
  เสถียร เซสชัน Discord ยังคงใช้ตัวแปร `show_widget` ที่ Discord เป็นเจ้าของ

## สิ่งที่ไม่ใช่เป้าหมาย (ของโปรแกรมนี้)

- การแชร์บอร์ดระหว่างผู้ใช้หลายคน/ACL (ในอนาคต; จะมาผ่านการแชร์เซสชัน)
- การเรนเดอร์บอร์ดแบบเนทีฟบน macOS/iOS (จะได้รับฟังก์ชันนี้ทุกที่ที่ฝัง
  Control UI; เส้นทางวิดเจ็ตแบบอินไลน์ไม่เปลี่ยนแปลง)
- วิดเจ็ตข้อมูลในตัว (การ์ดเซสชัน/การใช้งาน/Cron) — บริดจ์ความสามารถร่วมกับ
  วิดเจ็ตที่เอเจนต์เขียนครอบคลุม v1; รีจิสทรีชนิดในตัวเพิ่มได้ภายหลัง

## แผนการติดตั้งใช้งาน

เวิร์กทรีอิสระ สร้างด้วย Codex รีวิว+นำเข้าตามลำดับ นำเข้าแล้วจึงแก้ไข

| #   | สาขา                               | ขอบเขต                                                                                                                                                                              | ขึ้นต่อกับ                       |
| --- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| T1  | `claude/dashboard-remove-workspaces` | ลบ Plugin workspaces + UI + เอกสาร + คีย์ i18n; กฎล้างข้อมูลของ doctor                                                                                                              | —                                |
| T2  | `claude/dashboard-canvas-core`       | ยกระดับการโฮสต์วิดเจ็ต + `show_widget` ไปยังคอร์; Plugin canvas ยังคงเก็บเครื่องมือ Node; พฤติกรรมไม่เปลี่ยนแปลงเลย                                                                                | —                                |
| T3  | `claude/dashboard-domain`            | ตารางฐานข้อมูลเอเจนต์ (เพิ่มเวอร์ชันสคีมา), RPC `board.*` + เหตุการณ์, เครื่องมือ `dashboard`, อาร์กิวเมนต์ปักหมุด/ชื่อ/manifest `show_widget`, ประกาศระดับ 1, รีเซ็ตแต่คงบอร์ดไว้                                  | T2                               |
| T4  | `claude/dashboard-ui`                | หน้าบอร์ด + แถบแท็บ + กริดกระชับอัตโนมัติแบบยืดหยุ่น + ด็อกแชต (ซ้าย/ขวา/ล่าง/ซ่อน) + ตัวเลือกปักหมุดในบทสนทนา + หน้าบอร์ดในแถบด้านข้าง + การยืนยันรีเซ็ต                           | T3 (ใช้ม็อกก่อนผ่านฟิกซ์เจอร์สำหรับพัฒนา) |
| T5  | `claude/dashboard-capabilities`      | คลัง/ UI สำหรับสิทธิ์ + การตรึงตามไบต์; ย้ายวิดเจ็ต `html` ไปยังโฮสต์แซนด์บ็อกซ์ที่ใช้ร่วมกัน; เครื่องมือโฮสต์ (`openclaw.prompt.send/state.emit/data.read/cron.trigger`); CSP `net`; ชิมสำหรับการเขียน | T3, T4                           |
| T7  | `claude/dashboard-mcp-apps`          | ชนิดเนื้อหา `mcp-app`: ตัวเลือกปักหมุดในมุมมองแอปแบบอินไลน์, การจัดเก็บตัวบรรยาย, การออก/รีเฟรชสัญญาเช่าใหม่, สิทธิ์เครื่องมือเซิร์ฟเวอร์แบบถาวร (ใช้โฮสต์ MCP Apps ที่เผยแพร่แล้วซ้ำ)                   | T3, T4                           |
| T6  | เก็บรายละเอียด                               | E2E แบบสดบน Gateway ชั่วคราว (คีย์จริง), ภาพหน้าจอ, การแก้ไข, เขียน `/web/dashboard` ใหม่โดยเน้นผู้ใช้, รีวิวการเปิดใช้เป็นค่าเริ่มต้น                                                     | ทั้งหมด                              |

การตรวจสอบความถูกต้องตามกฎของรีโป: vitest แบบเจาะจงในเครื่อง, เกตทั้งหมดบน
Crabbox/Testbox, `$autoreview` ก่อนนำเข้าแต่ละครั้ง, การพิสูจน์แบบสดสำหรับ T6
