Skip to content

TD-5226: the ring marks the display the agent is acting on, in full control only - #112

Merged
captcrock merged 1 commit into
mainfrom
td/5226-ring-target
Nov 3, 2026
Merged

captcrock merged 1 commit into
mainfrom
td/5226-ring-target

Conversation

@captcrock

Copy link
Copy Markdown
Contributor

What

The real-display ring now means "an agent has the real screen":

  • Full control only. In background mode the ring is dark on every display. The policy file's mode is re-read when it changes, so Settings applies to every running client.
  • One display. On macOS only the display being acted on is lit: the last screenshot's display, or the one under the last move or click; before either, the primary display. Windows and Linux X11 still ring every display.
  • Episodes per session. overlay_session takes the session id; the ring holds while any TST Desk session's episode is open.
  • Clients that never bracket fade. Grok's own copy or another MCP client lights the ring per action, and it fades 30 s after the last one. Before this it stayed on until the client exited.
  • Stop is watched. A watcher re-checks Stop, the mode and the fade every quarter second, so Stop clears the ring without waiting for the next action.
  • The copy that acts paints. Grok's child always paints (following show_on_real_display), and Grok sessions no longer light the daemon's sidecar on a display it is not acting on.

Rules live in a new platform-neutral controller, mcp/tst-cu-mcp/src/tst_cu_mcp/overlay/control.py; painters only light, darken and aim. Decision recorded in DECISIONS.md (revises TD-3402 and TD-3407).

Tests

  • test_overlay_rules.py (new, 21 tests): mode, display, episodes, fade, Stop, refusals, the policy-file reader, and the helper's argument parsing and panel choice. Each rule's test was shown to fail with the rule removed.
  • test_overlay_gating.py updated for the aim-then-light order.
  • Core: session-keyed episodes, window-only episodes for Grok, Grok's child paint switch.
  • tst-cu-mcp suite on macOS: 607 passed, 3 failed. The 3 failures (DarwinBackend has no hit_test) fail the same way on main and are not touched here. CI does not run this suite.
  • Core: ruff, format and mypy tstd clean; touched tests pass.

Live (macOS, four displays)

Driven through the installed app's host socket with a temporary policy file and stop-file. The window server shows one lit ring panel at a time: primary, then the portrait display after a capture there, then the built-in display. None in background mode or while stopped; it comes back on Resume while the episode is open, and goes when the episode closes. Real screenshots of all four displays contain no ring.

The live run found a bug the unit tests could not: PyObjC refused the helper's class because of a one-argument method name, so the helper died at start. Fixed by making it a module function.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HUthoCdmzg2hy89ssk2cSF

…ontrol only

The ring lit every display at once, also in background mode, and a client
that never sent the episode bracket (Grok's own copy, another MCP client)
left it on until its process exited. Grok sessions in the packaged app also
lit the daemon's sidecar, a second ring on a display it was not acting on,
and one session closing its episode darkened another session's ring.

tst-cu-mcp now puts a platform-neutral controller (overlay/control.py) in
front of the painters:
- the ring shows only while the policy file's mode is full_control; the
  file is re-parsed when it changes, and a broken file reads as background
- it is aimed at the display being acted on: the last screenshot's, or the
  one under the last move or click; the macOS helper lights only that
  display's panel (target <display id>); Windows and Linux still ring all
- overlay_session takes the session id and the ring holds while any
  session's episode is open
- a client that never brackets lights it per action; it fades 30 s later
- a watcher re-checks Stop, the mode and the fade every quarter second

Grok's child always paints, with the user's show_on_real_display switch,
and Grok sessions tag their episodes for the window only.

The helper's retarget is a module function: PyObjC reads a method name
without trailing underscores as a zero-argument selector and refused the
class, which only the live run caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HUthoCdmzg2hy89ssk2cSF
@captcrock
captcrock merged commit a0ff25c into main Nov 3, 2026
4 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