README.th.md

August 21, 2026 · View on GitHub

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_appdevice, 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_textOCR หน้าจอปัจจุบันด้วยตัวช่วย Vision ที่ปลั๊กอินคอมไพล์ไว้ (การจดจำแม่นยำ zh-Hans + en-US) ใช้เมื่อ UI ทรีว่างหรือเสื่อม สำหรับข้อความที่วาดเป็นกราฟิก (ตัวเลขแบดจ์ ราคาที่ฝังในภาพ) หรือเพื่อตรวจสอบสิ่งที่อยู่บนจออย่างอิสระ คืนค่า {device, size, items:[{text, confidence, rect}]} โดย rect คือกล่องพิกเซลที่มีจุดกำเนิดซ้ายบน เรียงตามความมั่นใจ และจำกัดไว้ที่ ~40 KB ใช้ได้บนโฮสต์ macOS เท่านั้นdevice, query (สตริงย่อยไม่แยกตัวพิมพ์ใหญ่-เล็ก), min_confidence (ค่าเริ่มต้น 0.3)
android_tap_textOCR หน้าจอปัจจุบันแล้วแตะกึ่งกลางของข้อความที่ตรงที่สุด — กฎ ตรงทั้งหมด → มีอยู่ → รายชื่อผู้สมัคร ชุดเดียวกับ 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_backtracedevice, 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 ไม่บูตอะไรทั้งสิ้น มันเพียงเริ่มลูปเฟรมสำหรับอุปกรณ์ที่ออนไลน์อยู่แล้ว และปฏิเสธ (409 device_busy) ที่จะกระชากสตรีมไปจากอุปกรณ์อื่น การสลับอุปกรณ์ต้องใช้ท่าทาง /switch-device อย่างชัดแจ้ง ส่วนการบูต AVD ยังคงเป็นหน้าที่ของเครื่องมือ android_boot
  • Keep-alive และการหยุดเมื่อว่าง ลูปเฟรมที่ล่มจะเริ่มใหม่ในเบื้องหลัง (หน่วง ~5 วินาที) เมื่อไม่มีผู้บริโภคเลย สตรีมจะหยุดตัวเองหลัง 5 นาที การหยุดโดยตั้งใจไม่มีวันถูกขัดขวาง

ข้อกำหนด

  • Node ≥ 24.11.0
  • adb จาก platform-tools ของ Android SDK แก้ไขตามลำดับนี้: ตัวแปรสภาพแวดล้อม ADBadb บน 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

เริ่มต้นอย่างรวดเร็ว

  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 ไม่ใช่บั๊กของปลั๊กอิน ให้หมุนภายในแอปที่อนุญาต

การพัฒนา

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.mjssnapshot/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