---
read_when:
    - การพัฒนาหรือรีวิวฟีเจอร์แดชบอร์ดเซสชัน (บอร์ด)
    - การเปลี่ยนโฮสติ้งของวิดเจ็ต บริดจ์ของวิดเจ็ต หรือพื้นที่จัดเก็บบอร์ด
summary: 'แดชบอร์ดเซสชัน: สถาปัตยกรรมและแผนการนำไปใช้งาน (การออกแบบทางเทคนิค ก่อน GA)'
title: สถาปัตยกรรมแดชบอร์ด
x-i18n:
    generated_at: "2026-07-19T07:42:47Z"
    model: gpt-5.6
    postprocess_version: locale-links-v1
    prompt_version: 32
    provider: openai
    source_hash: 472b6a9268f552f56b7aaa3ceecaa99e15722188f10d703d3321e9d60166904f
    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.sendPrompt/emitState/read/call` เป็น API ที่คงที่สำหรับผู้เขียน
  ไม่ว่าการขนส่งเบื้องหลังจะเป็นช่องทางของเราหรือ AppBridge ก็เป็น
  รายละเอียดภายในที่ผู้เขียนวิดเจ็ตจะไม่เห็น การรายงานขนาดและโทเค็นธีม
  ใช้บริดจ์เดียวกัน

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

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

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

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

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

## เลย์เอาต์: กริดแบบยืดหยุ่น

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>` จุดยึดลำดับที่ไม่บังคับ; หากละไว้ = ต่อท้าย
- ผู้ใช้ลาก/ปรับขนาดได้อย่างอิสระ; โมเดลลำดับ+ขนาดเดียวกันสามารถส่งไปกลับได้

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

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

```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 { sessionKey, widget, payload }` — รับเข้าเหตุการณ์สถานะระดับ 1 —
  `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` ไปยังแกนหลัก** ที่เก็บเอกสารแคนวาส
  ตัวห่อเอกสาร การให้บริการ HTTP และเครื่องมือ `show_widget` จะกลายเป็นส่วนแกนหลัก
  (`src/canvas/`); Plugin จะเก็บเครื่องมือควบคุม node-canvas (`canvas`) และ
  A2UI ไว้ การประกาศ `pluginSurfaceUrls["canvas"]` และ
  เส้นทาง `/__openclaw__/canvas` เป็นสัญญาไคลเอนต์เนทีฟที่เผยแพร่แล้วและยังคง
  เสถียร เซสชัน Discord ยังคงใช้ตัวแปร `show_widget` ที่ Discord เป็นเจ้าของ
- **ไม่แตะต้อง WorkBoard** (การผสานรวมเป็นโครงการติดตามผล)

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

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

## แผนการดำเนินงาน

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

| #   | แบรนช์                               | ขอบเขต                                                                                                                                                                              | ขึ้นกับ                       |
| --- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| T1  | `claude/dashboard-remove-workspaces` | ลบ Plugin เวิร์กสเปซ + UI + เอกสาร + คีย์ i18n; กฎล้างข้อมูลของ doctor                                                                                                              | —                                |
| T2  | `claude/dashboard-canvas-core`       | ยกระดับการโฮสต์วิดเจ็ต + `show_widget` ไปยังแกนหลัก; Plugin แคนวาสเก็บเครื่องมือโหนดไว้; ไม่มีการเปลี่ยนแปลงพฤติกรรม                                                                                | —                                |
| T3  | `claude/dashboard-domain`            | ตาราง DB เอเจนต์ (เพิ่มเวอร์ชันสคีมา), RPC `board.*` + เหตุการณ์, เครื่องมือ `dashboard`, อาร์กิวเมนต์ปักหมุด/ชื่อ/แมนิเฟสต์ `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.
