Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

234 lines
10 KiB
Markdown

# iOS Computer-Use — Honest Scope
Apple does not let third-party apps drive other apps. There is no equivalent
to Android's `MediaProjection` for full-screen capture without a user prompt,
no `ScreenCaptureKit` (that is macOS only), and no system-wide accessibility
API that a sandboxed iOS app can use to inject events into other apps.
This document spells out what *is* possible on iOS, exactly how each surface
behaves, and the manual validation checklist that has to be run on a real
device before any of this ships.
## What works
### 1. ReplayKit foreground capture (own app)
`RPScreenRecorder.shared().startCapture(handler:completionHandler:)` returns
`CMSampleBuffer`s of *the host app's* window. Useful for "show me what's on
the Eliza screen right now."
```swift
RPScreenRecorder.shared().startCapture(handler: { sampleBuffer, type, error in
// Receive frames for own-app capture only.
}, completionHandler: { error in
// Setup result; ReplayKit shows a system permission prompt on first use.
})
```
Constraints:
- Frame delivery rate is the display refresh rate; the bridge throttles to a
caller-supplied `frameRate` (default 1Hz).
- Hard cap of 30 seconds per session. Past that, the work has to move to a
`BGProcessingTask`.
### 2. ReplayKit broadcast extension (system-wide capture)
A separate target with a ~50MB memory ceiling. Streams frames over an App
Group shared container into the main app. The user must start the broadcast
themselves via the system share-sheet picker — apps cannot programmatically
launch the extension.
```swift
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer,
with sampleBufferType: RPSampleBufferType) {
// Compress + dump into the App Group container.
}
```
**iOS 26 / 26.1 beta regression**: extensions are killed within ~3 seconds
even when memory headroom is fine. We surface this as
`extension_died` from `broadcastExtensionHandshake` so callers can fall back
to foreground capture. Track Apple's feedback status before shipping this
target on iOS 26.
### 3. Apple Vision OCR
`VNRecognizeTextRequest` is on-device, free, supports ~30 languages, and
typically runs sub-300ms on modern devices.
```swift
let request = VNRecognizeTextRequest { request, error in
let observations = request.results as? [VNRecognizedTextObservation]
// Top candidate per observation; each has bounding box, text, confidence.
}
request.recognitionLevel = .accurate
request.recognitionLanguages = ["en-US"]
try VNImageRequestHandler(cgImage: cg).perform([request])
```
This is the OCR provider the WS6 scene-builder will pick up on iOS. The
provider interface is in
`eliza/plugins/plugin-computeruse/src/mobile/ocr-provider.ts`.
### 4. App Intents
The only sanctioned way to drive other apps. Each target app must expose
intents (Shortcuts-style). We support invocation via x-callback URL schemes
for the system apps in the static registry
(`ios-app-intent-registry.ts`):
- Mail — `mailto:` with `subject` / `body` / `cc` / `bcc`
- Messages — `sms:` with `body`
- Maps — `http://maps.apple.com/?daddr=...&dirflg=...`
- Safari — open URL
For richer intents (Notes append, Reminders add, Music play with a query)
the user has to donate the action via Shortcuts; we can then invoke via
`AppIntent` on iOS 16+. The bridge's `appIntentList` returns the runtime
list of donated intents this app sees.
### 5. UIAccessibility (own-app reading only)
`accessibilitySnapshot` walks the key window's view hierarchy and returns
`accessibilityLabel` / `accessibilityValue` / role. iOS gives us no way to
read another app's UIAccessibility tree.
### 6. Apple Foundation Models (iOS 26+)
Apple ships an on-device LLM under the `FoundationModels` framework when
Apple Intelligence is enabled. We expose this as an *opportunistic*
fast-path. If unavailable, the existing llama-cpp-capacitor local Eliza-1
vision path stays as the default.
Entitlement and Info.plist updates are listed below.
## What does NOT work
Stock iOS does not allow any of the following from a third-party app, and
we do not pretend otherwise:
- **Driving other apps' UI**. No cross-app input synthesis, no
`MediaProjection` / `ScreenCaptureKit` equivalent, no system-wide
accessibility event injection.
- **Listing other running apps' processes**. The kernel hides this from
third-party apps; there is no `ps`-equivalent.
- **Persistent background inference past ~30s**, except via
`BGProcessingTask` (opportunistic, OS-scheduled, not guaranteed).
If a feature spec calls for any of the above, escalate it as not feasible
on iOS rather than inventing a workaround that will get the app rejected
or silently broken by Apple in the next OS update.
## Entitlements + Info.plist
Add the following to `apps/app/ios/App/App/App.entitlements`:
```xml
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
```
Add the following to `apps/app/ios/App/App/Info.plist`:
```xml
<key>NSScreenCaptureDescription</key>
<string>Captures the Eliza app window when you ask it to see the screen.</string>
<key>NSAppleEventsUsageDescription</key>
<string>Sends Shortcuts to other apps when you authorize an action.</string>
```
The screen capture string is shown on ReplayKit's first-launch system
prompt. The Apple Events string is shown only if the host (this app) ever
needs to drive AppleScript via the Catalyst surface; iOS apps that only
use `UIApplication.shared.open(url:)` to invoke x-callback intents do not
need it, but it costs nothing to include for forward compatibility with
Mac Catalyst builds.
## Validation checklist
The iOS bridge contract is covered by TypeScript tests, but physical iOS
device behavior is not proven by this repository alone. The required evidence
manifest is `docs/ios-device-validation.json`; keep it in
`requires_device_evidence` until a real device run records device/build
metadata, artifacts, and per-method results. Release gates that require
physical proof should run:
```bash
bun run --cwd plugins/plugin-computeruse validate:ios-device-evidence -- --require-complete
```
Without `--require-complete`, the same command validates that the manifest
still tracks every required method and evidence field. Before shipping any
iOS bridge release, complete the manifest for:
### Simulator vs device
| Surface | Simulator | Real device |
| -------------------------------- | --------- | ----------- |
| ReplayKit foreground capture | Partial | Required |
| Broadcast extension | No | Required |
| Vision OCR | Yes | Required |
| App Intents (x-callback) | Partial | Required |
| Accessibility snapshot | Yes | Required |
| Foundation Models | No | Required |
| Memory pressure probe | Limited | Required |
### Per-method checklist
For each method below, run on at least one A14-or-later iPhone running the
target iOS (currently iOS 26.1, with iOS 17.6 as the floor):
1. **`probe()`** — assert `data.platform === "ios"`, `osVersion` matches the
device, all six capability bits are present.
2. **`replayKitForegroundStart`** — assert the system permission prompt is
shown on first use; on accept, `replayKitForegroundDrain` returns frames
with non-empty `jpegBase64` and the device's screen resolution.
3. **`broadcastExtensionHandshake`** — bundle the extension target via Xcode,
tap the share-sheet broadcast picker, and assert `broadcastActive`
transitions to `true`. On iOS 26 betas, verify `regression.observed`
eventually flips to `true` after the extension dies.
4. **`visionOcr`** — render a known PNG with the string `"WS9 OCR SMOKE"`
into the app, run OCR with `recognitionLevel: "accurate"`, and assert
`fullText` contains the source string (case-insensitive).
5. **`appIntentList`** — verify the returned list is empty for a fresh
install and grows after the user donates intents via Shortcuts.
6. **`appIntentInvoke`** with `com.apple.mobilesafari.open-url` — assert
Safari opens to the provided URL. With `com.apple.MobileSMS.send-message`
— assert Messages opens with the recipient and body pre-filled.
7. **`accessibilitySnapshot`** — assert the returned tree's top-level node
has `role !== "labeled"` and `children.length > 0` on a populated screen.
8. **`foundationModelGenerate`** — on a device with Apple Intelligence
enabled, assert a short prompt returns non-empty text. With AI disabled,
assert the call resolves with `foundation_model_unavailable`.
9. **`memoryPressureProbe`** — invoke `UIApplication.shared.performMemoryWarning()`
(debug-only) and assert the next probe call returns
`severity >= 0.7` with `lastWarningAt` set.
## Hand-off to other workstreams
- **WS1 (memory-pressure arbiter)** consumes `MemoryPressureSample` via the
shared `IPressureSignal` contract in `ios-bridge.ts`. The bridge is the
producer; the arbiter is the consumer. WS1 will subscribe to push events
via the bridge once that channel lands; for now it polls `memoryPressureProbe`.
- **WS6 (scene-builder OCR)** picks up the iOS Vision provider through
`selectOcrProvider()` in `ocr-provider.ts`. Register the provider at app
boot when running on iOS:
```ts
registerOcrProvider(
createIosVisionOcrProvider(() => Capacitor.Plugins.ComputerUse),
);
```
## Apple documentation references
- ReplayKit — https://developer.apple.com/documentation/replaykit
- Vision Text Recognition — https://developer.apple.com/documentation/vision/recognizing_text_in_images
- App Intents — https://developer.apple.com/documentation/appintents
- Foundation Models — https://developer.apple.com/documentation/foundationmodels (iOS 26)
- App Groups — https://developer.apple.com/documentation/xcode/configuring-app-groups
- `os_proc_available_memory()` — https://developer.apple.com/documentation/foundation/process_info/3743117-os_proc_available_memory
- Background Tasks — https://developer.apple.com/documentation/backgroundtasks