Repository navigation
refactor: declare the profile fields each provider consumes, once - #3168
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 32 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
The code in 54890d4 looks correct to me, and the one check on this PR is green. I did not run the eager-closure budgets, the layering scan or the test suites. I also did not run it against a live cloud provider. The change refuses before any provider contact, so I did not ask for that. Nothing here blocks merge. Cubic's review of this head and #3165 landing first are what remain before merge. One state I did not check: a lease that is active in the registry with no in-memory provider session, for example a recovered Limrun lease. There, a failed re-creation now keeps the lease until its TTL instead of releasing it. Could you confirm whether that state can happen? Not blocking, take or leave: the new Could the checker and type sit next to The four earlier inline threads from Cubic are fixed at this head, so you can resolve them: #3168 (comment), #3168 (comment), #3168 (comment) and #3168 (comment). |
54890d4 to
fe6d0e8
Compare
|
Now that #3165 has merged, I've rebased this onto The rebase surfaced two gate failures, each fixed in its own commit with no behaviour change:
On the recovered-lease question: that state can't happen.
The only way to get an active lease with no provider session is two allocations at once for the same run and client. While the first is still waiting on the provider, the second sees the lease as reused. If the second fails, keeping the lease is correct; releasing it would pull the lease out from under the first allocation, which would then return an inactive lease and leave its instance without an owner. A related pre-existing issue, outside this PR: in that same concurrent case, if the second Limrun allocation succeeds, The non-blocking restructure (checker and type in the contracts |
…r consumes Lease allocation forwards every provider profile field, but only some routes checked which ones a provider could act on. The connect builder and the AWS Device Farm session preparation refused BrowserStack device features, while the Limrun lease runtime, BrowserStack itself, and a remote-config profile skipped any check. A typed client asking Limrun for providerOsVersion '18.0' was therefore given whatever instance Limrun picked without an error. Each lease provider now declares, for every Cloud provider profile field, whether it consumes or refuses it. The declaration is a total record over CloudProviderProfileFields, so adding a field fails to build until every provider decides. One refusal, rejectRefusedProviderProfileFields from the new @agent-device/contracts/provider-profile-fields subpath, runs at lease preparation (the WebDriver session manager for BrowserStack and AWS Device Farm, and Limrun allocation) and at connect, so connect, leases.allocate and remote-config profiles all fail the same way with INVALID_ARGS naming the flag and the provider. This replaces the BrowserStack-only device-feature check. AWS Device Farm now also refuses the hub-only app, OS version, project and build flags it never read, and BrowserStack refuses the AWS Device Farm flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… too The daemon hands a run's repeat lease_allocate the lease it already holds, and both the WebDriver runtime and the Limrun runtime returned the live session for a known lease before checking the request's profile fields. A second allocation on the same run that set a refused field, such as awsProjectArn on BrowserStack, therefore succeeded. Both runtimes now refuse before reusing a live session. Because a refusal of that repeat request is a provider allocate failure, the lease handler no longer releases a lease it reused when allocate throws; doing so would end the run's lease and leave the session the first allocation created without an owner. The runtime's profileFields option is now required, and each provider definition passes its own declaration into createRuntime, so a provider that forgets to wire its declaration fails to compile instead of silently refusing nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the profile field map The refusal's field-to-flag table and the flag column of BROWSERSTACK_DEVICE_FEATURE_SPECS spelled the same flags twice. PROVIDER_PROFILE_FIELD_FLAGS now lives beside CloudProviderProfileFields and is exported from @agent-device/contracts/remote, which the BrowserStack table already imports, so its rows read each flag from that map without growing the provider-webdriver eager closure. The refusal reads the same map. A command-registry test pins every entry to the CLI's canonical flag for the field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…celed A repeat allocation reuses the run's live lease, and a provider refusal leaves that lease in place. A requester that hangs up also surfaces as an allocate rejection, because the request signal aborts with the typed cancellation, so the handler preserved the lease for a requester that would never release it, and the provider session from the first allocation lingered until the TTL sweep. Release the lease and its provider session through the gone-requester path when the request was canceled, reused or not. Only a refusal while the requester still waits preserves the reused lease. The refused-repeat test now also asserts the first allocation's provider session is still owned by the lease. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The refusal builder emits INVALID_ARGS with the refused flags in details.flags. Key the runtime, lease, connect and adapter tests on those typed details so rewording the message or a provider label does not break them. The builder's own unit tests still pin the message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refusal named only the canonical --provider-* flag, so a user who set the field through an alias such as --os-version was told to drop a flag they never typed. The flags bag keeps the field, not the spelling, so the message now lists every accepted spelling, for example "--provider-os-version (--os-version)". details.flags stays canonical. Contracts cannot depend on the command registry, so the aliases live in PROVIDER_PROFILE_FIELD_FLAG_ALIASES, and a command-registry test checks that map against the aliases the CLI parses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's provider-only work pass and the reused-lease rules met in the lease_allocate handler and pushed it over the complexity threshold. Move the failure handling (release a new lease, keep a reused one, release a reused one whose requester left) into settleFailedAllocation. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cloud-connect-profile.test.ts is over the test-file size tripwire and may not grow. The two new refusal tests go to cloud-connect-profile-fields.test.ts, which mirrors the profile builders they exercise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Allocate and connect against an existing instance (LIM_IOS_INSTANCE_URL and token, no API key) accept the consumed --provider-app and refuse the rest, like a created instance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fe6d0e8 to
2386729
Compare
|
Rebased onto
One question I noticed but didn't change, since it's #3173's behaviour: on the attach path, |
|
This PR is ready at 2386729. The conflict from the earlier review (#3168 (comment)) is resolved, and all 16 checks pass on this head. There are no conflicts, and it needs a maintainer approval to merge. Not blocking, and you can take or leave these: the doc comment for I did not run the tests locally, and I did not re-review the four threads resolved before 54890d4. I did not run a live Limrun attach. A refused flag would fail before any provider client is created, so a unit test covers that route. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks. In 3d27e71, the On Limrun's attached instances: yes, I'd leave that for the per-route validation. Today |
|
Rechecked at 3d27e71. The only change since 2386729 moves the |
Reverts 62e814c. The maintainer judged the connect-route check redundant: plugin resolve and lease allocation both refuse those fields, and callstack#3168 owns profile-field admission. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
callstack#3168 landed the provider profile-field declarations and their refusal on main. This PR carried an earlier copy of the same work, so the conflicts resolve to main's version throughout: rejectRefusedProviderProfileFields with alias-aware messages, PROVIDER_PROFILE_FIELD_FLAGS and its aliases in remote-config-fields, the BrowserStack, AWS Device Farm and Limrun declarations (including the attach-path refusal), settleFailedAllocation in the lease handler, and main's tests. What stays from this PR is the providerDeviceType field TestMu introduces: its flag map entry, a 'refused' row in each existing declaration, and a 'consumed' row in the test declarations. The BrowserStack device-type integration test is dropped; main's repeat-allocation test covers that refusal path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(provider-webdriver): share hub upload and app-reference helpers
BrowserStack's app upload, install adapter, --provider-app resolution,
session-details URL artifacts, and orientation check were written for
one vendor. A second hosted Appium hub needs the same mechanics with a
different form field, reference scheme, and response shape, so move
them into shared helpers and have BrowserStack use them:
- webdriver-utils.ts: postHubAppUpload, createHubUploadApp,
resolveHubAppReference, appFileUploadForm, asRecord,
readProviderJsonBody, and requireProviderDeviceOrientation.
- artifact-results.ts: urlArtifactFromDetails.
- browserstack.ts: resolveBrowserStackAppReference, moved out of
provider-definitions.ts.
Two small BrowserStack behaviour changes come with the shared code:
- An upload response that is not JSON (a gateway error page, an empty
body) now fails with a typed COMMAND_FAILED that carries the HTTP
status, instead of a raw JSON SyntaxError.
- The http(s) scheme of a --provider-app URL is matched
case-insensitively, so HTTPS://... is passed through to the hub rather
than being treated as a local path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(provider-webdriver): bound and type session-details lookups
The BrowserStack session-details lookup behind `artifacts` had no
deadline, so a stalled API call could hang the command indefinitely. A
transport failure or a body that was not JSON surfaced as an untyped
fetch or SyntaxError, and a JSON array passed the object check and was
read as session details.
Add fetchProviderSessionDetails to webdriver-utils.ts and use it for
BrowserStack. It sends basic auth with a 15 second deadline and reports
every failure as COMMAND_FAILED: a timeout or network error with a retry
hint and the original error as its cause, and a non-2xx answer or a body
that is not a JSON object with the HTTP status and the parsed response.
Connection verification gets the same treatment through
fetchProviderVerificationJson, which BrowserStack now uses in place of
its private fetch. Behaviour is unchanged: 401/403 is UNAUTHORIZED with
a credential hint, any other HTTP failure points at the provider's
service status, and a transport failure points at network access. A
new test pins the two non-credential hints. sameOsVersion moves
alongside it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(provider-webdriver): add TestMu AI emulator and simulator provider
Add `testmu`, a hosted WebDriver provider for TestMu AI (formerly
LambdaTest) virtual devices: Android emulators and iOS simulators behind
the TestMu AI Appium hub. It follows the BrowserStack shape:
`connect testmu` verifies and saves a local profile, and `open` creates
the hosted session. The service hostnames still carry the lambdatest.com
domain.
connect
- Reads LT_USERNAME and LT_ACCESS_KEY, the variables TestMu AI SDKs use.
- Checks the device and OS version against the public virtual-device
catalog. The match is exact because the hub rejects `18` for a device
listed as `18.0`; the error lists the versions the device offers.
- Uses the authenticated app listing as the credential check, and looks
an lt:// id up in the list for the session's runtime (`emulator` or
`simulator`). An id that is not listed is reported as configured,
since TestMu AI validates it at session creation.
- Verifies against TESTMU_API_ENDPOINT when it is set, as the runtime
does, and composes catalog and listing URLs with URL so a base or
override that carries a query keeps it.
- Never creates a session.
Sessions
- Standard Appium keys stay `appium:`-prefixed; everything vendor-
specific goes in `lt:options`, merged per key with any configured
`lt:options`. `isRealMobile: false` and `w3c: true` are applied last,
so configuration cannot move the session to another device pool or
off the W3C dialect agent-device speaks.
- `appiumVersion` is sent only when --provider-appium-version pins one;
otherwise TestMu AI starts its default server for the device.
- Orientation, geo-location, timezone, Appium version, language, and
locale map onto `lt:options` through a table. The BrowserStack-only
network-profile, custom-network, and no-resign flags are refused by
flag name at connect and at session preparation, instead of being
silently dropped.
Apps
- --provider-app takes an lt:// id, an http(s) URL, or a local path. The
hub only accepts lt:// references, so a URL is handed to the upload
API to fetch (`storage=url`) and a local file is uploaded.
- An unzipped iOS `.app` directory is rejected before any request, with
a hint to zip it.
- Only a well-formed lt:// reference or app id in the upload response
counts as success.
Artifacts
- Read from the session-details API through the shared bounded lookup.
A 404 reads as pending until TestMu AI publishes the details.
`console_logs_url` is the device log on virtual devices.
- Session video, Appium, network, and command logs, screenshots, and
the dashboard link are returned once at least one artifact URL exists.
TESTMU_WEBDRIVER_ENDPOINT, TESTMU_APP_UPLOAD_ENDPOINT, and
TESTMU_API_ENDPOINT redirect the endpoints.
The session, upload, verification, and device-feature modules are loaded
with dynamic import, so the package entry's eager module graph does not
grow. The CLI reaches the device-feature checks through a new
`./testmu-device-features` subpath export. A .fallowrc entry covers the
exports that are read only through the dynamic import. `connect testmu`
is listed in the connect usage, the remote help topic, and the artifacts
provider description.
Co-authored-by: gautam-jain-dev <gautamj@lambdatest.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(provider-webdriver): support TestMu AI real devices
TestMu AI serves real devices and virtual devices from the same Appium
hub; `lt:options.isRealMobile` selects the pool. Add
`--provider-device-type real|virtual` to choose it. The default is
`virtual`, so existing `testmu` profiles and sessions keep their current
behaviour.
The value is a cloud provider profile field like the others. It is
defined in contracts as PROVIDER_DEVICE_TYPES and carried through the
lease_allocate projection, request overrides, the remote-config schema,
`connect`, the CLI request flags, and the session doctor's provider
keys. It reaches session preparation from a `connect testmu` profile and
from `client.leases.allocate({ providerDeviceType })`.
For `real`, TestMu AI sessions:
- Set `isRealMobile: true`. It is applied after any configured
`lt:options`, so configuration cannot switch pools.
- Upload through the real-device upload API. It has its own override,
TESTMU_REAL_DEVICE_APP_UPLOAD_ENDPOINT; TESTMU_APP_UPLOAD_ENDPOINT
keeps redirecting virtual-device uploads only. An `.app` directory is
refused with a hint to pass a signed .ipa.
- Verify against the real-device catalog
(`capability/generator?isVirtualDevice=false`). It lists devices
directly under the platform key rather than under `app.devices`. The
OS version must still match exactly, and real iOS devices are listed
by major version, such as `18`. A catalog response without the
expected pool shape fails with a typed error.
- Look an lt:// id up in the real-device app list (`type=android` or
`type=ios`), the same way virtual uploads are looked up under
`emulator` or `simulator`.
BrowserStack, AWS Device Farm, and Limrun refuse the flag at connect.
BrowserStack and AWS Device Farm also refuse it at session preparation,
which the typed client and hand-written profiles reach without
`connect`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(provider-webdriver): upload the archive of materialized iOS builds
Install from a remote source materializes an iOS build by extracting the
`.app` bundle from a zipped simulator build or an .ipa. The WebDriver
deployment runtime handed that extracted `.app` directory to the
provider's uploader. Hosted upload APIs take a file, not a directory, so
the upload could not succeed.
When the materialized artifact is an iOS `.app` extracted from a `.zip`
or `.ipa` archive, upload the archive it came from. Every other case
uploads the installable path as before: no archive, an archive of
another type, or an Android build. A provider without an uploader still
installs the extracted bundle path. The bundle id and launch target
hints are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: add the TestMu AI device cloud guide
Add a TestMu AI guide next to the BrowserStack and AWS Device Farm ones
and link it from the sidebar. It covers credentials and `connect`, the
exact device and OS version match, app references and uploads, device
features, real devices with `--provider-device-type real`, the CLI and
Node.js client workflows, artifacts, and endpoint overrides.
List TestMu AI among the device clouds in the README, the device clouds
overview, the client API page, and the command reference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(daemon): start a lease's TTL when its allocation completes
The registry records a lease, and stamps its expiry, before the lease
lifecycle provider allocates the session behind it. Hosted providers can
spend 30-80 seconds creating that session, so a lease on the default
one-minute inactivity window was expired, or nearly so, by the time its
client received it: the next command failed with "Lease is not active",
release reported nothing to release, and the paid provider session was
left running. The expiry sweep could also reap the lease while the
provider was still allocating.
Hold a lease work pass for the duration of the provider allocation, the
same protection admitted request work gets. The lease cannot expire
underneath the allocation, and ending the pass while the requester is
still waiting renews the lease for its own TTL from that moment. The
response now carries the renewed lease. A requester that hung up still
protects nothing, and its allocation is released as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(provider-webdriver): declare the profile fields each provider consumes
Lease allocation forwards every provider profile field, but only some
routes checked which ones a provider could act on. The connect builders
and the BrowserStack/AWS session preparation refused flags they did not
own, while the Limrun lease runtime and a remote-config profile skipped
that check. A typed client asking Limrun for providerDeviceType 'real'
was therefore given a simulator without an error.
Each lease provider now declares, for every Cloud provider profile field,
whether it consumes or refuses it. The declaration is a total record over
CloudProviderProfileFields, so adding a field fails to build until every
provider decides. One refusal, derived from that declaration, runs at
lease preparation (the WebDriver session manager for BrowserStack, AWS
Device Farm and TestMu AI, and Limrun allocation) and at connect, so
connect, leases.allocate and remote-config profiles all fail the same
way with INVALID_ARGS naming the flag and the provider.
This replaces the per-provider TestMu-only, TestMu-unsupported and
BrowserStack-only checks, the hand-copied complement of the BrowserStack
feature table, and the ./testmu-device-features package subpath. AWS
Device Farm now also refuses the hub-only app, OS version, project and
build flags it never read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(provider-webdriver): let the Apple materializer name the uploadable file
The WebDriver deployment runtime guessed what to upload for a materialized
iOS build from file extensions: an extracted .app with a .zip or .ipa
archive uploaded that archive. The archive it saw is the outermost one the
source arrived as, so a URL .zip wrapping an .ipa uploaded the wrapper zip
instead of the .ipa, and a zip holding a .app.tar.gz uploaded a zip with no
app bundle at its top.
Materialization now records the archive an installable was extracted from
directly, and the Apple materializer declares the file a hosted provider
uploads: the .ipa itself, or the zip a simulator .app was extracted from.
It names nothing when no such file exists. The deployment runtime uploads
the declared file and otherwise the installable path, with no inference of
its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(provider-webdriver): tighten app-reference, endpoint and upload input handling
Several inputs reached a hosted provider in a shape it could not use, or
failed with an untyped error:
- An empty `lt://` passed as a TestMu AI app reference and reached session
creation. A reference is now validated against the app-id grammar, and
an invalid one is INVALID_ARGS on connect and at session preparation.
- `LT://APP1` or `BS://id` was treated as a file path. App schemes now
match case-insensitively and are canonicalized to the spelling the hub
accepts.
- A directory given as `--provider-app` reached the upload and failed
with a raw EISDIR; it is now INVALID_ARGS with a hint.
- A missing path given straight to the TestMu AI upload failed with a
bare ENOENT; it now gets the same typed refusal as a directory.
- Session-detail URLs were built by string concatenation, so a query on
TESTMU_API_ENDPOINT or the BrowserStack details endpoint swallowed the
route. Routes are now appended to the URL path, keeping the query.
- A whitespace-only artifact URL was reported as a ready artifact; it is
now treated as absent, and URLs are trimmed.
- A 2xx connection-verification answer that is not JSON was reported as
a network failure. It is now COMMAND_FAILED with the HTTP status, and
each hub supplies its own service-status hint.
The connect help and the TestMu AI guide now say what connect checks for
`--provider-app` and what is only validated when the session is created.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(provider-webdriver): exercise in-flight deadlines and aborts for real
The BrowserStack session-details timeout test threw a prebuilt
TimeoutError from the fetch stub, so it passed even with the deadline
removed. The fetch now stays pending until the signal the lookup armed
aborts, and the test checks that signal is the 15 second deadline.
The TestMu AI upload cancellation test aborted before the upload reached
fetch, so it never covered a request in flight. It now waits for the
stub to start before aborting.
The connect suites shared a copied connectWithGeneratedProviderProfile
helper; it now lives in the shared test utilities.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(provider-webdriver): refuse profile fields on a repeat allocation too
The daemon hands a run's repeat lease_allocate the lease it already
holds, and both the WebDriver runtime and the Limrun runtime returned
the live session for a known lease before checking the request's
profile fields. A second allocation on the same run that set a refused
field, such as providerDeviceType on BrowserStack, therefore succeeded.
Both runtimes now refuse before reusing a live session. Because a
refusal of that repeat request is a provider allocate failure, the lease
handler no longer releases a lease it reused when allocate throws; doing
so would end the run's lease and leave the session the first allocation
created without an owner.
The runtime's profileFields option is now required, and each provider
definition passes its own declaration into createRuntime, so a provider
that forgets to wire its declaration fails to compile instead of
silently refusing nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(provider-webdriver): validate and canonicalize app references at connect
Connect only checked that an lt:// or bs:// reference had something after
the scheme, so `lt://a b` and `lt://a/b` were saved and failed only when
the hub created the session. Both hubs' reference grammars now live in
the light providers module, and connect, session preparation and the
TestMu AI upload reader all validate against them.
Connect also verified the raw spelling typed on the command line: the
generated profile stored `lt://APP1` for `LT://APP1`, but the flags
handed to verification were the raw CLI flags laid over the profile, so
the uploaded-app lookup missed and reported a local artifact. The
canonical reference now wins in the flags verification reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(provider-webdriver): refuse a directory before any hosted upload
A materialized build that names no uploadable file falls back to its
installable path, which for iOS is the extracted .app directory. The
deployment runtime handed that to the hub's uploader, and BrowserStack's
read it as a file and failed with a raw EISDIR.
The deployment runtime now refuses a directory before calling any hub's
uploader, with INVALID_ARGS naming the provider and path and a hint to
zip the .app or pass the .ipa, .apk, or .aab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): refuse a non-URL install-from-source source with a typed error
install-from-source sends its positional as a URL source. A local path
reached the iOS trusted-host check, where `new URL()` threw a TypeError
that surfaced as an untyped UNKNOWN "Invalid URL".
The CLI now refuses a positional that is not an http(s) URL with
INVALID_ARGS and points at `install <app> <path>` for local builds, and
the trusted-host check reports an unparsable source as INVALID_ARGS
instead of throwing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: support provider-owned plugin connections and WebDriver adapters
* chore(gates): expose the optional WebDriver plugin configuration
* feat: load TestMu from its separately installed provider plugin
* chore(gates): declare TestMu plugin build dependencies and analysis entrypoints
* fix: retain plugin tests through development-only package exports
* chore(gates): track TestMu packaging fixtures and workspace test dependencies
* chore(gates): smoke the optional packed WebDriver plugin SDK
* perf: load the shared WebDriver engine on demand
* chore(gates): bound the optional plugin SDK declarations
* fix: consolidate TestMu plugin helper imports
* fix(testmu): trim credentials and log URLs, canonicalize lt:// everywhere
Whitespace-only LT_USERNAME/LT_ACCESS_KEY now fail as missing in the
runtime, matching connect. Verification of a hand-authored profile,
upload responses, and app-list ids share one case-insensitive lt://
canonicalization, and a blank console_logs_url falls back to
device_logs_url.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(testmu): export credentials in help and note the fixed app listing
The help flow's bare assignments did not reach the next agent-device
process. The endpoint overrides do not move the app listing connect
uses for the credential check, so say so instead of implying a private
deployment is fully redirectable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(kernel): require a real Error behind the AppError brand
A plain object carrying the global brand symbol passed instanceof
AppError and left normalizeError with no code or message. Package copies
share the realm's Error.prototype, so the plugin-copy case still holds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(plugins): refuse a primitive factory result as an invalid plugin
'webDriver' in <primitive> threw a raw TypeError past the loader's
INVALID_ARGS validation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(plugins): archive into a fresh zip, not over a stale one
zip -r updates an existing archive in place, so leftover entries from an
earlier attempt would ride along in the upload.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(connect): look plugin capabilities up in the caller's environment
isConnectProviderName already took an env; connectionProviderCapabilities
read process.env, so under another plugin home the two disagreed and a
plugin provider fell back to the builtin capability shape.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(kernel): type the AppError brand lookup on a narrowed Error
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(plugins): reserve provider ids from one list
assertUniquePluginProviders merged RESERVED_PLUGIN_PROVIDERS into
whatever its callers passed, and the daemon passed the overlapping
DEFAULT_PROVIDER_RUNTIME_REQUIRED_IDS. Every caller now passes
RESERVED_PLUGIN_PROVIDERS, which takes the WebDriver ids from
CLOUD_WEBDRIVER_PROVIDERS, and a test pins every bundled runtime id
into it. plugins add/update no longer loads the provider runtimes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(plugins): name the WebDriver factory result check
Keeps instantiateProviderPlugin under the Fallow complexity threshold
after the object guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(fallow): stop suppressing statically imported TestMu helpers
connection.ts imports readTestMuDeviceFeatureFields and
readTestMuDeviceType statically, so Fallow sees them; only the lazily
read buildTestMuDeviceFeatureCapabilities still needs the exemption.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: allocate through a WebDriver plugin and assert typed ownership
The WebDriver plugin fixture now declares its profile fields and the
test allocates a session through the shared engine, refusing a field it
marks refused before any request. The refused repeat allocation also
asserts session-1 still resolves to the live lease, and the Limrun
refusal checks details.provider and details.flags instead of copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(connect): refuse a WebDriver plugin's refused fields before resolve
Builtin connect routes run rejectRefusedProviderProfileFields against
their declaration; the plugin route left it to the plugin. A
{ webDriver } plugin already hands core a total profileFields
declaration, so the connect route now applies it before the plugin's
resolve. Raw runtime plugins carry no declaration and still validate
their own flags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(testing): say when to run the packed provider plugin check
check-provider-plugin.mjs needs pnpm and a built core, which the Node
22.12 package lane cannot run and whose script body CI credits
verbatim, so it is a manual pre-push check for plugin package changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: document the plugin package check in its script, not testing.md
testing.md is over the agent-guidance byte budget with the note, so the
how and when now sit in the script header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* revert: let resolve and allocate own plugin profile-field refusal
Reverts 62e814c. The maintainer judged the connect-route check
redundant: plugin resolve and lease allocation both refuse those fields,
and #3168 owns profile-field admission.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* revert: keep the AppError brand check as it was
Reverts c553ed7 and aab5684. Plugins are trusted in-process code, so
a forged brand is not a threat the maintainer wants handled here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* revert: drop the plugin host archive fix with the apple host block
Reverts 1d52862; ProviderPluginHost.apple is removed next.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(plugins): remove the unused apple block from the plugin host
No plugin calls host.apple, and archiveDirectory copied the Limrun
dependency's zip helper. A later plugin that needs Apple packaging can
share one extracted helper with Limrun.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(webdriver): one owner for lease-flag and credential readers
requireRequest, requireRequestPlatform, requireFlag, readFlag and
requireEnv move from provider-definitions.ts into webdriver-utils.ts and
are exported from the provider-webdriver/plugin subpath; the TestMu
plugin drops its copies. requireEnv keeps the trim-empty rule, so a
whitespace-only BrowserStack credential now also fails as missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(testmu): import session and device-feature modules statically
connection.ts already imports them statically, so the dynamic imports
in plugin.ts saved nothing; the Fallow exemption for the lazy read goes
with them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* build(testmu): typecheck the plugin against core source, not dist
CI typechecks before it builds, so agent-device/plugins and
agent-device/plugins/webdriver resolved to dist types that did not
exist yet and ProviderPluginHost became any. Both tsconfigs map the two
specifiers to src/sdk, as examples/sdk does; provider-testmu maps
through its workspace link so the files stay outside its rootDir.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(fallow): credit the testmu plugin's type-only agent-device link
With the tsconfig mapping to core source, Fallow resolves the plugin's
agent-device/plugins imports to src/sdk files and reported the
devDependency unused, which a clean checkout surfaced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(testmu): assert only Limrun's device-type refusal at connect
Main's refusal names a field's aliases too, so the combined message this
test matched no longer exists, and main already covers Limrun refusing
the OS version. Keep the part this PR adds: --provider-device-type is
refused by Limrun.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(providers): share the lower-case scheme rule for hub app references
canonicalTestMuAppReference repeated the rule canonicalBrowserStackAppReference
applies: a hub matches only the lower-case scheme, so BS:// and LT:// are
rewritten to bs:// and lt://. Both now call canonicalSchemeReference from the
dependency-free provider-webdriver/providers subpath, so neither eager
closure grows. Behaviour is unchanged: BrowserStack still returns undefined
without the scheme, and TestMu still returns the value as given.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(provider-webdriver): export the plugin's webdriver-utils readers in one block
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(providers): refuse a lease when the daemon holds other plugin credentials
A provider plugin can declare agentDevicePlugin.credentialVariables in its
manifest. providerCredentialFingerprint falls back to that declaration for
providers without a built-in reader, so the client (shell env) and the
daemon (startup env) both fingerprint plugin credentials without loading
plugin code. TestMu declares LT_USERNAME and LT_ACCESS_KEY, which brings
it under the provider-credentials-changed refusal from #3207.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(plugins): split manifest connection and credential checks
Keeps readPluginManifest under the fallow complexity threshold, and types
the manifest fixture's version so the typecheck passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: explain installation of the TestMu package
* chore(gates): align TestMu package with workspace releases
* docs: explain daemon restart after plugin installation
* docs: show the complete plugin connection manifest
* chore(gates): deduplicate the merged package boundary fixture import
---------
Co-authored-by: Anurag Sharma <38938168+anurag-lambdatest@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
Summary
Split out of #3118 and stacked on #3165. Please review only the top three commits. This is the single consumed-field admission you asked us to keep in core.
@agent-device/contracts/provider-profile-fieldsexport holds:ProviderProfileFieldDeclaration, a full consumed/refused record over the profile fields.rejectRefusedProviderProfileFields.CLOUD_WEBDRIVER_PROFILE_FIELDS) and Limrun (LIMRUN_PROFILE_FIELDS). The runtime'sprofileFieldsis required.leases.allocate, remote-config, and repeat allocation of a live lease.PROVIDER_PROFILE_FIELD_FLAGSis the single field-to-flag map, and the BrowserStack device-feature specs derive their flags from it.Behaviour changes (nit [5]): flags that were silently ignored are now refused:
--provider-app,--provider-os-version(and its--os-versionalias),--provider-projectand--provider-build.--aws-*.--provider-app.None of the documented examples or help text use a refused flag.
Validation
pnpm check:affected --runpasses, including replay-compat with release tags fetched.eager-closure-budgetspasses.🤖 Generated with Claude Code