You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add first-class TV remote APIs for focus-first Android TV and tvOS automation.
This introduces the public tv-remote command, the Node/client client.command.tvRemote(...) method, runtime device.system.tvRemote(...), MCP/tool-schema exposure, command descriptors, output schemas, and platform capability routing. Android TV dispatches through D-pad keyevents; tvOS dispatches through the Apple remote press path.
The interaction model is TV-native: move focus with remote directions, verify focus with full-tree focused selectors, then select the focused control. The PR also keeps focused Android nodes in snapshots even when they are focusable but not clickable, so is focused, wait focused=true, and focused=true targeting do not miss TV focus.
Examples:
agent-device tv-remote press down --platform android --target tv
agent-device tv-remote press select--platform android --target tv
agent-device tv-remote longpress select--platform android --target tv
agent-device tv-remote press select--duration-ms 900 --platform android --target tv
agent-device tv-remote press back --platform ios --target tv
agent-device wait selector 'focused=true' --platform android --target tv
agent-device screenshot --overlay-refs tv-overlay.png --platform android --target tv
Operator-facing guidance is included in help tv, command help, website docs, and SkillGym coverage. Button aliases ok, center, and enter normalize to select; CLI tv-remote longpress <button> is a 500ms hold preset, --duration-ms overrides it, and structured APIs continue using durationMs. Android TV maps any positive duration to ADB's longpress form; tvOS uses the exact hold duration.
Validation
Manual Android TV emulator: tv-remote, select, and screenshot --overlay-refs
Manual tvOS simulator: tv-remote, focused predicate, and screenshot --overlay-refs
Full coverage was run after the final review fix. The prior actionable selector-read.test.ts coverage failure is fixed; the full local run still hit unrelated load-sensitive failures in pre-existing tests, and those failing files passed when rerun in isolation.
Fallow: src/core/tv-remote.ts:27 isTvRemoteButton is an unused export. Remove it or wire it to a real consumer.
Integration progress: pnpm test:integration:progress:check reports missing provider-backed integration command coverage for public command tv-remote (51/52 covered).
Please add the provider-backed integration coverage/classification for tv-remote, rerun pnpm check:fallow --base origin/main and pnpm test:integration:progress:check, and reply with what changed plus any remaining blocker.
Adds a first-class tv-remote command (with d-pad CLI alias) that routes Android TV to D-pad keyevents and tvOS to the Apple remote press path, plus a focused selector/predicate, a help tv topic, and a data-driven button model. The command wiring is thorough and consistently propagated across CLI → daemon → client → MCP → capability routing, and it's well tested. I verified end-to-end that durationMs survives the daemon writer (rides metadataFlags, not positionals), handleTvRemoteCommand always receives a single normalized positional (the CLI reader/daemon writer strip press/aliases before dispatch), the back → menu Apple mapping matches the runner remoteButton contract, the projected system client preserves all 8 methods, and focused was added consistently across the predicate, selector parser, matcher, and CLI validator. The includeRects refactor is behavior-preserving (all backends compare === true).
Two things worth a look, one behavioral and one cleanup:
1. focused reads request an interactive-only snapshot, which can drop the focused node on Android TV (false negative)
src/commands/interaction/runtime/selector-capture-policy.ts:19 — deriveSelectorCapturePolicy sets interactiveOnly: true for predicate: 'focused' and for any focused=true selector, and isCommand/waitForSelector/resolveSelectorNode now honor it.
On Android, a snapshot node's hittable is derived as attrs.clickable ?? attrs.focusable (src/platforms/android/ui-hierarchy.ts:495). Because uiautomator dumps always emit a clickable attribute, the ?? never falls through — hittable collapses to the clickable value. A node that is focusable="true" focused="true" clickable="false" (a focusable row/container in a leanback list, a focused text label, etc.) is therefore hittable:false. shouldIncludeInteractiveAndroidNode (ui-hierarchy.ts:730) keeps such a node only if it has meaningful text/id and a hittable ancestor/descendant or a collection ancestor — otherwise it's filtered out of the interactive tree.
Failure scenario: move focus onto a focusable-but-not-clickable Android TV container, then run agent-device is focused '<selector>' (or wait focused=true, or target focused=true). The focused node is absent from the interactiveOnly snapshot, so is reports pass:false, wait times out, and a focused=true target fails to resolve — even though the element is genuinely focused. This is exactly the verification workflow the PR documents in help tv ("If a fresh snapshot exposes a focused node, verify it with is focused"). The full (non-interactive) tree that focus reads would use without this policy contains the node. Worth confirming the design intent — reading focus off the full tree (or including focused nodes in the interactive filter) would avoid the narrowing.
2. durationMode is uniform dead generality
src/core/tv-remote.ts — all 8 entries in TV_REMOTE_BUTTON_DEFINITIONS carry an identical durationMode: { android: 'longpress', apple: 'exact' }, and the sole reader tvRemoteDurationMode hardcodes TV_REMOTE_BUTTON_DEFINITIONS.select.durationMode[platform] — it never varies by button. The actual Android longpress decision lives in pressAndroidTvRemote (durationMs && durationMs > 0), so this field is purely help-text data dressed up as per-button config. A single module-level constant would say the same thing with 7 fewer copies to keep in sync.
(Minor, related: the hint string 'tv-remote is supported only on Android TV targets.' is duplicated verbatim in register-builtins.ts and apple/plugin.ts, and '...only on tvOS devices.' in apple/plugin.ts — a TV_REMOTE_*_HINT constant in core/tv-remote.ts would collapse the wording-drift risk, though the parity-test copies are somewhat deliberate as independent oracles.)
Re-review after 2bab15a: no actionable blockers found. The focused-selector issue is addressed by keeping focused reads on full snapshots and preserving focused Android nodes in the interactive filter; the durationMode cleanup is now a module-level constant; provider-backed tv-remote coverage is present; and CI is green across 22 checks. The PR also includes manual Android TV emulator and tvOS simulator evidence. Labeling ready-for-human for maintainer judgment.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ready-for-humanValid work that needs human implementation, judgment, or maintainer merge
1 participant
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add first-class TV remote APIs for focus-first Android TV and tvOS automation.
This introduces the public
tv-remotecommand, the Node/clientclient.command.tvRemote(...)method, runtimedevice.system.tvRemote(...), MCP/tool-schema exposure, command descriptors, output schemas, and platform capability routing. Android TV dispatches through D-pad keyevents; tvOS dispatches through the Apple remote press path.The interaction model is TV-native: move focus with remote directions, verify focus with full-tree focused selectors, then select the focused control. The PR also keeps focused Android nodes in snapshots even when they are focusable but not clickable, so
is focused,wait focused=true, andfocused=truetargeting do not miss TV focus.Examples:
Operator-facing guidance is included in
help tv, command help, website docs, and SkillGym coverage. Button aliasesok,center, andenternormalize toselect; CLItv-remote longpress <button>is a 500ms hold preset,--duration-msoverrides it, and structured APIs continue usingdurationMs. Android TV maps any positive duration to ADB's longpress form; tvOS uses the exact hold duration.Validation
tv-remote,select, andscreenshot --overlay-refstv-remote, focused predicate, andscreenshot --overlay-refsselector-read.test.tscoverage failure is fixed; the full local run still hit unrelated load-sensitive failures in pre-existing tests, and those failing files passed when rerun in isolation.