Skip to content

feat(proxy): lease by device spec — --os, --image-tag honoured or refused by the backend - #3865

Draft
V3RON wants to merge 4 commits into
mainfrom
feat/proxy-device-spec
Draft

V3RON wants to merge 4 commits into
mainfrom
feat/proxy-device-spec

Conversation

@V3RON

@V3RON V3RON commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

open, install, reinstall and uninstall through connect proxy can now lease by OS version and Android image tag. A backend that cannot honour the spec refuses it rather than ignoring it. 38 files. Closes #3864.

Flag Key on agent_device.lease.allocate Echo on data.lease.device
--os <version|range> osVersion (opaque string) model, osVersion (exact)
--image-tag <tag> imageTag imageTag?

Rules:

  • The CLI sends the keys only on a by-spec allocate. devices.list is not called first.
  • A daemon refuses any request that names either key. This covers a local daemon and one behind agent-device proxy. The refusal is UNSUPPORTED_OPERATION with reason: 'device-spec-unsupported' and keys, and it happens before admission, so no lease is recorded.
  • If a lease comes back without lease.device, the CLI releases it and fails with the same reason.
  • A live lease serves a command only when every spec key the command names, and any --udid/--serial, matches it. Otherwise the command fails with CONNECTION_DEVICE_CONFLICT.
  • connect proxy stores the keys as defaults. The keys are visible in MCP and the SDK. open --json prints lease.device.
  • Docs: remote-proxy.md, help remote, and an amendment to ADR 0007. --physical is dropped, per the maintainer.
agent-device open MyApp --platform android --os 34 --image-tag google_apis_playstore

Validation

At 4f94d93e8, pnpm check:affected --run --head 4f94d93e8 passed with exactCommit=true; all 36 runnable checks passed. CI owns device lanes and coverage.

Review: three independent Opus rounds. All findings except the gaps below are fixed. The round-3 fixes were not reviewed again.

Assumption: a spec key that a command leaves out does not constrain a live lease. This follows the existing --device rule.
Assumption: env vars cannot set the keys; only flags and connect proxy can.

Known gaps:

  • MCP and the SDK never allocate a proxy lease, so through them the keys are always refused.
  • connect forms other than proxy accept the flags but do not store them, as with --device.
  • If a backend returns the run's existing lease without an echo, the CLI releases that lease.
  • If a spec lease expires and a non-allocating command re-allocates it by device, the new lease has no spec. A later open --os then fails with CONNECTION_DEVICE_CONFLICT.

View guided diff Turn on auto-fix

@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

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

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

@github-actions

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.48 MB 5.49 MB +7.3 kB
Package (unpacked) 5.48 MB 5.49 MB +7.3 kB
Package (download) 1.66 MB 1.66 MB +2.1 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 35.8 ms 35.6 ms -0.1 ms
CLI --help 63.9 ms 69.9 ms +6.0 ms

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Explicit blank spec values can silently become unconstrained requests instead of being validated or refused.

2 open issues
What changed in this PR

Adds OS-version and Android-image-tag selection to proxy device leases, addressing #3864.

Changes:

  • Exposes device-spec options through CLI, SDK, MCP, and connection defaults.
  • Adds backend confirmation, unsupported-spec refusal, and live-lease conflict checks.
  • Reports granted device details and adds tests and documentation.
File Description
website/​docs/​docs/​remote-proxy.md Documents spec-based leasing.
src/​mcp/​__tests__/​command-tools-operator-inputs.test.ts Checks MCP spec fields.
src/​daemon-client/​daemon-client-rpc.test.ts Tests allocation wire fields.
src/​commands/​schema/​option-schema.ts Excludes ambient environment defaults.
src/​commands/​schema/​command-overrides.ts Allows spec flags on connect.
src/​commands/​schema/​cli-help.ts Explains proxy spec behavior.
src/​commands/​management/​lease-device-spec.ts Shares command spec fields.
src/​commands/​management/​install.ts Adds install-family spec inputs.
src/​commands/​management/​app.ts Adds open spec inputs.
src/​commands/​command-flags.ts Projects spec request flags.
src/​client/​lease-client.ts Reads granted device facts.
src/​cli/​connection/​proxy-profile.ts Stores proxy spec defaults.
src/​cli/​connection/​profile-fields.ts Groups hosted allocation fields.
src/​cli/​commands/​router.ts Accepts granted device metadata.
src/​cli/​commands/​router-types.ts Types granted device metadata.
src/​cli/​commands/​proxy-lease-device.ts Validates and binds spec leases.
src/​cli/​commands/​generic.ts Prints granted device facts.
src/​cli/​commands/​connection-runtime.ts Integrates spec allocation and reporting.
src/​cli/​commands/​__tests__/​proxy-lease-device-spec.test.ts Tests proxy spec lifecycle.
src/​cli/​commands/​__tests__/​generic.test.ts Tests granted-device output.
src/​cli.ts Carries lease facts to output.
src/​__tests__/​agent-device-client-leases.test.ts Tests SDK allocation fields.
src/​__tests__/​agent-device-client-lease-device-spec.test.ts Tests daemon and proxy refusals.
scripts/​integration-progress-model.ts Identifies spec-test ownership.
packages/​remote/​src/​remote-connection-state.ts Persists lease-bound spec metadata.
packages/​remote/​src/​remote-config-schema.ts Adds spec profile fields.
packages/​daemon/​src/​server/​request-router.ts Refuses specs before admission.
packages/​contracts/​src/​request-envelope.ts Adds request spec fields.
packages/​contracts/​src/​remote-config-fields.ts Types profile spec defaults.
packages/​contracts/​src/​lease-scope.ts Defines shared spec handling.
packages/​contracts/​src/​facades/​client.ts Exports spec and device types.
packages/​contracts/​src/​client-lease.ts Extends lease contracts.
packages/​contracts/​src/​client-app.ts Extends app-operation options.
packages/​contracts/​src/​cli-flags.ts Types CLI spec flags.
packages/​contracts/​src/​__tests__/​lease-scope.test.ts Tests spec and echo readers.
packages/​command-registry/​src/​flag-groups.ts Shares supported spec flags.
packages/​command-registry/​src/​flag-definitions-target.ts Defines public spec flags.
docs/​adr/​0007-remote-device-leases.md Records spec-leasing decisions.

Review effort: Balanced

Comment on lines +326 to +328
return stripUndefined({
osVersion: nonEmptyString(source.osVersion),
imageTag: nonEmptyString(source.imageTag),
}
return deviceSpecUnsupportedError(
keys,
'The proxy backend leased a device without confirming the requested device spec, so the lease was released.',
@V3RON
V3RON marked this pull request as draft October 21, 2026 12:34
@thymikee

Copy link
Copy Markdown
Member

I reviewed 4f94d93. The design is sound, and one open Copilot thread still holds and needs a fix before this is ready: --os '' is dropped on the CLI but refused on MCP/SDK. readLeaseDeviceSpec drops empty values, while namedLeaseDeviceSpecKeys refuses them, so the two surfaces disagree. Could both surfaces handle an empty value the same way?

The new tests would fail on the old code, because the flags, keys and guard do not exist there. I did not rerun any tests. The honoured path, where a backend echoes lease.device, is proven only against a test-client fake, since simlock#814 is not available for a live allocate by spec.

Not blocking, and fine to take or leave: the release in src/cli/commands/proxy-lease-device.ts:138 uses .catch(() => undefined), so a failed release disappears, while releaseUnhonouredSpecLease puts released/releaseError in the conflict details. The spec key list has two names in packages/command-registry/src/flag-groups.ts:38, and readLeaseDeviceSpec, leaseDeviceSpecFields and the proxy profile code hand-write osVersion/imageTag instead of mapping over LEASE_DEVICE_SPEC_KEYS. The leaseDevice fact travels through the CLI router and then gets a special case for open twice in src/cli/commands/generic.ts:627, so open's own formatter could merge it once. The other Copilot thread also holds: the release message always says it was released, although details.released is right.

Smoke failed on open com.callstack.agentdevicelab --relaunch with android-app-close-timeout on a local daemon. That route uses no spec flag and no remote config, and the Android close and relaunch path does not overlap this diff, so it looks unrelated. Coverage also failed, but its log is cut off before any failing test, so I could not attribute it. Please rerun both, and if Coverage fails again, share the failing case. I know of no conflicts.

This branch has not been deployed

No deployments
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.

Proxy lease by device spec: --os and --image-tag, honoured or refused by the backend

3 participants