Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/ios-simulator-live.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: ios-simulator-live

# Live check of the iOS Simulator integration on a GitHub-hosted Mac with a
# booted simulator: real touch / keyboard / button input (dtuhidd on the
# xcode-27 image, CoreSimulator 1155.4+), screenshots, the JPEG stream, the
# helper's Seatbelt profile and the Desktop native view. Opt-in: run it
# manually or push a ci/ios-simulator-live* branch. See
# scripts/ios_simulator/live_check.py.

on:
workflow_dispatch:
inputs:
runner:
description: macOS runner label (xcode-27 covers the dtuhidd transport)
default: xcode-27
hid:
description: Force the HID transport (indigo or dtuhid); empty = automatic
default: ''
push:
branches: ['ci/ios-simulator-live**']

permissions:
contents: read

jobs:
live:
runs-on: ${{ inputs.runner || 'xcode-27' }}
timeout-minutes: 45
steps:
- uses: actions/checkout@v4

- name: Toolchain
run: |
sw_vers
xcodebuild -version
xcrun simctl list runtimes

- name: nlohmann/json header
run: |
mkdir -p "$RUNNER_TEMP/json/nlohmann"
curl -fsSL --retry 3 -o "$RUNNER_TEMP/json/nlohmann/json.hpp" \
https://github.com/nlohmann/json/releases/download/v3.12.0/json.hpp

- name: Live check (sandboxed helper)
env:
HID_TRANSPORT: ${{ inputs.hid }}
run: |
python3 scripts/ios_simulator/live_check.py \
--json-include "$RUNNER_TEMP/json" \
--out "$RUNNER_TEMP/ios-sim-live" \
${HID_TRANSPORT:+--hid "$HID_TRANSPORT"}

- name: Collect crash reports
if: always()
run: |
mkdir -p "$RUNNER_TEMP/ios-sim-live/crashes"
cp ~/Library/Logs/DiagnosticReports/acecode-ios-sim* \
~/Library/Logs/DiagnosticReports/ios_simulator_view* \
"$RUNNER_TEMP/ios-sim-live/crashes/" 2>/dev/null || true

- uses: actions/upload-artifact@v4
if: always()
with:
name: ios-simulator-live-${{ github.run_id }}
path: ${{ runner.temp }}/ios-sim-live
12 changes: 9 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ Daemon startup writes pid, port, guid, token, and heartbeat files under `<data_d

Loopback requests bypass daemon token auth. Non-loopback requests require `X-ACECode-Token` or `?token=`, and non-loopback dangerous mode is rejected. Keep [docs/daemon-api.md](docs/daemon-api.md) in sync for protocol changes.

远程 Web 代理连 daemon 时把上游源地址绑到 `127.0.0.2`,让 daemon 把转发进来的请求当成需要 token 的来源。macOS 的 lo0 只分配 127.0.0.1,绑定报 EADDRNOTAVAIL(asio 用自己的 `asio.system` 类别,要比 errno 值而不是 `std::errc`);此时改用本机的非回环 IPv4(优先远程客户端连进来的那个接口地址)作源地址。daemon 只听 127.0.0.1,这类来源只可能来自本机,照样要 token;代价是经代理的控制台(PTY)在 macOS 上被 loopback 门拒绝(Windows / Linux 上 127.0.0.2 仍算 loopback)。在此之前 macOS 上的远程 Web 是完全不通的。回归:`remote_web_proxy_test.cpp`、`web_server_smoke_test.cpp` 的两条真实代理用例。

The frontend has pure helpers under [web/src/lib/](web/src/lib) with Node-based tests. Prefer adding data-shaping logic there rather than embedding it directly in components.

### 消息通道(QQ / 微信 / 飞书 / 钉钉 / Telegram / Discord / LINE,openspec add-desktop-im-channels)
Expand Down Expand Up @@ -745,11 +747,15 @@ JS↔C++ bridge(同进程,webview `bind`,无 HTTP):
- **Xcode 选择**(`xcode_locator.cpp`):配置路径 > `DEVELOPER_DIR` > xcode-select(必须是完整 Xcode)> /Applications、~/Applications、Spotlight 里最新的。simctl / xcodebuild 直接执行 `<Developer>/usr/bin/…`,它们按自身位置解析 Xcode,所以**不要**改成 `xcrun`(那会回到 xcode-select)。SimulatorKit 在 Xcode 27 挪到 `Contents/SharedFrameworks`,两处都要找。
- **HID**:CoreSimulator < 1155.4 走 Indigo(`SimDeviceLegacyHIDClient` + `IndigoHIDMessageFor*`;单指触摸要手工重封成 0x140 字节两 payload 消息,第二个 payload 在 0xB0、field1=1/field2=2);≥ 1155.4(Xcode 27)走 dtuhidd 数字化器 XPC(`xpc_endpoint_create_mach_port_4sim`),guest 不再可靠接收 Indigo。手势/按键规划在 daemon(`input_plan.cpp`,纯函数有单测),时序在 helper 里执行。非 ASCII 文本走 `simctl pbcopy` + Cmd+V。
- **坐标 = 界面方向下的 point**:给模型的截图 1 像素 = 1 point 且转正,触摸在 helper 里反旋转。朝向来源:Xcode 27 runtime 用 guest 的 CoreDevice display-info(`com.apple.coredevice.feature.getdisplayinfo`);更老的 runtime 没有这个服务(405),退回 Simulator.app 偏好 `DevicePreferences.<udid>.SimulatorWindowOrientation`。无障碍坐标本来就是界面方向。
- **帧缓冲读取必须同步加锁**:`kIOSurfaceLockAvoidSync` 返回 `kIOReturnCannotLock`,无锁读到的是陈旧 CPU 副本(实测连续帧逐字节相同)。Intel Mac 上同步锁会等模拟器 GPU,动画期间能卡数秒 —— `simctl io screenshot` 同样卡,是平台特性;所以截图/推流在每设备独立队列,和输入互不阻塞;缩放旋转用 vImage(CPU),Core Image / VTPixelTransfer 同样被 GPU 同步拖住。原生零拷贝视图(IOSurface 进 CALayer)是后续项。
- **无障碍**:必须把 bridge token 代理装在 `AXPTranslator.sharedInstance`(装在 `sharediOSInstance` 上元素读属性时收不到回调,只剩根节点);属性用私有 `accessibilityMultipleAttributes:` 批量读(一元素一次往返);开机/启动 App 后头几次 frontmost 请求常为空,重试 3 次。
- **帧缓冲读取必须同步加锁**:`kIOSurfaceLockAvoidSync` 返回 `kIOReturnCannotLock`,无锁读到的是陈旧 CPU 副本(实测连续帧逐字节相同)。Intel Mac 上同步锁会等模拟器 GPU,动画期间能卡数秒 —— `simctl io screenshot` 同样卡,是平台特性;所以截图/推流在每设备独立队列,和输入互不阻塞;缩放旋转用 vImage(CPU),Core Image / VTPixelTransfer 同样被 GPU 同步拖住。Desktop 里改用下面的原生视图,不读回。
- **无障碍**:必须把 bridge token 代理装在 `AXPTranslator.sharedInstance`(装在 `sharediOSInstance` 上元素读属性时收不到回调,只剩根节点);属性用私有 `accessibilityMultipleAttributes:` 批量读(一元素一次往返);开机/启动 App 后头几秒 frontmost 请求常为空,按 0.4/0.7/1/1.5/2 秒退避重试(刚 `simctl boot` 完 1.4 秒不够)。
- **权限**:`ios_simulator` 进 `PermissionManager::should_auto_allow` 免确认名单(同 Agent Browser),但每台模拟器首次使用经 `ctx.ask_user_questions` 征求同意,记在 state.json `ios_simulator.device_consent`,超时/取消/未选一律算拒绝,yolo 隐式授权,设置页可撤销;`ios_simulator_build` 会执行工程脚本,走普通权限确认。plan 模式只允许观察类动作。
- **面板**:`WS /ws/ios-simulator/<udid>` 推 JPEG 二进制帧(带 JSON 头),收触摸/按键/文本;同一设备多个观看者共用一路 helper 流;订阅在后台线程里做,别放回 Crow IO 线程(首次要做 Xcode 发现 + 起 helper)。用户在面板里的操作不需要设备授权(授权约束的是 AI)。
- **验证现状**:本机 Xcode 26.6(绿色安装于 ~/Downloads)+ iOS 26.5 实测通过 describe / 截图(竖横屏)/ 点击 / 滑动 / 键盘 / 无障碍 / 推流;DTUHID 通道待 Xcode 27 机器验证。回归:`tests/tool/ios_simulator/*`、打包脚本测试(`tests/scripts/*` 中的 acecode-ios-sim 断言)。
- **helper 跑在 Seatbelt 里**(`helper_sandbox.cpp`,ACECode 自写的 profile,路径一律走 `sandbox-exec -D` 参数不拼进 SBPL):allow default + 禁 IP 网络 + 禁读 /Users、/Volumes、家目录下的文件内容(放行 helper 所在 .app 包、所选 Xcode、`~/Library/Developer/CoreSimulator|CoreDevice`、`~/Library/Logs/CoreSimulator`、三个模拟器偏好 plist、DARWIN 临时 / 缓存目录)+ 只许写 /dev、模拟器数据 / 日志与临时目录。两个坑:(1) CFBundle 定位主包要**列出可执行文件往上两级目录**(猜 X.app/Contents/MacOS 结构),不放行时主包为 NULL,CoreSimulator 的 `NSUserDefaults simulatorDefaults` 直接段错误 —— `listable_dirs` 只给列名,不给读内容;(2) 家目录取 `getpwuid`,CoreSimulator 不认 `$HOME`(假 HOME 下照样用真实设备集),按 `$HOME` 放行会把真实设备数据挡掉。sandbox-exec 应用失败(ACECode 自己已在沙盒里)时 hello 报 HELPER_EXITED,runtime 退回无沙盒启动并在 status 的 `helper.sandboxed/sandbox_note` 与日志里说明;`ACECODE_IOS_SIM_SANDBOX=0` 手动关闭。
- **Desktop 原生零拷贝视图**(`src/apps/desktop/ios_simulator_view*`):把模拟器 IOSurface 直接设成 CALayer contents,放在网页面板屏幕区域上方,每帧回调 `-setContentsChanged`(私有但 WebKit / Simulator 同用,缺失时重设 contents)。helper 与 Desktop 共用 `acecode_ios_sim_native` 静态库(`sim_core` / `sim_guest` / `sim_framebuffer`;stdout 写帧的 `sim_output` 只在 helper)。Desktop **只加载系统路径的 CoreSimulator**:屏幕描述符是 CoreSimulatorService 的 ROCK 代理,不需要 SimulatorKit,网页传来的 developer_dir 只作为字符串交给 CoreSimulator,绝不据此加载代码。视图 `hitTest` 恒 nil,输入照旧走网页 canvas → WS → helper;遮挡矩形与 Agent Browser 同一套(`nativeSurfaceOverlayGeometryByDocument`),macOS 上 CSS px = point。翻转视图的 backing layer `geometryFlipped=YES`,所以正角度 = 顺时针,与 helper 截图的顺时针旋转一致(CARenderer 离屏渲染验证过)。面板逻辑在 `web/src/lib/iosSimulatorNativeView.js`:原生 `rendering` 为真才对该 socket 发 `{type:"frames",enabled:false}` 停 JPEG(省掉编码),失败 / 被对话框挡住即恢复;socket 在原生视图上线后才连上时要在 `onopen` 补发,否则 JPEG 一直推。三个踩过的坑:ObjC block 捕获 C++ **引用参数时按引用捕获**,异步 block 里用之前先拷成局部值(否则读到已析构的 layout 字符串,json dump 报非法 UTF-8);`FramebufferSource` 构造函数返回前帧回调就可能到,回调只能经加锁的 `current_surface()` 访问 source;同一 udid 只 attach 一次,失败后靠页面「重新连接」重挂,别在每次布局时重试。回归:`ios_simulator_view_mac_smoke`(opt-in,真机模拟器上查 IOSurface contents / 几何 / 遮罩 / 显隐 / 卸载)、`tests/desktop/ios_simulator_view_layout_test.cpp`、前端 `iosSimulatorNativeView.test.js`。
- **DTUHID 报文**在 header-only 的 `dtuhid_messages.hpp`(helper 用 ARC、单测用纯 C++ 各自编译),schema / 相位 / 归一化 / 存活探测都有单测。本机没有 Xcode 27,真机验证靠 `.github/workflows/ios-simulator-live.yml`:GitHub 托管的 `xcode-27` 镜像(arm64,macOS 27 + Xcode 27 + iOS 27 运行时)上用 clang++ 直接编 helper / 沙盒 argv / 原生视图 smoke(不走 vcpkg 全量构建),`scripts/ios_simulator/live_check.py` 启动模拟器后用无障碍树核对点击打开设置、拖动滚动列表、键盘输入、Home 键返回,外加截图、推流、沙盒拒绝与原生视图。手动触发或推 `ci/ios-simulator-live*` 分支,普通 PR 不跑。键盘核对有两条路径:无障碍值含输入文字,或输入法(拼音等)把字母留在 marked text 里、值为空时比对前后截图。2026-10-16 在该镜像(macOS 27.0.1 / Xcode 27.0 27A266a / iOS 27.0,CoreSimulator 1174.15,自动选 dtuhid)上 16/16 通过,dtuhidd 的触摸 / 拖动 / 键盘 / 按键链路实测可用。
- **界面方向**:Xcode 27 的 CoreDevice display-info 回复是 `CoreDevice.output.displays[]`,主屏(`primary:true`)的 `currentOrientation` = `rot0|rot90|rot180|rot270`(xcode-27 runner 上抓到的真实报文),其它值一律算无回答。provider 在忙的机器上经常赶不上 2 秒:每次新建 guest 连接是慢的那步,所以连接按设备常驻复用;首次查询同步(5 秒)保证第一帧 / 第一次触摸方向正确,之后立即返回上次结果并在后台刷新,超时保留上次的有效值、5 秒后再试,不再每 600ms 卡住输入队列 2 秒;从没答过才退回 Simulator.app 窗口方向(Xcode 27 下 simctl 启动、没开 Simulator.app 时那份偏好不存在)。`display_info` 诊断另外报 `cached`(helper 实际在用的状态)。第一次 describe 要加载 Xcode 框架、连 CoreSimulatorService,冷机器上接近一分钟,daemon 的超时为此放到 90 秒。
- **验证现状**:本机 Xcode 26.6(绿色安装于 ~/Downloads)+ iOS 26.5(Indigo):沙盒内 helper 全部操作、E2E(断言 sandboxed)、原生视图 smoke、浏览器里注入假桥接验证面板切换 / 回退 / 输入映射、live_check 16/16 均通过;Xcode 27 见上一条。回归:`tests/tool/ios_simulator/*`、打包脚本测试(`tests/scripts/*` 中的 acecode-ios-sim 断言)。**别在这台 Mac 上跑 lldb**:开发者模式关闭,debugserver 会弹 SecurityAgent 密码框,弹框挂着期间所有非 Apple 签名的可执行文件(ninja、测试二进制、node)exec 都卡在 U 状态。

### Web UI: 控制台停靠区(ConsoleDock)

Expand Down
14 changes: 9 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "Vcpkg target triplet" FORCE)
endif()

project(acecode VERSION 1.0.8 LANGUAGES C CXX)
project(acecode VERSION 1.0.9 LANGUAGES C CXX)

include(${CMAKE_SOURCE_DIR}/cmake/acecode_source_guards.cmake)

Expand Down Expand Up @@ -105,7 +105,7 @@
set(ACECODE_WEB_DIST "${CMAKE_SOURCE_DIR}/web/dist")
set(ACECODE_WEB_EMBED_DIR "${ACECODE_WEB_DIST}")
if(NOT EXISTS "${ACECODE_WEB_DIST}")
message(WARNING

Check warning on line 108 in CMakeLists.txt

View workflow job for this annotation

GitHub Actions / unit-tests (linux-x64)

[acecode] web/dist/ not found — embedding a minimal fallback page. Run
"[acecode] web/dist/ not found — embedding a minimal fallback page. "
"Run `pnpm install && pnpm build` inside web/ before re-configuring for the full UI.")
set(ACECODE_WEB_EMBED_DIR "${CMAKE_BINARY_DIR}/generated/web-dist-fallback")
Expand Down Expand Up @@ -243,14 +243,18 @@
if(APPLE)
# iOS Simulator helper. It is a macOS-only payload: other platforms never
# build or package it.
add_library(acecode_ios_sim_native STATIC ${ACECODE_IOS_SIM_NATIVE_SOURCES})
set_target_properties(acecode_ios_sim_native PROPERTIES OBJCXX_STANDARD 17 OBJCXX_STANDARD_REQUIRED YES)
target_compile_options(acecode_ios_sim_native PRIVATE -fobjc-arc -mmacosx-version-min=14.0)
target_include_directories(acecode_ios_sim_native PUBLIC ${CMAKE_SOURCE_DIR}/src/adapters)
target_link_libraries(acecode_ios_sim_native PUBLIC nlohmann_json::nlohmann_json
"-framework Foundation" "-framework IOSurface")
add_executable(acecode-ios-sim ${ACECODE_IOS_SIM_HELPER_SOURCES})
set_target_properties(acecode-ios-sim PROPERTIES OBJCXX_STANDARD 17 OBJCXX_STANDARD_REQUIRED YES)
target_compile_options(acecode-ios-sim PRIVATE -fobjc-arc -mmacosx-version-min=14.0)
target_link_options(acecode-ios-sim PRIVATE -mmacosx-version-min=14.0)
target_include_directories(acecode-ios-sim PRIVATE ${CMAKE_SOURCE_DIR}/src/adapters)
target_link_libraries(acecode-ios-sim PRIVATE nlohmann_json::nlohmann_json
"-framework Foundation" "-framework CoreGraphics" "-framework Accelerate"
"-framework ImageIO" "-framework IOSurface")
target_link_libraries(acecode-ios-sim PRIVATE acecode_ios_sim_native
"-framework CoreGraphics" "-framework Accelerate" "-framework ImageIO")
install(TARGETS acecode-ios-sim RUNTIME DESTINATION . COMPONENT ios_simulator_runtime)
endif()

Expand Down
14 changes: 12 additions & 2 deletions cmake/acecode_desktop.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,18 @@ FetchContent_MakeAvailable(webview)
# acecode_testable 把 agent/TUI/web assets 全部拖入桌面壳。
if(APPLE)
set(ACECODE_AGENT_BROWSER_HOST_SOURCE ${ACECODE_AGENT_BROWSER_HOST_MAC_SOURCE})
# The macOS office host is the only ARC translation unit in this target.
# The macOS office host and the iOS Simulator view are the ARC
# translation units in this target.
set(ACECODE_DESKTOP_PET_SOURCE ${ACECODE_DESKTOP_PET_MAC_SOURCE})
set_source_files_properties(${ACECODE_DESKTOP_PET_MAC_SOURCE}
set(ACECODE_IOS_SIMULATOR_VIEW_SOURCE ${ACECODE_IOS_SIMULATOR_VIEW_MAC_SOURCE})
set_source_files_properties(${ACECODE_DESKTOP_PET_MAC_SOURCE} ${ACECODE_IOS_SIMULATOR_VIEW_MAC_SOURCE}
PROPERTIES COMPILE_OPTIONS "-fobjc-arc")
endif()

set(ACECODE_DESKTOP_SOURCES
${ACECODE_AGENT_BROWSER_HOST_SOURCE}
${ACECODE_IOS_SIMULATOR_VIEW_SOURCE}
${ACECODE_IOS_SIMULATOR_VIEW_BRIDGE_SOURCE}
${ACECODE_DESKTOP_MAIN_SOURCE}
${ACECODE_DESKTOP_SPLASH_SOURCE}
${ACECODE_DESKTOP_WEB_HOST_SOURCE}
Expand Down Expand Up @@ -115,6 +119,12 @@ target_link_libraries(acecode-desktop PRIVATE
nlohmann_json::nlohmann_json
)

if(APPLE)
# Zero-copy simulator screen: shares the helper's CoreSimulator access.
target_link_libraries(acecode-desktop PRIVATE acecode_ios_sim_native
"-framework QuartzCore" "-framework IOSurface")
endif()

if(ACECODE_DEEPIN)
# DTK package configs can set directory-wide definitions/includes. Keep
# their discovery in a child directory so they cannot affect the daemon,
Expand Down
2 changes: 1 addition & 1 deletion cmake/acecode_layer_contract.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ if(TARGET acecode_computer_use_native)
list(APPEND _primary_targets acecode_computer_use_native acecode-computer-use)
endif()
if(TARGET acecode-ios-sim)
list(APPEND _primary_targets acecode-ios-sim)
list(APPEND _primary_targets acecode_ios_sim_native acecode-ios-sim)
endif()
if(TARGET winpty_static)
list(APPEND _primary_targets winpty_static)
Expand Down
2 changes: 2 additions & 0 deletions cmake/acecode_layer_libraries.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ set(ACECODE_DESKTOP_BINARY_ONLY_SOURCES
${ACECODE_MACOS_CLI_LAUNCHER_SOURCE}
${ACECODE_DEEPIN_WINDOW_EFFECTS_SOURCE} ${ACECODE_DESKTOP_LINUX_SOURCE}
${ACECODE_AGENT_BROWSER_HOST_SOURCE} ${ACECODE_AGENT_BROWSER_HOST_MAC_SOURCE}
${ACECODE_IOS_SIMULATOR_VIEW_SOURCE} ${ACECODE_IOS_SIMULATOR_VIEW_MAC_SOURCE}
${ACECODE_IOS_SIMULATOR_VIEW_BRIDGE_SOURCE}
${ACECODE_DESKTOP_MAIN_SOURCE} ${ACECODE_DESKTOP_SPLASH_SOURCE}
${ACECODE_DESKTOP_WEB_HOST_SOURCE} ${ACECODE_DESKTOP_PET_SOURCE}
${ACECODE_DESKTOP_PET_MAC_SOURCE})
Expand Down
Loading
Loading