Skip to content

refactor(runtime): let platform runtimes list apps and read app state directly - #2295

Merged
thymikee merged 4 commits into
mainfrom
claude/audit-inline-app-hosts
Sep 5, 2026
Merged

refactor(runtime): let platform runtimes list apps and read app state directly#2295
thymikee merged 4 commits into
mainfrom
claude/audit-inline-app-hosts

Conversation

@thymikee

@thymikee thymikee commented Sep 5, 2026

Copy link
Copy Markdown
Member

refactor(runtime): let platform runtimes list apps and read app state directly

Summary

Two root host adapters, appInventory and appState, only forwarded a platform call back into
the platform's own package. Before: apps and appstate left the Apple, Android or Harmony
runtime and crossed into root to reach their own package. After: each runtime makes that
listApps / appState call through a lazy import() inside its package, keeping the deferred
load, the abort threading and the package/bundleId -> id rename. PlatformRuntimeHost loses
both keys, so fixtures stop stubbing platforms they do not own. Android appstate collapses to one
foreground-focus loop, now shared with limrun.

The Android runtime's new in-package adb calls need the process-wide adb host port bound. The
package declares that dependency instead of hiding it: createAndroidRuntimeModule({ bindAdbHost })
awaits the binding before the runtime loads, and the composition root supplies the one binding
implementation (evaluating src/platform-runtime-android-adb-host.ts, which binds once). No
registry wrapper, no caller-must-import-first ordering; the binding stays lazy, so the
composition root's eager closure is unchanged.

33 files touched, all inside the runtime/platform boundary.

Validation

Head 6df2d93627 (typed Android adb-host dependency on top of 558560ab7b): src/platform-runtime-android-adb-binding.test.ts (routed listApps and appState through the composed gateway) and packages/platform-android/src/runtime-facade.test.ts (binding awaited before load; a failing binding keeps the runtime unloaded) green; vitest related on the touched modules 221 files / 1402 tests green; oxlint, oxfmt, tsc (root and packages/platform-android), check:layering, check:fallow --base origin/main and the eager-closure budget gate clean.

Tested commit: 558560a — pnpm check:affected --run green locally (format, lint, typecheck, layering, di-seams, fallow, mcp-metadata, build, package, integration-node, macos-coverage, vitest-related, integration-progress, replay-compat, daemon-wire-compat, affected-selector, gate-manifest, gate-manifest-model, depgraph, tmpdir-leaks, tmpdir-leaks-model, coverage-model, wire-compat-model, production-exports, bundle-owner-files, fixture-cache, fixture-fallback, command-docs, agent-guidance, xctest-selection, maestro-conformance, mutation-model)

On a booted iPhone 17 Pro simulator and emulator-5554, these printed byte-identical
output before and after:

$ apps --platform ios --udid F7D6F9A4-...
Showing user-installed apps or deferred provider app assets. Use --all to include system apps on a live device.
AgentDeviceRunnerUITests-Runner (com.callstack.agentdevice.runner.uitests.xctrunner)
AgentDeviceRunner (com.callstack.agentdevice.runner)
Agent Device Tester (com.callstack.agentdevicelab)

$ apps --platform android --serial emulator-5554
Showing user-installed apps or deferred provider app assets. Use --all to include system apps on a live device.
Agentdevicelab (com.callstack.agentdevicelab)

$ appstate --platform android --serial emulator-5554
Foreground app: com.callstack.agentdevicelab
Activity: com.callstack.agentdevicelab.MainActivity

… directly

The root host carried two adapters, appInventory and appState, that only
forwarded a platform call back into that platform's own package. Each platform
runtime now performs its own listApps and appState call through a lazy import
inside its package, keeping the deferred load, the AbortSignal threading, and
the package/bundleId -> id rename. PlatformRuntimeHost loses both keys, so
Android, Apple and Harmony fixtures no longer stub the two platforms they do
not own.

Android is the one platform runtime whose package now reaches adb directly.
The adb host that adb mechanics require is bound by a module side effect that
only the root can perform, so the Android runtime-module registration binds it
before the module loads. loadAndroidMechanics keeps its own binding import for
the root host ports that reach mechanics without binding a runtime; neither
binder subsumes the other.

Android appstate now runs one foreground-focus loop instead of two. The host
shaped readAndroidAppState/AndroidAppStateHost pair is gone: limrun's adapter
already closes over its own adb executor, so it calls the executor variant
directly, and that variant took the per-attempt abort check the host variant
had. AppStateRuntimeCommand and AppStateRuntimeCommandResult described the
deleted host port and go with it.

Tests: the new ordering test in
src/platform-runtime-android-adb-binding.test.ts was seen red by deleting the
binding import from that registration (order came back
["android-runtime", "adb-host"]); the composed-gateway listApps test in the
same file was seen red by reverting the Android runtime's inlined listApps to a
host.appInventory lookup (TypeError reading 'android'); the new abort test in
packages/platform-android/src/app-state.test.ts was seen red by removing both
signal?.throwIfAborted() calls from readAndroidFocusWithExecutor (the second
dumpsys was issued and the call resolved). All green after.
The two PLATFORM_RUNTIME_HOST_FILES rows point at host files this change
deletes, and the ./platform-runtime-app-state-host.ts composition allowance has
no importer left.
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.44 MB 4.44 MB +213 B
Package (unpacked) 4.44 MB 4.44 MB +213 B
Package (download) 1.32 MB 1.32 MB +506 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.8 ms 27.6 ms -0.2 ms
CLI --help 80.3 ms 81.4 ms +1.1 ms

@thymikee

thymikee commented Sep 5, 2026

Copy link
Copy Markdown
Member Author

Reviewed exact head 558560ab7bf31a11775290ffc842e3c5a886cb7e. The platform-owned app inventory/state move and behavioral coverage are otherwise sound, but one P2 architecture blocker remains: the new Android route depends on a side-effect-only import of platform-runtime-android-adb-host.ts, and the new registry wrapper plus loadAndroidMechanics now maintain two separate hidden initialization paths. The paragraph explaining why neither subsumes the other and the import-order regression test are evidence that the dependency is implicit rather than owned. Please expose one typed construction/binding path for the Android runtime's ADB dependency (or otherwise make one owning interface initialize it exactly once), remove the duplicated side-effect setup, and keep the real routed listApps/appState tests. This should not encode another caller-must-import-first invariant.

…host binding

The Android runtime now calls adb from inside its package for listApps and
appState, which needs the process-wide adb host port bound. That dependency
was hidden in a registry wrapper doing a side-effect import, with a paragraph
explaining why it and loadAndroidMechanics did not subsume each other and an
import-order test pinning the ordering. The package now declares the
dependency: createAndroidRuntimeModule({ bindAdbHost }) awaits the binding
before the runtime loads, and the composition root supplies the one binding
implementation (evaluating its adb host module). The wrapper, the paragraph
and the import-order test are gone; the routed listApps test stays and a
routed appState test joins it.
@thymikee

thymikee commented Sep 5, 2026

Copy link
Copy Markdown
Member Author

Addressed in 6df2d93. The Android runtime module is now constructed with its adb dependency declared in the package's own interface: createAndroidRuntimeModule({ bindAdbHost }) awaits the binding before the runtime loads, and the composition root supplies the one implementation (evaluating src/platform-runtime-android-adb-host.ts, which binds the port exactly once per process). The registry wrapper, its paragraph and the import-order test are gone. A static import at the root was tried first and rejected by the eager-closure budget gate (+8 modules), so the binding stays lazy behind the typed dependency. loadAndroidMechanics is unchanged from main; it serves root host ports and SDK surfaces that reach mechanics without a runtime. Kept the routed listApps test and added a routed appState test through the composed gateway; runtime-facade.test.ts pins that the binding is awaited before load and that a failing binding keeps the runtime unloaded.

* origin/main:
  perf: bundle runtime dependencies and report full install size (#2310)
  ci: avoid unrelated Apple runner cache invalidation (#2303)
  fix(web): preserve the backend ref so snapshot refs match actionable refs (#2283)
  test(daemon): session-open-url-prewarm through the request seam (#2304)
  test(daemon): session-devices-batch-runtime through the request seam (#2305)
  chore(gates): layering baselines ratchet against merge-base (#2299)
  test(daemon): one typed conformance helper for the daemon runtime suites (#2298)
  chore(layering): derive the contracts export inventory from package.json (#2297)
  perf: bundle tar-stream to reduce install footprint (#2286)
  docs: simplify agent context and resolve conflicting guidance (#2287)
  refactor(cli): let help resolve command aliases itself and retire R12 (#2293)
  refactor(commands): retire the navigation-only type projection (#2294)
  feat(runtime): route managed leases through contained transports (#2285)
  refactor(contracts): build unavailable runtime facts once (#2291)
  refactor(cli): derive the common flag readers from the common-field table (#2292)
  feat(daemon): add managed allocation operation journal (#2284)
@thymikee
thymikee merged commit 0c8227e into main Sep 5, 2026
18 checks passed
@thymikee
thymikee deleted the claude/audit-inline-app-hosts branch September 5, 2026 20:40
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-05 20:41 UTC

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