actionagent 0.0.0 → 1.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/README.md +103 -0
  4. data/app/assets/builds/action_agent.css +2 -0
  5. data/app/assets/builds/action_agent.js +163 -0
  6. data/app/controllers/action_agent/api/agent_runs_controller.rb +128 -0
  7. data/app/controllers/action_agent/api/agents_controller.rb +411 -0
  8. data/app/controllers/action_agent/api/analytics_controller.rb +94 -0
  9. data/app/controllers/action_agent/api/api_keys_controller.rb +45 -0
  10. data/app/controllers/action_agent/api/base_controller.rb +112 -0
  11. data/app/controllers/action_agent/api/evaluations_controller.rb +148 -0
  12. data/app/controllers/action_agent/api/instance_tiers_controller.rb +106 -0
  13. data/app/controllers/action_agent/api/interactions_controller.rb +197 -0
  14. data/app/controllers/action_agent/api/mcp_controller.rb +218 -0
  15. data/app/controllers/action_agent/api/mcp_servers_controller.rb +156 -0
  16. data/app/controllers/action_agent/api/metrics_controller.rb +178 -0
  17. data/app/controllers/action_agent/api/provider_keys_controller.rb +52 -0
  18. data/app/controllers/action_agent/api/provider_models_controller.rb +119 -0
  19. data/app/controllers/action_agent/api/sandboxes_controller.rb +224 -0
  20. data/app/controllers/action_agent/api/session_recordings_controller.rb +372 -0
  21. data/app/controllers/action_agent/api/templates_controller.rb +94 -0
  22. data/app/controllers/action_agent/api/tools_controller.rb +58 -0
  23. data/app/controllers/action_agent/api/trace_reports_controller.rb +68 -0
  24. data/app/controllers/action_agent/api/traces_controller.rb +136 -0
  25. data/app/controllers/action_agent/application_controller.rb +105 -0
  26. data/app/controllers/action_agent/dashboard_controller.rb +104 -0
  27. data/app/controllers/action_agent/traces_controller.rb +121 -0
  28. data/app/jobs/action_agent/agent_execution_job.rb +52 -0
  29. data/app/jobs/action_agent/application_job.rb +12 -0
  30. data/app/jobs/action_agent/process_telemetry_traces_job.rb +86 -0
  31. data/app/jobs/action_agent/sandbox_cleanup_job.rb +42 -0
  32. data/app/jobs/action_agent/sandbox_provision_job.rb +56 -0
  33. data/app/jobs/action_agent/sandbox_run_job.rb +285 -0
  34. data/app/jobs/action_agent/trace_retention_job.rb +57 -0
  35. data/app/models/action_agent/agent.rb +343 -0
  36. data/app/models/action_agent/agent_context.rb +129 -0
  37. data/app/models/action_agent/agent_generation.rb +48 -0
  38. data/app/models/action_agent/agent_memory.rb +50 -0
  39. data/app/models/action_agent/agent_memory_entry.rb +14 -0
  40. data/app/models/action_agent/agent_message.rb +43 -0
  41. data/app/models/action_agent/agent_run.rb +151 -0
  42. data/app/models/action_agent/agent_template.rb +182 -0
  43. data/app/models/action_agent/agent_version.rb +48 -0
  44. data/app/models/action_agent/api_key.rb +53 -0
  45. data/app/models/action_agent/application_record.rb +27 -0
  46. data/app/models/action_agent/evaluation.rb +80 -0
  47. data/app/models/action_agent/evaluation_run.rb +20 -0
  48. data/app/models/action_agent/model_pricing.rb +80 -0
  49. data/app/models/action_agent/provider_key.rb +60 -0
  50. data/app/models/action_agent/recording_action.rb +119 -0
  51. data/app/models/action_agent/recording_snapshot.rb +88 -0
  52. data/app/models/action_agent/sandbox_instance_tier.rb +368 -0
  53. data/app/models/action_agent/sandbox_run.rb +45 -0
  54. data/app/models/action_agent/sandbox_session.rb +160 -0
  55. data/app/models/action_agent/session_recording.rb +178 -0
  56. data/app/models/action_agent/telemetry_trace.rb +357 -0
  57. data/app/models/concerns/action_agent/adapter_aware.rb +50 -0
  58. data/app/models/concerns/action_agent/ownable.rb +86 -0
  59. data/app/models/concerns/action_agent/session_recordable.rb +91 -0
  60. data/app/queries/action_agent/agent_executions.rb +201 -0
  61. data/app/serializers/action_agent/agent_message_serializer.rb +23 -0
  62. data/app/serializers/action_agent/interaction_preview.rb +22 -0
  63. data/app/serializers/action_agent/telemetry_trace_serializer.rb +122 -0
  64. data/app/serializers/action_agent/trace_interaction_serializer.rb +246 -0
  65. data/app/services/action_agent/agent_execution_service.rb +572 -0
  66. data/app/services/action_agent/agent_registrar.rb +197 -0
  67. data/app/services/action_agent/agent_scorecard.rb +191 -0
  68. data/app/services/action_agent/agent_toolbox.rb +504 -0
  69. data/app/services/action_agent/evaluation_runner_service.rb +481 -0
  70. data/app/services/action_agent/mcp_catalog.rb +247 -0
  71. data/app/services/action_agent/mcp_recording_middleware.rb +241 -0
  72. data/app/services/action_agent/mock_sandbox_backend.rb +52 -0
  73. data/app/services/action_agent/playwright_mcp_client.rb +148 -0
  74. data/app/services/action_agent/sandbox_orchestrator.rb +242 -0
  75. data/app/services/action_agent/session_recording_service.rb +228 -0
  76. data/app/services/action_agent/tool_discovery.rb +617 -0
  77. data/app/views/action_agent/dashboard/index.html.erb +5 -0
  78. data/app/views/action_agent/traces/_trace_detail.html.erb +117 -0
  79. data/app/views/action_agent/traces/index.html.erb +135 -0
  80. data/app/views/action_agent/traces/metrics.html.erb +145 -0
  81. data/app/views/action_agent/traces/show.html.erb +36 -0
  82. data/app/views/layouts/action_agent/application.html.erb +94 -0
  83. data/app/views/layouts/action_agent/react.html.erb +19 -0
  84. data/config/routes.rb +144 -0
  85. data/lib/action_agent/compatibility.rb +49 -0
  86. data/lib/action_agent/engine.rb +51 -0
  87. data/lib/action_agent/version.rb +5 -0
  88. data/lib/action_agent.rb +388 -0
  89. data/lib/actionagent.rb +6 -0
  90. data/lib/generators/action_agent/install_generator.rb +137 -0
  91. data/lib/generators/action_agent/templates/action_agent.rb.erb +82 -0
  92. data/lib/generators/action_agent/templates/add_agent_id_to_active_agent_telemetry_traces.rb.erb +24 -0
  93. data/lib/generators/action_agent/templates/create_active_agent_dashboard_tables.rb.erb +319 -0
  94. data/lib/generators/action_agent/templates/create_active_agent_telemetry_traces.rb.erb +58 -0
  95. metadata +209 -12
data/config/routes.rb ADDED
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActionAgent::Engine.routes.draw do
4
+ # The React dashboard's entry point. Its own paths (/traces, /metrics,
5
+ # /agents/1/edit, ...) are matched by the catch-all at the bottom of this
6
+ # file, so deep links and the browser's back button both work wherever the
7
+ # engine is mounted.
8
+ root to: "dashboard#index"
9
+
10
+ # The server-rendered console. Same data, no JavaScript — useful when the
11
+ # bundle can't run, and the surface the dashboard shipped with before the
12
+ # React app moved into the engine.
13
+ scope :console do
14
+ resources :traces, only: [ :index, :show ], controller: "traces" do
15
+ collection do
16
+ get :metrics
17
+ end
18
+ end
19
+ end
20
+
21
+ # The dashboard's own JSON API, read and written by the React app.
22
+ namespace :api do
23
+ # Telemetry ingestion, relative to wherever the engine is mounted:
24
+ # <mount>/api/traces (e.g. /activeagents/api/traces at the default mount).
25
+ # Authenticated with a bearer token, not a session.
26
+ resources :traces, only: [ :create ]
27
+
28
+ # A JSON API has no :new or :edit forms to serve.
29
+ resources :agents, except: [ :new, :edit ] do
30
+ member do
31
+ get :versions
32
+ post :restore
33
+ get :runs
34
+ post :execute
35
+ post :test
36
+ post :duplicate
37
+ get :export
38
+ get :analytics
39
+ end
40
+ collection do
41
+ get :presets
42
+ end
43
+ end
44
+
45
+ resources :templates, only: [ :index, :show ] do
46
+ member do
47
+ post :use
48
+ end
49
+ end
50
+
51
+ resources :runs, controller: "agent_runs", only: [ :index, :show ] do
52
+ member do
53
+ post :cancel
54
+ end
55
+ end
56
+
57
+ # Sandboxes. The engine ships the in-memory backend; an operator registers
58
+ # real ones (see ActionAgent.sandbox_backends).
59
+ resources :sandboxes, param: :id, only: [ :index, :create, :show, :destroy ] do
60
+ collection do
61
+ post :compare
62
+ end
63
+ member do
64
+ post :run
65
+ end
66
+ end
67
+
68
+ # Tool inventory — auto-detected from the tool roster each generation
69
+ # request offered, telemetry tool spans, and solid_agent records.
70
+ resources :tools, only: [ :index ]
71
+
72
+ # MCP services — detected servers unioned with the default catalog
73
+ # (McpCatalog), plus on-demand sandbox provisioning. Keys are catalog
74
+ # slugs like "sequential-thinking", so the id segment allows dashes.
75
+ resources :mcp_servers, only: [ :index, :show ], id: /[^\/]+/ do
76
+ member do
77
+ post :launch
78
+ end
79
+ end
80
+
81
+ resources :instance_tiers, only: [ :index, :show ] do
82
+ collection do
83
+ get :recommend
84
+ get :pricing
85
+ end
86
+ end
87
+
88
+ resources :session_recordings, only: [ :index, :show, :destroy ] do
89
+ member do
90
+ get :actions
91
+ get "snapshot/:action_id", action: :snapshot, as: :snapshot
92
+ post :export
93
+ post :handoff
94
+ post :record_action
95
+ post :complete, action: :complete_session
96
+ end
97
+ collection do
98
+ get :recent
99
+ post :start_user_session
100
+ end
101
+ end
102
+
103
+ resource :analytics, only: [], controller: "analytics" do
104
+ get "/", action: :index
105
+ end
106
+
107
+ # Reading traces is the same path as ingesting them, separated by verb:
108
+ # POST is the SDK's authenticated-by-token ingest above, GET is the
109
+ # dashboard's session-authenticated read.
110
+ resources :traces, only: [ :index, :show ], controller: "trace_reports", as: :trace_reports
111
+ resource :metrics, only: [ :show ], controller: "metrics"
112
+
113
+ # Conversations (contexts, messages, generations) behind Interactions.
114
+ resources :interactions, only: [ :index, :show ]
115
+
116
+ # Agent output evaluations.
117
+ resources :evaluations, only: [ :index, :show, :create, :destroy ] do
118
+ member do
119
+ post :run
120
+ end
121
+ end
122
+
123
+ # Credentials: dashboard API keys (token shown once on create) and the
124
+ # owner's own LLM provider credentials, both encrypted at rest.
125
+ resources :api_keys, only: [ :index, :create, :destroy ]
126
+ resources :provider_keys, only: [ :index, :create, :destroy ], param: :provider
127
+
128
+ # Model catalogs for the agent builder (Ollama queried live from the
129
+ # configured host; hosted providers curated).
130
+ resources :provider_models, only: [ :index ]
131
+ end
132
+
133
+ # The account's agents presented as an authenticated MCP server (tools +
134
+ # agent:// resources) over Streamable HTTP JSON-RPC. Authenticated with a
135
+ # dashboard API key rather than a session, so it sits outside the api
136
+ # namespace's session-authenticated controllers.
137
+ post "mcp", to: "api/mcp#create"
138
+
139
+ # Everything else under the mount is a client-side route: render the
140
+ # dashboard and let the browser resolve it. Anchored last so it can only
141
+ # ever catch what the routes above did not, and refuses /api paths so a
142
+ # mistyped endpoint answers as an API would rather than returning a page.
143
+ get "*path", to: "dashboard#index", constraints: ->(request) { !request.path.include?("/api/") }
144
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Transitional aliases for the names this engine shipped under when it lived
4
+ # inside the activeagent gem (<= 1.1.0).
5
+ #
6
+ # Three of these matter for more than tidiness:
7
+ #
8
+ # * ActiveAgent::ProcessTelemetryTracesJob — Active Job serializes the class
9
+ # name into the queue payload, so a job enqueued before the upgrade is
10
+ # dequeued after it and must still resolve to something.
11
+ # * ActiveAgent::TelemetryTrace — apps subclass it (the activeagents.ai
12
+ # platform does) and it is referenced from initializers.
13
+ # * ActiveAgent::Dashboard — every existing initializer calls
14
+ # ActiveAgent::Dashboard.configure.
15
+ #
16
+ # Each warns once through the deprecator and forwards to the new constant.
17
+ # Remove in the next major.
18
+ module ActionAgent
19
+ module Compatibility
20
+ RENAMED = {
21
+ "Dashboard" => "ActionAgent",
22
+ "TelemetryTrace" => "ActionAgent::TelemetryTrace",
23
+ "ProcessTelemetryTracesJob" => "ActionAgent::ProcessTelemetryTracesJob"
24
+ }.freeze
25
+
26
+ # Installs const_missing on ActiveAgent so the old names keep resolving
27
+ # without eagerly loading the engine's models (which would drag Active
28
+ # Record in at boot, the very thing the gem split fixed).
29
+ def self.install!
30
+ return if @installed
31
+
32
+ ActiveAgent.singleton_class.prepend(ConstMissing)
33
+ @installed = true
34
+ end
35
+
36
+ module ConstMissing
37
+ def const_missing(name)
38
+ replacement = RENAMED[name.to_s]
39
+ return super unless replacement
40
+
41
+ ActionAgent.deprecator.warn(
42
+ "ActiveAgent::#{name} has moved to #{replacement}. " \
43
+ "The dashboard is now the actionagent gem; update your references."
44
+ )
45
+ replacement.constantize
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAgent
4
+ # Rails engine for the Active Agent dashboard: the agent builder, runs,
5
+ # conversations, evaluations, traces, metrics, sandboxes and session
6
+ # recordings, plus the trace ingest API and the MCP server facade.
7
+ #
8
+ # Mount it wherever you like:
9
+ # mount ActionAgent::Engine => "/agents"
10
+ #
11
+ class Engine < ::Rails::Engine
12
+ isolate_namespace ActionAgent
13
+
14
+ engine_name "action_agent"
15
+
16
+ config.action_agent = ActiveSupport::OrderedOptions.new
17
+
18
+ # The dashboard's JS and CSS ship prebuilt in the gem. Adding the
19
+ # directory to the host app's asset paths is what lets a plain
20
+ # `mount ActionAgent::Engine` work without the host running a
21
+ # JavaScript build — or having a JavaScript build at all.
22
+ # Provider credentials and API keys are posted to the dashboard in the
23
+ # clear and encrypted at rest — filtering keeps them out of the request
24
+ # logs in between, where the gem would otherwise print them verbatim.
25
+ initializer "action_agent.filter_parameters" do |app|
26
+ app.config.filter_parameters += [ :credential, :api_key, :access_token ]
27
+ end
28
+
29
+ initializer "action_agent.assets", before: :append_assets_path do |app|
30
+ builds = root.join("app", "assets", "builds").to_s
31
+ next unless File.directory?(builds)
32
+
33
+ next unless app.config.respond_to?(:assets)
34
+
35
+ if app.config.assets.respond_to?(:paths)
36
+ app.config.assets.paths << builds unless app.config.assets.paths.include?(builds)
37
+ end
38
+
39
+ # Sprockets serves only what it was told to precompile, and the host's
40
+ # manifest.js cannot know about an engine's bundles. Without this the
41
+ # layout's stylesheet_link_tag/javascript_include_tag raise
42
+ # AssetNotPrecompiled in development and AssetNotFound in production —
43
+ # i.e. the dashboard's only page 500s on every sprockets-rails host.
44
+ # Propshaft serves everything on the load path and has no precompile
45
+ # list, so the respond_to? check is what distinguishes them.
46
+ if app.config.assets.respond_to?(:precompile)
47
+ app.config.assets.precompile |= %w[action_agent.js action_agent.css]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAgent
4
+ VERSION = "1.2.0"
5
+ end
data/lib/action_agent.rb CHANGED
@@ -0,0 +1,388 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionAgent
4
+ class << self
5
+ # Table name prefix for the engine's models. The engine's own
6
+ # migrations create `active_agent_*` tables, so the default matches.
7
+ #
8
+ # A host app that already owns these tables under different names (the
9
+ # activeagents.ai platform grew them unprefixed) sets this to "" rather
10
+ # than renaming production tables. The engine's migrations read the same
11
+ # value, so the schema and the models never disagree.
12
+ #
13
+ # Defined before the engine is required on purpose: Rails' isolate_namespace
14
+ # installs its own table_name_prefix on an on_load(:active_record) hook
15
+ # unless the module already has one, and that hook would win over any
16
+ # definition made afterwards.
17
+ attr_writer :table_name_prefix
18
+
19
+ def table_name_prefix
20
+ global = defined?(::ActiveRecord::Base) ? ::ActiveRecord::Base.table_name_prefix : ""
21
+ "#{global}#{@table_name_prefix ||= "active_agent_"}"
22
+ end
23
+ end
24
+ end
25
+
26
+ # Both are hard requirements, and both must be loaded here rather than left to
27
+ # the host app's Gemfile. Bundler.require only requires the gems an app lists
28
+ # directly, so a transitive dependency is installed and activated but never
29
+ # loaded:
30
+ #
31
+ # * active_agent — Compatibility.install! below dereferences ::ActiveAgent at
32
+ # load time. An app whose Gemfile happens to list actionagent first (which
33
+ # RuboCop's Bundler/OrderedGems will produce, since it sorts before
34
+ # activeagent) would otherwise die at Bundler.require.
35
+ # * solid_agent — AgentExecutionService includes SolidAgent::HasContext in the
36
+ # agent class it builds for every run, so without this every Run fails with
37
+ # an uninitialized-constant error on any install that does not list the gem
38
+ # itself.
39
+ require "active_agent"
40
+ require "solid_agent"
41
+
42
+ require "action_agent/version"
43
+ require "action_agent/engine"
44
+ require "action_agent/compatibility"
45
+
46
+ # Dashboard engine for visualizing telemetry data and managing agents.
47
+ #
48
+ # Mount the engine in your routes to access the full dashboard:
49
+ #
50
+ # # config/routes.rb
51
+ # mount ActionAgent::Engine => "/activeagents"
52
+ #
53
+ # The dashboard provides:
54
+ # - Agent management: Create, edit, version, and execute agents
55
+ # - Traces view: See all agent invocations with spans, timing, and token usage
56
+ # - Metrics view: Aggregate statistics and charts
57
+ # - Sandbox execution: Run agents in isolated environments
58
+ # - Session recordings: Capture and replay browser sessions
59
+ #
60
+ # = Configuration Modes
61
+ #
62
+ # == Local Mode (default)
63
+ # For self-hosted, single-tenant deployments:
64
+ #
65
+ # ActionAgent.configure do |config|
66
+ # config.authentication_method = ->(controller) { controller.authenticate_admin! }
67
+ # config.sandbox_service = :local # Docker/Incus
68
+ # end
69
+ #
70
+ # == Multi-tenant Mode
71
+ # For SaaS platforms with multiple accounts:
72
+ #
73
+ # ActionAgent.configure do |config|
74
+ # config.multi_tenant = true
75
+ # config.account_class = "Account"
76
+ # config.user_class = "User"
77
+ # config.current_account_method = :current_account
78
+ # config.current_user_method = :current_user
79
+ # config.authentication_method = ->(controller) { controller.authenticate_user! }
80
+ # config.sandbox_service = :cloud_run # Managed
81
+ # config.use_inertia = true
82
+ # end
83
+ #
84
+ module ActionAgent
85
+ class << self
86
+ # Deprecation warnings for this gem, routed through Rails' machinery so a
87
+ # host app can silence or escalate them like any other.
88
+ def deprecator
89
+ @deprecator ||= ActiveSupport::Deprecation.new("2.0", "ActionAgent")
90
+ end
91
+
92
+ # Authentication method to call on controllers
93
+ # @return [Proc, nil] A proc that receives the controller instance
94
+ attr_accessor :authentication_method
95
+
96
+ # Enable multi-tenant mode (requires account association)
97
+ # @return [Boolean]
98
+ attr_accessor :multi_tenant
99
+
100
+ # Class name for the Account model (multi-tenant mode)
101
+ # @return [String, nil]
102
+ attr_accessor :account_class
103
+
104
+ # Class name for the User model
105
+ # @return [String, nil]
106
+ attr_accessor :user_class
107
+
108
+ # Method to call on controller to get current account (multi-tenant mode).
109
+ # Only usable when the host app has mixed that method into the engine's
110
+ # controllers; otherwise use current_account_resolver.
111
+ # @return [Symbol, nil]
112
+ attr_accessor :current_account_method
113
+
114
+ # Method to call on controller to get current user. Same caveat as
115
+ # current_account_method — see current_user_resolver.
116
+ # @return [Symbol, nil]
117
+ attr_accessor :current_user_method
118
+
119
+ # Resolves the signed-in user from the controller. Preferred over
120
+ # current_user_method: the engine's controllers are their own base
121
+ # class, so a host app's `current_user` helper is not on them unless
122
+ # the app deliberately put it there.
123
+ # @return [Proc, nil]
124
+ attr_accessor :current_user_resolver
125
+
126
+ # Resolves the current tenant from the controller. See
127
+ # current_user_resolver.
128
+ # @return [Proc, nil]
129
+ attr_accessor :current_account_resolver
130
+
131
+ # The tenant whose telemetry relates to +owner+. Traces belong to
132
+ # accounts while agents may belong to users, so the two are not always
133
+ # the same record and a host app says how to get from one to the other.
134
+ # @return [Proc, nil]
135
+ attr_accessor :tenant_resolver
136
+
137
+ # The agents an owner can reach. Defaults to the ones that owner owns.
138
+ # A host app where those differ — the platform's agents belong to users
139
+ # while its API keys belong to accounts — supplies its own scope.
140
+ # @return [Proc, nil]
141
+ attr_accessor :agent_scope_resolver
142
+
143
+ # Custom trace model class (for host app overrides)
144
+ # @return [String, nil]
145
+ attr_accessor :trace_model_class
146
+
147
+ # Enable React/Inertia frontend instead of ERB
148
+ # @return [Boolean]
149
+ attr_accessor :use_inertia
150
+
151
+ # Custom layout for the dashboard
152
+ # @return [String, nil]
153
+ attr_accessor :layout
154
+
155
+ # Sandbox service type (:local, :cloud_run, :kubernetes)
156
+ # @return [Symbol]
157
+ attr_accessor :sandbox_service
158
+
159
+ # Custom sandbox limits (overrides defaults)
160
+ # @return [Hash, nil]
161
+ attr_accessor :sandbox_limits
162
+
163
+ # Storage service for screenshots/snapshots
164
+ # @return [Object, nil] Object responding to #signed_url_for and #fetch_snapshot
165
+ attr_accessor :storage_service
166
+
167
+ # Bearer token required by the ingest API in single-tenant mode. When
168
+ # unset the local ingest endpoint accepts unauthenticated posts, so set
169
+ # it whenever the mount is reachable beyond your own machine.
170
+ # (Multi-tenant mode authenticates per-account keys instead.)
171
+ # @return [String, nil]
172
+ attr_accessor :ingest_api_key
173
+
174
+ # @deprecated Never consumed — dashboard controllers inherit
175
+ # ActionController::Base. Retained as a no-op so existing
176
+ # initializers that set it keep booting; remove in the next major.
177
+ # @return [String]
178
+ attr_accessor :base_controller_class
179
+
180
+ # Called before each run/trace-ingest to enforce host-app limits.
181
+ # Receives (owner, kind) where kind is :execution or :trace_ingest, and
182
+ # returns nil to allow, or to deny: a message String, or a Hash merged
183
+ # into the response so the app can surface its own usage numbers.
184
+ # Denials surface as HTTP 402 (execution) / 429 (ingest).
185
+ #
186
+ # Unset means unlimited, which is what a self-hosted install wants.
187
+ # @return [Proc, nil]
188
+ attr_accessor :quota_checker
189
+
190
+ # Resolves LLM provider credentials for a run. Receives
191
+ # (owner, provider_name) and returns a Hash merged into the agent's
192
+ # generation options (e.g. { access_token: "sk-..." } or
193
+ # { host: "http://localhost:11434" }), or nil to fall back to the
194
+ # host app's config/active_agent.yml.
195
+ #
196
+ # Unset means config/active_agent.yml is the only source, which is what
197
+ # a self-hosted install wants.
198
+ # @return [Proc, nil]
199
+ attr_accessor :provider_credentials_resolver
200
+
201
+ # Extra sandbox backends contributed by the host app, as
202
+ # { "cloud_run" => "CloudRunService" }. The engine ships :mock and
203
+ # :local (Docker); cloud backends live in the app that operates them.
204
+ # @return [Hash{String => String}]
205
+ attr_accessor :sandbox_backends
206
+
207
+ # Whether the dashboard may execute agents against real providers.
208
+ # Disable to run the dashboard as a read-only observability surface.
209
+ # @return [Boolean]
210
+ attr_accessor :execution_enabled
211
+
212
+ # Where the dashboard's upgrade CTAs should send people. Unset in a
213
+ # self-hosted install, where there is nothing to upgrade, and the CTAs
214
+ # say so instead of linking nowhere.
215
+ # @return [String, nil]
216
+ attr_accessor :upgrade_url
217
+
218
+ # Called after the dashboard performs a metered action, as
219
+ # (owner, kind) — the counterpart to quota_checker, for host apps that
220
+ # track usage against a plan. Unset means nothing is counted.
221
+ # @return [Proc, nil]
222
+ attr_accessor :usage_recorder
223
+
224
+ # Maps an ingested trace to the owner that its newly observed agents
225
+ # belong to. Defaults to the trace's account in multi-tenant mode and to
226
+ # nobody in single-tenant mode. A host app whose agents hang off a
227
+ # different record (the platform's hang off the account's owning user)
228
+ # supplies its own mapping.
229
+ # @return [Proc, nil]
230
+ attr_accessor :trace_owner_resolver
231
+
232
+ # How long telemetry traces are kept before TraceRetentionJob prunes
233
+ # them. A Duration applies to every trace; a callable receives each
234
+ # owner and returns that owner's window (nil keeps everything). Unset
235
+ # means nothing is ever deleted.
236
+ # @return [ActiveSupport::Duration, Proc, nil]
237
+ attr_accessor :trace_retention
238
+
239
+ # Whether API keys and provider credentials are encrypted at rest with
240
+ # Active Record Encryption. On by default, which requires the host app
241
+ # to have run `rails db:encryption:init`. Turning it off stores those
242
+ # secrets in plain text — a deliberate downgrade, never a default.
243
+ # @return [Boolean]
244
+ attr_accessor :encrypt_credentials
245
+
246
+ # Value stored in polymorphic *_type columns for dashboard agents
247
+ # (agent_memories.memorable_type, agent_contexts.contextable_type).
248
+ # Unset means the class name. A host app whose existing rows were
249
+ # written under its own constant sets its name here.
250
+ # @return [String, nil]
251
+ attr_accessor :agent_polymorphic_name
252
+
253
+ # Returns whether multi-tenant mode is enabled.
254
+ #
255
+ # @return [Boolean]
256
+ def multi_tenant?
257
+ @multi_tenant == true
258
+ end
259
+
260
+ # Returns whether agent execution is permitted.
261
+ #
262
+ # @return [Boolean]
263
+ def execution_enabled?
264
+ @execution_enabled != false
265
+ end
266
+
267
+ # Tells the host app that +owner+ performed +kind+. Never raises: a
268
+ # bookkeeping failure must not fail the action that was already taken.
269
+ def record_usage(owner, kind)
270
+ usage_recorder&.call(owner, kind)
271
+ rescue StandardError => e
272
+ Rails.logger.warn("[ActionAgent] usage recording failed: #{e.message}")
273
+ nil
274
+ end
275
+
276
+ # Asks the host app whether +owner+ may perform +kind+.
277
+ #
278
+ # @return [String, Hash, nil] denial message or payload, nil when allowed
279
+ def quota_denial(owner, kind)
280
+ return nil if quota_checker.nil?
281
+
282
+ quota_checker.call(owner, kind)
283
+ end
284
+
285
+ # Provider options for +owner+, or {} when the host app has none and
286
+ # config/active_agent.yml should be used as-is.
287
+ #
288
+ # @return [Hash]
289
+ def provider_credentials(owner, provider)
290
+ return {} if provider_credentials_resolver.nil?
291
+
292
+ provider_credentials_resolver.call(owner, provider) || {}
293
+ rescue StandardError => e
294
+ Rails.logger.warn("[ActionAgent] provider credential lookup failed: #{e.message}")
295
+ {}
296
+ end
297
+
298
+ # Returns the trace model class to use.
299
+ #
300
+ # @return [Class] The trace model class
301
+ def trace_model
302
+ if trace_model_class
303
+ trace_model_class.constantize
304
+ else
305
+ ActionAgent::TelemetryTrace
306
+ end
307
+ end
308
+
309
+ # The tenant +owner+ belongs to. Identity unless the host app says
310
+ # otherwise, which is right for every single-tenant install.
311
+ def tenant_for(owner)
312
+ return owner if tenant_resolver.nil?
313
+
314
+ tenant_resolver.call(owner)
315
+ end
316
+
317
+ # The agents +owner+ can reach.
318
+ #
319
+ # @return [ActiveRecord::Relation]
320
+ def agents_for(owner)
321
+ return agent_model.for_owner(owner) if agent_scope_resolver.nil?
322
+
323
+ agent_scope_resolver.call(owner) || agent_model.none
324
+ end
325
+
326
+ # Returns the agent model class to use.
327
+ #
328
+ # @return [Class] The agent model class
329
+ def agent_model
330
+ ActionAgent::Agent
331
+ end
332
+
333
+ # Returns the configured owner class: the Account in multi-tenant mode,
334
+ # the User otherwise. Nil when the host app configured neither, which
335
+ # is the single-user self-hosted case.
336
+ #
337
+ # @return [Class, nil]
338
+ def owner_class
339
+ name = multi_tenant? ? account_class : user_class
340
+ name&.safe_constantize
341
+ end
342
+
343
+ # Configures the dashboard.
344
+ #
345
+ # @yield [config] Configuration block
346
+ def configure
347
+ yield self
348
+ end
349
+
350
+ # Reset configuration to defaults
351
+ def reset!
352
+ @authentication_method = nil
353
+ @multi_tenant = false
354
+ @account_class = nil
355
+ @user_class = nil
356
+ @current_account_method = nil
357
+ @current_user_method = nil
358
+ @current_user_resolver = nil
359
+ @current_account_resolver = nil
360
+ @agent_scope_resolver = nil
361
+ @tenant_resolver = nil
362
+ @trace_model_class = nil
363
+ @use_inertia = false
364
+ @layout = nil
365
+ @sandbox_service = :local
366
+ @sandbox_limits = nil
367
+ @storage_service = nil
368
+ @ingest_api_key = nil
369
+ @base_controller_class = "ActionController::Base" # deprecated no-op
370
+ @quota_checker = nil
371
+ @provider_credentials_resolver = nil
372
+ @sandbox_backends = {}
373
+ @execution_enabled = true
374
+ @table_name_prefix = "active_agent_"
375
+ @agent_polymorphic_name = nil
376
+ @encrypt_credentials = true
377
+ @trace_retention = nil
378
+ @trace_owner_resolver = nil
379
+ @usage_recorder = nil
380
+ @upgrade_url = nil
381
+ end
382
+ end
383
+
384
+ # Set defaults
385
+ reset!
386
+ end
387
+
388
+ ActionAgent::Compatibility.install!
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The gem is `actionagent`; the code lives under `action_agent`. This is the
4
+ # file Bundler requires when it sees `gem "actionagent"` — the same shim Rails
5
+ # ships for activerecord, actionpack and friends.
6
+ require "action_agent"