anima-core 0.1.0 → 0.2.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/CHANGELOG.md +13 -3
- data/Gemfile +17 -0
- data/Procfile +2 -0
- data/Procfile.dev +2 -0
- data/README.md +56 -26
- data/Rakefile +19 -7
- data/anima-core.gemspec +40 -0
- data/app/channels/application_cable/channel.rb +6 -0
- data/app/channels/application_cable/connection.rb +6 -0
- data/app/channels/session_channel.rb +126 -0
- data/app/controllers/api/sessions_controller.rb +25 -0
- data/app/controllers/application_controller.rb +4 -0
- data/app/jobs/agent_request_job.rb +59 -0
- data/app/jobs/count_event_tokens_job.rb +1 -1
- data/app/models/session.rb +18 -9
- data/bin/jobs +6 -0
- data/bin/rails +6 -0
- data/bin/rake +6 -0
- data/config/application.rb +4 -0
- data/config/cable.yml +14 -0
- data/config/database.yml +12 -0
- data/config/initializers/event_subscribers.rb +11 -0
- data/config/puma.rb +13 -0
- data/config/routes.rb +8 -0
- data/config.ru +5 -0
- data/db/cable_schema.rb +11 -0
- data/lib/agent_loop.rb +97 -0
- data/lib/anima/cli.rb +64 -9
- data/lib/anima/installer.rb +4 -3
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +1 -0
- data/lib/events/subscribers/action_cable_bridge.rb +35 -0
- data/lib/events/subscribers/persister.rb +14 -4
- data/lib/providers/anthropic.rb +11 -2
- data/lib/tui/app.rb +71 -13
- data/lib/tui/cable_client.rb +377 -0
- data/lib/tui/message_store.rb +49 -0
- data/lib/tui/screens/chat.rb +179 -68
- metadata +80 -3
data/lib/tui/screens/chat.rb
CHANGED
|
@@ -11,29 +11,32 @@ module TUI
|
|
|
11
11
|
ROLE_ASSISTANT = "assistant"
|
|
12
12
|
ROLE_LABELS = {ROLE_USER => "You", ROLE_ASSISTANT => "Anima"}.freeze
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
SCROLL_STEP = 1
|
|
15
|
+
MOUSE_SCROLL_STEP = 2
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
attr_reader :input, :message_store, :scroll_offset, :session_info
|
|
18
|
+
|
|
19
|
+
# @param cable_client [TUI::CableClient] WebSocket client connected to the brain
|
|
20
|
+
# @param message_store [TUI::MessageStore, nil] injectable for testing
|
|
21
|
+
def initialize(cable_client:, message_store: nil)
|
|
22
|
+
@cable_client = cable_client
|
|
23
|
+
@message_store = message_store || MessageStore.new
|
|
18
24
|
@input = ""
|
|
19
25
|
@loading = false
|
|
20
|
-
@
|
|
21
|
-
@
|
|
22
|
-
|
|
23
|
-
@
|
|
24
|
-
|
|
25
|
-
@persister = persister || Events::Subscribers::Persister.new(@session)
|
|
26
|
-
@shell_session = shell_session || ShellSession.new(session_id: @session.id)
|
|
27
|
-
|
|
28
|
-
Events::Bus.subscribe(@message_collector)
|
|
29
|
-
Events::Bus.subscribe(@persister)
|
|
26
|
+
@scroll_offset = 0
|
|
27
|
+
@auto_scroll = true
|
|
28
|
+
@visible_height = 0
|
|
29
|
+
@max_scroll = 0
|
|
30
|
+
@session_info = {id: cable_client.session_id, message_count: 0}
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
def messages
|
|
33
|
-
@
|
|
34
|
+
@message_store.messages
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def render(frame, area, tui)
|
|
38
|
+
process_incoming_messages
|
|
39
|
+
|
|
37
40
|
chat_area, input_area = tui.split(
|
|
38
41
|
area,
|
|
39
42
|
direction: :vertical,
|
|
@@ -47,7 +50,10 @@ module TUI
|
|
|
47
50
|
render_input(frame, input_area, tui)
|
|
48
51
|
end
|
|
49
52
|
|
|
53
|
+
# Scrolling bypasses the loading guard so users can read chat history during LLM calls
|
|
50
54
|
def handle_event(event)
|
|
55
|
+
return handle_mouse_event(event) if event.mouse?
|
|
56
|
+
return handle_scroll_key(event) if scroll_key?(event)
|
|
51
57
|
return false if @loading
|
|
52
58
|
|
|
53
59
|
if event.enter?
|
|
@@ -64,23 +70,15 @@ module TUI
|
|
|
64
70
|
end
|
|
65
71
|
end
|
|
66
72
|
|
|
73
|
+
# Creates a new session through the WebSocket protocol.
|
|
74
|
+
# The brain creates the session, switches the channel stream, and sends
|
|
75
|
+
# a session_changed signal followed by (empty) history. The client-side
|
|
76
|
+
# state reset happens when session_changed is received.
|
|
67
77
|
def new_session
|
|
68
|
-
@
|
|
69
|
-
@shell_session&.finalize
|
|
70
|
-
@session = Session.create!
|
|
71
|
-
@persister.session = @session
|
|
72
|
-
@message_collector.clear
|
|
73
|
-
@input = ""
|
|
74
|
-
@loading = false
|
|
75
|
-
@shell_session = ShellSession.new(session_id: @session.id)
|
|
76
|
-
@registry = nil
|
|
78
|
+
@cable_client.create_session
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
def finalize
|
|
80
|
-
@submit_thread&.join
|
|
81
|
-
@shell_session&.finalize
|
|
82
|
-
Events::Bus.unsubscribe(@message_collector)
|
|
83
|
-
Events::Bus.unsubscribe(@persister)
|
|
84
82
|
end
|
|
85
83
|
|
|
86
84
|
def loading?
|
|
@@ -89,6 +87,63 @@ module TUI
|
|
|
89
87
|
|
|
90
88
|
private
|
|
91
89
|
|
|
90
|
+
# Drains the WebSocket message queue and feeds events to the message store
|
|
91
|
+
def process_incoming_messages
|
|
92
|
+
@cable_client.drain_messages.each do |msg|
|
|
93
|
+
action = msg["action"]
|
|
94
|
+
type = msg["type"]
|
|
95
|
+
|
|
96
|
+
case action
|
|
97
|
+
when "session_changed"
|
|
98
|
+
handle_session_changed(msg)
|
|
99
|
+
when "sessions_list"
|
|
100
|
+
@sessions_list = msg["sessions"]
|
|
101
|
+
when "error"
|
|
102
|
+
# Silently ignored — no user-facing error display yet
|
|
103
|
+
else
|
|
104
|
+
case type
|
|
105
|
+
when "connection"
|
|
106
|
+
handle_connection_status(msg)
|
|
107
|
+
when "user_message"
|
|
108
|
+
@message_store.process_event(msg)
|
|
109
|
+
@session_info[:message_count] += 1
|
|
110
|
+
@loading = true
|
|
111
|
+
when "agent_message"
|
|
112
|
+
@message_store.process_event(msg)
|
|
113
|
+
@session_info[:message_count] += 1
|
|
114
|
+
@loading = false
|
|
115
|
+
else
|
|
116
|
+
@message_store.process_event(msg)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Reacts to connection lifecycle changes from the WebSocket client.
|
|
123
|
+
# Clears stale state on (re)subscription so fresh history from the server
|
|
124
|
+
# replaces any messages displayed before the disconnect.
|
|
125
|
+
def handle_connection_status(msg)
|
|
126
|
+
case msg["status"]
|
|
127
|
+
when "subscribed"
|
|
128
|
+
@message_store.clear
|
|
129
|
+
@loading = false
|
|
130
|
+
@session_info[:message_count] = 0
|
|
131
|
+
when "disconnected", "failed"
|
|
132
|
+
@loading = false
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def handle_session_changed(msg)
|
|
137
|
+
new_id = msg["session_id"]
|
|
138
|
+
@cable_client.update_session_id(new_id)
|
|
139
|
+
@message_store.clear
|
|
140
|
+
@session_info = {id: new_id, message_count: msg["message_count"] || 0}
|
|
141
|
+
@input = ""
|
|
142
|
+
@loading = false
|
|
143
|
+
@scroll_offset = 0
|
|
144
|
+
@auto_scroll = true
|
|
145
|
+
end
|
|
146
|
+
|
|
92
147
|
def render_messages(frame, area, tui)
|
|
93
148
|
lines = build_message_lines(tui)
|
|
94
149
|
|
|
@@ -104,9 +159,21 @@ module TUI
|
|
|
104
159
|
])
|
|
105
160
|
end
|
|
106
161
|
|
|
162
|
+
inner_width = [area.width - 2, 1].max
|
|
163
|
+
@visible_height = [area.height - 2, 0].max
|
|
164
|
+
|
|
165
|
+
content_widget = tui.paragraph(text: lines, wrap: true, style: tui.style(fg: "white"))
|
|
166
|
+
content_height = content_widget.line_count(inner_width)
|
|
167
|
+
|
|
168
|
+
@max_scroll = [content_height - @visible_height, 0].max
|
|
169
|
+
@scroll_offset = @max_scroll if @auto_scroll
|
|
170
|
+
@scroll_offset = @scroll_offset.clamp(0, @max_scroll)
|
|
171
|
+
|
|
107
172
|
widget = tui.paragraph(
|
|
108
173
|
text: lines,
|
|
109
174
|
wrap: true,
|
|
175
|
+
style: tui.style(fg: "white"),
|
|
176
|
+
scroll: [@scroll_offset, 0],
|
|
110
177
|
block: tui.block(
|
|
111
178
|
title: "Chat",
|
|
112
179
|
borders: [:all],
|
|
@@ -115,6 +182,18 @@ module TUI
|
|
|
115
182
|
)
|
|
116
183
|
)
|
|
117
184
|
frame.render_widget(widget, area)
|
|
185
|
+
|
|
186
|
+
if @max_scroll > 0
|
|
187
|
+
scrollbar = tui.scrollbar(
|
|
188
|
+
content_length: @max_scroll,
|
|
189
|
+
position: @scroll_offset,
|
|
190
|
+
orientation: :vertical_right,
|
|
191
|
+
thumb_style: {fg: "cyan"},
|
|
192
|
+
track_symbol: "│",
|
|
193
|
+
track_style: {fg: "dark_gray"}
|
|
194
|
+
)
|
|
195
|
+
frame.render_widget(scrollbar, area)
|
|
196
|
+
end
|
|
118
197
|
end
|
|
119
198
|
|
|
120
199
|
def build_message_lines(tui)
|
|
@@ -126,29 +205,38 @@ module TUI
|
|
|
126
205
|
end
|
|
127
206
|
|
|
128
207
|
label = ROLE_LABELS.fetch(msg[:role], msg[:role])
|
|
208
|
+
content_lines = msg[:content].to_s.split("\n", -1)
|
|
129
209
|
|
|
130
|
-
[
|
|
131
|
-
tui.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
]
|
|
210
|
+
lines = [tui.line(spans: [
|
|
211
|
+
tui.span(content: "#{label}: ", style: role_style),
|
|
212
|
+
tui.span(content: content_lines.first.to_s)
|
|
213
|
+
])]
|
|
214
|
+
content_lines.drop(1).each { |text| lines << tui.line(spans: [tui.span(content: text)]) }
|
|
215
|
+
lines << tui.line(spans: [tui.span(content: "")])
|
|
137
216
|
end
|
|
138
217
|
end
|
|
139
218
|
|
|
140
219
|
def render_input(frame, area, tui)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
220
|
+
disabled = @loading || !connected?
|
|
221
|
+
cursor = disabled ? "" : "\u2588"
|
|
222
|
+
border_style = disabled ? {fg: "dark_gray"} : {fg: "green"}
|
|
223
|
+
text_style = disabled ? tui.style(fg: "dark_gray") : tui.style(fg: "white")
|
|
224
|
+
|
|
225
|
+
title = if @loading
|
|
226
|
+
"Waiting..."
|
|
227
|
+
elsif !connected?
|
|
228
|
+
"Disconnected"
|
|
229
|
+
else
|
|
230
|
+
"Input"
|
|
231
|
+
end
|
|
144
232
|
|
|
145
233
|
widget = tui.paragraph(
|
|
146
234
|
text: tui.line(spans: [
|
|
147
235
|
tui.span(content: "> #{@input}#{cursor}", style: text_style)
|
|
148
236
|
]),
|
|
149
237
|
block: tui.block(
|
|
150
|
-
title:
|
|
151
|
-
titles:
|
|
238
|
+
title: title,
|
|
239
|
+
titles: disabled ? [] : [
|
|
152
240
|
{content: "Enter send", position: :bottom, alignment: :center}
|
|
153
241
|
],
|
|
154
242
|
borders: [:all],
|
|
@@ -162,42 +250,65 @@ module TUI
|
|
|
162
250
|
def submit_message
|
|
163
251
|
text = @input.strip
|
|
164
252
|
return if text.empty?
|
|
253
|
+
return unless @cable_client.status == :subscribed
|
|
165
254
|
|
|
166
|
-
Events::Bus.emit(Events::UserMessage.new(content: text, session_id: @session.id))
|
|
167
255
|
@input = ""
|
|
168
|
-
@
|
|
169
|
-
|
|
170
|
-
@submit_thread = Thread.new do
|
|
171
|
-
@client ||= LLM::Client.new
|
|
172
|
-
@registry ||= build_tool_registry
|
|
173
|
-
viewport_messages = @session.messages_for_llm
|
|
174
|
-
response = @client.chat_with_tools(
|
|
175
|
-
viewport_messages,
|
|
176
|
-
registry: @registry,
|
|
177
|
-
session_id: @session.id
|
|
178
|
-
)
|
|
179
|
-
Events::Bus.emit(Events::AgentMessage.new(content: response, session_id: @session.id))
|
|
180
|
-
rescue => e
|
|
181
|
-
Events::Bus.emit(Events::AgentMessage.new(content: "Error: #{e.message}", session_id: @session.id))
|
|
182
|
-
ensure
|
|
183
|
-
@loading = false
|
|
184
|
-
end
|
|
256
|
+
@cable_client.speak(text)
|
|
185
257
|
end
|
|
186
258
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
registry.register(Tools::Bash)
|
|
191
|
-
registry
|
|
259
|
+
# @return [Boolean] whether the event is an arrow or page key used for scrolling
|
|
260
|
+
def scroll_key?(event)
|
|
261
|
+
event.up? || event.down? || event.page_up? || event.page_down?
|
|
192
262
|
end
|
|
193
263
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
264
|
+
# Dispatches scroll key events to {#scroll_up} or {#scroll_down}
|
|
265
|
+
# @return [true] always redraws after scrolling
|
|
266
|
+
def handle_scroll_key(event)
|
|
267
|
+
if event.up?
|
|
268
|
+
scroll_up(SCROLL_STEP)
|
|
269
|
+
elsif event.down?
|
|
270
|
+
scroll_down(SCROLL_STEP)
|
|
271
|
+
elsif event.page_up?
|
|
272
|
+
scroll_up(@visible_height)
|
|
273
|
+
elsif event.page_down?
|
|
274
|
+
scroll_down(@visible_height)
|
|
200
275
|
end
|
|
276
|
+
true
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Handles mouse wheel scroll events; ignores other mouse events
|
|
280
|
+
# @return [Boolean] true if the event was a scroll wheel event
|
|
281
|
+
def handle_mouse_event(event)
|
|
282
|
+
if event.scroll_up?
|
|
283
|
+
scroll_up(MOUSE_SCROLL_STEP)
|
|
284
|
+
true
|
|
285
|
+
elsif event.scroll_down?
|
|
286
|
+
scroll_down(MOUSE_SCROLL_STEP)
|
|
287
|
+
true
|
|
288
|
+
else
|
|
289
|
+
false
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Scrolls the viewport up, clamping at the top.
|
|
294
|
+
# Disables auto-scroll when the user moves away from the bottom.
|
|
295
|
+
# @param lines [Integer] number of lines to scroll
|
|
296
|
+
def scroll_up(lines)
|
|
297
|
+
@scroll_offset = [@scroll_offset - lines, 0].max
|
|
298
|
+
@auto_scroll = @scroll_offset >= @max_scroll
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Scrolls the viewport down, clamping at max_scroll.
|
|
302
|
+
# Re-enables auto-scroll when the user reaches the bottom.
|
|
303
|
+
# @param lines [Integer] number of lines to scroll
|
|
304
|
+
def scroll_down(lines)
|
|
305
|
+
@scroll_offset = [@scroll_offset + lines, @max_scroll].min
|
|
306
|
+
@auto_scroll = @scroll_offset >= @max_scroll
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# @return [Boolean] true when WebSocket is fully subscribed and ready
|
|
310
|
+
def connected?
|
|
311
|
+
@cable_client.status == :subscribed
|
|
201
312
|
end
|
|
202
313
|
|
|
203
314
|
def printable_char?(event)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: anima-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yevhenii Hurin
|
|
@@ -9,6 +9,20 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: foreman
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.88'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.88'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: httparty
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -23,6 +37,20 @@ dependencies:
|
|
|
23
37
|
- - "~>"
|
|
24
38
|
- !ruby/object:Gem::Version
|
|
25
39
|
version: '0.24'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: puma
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '6.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '6.0'
|
|
26
54
|
- !ruby/object:Gem::Dependency
|
|
27
55
|
name: rails
|
|
28
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -51,6 +79,20 @@ dependencies:
|
|
|
51
79
|
- - "~>"
|
|
52
80
|
- !ruby/object:Gem::Version
|
|
53
81
|
version: '1.4'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: solid_cable
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.0'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.0'
|
|
54
96
|
- !ruby/object:Gem::Dependency
|
|
55
97
|
name: solid_queue
|
|
56
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -79,6 +121,20 @@ dependencies:
|
|
|
79
121
|
- - "~>"
|
|
80
122
|
- !ruby/object:Gem::Version
|
|
81
123
|
version: '2.0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: websocket-client-simple
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0.8'
|
|
131
|
+
type: :runtime
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0.8'
|
|
82
138
|
email:
|
|
83
139
|
- evgeny.gurin@gmail.com
|
|
84
140
|
executables:
|
|
@@ -89,25 +145,43 @@ files:
|
|
|
89
145
|
- ".mise.toml"
|
|
90
146
|
- ".reek.yml"
|
|
91
147
|
- CHANGELOG.md
|
|
148
|
+
- Gemfile
|
|
92
149
|
- LICENSE.txt
|
|
150
|
+
- Procfile
|
|
151
|
+
- Procfile.dev
|
|
93
152
|
- README.md
|
|
94
153
|
- Rakefile
|
|
154
|
+
- anima-core.gemspec
|
|
155
|
+
- app/channels/application_cable/channel.rb
|
|
156
|
+
- app/channels/application_cable/connection.rb
|
|
157
|
+
- app/channels/session_channel.rb
|
|
158
|
+
- app/controllers/api/sessions_controller.rb
|
|
159
|
+
- app/controllers/application_controller.rb
|
|
160
|
+
- app/jobs/agent_request_job.rb
|
|
95
161
|
- app/jobs/application_job.rb
|
|
96
162
|
- app/jobs/count_event_tokens_job.rb
|
|
97
163
|
- app/models/application_record.rb
|
|
98
164
|
- app/models/event.rb
|
|
99
165
|
- app/models/session.rb
|
|
166
|
+
- bin/jobs
|
|
167
|
+
- bin/rails
|
|
168
|
+
- bin/rake
|
|
169
|
+
- config.ru
|
|
100
170
|
- config/application.rb
|
|
101
171
|
- config/boot.rb
|
|
172
|
+
- config/cable.yml
|
|
102
173
|
- config/database.yml
|
|
103
174
|
- config/environment.rb
|
|
104
175
|
- config/environments/development.rb
|
|
105
176
|
- config/environments/production.rb
|
|
106
177
|
- config/environments/test.rb
|
|
178
|
+
- config/initializers/event_subscribers.rb
|
|
107
179
|
- config/initializers/inflections.rb
|
|
180
|
+
- config/puma.rb
|
|
108
181
|
- config/queue.yml
|
|
109
182
|
- config/recurring.yml
|
|
110
183
|
- config/routes.rb
|
|
184
|
+
- db/cable_schema.rb
|
|
111
185
|
- db/migrate/.keep
|
|
112
186
|
- db/migrate/20260308124202_create_sessions.rb
|
|
113
187
|
- db/migrate/20260308124203_create_events.rb
|
|
@@ -118,6 +192,7 @@ files:
|
|
|
118
192
|
- db/queue_schema.rb
|
|
119
193
|
- db/seeds.rb
|
|
120
194
|
- exe/anima
|
|
195
|
+
- lib/agent_loop.rb
|
|
121
196
|
- lib/anima.rb
|
|
122
197
|
- lib/anima/cli.rb
|
|
123
198
|
- lib/anima/installer.rb
|
|
@@ -126,6 +201,7 @@ files:
|
|
|
126
201
|
- lib/events/base.rb
|
|
127
202
|
- lib/events/bus.rb
|
|
128
203
|
- lib/events/subscriber.rb
|
|
204
|
+
- lib/events/subscribers/action_cable_bridge.rb
|
|
129
205
|
- lib/events/subscribers/message_collector.rb
|
|
130
206
|
- lib/events/subscribers/persister.rb
|
|
131
207
|
- lib/events/system_message.rb
|
|
@@ -140,6 +216,8 @@ files:
|
|
|
140
216
|
- lib/tools/registry.rb
|
|
141
217
|
- lib/tools/web_get.rb
|
|
142
218
|
- lib/tui/app.rb
|
|
219
|
+
- lib/tui/cable_client.rb
|
|
220
|
+
- lib/tui/message_store.rb
|
|
143
221
|
- lib/tui/screens/anthropic.rb
|
|
144
222
|
- lib/tui/screens/chat.rb
|
|
145
223
|
- lib/tui/screens/settings.rb
|
|
@@ -166,6 +244,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
166
244
|
requirements: []
|
|
167
245
|
rubygems_version: 3.6.9
|
|
168
246
|
specification_version: 4
|
|
169
|
-
summary:
|
|
170
|
-
growth
|
|
247
|
+
summary: A personal AI agent with desires, personality, and personal growth
|
|
171
248
|
test_files: []
|