Skip to content

feat: support simulator camera video files#752

Open
thymikee wants to merge 3 commits into
mainfrom
feat/android-camera-video-files
Open

feat: support simulator camera video files#752
thymikee wants to merge 3 commits into
mainfrom
feat/android-camera-video-files

Conversation

@thymikee

@thymikee thymikee commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

Add sample-video camera inputs for both emulator families agents need to test:

  • Android emulator boot accepts --camera-front / --camera-back modes or video file paths, with running-emulator rejection when camera sources cannot be changed safely.
  • iOS simulator app launch accepts open <app> --camera-video <path> / cameraVideo, starts the vendored serve-sim helper, injects the AVFoundation dylib for that app process, and stops the helper on app close.
  • Vendored only the serve-sim runtime camera artifacts we need with Apache-2.0 attribution under third_party/serve-sim-camera: the helper binary, injector dylib, README, and license. The local binary filenames are neutral (camera-helper, camera-injector.dylib). Unpacked payload is ~600 KB; helper binaries are ~584 KB.
  • Updated CLI/client docs and typed command metadata for the new camera options.

Validation

  • pnpm format
  • pnpm exec vitest run src/utils/__tests__/args.test.ts src/__tests__/client.test.ts src/core/__tests__/dispatch-open.test.ts src/daemon/__tests__/context.test.ts src/platforms/ios/__tests__/simulator-camera.test.ts
  • pnpm exec vitest run src/platforms/ios/__tests__/simulator-camera.test.ts after trimming unused vendored source files and renaming local binary artifacts
  • pnpm check:quick
  • pnpm check:unit
  • pnpm build
  • pnpm check:fallow --base origin/main
  • Manual Android emulator proof: launched Pixel_9_Pro_XL_API_37 with -camera-back videofile:/private/tmp/agent-device-back-camera.mp4 -camera-front none -feature VideoPlayback; repeated front-camera playback.
  • Manual iOS simulator proof: launched throwaway AVFoundation camera app on iPhone 17, iOS 26.2, with open com.agentdevice.CameraFeed --camera-video /private/tmp/agent-device-camera-proof/camera-feed.mp4; captured /private/tmp/agent-device-camera-proof/simcam-proof-final.png showing the MP4 test pattern in the camera preview.

Touched files: 29. Scope expanded from Android boot camera files to iOS simulator app-launch video injection at reviewer request.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.2 MB 1.2 MB +2.3 kB
JS gzip 385.4 kB 386.1 kB +747 B
npm tarball 497.3 kB 498.0 kB +700 B
npm unpacked 1.7 MB 1.7 MB +2.4 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.3 ms 28.3 ms -0.0 ms
CLI --help 43.9 ms 42.4 ms -1.5 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/8806.js +998 B +397 B
dist/src/1352.js +510 B +187 B
dist/src/session.js +537 B +96 B
dist/src/8173.js +168 B +50 B
dist/src/8699.js +100 B +17 B

@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-752/

Built to branch gh-pages at 2026-06-10 18:06 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@thymikee thymikee changed the title feat: support Android emulator camera video files feat: support simulator camera video files Jun 10, 2026
@thymikee thymikee force-pushed the feat/android-camera-video-files branch from a382ae2 to 7faedef Compare June 10, 2026 19:56
@thymikee thymikee force-pushed the feat/android-camera-video-files branch from 7faedef to 79c8bc7 Compare June 11, 2026 05:55

Copy link
Copy Markdown
Member Author

Code review

Verdict: significant issues — feature plumbing and tests are solid, but the vendoring approach and helper-process lifecycle need work before merge.

Findings

  1. Majorthird_party/serve-sim-camera/README.md: no integrity pinning for the vendored binaries — the README records only serve-sim@0.1.34, with no SHA-256 of camera-helper or camera-injector.dylib, no upstream commit/tarball hash, and no verification script, so nobody can confirm the ~600 KB opaque Mach-O blobs actually match upstream.

  2. Major (repo convention break)third_party/serve-sim-camera/bin/*: these are the first compiled binaries committed to the repo; every existing helper (android-snapshot-helper, android-multitouch-helper, macos-helper, ios-runner) is vendored as source and built at package time. Committing unauditable executables that get DYLD-injected into user app processes is a meaningful supply-chain escalation.

  3. Majorsrc/platforms/ios/simulator-camera.ts:53-66 + src/platforms/ios/apps.ts:322,1174: the helper is spawned detached/unref'd and stopIosSimulatorCameraVideo is only called from closeIosApp and on launch failure. If the app crashes, the simulator is shut down (shutdown has no camera cleanup), the daemon dies, or the user re-opens the app without --camera-video, the helper loops the video forever with no cleanup path.

  4. Majorsrc/platforms/ios/simulator-camera.ts:53-66,110-121: only file existence is validated (no format check), and after runCmdDetached there's no check that the helper survived startup or created the shm segment. A corrupt/unsupported video makes the helper exit immediately; the app then launches "successfully" with a dead camera feed, and the only evidence is an unread log in os.tmpdir().

  5. Majorsrc/daemon/handlers/session-state.ts:205,243: iOS cameraVideo is expanded with SessionStore.expandHome(value, req.meta?.cwd), but Android flags.cameraFront/cameraBack are passed raw to ensureAndroidEmulatorBooted, where resolveAndroidEmulatorCameraMode (src/platforms/android/devices.ts:489-520) resolves against the daemon's cwd and never expands ~ — yet the flag help and docs advertise ./front.mp4, which will fail (or hit the wrong file) in daemon mode.

  6. Minorsrc/platforms/ios/simulator-camera.ts:86-101: stop SIGTERMs the pid from a tmp-dir state file without verifying the process is still the camera helper; after a host reboot or PID reuse it can kill an unrelated process.

  7. Minor — renaming upstream artifacts "to avoid exposing upstream internal artifact names" makes audit-by-diff against the upstream npm tarball harder, and the original filenames aren't recorded; combined with finding 1, the Apache-2.0 claim and version are effectively unverifiable from the repo (LICENSE itself does ship correctly via files).

  8. Minor (tests)simulator-camera.test.ts covers the happy path and non-simulator rejection only — nothing for stop-on-close, stale/missing state files, or missing video file; Android tests don't cover the explicit videofile: prefix branch or the invalid-mode error.

Verified clean

The Android running-emulator rejection (assertCameraInputsCanApplyToEmulator) is correct and tested for both paths; args pass via spawn(..., shell: false) arrays (no injection); shm names are per-launch hashed and within macOS limits, so concurrent sessions don't collide; deep-link paths correctly strip cameraVideo; docs honestly state the forced relaunch.

Overall

The TypeScript integration is well-plumbed and validated at every dispatch layer, but two structural weaknesses remain. Shipping renamed, checksum-less third-party Mach-O executables in git — in a repo where every other helper builds from source — is hard to audit and update safely; at minimum the README needs per-file SHA-256s, original artifact names, and the upstream tarball hash, and the better design is fetching the pinned serve-sim@0.1.34 artifact at install/build time with checksum verification. Separately, the detached helper needs a liveness check at startup and cleanup hooks on simulator shutdown/session close, or long-lived agent hosts will accumulate orphaned video-looping processes.


Generated by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Overview: Adds sample-video camera inputs — Android emulator boot --camera-front/--camera-back (mode or video file) and iOS simulator open <app> --camera-video <path> (starts a vendored serve-sim helper + injects an AVFoundation dylib via SIMCTL_CHILD_*, stopped on app close), with the helper/dylib vendored under third_party/serve-sim-camera.

Regression risk: Medium — new behavior is well platform-gated and additive, but it touches several shared core/daemon files that have been heavily refactored on main (command-family facets, selector-capture runtime), so the conflicts are non-trivial and the iOS path adds a detached child process + dylib injection into the simctl launch env that did not exist before.

Must rebase

This PR is CONFLICTING and cannot merge as-is. Two edited files no longer exist on main after the facets refactor (#849/#854):

  • src/commands/client-command-metadata.ts — gone; boot/open metadata now lives in per-family modules (e.g. src/commands/management/device.ts). The cameraFront/cameraBack/cameraVideo field declarations must be re-applied there.
  • src/commands/cli-grammar/apps.ts — gone; CLI readers moved under src/commands/cli-grammar/registry.ts + family modules. The boot/open reader additions must move accordingly.

package.json, client-types.ts, client-normalizers.ts, dispatch.ts, dispatch-context.ts, interactor-types.ts, interactors/apple.ts, and client.test.ts also conflict and need manual resolution.

Blocking

  • src/platforms/ios/simulator-camera.ts:111-123 — the detached helper can leak its log fd and run unbounded. The helper is runCmdDetached + unref'd with no timeout; if the path that later kills it is never reached (daemon crash, or open succeeds but the app is never closed), the helper survives indefinitely and the SHM/log persist in os.tmpdir(). Don't rely solely on closeIosApp for teardown — register cleanup in the session lifecycle so the helper is reaped on session close, not only on explicit app close. (Today the only stop sites are closeIosApp and the launch-failure catch.)
  • src/platforms/ios/simulator-camera.ts:153-161process.kill can signal a recycled PID and rethrows non-ESRCH errors inside teardown. With no PID-liveness/identity check, a process.kill of a since-recycled helper PID could hit an unrelated process. Verify the PID still maps to the helper (record/compare start time, or use a process group) before signalling, and treat EPERM like ESRCH so teardown isn't masked.
  • src/daemon/handlers/session-open.ts:104,114-120 — confirm ~/-prefixed --camera-video is expanded on every open path. Home-expansion is applied in contextForOpenDispatch, but contextForRuntimeLaunchUrl (line 104) does delete context.cameraVideo. If any open variant reaches dispatchCommand without going through contextForOpenDispatch, a ~/ path is passed unexpanded to fsp.stat and fails with a confusing "file does not exist". Add a test that open with a ~-relative --camera-video resolves against meta.cwd.

Non-blocking

  • src/platforms/android/devices.ts:757-787resolveAndroidEmulatorCameraMode uses existsSync (sync I/O) on the daemon path; prefer the async fsp.stat/isFilePath pattern used elsewhere. The value is passed as a discrete arg (videofile:<resolved>, no shell, so no injection), but a path resolving to something starting with - could be misparsed by emulator — consider rejecting leading-- paths.
  • src/platforms/ios/simulator-camera.ts:1187-1193resolveVendorExecutable only checks fs.existsSync, not the executable bit. The binaries are committed 100755, but an npm-extracted copy could lose the bit; isExecutablePath gives a clearer error than a later spawn EACCES.
  • third_party/serve-sim-camera packaging — verify pnpm pack actually includes third_party/serve-sim-camera/bin/* after the files entry is added; binary assets under a new top-level dir are easy to silently drop, and resolveVendorExecutable hard-fails without them. A packaging smoke assertion is worth adding.
  • iOS-only env injection looks correctly scopedSIMCTL_CHILD_DYLD_INSERT_LIBRARIES is only spread into the simctl launch env when cameraLaunch.env is set, so non-camera launches are unaffected. Add a one-line comment that this env intentionally must not leak to other runXcrun calls.
  • Test coverage — the new iOS test mocks runCmdDetached and asserts env/args well; Android tests cover file/mode/running-emulator rejection. Missing: a test that the helper is stopped on close, plus the home-expansion test above. No tests weakened/removed.
  • Platform gating is solid: camera-video is rejected for non-iOS-simulator in three layers (dispatch.ts:293, apps.ts:913, simulator-camera.ts:1164) and can't enable on physical devices; Android camera is rejected for non-Android and already-running emulators (devices.ts:718). Redundant but defensible defense-in-depth.

🤖 Automated review via Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant