Skip to content

feat(mobile): a device lease carries agent-device client configuration - #416

Merged
okwasniewski merged 2 commits into
mainfrom
feat/mobile-lease-client
Sep 22, 2026
Merged

okwasniewski merged 2 commits into
mainfrom
feat/mobile-lease-client

Conversation

@okwasniewski

@okwasniewski okwasniewski commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

A DeviceLease can now carry client: agent-device client configuration the worker's client is created with, next to the daemon address, which becomes optional.

  • agent-device ships its own device-cloud runtimes (Limrun, BrowserStack, AWS Device Farm). Their daemon resolves a hosted device from the lease scope on each command: tenant, runId, leaseId, leaseBackend, leaseProvider, as leases.allocate returned it. A command without that scope falls back to the local inventory, and on a Mac with a booted simulator the test silently runs there while the hosted instance sits idle.
  • The engine's client only knew a session name and a daemon URL, so a provider on one of those runtimes had to put a proxy in front of the daemon that stamped the scope onto every request. Now it returns what leases.allocate gave it.
  • client.stateDir reaches a daemon the provider started for the run, so such a provider needs no daemon address at all. A lease names daemon, client, or both.
  • client is JSON data, since it travels to the worker through the environment under the existing 16 KiB cap. session, daemonBaseUrl, and daemonAuthToken stay with the engine and daemon; a lease setting them is refused at prepare.
  • DeviceClientConfig and DeviceConnection are exported. The mobile guide gains a second example, a provider on a device cloud through agent-device, typechecked like the first.
const lease = await client.leases.allocate({ tenant, runId, leaseBackend, leaseProvider, platform, target: 'mobile' });
return {
  id: lease.leaseId,
  client: { stateDir, tenant: lease.tenantId, runId: lease.runId, leaseId: lease.leaseId, leaseBackend, leaseProvider },
};
flowchart LR
  P[provider.acquire] -->|lease.client| B[SlotBinding in worker env]
  B --> F["createAgentDeviceClient({ ...client, session, daemonBaseUrl?, daemonAuthToken? })"]
  F --> D[daemon]
  D -->|lease scope on every command| H[hosted device]
Loading

Refs: packages/mobile/src/bindings.ts (DeviceClientConfig, isSlotBinding), packages/mobile/src/provider.ts (DeviceLease, isDeviceLease), packages/mobile/src/engine.ts (the client factory), docs/examples/mobile/device-cloud-provider.ts.


Devin Review

@mintlify

mintlify Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
e2e 🟢 Ready View Preview Sep 22, 2026, 12:37 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e web: 11 passed

All 11 tests in 6 files
Test Time
🟢 tests/deferred-dialog.e2e.ts · 1 passed 1.2s
🟢 one submit opens one confirm, even when double-clicked 1.2s
🟢 tests/filter-deep-link.e2e.ts · 2 passed 246ms
🟢 filter deep link › filtering writes the query to the URL 122ms
🟢 filter deep link › opening a shared link restores the filter 124ms
🟢 tests/login-form.e2e.ts · 4 passed 748ms
🟢 login form › rejects a malformed email 175ms
🟢 login form › rejects a short password 176ms
🟢 login form › rejects an unknown account 191ms
🟢 login form › signs in and logs out 206ms
🟢 tests/overlay-trap.e2e.ts · 1 passed 716ms
🟢 continues only after the banner and the overlay are gone 716ms
🟢 tests/rich-text-editor.e2e.ts · 2 passed 444ms
🟢 rich text editor › bolding exactly the required word passes the check 215ms
🟢 rich text editor › bold that spills into the previous word fails the check 229ms
🟢 tests/smoke.e2e.ts · 1 passed 402ms
🟢 home lists the scenarios and opens one 402ms

e2e 0.15.0-canary-20260921154506 · 4.3s · web · run artifacts

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 2 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread packages/mobile/src/bindings.ts Outdated
}

function isJsonValue(value: unknown): boolean {
if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return true;

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.

🟡 Client numbers mutate between processes

When client contains NaN or infinity, isJsonValue accepts it before JSON serialization converts it to null. Warm-up and workers then use different client configuration.

Learn more

JSON has no representation for NaN, Infinity, or -Infinity. JavaScript's JSON.stringify silently emits null for each value instead of rejecting it. The runner-side warm-up uses the original lease object, but child workers reconstruct the binding from serialized JSON. A non-finite client option therefore changes after warm-up.

Example: A provider returns client: { leaseTtlMs: Infinity }. Warm-up creates a client with Infinity; the worker receives { leaseTtlMs: null }.

Recommended fix: Accept numeric values only when Number.isFinite(value) is true, and add coverage for all three non-finite values.

Suggested change
if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return true;
if (value === null || typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value)) || typeof value === 'boolean') return true;

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 8b71786: isJsonValue accepts a number only when Number.isFinite holds, and the refusal test covers NaN and both infinities.

Comment thread packages/mobile/src/bindings.ts Outdated
* worker through the environment; `session`, `daemonBaseUrl`, and
* `daemonAuthToken` are not part of it.
*/
export type DeviceClientConfig = Omit<AgentDeviceClientConfig, 'session' | 'daemonBaseUrl' | 'daemonAuthToken'>;

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.

🟡 Typed transports always fail leasing

DeviceClientConfig includes the function-valued daemonTransport, but isClientConfig rejects every function. Providers using this exported option typecheck, then every lease fails during prepare.

Suggested change
export type DeviceClientConfig = Omit<AgentDeviceClientConfig, 'session' | 'daemonBaseUrl' | 'daemonAuthToken'>;
export type DeviceClientConfig = Omit<AgentDeviceClientConfig, 'session' | 'daemonBaseUrl' | 'daemonAuthToken' | 'daemonTransport'>;

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In agent-device 0.21.6 daemonTransport is a string preference (auto | socket | http), not a function, so a typed value would have leased fine. It is still not a device fact but how the worker reaches its daemon, so 8b71786 drops it from DeviceClientConfig and refuses it at prepare like the other reserved keys.

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e web agent: 21 passed, 3 skipped

22 agent steps · 83 model calls · 294.9k tokens (83% cached) · $0.0426

All 24 tests in 4 files
Test Agent Time
⏭️ tests-agent/scenarios.e2e.ts · 16 passed, 3 skipped 16 steps · 69 calls 2m 50s
⏭️ act completes shadow-dom-form (skipped: skipped)
🟢 act completes lying-labels 1 step · 5 calls 9.0s
🟢 act completes iframe-form 1 step · 3 calls 5.9s
🟢 act completes infinite-scroll 1 step · 5 calls 20.9s
⏭️ act completes virtualized-table (skipped: skipped)
🟢 act completes overlay-trap 1 step · 4 calls 7.7s
🟢 act completes sticky-chrome 1 step · 5 calls 18.1s
🟢 act completes async-states 1 step · 6 calls 16.0s
🟢 act completes debounced-search 1 step · 4 calls 13.1s
🟢 act completes hover-menu 1 step · 5 calls 9.4s
🟢 act completes context-menu-trap 1 step · 5 calls 10.2s
🟢 act completes stale-dom 1 step · 4 calls 6.7s
🟢 act completes otp-auto-advance 1 step · 3 calls 6.7s
🟢 act completes date-picker 1 step · 7 calls 13.3s
⏭️ act completes rich-text-editor (skipped: skipped)
🟢 act completes playbook-cleanup 1 step · 5 calls 9.9s
🟢 act completes promo-storefront 1 step · 3 calls 11.4s
🟢 act completes gift-card-purchase 1 step · 3 calls 8.0s
🟢 act filters the list and the URL carries the filter 1 step · 2 calls 3.8s
🟢 tests-agent/login-form.e2e.ts · 1 passed 1 step · 3 calls 8.8s
🟢 act signs in with the benchmark account 1 step · 3 calls 8.8s
🟢 tests-agent/planted-bugs.e2e.ts · 3 passed 4 steps · 8 calls 24.5s
🟢 cart totals: a judgment catches the stale order summary 2 steps · 4 calls 15.8s
🟢 checkout review: a judgment catches the broken copy 1 step · 1 call 2.4s
🟢 wishlist: a judgment catches the lost saved items 1 step · 3 calls 6.2s
🟢 tests-agent/unique-cache.e2e.ts · 1 passed 1 step · 3 calls 4.9s
🟢 act adds an address with a run-unique label 1 step · 3 calls 4.9s

e2e 0.15.0-canary-20260921154506 · 3m 29s · web · run artifacts

@okwasniewski
okwasniewski merged commit 41f3b32 into main Sep 22, 2026
8 checks passed
@okwasniewski
okwasniewski deleted the feat/mobile-lease-client branch September 22, 2026 12:48

This branch was successfully deployed

1 active deployment
staging - docs — 8b71786e Deployed Sep 22, 2026 by mintlify[bot]
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.

2 participants