anima-core 0.2.1 → 1.0.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 +27 -1
- data/CHANGELOG.md +19 -0
- data/README.md +213 -43
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +195 -45
- data/app/decorators/user_message_decorator.rb +16 -5
- data/app/jobs/agent_request_job.rb +55 -2
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +15 -4
- data/app/models/concerns/event/broadcasting.rb +81 -0
- data/app/models/event.rb +20 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +366 -21
- data/config/application.rb +2 -0
- data/config/initializers/event_subscribers.rb +0 -1
- data/config/routes.rb +0 -6
- data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
- data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -6
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +5 -40
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/events/subscribers/persister.rb +1 -0
- data/lib/events/user_message.rb +17 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +11 -20
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +226 -0
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +151 -0
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tools/write.rb +86 -0
- data/lib/tui/app.rb +985 -26
- data/lib/tui/cable_client.rb +69 -31
- data/lib/tui/message_store.rb +103 -8
- data/lib/tui/screens/chat.rb +293 -45
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +290 -3
- data/app/controllers/api/sessions_controller.rb +0 -25
- data/lib/events/subscribers/action_cable_bridge.rb +0 -59
|
@@ -12,17 +12,28 @@ class CountEventTokensJob < ApplicationJob
|
|
|
12
12
|
# @param event_id [Integer] the Event record to count tokens for
|
|
13
13
|
def perform(event_id)
|
|
14
14
|
event = Event.find(event_id)
|
|
15
|
-
return if event
|
|
15
|
+
return if already_counted?(event)
|
|
16
16
|
|
|
17
17
|
provider = Providers::Anthropic.new
|
|
18
18
|
messages = [{role: event.api_role, content: event.payload["content"].to_s}]
|
|
19
19
|
|
|
20
20
|
token_count = provider.count_tokens(
|
|
21
|
-
model:
|
|
21
|
+
model: Anima::Settings.model,
|
|
22
22
|
messages: messages
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
#
|
|
26
|
-
|
|
25
|
+
# Guard against parallel jobs: reload and re-check before writing.
|
|
26
|
+
# Uses update! (not update_all) so {Event::Broadcasting} after_update_commit
|
|
27
|
+
# broadcasts the updated token count to connected clients.
|
|
28
|
+
event.reload
|
|
29
|
+
return if already_counted?(event)
|
|
30
|
+
|
|
31
|
+
event.update!(token_count: token_count)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def already_counted?(event)
|
|
37
|
+
event.token_count > 0
|
|
27
38
|
end
|
|
28
39
|
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Broadcasts Event records to connected WebSocket clients via ActionCable.
|
|
4
|
+
# Follows the Turbo Streams pattern: events are broadcast on both create
|
|
5
|
+
# and update, with an action type so clients can distinguish append from
|
|
6
|
+
# replace operations.
|
|
7
|
+
#
|
|
8
|
+
# Each broadcast includes the Event's database ID, enabling clients to
|
|
9
|
+
# maintain an ID-indexed store for efficient in-place updates (e.g. when
|
|
10
|
+
# token counts arrive asynchronously from {CountEventTokensJob}).
|
|
11
|
+
#
|
|
12
|
+
# When a new event pushes old events out of the LLM's context window,
|
|
13
|
+
# the broadcast includes `evicted_event_ids` so clients can remove
|
|
14
|
+
# phantom messages that the agent no longer knows about.
|
|
15
|
+
#
|
|
16
|
+
# @example Create broadcast payload
|
|
17
|
+
# {
|
|
18
|
+
# "type" => "user_message", "content" => "hello", ...,
|
|
19
|
+
# "id" => 42, "action" => "create",
|
|
20
|
+
# "rendered" => { "basic" => { "role" => "user", "content" => "hello" } }
|
|
21
|
+
# }
|
|
22
|
+
#
|
|
23
|
+
# @example Broadcast with viewport evictions
|
|
24
|
+
# {
|
|
25
|
+
# "type" => "agent_message", "content" => "...", ...,
|
|
26
|
+
# "id" => 99, "action" => "create",
|
|
27
|
+
# "evicted_event_ids" => [101, 102, 103]
|
|
28
|
+
# }
|
|
29
|
+
#
|
|
30
|
+
# @example Update broadcast payload (e.g. token count arrives)
|
|
31
|
+
# {
|
|
32
|
+
# "type" => "user_message", "content" => "hello", ...,
|
|
33
|
+
# "id" => 42, "action" => "update",
|
|
34
|
+
# "rendered" => { "debug" => { "role" => "user", "content" => "hello", "tokens" => 15 } }
|
|
35
|
+
# }
|
|
36
|
+
module Event::Broadcasting
|
|
37
|
+
extend ActiveSupport::Concern
|
|
38
|
+
|
|
39
|
+
ACTION_CREATE = "create"
|
|
40
|
+
ACTION_UPDATE = "update"
|
|
41
|
+
|
|
42
|
+
included do
|
|
43
|
+
after_create_commit :broadcast_create
|
|
44
|
+
after_update_commit :broadcast_update
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def broadcast_create
|
|
50
|
+
broadcast_event(action: ACTION_CREATE)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def broadcast_update
|
|
54
|
+
broadcast_event(action: ACTION_UPDATE)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Decorates the event for the session's current view mode and broadcasts
|
|
58
|
+
# the payload to the session's ActionCable stream. Includes viewport
|
|
59
|
+
# eviction metadata so clients can remove messages the LLM has forgotten.
|
|
60
|
+
#
|
|
61
|
+
# @param action [String] ACTION_CREATE or ACTION_UPDATE — tells clients how to handle the event
|
|
62
|
+
def broadcast_event(action:)
|
|
63
|
+
return unless session_id
|
|
64
|
+
|
|
65
|
+
session = Session.find_by(id: session_id)
|
|
66
|
+
return unless session
|
|
67
|
+
|
|
68
|
+
mode = session.view_mode
|
|
69
|
+
decorator = EventDecorator.for(self)
|
|
70
|
+
broadcast_payload = payload.merge("id" => id, "action" => action)
|
|
71
|
+
|
|
72
|
+
if decorator
|
|
73
|
+
broadcast_payload["rendered"] = {mode => decorator.render(mode)}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
evicted_ids = session.recalculate_viewport!
|
|
77
|
+
broadcast_payload["evicted_event_ids"] = evicted_ids if evicted_ids.any?
|
|
78
|
+
|
|
79
|
+
ActionCable.server.broadcast("session_#{session_id}", broadcast_payload)
|
|
80
|
+
end
|
|
81
|
+
end
|
data/app/models/event.rb
CHANGED
|
@@ -16,9 +16,12 @@
|
|
|
16
16
|
# @!attribute tool_use_id
|
|
17
17
|
# @return [String, nil] Anthropic-assigned ID correlating tool_call and tool_response
|
|
18
18
|
class Event < ApplicationRecord
|
|
19
|
+
include Event::Broadcasting
|
|
20
|
+
|
|
19
21
|
TYPES = %w[system_message user_message agent_message tool_call tool_response].freeze
|
|
20
22
|
LLM_TYPES = %w[user_message agent_message].freeze
|
|
21
|
-
CONTEXT_TYPES = %w[user_message agent_message tool_call tool_response].freeze
|
|
23
|
+
CONTEXT_TYPES = %w[system_message user_message agent_message tool_call tool_response].freeze
|
|
24
|
+
PENDING_STATUS = "pending"
|
|
22
25
|
|
|
23
26
|
ROLE_MAP = {"user_message" => "user", "agent_message" => "assistant"}.freeze
|
|
24
27
|
|
|
@@ -43,6 +46,17 @@ class Event < ApplicationRecord
|
|
|
43
46
|
# @return [ActiveRecord::Relation]
|
|
44
47
|
scope :context_events, -> { where(event_type: CONTEXT_TYPES) }
|
|
45
48
|
|
|
49
|
+
# @!method self.pending
|
|
50
|
+
# User messages queued during active agent processing, not yet sent to LLM.
|
|
51
|
+
# @return [ActiveRecord::Relation]
|
|
52
|
+
scope :pending, -> { where(status: PENDING_STATUS) }
|
|
53
|
+
|
|
54
|
+
# @!method self.deliverable
|
|
55
|
+
# Events eligible for LLM context (excludes pending messages).
|
|
56
|
+
# NULL status means delivered/processed — the only excluded value is "pending".
|
|
57
|
+
# @return [ActiveRecord::Relation]
|
|
58
|
+
scope :deliverable, -> { where(status: nil) }
|
|
59
|
+
|
|
46
60
|
# Maps event_type to the Anthropic Messages API role.
|
|
47
61
|
# @return [String] "user" or "assistant"
|
|
48
62
|
def api_role
|
|
@@ -59,6 +73,11 @@ class Event < ApplicationRecord
|
|
|
59
73
|
event_type.in?(CONTEXT_TYPES)
|
|
60
74
|
end
|
|
61
75
|
|
|
76
|
+
# @return [Boolean] true if this is a pending message not yet sent to the LLM
|
|
77
|
+
def pending?
|
|
78
|
+
status == PENDING_STATUS
|
|
79
|
+
end
|
|
80
|
+
|
|
62
81
|
# Heuristic token estimate: ~4 bytes per token for English prose.
|
|
63
82
|
# Tool events are estimated from the full payload JSON since tool_input
|
|
64
83
|
# and tool metadata contribute to token count. Messages use content only.
|
data/app/models/goal.rb
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# A persistent objective tracked by the analytical brain during a session.
|
|
4
|
+
# Goals form a two-level hierarchy: root goals represent high-level
|
|
5
|
+
# objectives (semantic episodes), while sub-goals are TODO-style steps
|
|
6
|
+
# rendered as checklist items in the agent's system prompt.
|
|
7
|
+
#
|
|
8
|
+
# The analytical brain creates and completes goals; the main agent sees
|
|
9
|
+
# them in its context window but never manages them directly.
|
|
10
|
+
class Goal < ApplicationRecord
|
|
11
|
+
STATUSES = %w[active completed].freeze
|
|
12
|
+
|
|
13
|
+
belongs_to :session
|
|
14
|
+
belongs_to :parent_goal, class_name: "Goal", optional: true
|
|
15
|
+
has_many :sub_goals, -> { order(:created_at) }, class_name: "Goal", foreign_key: :parent_goal_id, dependent: :destroy
|
|
16
|
+
|
|
17
|
+
validates :description, presence: true
|
|
18
|
+
validates :status, inclusion: {in: STATUSES}
|
|
19
|
+
validate :parent_goal_belongs_to_same_session, if: :parent_goal
|
|
20
|
+
validate :parent_goal_is_root, if: :parent_goal
|
|
21
|
+
|
|
22
|
+
scope :active, -> { where(status: "active") }
|
|
23
|
+
scope :completed, -> { where(status: "completed") }
|
|
24
|
+
scope :root, -> { where(parent_goal_id: nil) }
|
|
25
|
+
|
|
26
|
+
after_commit :broadcast_goals_update
|
|
27
|
+
|
|
28
|
+
# @return [Boolean] true if this goal has been completed
|
|
29
|
+
def completed? = status == "completed"
|
|
30
|
+
|
|
31
|
+
# @return [Boolean] true if this is a root goal (no parent)
|
|
32
|
+
def root? = !parent_goal_id
|
|
33
|
+
|
|
34
|
+
# Cascades completion to all active sub-goals. Called when a root goal
|
|
35
|
+
# is finished — remaining sub-items are implicitly resolved because
|
|
36
|
+
# the semantic episode that spawned them has ended.
|
|
37
|
+
#
|
|
38
|
+
# Uses +update_all+ to avoid N per-record +after_commit+ broadcasts;
|
|
39
|
+
# the caller ({AnalyticalBrain::Tools::FinishGoal}) wraps the whole
|
|
40
|
+
# operation in a transaction so the root goal's single broadcast
|
|
41
|
+
# includes the cascaded state.
|
|
42
|
+
#
|
|
43
|
+
# @return [void]
|
|
44
|
+
def cascade_completion!
|
|
45
|
+
now = Time.current
|
|
46
|
+
sub_goals.active.update_all(status: "completed", completed_at: now, updated_at: now)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Serializes this goal for ActionCable broadcast and TUI display.
|
|
50
|
+
# Includes nested sub-goals for root goals.
|
|
51
|
+
#
|
|
52
|
+
# @return [Hash{String => Object}] with keys "id", "description", "status",
|
|
53
|
+
# and "sub_goals" (Array of Hash with "id", "description", "status")
|
|
54
|
+
def as_summary
|
|
55
|
+
{
|
|
56
|
+
"id" => id,
|
|
57
|
+
"description" => description,
|
|
58
|
+
"status" => status,
|
|
59
|
+
"sub_goals" => sub_goals.map { |sub|
|
|
60
|
+
{"id" => sub.id, "description" => sub.description, "status" => sub.status}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def parent_goal_belongs_to_same_session
|
|
68
|
+
return if parent_goal.session_id == session_id
|
|
69
|
+
|
|
70
|
+
errors.add(:parent_goal, "must belong to the same session")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parent_goal_is_root
|
|
74
|
+
return unless parent_goal.parent_goal_id
|
|
75
|
+
|
|
76
|
+
errors.add(:parent_goal, "cannot nest deeper than two levels")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Broadcasts goal changes to all clients subscribed to this session.
|
|
80
|
+
# Mirrors the Session#broadcast_active_skills_update pattern so the
|
|
81
|
+
# TUI info panel updates reactively.
|
|
82
|
+
#
|
|
83
|
+
# @return [void]
|
|
84
|
+
def broadcast_goals_update
|
|
85
|
+
ActionCable.server.broadcast("session_#{session_id}", {
|
|
86
|
+
"action" => "goals_updated",
|
|
87
|
+
"session_id" => session_id,
|
|
88
|
+
"goals" => session.goals_summary
|
|
89
|
+
})
|
|
90
|
+
end
|
|
91
|
+
end
|