Repository navigation
feat(hardware-key): press volume, power, media, and TV keys on Android - #3817
mikolajadamowicz wants to merge 7 commits into
Conversation
Add `agent-device hardware-key <key>` and `client.command.hardwareKey()`: one press of a volume (`volume-up`, `volume-down`, `mute`), power (`power`, `sleep`, `wake`), or media (`media-play-pause`, `media-next`, `media-previous`, `media-rewind`, `media-fast-forward`) key. It covers QA checks such as volume and mute handling, standby and wake during playback, and media-key handling in a video-streaming app. Android emulators and devices send the key as `adb shell input keyevent` with the numeric KeyEvent code, locally and on Limrun Android sessions, which ride the same Android interactor as `home` and `app-switcher`. The key vocabulary and its Android keycodes sit beside the TV remote table in `contracts/tv-remote.ts`, and the press joins the system-button family as its one control with an argument, so every owner that does not name the cell refuses it with that family's denial. Both homes are modules the CLI, daemon, and runtime closures already evaluate, so no eager closure grows. The Apple runner exposes only the home and Action Button presses, so iOS, tvOS, and macOS refuse the key, as do web, Linux, Vega OS, HarmonyOS, Limrun iOS, and the WebDriver (Appium) backend. Like `action-button`, the command declares no post-action observation: the key goes to the system and the media session, and power or sleep leaves the display off. Session events summarize the press with its key, and `help tv` points Android TV playback checks at it.
…ables The six-platform coverage declaration and its Android and Apple evidence rows, the runtime-binding conformance table, and the Apple provider output guard's driven set enumerate the public surface, so each one names `hardware-key`.
The generic dispatcher grew a dedicated hardware-key arm, which with the telephony and uninstall arms from main put resolveGenericRuntimeExecution past the cyclomatic bar. The system-button module now owns one entry point for its no-argument buttons and its keyed control, so the dispatcher keeps a single arm for the family, and the request positionals are read once instead of per arm.
Add channel-up, channel-down, last-channel, guide, info, captions, tv-input, and the four color keys to `hardware-key`. Each name maps to its standard Android KeyEvent keycode (166, 167, 229, 172, 165, 175, 178, 183-186), so a QA run on a streaming app's Android TV build can drive the channel and program-guide buttons a TV remote sends. `tv-remote` has none of them.
Vendor Android TV remotes carry buttons that no standard KeyEvent name covers, such as operator-specific channel or app keys. `hardware-key android-keycode:<n>` sends keycode n (1 to 2147483647) through the same `input keyevent` path as the named keys, so such a button can be driven without leaving agent-device for a raw adb call, and the press still lands in the session log and in replay scripts as a positional. The token rides the existing `key` input instead of a new flag: the parser, the Android keycode mapping, and the session-log reader each accept it, and the MCP input becomes a described string, because the schema vocabulary has no way to express "one of these names or this pattern". This commit stands alone and can be dropped without touching the rest of the command.
…ption The help topic and command description hand-listed keys the HARDWARE_KEY_DEFINITIONS table owns; the topic copy already omitted power, sleep, and wake. Render HARDWARE_KEY_USAGE instead and keep only what the usage line cannot say.
Fallow reports no unused-type issue under these directives, so the gate charges the branch for them once client-types joins its change set. Verified stale on origin/main with the file alone in scope: removing the directives leaves the re-exports and the published surface unchanged.
31a9dc2 to
3204bcf
Compare
|
Rebased onto current main. Head is One lint failure is left and I want your call before touching it. #3804 pinned A new command has to add its descriptor to How do you want new commands to land under the new cap? Options I see: raise the cap here, split the registry first as a separate PR, or something else. I didn't want to move a cap you set this morning on my own. Everything else I could run locally is green: typecheck passes, and the gate stops at this one lint error. |
|
Thanks for the PR. I found no code defects at 3204bcf, but it is not ready yet: two CI failures come from this diff, and I have a design question at the end. The live ADB run on phone and TV emulators is as you reported it; I did not reproduce it. No conflicts. Two checks fail, and the diff causes both. Lint fails because the +12 lines in packages/command-registry/src/registry.ts take the file from 2179, exactly the cap, to 2191. Integration fails because Before merge, please add a provider-scenario test for Not blocking: the six dropped suppressions in src/client/client-types.ts are unrelated to hardware-key, and I did not confirm they were stale. Could |
Summary
Adds
hardware-key, one command that presses a single hardware key through Android ADB key events. 22 named keys (volume, mute, power/sleep/wake, five media keys, channel/guide/info/captions/tv-input/four color keys), plusandroid-keycode:<n>for vendor remote buttons no name covers.tv-remotecovers D-pad navigation; none of these keys had a route before, so agents fell back to rawadb shell input keyevent.Presses ride the system-button family's admission and binding:
hardwareKeyis one more fact cell, so every owner without a key-event channel (Apple, web, Linux, HarmonyOS, WebDriver, Limrun iOS) refuses withUNSUPPORTED_OPERATIONthrough its existing denial, and Limrun Android is admitted like the local family. 49 files, ~346 production lines; the rest is tests, coverage tables, and docs.Validation
Device evidence at 31a9dc2; rebased to 3204bcf, range-diff clean.
input keyevent <n>argv for every one;volume-*/mutemovedvolume_music_speakeranddumpsys audio;sleep/wake/powermovedmWakefulness.tv-remote,home, andapp-switcherregression-checked on the same session.pnpm check:affected --rungreen except symlink tests that fail identically on base eef1a3f (code-signature,runner-artifact-manifest; sandbox /private/tmp). Alsocheck:fallow, lint, typecheck, format, mcp-metadata, unit suite (14914 passed).registry.tsmax-lines cap from chore: enforce file size limits with oxlint #3804 (see my comment).