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
13 changes: 12 additions & 1 deletion .acecode/skills/verify-package/scripts/verify_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ def print_dry_run(repo: Path, build_dir: Path, staging: Path, platform: str,
components = ["models_dev_registry", "default_seed_bundle"]
if platform in ("windows", "darwin"):
components.append("computer_use_runtime")
if platform == "darwin":
components.append("ios_simulator_runtime")
for component in components:
print(" " + " ".join([
cmake_command, "--install", str(build_dir), "--config", "MinSizeRel",
Expand Down Expand Up @@ -347,6 +349,8 @@ def stage(report: Report, repo: Path, build_dir: Path, staging: Path,
components = ["models_dev_registry", "default_seed_bundle"]
if platform in ("windows", "darwin"):
components.append("computer_use_runtime")
if platform == "darwin":
components.append("ios_simulator_runtime")
for component in components:
if not run_tool(report, f"cmake install {component}",
[cmake, "--install", str(build_dir),
Expand All @@ -364,6 +368,12 @@ def structural_checks(report: Report, repo: Path, staging: Path,
report.add("computer use runtime adjacency", "pass")
else:
report.add("computer use runtime adjacency", "fail", f"missing or empty {helper}")
if platform == "darwin" and "tui" in targets:
simulator = staging / "acecode-ios-sim"
if simulator.is_file() and simulator.stat().st_size > 0 and os.access(simulator, os.X_OK):
report.add("iOS Simulator runtime adjacency", "pass")
else:
report.add("iOS Simulator runtime adjacency", "fail", f"missing or empty {simulator}")
if "tui" in targets or platform != "darwin":
check_models_dev(report, "models_dev registry (staged share/)",
staging / "share" / "acecode" / "models_dev",
Expand All @@ -377,7 +387,8 @@ def structural_checks(report: Report, repo: Path, staging: Path,
contents = bundle / "Contents"
for required in (contents / "MacOS" / "ACECode",
contents / "MacOS" / "acecode-daemon",
contents / "MacOS" / "acecode-computer-use"):
contents / "MacOS" / "acecode-computer-use",
contents / "MacOS" / "acecode-ios-sim"):
if required.is_file() and required.stat().st_size > 0 and os.access(required, os.X_OK):
report.add(f"app bundle {required.name}", "pass")
else:
Expand Down
13 changes: 12 additions & 1 deletion .agents/skills/verify-package/scripts/verify_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ def print_dry_run(repo: Path, build_dir: Path, staging: Path, platform: str,
components = ["models_dev_registry", "default_seed_bundle"]
if platform in ("windows", "darwin"):
components.append("computer_use_runtime")
if platform == "darwin":
components.append("ios_simulator_runtime")
for component in components:
print(" " + " ".join([
cmake_command, "--install", str(build_dir), "--config", "MinSizeRel",
Expand Down Expand Up @@ -347,6 +349,8 @@ def stage(report: Report, repo: Path, build_dir: Path, staging: Path,
components = ["models_dev_registry", "default_seed_bundle"]
if platform in ("windows", "darwin"):
components.append("computer_use_runtime")
if platform == "darwin":
components.append("ios_simulator_runtime")
for component in components:
if not run_tool(report, f"cmake install {component}",
[cmake, "--install", str(build_dir),
Expand All @@ -364,6 +368,12 @@ def structural_checks(report: Report, repo: Path, staging: Path,
report.add("computer use runtime adjacency", "pass")
else:
report.add("computer use runtime adjacency", "fail", f"missing or empty {helper}")
if platform == "darwin" and "tui" in targets:
simulator = staging / "acecode-ios-sim"
if simulator.is_file() and simulator.stat().st_size > 0 and os.access(simulator, os.X_OK):
report.add("iOS Simulator runtime adjacency", "pass")
else:
report.add("iOS Simulator runtime adjacency", "fail", f"missing or empty {simulator}")
if "tui" in targets or platform != "darwin":
check_models_dev(report, "models_dev registry (staged share/)",
staging / "share" / "acecode" / "models_dev",
Expand All @@ -377,7 +387,8 @@ def structural_checks(report: Report, repo: Path, staging: Path,
contents = bundle / "Contents"
for required in (contents / "MacOS" / "ACECode",
contents / "MacOS" / "acecode-daemon",
contents / "MacOS" / "acecode-computer-use"):
contents / "MacOS" / "acecode-computer-use",
contents / "MacOS" / "acecode-ios-sim"):
if required.is_file() and required.stat().st_size > 0 and os.access(required, os.X_OK):
report.add(f"app bundle {required.name}", "pass")
else:
Expand Down
13 changes: 12 additions & 1 deletion .claude/skills/verify-package/scripts/verify_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ def print_dry_run(repo: Path, build_dir: Path, staging: Path, platform: str,
components = ["models_dev_registry", "default_seed_bundle"]
if platform in ("windows", "darwin"):
components.append("computer_use_runtime")
if platform == "darwin":
components.append("ios_simulator_runtime")
for component in components:
print(" " + " ".join([
cmake_command, "--install", str(build_dir), "--config", "MinSizeRel",
Expand Down Expand Up @@ -347,6 +349,8 @@ def stage(report: Report, repo: Path, build_dir: Path, staging: Path,
components = ["models_dev_registry", "default_seed_bundle"]
if platform in ("windows", "darwin"):
components.append("computer_use_runtime")
if platform == "darwin":
components.append("ios_simulator_runtime")
for component in components:
if not run_tool(report, f"cmake install {component}",
[cmake, "--install", str(build_dir),
Expand All @@ -364,6 +368,12 @@ def structural_checks(report: Report, repo: Path, staging: Path,
report.add("computer use runtime adjacency", "pass")
else:
report.add("computer use runtime adjacency", "fail", f"missing or empty {helper}")
if platform == "darwin" and "tui" in targets:
simulator = staging / "acecode-ios-sim"
if simulator.is_file() and simulator.stat().st_size > 0 and os.access(simulator, os.X_OK):
report.add("iOS Simulator runtime adjacency", "pass")
else:
report.add("iOS Simulator runtime adjacency", "fail", f"missing or empty {simulator}")
if "tui" in targets or platform != "darwin":
check_models_dev(report, "models_dev registry (staged share/)",
staging / "share" / "acecode" / "models_dev",
Expand All @@ -377,7 +387,8 @@ def structural_checks(report: Report, repo: Path, staging: Path,
contents = bundle / "Contents"
for required in (contents / "MacOS" / "ACECode",
contents / "MacOS" / "acecode-daemon",
contents / "MacOS" / "acecode-computer-use"):
contents / "MacOS" / "acecode-computer-use",
contents / "MacOS" / "acecode-ios-sim"):
if required.is_file() and required.stat().st_size > 0 and os.access(required, os.X_OK):
report.add(f"app bundle {required.name}", "pass")
else:
Expand Down
13 changes: 12 additions & 1 deletion .codex/skills/verify-package/scripts/verify_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ def print_dry_run(repo: Path, build_dir: Path, staging: Path, platform: str,
components = ["models_dev_registry", "default_seed_bundle"]
if platform in ("windows", "darwin"):
components.append("computer_use_runtime")
if platform == "darwin":
components.append("ios_simulator_runtime")
for component in components:
print(" " + " ".join([
cmake_command, "--install", str(build_dir), "--config", "MinSizeRel",
Expand Down Expand Up @@ -347,6 +349,8 @@ def stage(report: Report, repo: Path, build_dir: Path, staging: Path,
components = ["models_dev_registry", "default_seed_bundle"]
if platform in ("windows", "darwin"):
components.append("computer_use_runtime")
if platform == "darwin":
components.append("ios_simulator_runtime")
for component in components:
if not run_tool(report, f"cmake install {component}",
[cmake, "--install", str(build_dir),
Expand All @@ -364,6 +368,12 @@ def structural_checks(report: Report, repo: Path, staging: Path,
report.add("computer use runtime adjacency", "pass")
else:
report.add("computer use runtime adjacency", "fail", f"missing or empty {helper}")
if platform == "darwin" and "tui" in targets:
simulator = staging / "acecode-ios-sim"
if simulator.is_file() and simulator.stat().st_size > 0 and os.access(simulator, os.X_OK):
report.add("iOS Simulator runtime adjacency", "pass")
else:
report.add("iOS Simulator runtime adjacency", "fail", f"missing or empty {simulator}")
if "tui" in targets or platform != "darwin":
check_models_dev(report, "models_dev registry (staged share/)",
staging / "share" / "acecode" / "models_dev",
Expand All @@ -377,7 +387,8 @@ def structural_checks(report: Report, repo: Path, staging: Path,
contents = bundle / "Contents"
for required in (contents / "MacOS" / "ACECode",
contents / "MacOS" / "acecode-daemon",
contents / "MacOS" / "acecode-computer-use"):
contents / "MacOS" / "acecode-computer-use",
contents / "MacOS" / "acecode-ios-sim"):
if required.is_file() and required.stat().st_size > 0 and os.access(required, os.X_OK):
report.add(f"app bundle {required.name}", "pass")
else:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ jobs:
--keychain "${{ steps.macos-keychain.outputs.keychain }}" \
--binary "build/${{ matrix.executable }}" \
--binary "build/acecode-computer-use" \
--binary "build/acecode-ios-sim" \
--app "build/ACECode.app"

- name: Notarize and staple macOS app
Expand Down Expand Up @@ -629,8 +630,11 @@ jobs:
cmake --install build --config MinSizeRel --prefix "$PWD/$package_dir" --component whatsapp_channel
if [ "$RUNNER_OS" = "macOS" ]; then
cmake --install build --config MinSizeRel --prefix "$PWD/$package_dir" --component computer_use_runtime
cmake --install build --config MinSizeRel --prefix "$PWD/$package_dir" --component ios_simulator_runtime
test -x "$package_dir/acecode-computer-use"
test -x "$package_dir/acecode-ios-sim"
test -x "build/ACECode.app/Contents/MacOS/acecode-computer-use"
test -x "build/ACECode.app/Contents/MacOS/acecode-ios-sim"
fi
validate_models_dev_registry "$package_dir/share/acecode/models_dev"
python3 scripts/verify_seed_bundle.py \
Expand Down Expand Up @@ -669,7 +673,9 @@ jobs:
--packaged "$extracted_package/share/acecode/seed"
if [ "$RUNNER_OS" = "macOS" ]; then
test -x "$extracted_package/acecode-computer-use"
test -x "$extracted_package/acecode-ios-sim"
test -x "$extracted_package/ACECode.app/Contents/MacOS/acecode-computer-use"
test -x "$extracted_package/ACECode.app/Contents/MacOS/acecode-ios-sim"
validate_models_dev_registry \
"$extracted_package/ACECode.app/Contents/Resources/share/acecode/models_dev"
python3 scripts/verify_seed_bundle.py \
Expand Down
14 changes: 14 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,20 @@ JS↔C++ bridge(同进程,webview `bind`,无 HTTP):
- **前端页签只有一个来源**:`web/src/lib/agentBrowserPages.js`(App 级单写者登记表,`App.jsx` 挂载时 `installAgentBrowserPageListener()` + 全量 `reconcileAgentBrowserPageStore()`,ChatView 切会话再按会话对账)→ `previewTabs.js::syncBrowserTabsForSession` 派生页签;本 ChatView 首次见到的页面自动打开并激活(`revealedBrowserPagesRef` 按会话记)。**不要**再从 `agentBrowserActivityFromItems` 创建页签或调无参 `getAgentBrowserState()` 猜全局活动页,活动推断现在只决定彩虹边框与「Agent 切目标页时前置该页签」。旧 Desktop 不带 owner 的事件走 `claimUnownedAgentBrowserPage` 本地认领兜底。
- 回归测试:`tests/desktop/agent_browser_page_directory_test.cpp`、`tests/tool/agent_browser_tools_test.cpp` 的 owner 两条、Windows smoke 的 `SMOKE_OWNERSHIP_OK` 段、前端 `agentBrowserPages.test.js` + `previewTabs.test.js::syncBrowserTabsForSession` + `agentBrowserArchitecture.test.js` 的归属合同。设计全文见 [docs/agent-browser.md](docs/agent-browser.md)「页面归属」。

### iOS 模拟器(openspec add-ios-simulator,仅 macOS)

逆向 Claude Desktop 的同类功能后自研复刻:Claude 用 FBSimulatorControl 的 Swift helper 且要求 xcode-select 指向完整 Xcode;我们的 iOS 同事常用绿色安装的 Xcode,所以 Xcode 必须自动发现 + 可配置。用户文档 [docs/ios-simulator.md](docs/ios-simulator.md),接口 [docs/daemon-api.md](docs/daemon-api.md) 第 20 节。

- **helper `acecode-ios-sim`**(`src/adapters/tool/ios_simulator/helper/*.mm`,CMake 只在 `if(APPLE)` 建,Desktop 拷进 `ACECode.app/Contents/MacOS`,便携包装 `ios_simulator_runtime` 组件;其它平台不构建不打包)。私有 CoreSimulator / SimulatorKit / AccessibilityPlatformTranslation 一律运行期 `NSClassFromString` / `dlsym` + 协议转发,每次调用 `@try` 兜底,不产生链接期符号;Indigo 结构与 DTUHID 报文形状取自 idb(MIT,`helper/LICENSE-idb.txt`)。协议是 header-only 的 `wire.hpp`:`u32le body | u32le json_len | json | binary`,daemon 与 helper 共用。
- **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 次。
- **权限**:`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 断言)。

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

`add-console-dock`:TopBar `>_` 按钮(icon `terminal` = TerminalReadWrite.svg)或 ``Ctrl+` `` toggle 主内容列底部的终端停靠区(横跨 ChatView+SidePanel,不动 Sidebar/TopBar)。前端 xterm.js(`@xterm/xterm` + `@xterm/addon-fit`,bundle +~190KB gzip);多 tab(+ 新建 / 逐 tab × 杀会话 / 整体收起保活);顶边拖高(`clampDockHeight`,[120, 视口 80%]);偏好 `acecode.consoleDock.v1` `{open, height}`。纯函数层 `web/src/lib/consoleDock.js`(tab 状态机/帧分流/退避/URL,Node 单测)。**xterm 聚焦时 ``Ctrl+` `` 靠 `attachCustomKeyEventHandler` 放行冒泡** — 删掉它快捷键在终端内失灵。
Expand Down
19 changes: 18 additions & 1 deletion 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.7 LANGUAGES C CXX)
project(acecode VERSION 1.0.8 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 @@ -240,6 +240,20 @@
install(TARGETS acecode-computer-use RUNTIME DESTINATION . COMPONENT computer_use_runtime)
endif()

if(APPLE)
# iOS Simulator helper. It is a macOS-only payload: other platforms never
# build or package it.
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")
install(TARGETS acecode-ios-sim RUNTIME DESTINATION . COMPONENT ios_simulator_runtime)
endif()

add_executable(acecode
${ACECODE_MAIN_SOURCE}
${ACECODE_WINDOWS_RESOURCES}
Expand All @@ -248,6 +262,9 @@
if(WIN32 OR APPLE)
add_dependencies(acecode acecode-computer-use)
endif()
if(APPLE)
add_dependencies(acecode acecode-ios-sim)
endif()

add_custom_command(TARGET acecode POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:acecode>/channels/whatsapp"
Expand Down
Loading
Loading