Skip to content

Fix focus-gated needs-input flicker in live emulator query-response forwarding - #950

Merged
anutron merged 2 commits into
masterfrom
argus/fix-live-emulator-writeinput
Aug 23, 2026
Merged

Fix focus-gated needs-input flicker in live emulator query-response forwarding#950
anutron merged 2 commits into
masterfrom
argus/fix-live-emulator-writeinput

Conversation

@anutron

@anutron anutron commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Focusing a Hera rail task's pane made its needs-input icon cycle moon → spinner → (?) → then bounce between (?) and moon roughly every second for as long as the pane stayed focused, with static/unresized/non-restarted content. Unfocusing settled it to moon and it stayed there.
  • Root cause: the live PTY emulator's terminal-capability-query auto-answer forwarder (forwardEmulatorResponse, added by the recent OSC 10/11 color-query feature) delivered responses via Session.WriteInput, which stamps lastUserInput exactly like a real keystroke. idle-detection's clear-on-input logic (NeedsInputClear) reads that timestamp to decide "the user answered," so it falsely cleared a still-unanswered (?) flag every time the agent CLI's own periodic redraw re-triggered a capability-query response — which only happens on the currently-focused pane's live (non-discard) emulator, and recurs on roughly the same ~1s cadence as the app's needs-input detection ticker.
  • Fix: route the forward through Session.WriteInputSystem instead — it advances only the work-cycle timestamp, mirroring the existing reliable-notify delivery pattern (internal/notify) that exists for exactly this reason. agentview.TerminalAdapter gained a WriteInputSystem method for this; real session types already implemented it, test fakes got a stub.
  • The "settles to moon and stays there once unfocused" half of the report is not a separate bug — confirmed it's the pre-existing ClearedMarker stale-recandidacy suppression (BUG-063/BUG-067) legitimately recognizing unchanged content as already-cleared once no more live emulator exists to keep perturbing it. No change needed there; documented in the gotcha.

Process

  • Routed through an OpenSpec change folder (fix-live-emulator-writeinput), archived atomically in this same PR (openspec/changes/archive/2026-08-22-fix-live-emulator-writeinput/, base spec terminal-rendering updated).
  • Added a gotcha bullet to context/knowledge/gotchas/pty-terminal.md (and updated the index) documenting the invariant.

Test plan

  • New/updated unit tests assert the delivery goes through WriteInputSystem, not WriteInput (TestTerminalPane_ForwardEmulatorResponse_ForwardsToSession, _DropsStaleOwner, the pre-existing TestTerminalPane_LiveEmulatorForwardsQueryResponseToSession).
  • New end-to-end regression against a real *agent.Session proves LastUserInput stays zero while LastInput advances (TestTerminalPane_ForwardEmulatorResponse_DoesNotAdvanceLastUserInput).
  • Verified all new/updated tests fail against the pre-fix code (temporarily reverted, confirmed red, restored).
  • make pre-pr green except govulncheck (stdlib CVEs, advisory-only — CI itself runs this step with continue-on-error: true, same result on master).
  • Full suite + -race on all touched packages green; two known pre-existing, unrelated local flakes confirmed independent of this change (internal/agent ARGUS_* env leakage in this sandbox, internal/selfupdate 1Password gpg-signing).

🤖 Generated with Claude Code

…delivery

The live PTY emulator's OSC 10/11 query-response forwarder delivered via
Session.WriteInput, which stamps lastUserInput exactly like a real keystroke.
idle-detection's clear-on-input logic reads that timestamp to decide the user
answered a pending prompt, so a still-focused pane's periodic capability-query
response falsely cleared the "(?)" needs-input flag every ~1s, producing a
moon/(?) bounce for as long as the pane stayed focused.

Route the forward through WriteInputSystem instead, mirroring the existing
reliable-notify delivery pattern that exists for exactly this reason.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…igin (#952)

WriteInput (human keystroke) and WriteInputSystem (argus-injected input)
are genuinely distinct semantics the needs-input heuristics depend on
(BUG-034) - not tech debt to collapse. But nothing structurally forced a
caller to pick the right one, and picking the wrong one has now happened
twice (BUG-034 itself, then fix-live-emulator-writeinput). Replace both
with WriteInput(p []byte, origin agentview.InputOrigin), a mandatory
second parameter with no default, across agent.SessionHandle,
agentview.TerminalAdapter, notify.SessionHandleIface,
daemon/client.RemoteSession, and apiclient.Session.

Also threads origin across the daemon RPC (WriteReq.Origin, ProtocolVersion
4->5, zero-value-safe both directions since OriginUser is the zero value)
and the REST /input path (X-Input-Origin header, defaulting to user),
closing the gap where a system-origin write silently degraded to
user-input semantics once it crossed a process boundary.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Merging this branch changes the coverage (3 decrease, 3 increase)

Impacted Packages Coverage Δ 🤖
github.com/drn/argus/internal/agent 89.88% (-0.02%) 👎
github.com/drn/argus/internal/api 87.00% (+0.01%) 👍
github.com/drn/argus/internal/apiclient 88.08% (+0.24%) 👍
github.com/drn/argus/internal/app/agentview 92.11% (-7.89%) 👎
github.com/drn/argus/internal/daemon 84.97% (ø)
github.com/drn/argus/internal/daemon/client 83.78% (+0.17%) 👍
github.com/drn/argus/internal/notify 90.60% (ø)
github.com/drn/argus/internal/tui 83.22% (-0.01%) 👎
github.com/drn/argus/internal/tui/hera 92.50% (ø)
github.com/drn/argus/internal/tui/terminal 96.12% (ø)

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/drn/argus/internal/agent/iface.go 0.00% (ø) 0 0 0
github.com/drn/argus/internal/agent/needsinput.go 95.09% (ø) 265 252 13
github.com/drn/argus/internal/agent/session.go 95.98% (-0.10%) 199 (-5) 191 (-5) 8 👎
github.com/drn/argus/internal/api/handlers.go 87.43% (+0.05%) 732 (+3) 640 (+3) 92 👍
github.com/drn/argus/internal/api/push.go 94.88% (ø) 254 241 13
github.com/drn/argus/internal/apiclient/client.go 93.06% (+0.20%) 72 (+2) 67 (+2) 5 👍
github.com/drn/argus/internal/apiclient/session.go 82.43% (+0.43%) 148 (-2) 122 (-1) 26 (-1) 👍
github.com/drn/argus/internal/apiclient/settings.go 85.07% (ø) 67 57 10
github.com/drn/argus/internal/apiclient/terminal.go 82.46% (+0.97%) 57 (+3) 47 (+3) 10 👍
github.com/drn/argus/internal/app/agentview/terminal.go 0.00% (ø) 3 (+3) 0 3 (+3)
github.com/drn/argus/internal/daemon/client/handle.go 93.80% (+0.25%) 129 (+5) 121 (+5) 8 👍
github.com/drn/argus/internal/daemon/sessioncore.go 78.26% (ø) 161 126 35
github.com/drn/argus/internal/daemon/types.go 100.00% (ø) 1 1 0
github.com/drn/argus/internal/notify/service.go 90.91% (ø) 132 120 12
github.com/drn/argus/internal/notify/types.go 0.00% (ø) 0 0 0
github.com/drn/argus/internal/tui/app.go 78.71% (ø) 2752 2166 586
github.com/drn/argus/internal/tui/hera/panes.go 94.29% (ø) 140 132 8
github.com/drn/argus/internal/tui/heraactions.go 76.17% (ø) 579 441 138
github.com/drn/argus/internal/tui/terminal/terminalpane.go 96.31% (ø) 841 810 31

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/drn/argus/internal/agent/create_test.go
  • github.com/drn/argus/internal/agent/session_race_test.go
  • github.com/drn/argus/internal/agent/session_test.go
  • github.com/drn/argus/internal/api/handlers_test.go
  • github.com/drn/argus/internal/api/needsinput_test.go
  • github.com/drn/argus/internal/api/notify_test.go
  • github.com/drn/argus/internal/apiclient/client_test.go
  • github.com/drn/argus/internal/apiclient/provider_test.go
  • github.com/drn/argus/internal/daemon/client/client_test.go
  • github.com/drn/argus/internal/daemon/client/handle_test.go
  • github.com/drn/argus/internal/daemon/nudge_test.go
  • github.com/drn/argus/internal/notify/service_test.go
  • github.com/drn/argus/internal/tui/app_test.go
  • github.com/drn/argus/internal/tui/bug031_test.go
  • github.com/drn/argus/internal/tui/hera/panes_test.go
  • github.com/drn/argus/internal/tui/heraactions_test.go
  • github.com/drn/argus/internal/tui/terminal/forward_wheel_test.go
  • github.com/drn/argus/internal/tui/terminal/terminalpane_test.go

@anutron
anutron merged commit 3eb749c into master Aug 23, 2026
1 check passed
@anutron
anutron deleted the argus/fix-live-emulator-writeinput branch August 23, 2026 07:20
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