README.th.md
August 21, 2026 · View on GitHub
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
อุปกรณ์ 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 <package>: PSS รวม การแบ่ง Java/native/graphics และหมวดหมู่อันดับต้น ๆ — คำตอบฝั่ง Android สำหรับสรุปหน่วยความจำรั่ว | device, bundle_id (จำเป็น) |
android_app_info | ข้อมูลแอปที่ติดตั้งจาก dumpsys package <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/<token>(สตรีม multipart PNG สด),/screenshot/<token>(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 (<DSH_HOME>/cache/dsh-android/stream-access.key, โหมด 0600, สร้างแบบอะตอมมิก) ความสามารถที่ออกให้อุปกรณ์หนึ่งจะใช้ไม่ได้ทันทีที่อุปกรณ์อื่นเข้ายึดช่องสตรีม และความสามารถสำหรับสกรีนช็อตก็เล่นซ้ำกับเส้นทางสตรีมไม่ได้ - เส้นทางสกรีนช็อตให้บริการไดเรกทอรีเดียวเท่านั้น เส้นทางถูกเดินด้วย
lstat(ลิงก์สัญลักษณ์ใด ๆ ถูกปฏิเสธ) ปิดท้ายด้วยการตรวจการบรรจุอยู่ด้วยrealpathเปิดด้วยO_NOFOLLOWจำกัดขนาด และตรวจสอบซ้ำหลังอ่าน — ไฟล์ที่ถูกสลับเป็นซิมลิงก์ระหว่างการออกโทเคนกับการดึงจึงไม่มีวันถูกให้บริการ /grantไม่บูตอะไรทั้งสิ้น มันเพียงเริ่มลูปเฟรมสำหรับอุปกรณ์ที่ออนไลน์อยู่แล้ว และปฏิเสธ (409device_busy) ที่จะกระชากสตรีมไปจากอุปกรณ์อื่น การสลับอุปกรณ์ต้องใช้ท่าทาง/switch-deviceอย่างชัดแจ้ง ส่วนการบูต AVD ยังคงเป็นหน้าที่ของเครื่องมือandroid_boot- Keep-alive และการหยุดเมื่อว่าง ลูปเฟรมที่ล่มจะเริ่มใหม่ในเบื้องหลัง (หน่วง ~5 วินาที) เมื่อไม่มีผู้บริโภคเลย สตรีมจะหยุดตัวเองหลัง 5 นาที การหยุดโดยตั้งใจไม่มีวันถูกขัดขวาง
ข้อกำหนด
- Node ≥ 24.11.0
- adb จาก platform-tools ของ Android SDK แก้ไขตามลำดับนี้: ตัวแปรสภาพแวดล้อม
ADB→adbบนPATH→<ANDROID_HOME>/<ANDROID_SDK_ROOT>/รูท 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 บนอุปกรณ์แล้วเลือกเป็น 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
dsh plugin --profile web add @zseven-w/dsh-android@latest
dsh web
หรือเพิ่มเป็น dependency ของแพ็กเกจโปรไฟล์ที่มีอยู่แล้ว:
pnpm add @zseven-w/dsh-android
เริ่มต้นอย่างรวดเร็ว
- ค้นหาอุปกรณ์ — "แสดงรายการอุปกรณ์ Android" →
android_devices - เริ่มสตรีม — "สตรีม emulator-5554" →
android_bootแผงจะเปิดขึ้นพร้อมอุปกรณ์แบบสด (การส่งชื่อ AVD จะบูตอีมูเลเตอร์ตัวนั้นก่อน) - แตะบนวิดีโอ — แตะหรือลากบนแผงโดยตรง หรือให้เอเจนต์ขับเคลื่อน: "เปิดการตั้งค่า แล้วแตะ Display" →
android_interactหรือandroid_ui_tree+android_tap_elementสำหรับการแตะตามเอกลักษณ์ หรือandroid_find_text+android_tap_textเมื่อทรีมองไม่เห็น - บิลด์และรันแอปของคุณ — "บิลด์และรัน /path/to/MyApp" →
android_build_runการบิลด์ Gradle เต็มใช้เวลาหลายนาที เมื่อเสร็จแล้วแอปจะเปิดขึ้นและคุณดูมันสด ๆ ในแผงได้ - อ่านล็อก — "แสดง 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 ไม่ใช่บั๊กของปลั๊กอิน ให้หมุนภายในแอปที่อนุญาต
การพัฒนา
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 เองคืนเฟรมว่างเปล่า
เครื่องมือที่เกี่ยวกับหน้าจอทุกตัวจึงได้รับผลกระทบ) ให้เปิดอีมูเลเตอร์ใหม่ด้วยการเรนเดอร์ด้วยซอฟต์แวร์:
emulator -avd <name> -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 — สถาปัตยกรรมเดียวกันสำหรับ iOS ซิมูเลเตอร์และ iPhone ที่เชื่อมต่อ USB
- DSH Crew — มอบหมายงานให้เอเจนต์ DSH จาก Claude Code / Codex
- DSH Noema — หน่วยความจำระยะยาวสำหรับ DSH
- DSH OpenPencil — ตรวจดูและแก้ไขเอกสารออกแบบ
.opในบทสนทนา
เครดิต & ใบอนุญาต
- Android SDK platform-tools (
adb) — แก้ไขตอนรันไทม์ ไม่มีการแจกจ่ายซ้ำ: ใบอนุญาต SDK ของ Google ไม่อนุญาตให้แถมมาด้วย - ADBKeyboard — Senzhk — IME บนอุปกรณ์ที่เป็นทางเลือก เบื้องหลังการพิมพ์ข้อความที่ไม่ใช่ ASCII (Apache-2.0 ไม่ได้แถมมาด้วย)
- สถาปัตยกรรมและท่าทีด้านเส้นทางใช้ร่วมกับ dsh-ios ซึ่งเป็นต้นทางที่ปลั๊กอินนี้ถูกพอร์ตมา
- ดูประกาศฉบับเต็มที่ THIRD_PARTY_NOTICES.md
ใบอนุญาต: MIT