Skip to content

feat(attach): deliver control requests and stop notices scrolling the screen - #51

Open
aksOps wants to merge 1 commit into
chore/test-infrafrom
feat/control-request-notice
Open

feat(attach): deliver control requests and stop notices scrolling the screen#51
aksOps wants to merge 1 commit into
chore/test-infrafrom
feat/control-request-notice

Conversation

@aksOps

@aksOps aksOps commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

The last two findings #48 verified but deferred. They turned out to be one change: both are about how UAM talks to a viewer whose screen belongs to the agent.

Stacked on #50 (it uses the end-to-end harness landed there). Base retargets to main once #50 merges.

prefix r reached nobody

The client wrote the frame and told the asker "control requested". The host accepted it and returned. The controller — the only party who can act on it — was never told, so the documented feature did nothing.

The notice is delivered as terminal output, not a new control event. That is deliberate: observeRoleEvent returns an error for any event it does not recognise, and the host cannot add a capability to gate it either — validateClientHello rejects unknown capabilities, so a new client advertising one would fail to attach to a still-running older host. Terminal output is the one channel every protocol version already understands.

Transfer stays controller-owned. The request is a notice, not a handoff.

Notices scrolled the agent's screen

Every notice was "\r\n[uam: ...]\r\n" written at the cursor. On the alternate screen that pushes the agent's display up, and nothing repaints it — the emulator never saw those bytes, because they are the client's, not the agent's.

Notices are now painted on the terminal's bottom rows, wrapped in DECSC/DECRC, so the cursor and pen come back exactly where the agent left them and the screen does not move. The agent's next repaint of those rows clears the notice, which is the right lifetime for it.

One thing I got wrong first time and caught in the e2e run: my initial version truncated a notice to one row. prefix i is longer than 80 columns, so that silently ate most of the key list — worse than the scrolling it replaced. It now wraps upward across as many bottom rows as it needs.

Verification

  • New unit tests: painting targets the last row and moves nothing; long notices keep their content across rows and stay within the width; wide runes respect cell width; no geometry falls back to the plain form; a standby's request reaches the controller with the requester's id, painted on the controller's own geometry; a controller requesting from itself is not notified; a request does not move control.
  • New end-to-end case: a real standby sends prefix r and the notice appears on a real controller's terminal.
  • go test ./... — all green. make test-e2e — all green. go test -race ./internal/session/... — clean. go vet, gofmt, golangci-lint — clean.

Also in this branch: the e2e harness now registers viewer cleanup on the subtest rather than the parent. The truncation bug above cascaded into two unrelated failures because the failing subtest left its client attached holding the controller role — the harness should not make one failure look like three.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X3PrstXFvYdemQrz8Vb1b1

… screen

The last two findings #48 verified but deferred, which turn out to be one
change: both are about how UAM talks to a viewer whose screen belongs to the
agent.

`prefix r` reached nobody. The client wrote the frame and told the asker
"control requested"; the host accepted it and returned. The controller — the
only party who can act on it — was never told. The notice is now delivered as
terminal output rather than a new control event, deliberately: the client
rejects control events it does not recognise, so a viewer from an older build
would drop its connection instead of ignoring one. Transfer stays
controller-owned; the request is a notice, not a handoff.

Notices scrolled the agent's screen. Every UAM notice was written as
"\r\n[uam: ...]\r\n" at the cursor, which on the alternate screen pushed the
agent's display up with nothing to repaint it. They are now painted on the
terminal's bottom rows with the cursor saved and restored around them, so the
agent's screen does not move and its next repaint of those rows clears the
notice. A notice longer than the terminal — `prefix i` runs past 80 columns —
wraps upward across as many bottom rows as it needs rather than being cut,
since losing half the key list is worse than using two rows. Without a known
geometry the plain form stays.

Verified end to end: a standby's `prefix r` now appears on the controller's
real terminal, and the info notice keeps its full text across the wrap.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01X3PrstXFvYdemQrz8Vb1b1
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