Class: AgentsControl::ScreenWatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/agents_control/screen_watcher.rb

Overview

Notices Claude Code's local menus on a session's screen.

Dialogs the CLI shows itself in response to a local human command — switching models via /model, folder trust on first launch — never produce a hook event at all. The only way to see this state is to read the screen itself.

Goes through the full Registry — sees bare iTerm2 tabs too, not just tmux panes, same as RateLimitWatcher.

The heuristic is narrow: consecutive lines "❯ 1. …" and " 2. …" — this is how Claude Code draws any choice (folder trust, model switch, a tool permission if it ever made it to the screen), and almost nothing else draws like this.

Constant Summary collapse

CURSOR_OPTION =

"1." next to the cursor marks the first option — the start of a menu.

/^\s*❯\s*1\.\s+(.+?)\s*$/
OPTION =

Later options come without the cursor, just numbered.

/^\s*(\d+)\.\s+(.+?)\s*$/
LINES =

A 30-line screen comfortably covers any CLI dialog worth catching — Claude Code doesn't draw menus that long.

30

Instance Method Summary collapse

Constructor Details

#initialize(registry:, config:, store:, api:, logger: nil) ⇒ ScreenWatcher

Returns a new instance of ScreenWatcher.



31
32
33
34
35
36
37
38
# File 'lib/agents_control/screen_watcher.rb', line 31

def initialize(registry:, config:, store:, api:, logger: nil)
  @registry = registry
  @config = config
  @store = store
  @api = api
  @logger = logger
  @running = false
end

Instance Method Details

#startObject



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/agents_control/screen_watcher.rb', line 40

def start
  return self unless enabled?

  @running = true
  @thread = Thread.new do
    tick while @running
  ensure
    nil
  end

  self
end

#stopObject



53
54
55
56
# File 'lib/agents_control/screen_watcher.rb', line 53

def stop
  @running = false
  @thread&.kill
end

#tickObject

One pass over all agent sessions. Public method — tests call it directly, without spinning up a real background thread.



60
61
62
63
64
65
66
# File 'lib/agents_control/screen_watcher.rb', line 60

def tick
  candidates.each { |session| check(session) }
rescue StandardError => e
  log("failure: #{e.class}: #{e.message}")
ensure
  sleep(interval) if @running
end