DSH iOS

DSH iOS 模拟器

DeepSeek Harness 对话里嵌入一台实时、可交互的 iOS 模拟器——USB 连接的真机 iPhone 同样支持。
22 个智能体工具 • 侧边栏实时 MJPEG 面板 • 模拟器与 USB 真机 • 列表/信息流行级操作 • SwiftUI 预览热重载

npm: @zseven-w/dsh-ios · 当前插件版本:0.1.0-rc.3 · 已在 DSH 0.1.1-rc.1 验证

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

npm: @zseven-w/dsh-ios · 当前插件版本: 0.1.0-rc.3 · 已在 DSH 0.1.1-rc.1 验证


DSH iOS Simulator — a real iPhone inside the conversation

在 DSH 对话中直接操作真机 —— 左侧是 Agent 的工具调用,右侧是实时设备面板

## 为什么选择 DSH iOS 模拟器 DSH iOS 模拟器让智能体在对话里拥有一台真正的 iOS 模拟器,也让你亲眼看到画面。智能体可以启动设备、用 Xcode 工程或 Swift 包构建并运行 App、按无障碍身份或 OCR 文字驱动界面、读取统一日志,还能检查进程、调用栈与内存泄漏;与此同时,设备的实时画面会渲染在常驻的侧边栏面板里,你可以在视频上直接点按、拖拽、旋转、按 Home 键。同样的操作也能作用于 USB 连接的真机 iPhone:插件会在手机上构建并启动 WebDriverAgent,把控制与画面端口经回环隧道转发,把设备画面投进同一套面板、卡片与工具。整个过程不用图片内容块,也没有录屏文件——视觉数据只会通过 DSH webserver 签发的限时 URL 进入界面。 | | | | --- | --- | | 🖥️ **对话里的实时模拟器** | 已启动设备的 serve-sim MJPEG 画面,经签名后的 `/_dsh/dsh-ios/*` 路由代理进常驻的右侧面板——浏览器永远不会接触 serve-sim 的端口。 | | 📱 **USB 真机 iPhone** | `ios_real_start_wda` 在已连接的手机上构建并启动 WebDriverAgent,把控制(REST)与画面(MJPEG)端口经回环隧道转发;同一套面板、工具、卡片与状态胶囊即可驱动真机。设备必须处于解锁状态,真机账户上的每一次点按都受插件的“先识别、再点按”规则约束。 | | 🛠️ **22 个智能体工具** | 设备列表、启动/关闭、截图、交互、构建运行、统一日志、基于 AXe 的 UI 树与按元素点击、列表/信息流行级操作、Vision OCR 找字/点字、SwiftUI 预览热重载、进程列表、调用栈、泄漏分析、App 信息。 | | 👆 **可交互面板** | 在实时画面上点按、拖拽;Home / 旋转 / 截图 / 刷新图标工具栏(悬停提示);尺寸模式(适应 · 50–125% · S/M/L);边框样式(无框 / 边框 / 真机框);拖拽调宽上限 960px、双击复位;横屏自动加宽。 | | 🧾 **列表与信息流行** | `ios_sim_ui_rows` 把深层无障碍快照转成带索引、标签与通用解析计数器的行;`ios_sim_tap_row` 在行内按相对坐标点按,并用计数器符合预期的 ±1 变化验证操作是否生效——这是列表类 App 唯一可靠的确认方式。 | | 🔐 **仅回环的传输** | serve-sim 只绑定 127.0.0.1 的专属端口段;每条路由都要求回环对端、回环 `Host` 与 Fetch-Metadata/Origin 校验;HMAC 能力令牌 10 分钟内过期。。 | | ⚡ **SwiftUI 预览热重载** | `ios_sim_preview` 在包之外生成一次性宿主 App,把你的预览编译成 dylib,编辑后无需重启即可热替换进正在运行的模拟器(约 2–5 秒)。 | | 🧭 **语义化 UI 自动化** | `ios_sim_ui_tree` 导出无障碍元素树(基于 AXe),`ios_sim_tap_element` 按标签或标识符点击;当元素树为空或退化时,`ios_sim_find_text` 直接对屏幕做 OCR,`ios_sim_tap_text` 点击命中的文字——按身份或按文字点击,而不是猜坐标。 | ## 工具 全部 22 个工具在任何主机上都会注册,且只返回纯 JSON——视觉数据只通过 `presentationMeta` + 签名路由进入界面,绝不以图片块形式返回。模拟器 udid 自动走 simctl/serve-sim,真机 udid 自动走 WebDriverAgent。非 macOS 主机(或 serve-sim 无法解析)上工具仍然注册,但调用时会返回明确的错误;唯一的例外是 `ios_sim_preview` 的 `status`,它在任何主机上都会如实返回 `{ running: false }`。 ### 核心模拟器工具 | 工具 | 作用 | 关键参数 | | --- | --- | --- | | `ios_sim_devices` | 列出这台 Mac 上可用的 iOS 模拟器设备(udid、名称、运行时、状态)以及哪些已启动,另外在 `realDevices` 里列出 USB 连接的真机 iPhone(udid、名称、osVersion、model、state、developerMode)。先用它发现要传给其他工具的 udid 或名称。 | — | | `ios_sim_boot` | 启动指定设备并开始其 serve-sim 实时推流;推流在对话期间保持存活,面板可以实时展示模拟器。 | `udid`(必填——udid 或设备名称) | | `ios_sim_shutdown` | 关闭指定设备;若推流目标正是该设备,则同时停止推流。 | `udid`(必填) | | `ios_sim_screenshot` | 截取一张 PNG,返回简短的 JSON 摘要(路径、字节数、尺寸、设备);图片在卡片/面板中渲染,绝不会以图片块形式返回。正在推流的模拟器与 USB 连接的真机(经 WebDriverAgent)都可以截。 | `udid`(可选——默认取正在推流的设备,其次取第一个已启动的模拟器) | | `ios_sim_interact` | 与正在推流的设备交互——模拟器或 USB 真机均可:在 0..1 归一化坐标上点按、输入文字(模拟器为美式键盘)、按下硬件按键(`home`、`lock`、`volumeUp`…)、滚动或发送触摸手势;操作稳定后(约 300 毫秒)附带一张新截图展示效果。 | `action`(必填——`tap`/`type`/`button`/`gesture`/`scroll`),`x`/`y`、`text`、`name`、`json` | | `ios_sim_list_apps` | 列出模拟器或已连接真机上**已安装**的 App(bundle id、显示名、版本、是否系统 App)——第三方 App 的 bundle id 无法猜测,先列出它,或给 `ios_sim_launch_app` 传 `name`。列举失败会抛错(例如“设备当前无法通过 CoreDevice 访问”)而不是返回空列表,所以 `count: 0` 一定意味着设备上确实没有匹配的 App。 | `udid`(可选)、`query`(对显示名与 bundle id 同时做不区分大小写的子串匹配,支持中文)、`include_system`(默认 false) | | `ios_sim_launch_app` | 启动已安装的 App(模拟器或已连接真机均可):既可以传 `bundleId`,也可以传 `name`(对显示名做不区分大小写的子串匹配,走同一套列举逻辑,支持中文)。两者只能给其一;启动失败或名称有歧义时,错误里会直接给出下一步该怎么做(从源码构建请用 `ios_sim_build_run`)。 | `bundleId` 或 `name`(二选一)、`udid`、`relaunch` | | `ios_sim_build_run` | 为模拟器构建 `.xcodeproj`、`.xcworkspace` 或 Swift 包,安装生成的 `.app` 并启动;真机 udid 则改为在手机上构建、安装并启动(需要 Apple Development 签名)。构建失败时返回过滤后的 `xcodebuild` 报错尾部。完整构建通常需要几分钟。 | `projectPath`(必填)、`scheme`、`udid`(推流设备 → 已启动设备 → 最新运行时 iPhone,会自动启动)、`configuration`(默认 `Debug`) | | `ios_real_start_wda` | 在 USB 连接的真机 iPhone 上启动 WebDriverAgent(WDA)——仅限真机,绝不用于模拟器。若已有 WDA 在响应则直接接管,否则执行 `xcodebuild` 构建/启动(冷构建可能耗时数分钟),然后等待 WDA 就绪并返回实时面板所用的控制/MJPEG 端口。当 `ios_sim_screenshot` / `ios_sim_interact` / `ios_sim_ui_tree` / `ios_sim_tap_element` 报告该设备 WDA 未运行时,先调用本工具。 | `udid`(必填——来自 `ios_sim_devices.realDevices` 的真机 udid) | ### UI 树工具(基于 AXe) | 工具 | 作用 | 关键参数 | | --- | --- | --- | | `ios_sim_ui_tree` | 导出前台 App 的无障碍元素树(标签、标识符、取值、以点为单位的 frame)以及屏幕尺寸(点)——模拟器走 AXe,USB 真机走 WebDriverAgent(真机默认限制快照深度:繁忙 App 的不限深快照实测约 32 秒 / 751 KB,限深后约 2 秒);输出上限约 40 KB(超出时裁掉最深层级,并置 `truncated` + 提示)。 | `udid`(可选)、`max_depth`、`filter`(对标签/标识符/类型做不区分大小写的子串匹配) | | `ios_sim_tap_element` | 按身份点击元素——先精确匹配,再做不区分大小写的子串匹配(`identifier`/`label`);嵌套重复元素折叠为同一个目标,若有多个不同元素匹配则逐一列出候选。点击落在元素中心(模拟器走 AXe HID,真机走 WebDriverAgent),随后约 300 毫秒截一张效果图;传 `expect_text` / `expect_gone` 则点击与验证合并为一次往返(`expected.matched`)。 | `udid`(可选)、`identifier`、`label`、`expect_text`、`expect_gone` | ### 列表与信息流行 列表/信息流类 App 把每条内容聚合进一个无障碍 Cell——标签里包含整条摘要与全部计数器(“57 回复。18 喜欢。592 次查看”),没有可以匹配的逐控件子按钮,而且这些行只有在深层快照里才会出现。下面两个工具把这种结构暴露为“行”,并在行内操作。 | 工具 | 作用 | 关键参数 | | --- | --- | --- | | `ios_sim_ui_rows` | 把前台 App 可见的列表/信息流行读成“行”而不是原始树:每一行包含索引、以点为单位的 frame、聚合标签,以及从标签里通用解析出的计数器(数字 + 分类词,如 `57 回复` → 回复=57,中文或英文——不内置任何 App 词汇)。行只有在深层快照里才出现:真机上默认 `max_depth` 为 60,每次调用约 15–25 秒 / ~0.5 MB(WDA 串行处理请求)——先考虑廉价的观察手段(`ios_sim_find_text` / `ios_sim_ui_tree`)。计数器按启发式解析、键值可原样回传:给 `ios_sim_tap_row.expect_count` 传键时务必与列表完全一致。找不到行时结果会说明原因(深度太浅 / 不是列表页 / 深度读取后确实没有无障碍信息)——浅读绝不会被报告成“该 App 没有无障碍信息”;屏幕外的行会被排除并计入 `omittedOffscreen`。 | `udid`(可选)、`max_depth`(仅真机生效;默认 60) | | `ios_sim_tap_row` | 在一条可见列表行内按相对位置点按(行由 `ios_sim_ui_rows` 报告:0 基索引;x/y 为该行 frame 的比例——0 = 左/上边缘,1 = 右/下边缘,默认 0.5 = 中心),模拟器走 AXe、USB 真机走 WebDriverAgent。行的 frame 来自一次全新的树读取,绝不猜测绝对屏幕坐标;索引越界直接失败(绝不截断收拢)。安全闸:传 `expect_count={key,delta}` 时工具会重新读取行标签,校验计数器恰好变化 +1/−1(`countCheck.verified`);若键不在该行解析出的计数器里,点按会在执行前被拒绝——真机上的点按绝不是试探。不传 `expect_count` 时点按仍会执行(明确的相对位置本身就是定位),但不会有任何验证。 | `udid`(可选)、`index`(必填)、`x`、`y`(0..1 比例)、`max_depth`、`expect_count`(`{key, delta}`) | ### OCR 工具(Vision) | 工具 | 作用 | 关键参数 | | --- | --- | --- | | `ios_sim_find_text` | 用插件编译的 Vision 助手对已启动模拟器或 USB 真机的当前屏幕做 OCR(识别准确,zh-Hans + en-US,首次使用由 `swiftc` 编译进 `~/Library/Caches/dsh-ios/bin/ocr`)。适用于无障碍树为空或退化、文字以图形渲染(角标数字、嵌进图片的价格)或需要独立核对屏幕内容的场景。先截取一张新截图,再返回 `{device, size, items:[{text, confidence, rect}]}`——rect 是以设备点为单位的框(原点在左上),按置信度排序,输出上限约 40 KB(`truncated` 表示丢掉了置信度最低的尾部;可用 `query` 收窄或调高 `min_confidence`)。 | `udid`(可选)、`query`(不区分大小写的子串)、`min_confidence`(默认 0.3) | | `ios_sim_tap_text` | 对当前屏幕做 OCR 并点按最佳文字匹配的中心——沿用与 `ios_sim_tap_element` 相同的“先精确、再忽略大小写包含、多候选报歧义”规则,适用于无障碍树看不到的文字(无 a11y 的 App、角标数字、嵌进图片的文字)。真机上通过 WebDriverAgent 落在设备绝对坐标;模拟器上经 serve-sim 控制以归一化坐标下发(先调用 `ios_sim_boot`)。约 300 毫秒后附一张新截图展示效果;传 `expect_text` / `expect_gone` 则点击与验证合并为一次往返(`expected.matched`)。在真机上每一次点按都有真实后果——绝不靠点按来试探一个未识别的控件。 | `udid`(可选)、`query`(必填)、`min_confidence`、`expect_text`、`expect_gone` | | `ios_sim_wait_for` | 等待某段文字在屏幕上出现或消失:复用 `ios_sim_find_text` 的截图+OCR 流水线轮询,直到条件成立或超时(默认 8 秒,上限 60 秒)。超时是正常的 `matched:false` 结果,绝不抛错——一次调用替代手动循环 find_text(真机上每轮约 1.2 秒)。命中时 `item` 携带 OCR 文字、置信度与设备点坐标框。 | `udid`(可选)、`text`(必填)、`mode`(`appear`/`disappear`)、`timeout_ms`、`min_confidence` | ### 日志工具 | 工具 | 作用 | 关键参数 | | --- | --- | --- | | `ios_sim_logs` | 从设备统一日志读取 App 运行时的输出:`snapshot`(`log show --last `,默认 2m)或 `follow`(有界实时捕获 `duration_seconds`,默认 10 秒、上限 60——绝不会挂起不返回)。输出上限约 300 行 / 30 KB,并附收窄提示。 | `udid`(可选)、`mode`(`snapshot`/`follow`)、`duration`、`duration_seconds`、`bundle_id`、`predicate`(原始 NSPredicate,优先于 `bundle_id`)、`level`(`default`/`info`/`debug`)、`grep` | ### 预览工具 | 工具 | 作用 | 关键参数 | | --- | --- | --- | | `ios_sim_preview` | 在模拟器里实时热重载 SwiftUI 预览:`start`(默认)校验包、在插件缓存里生成一次性宿主 App(绝不会写进你的包)、把包编译为模拟器 dylib、安装并启动宿主、然后监听源码——每次编辑都会重新构建并热替换,无需重启(约 2–5 秒)。编译错误不会杀死会话:宿主保留最后一次成功的预览,错误尾部通过 `status` 返回;同一时间只能运行一个预览会话。 | `packagePath`(`start` 时必填)、`udid`、`action`(`start`/`status`/`stop`)、`previewFilter`(对预览名称做不区分大小写的子串匹配) | ### 调试工具 | 工具 | 作用 | 关键参数 | | --- | --- | --- | | `ios_sim_processes` | 从模拟器自身的 launchd 列出其运行中的 App 进程(宿主机可见的 pid、进程名、bundle id)——backtrace/leaks 的 pid 来源;真机 udid 则改经 devicectl 列出手机上的进程。 | `udid`(可选)、`filter`(对进程名/bundle id 做不区分大小写的子串匹配) | | `ios_sim_backtrace` | 一次性批量 LLDB(attach → thread backtrace → detach,绝非常驻会话);输出上限约 200 行、主线程在前,目标进程必定被验证已恢复运行。当 macOS 拒绝 attach(开发者模式未开启)时,回退到 Xcode 的 `sample` 引擎(不挂起进程)并给出开启提示。仅支持模拟器——真机会被明确拒绝并说明原因。 | `udid`(可选)、`pid` / `bundle_id`、`all_threads`(默认 true) | | `ios_sim_leaks` | 用 Xcode 的 `leaks` 工具分析泄漏:`summary`(泄漏数、泄漏总字节、前约 30 种泄漏类型)或 `memgraph`(生成 `.memgraph` 工件,用 Xcode Instruments 打开,插件绝不解析)。扫描期间 App 会被挂起,但之后必定恢复。仅支持模拟器。 | `udid`(可选)、`pid` / `bundle_id`、`mode`(`summary`/`memgraph`) | | `ios_sim_app_info` | 读取已安装 App 的信息:App 包路径、可写数据容器、Info.plist 关键字段——模拟器走 `simctl appinfo`(附 `get_app_container` 回退),USB 真机走 `devicectl`;未安装时返回 `installed: false`,并在 `note` 中提示改用 `ios_sim_list_apps`。 | `udid`(可选)、`bundle_id`(必填) | ## 展示面 - **侧边栏面板——“iOS 模拟器”。** 实时画面位于常驻的右侧面板(固定停靠、把对话区让开;窄视口下退化为居中浮层)。面板渲染实时 MJPEG 画面,支持在视频上直接点按、拖拽手势,并有图标工具栏(Home、截图、旋转、刷新),按钮带悬停提示。尺寸控制提供**适应**(铺满面板宽度)、**50–125%**(按设备逻辑宽度缩放)以及 **S / M / L** 预设(按设备短边定尺寸;横屏时按显示比例缩放,保持设备物理大小)。边框样式为**无框 / 边框 / 真机框**(frameless / bezel / 逼真的设备外壳),圆角按比例计算。设备旋转为横屏时面板自动加宽到舒适尺寸,转回竖屏时恢复你原来的宽度——横屏期间你手动拖过宽度则以你的选择为准。左侧边缘的把手可以拖宽/收窄面板(上限 960px;双击恢复默认宽度)。当推流目标是 USB 连接的 iPhone 时,同一个面板显示手机经 WebDriverAgent 的 MJPEG 画面,操作方式完全一致。 - **紧凑对话卡片。** 工具结果渲染为单行卡片,不含任何内联图片:统一的**“iOS 模拟器”**标题 + 操作副标签(启动 / 截图 / 交互 / 构建运行 / 启动 WebDriverAgent)+ 设备名 + 状态徽标 + “在侧边栏打开”提示。点击卡片所在行即可打开面板;点击按钮、链接或实时画面本身不会触发。 - **输入框上方的状态胶囊。** 面板关闭且推流在线时,输入框上方会出现一个绿点小胶囊(`<设备名> · 实时`),点击即可打开面板。它受会话门控:只有当当前会话里挂载着模拟器结果时才渲染并轮询,切换到没有结果的会话即自动隐藏。 - **标准模式与 Code 模式。** 标准会话使用宿主下发的 `presentationMeta`;Code 模式(PTC)的嵌套调用不会携带 meta,客户端会从结果中的完整 JSON 重建出完全一致的 meta——面板、卡片和状态胶囊在两种模式下都能工作。 ## 安全 - 浏览器永远不会接触 serve-sim 的端口。所有流量都经由 DSH webserver 源站上的 `/_dsh/dsh-ios/*` 路由:`/stream/`(MJPEG 代理)、`/screenshot/`(缓存 PNG)、`/ws?token=…`(HID 控制转发),以及 `/grant`、`/capture`、`/status` 端点。 - 令牌是 HMAC-SHA256 能力凭证(`base64url(payload).base64url(mac)`),10 分钟内过期,用每个 DSH 主目录私有的密钥签名(`/cache/dsh-ios/stream-access.key`,0600,原子创建)。 - 每条路由在检查任何能力之前先应用回环/可信传输围栏:回环对端地址、回环 `Host`(拒绝 DNS 重绑定)、Fetch-Metadata/Origin 校验。截图路由只提供插件缓存目录内的文件(拒绝符号链接,并做 `realpath` 包含性校验)。 - serve-sim 以前台子进程方式运行,仅绑定回环地址的专属端口段(3181–3244),绝不会动用户自己在 3100 端口上的 serve-sim;从不使用 `--host`。。 - **孤儿进程收养/回收**——若上一个 DSH 宿主被异常杀死、其 serve-sim 子进程存活了下来:同一设备会被直接收养(孤儿进程的握手信息视为权威);若残留进程占用槽位却服务着别的设备,则通过 `serve-sim -k` 回收并重试一次。 - **保活与空闲停止**——推流崩溃后约 5 秒会在后台自动重启;当没有消费者时,空闲 5 分钟自动停止。主动停止绝不会被保活逻辑对抗。(真机 runner 有意豁免空闲回收:重启它意味着一次数分钟的 `xcodebuild` 重新构建。) ## 环境要求 - **macOS + 完整版 Xcode**——仅装 Command Line Tools 不够。`xcodebuild`、`xcrun simctl` 和模拟器运行时都随 Xcode 提供。 - **Xcode 中至少安装一个 iOS 模拟器运行时**。 - **DSH ≥ 0.1.0-rc.6 且使用 Web 版**,才能显示面板。无头(headless)配置下插件同样可用:22 个工具照常工作,只是没有实时画面。 - **非 macOS 主机**:插件依然能加载,22 个工具也会注册,但每次调用都会返回明确的错误信息(`iOS Simulator requires macOS with Xcode …`)。 - **serve-sim** 作为本插件的 npm 依赖随包安装,正式安装时会从本地解析;开发目录则回退到 `npx -y serve-sim`(首次使用需要联网)。 - **AXe**(可选——只有基于 AXe 的工具需要:`ios_sim_ui_tree` / `ios_sim_tap_element`,以及模拟器上的 `ios_sim_ui_rows` / `ios_sim_tap_row`):`brew install cameroncooke/axe/axe`,或让插件自动下载固定版本(v1.8.0,校验 SHA-256)到 `~/Library/Caches/dsh-ios/bin`。`DSH_IOS_AXE_BIN` 可覆盖解析结果;`DSH_IOS_AXE_OFFLINE=1` 可禁用下载。 - **Vision OCR**(可选——只有 `ios_sim_find_text` / `ios_sim_tap_text` 需要):插件首次使用时用 `swiftc` 把内置的 `assets/ocr.swift` 编译到 `~/Library/Caches/dsh-ios/bin/ocr`(识别 zh-Hans + en-US)。 - **lldb attach 需要 macOS 开发者模式**:执行一次 `sudo DevToolsSecurity -enable`。在此之前 `ios_sim_backtrace` 会改用 Xcode 的 `sample` 引擎(不挂起进程),`ios_sim_leaks` 会带着开启提示降级运行。。首次 WDA 构建会安装签名的 WebDriverAgentRunner:按提示在设备上信任其证书;免费团队签名描述文件 7 天过期后需重新运行 `ios_real_start_wda`。 ## 安装到 DSH ```sh dsh plugin --profile web add @zseven-w/dsh-ios@latest dsh web ``` ## 快速开始 一次典型的对话流程: 1. **发现设备**——“列出可用的模拟器。” → `ios_sim_devices`。 2. **启动**——“启动 iPhone 17 Pro。” → `ios_sim_boot`。推流开始,**“iOS 模拟器”面板**随之打开:设备在侧边栏里实时显示。(点击任意模拟器卡片所在行,或输入框上方的状态胶囊,即可重新打开。) 3. **在画面上点按**——直接在面板上点按、拖拽;或让智能体驱动界面:“打开设置,然后点 General。” → `ios_sim_interact`(按元素身份点击用 `ios_sim_ui_tree` + `ios_sim_tap_element`;按文字点击用 `ios_sim_find_text` + `ios_sim_tap_text`;列表/信息流 App 用 `ios_sim_ui_rows` + `ios_sim_tap_row`)。 4. **构建并运行你的 App**——“构建并运行 /path/to/MyApp.xcodeproj。” → `ios_sim_build_run`。完整构建需要几分钟;完成后 App 会在模拟器里启动,你可以在面板里实时观看。 5. **预览热重载**——“显示 /path/to/MyPackage 的 SwiftUI 预览。” → `ios_sim_preview start`。修改源码后,预览会在约 2–5 秒内热替换进正在运行的模拟器——无需重启。 6. **驱动真机 iPhone**——用 USB 数据线连接手机并解锁,然后说“在手机上启动 WebDriverAgent。” → `ios_real_start_wda`。面板切到手机的实时画面,所有工具都接受其 `realDevices` 里的 udid;调用失败时读面板状态里的编码原因(`device-locked`、`cert-untrusted`、`profile-expired`、`tunnel-failed`、`device-unplugged`)。 ## 疑难排查 - **backtrace 用的是 `sample` 而不是 lldb,或 leaks 提示受限检查**——macOS 开发者模式未开启。执行一次 `sudo DevToolsSecurity -enable` 后重试。在此之前工具会平滑降级:`ios_sim_backtrace` 回退到 Xcode 的 `sample`(已符号化、不挂起进程),`ios_sim_leaks` 会给出开启提示。 - **`ios_sim_ui_tree` / `ios_sim_tap_element` 需要 AXe**——用 `brew install cameroncooke/axe/axe` 安装,或让插件在首次使用时自动下载固定版本(需要能访问 github.com)。错误信息里始终附带完整的安装提示;`DSH_IOS_AXE_BIN=/path/to/axe` 可覆盖解析结果。行工具(`ios_sim_ui_rows` / `ios_sim_tap_row`)在模拟器上同样需要 AXe。 - **`ios_sim_find_text` / `ios_sim_tap_text` 报告缺少 OCR 助手**——首次使用会用 `swiftc`(需要 Xcode)把内置的 `assets/ocr.swift` 编译到 `~/Library/Caches/dsh-ios/bin/ocr`;错误信息里带具体路径与提示。 - **`ios_sim_ui_rows` 找不到行**——结果会说明原因:深度太浅(调大 `max_depth`;真机上每次更深快照约 15–25 秒)、不是列表页,或深度读取后确实没有无障碍信息。浅读绝不会被误报为“缺少无障碍支持”。 - **iOS 26.2 模拟器上的 `ios_sim_leaks` 怪癖**——在 iOS 26.2 运行时上,即使开发者模式已开启,Xcode 的 `leaks` 也可能无法分析模拟器进程,报出 `Failed to get DYLD info` 或 minimal-corpse 之类的致命诊断。工具会平滑降级:你能看到原始诊断,目标进程必定被验证恢复,不会卡住。插件侧没有修复办法——遇到时试试 `mode: "memgraph"` 或换一个运行时。。 - **推流自己停了**——这是空闲策略,不是崩溃:没有消费者(面板关闭、没有挂载的卡片、没有活跃路由)时,推流会在 5 分钟后停止,并在下一次工具调用或打开面板时重启。崩溃的推流则会在约 5 秒内于后台自动重启。 ## 开发 ```sh pnpm install pnpm run build # 宿主 tsc + 客户端打包 → lib/ pnpm run typecheck ``` `scripts/` 下的冒烟测试会验证编译产物 `lib/`(需要启动模拟器或连接真机 USB 的部分仅限 macOS;设置 `DSH_IOS_SMOKE_SKIP_SIM=1` 可跳过这些部分): | 脚本 | 覆盖内容 | | --- | --- | | `node scripts/dev-smoke.mjs` | 模拟器宿主:二进制解析、推流启动、控制、保活、dispose。 | | `node scripts/dev-tools-smoke.mjs [--full-build]` | 在真实模拟器上验证核心工具(加 `--full-build` 还会执行一次真实构建)。 | | `node scripts/dev-routes-smoke.mjs` | 签名 Web 路由:grant、推流代理、截图、ws 转发、围栏、过期。 | | `node scripts/dev-card-smoke.mjs` | 客户端卡片:静态 SSR(断言无 ``)、status/capture 契约、近实时的网络部分。 | | `node scripts/dev-panel-smoke.mjs` | 面板组件、尺寸模式、边框样式、停靠/触发/胶囊逻辑(纯静态)。 | | `node scripts/dev-logs-smoke.mjs` | `ios_sim_logs` 的 snapshot/follow、过滤器、上限、进程回收。 | | `node scripts/dev-uitree-smoke.mjs` | UI 树工具:AXe 解析/下载管线、选择器、真实模拟器上的树与点击。 | | `node scripts/dev-debug-smoke.mjs` | 调试工具:进程、调用栈(lldb + sample)、泄漏、App 信息。 | | `node scripts/dev-preview-smoke.mjs` | 预览热重载:启动、编辑 → 不重启的热替换、错误恢复、停止。 | | `node scripts/dev-orphan-smoke.mjs` | 宿主被异常杀死后孤儿 serve-sim 的收养/回收。 | | `node scripts/dev-ocr-smoke.mjs` | Vision-OCR 工具:助手解析、swiftc 编译缓存、识别管线、tap-text 路由。 | | `node scripts/dev-wda-smoke.mjs` | WebDriverAgent 宿主:`ServerURLHere` 解析、失败分类、隧道、保活(mock;可选实测)。 | | `node scripts/dev-realdevice-smoke.mjs` | 对 USB 连接的 iPhone 执行 `xcrun devicectl`——工具所用的真实代码路径。 | | `node scripts/dev-realstart-smoke.mjs` | `/real-start` 路由:围栏、编码拒绝、构建/启动门控(纯静态)。 | | `node scripts/dev-realtools-smoke.mjs` | `ios_sim_screenshot` / `ios_sim_interact` / `ios_sim_ui_tree` / `ios_sim_tap_element` 的真机后端,以及 `ios_real_start_wda`。 | ## 生态 - [DSH Android](https://github.com/ZSeven-W/dsh-android) — 在对话中运行 Android 模拟器或 USB 真机,全部由 adb 驱动 - [DSH Crew](https://github.com/ZSeven-W/dsh-crew) — 从 Claude Code / Codex 把任务派给 DSH agent - [DSH Noema](https://github.com/ZSeven-W/dsh-noema) — DSH 的长期记忆 - [DSH OpenPencil](https://github.com/ZSeven-W/dsh-openpencil) — 在对话中查看和编辑 `.op` 设计文档 ## 致谢与许可证 - [serve-sim](https://github.com/EvanBacon/serve-sim) —— Evan Bacon —— 模拟器推流引擎(Apache-2.0;随包安装的运行时依赖)。 - [AXe](https://github.com/cameroncooke/AXe) —— Cameron Cooke —— UI 树工具所依赖的无障碍 CLI(MIT)。 - [WebDriverAgent](https://github.com/appium/WebDriverAgent) —— 插件在真机上构建并启动的 WebDriver 服务器(BSD 许可)。 - 架构受 Codex 的 “Build iOS Apps” 插件启发;SwiftUI 预览引擎是对其公开文档所述方案的洁净室(clean-room)重实现,未复制任何 Codex 代码。 - 完整声明见 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)。 **许可证**:MIT