Move back, home, appSwitcher, clipboard read/write, setSetting, and
screenshot into the Interactor abstraction, eliminating scattered
if-platform checks in dispatch.ts.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve screenshot path against caller cwd (#220)
When the daemon process starts from a different directory than the
caller (e.g. executor install dir vs suite workspace), relative
screenshot paths were resolved against the daemon's cwd instead of
the caller's cwd.
Apply the same SessionStore.expandHome(path, meta.cwd) pattern already
used by replay and record before dispatching the command.
Closes#220
* fix: record normalized screenshot paths
---------
Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
* fix: prevent auto-selection of physical iOS devices when simulators are available
When running with --platform ios and a physical iPhone connected but no
simulators booted, the tool would connect to the physical device instead
of booting a simulator. Add a safeguard in resolveTargetDevice that
rejects auto-selected physical devices and falls back to
findBootableIosSimulator, which queries simctl without the isAvailable
filter to find any bootable simulator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty device list and filter unavailable simulators in fallback
- Extract resolveIosDevice() so the iOS fallback logic is testable via
dependency injection without module mocking
- Catch DEVICE_NOT_FOUND from selectDevice when no explicit selector was
used, so the simulator fallback runs even when listIosDevices returns
an empty list (all simulators filtered by isAvailable)
- Restore isAvailable check in findBootableIosSimulator to avoid
returning simulators with missing runtimes that cannot boot
- Add 8 tests covering: physical-device rejection, empty-list recovery,
explicit-selector passthrough, and normal simulator-present path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Physical iOS devices always report booted=true, so the "prefer booted"
tiebreaker would pick them over unbooted simulators. This caused sessions
to bind to a connected iPhone instead of a simulator when no explicit
device selector (--udid/--device/--serial) was provided.
Now, when multiple candidates exist and no explicit device was requested,
physical devices are filtered out if virtual alternatives are available.
The simulator is then booted on demand by the existing ensureBootedSimulator
flow.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The 1.5s per-command timeout in resolveAndroidEmulatorAvdName was too
aggressive for emulators over remote adb daemon transport, causing
open --relaunch to fail during device discovery.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
adb shell concatenates all arguments with spaces into a single string
for the remote shell. Without quoting, the && in the mkdir/cat script
was interpreted as a command separator, causing mkdir to run with no
arguments ("mkdir: Needs 1 argument").
Wrapping the script in single quotes ensures the remote device shell
passes the entire command as one argument to sh -c.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When `xcrun simctl launch` fails with FBSOpenApplicationServiceErrorDomain
code=4 persistently (e.g. architecture mismatch), all 30 retries would fail
identically, wasting 30 seconds before surfacing a generic error. Now we bail
after 3 consecutive identical FBS failures, probe the simulator for root cause
(app not installed, device binary on simulator, etc.), and surface an actionable
hint. Also reduces maxAttempts from 30 to 10 as a secondary safety net.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
resolveAndroidApp treated any input containing a dot as a package name,
which caused APK file paths (e.g. /path/to/app-debug.apk) to flow through
as appId into runtime hints, failing with a misleading "app not debuggable"
diagnosis. Android package names never contain slashes, so exclude paths
with '/' from the dot-based package heuristic.
Also differentiate probe failures in writeAndroidDevPrefs: check
isAndroidRunAsDeniedOutput on the probe result so connectivity/transport
errors are not misattributed to debuggability.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The artifact download endpoint was at GET /artifacts/{id}, but proxies
in the sandbox → proxy → daemon architecture only forward known paths
(/rpc, /health, /upload). Move downloads to GET /upload/{id} so they
share the already-forwarded /upload path (POST for uploads, GET for
downloads).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add remote runtime hints and artifact retrieval
* fix: apply session runtime transport hints on open
* fix: secure and time out artifact downloads