Skip to content

feat: drive a chat from claude.ai with Remote Control (5.8.0) - #66

Merged
serialexperimentslainnnn merged 8 commits into
developfrom
feature/remote-control
Aug 28, 2026
Merged

serialexperimentslainnnn merged 8 commits into
developfrom
feature/remote-control

Conversation

@serialexperimentslainnnn

Copy link
Copy Markdown
Owner

Adds Remote Control to a chat, and cuts the 5.8.0 release.

What it does

A chat running in the IDE can be connected to claude.ai or the Claude mobile app, and keeps
executing on this machine the whole time. Two controls, both wired to the same destination: a
phone button in the chat's button row, left of the guard's shield, and a Remote control row in
the settings menu.

How, and why it is not the slash command

The slash command is closed in the mode this plugin launches the binary in, and answers
"isn't available in this environment". The control request behind it is not: the binary takes a
remote_control subtype carrying an enabled flag, and runs the bridge itself with the session
credential. Same shape as the btw command, which is a side_question request rather than the
command it looks like. Neither subtype is declared in the vendored SDK types, so both were
verified against the binary, as the protocol map prescribes.

The reply rides SessionControlClient.send rather than SessionQueries.ask, because a success
comes back with no payload at all and ask decodes the payload whether the request succeeded or
failed. Built on ask, the toggle would paint itself on after a refusal and never say so.

Neither control turns itself on until the binary has said yes. Settings rows may now declare
themselves host-owned, and the page leaves those alone instead of flipping them on the click,
which is correct for every other row in that menu because they write to the settings document
and cannot be refused.

Gates, run locally on this tree

  • JVM tests and koverVerify: green
  • detekt and spotlessCheck: green
  • ESLint and Prettier: clean
  • Frontend tests: 616 passed in 29 files
  • npm audit, distributed scope: 0 vulnerabilities
  • verifyPlugin: green
  • Build: zip carries 0 node_modules entries; the jar carries LICENSE, THIRD-PARTY-NOTICES.md
    and the licences directory

Validated by hand

Installed and exercised in PyCharm. The refusal path was reproduced deliberately against an
account without Remote Control, and both controls report it rather than failing quietly.

The release workflow reads the version from build.gradle.kts, and the README
states it twice as user-facing text: the badge and the name of the artifact
buildPlugin writes. All three move together or the README describes a zip
that no build produces.
The bump missed a fourth place the version is written. PluginIdentity is the
one the plugin announces on every outbound request, and the descriptor cannot
be read at runtime without an internal API this build refuses to ship, so the
constant is hand-kept and PluginIdentityTest is what keeps it honest. It went
red on 5.7.0 against a 5.7.1 build, which is the test doing its job.
The slash command is closed in the mode this plugin launches, and answers
"isn't available in this environment". The control request behind it is not:
the binary takes a remote_control subtype carrying an enabled flag, and runs
the bridge itself with the session credential. That is the same shape as the
btw command, which is a side_question request rather than the command it
looks like.

The reply rides SessionControlClient.send instead of SessionQueries.ask,
because a success comes back with no payload at all. Ask decodes the payload
whether the request succeeded or failed, so a toggle built on it would paint
itself on after a refusal and never say so.

Two ways in, one path: the composer's phone button and the settings menu row
both emit the same settingsToggle key, so there is one destination to keep
honest rather than two that can disagree.
The binary answers the enable request with an error when the account or the
organisation has Remote Control switched off, so the host state was already
right. What lied was the page.

The settings row flipped itself on the click, before the host had answered.
That is correct for every other row in this menu, because they write to the
settings document and cannot be refused; this is the first one whose
destination can say no. Rows may now declare themselves host-owned, and the
page leaves those alone until the state it is sent says otherwise.

The composer button had no way to fail. It stayed off after a refusal with
the reason reachable only by reading the transcript, which is the same as
failing silently for anyone who was watching the button they just pressed.
The refusal now travels to the page and the button wears it.
Remote Control is a new capability, not a repair, so the release is a minor.
The number is written in four places and the release workflow greps only the
first of them, which is why the other three are moved in the same commit.

The changelog section is the one the release job extracts for the GitHub
Release, and the release notes are what the Marketplace shows as What's New.
Both name what the feature needs to work, because the refusal a user is most
likely to meet is an account or organisation that has Remote Control off.
Approving a call from the phone left the card sitting in the chat forever.
With Remote Control on, the binary forwards the prompt to claude.ai as well,
and when the remote answers it withdraws the one it left here — over a
top-level control_cancel_request frame carrying the request_id it echoes.
The parser had no entry for that type, so it fell through to Other and was
dropped, and nothing ever took the card down.

The withdrawal writes no reply. The binary already has its answer and is no
longer waiting, so answering a request that was retracted would be inventing
a second verdict. It closes the review diff the same way a rejection does,
because a card that leaves without closing it strands the pane.

This is the connected surface the feature commit missed: enabling Remote
Control adds an inbound frame the plugin never had to understand before.
checkDrift had not been run since the tools moved: it recorded SDK 0.3.233
and binary 2.1.233 against a machine running 0.3.250 and 2.1.250. A drift
gate whose baseline is stale reports on a protocol nobody is speaking.

The detector updates the tools itself before reporting, which is what moved
the lockfile here. The vendored SDK is protocol reference and ships to
nobody, and package.json already allowed the newer one, so the range is
unchanged. The surface came back fully covered once the withdrawal frame was
modelled.
Its own section in the user guide and an entry at the top of What's new.
Says what the feature needs before it will work, because the account and
organisation gates are the refusal a reader is most likely to meet, and says
plainly that the guard still decides first on this machine — connecting a
phone changes who can answer a permission, never what the chat may do.
@serialexperimentslainnnn
serialexperimentslainnnn merged commit c4f8d24 into develop Aug 28, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant