Class: AgentsControl::Channels::Telegram::Keyboards

Inherits:
Object
  • Object
show all
Defined in:
lib/agents_control/channels/telegram/keyboards.rb

Overview

Building inline keyboards and rendering lists.

The key Bot API constraint: a button's callback_data can't exceed 64 bytes. An iTerm2 session UUID alone (36 characters, plus an action, plus separators) already cuts it close, let alone a path or a command.

So a button carries only a short key from Store, and the actual action lives on the daemon's side. Side benefit: the key is one-shot, and pressing the same button twice does nothing the second time.

Constant Summary collapse

ACTION_TTL =

How long a button lives. It has to survive a quick trip out, but not hang around forever: a stale message shouldn't stay a working remote control.

3600

Instance Method Summary collapse

Constructor Details

#initialize(store:) ⇒ Keyboards

Returns a new instance of Keyboards.



23
24
25
# File 'lib/agents_control/channels/telegram/keyboards.rb', line 23

def initialize(store:)
  @store = store
end

Instance Method Details

#confirm(action, session, label: "Yes, close it") ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/agents_control/channels/telegram/keyboards.rb', line 40

def confirm(action, session, label: "Yes, close it")
  {
    inline_keyboard: [[
      button("⚠️ #{label}", action: action, session: session),
      button("cancel", action: "cancel", session: session)
    ]]
  }
end

#list_actionsObject



49
50
51
52
53
54
# File 'lib/agents_control/channels/telegram/keyboards.rb', line 49

def list_actions
  { inline_keyboard: [[
    { text: "🤖 agents", callback_data: put(action: "list_agents") },
    { text: "📋 all tabs", callback_data: put(action: "list_tabs") }
  ]] }
end

Buttons under /start and /help — the full command list isn't always visible in the Telegram UI (the menu button is easy to miss among everything else), so this keeps it available at any moment right in the message.



60
61
62
63
64
65
66
67
# File 'lib/agents_control/channels/telegram/keyboards.rb', line 60

def main_menu
  { inline_keyboard: [
    [{ text: "🤖 agents", callback_data: put(action: "list_agents") },
     { text: "📋 all tabs", callback_data: put(action: "list_tabs") }],
    [{ text: "📊 status", callback_data: put(action: "status") },
     { text: "⚙️ settings", callback_data: put(action: "show_settings") }]
  ] }
end

#render_list(sessions, chat_id:, title:, universe: sessions) ⇒ Object

A list with short numbers: commands like /run 2 ls address sessions by them later.

Numbers are assigned against the full session list (universe), not the displayed subset — "All tabs" and "Agents" both number against the same universe, so a session keeps the same number in either view as long as the set of tabs hasn't changed. An /run with a remembered number must always hit the session the human actually saw, never a different one that happened to land on the same row in a shorter, filtered list.



79
80
81
82
83
84
85
86
87
88
# File 'lib/agents_control/channels/telegram/keyboards.rb', line 79

def render_list(sessions, chat_id:, title:, universe: sessions)
  numbers = numbered(universe)
  @store.put(numbers.invert, ttl: ACTION_TTL, key: index_key(chat_id))

  lines = sessions.map do |session|
    "#{numbers[session.id].to_s.rjust(2)}. #{marker(session)} #{describe(session)}"
  end

  ([title, ""] + lines).join("\n")
end

#session_actions(session) ⇒ Object

Buttons under a single session's card.



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/agents_control/channels/telegram/keyboards.rb', line 28

def session_actions(session)
  rows = [[
    button("🎯 focus", action: "focus", session: session),
    button("👁 screen", action: "screen", session: session)
  ]]

  # Closing a tab goes through confirmation: a mistap on a phone is too easy.
  rows << [button("✖️ close", action: "close_confirm", session: session)] unless session.terminalless?

  { inline_keyboard: rows }
end

#session_for(chat_id, number) ⇒ Object



90
91
92
93
94
# File 'lib/agents_control/channels/telegram/keyboards.rb', line 90

def session_for(chat_id, number)
  map = @store.get(index_key(chat_id)) || {}

  map[number.to_s]
end