# ภาพและ Desktop access — JUJU 0.8.2

JUJU ส่งภาพให้ MCP client ที่รองรับ image content และอ่าน/ควบคุม **หน้าต่างของ
แอปที่เจ้าของอนุญาต** ผ่าน backend ตาม OS: macOS Screen Recording/Accessibility,
Windows PrintWindow + UI Automation + SendInput และ Linux X11/X-Resource/XTEST +
AT-SPI. ไม่ต้องมี LLM API key หรือ vendor relay เพิ่ม การอนุญาต Desktop ยังคง
แยกจาก OAuth, workspace access และ trust mode

## เครื่องมือ

| Tool | ทำอะไร |
|---|---|
| `read_image` | อ่าน PNG/JPEG/GIF/WebP จาก workspace ผ่าน file/secret policy เดิม |
| `desktop_status` | ตรวจสิทธิ์ของ workspace รอบนี้, helper และ OS permissions; ไม่จับภาพ |
| `desktop_windows` | รายการหน้าต่างที่มองเห็นของ app IDs ที่อนุญาต สูงสุด 100 |
| `desktop_capture` | JPEG ของหน้าต่างเดียว พร้อม snapshotId; เปิด OCR ด้วย `ocr:true` |
| `desktop_accessibility` | อ่าน UI text/roles ของหน้าต่างที่ capture ไว้ สูงสุด 100 elements, depth 6 |
| `desktop_action` | focus, move, click/double/right click, drag, scroll, key และ type |

บน macOS `ocr:true` ใช้ Apple Vision ในเครื่อง; Windows backend ไม่สร้าง OCR
ปลอมและ Linux ใช้ภาพ + Accessibility/AT-SPI ตามที่แอปเปิดเผย หาก client/model
อ่าน image content ได้ให้ใช้ภาพจริงเป็นหลัก แล้วใช้ Accessibility ประกอบ

## 1. เตรียม Desktop backend (เจ้าของทำในเครื่อง)

หลังติดตั้ง 0.8.2 ใช้ setup กลาง:

```bash
npm install -g juju-mcp@0.8.2
juju setup --components desktop
juju setup --check --components desktop
```

`juju setup` ใช้เฉพาะ source/helper ที่มากับแพ็กเกจและ private JUJU state ไม่
compile หรือ execute code จาก workspace: macOS compile Swift ด้วย Xcode Command
Line Tools, Windows compile helper ด้วย .NET Framework desktop assemblies และ
Linux เตรียม Python/X11/Pillow/AT-SPI dependencies. Windows ต้องเป็น unlocked
interactive desktop session; Linux claim ของ 0.8.2 คือ X11/XWayland ที่มี
`DISPLAY`, X-Resource/XTEST และ AT-SPI — pure Wayland portal-only ยังไม่ถูกอ้าง
ว่ารองรับ

macOS สามารถขอ/check OS permission ด้วย:

```bash
juju desktop setup --request-permissions
```

จากนั้นยืนยัน Screen Recording และ Accessibility ใน System Settings เอง JUJU
ไม่กดอนุญาตหรือแก้ฐานข้อมูลสิทธิ์ OS แทนเจ้าของ Windows/Linux ไม่มี UAC/secure
Desktop/Wayland portal bypass การเตรียม backend ยัง **ไม่** เปิดสิทธิ์รายแอปของ
JUJU ต้องเลือก app ID ในขั้นถัดไป

## 2. เปิด workspace และอนุญาตรายแอป

### ตั้งครั้งเดียว จำไว้สำหรับโปรเจกต์นี้

ในโฟลเดอร์โปรเจกต์ที่ต้องการ รันได้แม้ JUJU ยังไม่เปิด:

```bash
juju desktop allow --app com.google.Chrome --mode control --persist --yes
juju
```

ครั้งต่อไปเข้าโฟลเดอร์เดิมแล้วพิมพ์ `juju` ได้เลย ไม่ต้องรัน `desktop allow`
ทุกครั้ง และไม่หมดอายุหลัง 60 นาที ใช้ `--mode view` ถ้าต้องการเพียงดูหน้าต่าง
ค่าที่บันทึกอยู่ในฐานข้อมูลส่วนตัวของ JUJU ผูกกับ realpath ของโปรเจกต์นี้
ไม่เขียนลง repository ไม่เปิดสิทธิ์ให้พาธอื่น และไม่เปิดเซิร์ฟเวอร์อัตโนมัติ
ถ้าเซิร์ฟเวอร์ของพาธนี้รันอยู่แล้ว ค่ามีผลกับการเรียกครั้งถัดไป

การกลับมาโปรเจกต์เดิมหรือ restart จะใช้สิทธิ์ที่จำไว้ แต่ snapshots, workspace
epoch และ approvals เก่ายังคงใช้ต่อไม่ได้ ต้อง capture ใหม่ `--persist`
ใช้ร่วมกับ `--minutes` ไม่ได้ และการอนุญาตครั้งใหม่จะแทนรายการแอปเดิมทั้งหมด

ตรวจจาก Terminal อีกอันในโฟลเดอร์เดิมขณะ JUJU รัน:

```bash
juju desktop status
```

จะเห็น `persistent: true` และ `expiresAt: null` หน้า Local Config แสดง
“จำไว้สำหรับโปรเจกต์นี้จนกว่าจะปิดสิทธิ์” การจำค่าไม่ข้าม OS desktop permissions,
OAuth/client scopes หรือ trust/local action approval ด้านล่าง

ในส่วน Desktop access ของหน้า Local Config มีกล่องคำสั่งอนุญาต Chrome
พร้อมปุ่ม **คัดลอกคำสั่ง** ให้นำไปวางใน Terminal ของโปรเจกต์นี้

### อนุญาตชั่วคราว

Terminal แรก:

```bash
cd "/absolute/path/to/project"
juju
```

Terminal อีกอัน ใน **workspace เดียวกับที่ server กำลังให้บริการ**:

```bash
juju desktop allow --app com.apple.TextEdit --mode view --minutes 60 --yes
juju desktop status
```

`view` อนุญาตจับภาพ/OCR/อ่านข้อความจากแอปนั้น หากต้องการคลิกและพิมพ์:

```bash
juju desktop allow --app com.apple.TextEdit --mode control --minutes 60 --yes
```

macOS ใช้ exact bundle IDs เช่น `com.apple.TextEdit`; Windows/Linux ให้รัน
`juju desktop apps` แล้วใช้ exact `win.<hash>` / `linux.<hash>` ที่คืนมา ไม่มี
wildcard และไม่ใช้ executable basename เป็นสิทธิ์แทน identity

ถ้าไม่ใช้ `--persist` สิทธิ์มีอายุ 1–480 นาที (ค่าเริ่มต้น 60) และผูกกับ
workspace/epoch ของ process ที่กำลังรัน **สิทธิ์ชั่วคราวต้องอนุญาตใหม่หลัง
restart หรือสลับ workspace** การเลือกชั่วคราวแทนค่าที่จำไว้เดิมด้วย
ทั้งสองแบบไม่ส่งต่อสิทธิ์ข้ามพาธ และ `--allow --all` / `--bypass`
ไม่เปิด desktop access ให้อัตโนมัติ

HTTP ต้องมี `juju:exec` ทั้ง token และ client/path ACL สำหรับ desktop tools
ที่อ่านหรือควบคุม UI เนื่องจากเข้าถึงข้อมูลนอก workspace ได้ `desktop_status`
ใช้ `juju:read` ส่วน STDIO ยังคงต้องมี local desktop permission เช่นกัน

การควบคุมยังใช้ trust policy: `inspect`/`edit` ต้อง `juju approve ID` สำหรับ
คำสั่งที่ตรงกัน; `trusted` ใช้ standing consent หากต้องการเลือก trusted:

```bash
juju trust --mode trusted --yes
```

คำสั่งนี้อนุญาต shell/test/install ด้วยสิทธิ์ OS ของคุณเช่นเดิม ไม่ใช่เฉพาะจอ
Snapshot อายุไม่เกิน 30 วินาที หากอนุมัติช้าเกินต้อง capture และขออนุมัติใหม่

## 3. ลำดับการใช้จาก AI

1. `project_overview` เพื่อรับ workspaceId/workspaceEpoch
2. `desktop_status` ตรวจ mode, helper และ OS permissions
3. `desktop_windows` เลือก windowId ของแอปที่อนุญาต
4. `desktop_capture` พร้อม windowId และ `ocr:true` ตามต้องการ
5. ตรวจภาพ/ข้อความ แล้วส่ง `desktop_action` โดยใช้ snapshotId, idempotencyKey
   ใหม่ และพิกัด **pixel ในภาพที่เพิ่งได้รับ** (จุดเริ่มมุมซ้ายบน)
6. Capture ใหม่เพื่อยืนยันผลหลังทุก action; `posted:true` หมายถึงส่ง OS event
   แล้ว ไม่ได้ยืนยันว่าแอปทำงานตามเป้าหมายสำเร็จ

ตัวอย่างส่วน action (ยังต้องแนบ workspace context และ snapshotId จริง):

```json
{
  "snapshotId": "<from desktop_capture>",
  "idempotencyKey": "click-save-001",
  "action": { "kind": "click", "x": 250, "y": 120, "button": "left", "count": 1 }
}
```

ถ้าหน้าต่างไม่อยู่ด้านหน้า ให้ใช้ `{"kind":"focus"}` แล้ว capture ใหม่ก่อนส่ง
input การตรวจจะปฏิเสธ window ที่ปิด/เปลี่ยน PID/เคลื่อนหรือเปลี่ยนขนาด และ input
ที่ไม่ได้ส่งไปหน้าต่างด้านหน้า พิกัดคลิก/scroll ถูกตรวจ overlay ณ จุดส่งด้วย
UI ยังเปลี่ยนได้ระหว่างตรวจและส่ง event การตรวจนี้ไม่ใช่ OS sandbox

`key` รองรับ a–z, 0–9, F1–F12, enter/tab/escape/backspace/delete, ลูกศร,
home/end/pageup/pagedown และ modifiers: command/control/option/shift
`type` ส่ง Unicode สูงสุด 2,000 UTF-16 units โดยไม่ใช้ clipboard; บาง framework
อาจแปล keyboard events เอง จึงต้องตรวจผลจริงในแอปนั้น ไม่มีการรับ AppleScript
หรือ shell string ผ่าน desktop_action

## หยุดและจัดการ retry

```bash
juju desktop disable
```

ใช้ได้แม้เซิร์ฟเวอร์หยุดอยู่ คำสั่งนี้ปิดและลืมสิทธิ์ทั้งแบบชั่วคราวและแบบ
`--persist` ของพาธปัจจุบัน เมื่อเปิด JUJU ใหม่จะไม่คืนสิทธิ์เดิม ปุ่มปิดใน
Local Config ให้ผลเดียวกันกับ workspace ที่แสดงบนหน้าเว็บ

หรือกด **หยุดการเข้าถึงจอ** ใน Local Config ปุ่มนี้อยู่บนพอร์ตส่วนตัว 21731
และตรวจ token + workspace/epoch เหมือนการบันทึก config อื่น การปิดมีผลกับคำสั่ง
ถัดไปและล้าง snapshots; ย้อน OS event ที่ส่งไปแล้วไม่ได้

การเรียก desktop ทำทีละรายการภายใน workspace service ไม่รัน mouse/keyboard
หลายคำสั่งพร้อมกัน หลังเริ่ม action จะล้าง snapshots ทั้งหมดก่อน dispatch
เก็บ receipt แบบ durable ที่มี digest/state เท่านั้น ไม่เก็บข้อความที่พิมพ์
หรือภาพใน SQLite/audit Retry key เดิมกับ arguments เดิมคืน receipt โดยไม่ส่ง
input ซ้ำ หาก helper ขาดการติดต่อหรือ process หยุดกลาง action จะถือผลไม่แน่นอน
และ **ไม่ auto-repeat** ให้ capture ตรวจแอปก่อนตัดสินใจใหม่

Receipt มีเพดาน 10,000 รายการต่อ state DB เมื่อเต็มจะปฏิเสธ action ใหม่เพื่อให้
เจ้าของดูแล state ไม่ลบหลักฐานของคำสั่งไม่แน่นอนทิ้งเอง

## ขอบเขตและผลตรวจจริง

- ภาพจาก native helper: JPEG ด้านยาวสูงสุด 2,000 pixels, ไม่เกิน 3 MiB;
  snapshots ใน memory สูงสุด 32, อายุไม่เกิน 30 วินาที ไม่บันทึก screenshot ลง disk
- Captures แสดงเฉพาะหน้าต่างที่เลือก ไม่ได้เปิด whole-display recording,
  clipboard API, browser DOM automation หรือเครื่องมือควบคุมระบบทุกประเภท
- App allowlist ไม่ใช่ sandbox: แอปที่อนุญาตอาจแสดงข้อมูลนอก workspace และอาจ
  เปลี่ยนไฟล์/เปิดแอปอื่นได้ Screenshot/OCR อาจเห็นข้อมูลลับที่แสดงบนจอ
  Accessibility ละค่าของ secure text fields แต่ไม่ใช่การรับประกัน redact ทุกข้อมูล
- โค้ดไม่ได้ป้องกัน hostile same-UID process และ trusted shell สามารถใช้อำนาจ
  OS ของเจ้าของได้อยู่แล้ว การยืนยันใน AI client ไม่แทน OS/local permission
- macOS arm64: backend เดิมและ full repository gate ผ่าน; OS permission ยังคง
  เป็นสิทธิ์ที่เจ้าของต้องให้เอง
- Windows Server x64 / Node v24.21.0: 0.8.2 native acceptance จับภาพหน้าต่างจริง
  32,376 bytes, focus สำเร็จ และ UI Automation คืน 20 elements โดยไม่ bypass UAC/UIPI
- Debian arm64 / Node 22 + Xvfb: process identity, capture 6,616 bytes, focus และ
  AT-SPI path ผ่านจริง; claim จำกัดที่ X11/XWayland-style sessions ตามด้านบน
- pure Wayland-only, macOS Intel และ Windows ARM64 ไม่มี native acceptance ในรอบนี้
- Remote Desktop Commander เป็นผลิตภัณฑ์แยก งานนี้เพิ่มเครื่องมือของ JUJU เอง
  ไม่ได้อ้าง feature parity ทุกฟังก์ชันหรือการใช้ API ภายในของผลิตภัณฑ์นั้น

รันทดสอบ native แยกจาก default tests (เปิดเฉพาะแอปจำลอง ไม่ใช้แอปงานของผู้ใช้):

```bash
npm run test:desktop:native
# หลังให้ OS Accessibility แล้ว ทดสอบการคลิกปุ่มใน fixture:
npm run test:desktop:native -- --control
```

ผลบันทึกที่ `artifacts/desktop-native-report.json`; ชุด unit/security ใช้ fake
OS adapter แต่ตรวจ OAuth, SQLite, IPC, policy, snapshot และ receipt จริง
ดู [TEST_REPORT](TEST_REPORT.md) และ [ADR-020](adr/020-desktop-access.md)
