anima-core 1.3.0 → 1.5.0
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 +4 -4
- data/.reek.yml +23 -26
- data/README.md +118 -104
- data/agents/thoughts-analyzer.md +12 -7
- data/anima-core.gemspec +1 -0
- data/app/channels/session_channel.rb +38 -58
- data/app/decorators/agent_message_decorator.rb +7 -2
- data/app/decorators/message_decorator.rb +31 -100
- data/app/decorators/pending_from_melete_decorator.rb +36 -0
- data/app/decorators/pending_from_melete_goal_decorator.rb +13 -0
- data/app/decorators/pending_from_melete_skill_decorator.rb +19 -0
- data/app/decorators/pending_from_melete_workflow_decorator.rb +13 -0
- data/app/decorators/pending_from_mneme_decorator.rb +44 -0
- data/app/decorators/pending_message_decorator.rb +94 -0
- data/app/decorators/pending_subagent_decorator.rb +46 -0
- data/app/decorators/pending_tool_response_decorator.rb +51 -0
- data/app/decorators/pending_user_message_decorator.rb +22 -0
- data/app/decorators/system_message_decorator.rb +5 -0
- data/app/decorators/tool_call_decorator.rb +16 -5
- data/app/decorators/tool_response_decorator.rb +2 -2
- data/app/decorators/user_message_decorator.rb +7 -2
- data/app/jobs/count_tokens_job.rb +23 -0
- data/app/jobs/drain_job.rb +169 -0
- data/app/jobs/melete_enrichment_job/goal_change_listener.rb +52 -0
- data/app/jobs/melete_enrichment_job.rb +48 -0
- data/app/jobs/mneme_enrichment_job.rb +46 -0
- data/app/jobs/tool_execution_job.rb +87 -0
- data/app/models/concerns/token_estimation.rb +54 -0
- data/app/models/goal.rb +23 -11
- data/app/models/message.rb +46 -48
- data/app/models/pending_message.rb +407 -12
- data/app/models/pinned_message.rb +8 -3
- data/app/models/session.rb +660 -566
- data/app/models/snapshot.rb +11 -21
- data/bin/inspect-cassette +157 -0
- data/bin/release +212 -0
- data/bin/with-llms +20 -0
- data/config/application.rb +1 -0
- data/config/database.yml +1 -0
- data/config/initializers/event_subscribers.rb +71 -4
- data/config/initializers/inflections.rb +3 -1
- data/db/cable_structure.sql +9 -0
- data/db/migrate/20260330120000_add_source_to_pending_messages.rb +8 -0
- data/db/migrate/20260401180000_add_api_metrics_to_messages.rb +7 -0
- data/db/migrate/20260401210935_remove_recalled_message_ids_from_sessions.rb +5 -0
- data/db/migrate/20260403080031_add_initial_cwd_to_sessions.rb +5 -0
- data/db/migrate/20260407170803_remove_viewport_message_ids_from_sessions.rb +5 -0
- data/db/migrate/20260407180400_remove_mneme_snapshot_pointer_columns_from_sessions.rb +6 -0
- data/db/migrate/20260411120553_add_token_count_to_pinned_messages.rb +5 -0
- data/db/migrate/20260411172926_remove_active_skills_and_workflow_from_sessions.rb +6 -0
- data/db/migrate/20260412110625_replace_processing_with_aasm_state.rb +6 -0
- data/db/migrate/20260418150323_add_kind_and_message_type_to_pending_messages.rb +6 -0
- data/db/migrate/20260419120000_add_drain_fields_to_pending_messages.rb +7 -0
- data/db/migrate/20260419130000_drop_pending_messages_kind_default.rb +5 -0
- data/db/migrate/20260419140000_add_drain_indexes_to_pending_messages.rb +8 -0
- data/db/migrate/20260420100000_add_hud_visibility_to_sessions.rb +15 -0
- data/db/queue_structure.sql +61 -0
- data/db/structure.sql +133 -0
- data/lib/agents/registry.rb +1 -1
- data/lib/anima/cli.rb +41 -13
- data/lib/anima/installer.rb +13 -0
- data/lib/anima/settings.rb +16 -36
- data/lib/anima/version.rb +1 -1
- data/lib/events/authentication_required.rb +24 -0
- data/lib/events/bounce_back.rb +4 -4
- data/lib/events/eviction_completed.rb +28 -0
- data/lib/events/goal_created.rb +28 -0
- data/lib/events/goal_updated.rb +32 -0
- data/lib/events/llm_responded.rb +35 -0
- data/lib/events/message_created.rb +27 -0
- data/lib/events/message_updated.rb +25 -0
- data/lib/events/session_state_changed.rb +30 -0
- data/lib/events/skill_activated.rb +28 -0
- data/lib/events/start_melete.rb +36 -0
- data/lib/events/start_mneme.rb +33 -0
- data/lib/events/start_processing.rb +32 -0
- data/lib/events/subagent_evicted.rb +31 -0
- data/lib/events/subscribers/active_state_broadcaster.rb +27 -0
- data/lib/events/subscribers/authentication_broadcaster.rb +34 -0
- data/lib/events/subscribers/drain_kickoff.rb +20 -0
- data/lib/events/subscribers/eviction_broadcaster.rb +26 -0
- data/lib/events/subscribers/llm_response_handler.rb +111 -0
- data/lib/events/subscribers/melete_kickoff.rb +24 -0
- data/lib/events/subscribers/message_broadcaster.rb +34 -0
- data/lib/events/subscribers/mneme_kickoff.rb +24 -0
- data/lib/events/subscribers/mneme_scheduler.rb +21 -0
- data/lib/events/subscribers/persister.rb +8 -9
- data/lib/events/subscribers/session_state_broadcaster.rb +33 -0
- data/lib/events/subscribers/subagent_message_router.rb +28 -34
- data/lib/events/subscribers/subagent_visibility_broadcaster.rb +33 -0
- data/lib/events/subscribers/tool_response_creator.rb +33 -0
- data/lib/events/subscribers/transient_broadcaster.rb +1 -1
- data/lib/events/tool_executed.rb +34 -0
- data/lib/events/workflow_activated.rb +27 -0
- data/lib/llm/client.rb +46 -199
- data/lib/mcp/client_manager.rb +41 -46
- data/lib/mcp/stdio_transport.rb +9 -5
- data/lib/{analytical_brain → melete}/runner.rb +73 -68
- data/lib/{analytical_brain → melete}/tools/activate_skill.rb +3 -3
- data/lib/{analytical_brain → melete}/tools/assign_nickname.rb +3 -3
- data/lib/{analytical_brain → melete}/tools/everything_is_ready.rb +2 -2
- data/lib/{analytical_brain → melete}/tools/finish_goal.rb +6 -3
- data/lib/melete/tools/goal_messaging.rb +29 -0
- data/lib/{analytical_brain → melete}/tools/read_workflow.rb +4 -4
- data/lib/{analytical_brain → melete}/tools/rename_session.rb +3 -3
- data/lib/{analytical_brain → melete}/tools/set_goal.rb +6 -2
- data/lib/{analytical_brain → melete}/tools/update_goal.rb +9 -5
- data/lib/{analytical_brain.rb → melete.rb} +6 -3
- data/lib/mneme/base_runner.rb +121 -0
- data/lib/mneme/l2_runner.rb +14 -20
- data/lib/mneme/recall_runner.rb +132 -0
- data/lib/mneme/runner.rb +123 -165
- data/lib/mneme/search.rb +104 -62
- data/lib/mneme/tools/nothing_to_surface.rb +25 -0
- data/lib/mneme/tools/save_snapshot.rb +2 -10
- data/lib/mneme/tools/surface_memory.rb +89 -0
- data/lib/mneme.rb +11 -5
- data/lib/providers/anthropic.rb +112 -7
- data/lib/shell_session.rb +290 -432
- data/lib/skills/definition.rb +2 -2
- data/lib/skills/registry.rb +1 -1
- data/lib/tools/base.rb +16 -1
- data/lib/tools/bash.rb +25 -55
- data/lib/tools/edit.rb +2 -0
- data/lib/tools/mark_goal_completed.rb +4 -5
- data/lib/tools/read.rb +2 -0
- data/lib/tools/registry.rb +85 -4
- data/lib/tools/response_truncator.rb +1 -1
- data/lib/tools/{recall.rb → search_messages.rb} +19 -21
- data/lib/tools/spawn_specialist.rb +22 -14
- data/lib/tools/spawn_subagent.rb +30 -20
- data/lib/tools/subagent_prompts.rb +17 -19
- data/lib/tools/think.rb +1 -1
- data/lib/tools/{remember.rb → view_messages.rb} +10 -10
- data/lib/tools/write.rb +2 -0
- data/lib/tui/app.rb +393 -149
- data/lib/tui/braille_spinner.rb +7 -7
- data/lib/tui/cable_client.rb +9 -16
- data/lib/tui/decorators/base_decorator.rb +47 -6
- data/lib/tui/decorators/bash_decorator.rb +1 -1
- data/lib/tui/decorators/edit_decorator.rb +4 -2
- data/lib/tui/decorators/read_decorator.rb +4 -2
- data/lib/tui/decorators/think_decorator.rb +2 -2
- data/lib/tui/decorators/web_get_decorator.rb +1 -1
- data/lib/tui/decorators/write_decorator.rb +4 -2
- data/lib/tui/flash.rb +19 -14
- data/lib/tui/formatting.rb +20 -9
- data/lib/tui/input_buffer.rb +6 -6
- data/lib/tui/message_store.rb +165 -28
- data/lib/tui/performance_logger.rb +2 -3
- data/lib/tui/screens/chat.rb +149 -79
- data/lib/tui/settings.rb +93 -0
- data/lib/workflows/definition.rb +3 -3
- data/lib/workflows/registry.rb +1 -1
- data/skills/github.md +38 -0
- data/templates/config.toml +16 -32
- data/templates/tui.toml +209 -0
- data/workflows/review_pr.md +18 -14
- metadata +98 -29
- data/app/jobs/agent_request_job.rb +0 -199
- data/app/jobs/analytical_brain_job.rb +0 -33
- data/app/jobs/count_message_tokens_job.rb +0 -39
- data/app/jobs/passive_recall_job.rb +0 -29
- data/app/models/concerns/message/broadcasting.rb +0 -85
- data/config/initializers/fts5_schema_dump.rb +0 -21
- data/lib/agent_loop.rb +0 -186
- data/lib/analytical_brain/tools/deactivate_skill.rb +0 -39
- data/lib/analytical_brain/tools/deactivate_workflow.rb +0 -34
- data/lib/environment_probe.rb +0 -232
- data/lib/events/agent_message.rb +0 -11
- data/lib/events/subscribers/message_collector.rb +0 -64
- data/lib/events/tool_call.rb +0 -31
- data/lib/events/tool_response.rb +0 -33
- data/lib/mneme/compressed_viewport.rb +0 -200
- data/lib/mneme/passive_recall.rb +0 -69
data/lib/tui/message_store.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "settings"
|
|
4
|
+
|
|
3
5
|
module TUI
|
|
4
6
|
# Thread-safe in-memory store for chat entries displayed in the TUI.
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
+
# Holds the WebSocket-delivered view of the session's conversation with
|
|
8
|
+
# no dependency on Rails or the Events module.
|
|
7
9
|
#
|
|
8
10
|
# Accepts Action Cable message payloads and stores typed entries:
|
|
9
11
|
# - `{type: :rendered, data:, message_type:, id:}` for messages with structured decorator output
|
|
@@ -40,6 +42,7 @@ module TUI
|
|
|
40
42
|
@pending_by_id = {}
|
|
41
43
|
@mutex = Mutex.new
|
|
42
44
|
@version = 0
|
|
45
|
+
@token_economy = default_token_economy
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
# Monotonically increasing counter that bumps on every mutation.
|
|
@@ -60,6 +63,36 @@ module TUI
|
|
|
60
63
|
@mutex.synchronize { @entries.size + @pending_entries.size }
|
|
61
64
|
end
|
|
62
65
|
|
|
66
|
+
# Returns token economy data for HUD display.
|
|
67
|
+
#
|
|
68
|
+
# Token counts, rate limits, and cache hit rate reflect the most recent API
|
|
69
|
+
# call — accumulating them across a session produces values larger than the
|
|
70
|
+
# context window, which is meaningless. `call_count` and `cache_history`
|
|
71
|
+
# remain session-wide so the HUD can detect "any metrics yet?" and render
|
|
72
|
+
# the per-call sparkline.
|
|
73
|
+
#
|
|
74
|
+
# @return [Hash] token economy stats:
|
|
75
|
+
# - :input_tokens [Integer] uncached input tokens from the latest call
|
|
76
|
+
# - :output_tokens [Integer] output tokens from the latest call
|
|
77
|
+
# - :cache_read_input_tokens [Integer] cached token reads from the latest call
|
|
78
|
+
# - :cache_creation_input_tokens [Integer] cache writes from the latest call
|
|
79
|
+
# - :call_count [Integer] number of API calls tracked this session
|
|
80
|
+
# - :cache_hit_rate [Float] hit rate of the latest call (0.0-1.0)
|
|
81
|
+
# - :rate_limits [Hash, nil] latest rate limit values from API
|
|
82
|
+
# - :cache_history [Array<Float>] per-call hit rates for the sparkline
|
|
83
|
+
def token_economy
|
|
84
|
+
@mutex.synchronize do
|
|
85
|
+
stats = @token_economy.dup
|
|
86
|
+
total_input = stats[:input_tokens] + stats[:cache_read_input_tokens] + stats[:cache_creation_input_tokens]
|
|
87
|
+
stats[:cache_hit_rate] = if total_input > 0
|
|
88
|
+
stats[:cache_read_input_tokens].to_f / total_input
|
|
89
|
+
else
|
|
90
|
+
0.0
|
|
91
|
+
end
|
|
92
|
+
stats
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
63
96
|
# Processes a raw event payload from the WebSocket channel.
|
|
64
97
|
# Uses structured decorator data when available; falls back to
|
|
65
98
|
# role/content extraction for messages and tool counter aggregation.
|
|
@@ -67,12 +100,20 @@ module TUI
|
|
|
67
100
|
# Events with `"action" => "update"` and a matching `"id"` replace
|
|
68
101
|
# the existing entry's data in-place rather than appending.
|
|
69
102
|
#
|
|
103
|
+
# Extracts api_metrics when present and records the latest call's
|
|
104
|
+
# token economy data for the HUD.
|
|
105
|
+
#
|
|
70
106
|
# @param event_data [Hash] Action Cable event payload with "type", "content",
|
|
71
|
-
# and optionally "rendered" (hash of mode => lines), "id", "action"
|
|
107
|
+
# and optionally "rendered" (hash of mode => lines), "id", "action", "api_metrics"
|
|
72
108
|
# @return [Boolean] true if the event type was recognized and handled
|
|
73
109
|
def process_event(event_data)
|
|
74
110
|
message_id = event_data["id"]
|
|
75
111
|
|
|
112
|
+
# Track API metrics for token economy HUD (only on create, not update)
|
|
113
|
+
if event_data["action"] != "update"
|
|
114
|
+
track_api_metrics(event_data["api_metrics"])
|
|
115
|
+
end
|
|
116
|
+
|
|
76
117
|
if event_data["action"] == "update" && message_id
|
|
77
118
|
return update_existing(event_data, message_id)
|
|
78
119
|
end
|
|
@@ -93,6 +134,7 @@ module TUI
|
|
|
93
134
|
|
|
94
135
|
# Removes all entries. Called on view mode change and session switch
|
|
95
136
|
# to prepare for re-decorated viewport messages from the server.
|
|
137
|
+
# Resets token economy totals since we're starting fresh.
|
|
96
138
|
# @return [void]
|
|
97
139
|
def clear
|
|
98
140
|
@mutex.synchronize do
|
|
@@ -100,28 +142,44 @@ module TUI
|
|
|
100
142
|
@entries_by_id = {}
|
|
101
143
|
@pending_entries = []
|
|
102
144
|
@pending_by_id = {}
|
|
145
|
+
@token_economy = default_token_economy
|
|
103
146
|
@version += 1
|
|
104
147
|
end
|
|
105
148
|
end
|
|
106
149
|
|
|
107
|
-
# Adds a pending message to the separate pending list
|
|
150
|
+
# Adds a pending message to the separate pending list, or removes any
|
|
151
|
+
# existing entry when the decorator hides the PM in the current view
|
|
152
|
+
# mode (rendered hash present but its sole value is nil).
|
|
153
|
+
#
|
|
154
|
+
# Accepts the full +pending_message_created+ event payload. Falls back
|
|
155
|
+
# to a dimmed user-message envelope when no +rendered+ key is present
|
|
156
|
+
# (legacy producers, simple test fixtures).
|
|
157
|
+
#
|
|
108
158
|
# Pending messages always render after real messages.
|
|
109
159
|
#
|
|
110
160
|
# @param pending_message_id [Integer] PendingMessage database ID
|
|
111
|
-
# @param
|
|
161
|
+
# @param payload [Hash] event payload with "content", "message_type",
|
|
162
|
+
# and optionally "rendered" (hash of mode => decorator output)
|
|
112
163
|
# @return [void]
|
|
113
|
-
def add_pending(pending_message_id,
|
|
164
|
+
def add_pending(pending_message_id, payload)
|
|
165
|
+
data = pending_entry_data(payload)
|
|
166
|
+
message_type = payload["message_type"] || "user_message"
|
|
167
|
+
|
|
114
168
|
@mutex.synchronize do
|
|
115
|
-
|
|
116
|
-
type: :rendered,
|
|
117
|
-
data: {"role" => "user", "content" => content, "status" => "pending"},
|
|
118
|
-
message_type: "user_message",
|
|
119
|
-
pending_message_id: pending_message_id
|
|
120
|
-
}
|
|
121
|
-
old = @pending_by_id[pending_message_id]
|
|
169
|
+
old = @pending_by_id.delete(pending_message_id)
|
|
122
170
|
@pending_entries.delete(old) if old
|
|
123
|
-
|
|
124
|
-
|
|
171
|
+
|
|
172
|
+
if data
|
|
173
|
+
entry = {
|
|
174
|
+
type: :rendered,
|
|
175
|
+
data: data,
|
|
176
|
+
message_type: message_type,
|
|
177
|
+
pending_message_id: pending_message_id
|
|
178
|
+
}
|
|
179
|
+
@pending_entries << entry
|
|
180
|
+
@pending_by_id[pending_message_id] = entry
|
|
181
|
+
end
|
|
182
|
+
|
|
125
183
|
@version += 1
|
|
126
184
|
end
|
|
127
185
|
end
|
|
@@ -169,24 +227,21 @@ module TUI
|
|
|
169
227
|
end
|
|
170
228
|
end
|
|
171
229
|
|
|
172
|
-
# Removes entries
|
|
173
|
-
#
|
|
174
|
-
#
|
|
230
|
+
# Removes all entries with message ID <= cutoff. Used when Mneme
|
|
231
|
+
# evicts messages above the cutoff in the chat view (older messages
|
|
232
|
+
# at the top with smaller IDs).
|
|
175
233
|
#
|
|
176
|
-
# @param
|
|
234
|
+
# @param cutoff_id [Integer] last evicted message ID
|
|
177
235
|
# @return [Integer] count of entries actually removed
|
|
178
|
-
def
|
|
236
|
+
def remove_above(cutoff_id)
|
|
179
237
|
@mutex.synchronize do
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
entry = @entries_by_id.delete(message_id)
|
|
183
|
-
next unless entry
|
|
184
|
-
|
|
238
|
+
evicted = @entries.select { |e| e[:id] && e[:id] <= cutoff_id }
|
|
239
|
+
evicted.each do |entry|
|
|
185
240
|
@entries.delete(entry)
|
|
186
|
-
|
|
241
|
+
@entries_by_id.delete(entry[:id])
|
|
187
242
|
end
|
|
188
|
-
@version += 1 if
|
|
189
|
-
|
|
243
|
+
@version += 1 if evicted.any?
|
|
244
|
+
evicted.size
|
|
190
245
|
end
|
|
191
246
|
end
|
|
192
247
|
|
|
@@ -211,6 +266,33 @@ module TUI
|
|
|
211
266
|
end
|
|
212
267
|
end
|
|
213
268
|
|
|
269
|
+
# Builds the +data+ hash for a pending entry. Returns nil when the
|
|
270
|
+
# decorator hid the PM (the +rendered+ key is present but its sole
|
|
271
|
+
# value is nil), so the caller can treat that as a removal. Falls
|
|
272
|
+
# back to a dimmed user-message envelope when no +rendered+ key is
|
|
273
|
+
# present at all (legacy callers, fixtures).
|
|
274
|
+
#
|
|
275
|
+
# The decorator's hash already carries its own +"status" => "pending"+
|
|
276
|
+
# marker; the symbolized form is converted here so the TUI's lookup
|
|
277
|
+
# via +data["status"]+ remains uniform.
|
|
278
|
+
def pending_entry_data(payload)
|
|
279
|
+
if payload.key?("rendered")
|
|
280
|
+
values = payload["rendered"]&.values&.compact
|
|
281
|
+
return nil if values.nil? || values.empty?
|
|
282
|
+
|
|
283
|
+
normalize_pending_data(values.first)
|
|
284
|
+
else
|
|
285
|
+
{"role" => "user", "content" => payload["content"], "status" => "pending"}
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Symbol/string keys arrive interchangeably from decorators that build
|
|
290
|
+
# hash literals server-side. Normalize keys to strings so chat.rb's
|
|
291
|
+
# +data["role"]+ / +data["status"]+ lookups always hit.
|
|
292
|
+
def normalize_pending_data(rendered)
|
|
293
|
+
rendered.transform_keys(&:to_s)
|
|
294
|
+
end
|
|
295
|
+
|
|
214
296
|
# Extracts the first non-nil structured data hash from the rendered payload.
|
|
215
297
|
# The "rendered" hash is keyed by view mode — the server includes only the
|
|
216
298
|
# session's current mode, so there is always at most one entry.
|
|
@@ -346,5 +428,60 @@ module TUI
|
|
|
346
428
|
last = @entries.last
|
|
347
429
|
last if last&.dig(:type) == :tool_counter
|
|
348
430
|
end
|
|
431
|
+
|
|
432
|
+
# Default token economy state for initialization and reset.
|
|
433
|
+
# @return [Hash]
|
|
434
|
+
def default_token_economy
|
|
435
|
+
{
|
|
436
|
+
input_tokens: 0,
|
|
437
|
+
output_tokens: 0,
|
|
438
|
+
cache_read_input_tokens: 0,
|
|
439
|
+
cache_creation_input_tokens: 0,
|
|
440
|
+
call_count: 0,
|
|
441
|
+
rate_limits: nil,
|
|
442
|
+
cache_history: []
|
|
443
|
+
}
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Records API metrics from the most recent message.
|
|
447
|
+
#
|
|
448
|
+
# Token counts and rate limits are last-wins (per-request semantics);
|
|
449
|
+
# `call_count` increments and `cache_history` appends so the HUD can
|
|
450
|
+
# show a sparkline of per-call hit rates.
|
|
451
|
+
#
|
|
452
|
+
# @param api_metrics [Hash, nil] metrics from API response with "usage" and "rate_limits"
|
|
453
|
+
# @return [void]
|
|
454
|
+
def track_api_metrics(api_metrics)
|
|
455
|
+
return unless api_metrics.is_a?(Hash)
|
|
456
|
+
|
|
457
|
+
@mutex.synchronize do
|
|
458
|
+
usage = api_metrics["usage"]
|
|
459
|
+
if usage.is_a?(Hash)
|
|
460
|
+
input = usage["input_tokens"].to_i
|
|
461
|
+
cache_read = usage["cache_read_input_tokens"].to_i
|
|
462
|
+
cache_create = usage["cache_creation_input_tokens"].to_i
|
|
463
|
+
|
|
464
|
+
@token_economy[:input_tokens] = input
|
|
465
|
+
@token_economy[:output_tokens] = usage["output_tokens"].to_i
|
|
466
|
+
@token_economy[:cache_read_input_tokens] = cache_read
|
|
467
|
+
@token_economy[:cache_creation_input_tokens] = cache_create
|
|
468
|
+
@token_economy[:call_count] += 1
|
|
469
|
+
|
|
470
|
+
# Per-call cache hit rate for sparkline graph
|
|
471
|
+
total = input + cache_read + cache_create
|
|
472
|
+
hit_rate = (total > 0) ? cache_read.to_f / total : 0.0
|
|
473
|
+
history = @token_economy[:cache_history]
|
|
474
|
+
history.shift if history.size >= Settings.message_store_max_cache_history
|
|
475
|
+
history << hit_rate
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
rate_limits = api_metrics["rate_limits"]
|
|
479
|
+
if rate_limits.is_a?(Hash)
|
|
480
|
+
@token_economy[:rate_limits] = rate_limits
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
@version += 1
|
|
484
|
+
end
|
|
485
|
+
end
|
|
349
486
|
end
|
|
350
487
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "logger"
|
|
4
|
+
require_relative "settings"
|
|
4
5
|
|
|
5
6
|
module TUI
|
|
6
7
|
# Frame-level performance logger for TUI render profiling.
|
|
@@ -18,8 +19,6 @@ module TUI
|
|
|
18
19
|
# logger.measure(:line_count) { widget.line_count(width) }
|
|
19
20
|
# logger.end_frame
|
|
20
21
|
class PerformanceLogger
|
|
21
|
-
LOG_PATH = "log/tui_performance.log"
|
|
22
|
-
|
|
23
22
|
# @param enabled [Boolean] whether to actually log (no-op when false)
|
|
24
23
|
def initialize(enabled: false)
|
|
25
24
|
@enabled = enabled
|
|
@@ -30,7 +29,7 @@ module TUI
|
|
|
30
29
|
|
|
31
30
|
return unless @enabled
|
|
32
31
|
|
|
33
|
-
@logger = Logger.new(
|
|
32
|
+
@logger = Logger.new(Settings.performance_log_path, 1, 5 * 1024 * 1024) # 5MB rotation
|
|
34
33
|
@logger.formatter = proc { |_sev, time, _prog, msg| "#{time.strftime("%H:%M:%S.%L")} #{msg}\n" }
|
|
35
34
|
@logger.info("TUI Performance Logger started — pid=#{Process.pid}")
|
|
36
35
|
end
|