legion-tty 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e484905e47a82bedca53b308ff66880c8b64bcb320d9af6a1130fe4ad50a056
4
- data.tar.gz: f5c49491e1227c5984e99f161662eef56a5d5924903666aa2ea9dcaecccf098f
3
+ metadata.gz: 2c188384bc3e940bfb528a15116ef146f721829b0bfc70bbc1df623795d42d19
4
+ data.tar.gz: '086e3dce92e450f34464e3c55136241f160313f5c52538ff5a6368e1df10ce14'
5
5
  SHA512:
6
- metadata.gz: f2eb92406a0f61f109213eec5858122038e270052cdca8c9c4f3bfcfba7d64d0ead54a1f40b53dd356aa08dc172f920a09f47c46dd108c15a708e2a680ffe474
7
- data.tar.gz: e107d25bbb2ce75ce886329f8ef02f2fb5991278c5a4d140cc809d940adf34aba9f14a9c4269309be847ae78c78d48ee4d33540d03bfc71712d4b4261ae7a080
6
+ metadata.gz: 1ba2062dd30d8c912fe163a2d2a0161b70169779f7cd54d94668a3e8ffab9ec463e1e4c3038f47054cda4e1c1d392ca1fe4a378fc630b2efc4ba829a35ff8563
7
+ data.tar.gz: c7240dfdd607a8c04bfd2b449863330e48b563d54d778976d974ddf83afe5ccc67ff8cc68f0bc71fba9f78fc6bd10bb03efa15f99fbfe11542812370b9d751cd
data/CHANGELOG.md CHANGED
@@ -1,12 +1,19 @@
1
1
  # Changelog
2
2
 
3
- ## [0.5.2] - 2026-04-20
3
+ ## [0.5.4] - 2026-04-28
4
4
 
5
5
  ### Fixed
6
- - **Bracketed paste support** — `App#run_loop` now enables bracketed paste mode (`\e[?2004h`) on startup and disables (`\e[?2004l`) on shutdown; `read_csi_sequence` detects `\e[200~` / `\e[201~` paste markers and buffers the full pasted text into a `{ paste: text }` event (closes #22)
7
- - **InputBar paste handling** — `handle_key` recognizes paste Hash events and inserts the full pasted text at the cursor position, replacing newlines with spaces, without triggering submit (closes #22)
8
- - **Non-symbol key guard** — `dispatch_key` checks `key.is_a?(Symbol)` before the scroll-event include check, preventing `NoMethodError` on Hash paste events (closes #22)
9
- - **`normalize_key` paste passthrough** — Hash events (paste) pass through without KEY_MAP lookup (closes #22)
6
+ - **LLM provider probe routing** — `Background::LlmProbe` now validates enabled providers through `Legion::LLM.ask` with the configured provider/model instead of calling RubyLLM directly.
7
+ - **Extension catalog provider stack** — the extensions screen now categorizes native `lex-llm-*` provider gems as AI extensions and shows `lex-llm-gateway` as legacy compatibility instead of an active Core extension.
8
+ - **Future native LLM providers** — the extensions screen now classifies future `lex-llm-*` provider gems as AI automatically while preserving `lex-llm-gateway` as Legacy.
9
+ - **Development dependency cleanup** — removed the stale direct `ruby_llm` Gemfile dependency so local TTY development follows the Legion-native LLM path.
10
+
11
+ ## [0.5.3] - 2026-04-20
12
+
13
+ ### Fixed
14
+ - **Scroll performance** — `MessageStream` now caches rendered lines per message keyed on `(object_id, content_hash, role, width, reactions, annotations, tags, pinned)`; cache invalidates automatically on content change (streaming), display option change, or width change — markdown re-parsing only runs for new/changed messages (closes #23)
15
+ - **Input coalescing** — `App#run_loop` drains all queued keys before the next `render_frame`, so rapid typing no longer triggers O(total-messages) renders per keystroke (closes #23)
16
+ - **PgUp/PgDn scroll step** — now scrolls by half-viewport (`(height-3)/2`) instead of a fixed 10 lines, matching Ctrl+B/Ctrl+F behavior (closes #23)
10
17
 
11
18
  ## [0.5.1] - 2026-04-18
12
19
 
@@ -542,7 +542,7 @@ module Legion
542
542
 
543
543
  def try_settings_llm
544
544
  # All LLM calls route through the LegionIO daemon API.
545
- # No raw RubyLLM session is created here nil signals "use daemon path".
545
+ # No raw provider session is created here; nil signals "use daemon path".
546
546
  if Legion::TTY::DaemonClient.available?
547
547
  log.debug { 'TTY: daemon available, LLM routed through daemon' }
548
548
  else
@@ -8,6 +8,8 @@ module Legion
8
8
  class LlmProbe
9
9
  include Legion::Logging::Helper
10
10
 
11
+ PROBE_PROMPT = 'Respond with only: pong'
12
+
11
13
  def initialize(logger: nil, wait_queue: nil)
12
14
  @boot_log = logger
13
15
  @wait_queue = wait_queue
@@ -76,7 +78,7 @@ module Legion
76
78
  def ping_provider(name, config)
77
79
  model = config[:default_model]
78
80
  start_time = Time.now
79
- RubyLLM.chat(model: model, provider: name).ask('Respond with only: pong')
81
+ Legion::LLM.ask(message: PROBE_PROMPT, model: model, provider: name)
80
82
  latency = ((Time.now - start_time) * 1000).round
81
83
  { name: name, model: model, status: :ok, latency_ms: latency }
82
84
  rescue StandardError => e
@@ -28,10 +28,13 @@ module Legion
28
28
  @show_numbers = false
29
29
  @colorize = true
30
30
  @show_timestamps = true
31
+ @line_cache = {}
32
+ @cache_options_hash = nil
31
33
  end
32
34
 
33
35
  def add_message(role:, content:)
34
36
  @messages << { role: role, content: content, tool_panels: [], timestamp: Time.now }
37
+ compact_cache if @line_cache.size > @messages.size * 2
35
38
  end
36
39
 
37
40
  def append_streaming(text)
@@ -87,15 +90,35 @@ module Legion
87
90
 
88
91
  private
89
92
 
90
- def build_all_lines(width)
93
+ def build_all_lines(width) # rubocop:disable Metrics/AbcSize
94
+ current_opts = options_hash
95
+ if current_opts != @cache_options_hash
96
+ @line_cache.clear
97
+ @cache_options_hash = current_opts
98
+ end
99
+
91
100
  filtered_messages.each_with_index.flat_map do |msg, idx|
92
101
  next [] if @mute_system && msg[:role] == :system
93
102
  next [] if @silent_mode && msg[:role] == :assistant
94
103
 
95
- render_message(msg, width, @show_numbers ? idx + 1 : nil)
104
+ cache_key = message_cache_key(msg, width)
105
+ @line_cache[cache_key] ||= render_message(msg, width, @show_numbers ? idx + 1 : nil)
96
106
  end
97
107
  end
98
108
 
109
+ def options_hash
110
+ [@wrap_width, @show_numbers, @colorize, @show_timestamps, @highlights, @truncate_limit].hash
111
+ end
112
+
113
+ def message_cache_key(msg, width)
114
+ [msg.object_id, msg[:content], msg[:role], width,
115
+ msg[:reactions], msg[:annotations], msg[:tags], msg[:pinned]].hash
116
+ end
117
+
118
+ def compact_cache
119
+ @line_cache.clear
120
+ end
121
+
99
122
  def filtered_messages
100
123
  return @messages if @filter.nil?
101
124
 
@@ -208,12 +208,10 @@ module Legion
208
208
 
209
209
  def handle_scroll_key(key)
210
210
  case key
211
- when :page_up then @message_stream.scroll_up(10)
212
- when :page_down then @message_stream.scroll_down(10)
211
+ when :page_up, :ctrl_b then @message_stream.scroll_up(half_page_lines)
212
+ when :page_down, :ctrl_f then @message_stream.scroll_down(half_page_lines)
213
213
  when :scroll_up then @message_stream.scroll_up(3)
214
214
  when :scroll_down then @message_stream.scroll_down(3)
215
- when :ctrl_b then @message_stream.scroll_up(half_page_lines)
216
- when :ctrl_f then @message_stream.scroll_down(half_page_lines)
217
215
  when :home then @message_stream.scroll_up(@message_stream.messages.size * 5)
218
216
  when :end then @message_stream.scroll_down(@message_stream.scroll_offset)
219
217
  else return nil
@@ -12,15 +12,20 @@ module Legion
12
12
  include Legion::Logging::Helper
13
13
 
14
14
  CORE = %w[lex-node lex-tasker lex-scheduler lex-conditioner lex-transformer
15
- lex-synapse lex-health lex-log lex-ping lex-metering lex-llm-gateway
15
+ lex-synapse lex-health lex-log lex-ping lex-metering lex-llm-ledger
16
16
  lex-codegen lex-exec lex-lex lex-telemetry lex-audit lex-detect].freeze
17
17
 
18
- AI = %w[lex-claude lex-openai lex-gemini].freeze
18
+ AI = %w[lex-azure-ai lex-bedrock lex-claude lex-foundry lex-gemini lex-llamacpp
19
+ lex-mlx lex-ollama lex-openai lex-uais lex-xai lex-llm lex-llm-anthropic
20
+ lex-llm-azure-foundry lex-llm-bedrock lex-llm-gemini lex-llm-mlx
21
+ lex-llm-ollama lex-llm-openai lex-llm-vertex lex-llm-vllm].freeze
22
+
23
+ LEGACY = %w[lex-llm-gateway].freeze
19
24
 
20
25
  SERVICE = %w[lex-http lex-vault lex-github lex-consul lex-kerberos lex-tfe
21
26
  lex-redis lex-memcached lex-elasticsearch lex-s3].freeze
22
27
 
23
- CATEGORIES = [nil, 'Core', 'AI', 'Service', 'Agentic', 'Other'].freeze
28
+ CATEGORIES = [nil, 'Core', 'AI', 'Service', 'Agentic', 'Legacy', 'Other'].freeze
24
29
 
25
30
  def initialize(app, output: $stdout)
26
31
  super(app)
@@ -116,6 +121,8 @@ module Legion
116
121
 
117
122
  def categorize(name)
118
123
  return 'Core' if CORE.include?(name)
124
+ return 'Legacy' if LEGACY.include?(name)
125
+ return 'AI' if name.start_with?('lex-llm-')
119
126
  return 'AI' if AI.include?(name)
120
127
  return 'Service' if SERVICE.include?(name)
121
128
  return 'Agentic' if name.match?(/^lex-agentic-|^lex-theory-|^lex-mind-|^lex-planning|^lex-attention/)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module TTY
5
- VERSION = '0.5.2'
5
+ VERSION = '0.5.4'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-tty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity