DSH Android

DSH Android

อุปกรณ์ Android แบบสดภายในบทสนทนาของ DeepSeek Harness — อีมูเลเตอร์หรือโทรศัพท์ผ่าน USB ขับเคลื่อนทั้งหมดผ่าน adb
20 เครื่องมือเอเจนต์ • สตรีมสดในโพรเซสเดียวกัน ไม่ต้องมีตัวช่วยภายนอก • แผงนำทางสามปุ่ม • บิลด์ & รันด้วย Gradle • Vision OCR

npm: @zseven-w/dsh-android · รีลีสปลั๊กอินปัจจุบัน: 0.1.0-rc.4 · ทดสอบกับ DSH 0.1.1-rc.1

English · 简体中文 · 繁體中文 · 日本語 · 한국어 · Français · Español · Deutsch · Português · Русский · हिन्दी · Türkçe · ไทย · Tiếng Việt · Bahasa Indonesia


DSH Android — อุปกรณ์ Android แบบสดภายในบทสนทนา

อุปกรณ์ Android ที่สตรีมและควบคุมจากในบทสนทนา DSH — การเรียกเครื่องมือของเอเจนต์อยู่ตรงกลาง แผงอุปกรณ์สดอยู่ทางขวา

## ทำไมต้อง DSH Android DSH Android มอบอุปกรณ์ Android เครื่องจริงให้เอเจนต์ภายในบทสนทนา — และมอบพิกเซลให้คุณ เอเจนต์สามารถเริ่มสตรีมบนอีมูเลเตอร์หรือโทรศัพท์ที่เชื่อมต่อ USB บิลด์และติดตั้งโปรเจกต์ Gradle ขับเคลื่อน UI ด้วย `resource-id`/ข้อความ หรือด้วย OCR อ่าน logcat และตรวจสอบโพรเซสกับหน่วยความจำได้ ในขณะที่สตรีมสดของอุปกรณ์แสดงผลอยู่ในแผงข้างแบบถาวร ซึ่งคุณแตะ ลาก หมุน และกด Back / Home / Recents บนวิดีโอได้โดยตรง ไม่มีบล็อกภาพ ไม่มีไฟล์บันทึกหน้าจอ: ไบต์ภาพเข้าถึง UI ผ่าน URL ที่ลงนามและหมดอายุซึ่งให้บริการโดยเว็บเซิร์ฟเวอร์ DSH เท่านั้น มีเส้นทางโค้ดเพียงเส้นทางเดียวเท่านั้น `adb devices -l` รายงาน **serial** และ serial นั้นคือเอกลักษณ์เดียวของอุปกรณ์ — ไม่ว่าจะเป็น `emulator-5554` serial ของอุปกรณ์ USB หรือเป้าหมายแบบ `ip:port` ทั้งหมดทำงานเหมือนกันทุกประการ ปลั๊กอินไม่ผูกกับผลิตภัณฑ์อีมูเลเตอร์ตัวใด (AVD, Genymotion, WSA, ฟาร์มอุปกรณ์บนคลาวด์) และไม่มีการแยกซิมูเลเตอร์/อุปกรณ์จริงให้ต้องคิดถึง | | | | --- | --- | | 📱 **อุปกรณ์สดในบทสนทนา** | สตรีม PNG แบบ `multipart/x-mixed-replace` ที่ผลิต**ในโพรเซสเดียวกัน** และให้บริการตรงจากบัฟเฟอร์เฟรมล่าสุดผ่านเส้นทาง `/_dsh/dsh-android/*` ที่ลงนาม | | 🔌 **ไม่มีตัวช่วยสตรีมภายนอก ไม่มีพอร์ตภายใน** | โพรเซสลูก `adb exec-out` ตัวเดียวที่คงอยู่ถาวรรัน `while :; do screencap -p; done` โฮสต์แยกก้อน PNG ที่ต่อกันออกเป็นเฟรมเอง ไม่มีเซิร์ฟเวอร์สตรีมลูปแบ็กให้พร็อกซี ไม่มีช่วงพอร์ตให้จัดการ และไม่มีอะไรให้รับเลี้ยงหลังการปิดที่ไม่สุภาพ | | 🧩 **เส้นทางโค้ด adb เส้นทางเดียว** | อีมูเลเตอร์กับโทรศัพท์คือสิ่งเดียวกันสำหรับ adb และสำหรับปลั๊กอินนี้ ไม่มีสแตกคู่ `simctl`/WebDriverAgent ไม่ต้องบิลด์แล้วเชื่อถือใบรับรองก่อนอุปกรณ์จริงจะใช้งานได้ | | 🛠️ **20 เครื่องมือเอเจนต์** | อุปกรณ์ บูต/ปิดเครื่อง สกรีนช็อต โต้ตอบ บิลด์ & รันด้วย Gradle แสดงรายการ/เปิดแอป UI ทรีแบบ `uiautomator` + แตะตามองค์ประกอบ การทำงานกับแถวรายการ/ฟีด ค้นหา/แตะ/รอข้อความด้วย Vision OCR logcat โพรเซส แบ็กเทรซ ANR/แครช meminfo ข้อมูลแอป | | 👆 **แผงนำทางสามปุ่ม** | แตะและลากบนวิดีโอสด แถบเครื่องมือพร้อม **◁ Back · ○ Home · □ Recents** และปุ่มหมุน สกรีนช็อต รีเฟรช พร้อมเมนูอุปกรณ์สำหรับแถบแจ้งเตือน การตั้งค่าด่วน ล็อก ปลุก และผู้ช่วย | | 🖼️ **มัลติโมดัลในตัว** | บนโมเดลที่รับภาพได้ เครื่องมือจับภาพทุกตัว (screenshot, interact, tap_element, tap_text, tap_row) จะคืนสกรีนช็อตนั้นเองมาเป็น image block — โมเดลเห็นหน้าจอโดยตรง OCR ยังคงอยู่สำหรับการแตะข้อความแบบแม่นระดับพิกเซลและเส้นทางที่รองรับเฉพาะข้อความ ส่วนโมเดลที่รับเฉพาะข้อความยังได้สรุปแบบ JSON ธรรมดาเหมือนเดิม | | 🔐 **เส้นทางที่ลงนามและรับเฉพาะลูปแบ็ก** | ทุกเส้นทางต้องมีเพียร์ลูปแบ็ก `Host` แบบลูปแบ็ก (ปฏิเสธ DNS rebinding) และการตรวจสอบ Fetch-Metadata/Origin — ก่อนที่จะพิจารณาความสามารถใด ๆ ความสามารถแบบ HMAC-SHA256 หมดอายุภายใน 10 นาที | | 🔍 **ระบบอัตโนมัติเชิงความหมาย + เชิงภาพ** | `android_ui_tree` ดัมป์ลำดับชั้น `uiautomator` และ `android_tap_element` แตะตาม `resource-id` ข้อความ หรือ content-description เมื่อทรีว่างหรือข้อความถูกฝังอยู่ในภาพ `android_find_text` / `android_tap_text` จะ OCR หน้าจอแทนการเดาพิกัด | ## เครื่องมือ เครื่องมือทั้ง 20 ตัวลงทะเบียนบนทุกโฮสต์และคืนค่า JSON ธรรมดา — ไบต์ภาพเข้าถึง UI ผ่าน `presentationMeta` + เส้นทางที่ลงนามเท่านั้น ไม่มีวันเป็นบล็อกภาพ เมื่อแก้ไข adb ไม่ได้ เครื่องมือยังลงทะเบียนอยู่และทุกการเรียกจะล้มเหลวพร้อมข้อผิดพลาดที่อธิบายวิธีแก้ไว้ชัดเจน พิกัดเป็น **ค่านอร์มัลไลซ์ 0..1 ของเฟรมที่สตรีม** ในทุกที่ เฟรมเป็นไปตามการหมุนของจอ (แอปแนวนอนสตรีมที่ 2400×1080 บนอุปกรณ์ 1080×2400) และ `input tap` ใช้พื้นที่พิกัดเดียวกัน จึงไม่มีการคำนวณการหมุนฝั่งไคลเอนต์อยู่ที่ใดเลยในปลั๊กอินนี้ ### เครื่องมือหลัก | เครื่องมือ | หน้าที่ | พารามิเตอร์หลัก | | --- | --- | --- | | `android_devices` | แสดงรายการอุปกรณ์ทุกเครื่องที่ `adb devices -l` รายงาน (serial สถานะ อีมูเลเตอร์/อุปกรณ์จริง รุ่น เวอร์ชัน Android ระดับ API ชื่อ AVD) พร้อมชื่อ AVD ของเครื่องนี้ใต้ `avds` ใช้ค้นหา serial ที่เครื่องมืออื่นต้องการ การแจกแจงที่ล้มเหลวจะโยนข้อผิดพลาดแทนการคืนรายการว่าง | — | | `android_boot` | เริ่มสตรีมสด ส่ง serial ที่อยู่ในสถานะ ONLINE เพื่อสตรีมทันที หรือส่งชื่อ AVD เพื่อเปิดอีมูเลเตอร์ตัวนั้นก่อนแล้วสตรีมเมื่อบูตเสร็จ (ใช้เวลาหลายนาทีเมื่อเริ่มจากเย็น) สตรีมคงอยู่ตลอดบทสนทนาเพื่อให้แผงแสดงอุปกรณ์แบบสดได้ | `device` (จำเป็น — serial หรือชื่อ AVD) | | `android_shutdown` | ปิดอีมูเลเตอร์ (`adb emu kill`) และหยุดสตรีมเมื่อสตรีมเล็งไปที่อุปกรณ์นั้น อุปกรณ์จริงจะถูกปฏิเสธพร้อมเหตุผล: adb ปิดเครื่องโทรศัพท์ไม่ได้ | `device` | | `android_screenshot` | จับภาพ PNG และคืนสรุป JSON สั้น ๆ (เส้นทาง ไบต์ ขนาด อุปกรณ์) ภาพแสดงผลในการ์ดและแผง ไม่มีวันเป็นบล็อกภาพ | `device` (ไม่บังคับ — อุปกรณ์ที่กำลังสตรีม ถ้าไม่มีก็อุปกรณ์ออนไลน์เพียงเครื่องเดียว) | | `android_interact` | โต้ตอบกับอุปกรณ์ที่กำลังสตรีม: แตะที่พิกัดนอร์มัลไลซ์ 0..1 พิมพ์ข้อความ กดปุ่มนำทางหรือปุ่มฮาร์ดแวร์ (`back`, `home`, `recents`, `power`, `volume_up`, `volume_down`, `menu`, `enter`, `delete`) ส่งท่าทางปัด หรือเลื่อน หลังการกระทำนิ่งแล้ว (~300 ms) สกรีนช็อตใหม่จะแสดงผลลัพธ์ | `action` (จำเป็น — `tap`/`type`/`button`/`gesture`/`scroll`), `x`/`y`, `text`, `name`, `json`, `device` | | `android_list_apps` | แสดงรายการแพ็กเกจที่ติดตั้งบนอุปกรณ์ (`pm list packages`) พร้อมชื่อเวอร์ชันจาก `dumpsys package` และป้ายกำกับที่มนุษย์อ่านได้เมื่อแก้ไขได้ — ชื่อแพ็กเกจของบุคคลที่สามเดาไม่ได้ จึงต้องแสดงรายการก่อนหรือส่ง `name` ให้ `android_launch_app` | `device`, `query` (สตริงย่อยไม่แยกตัวพิมพ์ใหญ่-เล็ก รวม CJK), `include_system` (ค่าเริ่มต้น false) | | `android_launch_app` | เปิดแอปที่ติดตั้งแล้วด้วย `packageName` หรือด้วย `name` (สตริงย่อยของป้ายกำกับแบบไม่แยกตัวพิมพ์ใหญ่-เล็ก แก้ไขผ่านการแสดงรายการชุดเดียวกัน) ต้องเป็นหนึ่งในสองอย่างพอดี `relaunch` จะบังคับหยุดแอปก่อน | `packageName` หรือ `name` (อย่างใดอย่างหนึ่ง), `device`, `relaunch` | | `android_build_run` | บิลด์โปรเจกต์ Gradle (`./gradlew assembleDebug`) ติดตั้ง APK แบบ debug ที่ได้ (`adb install -r`) แล้วเปิดใช้งาน การบิลด์เต็มใช้เวลาหลายนาที เมื่อล้มเหลว ผลลัพธ์จะมีหางข้อผิดพลาดของ Gradle ติดมาด้วย | `projectPath` (จำเป็น), `device` | ### เครื่องมือ UI ทรีและแถวรายการ (`uiautomator`) | เครื่องมือ | หน้าที่ | พารามิเตอร์หลัก | | --- | --- | --- | | `android_ui_tree` | ดัมป์ลำดับชั้น `uiautomator` ของแอปหน้าสุดออกมาเป็นโหนด — `type` (ส่วนท้ายของชื่อคลาส), `text`, `contentDesc`, `resourceId`, `bounds` เป็นพิกเซล, `enabled`, `focused` — จำกัดไว้ที่ ~40 KB (ระดับที่ลึกที่สุดถูกตัดทิ้งและตั้งค่า `truncated`) | `device`, `max_depth`, `filter` (สตริงย่อยไม่แยกตัวพิมพ์ใหญ่-เล็กบนข้อความ/content-description/resource-id) | | `android_tap_element` | แตะองค์ประกอบตามเอกลักษณ์ — `resource_id` จับคู่กับ `resource-id` ของโหนด ส่วน `text` จับคู่กับข้อความหรือ content-description ของมัน จับคู่ตรงทั้งหมดก่อน แล้วจึงเป็นสตริงย่อยแบบไม่แยกตัวพิมพ์ใหญ่-เล็ก ตัวซ้ำที่ซ้อนกันถูกยุบเป็นเป้าหมายเดียว และการจับคู่ที่กำกวมจะแสดงผู้สมัครสูงสุด 8 รายการแทนการเลือกให้เอง องค์ประกอบที่ถูกปิดใช้งานจะถูกปฏิเสธ การแตะลงกลางองค์ประกอบ แล้วสกรีนช็อตที่ ~300 ms แสดงผลลัพธ์ ส่ง `expect_text` / `expect_gone` แล้วการแตะพร้อมการตรวจสอบจะกลายเป็นการไป-กลับครั้งเดียว | `device`, `resource_id`, `text`, `expect_text`, `expect_gone` | | `android_ui_rows` | อ่านหน้าจอรายการ/ฟีด (`RecyclerView` และเพื่อนพ้อง) เป็นแถวแทนทรีดิบ: โหนดลูกที่มีรูปทรงเหมือนกันซ้ำ ๆ กลายเป็นแถวที่พกดัชนี เฟรมหน่วยพิกเซล ป้ายกำกับที่รวมแล้ว และตัวนับที่แยกวิเคราะห์ออกจากป้ายกำกับนั้น (ตัวเลข + โทเคนจำแนก ภาษาจีนหรืออังกฤษ — ไม่ฮาร์ดโค้ดคำศัพท์ของแอปใด ๆ) คีย์ของตัวนับไป-กลับได้: ส่งคีย์ให้ `android_tap_row.expect_count` ตรงตามที่แสดงในรายการ | `device`, `max_depth` | | `android_tap_row` | แตะที่ตำแหน่งสัมพัทธ์ภายในแถวที่มองเห็นหนึ่งแถว (`index` จาก `android_ui_rows` ส่วน `x`/`y` เป็นเศษส่วนของเฟรมแถวนั้น ค่าเริ่มต้น 0.5 = กึ่งกลาง) เฟรมมาจากการอ่านทรีครั้งใหม่ จึงไม่มีการเดาพิกัดสัมบูรณ์ และดัชนีที่เกินช่วงจะล้มเหลวแทนการตัดทอนค่า เมื่อใส่ `expect_count={key, delta}` เครื่องมือจะอ่านแถวซ้ำหลัง ~800 ms และยืนยันว่าตัวนับขยับไป ±1 พอดี คีย์ที่ไม่รู้จักจะทำให้การแตะถูกปฏิเสธก่อนเกิดขึ้น | `device`, `index` (จำเป็น), `x`, `y`, `expect_count` (`{key, delta}`) | ### เครื่องมือ OCR ล็อก และดีบัก | เครื่องมือ | หน้าที่ | พารามิเตอร์หลัก | | --- | --- | --- | | `android_find_text` | OCR หน้าจอปัจจุบันด้วยตัวช่วย Vision ที่ปลั๊กอินคอมไพล์ไว้ (การจดจำแม่นยำ zh-Hans + en-US) ใช้เมื่อ UI ทรีว่างหรือเสื่อม สำหรับข้อความที่วาดเป็นกราฟิก (ตัวเลขแบดจ์ ราคาที่ฝังในภาพ) หรือเพื่อตรวจสอบสิ่งที่อยู่บนจออย่างอิสระ คืนค่า `{device, size, items:[{text, confidence, rect}]}` โดย rect คือกล่อง**พิกเซล**ที่มีจุดกำเนิดซ้ายบน เรียงตามความมั่นใจ และจำกัดไว้ที่ ~40 KB ใช้ได้บนโฮสต์ macOS เท่านั้น | `device`, `query` (สตริงย่อยไม่แยกตัวพิมพ์ใหญ่-เล็ก), `min_confidence` (ค่าเริ่มต้น 0.3) | | `android_tap_text` | OCR หน้าจอปัจจุบันแล้วแตะกึ่งกลางของข้อความที่ตรงที่สุด — กฎ ตรงทั้งหมด → มีอยู่ → รายชื่อผู้สมัคร ชุดเดียวกับ `android_tap_element` สำหรับข้อความที่ UI ทรีมองไม่เห็น กึ่งกลางพิกเซลที่ตรงกันจะถูกนอร์มัลไลซ์เทียบกับขนาดเฟรมแล้วส่งเป็นการแตะ หลัง ~300 ms สกรีนช็อตใหม่จะแสดงผลลัพธ์ ใช้ได้บนโฮสต์ macOS เท่านั้น | `device`, `query` (จำเป็น), `min_confidence`, `expect_text`, `expect_gone` | | `android_wait_for` | รอจนกว่าข้อความจะปรากฏหรือหายไป โดยวนตรวจผ่านไปป์ไลน์จับภาพ + OCR ชุดเดียวกันทุก 600 ms จนเงื่อนไขเป็นจริงหรือหมดเวลา (ค่าเริ่มต้น 8 วินาที สูงสุด 60 วินาที) การหมดเวลาคือคำตอบปกติ `matched:false` ไม่ใช่ข้อผิดพลาด ใช้ได้บนโฮสต์ macOS เท่านั้น | `device`, `text` (จำเป็น), `mode` (`appear`/`disappear`), `timeout_ms`, `min_confidence` | | `android_logs` | อ่านสิ่งที่อุปกรณ์บันทึกไว้: `snapshot` (`logcat -d -v time` ในช่วงเวลาล่าสุด ค่าเริ่มต้น 2m) หรือ `follow` (การจับสดแบบมีขอบเขตตาม `duration_seconds` ค่าเริ่มต้น 10 สูงสุด 60 — ไม่มีวันเป็นสตรีมค้าง) กรองให้เหลือแอปเดียวด้วย `bundle_id` (ชื่อแพ็กเกจ Android ที่แก้ไขเป็น pid ของมัน) เอาต์พุตจำกัดไว้ที่ ~300 บรรทัด / 30 KB พร้อมคำใบ้ให้แคบลง | `device`, `mode` (`snapshot`/`follow`), `duration`, `duration_seconds`, `bundle_id`, `grep` | | `android_processes` | แสดงรายการโพรเซสที่กำลังทำงานบนอุปกรณ์ (`ps -A`) เป็น `{pid, name}` — แหล่ง pid สำหรับ `android_backtrace` | `device`, `filter` (สตริงย่อยไม่แยกตัวพิมพ์ใหญ่-เล็กบนชื่อโพรเซส) | | `android_backtrace` | สั่งให้โพรเซสดัมป์สแตกของมัน (`kill -3`) แล้วอ่าน ANR trace ที่ได้จาก `/data/anr/` อุปกรณ์ที่ไม่ได้รูทส่วนใหญ่ปฏิเสธไดเรกทอรีนั้น เครื่องมือจึงลดระดับไปใช้บัฟเฟอร์แครช (`logcat -b crash -d`) และรายงานตามจริงว่าเอนจินใดเป็นผู้ตอบและมันมองไม่เห็นอะไรบ้าง | `device`, `pid` หรือ `bundle_id` | | `android_meminfo` | แยกวิเคราะห์ `dumpsys meminfo `: PSS รวม การแบ่ง Java/native/graphics และหมวดหมู่อันดับต้น ๆ — คำตอบฝั่ง Android สำหรับสรุปหน่วยความจำรั่ว | `device`, `bundle_id` (จำเป็น) | | `android_app_info` | ข้อมูลแอปที่ติดตั้งจาก `dumpsys package `: ชื่อและรหัสเวอร์ชัน ไดเรกทอรีข้อมูล เส้นทางโค้ด เวลาติดตั้งครั้งแรก และแฟล็กระบบ แอปที่ไม่มีอยู่จะคืน `installed: false` พร้อมหมายเหตุที่ชี้ไปยัง `android_list_apps` — ไม่โยนข้อผิดพลาด | `device`, `bundle_id` (จำเป็น) | ## พื้นผิวการแสดงผล - **แผงข้าง** มุมมองสดอยู่ในแผงขวาแบบถาวร (ด็อกคงที่ที่ดันบทสนทนาออกข้าง หรือโอเวอร์เลย์กึ่งกลางบนวิวพอร์ตแคบ) แผงแสดงสตรีม PNG สดและรับการคลิกเพื่อแตะและการลากเพื่อส่งท่าทางบนวิดีโอโดยตรง พร้อมแถบเครื่องมือที่มี **◁ Back**, **○ Home**, **□ Recents** และปุ่มหมุน สกรีนช็อต รีเฟรช เมนูอุปกรณ์รันการกระทำระดับอุปกรณ์ห้าอย่าง (แถบแจ้งเตือน การตั้งค่าด่วน ล็อก ปลุก ผู้ช่วย) ตัวเลือกอุปกรณ์แสดงอุปกรณ์ adb ทุกเครื่องในรายการเดียว จัดกลุ่มตามชนิด โดย AVD ที่ออฟไลน์แสดงเป็นคำใบ้ที่ชี้ไปยัง `android_boot` แทนการบูตเมื่อคลิก โหมดขนาดและสไตล์เฟรม (ไร้เฟรม / ขอบจอ / ตัวเครื่องโทรศัพท์) ทำงานเหมือนในฝาแฝดฝั่ง iOS แผงปรับอัตราส่วนภาพจากขนาดธรรมชาติของเฟรมเอง การหมุนจอจึงไม่ต้องตั้งค่าอะไรเลย - **การ์ดสนทนาแบบกะทัดรัด** ผลลัพธ์เครื่องมือแสดงเป็นการ์ดบรรทัดเดียวโดยไม่มีภาพในบรรทัด: ชื่ออุปกรณ์ ป้ายย่อยของการกระทำ แบดจ์สถานะ และคำใบ้ "เปิดในแผงข้าง" การคลิกแถวจะเปิดแผง - **แคปซูลสถานะเหนือช่องพิมพ์** ขณะแผงปิดและมีสตรีมออนไลน์ เม็ดเล็ก ๆ จะปรากฏเหนือช่องพิมพ์ข้อความและเปิดแผงเมื่อคลิก - **โหมดมาตรฐานและโหมด Code** เซสชันมาตรฐานใช้ `presentationMeta` ที่โฮสต์ฉายให้ ส่วนดิสแพตช์แบบซ้อนของโหมด Code ไม่พก meta มาด้วย ไคลเอนต์จึงสร้าง meta เดียวกันขึ้นใหม่จาก JSON ผลลัพธ์ถาวร — แผง การ์ด และแคปซูลทำงานได้ทั้งสองโหมด ## ความปลอดภัย - **เบราว์เซอร์ไม่เคยคุยกับ adb และไม่มีพอร์ตภายในให้คุยด้วย** สตรีมถูกผลิตในโพรเซสนี้และให้บริการจากหน่วยความจำ ทุกไบต์ข้ามออริจินของเว็บเซิร์ฟเวอร์ DSH ผ่านเส้นทาง `/_dsh/dsh-android/*` ที่ปลั๊กอินเป็นเจ้าของ: `/stream/` (สตรีม multipart PNG สด), `/screenshot/` (PNG แคช) พร้อมกับ `/grant`, `/switch-device`, `/devices`, `/capture`, `/status`, `/control` และ `/device-action` นี่คือพื้นผิวการโจมตีที่เล็กกว่าเซิร์ฟเวอร์สตรีมลูปแบ็กที่ถูกพร็อกซีอย่างเคร่งครัด - **รั้วลูปแบ็กสามชั้น ที่ใช้ก่อนอ่านความสามารถใด ๆ** เพียร์ของการขนส่งต้องเป็นที่อยู่ลูปแบ็ก เฮดเดอร์ `Host` ต้องระบุออทอริตีแบบลูปแบ็ก (`Host` ที่มาจาก DNS rebinding จึงถูกปฏิเสธ) และ Fetch-Metadata/`Origin` ต้องเป็นออริจินเดียวกัน Host และ Origin คือข้อมูลที่ผู้เรียกควบคุมได้ และไม่มีวันถูกเชื่อถือโดยลำพัง - **ความสามารถแบบ HMAC-SHA256 ที่หมดอายุภายใน 10 นาที** จัดรูปแบบเป็น `base64url(payload).base64url(mac)` และลงนามด้วยคีย์ขนาด 32 ไบต์ต่อโฮมของ DSH (`/cache/dsh-android/stream-access.key`, โหมด 0600, สร้างแบบอะตอมมิก) ความสามารถที่ออกให้อุปกรณ์หนึ่งจะใช้ไม่ได้ทันทีที่อุปกรณ์อื่นเข้ายึดช่องสตรีม และความสามารถสำหรับสกรีนช็อตก็เล่นซ้ำกับเส้นทางสตรีมไม่ได้ - **เส้นทางสกรีนช็อตให้บริการไดเรกทอรีเดียวเท่านั้น** เส้นทางถูกเดินด้วย `lstat` (ลิงก์สัญลักษณ์ใด ๆ ถูกปฏิเสธ) ปิดท้ายด้วยการตรวจการบรรจุอยู่ด้วย `realpath` เปิดด้วย `O_NOFOLLOW` จำกัดขนาด และตรวจสอบซ้ำหลังอ่าน — ไฟล์ที่ถูกสลับเป็นซิมลิงก์ระหว่างการออกโทเคนกับการดึงจึงไม่มีวันถูกให้บริการ - **`/grant` ไม่บูตอะไรทั้งสิ้น** มันเพียงเริ่มลูปเฟรมสำหรับอุปกรณ์ที่ออนไลน์อยู่แล้ว และปฏิเสธ (409 `device_busy`) ที่จะกระชากสตรีมไปจากอุปกรณ์อื่น การสลับอุปกรณ์ต้องใช้ท่าทาง `/switch-device` อย่างชัดแจ้ง ส่วนการบูต AVD ยังคงเป็นหน้าที่ของเครื่องมือ `android_boot` - **Keep-alive และการหยุดเมื่อว่าง** ลูปเฟรมที่ล่มจะเริ่มใหม่ในเบื้องหลัง (หน่วง ~5 วินาที) เมื่อไม่มีผู้บริโภคเลย สตรีมจะหยุดตัวเองหลัง 5 นาที การหยุดโดยตั้งใจไม่มีวันถูกขัดขวาง ## ข้อกำหนด - **Node ≥ 24.11.0** - **adb** จาก platform-tools ของ Android SDK แก้ไขตามลำดับนี้: ตัวแปรสภาพแวดล้อม `ADB` → `adb` บน `PATH` → ``/``/รูท SDK เริ่มต้นตามระบบปฏิบัติการ + `/platform-tools/adb` ติดตั้งด้วย `sdkmanager "platform-tools"` ด้วย Android Studio หรือด้วย `brew install --cask android-platform-tools` หากไม่มี adb ปลั๊กอินก็ยังโหลดและเครื่องมือทั้ง 20 ตัวยังลงทะเบียน ทุกการเรียกจะอธิบายว่าขาดอะไรไป - **อุปกรณ์หนึ่งเครื่อง**: อีมูเลเตอร์ยี่ห้อใดก็ได้ หรือโทรศัพท์ที่เปิดการดีบักผ่าน USB ไว้ ตัวเปิด `emulator` เป็นทางเลือกและมีเพียง `android_boot` แบบระบุชื่อ AVD เท่านั้นที่ต้องใช้ — อย่างอื่นทำงานกับทุกอย่างที่ adb มองเห็น - **DSH ≥ 0.1.0-rc.6 พร้อมเว็บบันเดิล** สำหรับแผง โปรไฟล์ headless ก็ใช้ได้: เครื่องมือทั้ง 20 ตัวทำงานปกติ แค่ไม่มีมุมมองสด - **โฮสต์ macOS สำหรับ OCR** (มีเพียง `android_find_text` / `android_tap_text` / `android_wait_for` ที่ต้องใช้): ปลั๊กอินคอมไพล์ `assets/ocr.swift` ที่แถมมาด้วย `swiftc` เมื่อใช้ครั้งแรกลงใน `~/Library/Caches/dsh-android/bin/ocr` บนโฮสต์ Linux และ Windows เครื่องมือสามตัวนั้นจะรายงานว่า OCR ต้องใช้เฟรมเวิร์ก Vision ของ macOS ส่วนอีก 17 ตัวไม่ได้รับผลกระทบ ตัวแปรแทนที่: `DSH_ANDROID_OCR_DIR`, `DSH_ANDROID_OCR_SWIFT`, `DSH_ANDROID_SWIFTC` - **ADBKeyboard** (ไม่บังคับ สำหรับการป้อน CJK และอิโมจิ): `adb shell input text` รองรับเฉพาะ ASCII ติดตั้ง [ADBKeyboard](https://github.com/senzhk/ADBKeyBoard) บนอุปกรณ์แล้วเลือกเป็น IME ที่ใช้งานอยู่ ข้อความที่ไม่ใช่ ASCII จะถูกส่งผ่านอินเทอร์เฟซบรอดแคสต์ของมัน หากไม่มี การพิมพ์ข้อความที่ไม่ใช่ ASCII จะถูกปฏิเสธพร้อมคำใบ้การติดตั้ง — ไม่มีวันพิมพ์ผิดแบบเงียบ ๆ ## อุปกรณ์จริง ไม่มีอะไรเทียบเท่า WebDriverAgent ที่ต้องบิลด์ ลงนาม เชื่อถือ หรือลงนามใหม่ทุกเจ็ดวัน เปิดการดีบักผ่าน USB เสียบโทรศัพท์ ยอมรับคำขออนุญาตบนหน้าจออุปกรณ์ แล้วมันก็ปรากฏใน `android_devices` โดยเครื่องมือทุกตัวใช้งานกับมันได้ อุปกรณ์ที่ยังไม่ได้รับอนุญาตจะถูกรายงานตามจริงพร้อมคำใบ้เรื่องคำขออนุญาต ไม่ใช่ความล้มเหลวลึกลับ ข้อจำกัดตามจริงสามข้อ: - **อัตราเฟรมต่ำกว่าเมื่อผ่าน USB** — ราว 2–5 fps กับโทรศัพท์ เทียบกับ 5–10 fps บนอีมูเลเตอร์ เพราะทุกเฟรมข้ามลิงก์ USB มาในรูป PNG เต็มภาพ - **การพิมพ์ CJK ต้องใช้ ADBKeyboard** (ดูด้านบน) เรื่องนี้กระทบทั้งอีมูเลเตอร์และโทรศัพท์เหมือนกัน - **`android_shutdown` ปิดเครื่องโทรศัพท์ไม่ได้** adb ไม่มีคำสั่งแบบนั้น เครื่องมือจึงบอกตรง ๆ แทนการเสแสร้ง ## ประสิทธิภาพ วัดบนอีมูเลเตอร์ (Android 14, 1080×2400): | | | | --- | --- | | ลูป screencap แบบถาวร | ≈ 8 fps | | เฟรมแรกของ `ensureStreaming` | ~200 ms | | การไป-กลับของ `input tap` | ~130 ms | โพรเซสลูกตัวเดียวที่คงอยู่ถาวรคือสิ่งที่ซื้อประสิทธิภาพนี้มา: การสร้าง `adb` หนึ่งตัวต่อเฟรมมีต้นทุน ~50–100 ms ก่อนที่พิกเซลใด ๆ จะขยับ คาดหวังได้ราว ~5–10 fps บนอีมูเลเตอร์ และ ~2–5 fps บนโทรศัพท์ผ่าน USB ขึ้นอยู่กับเครื่องและความหนาแน่นของหน้าจอ ## ติดตั้งลงใน DSH ```sh dsh plugin --profile web add @zseven-w/dsh-android@latest dsh web ``` หรือเพิ่มเป็น dependency ของแพ็กเกจโปรไฟล์ที่มีอยู่แล้ว: ```sh pnpm add @zseven-w/dsh-android ``` ## เริ่มต้นอย่างรวดเร็ว 1. **ค้นหาอุปกรณ์** — "แสดงรายการอุปกรณ์ Android" → `android_devices` 2. **เริ่มสตรีม** — "สตรีม emulator-5554" → `android_boot` แผงจะเปิดขึ้นพร้อมอุปกรณ์แบบสด (การส่งชื่อ AVD จะบูตอีมูเลเตอร์ตัวนั้นก่อน) 3. **แตะบนวิดีโอ** — แตะหรือลากบนแผงโดยตรง หรือให้เอเจนต์ขับเคลื่อน: "เปิดการตั้งค่า แล้วแตะ Display" → `android_interact` หรือ `android_ui_tree` + `android_tap_element` สำหรับการแตะตามเอกลักษณ์ หรือ `android_find_text` + `android_tap_text` เมื่อทรีมองไม่เห็น 4. **บิลด์และรันแอปของคุณ** — "บิลด์และรัน /path/to/MyApp" → `android_build_run` การบิลด์ Gradle เต็มใช้เวลาหลายนาที เมื่อเสร็จแล้วแอปจะเปิดขึ้นและคุณดูมันสด ๆ ในแผงได้ 5. **อ่านล็อก** — "แสดง logcat สองนาทีล่าสุดของ com.example.app" → `android_logs` ## การแก้ปัญหา - **เครื่องมือทุกตัวบอกว่า adb ใช้ไม่ได้** — ข้อผิดพลาดจะระบุลำดับการแก้ไขทั้งสามชั้น ตั้ง `ADB=/path/to/adb` วาง `adb` ไว้บน `PATH` หรือติดตั้ง platform-tools ของ SDK (`sdkmanager "platform-tools"`) - **อุปกรณ์อยู่ในสถานะ `unauthorized`** — ยอมรับคำขออนุญาตการดีบัก USB บนหน้าจออุปกรณ์ `android_devices` รายงานสถานะตามจริงแทนการซ่อนอุปกรณ์ - **`android_boot` หา AVD ไม่เจอ** — ค้นหาตัวเปิด `emulator` ไม่พบ ให้เปิดอีมูเลเตอร์ด้วยวิธีใดก็ได้ มันจะปรากฏใน `android_devices` ทันทีที่ adb มองเห็น แล้ว `android_boot` ก็รับ serial ของมันไปใช้ได้ - **ข้อความที่ไม่ใช่ ASCII ถูกปฏิเสธ** — ติดตั้ง ADBKeyboard แล้วเลือกเป็นวิธีป้อนข้อมูล (ดูหัวข้อข้อกำหนด) การปฏิเสธนี้ตั้งใจ: `input text` จะทิ้งหรือทำให้อักขระเพี้ยนแบบเงียบ ๆ - **`android_find_text` บอกว่า OCR ใช้ไม่ได้** — OCR ต้องใช้โฮสต์ macOS (เฟรมเวิร์ก Vision ของ Apple) เครื่องมืออีก 17 ตัวที่ไม่ใช่ OCR ทำงานได้ทุกที่ - **สตรีมหยุดเอง** — นั่นคือนโยบายเมื่อว่าง ไม่ใช่การล่ม: เมื่อไม่มีผู้บริโภคเลย (แผงปิด ไม่มีการ์ดติดตั้ง ไม่มีเส้นทางทำงาน) สตรีมจะหยุดหลัง 5 นาที และเริ่มใหม่ในการเรียกเครื่องมือครั้งถัดไปหรือเมื่อเปิดแผง ลูปที่ล่มจะเริ่มใหม่เองภายใน ~5 วินาที - **การหมุนดูผิดเพี้ยนบนหน้าจอโฮม** — ตัวเรียกแอปและการตั้งค่าตรึงตัวเองไว้ที่แนวตั้งและเพิกเฉยต่อ `user_rotation` นั่นคือพฤติกรรมปกติของ Android ไม่ใช่บั๊กของปลั๊กอิน ให้หมุนภายในแอปที่อนุญาต ## การพัฒนา ```sh pnpm install pnpm run build # host tsc + client bundle → lib/ pnpm run typecheck pnpm test # every static suite; no device required ``` การทดสอบสโมกใน `scripts/` ตรวจสอบ `lib/` ที่บิลด์แล้ว ทั้งหมดเป็นแบบคงที่ยกเว้น `dev-emulator-smoke.mjs` ซึ่งต้องใช้อุปกรณ์และรายงาน SKIP (exit 0) เมื่อไม่มีอุปกรณ์ | สคริปต์ | สิ่งที่ครอบคลุม | | --- | --- | | `node scripts/dev-adb-smoke.mjs` | การแก้ไข adb (env / PATH / SDK) กับไบนารีจำลอง การแยกวิเคราะห์ `devices -l` `exec-out` ที่ปลอดภัยกับข้อมูลไบนารี ตัวแยกเฟรม PNG และการรีซิงก์ของมัน การหลีกอักขระใน input text และวงจรชีวิตของโฮสต์ (สตรีม ควบคุม หยุดเมื่อว่าง dispose) กับทูลเชนปลอม | | `node scripts/dev-routes-static-smoke.mjs` | เส้นทางที่ลงนามกับโฮสต์ปลอม: grant แบบสัมพัทธ์ โทเคนที่หมดอายุ/ปลอม/ผิดชนิด รั้วลูปแบ็ก ซอง 405/415/400 การปฏิเสธอุปกรณ์แบบมีรหัส การตรวจสอบ `/control` รูปทรงของการหมุน การบรรจุอยู่ของสกรีนช็อต และสตรีม multipart สด | | `node scripts/dev-tools-smoke.mjs` | เครื่องมือหลักกับโฮสต์ปลอมผ่านตะเข็บ `createAndroidTools` | | `node scripts/dev-uitree-smoke.mjs` | เครื่องมือ UI ทรีและแถวรายการ: การแยกวิเคราะห์ XML ของ `uiautomator` ตัวเลือก การจำกัดความลึก ฮิวริสติกของแถวและตัวนับ | | `node scripts/dev-logs-smoke.mjs` | snapshot/follow ของ `android_logs` ตัวกรอง ขีดจำกัด และการเก็บเกี่ยวโพรเซส | | `node scripts/dev-panel-smoke.mjs` | คอมโพเนนต์แผง โหมดขนาด สไตล์เฟรม ลอจิกด็อก/ตัวกระตุ้น/แคปซูล (SSR เท่านั้น) | | `node scripts/dev-emulator-smoke.mjs [serial]` | อุปกรณ์สด: เฟรมแรก อัตราเฟรมต่อเนื่อง การไป-กลับของการแตะ dispose | ## การแก้ปัญหา ### สตรีมว่างเปล่า / ขาวล้วนบนอีมูเลเตอร์ หากแผงสตรีมภาพขาวล้วน (หรือดำล้วน) ในขณะที่ `android_ui_tree` ยังมองเห็นองค์ประกอบ UI จริงอยู่ แสดงว่าการอ่านกลับเฟรมบัฟเฟอร์จาก GPU ของโฮสต์ที่อีมูเลเตอร์ใช้เสียบนเครื่องของคุณ (เป็นปัญหา gfxstream ที่รู้จักกัน บนโฮสต์ macOS บางเครื่อง — ตัว `screencap` เองคืนเฟรมว่างเปล่า เครื่องมือที่เกี่ยวกับหน้าจอทุกตัวจึงได้รับผลกระทบ) ให้เปิดอีมูเลเตอร์ใหม่ด้วยการเรนเดอร์ด้วยซอฟต์แวร์: ```bash emulator -avd -gpu swiftshader_indirect ``` หรือตั้ง `hw.gpu.mode=swiftshader_indirect` ใน `config.ini` ของ AVD นั้น อุปกรณ์จริง ไม่มีวันได้รับผลกระทบ ## แผนงานข้างหน้า - **แหล่งภาพที่ให้อัตราเฟรมสูงกว่า** ตะเข็บ `StreamSource` ถูกออกแบบให้เสียบเปลี่ยนได้โดยตั้งใจ: เส้นทาง `scrcpy-server` + WebCodecs H.264 จะเข้ามาแทนสตรีม PNG แบบต่อเฟรมได้โดยไม่ต้องแตะเส้นทาง เครื่องมือ หรือแผงเลย - **โหลดซ้ำทันทีของพรีวิว Compose** ฝาแฝดฝั่ง iOS สลับร้อนพรีวิว SwiftUI ในรูป dylib แต่ปัจจุบัน Compose ยังไม่มีพรีมิทีฟการสลับร้อนที่เทียบเท่า เรื่องนี้จึงยังเป็นรายการสำหรับอนาคต แทนที่จะเป็นของที่ปล่อยออกมาแล้วไม่เสถียร ## ระบบนิเวศ - [DSH iOS Simulator](https://github.com/ZSeven-W/dsh-ios) — สถาปัตยกรรมเดียวกันสำหรับ iOS ซิมูเลเตอร์และ iPhone ที่เชื่อมต่อ USB - [DSH Crew](https://github.com/ZSeven-W/dsh-crew) — มอบหมายงานให้เอเจนต์ DSH จาก Claude Code / Codex - [DSH Noema](https://github.com/ZSeven-W/dsh-noema) — หน่วยความจำระยะยาวสำหรับ DSH - [DSH OpenPencil](https://github.com/ZSeven-W/dsh-openpencil) — ตรวจดูและแก้ไขเอกสารออกแบบ `.op` ในบทสนทนา ## เครดิต & ใบอนุญาต - [Android SDK platform-tools](https://developer.android.com/tools/releases/platform-tools) (`adb`) — แก้ไขตอนรันไทม์ ไม่มีการแจกจ่ายซ้ำ: ใบอนุญาต SDK ของ Google ไม่อนุญาตให้แถมมาด้วย - [ADBKeyboard](https://github.com/senzhk/ADBKeyBoard) — Senzhk — IME บนอุปกรณ์ที่เป็นทางเลือก เบื้องหลังการพิมพ์ข้อความที่ไม่ใช่ ASCII (Apache-2.0 ไม่ได้แถมมาด้วย) - สถาปัตยกรรมและท่าทีด้านเส้นทางใช้ร่วมกับ [dsh-ios](https://github.com/ZSeven-W/dsh-ios) ซึ่งเป็นต้นทางที่ปลั๊กอินนี้ถูกพอร์ตมา - ดูประกาศฉบับเต็มที่ [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) **ใบอนุญาต**: MIT