* feat: parametrise .ad replay scripts
Support ${VAR} substitution with env header directives and CLI -e
overrides so flows can be reused across app variants, environments,
and devices without duplicating the script.
Precedence (high->low): CLI -e > AD_* shell env > file-local env >
built-ins (AD_PLATFORM, AD_SESSION, AD_FILENAME, AD_DEVICE,
AD_ARTIFACTS). Supports ${VAR:-default} fallback, \${ escape, and
fails with file:line on unresolved vars.
* refactor: harden .ad replay parametrisation
Tighten the parametrisation surface introduced in the feat commit after
an internal review pass:
- Reserve the AD_* namespace for built-ins. User env (file env, CLI -e,
shell AD_VAR_*) can no longer define AD_* keys, which closes a built-in
shadowing vector (e.g. AD_VAR_AD_SESSION).
- Change the shell-env prefix from AD_* to AD_VAR_* so unrelated CI
secrets that happen to start with AD_ (AD_TOKEN, AD_SECRET_KEY) are
not auto-imported into replay scripts.
- Extend the replay -u guard to also reject scripts with \${VAR}
substitutions in any action, not just those with env directives, so
the writer never silently drops substitutions on heal-rewrite.
- Reword DX-unfriendly regex errors ("must match /^[A-Z_]...$/" ->
"must be uppercase letters, digits, and underscores, e.g. APP_ID").
- Docs rewrite with precedence table, three recipes, fallback/escape
examples, and a Notes block covering replay -u limitation, remote
daemon caveat, no nested fallback, and loud typo behaviour.
- Additional unit tests: namespace reservation on every path, shell
prefix migration, \${VAR} round-trip preservation through
writeReplayScript, green-path integration test with a fake invoke.
* fix: collect .ad replay shell env on the CLI, not the daemon
Review feedback (https://github.com/callstackincubator/agent-device/pull/433#discussion_r3130695225):
the daemon was reading AD_VAR_* from its own process.env, which meant
"AD_VAR_K=V agent-device replay" only worked if V was set when the
daemon started, and never worked for remote daemons.
The client now filters process.env for AD_VAR_* at request time and
ships the result as replayShellEnv on the DaemonRequest flags. The
daemon prefers the request value when present and falls back to its
own process.env for direct-daemon callers (internal tests).
Adds two integration tests pinning both paths and updates the docs
Notes block to reflect the new behaviour.
* fix: thread per-attempt artifacts dir into AD_ARTIFACTS under test
Review feedback (https://github.com/callstackincubator/agent-device/pull/433#discussion_r3130695235):
AD_ARTIFACTS is documented as available under "agent-device test", but
buildReplayBuiltinVars was only reading the raw flags.artifactsDir.
Under the default artifacts layout the flag is unset and \${AD_ARTIFACTS}
failed; when set with --artifacts-dir it pointed at the suite root, not
the resolved per-attempt directory the test runner actually writes to.
Plumb the attempt-level artifacts dir from session-test.ts through the
runReplay callback (via runReplayTestAttempt) down into the nested
replay request's flags.artifactsDir. The daemon side is unchanged -
buildReplayBuiltinVars just now sees the right value.
Extracts the nested-request flag merge into a testable helper
(buildNestedReplayFlags) to close the coverage gap between the test
harness and the replay runtime.
* refactor: DRY .ad replay parametrisation internals
- Share VAR_KEY_RE between session-replay-vars and session-replay-script
instead of re-declaring the same /^[A-Z_][A-Z0-9_]*$/ in each.
- Fold resolveReplayFlags + resolveReplayRuntime into a single generic
resolveStringProps<T>; the two were near-identical object-walkers.
- Un-export parseReplayEnvLine (was only used inside its own module).
- Table-drive the four "reject AD_* namespace" tests with test.each
instead of four near-duplicate test blocks.
- Extract runReplayFixture helper for runReplayScriptFile integration
tests; each test is now ~10 lines instead of ~30.
No behaviour change. 812 tests still green.
* docs: clarify replay built-in variables
* test: cover replay env serialization
---------
Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
* refactor: remove dead code and unused exports
- Delete interaction-get.ts, interaction-is.ts, interaction-selector.ts (superseded by selector-runtime dispatchers)
- Remove handleWaitCommand and its private helpers from snapshot-wait.ts (replaced by dispatchWaitViaRuntime)
- Remove unused distanceFromSafeViewportBand/isRectWithinSafeViewportBand from rect-visibility
- Trim Linux platform barrel to only the re-export still in use (snapshotLinux)
* refactor: consolidate duplicated helpers
- Move sleep() to utils/timeouts.ts; remove 6 duplicate implementations
- Use existing isApplePlatform() helper for ios||macos checks (5 sites)
- Export trimRuntimeValue from runtime-hints; drop duplicate trimRuntimeString
- Merge normalizeTextSurfaceType/normalizeType into single text-surface helper
- Remove pointless isScrollableContainerType wrapper
* refactor: consolidate accidentally-duplicated type definitions
- SnapshotDiffLine/Summary: single definition in utils/snapshot-diff, re-exported from capture-snapshot
- FindLocator: single definition in utils/finders, re-exported from client-types
- JsonRpc envelope in http-server now uses JsonRpcRequestEnvelope/JsonRpcId from contracts
- Inline 'primary'|'secondary'|'middle' literals replaced with ClickButton (internal sites only)
* refactor: strengthen weak types and fix DEVICE_IN_USE downgrade bug
- Add toAppErrorCode() validator and DEVICE_IN_USE to AppErrorCode union
- Replace 'as any' casts on wire error codes with the validator (daemon-error, daemon-client, request-router, http-server)
- Type Metro worker payload as MetroTunnelResponseMessage
- Type xctestrun plist parsing with explicit partial schema
- Narrow 'details.stderr' via typeof checks on Android error paths
- Type runner-session parseRunnerResponse with RunnerResponsePayload
Bug fix: handler emitted 'DEVICE_IN_USE' but router cast silently downgraded to
'COMMAND_FAILED' because the code was missing from AppErrorCode. Clients can now
react to same-device contention.
* refactor: collapse redundant 'ignore' comments in empty catch blocks
- Replace 16 instances of 3-line catch { // ignore } with 1-line catch {}
- Remove one self-describing 'Re-export public API' comment
Specific 'ignore shutdown races' / 'ignore malformed pid files' style comments
that name concrete failure modes are kept.
* refactor: eliminate circular dependencies by extracting shared types to leaves
Resolves all 44 cycles reported by madge. Pattern throughout: extract shared
type into a leaf module; both producer and consumer import from the leaf;
original module re-exports for API stability.
New leaf type modules:
- src/runtime-contract.ts (AgentDeviceRuntime, CommandContext, ...)
- src/metro-types.ts (MetroRuntimeHints, MetroBridgeResult, ...)
- src/commands/runtime-types.ts (CommandResult, RuntimeCommand, ...)
- src/commands/diagnostics-types.ts
- src/cli/commands/router-types.ts (ClientCommandParams, ...)
- src/core/interactor-types.ts (Interactor, BackMode, ...)
- src/platforms/ios/runner-session-types.ts (RunnerSession)
- src/utils/screenshot-diff-region-types.ts (MutableDiffRegion)
- src/daemon/handlers/record-trace-types.ts
madge --circular now reports 0 cycles (was 44). No runtime behavior changes.
* fix: preserve wire error codes verbatim (addresses codex review)
The initial weak-types pass validated wire error codes against a closed
union, silently downgrading any unknown code to COMMAND_FAILED. This
dropped signals like AMBIGUOUS_MATCH that handlers emit and clients are
documented to handle (skills/agent-device/references/exploration.md).
- Widen AppErrorCode to 'KnownAppErrorCode | (string & {})' so autocomplete
of known codes is preserved while any wire code flows through
- toAppErrorCode now preserves any non-empty code; fallback only when
undefined or empty
- Add AMBIGUOUS_MATCH to KnownAppErrorCode (documented public code)
- Add test coverage for preservation and fallback behavior
* refactor: address review follow-ups
- Add DEVICE_IN_USE to the batch error taxonomy in exploration.md (now
observable by clients after earlier fix, needs agent-facing guidance)
- Delete one-line src/platforms/linux/index.ts barrel; both consumers
(core/dispatch, daemon/handlers/snapshot-capture) now import from
platforms/linux/snapshot directly
- Replace inline { tenantId; runId; leaseId } shapes with MetroBridgeScope
alias at client-types, metro, and cli/commands/connection-runtime
- Consolidate remaining inline setTimeout wrappers onto utils/timeouts.ts#sleep
(12 files, ~18 sites). Left test files and the runtime-clock aware helper
in commands/selector-read-utils alone. Also removes the local sleepMs
helper from daemon-client.ts.
* refactor: address low-priority review follow-ups
- daemon-client RPC error path: stringify any non-null data.code instead
of only forwarding strings. Preserves numeric codes from hypothetical
future proxies/servers; for first-party daemon today this is a no-op
since handlers already emit strings.
- errors.ts: expand AppErrorCode comment to call out the exhaustiveness
tradeoff of the '(string & {})' widening for SDK consumers.
* Remove test assertions redundant with TypeScript static analysis
Drop assertions that merely verify unset optional properties are
undefined (a JavaScript language guarantee) and a second test block
whose only runtime value was re-checking literals just assigned to
typed variables. TypeScript's compiler already validates that these
object literals conform to the declared union types.
* fix: remove unused descriptor variable to fix noUnusedLocals
* refactor: extract shared test utilities from duplicated patterns
Introduce src/__tests__/test-utils/ with reusable device fixtures,
session factories, store factory, snapshot builders, and mocked
binary helpers. Migrate 27 test files to use these shared utilities,
eliminating duplicated DeviceInfo constants, makeSession/makeStore
helpers, snapshot node builders, and withMockedAdb/withMockedXcrun
across core, daemon, and handler tests.
Add AGENTS.md guidance to check test-utils before writing new tests.
* fix: restore public Metro type contract coverage with satisfies fixtures
* refactor: reduce duplication and simplify codebase
Eliminate copy-pasted functions, inline error boilerplate, and repeated
patterns across daemon handlers and platform modules.
Key changes:
- Deduplicate isEnvTruthy, displayNodeLabel, roundPercent into single sources
- Extract throwDaemonError helper for client/CLI daemon response errors
- Add sessionNotFoundResponse/unsupportedOperationResponse helpers and
adopt errorResponse() across ~30 handler files (-565 lines)
- Unify BATCH_PARENT_FLAG_KEYS/REPLAY_PARENT_FLAG_KEYS into shared
mergeParentFlags helper in handler-utils
- Extract createLinuxToolResolver for screenshot/clipboard tool detection
- Remove stale type-sync comments from contracts.ts and metro.ts
* refactor: deeper structural simplification pass
Bigger wins from restructuring, not just mechanical dedup:
- cli.ts: move logTailStopper to try/finally (eliminates 28 duplicate
calls), extract writeCommandCliOutput/writeLogsCliOutput/writeNetworkCliOutput
from 350-line if/else chain, fix remaining throwDaemonError site
- session-store.ts: replace 67-line sanitizeFlags destructure/reconstruct
with 10-line pick-from-array loop
- record-trace: extract finalizeRecordingOverlay helper, replacing 4
copies of the telemetry+overlay block across ios/android/recording files
- Deduplicate normalizeText (finders.ts + selectors-match.ts)
- Fix isEnvTruthy to preserve whitespace-tolerant parsing (.trim())
* fix: ensure logTailStopper runs before process.exit
process.exit() does not unwind the stack, so finally blocks are
skipped. Restore explicit logTailStopper() calls before each
process.exit() to prevent leaking the background daemon log tail
process. The finally block remains as a safety net for normal
return paths.
Also refactor writeCommandCliOutput to return an exit code instead
of calling process.exit() directly, keeping the exit decision in
the caller where cleanup is visible.
* refactor: simplify daemon failure responses
* refactor: remove redundant daemon response cast
* feat: expose bridge-facing contract and metro APIs
* chore: address review feedback on public contract exports
* refactor: reuse public daemon contracts internally
* refactor: reuse public metro types internally
* fix: widen bridge-facing public contracts
* refactor: expose metro and remote config node apis
* refactor: tighten public node api surface
* refactor: tighten metro public type boundaries
* refactor: normalize remote config public profile
* refactor: make remote config parser library-owned
* refactor: share remote config parsing primitives
* refactor: avoid regex in base url normalization
* fix: auto-manage metro companion for remote runs
* fix: fail fast on non-retryable metro bridge errors
* fix: scope metro companion ownership by profile
* fix: align remote close metro companion cleanup
* fix: guard metro companion shutdown reconnect
* test: ignore expected bridge reset in metro reconnect test
* chore: format, cleanup