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 support with a d-pad CLI alias across CLI, runtime, client, MCP, command descriptors, and platform capability routing.
Wire Android TV to D-pad keyevents and tvOS to the Apple remote press path, then improve the operator/API ergonomics around that surface: data-driven TV button semantics with ok/center/enter aliases, explicit Android longpress vs tvOS hold-duration docs, a dedicated help tv topic, website/client examples, reusable focus capture policy, and a narrow system-command Node client projection from command facets.
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-remotesupport with ad-padCLI alias across CLI, runtime, client, MCP, command descriptors, and platform capability routing.Wire Android TV to D-pad keyevents and tvOS to the Apple remote press path, then improve the operator/API ergonomics around that surface: data-driven TV button semantics with
ok/center/enteraliases, explicit Android longpress vs tvOS hold-duration docs, a dedicatedhelp tvtopic, website/client examples, reusable focus capture policy, and a narrow system-command Node client projection from command facets.Validation
pnpm formatpnpm buildpnpm check:quickpnpm exec vitest run src/commands/system/index.test.ts src/core/__tests__/dispatch-tv-remote.test.ts src/commands/system/runtime/system.test.ts src/mcp/__tests__/command-tools.test.ts src/cli/parser/__tests__/cli-help-topics.test.ts src/commands/interaction/runtime/selector-read.test.ts src/commands/interaction/runtime/selector-capture-policy.test.ts src/__tests__/cli-client-commands.test.ts src/core/command-descriptorpnpm test:skillgym:case android-tv-focus-uses-tv-remotepnpm exec vitest run src/platforms/android/__tests__/input-actions.test.tspnpm exec vitest run src/daemon/__tests__/runtime-hints.test.ts src/platforms/__tests__/install-source.test.ts src/platforms/apple/core/__tests__/runner-client.test.ts src/platforms/apple/core/__tests__/runner-session.test.tstv-remote,d-pad,select, andscreenshot --overlay-refstv-remote, focused predicate, andscreenshot --overlay-refsFull
pnpm check:unitwas rerun and hit unrelated host-load timing/slow-test failures; every failed file passed when rerun in isolation.