Files
elizaos--eliza/plugins/plugin-computeruse/docs/IOS_CONSTRAINTS.md
T
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

10 KiB

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 CMSampleBuffers of the host app's window. Useful for "show me what's on the Eliza screen right now."

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.

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.

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:

<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:

<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:

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:
    registerOcrProvider(
      createIosVisionOcrProvider(() => Capacitor.Plugins.ComputerUse),
    );
    

Apple documentation references