actionagent 1.8.1 → 1.9.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/app/assets/builds/action_agent.css +1 -1
- data/app/assets/builds/action_agent.js +76 -73
- data/app/assets/builds/action_agent_recorder.js +78 -0
- data/app/assets/builds/action_agent_replay.js +226 -0
- data/app/controllers/action_agent/api/agent_runs_controller.rb +3 -1
- data/app/controllers/action_agent/api/agents_controller.rb +1 -0
- data/app/controllers/action_agent/api/api_keys_controller.rb +12 -2
- data/app/controllers/action_agent/api/base_controller.rb +63 -2
- data/app/controllers/action_agent/api/claude_logins_controller.rb +93 -0
- data/app/controllers/action_agent/api/code_sessions_controller.rb +53 -6
- data/app/controllers/action_agent/api/dashboard_assistant_controller.rb +2 -2
- data/app/controllers/action_agent/api/draft_pull_requests_controller.rb +248 -0
- data/app/controllers/action_agent/api/evaluation_fixes_controller.rb +27 -0
- data/app/controllers/action_agent/api/evaluations_controller.rb +9 -4
- data/app/controllers/action_agent/api/explorations_controller.rb +213 -0
- data/app/controllers/action_agent/api/github_app_manifests_controller.rb +125 -0
- data/app/controllers/action_agent/api/github_connections_controller.rb +40 -2
- data/app/controllers/action_agent/api/github_installations_controller.rb +280 -0
- data/app/controllers/action_agent/api/input_requests_controller.rb +77 -0
- data/app/controllers/action_agent/api/mcp_controller.rb +46 -12
- data/app/controllers/action_agent/api/members_controller.rb +36 -0
- data/app/controllers/action_agent/api/project_explorations_controller.rb +80 -0
- data/app/controllers/action_agent/api/project_install_pull_requests_controller.rb +305 -0
- data/app/controllers/action_agent/api/project_secrets_controller.rb +96 -0
- data/app/controllers/action_agent/api/project_setup_controller.rb +151 -0
- data/app/controllers/action_agent/api/project_sign_ins_controller.rb +112 -0
- data/app/controllers/action_agent/api/projects_controller.rb +512 -0
- data/app/controllers/action_agent/api/provider_keys_controller.rb +176 -24
- data/app/controllers/action_agent/api/provider_models_controller.rb +37 -15
- data/app/controllers/action_agent/api/recording_event_ingest_controller.rb +67 -0
- data/app/controllers/action_agent/api/sandbox_browser_tickets_controller.rb +50 -0
- data/app/controllers/action_agent/api/sandbox_browsers_controller.rb +65 -0
- data/app/controllers/action_agent/api/sandboxes_controller.rb +140 -8
- data/app/controllers/action_agent/api/scenario_catalog_entries_controller.rb +188 -0
- data/app/controllers/action_agent/api/scenario_catalogs_controller.rb +302 -0
- data/app/controllers/action_agent/api/session_recordings_controller.rb +165 -34
- data/app/controllers/action_agent/api/sessions_controller.rb +111 -0
- data/app/controllers/action_agent/api/telemetry_keys_controller.rb +21 -0
- data/app/controllers/action_agent/dashboard_controller.rb +17 -6
- data/app/controllers/action_agent/session_player_controller.rb +58 -0
- data/app/controllers/concerns/action_agent/api/agent_serialization.rb +1 -0
- data/app/controllers/concerns/action_agent/api/mcp_dashboard_tools.rb +623 -18
- data/app/controllers/concerns/action_agent/api/project_browser.rb +46 -0
- data/app/controllers/concerns/action_agent/api/project_secret_authorization.rb +57 -0
- data/app/jobs/action_agent/agent_execution_job.rb +9 -39
- data/app/jobs/action_agent/agent_resume_job.rb +84 -0
- data/app/jobs/action_agent/code_session_job.rb +26 -2
- data/app/jobs/action_agent/draft_pull_request_job.rb +43 -0
- data/app/jobs/action_agent/exploration_job.rb +80 -0
- data/app/jobs/action_agent/input_request_expiry_job.rb +22 -0
- data/app/jobs/action_agent/project_evaluation_job.rb +59 -0
- data/app/jobs/action_agent/project_setup_job.rb +21 -0
- data/app/jobs/action_agent/sandbox_cleanup_job.rb +20 -3
- data/app/jobs/action_agent/sandbox_provision_job.rb +77 -10
- data/app/jobs/action_agent/sandbox_run_job.rb +9 -37
- data/app/jobs/action_agent/verify_evaluation_fix_job.rb +37 -0
- data/app/models/action_agent/agent.rb +39 -26
- data/app/models/action_agent/agent_run.rb +134 -23
- data/app/models/action_agent/agent_template.rb +35 -1
- data/app/models/action_agent/api_key.rb +14 -0
- data/app/models/action_agent/catalog_scenario.rb +75 -0
- data/app/models/action_agent/code_session.rb +16 -4
- data/app/models/action_agent/draft_pull_request.rb +131 -0
- data/app/models/action_agent/evaluation.rb +84 -0
- data/app/models/action_agent/exploration.rb +847 -0
- data/app/models/action_agent/github_installation.rb +159 -0
- data/app/models/action_agent/input_request.rb +291 -0
- data/app/models/action_agent/project.rb +789 -0
- data/app/models/action_agent/project_secret.rb +339 -0
- data/app/models/action_agent/provider_key.rb +200 -3
- data/app/models/action_agent/recording_event.rb +145 -0
- data/app/models/action_agent/recording_snapshot.rb +7 -7
- data/app/models/action_agent/sandbox_session.rb +297 -14
- data/app/models/action_agent/scenario_catalog.rb +172 -0
- data/app/models/action_agent/scenario_product.rb +57 -0
- data/app/models/action_agent/scenario_set.rb +138 -0
- data/app/models/action_agent/session_recording.rb +121 -14
- data/app/models/action_agent/telemetry_trace.rb +74 -19
- data/app/models/concerns/action_agent/ownable.rb +84 -13
- data/app/serializers/action_agent/input_request_serializer.rb +47 -0
- data/app/serializers/action_agent/scenario_catalog_serializer.rb +23 -0
- data/app/serializers/action_agent/session_summary_serializer.rb +146 -0
- data/app/services/action_agent/agent_execution_service.rb +450 -72
- data/app/services/action_agent/agent_sync.rb +5 -2
- data/app/services/action_agent/agent_tool_roster.rb +19 -4
- data/app/services/action_agent/agent_toolbox.rb +215 -9
- data/app/services/action_agent/browser_live_ticket.rb +78 -0
- data/app/services/action_agent/browser_sidecar.rb +213 -0
- data/app/services/action_agent/browser_sign_in.rb +163 -0
- data/app/services/action_agent/browser_tool_redaction.rb +92 -0
- data/app/services/action_agent/claude_code_auth.rb +70 -4
- data/app/services/action_agent/dashboard_assistant_service.rb +24 -10
- data/app/services/action_agent/draft_pull_request_publisher.rb +719 -0
- data/app/services/action_agent/evaluation_fix.rb +101 -0
- data/app/services/action_agent/evaluation_fix_comparison.rb +66 -0
- data/app/services/action_agent/evaluation_runner_service.rb +4 -5
- data/app/services/action_agent/evaluation_standing.rb +15 -8
- data/app/services/action_agent/explorer_execution_service.rb +517 -0
- data/app/services/action_agent/github_client.rb +405 -64
- data/app/services/action_agent/live_updates.rb +31 -0
- data/app/services/action_agent/local_sandbox_backend.rb +1218 -83
- data/app/services/action_agent/local_sandbox_claude_login.rb +151 -0
- data/app/services/action_agent/mcp_client.rb +6 -2
- data/app/services/action_agent/mcp_recording_middleware.rb +104 -145
- data/app/services/action_agent/mcp_tool_dispatcher.rb +36 -13
- data/app/services/action_agent/mock_sandbox_backend.rb +3 -1
- data/app/services/action_agent/playwright_mcp_client.rb +7 -0
- data/app/services/action_agent/project_boot_fix.rb +309 -0
- data/app/services/action_agent/project_capabilities.rb +200 -0
- data/app/services/action_agent/project_install_pull_request.rb +225 -0
- data/app/services/action_agent/project_preflight.rb +178 -0
- data/app/services/action_agent/project_secret_discovery.rb +142 -0
- data/app/services/action_agent/project_setup.rb +429 -0
- data/app/services/action_agent/provider_credentials.rb +102 -0
- data/app/services/action_agent/recording_event_ingest.rb +181 -0
- data/app/services/action_agent/runtime_tool_roster.rb +52 -0
- data/app/services/action_agent/sample_data/conference_ticket.rb +516 -0
- data/app/services/action_agent/sandbox_boot_spec.rb +558 -0
- data/app/services/action_agent/sandbox_browser.rb +299 -0
- data/app/services/action_agent/sandbox_browser_driver.rb +85 -0
- data/app/services/action_agent/sandbox_credential_paths.rb +26 -0
- data/app/services/action_agent/sandbox_mail.rb +103 -0
- data/app/services/action_agent/sandbox_manifest.rb +166 -13
- data/app/services/action_agent/sandbox_orchestrator.rb +276 -22
- data/app/services/action_agent/sandbox_patch.rb +253 -0
- data/app/services/action_agent/scenario_catalog_import.rb +255 -0
- data/app/services/action_agent/scenario_catalog_repository_import.rb +81 -0
- data/app/services/action_agent/scenario_evaluation_runner.rb +119 -16
- data/app/services/action_agent/secret_scrubber.rb +33 -4
- data/app/services/action_agent/session_index.rb +270 -0
- data/app/services/action_agent/session_recording_service.rb +11 -0
- data/app/services/action_agent/session_timeline.rb +505 -0
- data/app/views/action_agent/github_app_manifests/created.html.erb +55 -0
- data/app/views/action_agent/session_player/show.html.erb +19 -0
- data/config/routes.rb +152 -1
- data/lib/action_agent/claude_login_process.rb +107 -0
- data/lib/action_agent/engine.rb +55 -5
- data/lib/action_agent/secret_requests.rb +80 -0
- data/lib/action_agent/version.rb +1 -1
- data/lib/action_agent.rb +507 -22
- data/lib/generators/action_agent/install_generator.rb +37 -0
- data/lib/generators/action_agent/templates/action_agent.rb.erb +158 -0
- data/lib/generators/action_agent/templates/migrations/015_add_provider_key_scope.rb.erb +72 -0
- data/lib/generators/action_agent/templates/migrations/016_create_active_agent_input_requests.rb.erb +85 -0
- data/lib/generators/action_agent/templates/migrations/017_create_active_agent_recording_events.rb.erb +57 -0
- data/lib/generators/action_agent/templates/migrations/018_add_browser_to_active_agent_sandbox_sessions.rb.erb +25 -0
- data/lib/generators/action_agent/templates/migrations/019_create_active_agent_projects.rb.erb +79 -0
- data/lib/generators/action_agent/templates/migrations/020_add_github_app_installations.rb.erb +62 -0
- data/lib/generators/action_agent/templates/migrations/021_create_active_agent_explorations.rb.erb +63 -0
- data/lib/generators/action_agent/templates/migrations/023_create_active_agent_draft_pull_requests.rb.erb +75 -0
- data/lib/generators/action_agent/templates/migrations/024_add_kind_to_active_agent_project_secrets.rb.erb +14 -0
- data/lib/generators/action_agent/templates/migrations/025_create_active_agent_scenario_catalogs.rb.erb +110 -0
- data/lib/generators/action_agent/templates/migrations/026_add_evaluation_fixes_to_code_sessions.rb.erb +21 -0
- data/lib/tasks/action_agent.rake +33 -0
- data/lib/tasks/browser.rake +22 -0
- data/lib/tasks/sandbox.rake +1 -1
- data/lib/tasks/scenario_catalogs.rake +71 -0
- metadata +96 -2
data/lib/action_agent.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActionAgent
|
|
4
|
+
# Raised as the engine loads for a configuration it cannot run with.
|
|
5
|
+
class ConfigurationError < StandardError; end
|
|
6
|
+
|
|
4
7
|
class << self
|
|
5
8
|
# Table name prefix for the engine's models. The engine's own
|
|
6
9
|
# migrations create `active_agent_*` tables, so the default matches.
|
|
@@ -61,6 +64,7 @@ require "solid_agent"
|
|
|
61
64
|
require "action_agent/version"
|
|
62
65
|
require "action_agent/engine"
|
|
63
66
|
require "action_agent/compatibility"
|
|
67
|
+
require "action_agent/secret_requests"
|
|
64
68
|
|
|
65
69
|
# Dashboard engine for visualizing telemetry data and managing agents.
|
|
66
70
|
#
|
|
@@ -105,7 +109,44 @@ require "action_agent/compatibility"
|
|
|
105
109
|
#
|
|
106
110
|
module ActionAgent
|
|
107
111
|
# What ActionAgent.claude_code_auth may be set to.
|
|
108
|
-
CLAUDE_CODE_AUTH_MODES = %i[api_key local_login].freeze
|
|
112
|
+
CLAUDE_CODE_AUTH_MODES = %i[api_key local_login sandbox_login].freeze
|
|
113
|
+
|
|
114
|
+
# What ActionAgent.provider_key_scope may be set to.
|
|
115
|
+
PROVIDER_KEY_SCOPES = %i[organization personal_override].freeze
|
|
116
|
+
|
|
117
|
+
# The privileged actions ActionAgent.permission_checker is asked about:
|
|
118
|
+
#
|
|
119
|
+
# :manage_credentials store, test or delete an organization provider
|
|
120
|
+
# credential, hand the organization's to a
|
|
121
|
+
# project's code, or store or test a member's
|
|
122
|
+
# personal Ollama host (the subject is that
|
|
123
|
+
# personal key); a member's other personal keys
|
|
124
|
+
# need none
|
|
125
|
+
# :manage_github connect, disconnect, or choose the repositories of
|
|
126
|
+
# the GitHub connection or a GitHub App
|
|
127
|
+
# installation; link or unlink an installation;
|
|
128
|
+
# create a GitHub App from a manifest; read a
|
|
129
|
+
# repository the connection has not selected
|
|
130
|
+
# :manage_api_keys create or revoke a dashboard API key
|
|
131
|
+
# :publish_pull_request open a pull request from a sandbox's changes
|
|
132
|
+
# :answer_input_request answer or decline a run's request for input
|
|
133
|
+
# :manage_project_secrets set or remove a project's secrets, change the ref
|
|
134
|
+
# they are handed to, or delete a project that has
|
|
135
|
+
# them
|
|
136
|
+
# :take_over_browser drive a run's browser by hand
|
|
137
|
+
# :manage_recordings delete a session recording
|
|
138
|
+
# :replace_scenarios replace or merge an evaluation's scenarios
|
|
139
|
+
PERMISSION_ACTIONS = %i[
|
|
140
|
+
manage_credentials
|
|
141
|
+
manage_github
|
|
142
|
+
manage_api_keys
|
|
143
|
+
publish_pull_request
|
|
144
|
+
answer_input_request
|
|
145
|
+
manage_project_secrets
|
|
146
|
+
take_over_browser
|
|
147
|
+
manage_recordings
|
|
148
|
+
replace_scenarios
|
|
149
|
+
].freeze
|
|
109
150
|
|
|
110
151
|
class << self
|
|
111
152
|
# Deprecation warnings for this gem, routed through Rails' machinery so a
|
|
@@ -159,8 +200,9 @@ module ActionAgent
|
|
|
159
200
|
# config.agent_actor_resolver = ->(controller) { controller.current_user }
|
|
160
201
|
#
|
|
161
202
|
# Unset means the dashboard's signed-in user, and, for the MCP endpoint,
|
|
162
|
-
# the API key's owner — the identity
|
|
163
|
-
# whose keys are issued per end user
|
|
203
|
+
# the user who created the API key, else the key's owner — the identity
|
|
204
|
+
# that authenticated the call. A host whose keys are issued per end user
|
|
205
|
+
# overrides this to return that user.
|
|
164
206
|
#
|
|
165
207
|
# Returning nil runs the agent unattributed, which a correctly written
|
|
166
208
|
# host scope reads as "no access". That is the safe direction, and it is
|
|
@@ -209,9 +251,80 @@ module ActionAgent
|
|
|
209
251
|
# @return [Hash, nil]
|
|
210
252
|
attr_accessor :sandbox_limits
|
|
211
253
|
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
|
|
254
|
+
# Caps on the events a browser may post to a session recording, merged
|
|
255
|
+
# over RecordingEvent::DEFAULT_LIMITS. Keys:
|
|
256
|
+
#
|
|
257
|
+
# batch_events events in one batch
|
|
258
|
+
# batch_bytes bytes in one batch's request body
|
|
259
|
+
# recording_events events stored on one recording
|
|
260
|
+
# recording_bytes bytes of event JSON stored on one recording
|
|
261
|
+
#
|
|
262
|
+
# A batch over any of them is refused and counted on the recording.
|
|
263
|
+
# @return [Hash, nil]
|
|
264
|
+
attr_accessor :recording_limits
|
|
265
|
+
|
|
266
|
+
# Active Storage in the engine. The engine attaches files in a few
|
|
267
|
+
# places: the files uploaded with a run (AgentRun attachments), a session
|
|
268
|
+
# recording's snapshots and oversized event payloads, and a scenario
|
|
269
|
+
# catalog's YAML. Three settings:
|
|
270
|
+
#
|
|
271
|
+
# :auto (the default) use Active Storage when the host app has it
|
|
272
|
+
# loaded and its tables migrated; otherwise those features keep
|
|
273
|
+
# their metadata-only form (runs refuse files, recordings keep
|
|
274
|
+
# events inline, catalogs live in the database alone).
|
|
275
|
+
# true require it: the first engine model to load raises
|
|
276
|
+
# ConfigurationError when Active Storage is not loaded, so a
|
|
277
|
+
# host that depends on attachments finds out at boot.
|
|
278
|
+
# false never attach, even when the host has Active Storage.
|
|
279
|
+
#
|
|
280
|
+
# Decided as the models load, so set it in an initializer.
|
|
281
|
+
# @return [Symbol, Boolean]
|
|
282
|
+
attr_accessor :active_storage
|
|
283
|
+
|
|
284
|
+
# The Active Storage service the engine's attachments are written to, by
|
|
285
|
+
# its name in config/storage.yml (`:recordings`, `:gcs`). nil, the
|
|
286
|
+
# default, uses the host's default service. Rails checks the name as the
|
|
287
|
+
# models load, so a misspelling fails at boot rather than on the first
|
|
288
|
+
# upload.
|
|
289
|
+
# @return [Symbol, String, nil]
|
|
290
|
+
attr_accessor :active_storage_service
|
|
291
|
+
|
|
292
|
+
# Whether the engine's models apply their Active Storage macros
|
|
293
|
+
# (has_one_attached and friends), per +active_storage+.
|
|
294
|
+
# @raise [ConfigurationError] when the option is true and Active Storage
|
|
295
|
+
# is not loaded
|
|
296
|
+
# @return [Boolean]
|
|
297
|
+
def active_storage_macros?(loaded: defined?(::ActiveStorage) ? true : false)
|
|
298
|
+
case active_storage
|
|
299
|
+
when true
|
|
300
|
+
return true if loaded
|
|
301
|
+
|
|
302
|
+
raise ConfigurationError,
|
|
303
|
+
"ActionAgent.active_storage is true but Active Storage is not loaded: add `require \"active_storage/engine\"` " \
|
|
304
|
+
"to config/application.rb and run `bin/rails active_storage:install`, or set the option to :auto or false"
|
|
305
|
+
when false, nil then false
|
|
306
|
+
else loaded
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Whether an attachment can be written right now: the macros applied and
|
|
311
|
+
# the blobs table migrated. Never raises, so a host that skipped
|
|
312
|
+
# `rails active_storage:install` still runs agents; it just cannot
|
|
313
|
+
# attach files to them.
|
|
314
|
+
# @return [Boolean]
|
|
315
|
+
def active_storage_available?
|
|
316
|
+
return false unless active_storage_macros?
|
|
317
|
+
|
|
318
|
+
::ActiveStorage::Blob.table_exists?
|
|
319
|
+
rescue StandardError
|
|
320
|
+
false
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# The options the engine's attachment macros are declared with.
|
|
324
|
+
# @return [Hash]
|
|
325
|
+
def attachment_options
|
|
326
|
+
active_storage_service.present? ? { service: active_storage_service.to_sym } : {}
|
|
327
|
+
end
|
|
215
328
|
|
|
216
329
|
# Bearer token required in single-tenant mode by the endpoints other
|
|
217
330
|
# applications post to: trace ingest (<mount>/api/traces) and published
|
|
@@ -281,6 +394,11 @@ module ActionAgent
|
|
|
281
394
|
# :trace_ingest — a POST to <mount>/api/traces; HTTP 429
|
|
282
395
|
# :evaluation_report — a report <mount>/api/evaluation_reports would
|
|
283
396
|
# store (never an identical retry); HTTP 429
|
|
397
|
+
# :project — creating a project; HTTP 402, and usage is
|
|
398
|
+
# recorded under the same kind once it exists
|
|
399
|
+
# :browser_minutes — starting a sandbox's browser; HTTP 402
|
|
400
|
+
# :exploration — starting the explorer on a project; HTTP 402,
|
|
401
|
+
# and usage is recorded once for each start
|
|
284
402
|
#
|
|
285
403
|
# The owner of an ingest kind is the tenant the key resolved to, nil on a
|
|
286
404
|
# single-tenant install.
|
|
@@ -289,17 +407,111 @@ module ActionAgent
|
|
|
289
407
|
# @return [Proc, nil]
|
|
290
408
|
attr_accessor :quota_checker
|
|
291
409
|
|
|
410
|
+
# Decides whether the signed-in user may perform a privileged action,
|
|
411
|
+
# called as (user, action, subject):
|
|
412
|
+
#
|
|
413
|
+
# user the signed-in user (the dashboard's current_user), or nil
|
|
414
|
+
# action one of PERMISSION_ACTIONS
|
|
415
|
+
# subject the record the action applies to, unsaved when the action
|
|
416
|
+
# creates it, with its owner columns set
|
|
417
|
+
#
|
|
418
|
+
# A truthy answer allows and false denies. {.permitted?} describes how a
|
|
419
|
+
# nil answer, an exception and a missing user are treated. A denied API
|
|
420
|
+
# request gets HTTP 403, and a denied GitHub connect or callback returns
|
|
421
|
+
# to Settings.
|
|
422
|
+
#
|
|
423
|
+
# For :answer_input_request the subject is the InputRequest, whose
|
|
424
|
+
# `requested_by_id` is the id of the user the paused run acts for.
|
|
425
|
+
#
|
|
426
|
+
# config.permission_checker = ->(user, action, subject) {
|
|
427
|
+
# user.present? && (user.admin? ||
|
|
428
|
+
# action == :answer_input_request && subject.requested_by_id.in?([ nil, user.id ]))
|
|
429
|
+
# }
|
|
430
|
+
#
|
|
431
|
+
# Unset means everyone who can reach the dashboard may do everything,
|
|
432
|
+
# which is what a single-user install wants. In multi-tenant mode it means
|
|
433
|
+
# every member of a tenant may do everything except answer a request for
|
|
434
|
+
# input another member's run raised (InputRequest#answerable_by?), and the
|
|
435
|
+
# engine logs a warning at boot (see {.warn_about_unchecked_permissions}).
|
|
436
|
+
# @return [Proc, nil]
|
|
437
|
+
attr_accessor :permission_checker
|
|
438
|
+
|
|
292
439
|
# Resolves LLM provider credentials for a run. Receives
|
|
293
440
|
# (owner, provider_name) and returns a Hash merged into the agent's
|
|
294
441
|
# generation options (e.g. { access_token: "sk-..." } or
|
|
295
442
|
# { host: "http://localhost:11434" }), or nil to fall back to the
|
|
296
|
-
# host app's
|
|
443
|
+
# dashboard's stored organization key and then the host app's
|
|
444
|
+
# config/active_agent.yml.
|
|
445
|
+
#
|
|
446
|
+
# A resolver that declares an +actor:+ keyword (or accepts any keyword
|
|
447
|
+
# with **) is also told who is acting: the user who started the run, the
|
|
448
|
+
# signed-in user for the dashboard assistant and the model pickers, nil
|
|
449
|
+
# for the evaluation judge and a run nobody started:
|
|
450
|
+
#
|
|
451
|
+
# config.provider_credentials_resolver = ->(owner, provider, actor: nil) {
|
|
452
|
+
# owner.provider_key_for(provider)&.generation_options
|
|
453
|
+
# }
|
|
454
|
+
#
|
|
455
|
+
# A personal key (see provider_key_scope) is tried before the resolver,
|
|
456
|
+
# so a resolver that reads the dashboard's provider_keys table by
|
|
457
|
+
# provider alone has to keep to organization rows
|
|
458
|
+
# (ActionAgent::ProviderKey.for_owner does).
|
|
297
459
|
#
|
|
298
460
|
# Unset means config/active_agent.yml is the only source, which is what
|
|
299
|
-
# a self-hosted install wants.
|
|
461
|
+
# a self-hosted install wants. See {.provider_credentials} for how an
|
|
462
|
+
# exception is treated.
|
|
300
463
|
# @return [Proc, nil]
|
|
301
464
|
attr_accessor :provider_credentials_resolver
|
|
302
465
|
|
|
466
|
+
# Whether a member's own provider key may stand in for the
|
|
467
|
+
# organization's. One of PROVIDER_KEY_SCOPES:
|
|
468
|
+
#
|
|
469
|
+
# :organization (default) runs use the organization's key, the
|
|
470
|
+
# host resolver's, or config/active_agent.yml
|
|
471
|
+
# :personal_override a member may also save a personal key per
|
|
472
|
+
# provider in Settings, and the runs that member
|
|
473
|
+
# starts, the dashboard assistant and the model
|
|
474
|
+
# pickers use it before the organization's
|
|
475
|
+
#
|
|
476
|
+
# A personal key sends the organization's agent traffic to the member's
|
|
477
|
+
# own provider account, so an install opts in. Without an account_class
|
|
478
|
+
# there are no personal keys and the setting has no effect. The
|
|
479
|
+
# evaluation judge, sandboxes, and Claude Code and Codex connections
|
|
480
|
+
# always use organization keys. A personal Ollama host decides where the
|
|
481
|
+
# server sends requests, so storing one asks permission_checker for
|
|
482
|
+
# :manage_credentials.
|
|
483
|
+
# @return [Symbol]
|
|
484
|
+
attr_reader :provider_key_scope
|
|
485
|
+
|
|
486
|
+
def provider_key_scope=(value)
|
|
487
|
+
scope = value.nil? ? :organization : value.to_sym
|
|
488
|
+
unless PROVIDER_KEY_SCOPES.include?(scope)
|
|
489
|
+
raise ArgumentError, "provider_key_scope must be one of #{PROVIDER_KEY_SCOPES.join(', ')}, got #{value.inspect}"
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
@provider_key_scope = scope
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Lists the members of an owner for the Organization view's Team Members
|
|
496
|
+
# table, answering GET <mount>/api/members. Receives (owner) and returns
|
|
497
|
+
# an Array of Hashes with :id, :name, :email and :role; any other key is
|
|
498
|
+
# dropped before it reaches the browser.
|
|
499
|
+
#
|
|
500
|
+
# config.members_resolver = ->(account) {
|
|
501
|
+
# account.memberships.includes(:user).map { |m| { id: m.user.id, name: m.user.name, email: m.user.email, role: m.role } }
|
|
502
|
+
# }
|
|
503
|
+
#
|
|
504
|
+
# Unset, or when it raises (logged), the table lists the signed-in user
|
|
505
|
+
# alone.
|
|
506
|
+
# @return [Proc, nil]
|
|
507
|
+
attr_accessor :members_resolver
|
|
508
|
+
|
|
509
|
+
# Where the Organization view's "+ Invite Member" button links: the
|
|
510
|
+
# host app's own invitation page. Unset, the button is not shown; the
|
|
511
|
+
# engine has no invitations of its own.
|
|
512
|
+
# @return [String, nil]
|
|
513
|
+
attr_accessor :member_invite_url
|
|
514
|
+
|
|
303
515
|
# Sandbox backends contributed by the host app, as
|
|
304
516
|
# { "cloud_run" => "CloudRunService" }. The engine ships only :mock;
|
|
305
517
|
# every real backend (Docker/Incus, Cloud Run, Kubernetes) lives in the
|
|
@@ -328,6 +540,34 @@ module ActionAgent
|
|
|
328
540
|
# @return [Integer]
|
|
329
541
|
attr_accessor :local_sandbox_boot_timeout
|
|
330
542
|
|
|
543
|
+
# A checkout of the browser sidecar (the repository's browser-sidecar/
|
|
544
|
+
# directory) for the :local backend to run instead of the package
|
|
545
|
+
# `bin/rails action_agent:browser:install` installs. For working on the
|
|
546
|
+
# sidecar itself: a checkout's version is not checked against the
|
|
547
|
+
# engine's. Unset, the installed package runs.
|
|
548
|
+
# @return [String, Pathname, nil]
|
|
549
|
+
attr_accessor :browser_sidecar_path
|
|
550
|
+
|
|
551
|
+
# The Node.js and npm executables the browser sidecar is installed and
|
|
552
|
+
# run with.
|
|
553
|
+
# @return [String]
|
|
554
|
+
attr_accessor :node_command, :npm_command
|
|
555
|
+
|
|
556
|
+
# How long the :local backend waits for a browser to start, in seconds.
|
|
557
|
+
# @return [Integer]
|
|
558
|
+
attr_accessor :browser_start_timeout
|
|
559
|
+
|
|
560
|
+
# Origins, besides the one a browser is started from, whose dashboard
|
|
561
|
+
# pages may open its live view, such as "http://127.0.0.1:3000" when the
|
|
562
|
+
# dashboard is also reached that way. A live view's WebSocket refuses
|
|
563
|
+
# every other Origin.
|
|
564
|
+
# @return [Array<String>]
|
|
565
|
+
attr_writer :browser_live_origins
|
|
566
|
+
|
|
567
|
+
def browser_live_origins
|
|
568
|
+
Array(@browser_live_origins).map(&:to_s)
|
|
569
|
+
end
|
|
570
|
+
|
|
331
571
|
# The Claude Code executable a sandbox backend runs headless sessions
|
|
332
572
|
# with. The :local backend runs it on the dashboard's machine.
|
|
333
573
|
# @return [String]
|
|
@@ -368,18 +608,26 @@ module ActionAgent
|
|
|
368
608
|
# or stores it; it only asks `claude auth status` whether there is one.
|
|
369
609
|
# That login is the dashboard user's own, so this works with the :local
|
|
370
610
|
# sandbox backend only, and other backends refuse Claude Code sessions.
|
|
371
|
-
#
|
|
611
|
+
# :sandbox_login lets a signed-in user authorize the unmodified CLI in
|
|
612
|
+
# one sandbox. Its credentials remain in that sandbox's private CLI
|
|
613
|
+
# config, outside the checkout, until stop/expiry/disconnect.
|
|
614
|
+
# @return [Symbol] :api_key, :local_login or :sandbox_login
|
|
372
615
|
attr_reader :claude_code_auth
|
|
373
616
|
|
|
374
617
|
def claude_code_auth=(value)
|
|
375
618
|
mode = value.to_s.to_sym
|
|
376
619
|
unless CLAUDE_CODE_AUTH_MODES.include?(mode)
|
|
377
|
-
raise ArgumentError, "ActionAgent.claude_code_auth must be :api_key or :
|
|
620
|
+
raise ArgumentError, "ActionAgent.claude_code_auth must be :api_key, :local_login or :sandbox_login, not #{value.inspect}"
|
|
378
621
|
end
|
|
379
622
|
|
|
380
623
|
@claude_code_auth = mode
|
|
381
624
|
end
|
|
382
625
|
|
|
626
|
+
# Hosted adapters must opt in after the operator has reviewed Anthropic's
|
|
627
|
+
# hosting terms. :local supports sandbox-scoped login without this flag.
|
|
628
|
+
attr_accessor :claude_code_hosted_login_enabled
|
|
629
|
+
attr_accessor :claude_code_login_timeout
|
|
630
|
+
|
|
383
631
|
# Whether the dashboard may execute agents against real providers.
|
|
384
632
|
# Disable to run the dashboard as a read-only observability surface.
|
|
385
633
|
# @return [Boolean]
|
|
@@ -411,6 +659,24 @@ module ActionAgent
|
|
|
411
659
|
# @return [Boolean, nil]
|
|
412
660
|
attr_accessor :assistant_enabled
|
|
413
661
|
|
|
662
|
+
# Whether the dashboard records a person's view of the Run Agent
|
|
663
|
+
# workbench, as the browser lane of the conversation's replay. On unless
|
|
664
|
+
# set to false.
|
|
665
|
+
#
|
|
666
|
+
# While the workbench has a conversation open, the page is recorded with
|
|
667
|
+
# rrweb, each visit into a recording of its own (`source: "dashboard"`),
|
|
668
|
+
# and nowhere else in the dashboard. Field values are masked. Elements
|
|
669
|
+
# marked `data-aa-secret` (credential fields and displays), the CSRF
|
|
670
|
+
# token and hidden inputs are recorded as blank boxes. Each batch is
|
|
671
|
+
# scrubbed of the owner's stored credentials before it is kept. What the
|
|
672
|
+
# page shows otherwise, the conversation included, is recorded as shown.
|
|
673
|
+
#
|
|
674
|
+
# Off, the dashboard loads no recorder and refuses the batches a
|
|
675
|
+
# dashboard session posts. Conversations still replay from their
|
|
676
|
+
# messages, model calls and tool calls.
|
|
677
|
+
# @return [Boolean]
|
|
678
|
+
attr_accessor :capture_dashboard_sessions
|
|
679
|
+
|
|
414
680
|
# Resolves a host application's runner for one scenario evaluation.
|
|
415
681
|
# Return nil for the engine's normal Agent#test_execute path, or a callable
|
|
416
682
|
# accepting evaluation:, owner:, scenarios:, models:, on_result: and
|
|
@@ -419,6 +685,14 @@ module ActionAgent
|
|
|
419
685
|
# @return [Proc, nil]
|
|
420
686
|
attr_accessor :scenario_evaluation_adapter_resolver
|
|
421
687
|
|
|
688
|
+
# How many answerable candidates an exploration's review pre-selects for
|
|
689
|
+
# acceptance: an Integer, or a Proc called with (owner) that returns one.
|
|
690
|
+
# A platform can cap a free plan's suite this way. Unset, or nil from the
|
|
691
|
+
# Proc, pre-selects every answerable candidate. See
|
|
692
|
+
# {.exploration_preselect_limit_for}.
|
|
693
|
+
# @return [Integer, Proc, nil]
|
|
694
|
+
attr_accessor :exploration_preselect_limit
|
|
695
|
+
|
|
422
696
|
# Where the dashboard's upgrade CTAs should send people. Unset in a
|
|
423
697
|
# self-hosted install, where there is nothing to upgrade, and the CTAs
|
|
424
698
|
# say so instead of linking nowhere.
|
|
@@ -458,9 +732,17 @@ module ActionAgent
|
|
|
458
732
|
|
|
459
733
|
# Called after the dashboard performs a metered action, as
|
|
460
734
|
# (owner, kind) — the counterpart to quota_checker, for host apps that
|
|
461
|
-
# track usage against a plan. The kinds
|
|
462
|
-
#
|
|
463
|
-
#
|
|
735
|
+
# track usage against a plan. The kinds:
|
|
736
|
+
#
|
|
737
|
+
# :execution — each agent run
|
|
738
|
+
# :evaluation_report — each report the collector stores; an identical
|
|
739
|
+
# retry is not counted again
|
|
740
|
+
# :browser_minutes — each browser that stopped, with the minutes it
|
|
741
|
+
# ran, rounded up, as a third argument
|
|
742
|
+
#
|
|
743
|
+
# A recorder that takes a third argument receives the quantity, and nil
|
|
744
|
+
# for a kind that has none when the argument is required; one that takes
|
|
745
|
+
# two is called as (owner, kind). Unset means nothing is counted.
|
|
464
746
|
# @return [Proc, nil]
|
|
465
747
|
attr_accessor :usage_recorder
|
|
466
748
|
|
|
@@ -484,6 +766,13 @@ module ActionAgent
|
|
|
484
766
|
# @return [ActiveSupport::Duration, Proc, nil]
|
|
485
767
|
attr_accessor :trace_retention
|
|
486
768
|
|
|
769
|
+
# How long a run's request for input waits for an answer. Past it, the
|
|
770
|
+
# request expires and its run fails, once an answer, the request list,
|
|
771
|
+
# the run's page or InputRequestExpiryJob reaches it. One day by default;
|
|
772
|
+
# nil lets a request wait until it is answered or its run is cancelled.
|
|
773
|
+
# @return [ActiveSupport::Duration, nil]
|
|
774
|
+
attr_accessor :input_request_ttl
|
|
775
|
+
|
|
487
776
|
# Whether API keys and provider credentials are encrypted at rest with
|
|
488
777
|
# Active Record Encryption. On by default, which requires the host app
|
|
489
778
|
# to have run `rails db:encryption:init`. Turning it off stores those
|
|
@@ -505,6 +794,23 @@ module ActionAgent
|
|
|
505
794
|
# @return [String]
|
|
506
795
|
attr_accessor :github_oauth_scopes
|
|
507
796
|
|
|
797
|
+
# The GitHub App checkout sandboxes get repository access through
|
|
798
|
+
# (Settings -> Integrations -> Install the GitHub App). Each falls back to
|
|
799
|
+
# the matching GITHUB_APP_* variable (GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY,
|
|
800
|
+
# GITHUB_APP_SLUG, GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRET), and the
|
|
801
|
+
# dashboard offers no installation until all five are present. Register
|
|
802
|
+
# the App with "Request user authorization (OAuth) during installation"
|
|
803
|
+
# and "Redirect on update" turned on and
|
|
804
|
+
# <mount>/api/github_installations/callback as its callback URL, or create
|
|
805
|
+
# it from Settings with the manifest flow.
|
|
806
|
+
#
|
|
807
|
+
# The private key is the PEM GitHub generated for the App. A key whose
|
|
808
|
+
# line breaks were written as "\n" (one line in an env file) is read with
|
|
809
|
+
# real line breaks.
|
|
810
|
+
# @return [String, nil]
|
|
811
|
+
attr_writer :github_app_id, :github_app_private_key, :github_app_slug,
|
|
812
|
+
:github_app_client_id, :github_app_client_secret
|
|
813
|
+
|
|
508
814
|
# MCP servers the host app itself serves or connects, appended to the
|
|
509
815
|
# built-in catalog (MCPCatalog) so the MCP Services view lists them and
|
|
510
816
|
# telemetry traffic attributes to them. Each entry is a hash shaped like
|
|
@@ -586,6 +892,36 @@ module ActionAgent
|
|
|
586
892
|
github_client_id.present? && github_client_secret.present?
|
|
587
893
|
end
|
|
588
894
|
|
|
895
|
+
def github_app_id
|
|
896
|
+
(@github_app_id.presence || ENV["GITHUB_APP_ID"].presence)&.to_s
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
def github_app_private_key
|
|
900
|
+
key = @github_app_private_key.presence || ENV["GITHUB_APP_PRIVATE_KEY"].presence
|
|
901
|
+
return nil if key.nil?
|
|
902
|
+
|
|
903
|
+
key.include?("\n") ? key : key.gsub("\\n", "\n")
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
def github_app_slug
|
|
907
|
+
@github_app_slug.presence || ENV["GITHUB_APP_SLUG"].presence
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
def github_app_client_id
|
|
911
|
+
@github_app_client_id.presence || ENV["GITHUB_APP_CLIENT_ID"].presence
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
def github_app_client_secret
|
|
915
|
+
@github_app_client_secret.presence || ENV["GITHUB_APP_CLIENT_SECRET"].presence
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
# Whether the GitHub App installation flow can run on this install: every
|
|
919
|
+
# github_app_* setting is present.
|
|
920
|
+
# @return [Boolean]
|
|
921
|
+
def github_app_configured?
|
|
922
|
+
[ github_app_id, github_app_private_key, github_app_slug, github_app_client_id, github_app_client_secret ].all?(&:present?)
|
|
923
|
+
end
|
|
924
|
+
|
|
589
925
|
def multi_tenant?
|
|
590
926
|
@multi_tenant == true
|
|
591
927
|
end
|
|
@@ -612,6 +948,13 @@ module ActionAgent
|
|
|
612
948
|
@execution_enabled != false
|
|
613
949
|
end
|
|
614
950
|
|
|
951
|
+
# Returns whether the dashboard records the Run Agent workbench.
|
|
952
|
+
#
|
|
953
|
+
# @return [Boolean]
|
|
954
|
+
def capture_dashboard_sessions?
|
|
955
|
+
@capture_dashboard_sessions != false
|
|
956
|
+
end
|
|
957
|
+
|
|
615
958
|
# Returns whether the dashboard assistant is available. Unconfigured, it
|
|
616
959
|
# follows the environment: development and test yes, everywhere else no.
|
|
617
960
|
#
|
|
@@ -635,10 +978,13 @@ module ActionAgent
|
|
|
635
978
|
Pathname.new(@local_sandbox_root.presence || Rails.root.join("tmp", "action_agent", "sandboxes"))
|
|
636
979
|
end
|
|
637
980
|
|
|
638
|
-
# Tells the host app that +owner+ performed +kind
|
|
639
|
-
#
|
|
640
|
-
|
|
641
|
-
|
|
981
|
+
# Tells the host app that +owner+ performed +kind+, +quantity+ times when
|
|
982
|
+
# given (see usage_recorder). Never raises: a bookkeeping failure must not
|
|
983
|
+
# fail the action that was already taken.
|
|
984
|
+
def record_usage(owner, kind, quantity = nil)
|
|
985
|
+
return nil if usage_recorder.nil?
|
|
986
|
+
|
|
987
|
+
usage_recorder.call(*usage_arguments(usage_recorder, owner, kind, quantity))
|
|
642
988
|
rescue StandardError => e
|
|
643
989
|
Rails.logger.warn("[ActionAgent] usage recording failed: #{e.message}")
|
|
644
990
|
nil
|
|
@@ -657,6 +1003,21 @@ module ActionAgent
|
|
|
657
1003
|
UNLIMITED_USAGE.dup
|
|
658
1004
|
end
|
|
659
1005
|
|
|
1006
|
+
# The exploration_preselect_limit for +owner+, or nil for no limit. A
|
|
1007
|
+
# value that is not a non-negative Integer, and a Proc that raises, mean
|
|
1008
|
+
# no limit: the setting only trims a default selection, and the
|
|
1009
|
+
# reviewer can still select every candidate.
|
|
1010
|
+
#
|
|
1011
|
+
# @return [Integer, nil]
|
|
1012
|
+
def exploration_preselect_limit_for(owner)
|
|
1013
|
+
limit = exploration_preselect_limit
|
|
1014
|
+
limit = limit.call(owner) if limit.respond_to?(:call)
|
|
1015
|
+
limit.is_a?(Integer) && !limit.negative? ? limit : nil
|
|
1016
|
+
rescue StandardError => e
|
|
1017
|
+
Rails.logger.warn("[ActionAgent] exploration_preselect_limit failed: #{e.class}: #{e.message}")
|
|
1018
|
+
nil
|
|
1019
|
+
end
|
|
1020
|
+
|
|
660
1021
|
# Asks the host app whether +owner+ may perform +kind+.
|
|
661
1022
|
#
|
|
662
1023
|
# @return [String, Hash, nil] denial message or payload, nil when allowed
|
|
@@ -666,19 +1027,103 @@ module ActionAgent
|
|
|
666
1027
|
quota_checker.call(owner, kind)
|
|
667
1028
|
end
|
|
668
1029
|
|
|
669
|
-
#
|
|
670
|
-
#
|
|
1030
|
+
# Whether +user+ may perform +action+ on +subject+, as permission_checker
|
|
1031
|
+
# answers. Always true when no checker is configured, in either mode.
|
|
1032
|
+
# Otherwise:
|
|
1033
|
+
#
|
|
1034
|
+
# - an exception from the checker denies, and is logged
|
|
1035
|
+
# - in multi-tenant mode a nil +user+ denies without asking the checker,
|
|
1036
|
+
# and a nil answer denies
|
|
1037
|
+
# - in single-tenant mode a nil answer allows
|
|
1038
|
+
#
|
|
1039
|
+
# @raise [ArgumentError] when +action+ is not one of PERMISSION_ACTIONS
|
|
1040
|
+
# @return [Boolean]
|
|
1041
|
+
def permitted?(user, action, subject = nil)
|
|
1042
|
+
unless PERMISSION_ACTIONS.include?(action)
|
|
1043
|
+
raise ArgumentError, "Unknown permission action #{action.inspect}; expected one of #{PERMISSION_ACTIONS.join(', ')}"
|
|
1044
|
+
end
|
|
1045
|
+
return true if permission_checker.nil?
|
|
1046
|
+
return false if multi_tenant? && user.nil?
|
|
1047
|
+
|
|
1048
|
+
answer = begin
|
|
1049
|
+
permission_checker.call(user, action, subject)
|
|
1050
|
+
rescue StandardError => e
|
|
1051
|
+
Rails.logger.error("[ActionAgent] permission_checker raised for #{action}, denying: #{e.class}: #{e.message}")
|
|
1052
|
+
return false
|
|
1053
|
+
end
|
|
1054
|
+
return !multi_tenant? if answer.nil?
|
|
1055
|
+
|
|
1056
|
+
answer ? true : false
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1059
|
+
# Logs a warning when the install is multi-tenant and has no
|
|
1060
|
+
# permission_checker, because every member of a tenant may then perform
|
|
1061
|
+
# every privileged action. The engine calls it once the host's
|
|
1062
|
+
# initializers have run.
|
|
1063
|
+
#
|
|
1064
|
+
# @return [Boolean] whether it warned
|
|
1065
|
+
def warn_about_unchecked_permissions
|
|
1066
|
+
return false unless multi_tenant? && permission_checker.nil?
|
|
1067
|
+
|
|
1068
|
+
Rails.logger&.warn(
|
|
1069
|
+
"[ActionAgent] multi_tenant is on and no permission_checker is configured, so every member of a tenant " \
|
|
1070
|
+
"may store provider credentials, connect GitHub and create API keys. Set config.permission_checker to " \
|
|
1071
|
+
"restrict them."
|
|
1072
|
+
)
|
|
1073
|
+
true
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
# Provider options the host's provider_credentials_resolver returns for
|
|
1077
|
+
# +owner+, or {} when it is unset or has none. +actor+ is passed only to
|
|
1078
|
+
# a resolver that declares an actor: keyword or accepts **; any other is
|
|
1079
|
+
# called with (owner, provider).
|
|
1080
|
+
#
|
|
1081
|
+
# A resolver that raises is logged and yields {} on a single-tenant
|
|
1082
|
+
# install. On a multi-tenant install it raises
|
|
1083
|
+
# ActionAgent::ProviderCredentials::Unresolved instead, so the run
|
|
1084
|
+
# fails rather than generating on the platform's own credentials.
|
|
1085
|
+
#
|
|
1086
|
+
# Callers resolving what a generation runs on use
|
|
1087
|
+
# ActionAgent::ProviderCredentials.resolve, which also tries the
|
|
1088
|
+
# dashboard's stored keys.
|
|
671
1089
|
#
|
|
672
1090
|
# @return [Hash]
|
|
673
|
-
def provider_credentials(owner, provider)
|
|
1091
|
+
def provider_credentials(owner, provider, actor: nil)
|
|
674
1092
|
return {} if provider_credentials_resolver.nil?
|
|
675
1093
|
|
|
676
|
-
|
|
1094
|
+
resolver = provider_credentials_resolver
|
|
1095
|
+
result = if resolver_accepts_actor?(resolver)
|
|
1096
|
+
resolver.call(owner, provider, actor: actor)
|
|
1097
|
+
else
|
|
1098
|
+
resolver.call(owner, provider)
|
|
1099
|
+
end
|
|
1100
|
+
result || {}
|
|
677
1101
|
rescue StandardError => e
|
|
1102
|
+
if multi_tenant?
|
|
1103
|
+
raise ActionAgent::ProviderCredentials::Unresolved,
|
|
1104
|
+
"The provider_credentials_resolver failed for #{provider} (#{e.class})"
|
|
1105
|
+
end
|
|
1106
|
+
|
|
678
1107
|
Rails.logger.warn("[ActionAgent] provider credential lookup failed: #{e.message}")
|
|
679
1108
|
{}
|
|
680
1109
|
end
|
|
681
1110
|
|
|
1111
|
+
# The members ActionAgent.members_resolver lists for +owner+, each
|
|
1112
|
+
# reduced to id, name, email and role. Nil when no resolver is set or it
|
|
1113
|
+
# raised (logged), so the caller can list the signed-in user instead.
|
|
1114
|
+
#
|
|
1115
|
+
# @return [Array<Hash>, nil]
|
|
1116
|
+
def members_for(owner)
|
|
1117
|
+
return nil if members_resolver.nil?
|
|
1118
|
+
|
|
1119
|
+
Array(members_resolver.call(owner)).map do |member|
|
|
1120
|
+
member.to_h.symbolize_keys.slice(:id, :name, :email, :role)
|
|
1121
|
+
end
|
|
1122
|
+
rescue StandardError => e
|
|
1123
|
+
Rails.logger.warn("[ActionAgent] members_resolver failed: #{e.class}: #{e.message}")
|
|
1124
|
+
nil
|
|
1125
|
+
end
|
|
1126
|
+
|
|
682
1127
|
# Returns the trace model class to use.
|
|
683
1128
|
#
|
|
684
1129
|
# @return [Class] The trace model class
|
|
@@ -761,17 +1206,28 @@ module ActionAgent
|
|
|
761
1206
|
@layout = nil
|
|
762
1207
|
@sandbox_service = :mock
|
|
763
1208
|
@sandbox_limits = nil
|
|
764
|
-
@
|
|
1209
|
+
@recording_limits = nil
|
|
1210
|
+
@active_storage = :auto
|
|
1211
|
+
@active_storage_service = nil
|
|
765
1212
|
@ingest_api_key = nil
|
|
766
1213
|
@base_controller_class = "ActionController::Base" # deprecated no-op
|
|
767
1214
|
@model_concerns = []
|
|
768
1215
|
@controller_concerns = []
|
|
769
1216
|
@quota_checker = nil
|
|
1217
|
+
@permission_checker = nil
|
|
770
1218
|
@provider_credentials_resolver = nil
|
|
1219
|
+
@provider_key_scope = :organization
|
|
1220
|
+
@members_resolver = nil
|
|
1221
|
+
@member_invite_url = nil
|
|
771
1222
|
@sandbox_backends = {}
|
|
772
1223
|
@local_sandboxes_enabled = nil
|
|
773
1224
|
@local_sandbox_root = nil
|
|
774
1225
|
@local_sandbox_boot_timeout = 600
|
|
1226
|
+
@browser_sidecar_path = nil
|
|
1227
|
+
@node_command = "node"
|
|
1228
|
+
@npm_command = "npm"
|
|
1229
|
+
@browser_start_timeout = 60
|
|
1230
|
+
@browser_live_origins = []
|
|
775
1231
|
@claude_code_command = "claude"
|
|
776
1232
|
@claude_code_permission_mode = "acceptEdits"
|
|
777
1233
|
@claude_code_max_turns = nil
|
|
@@ -779,17 +1235,26 @@ module ActionAgent
|
|
|
779
1235
|
@codex_command = "codex"
|
|
780
1236
|
@codex_timeout = 1800
|
|
781
1237
|
@claude_code_auth = :api_key
|
|
1238
|
+
@claude_code_hosted_login_enabled = false
|
|
1239
|
+
@claude_code_login_timeout = 300
|
|
782
1240
|
@execution_enabled = true
|
|
783
1241
|
@run_host_agent_classes = false
|
|
784
1242
|
@assistant_enabled = nil
|
|
1243
|
+
@capture_dashboard_sessions = true
|
|
785
1244
|
|
|
786
1245
|
@scenario_evaluation_adapter_resolver = nil
|
|
787
1246
|
@table_name_prefix = "active_agent_"
|
|
788
1247
|
@agent_polymorphic_name = nil
|
|
789
1248
|
@encrypt_credentials = true
|
|
1249
|
+
@input_request_ttl = 1.day
|
|
790
1250
|
@github_client_id = nil
|
|
791
1251
|
@github_client_secret = nil
|
|
792
1252
|
@github_oauth_scopes = "repo read:user"
|
|
1253
|
+
@github_app_id = nil
|
|
1254
|
+
@github_app_private_key = nil
|
|
1255
|
+
@github_app_slug = nil
|
|
1256
|
+
@github_app_client_id = nil
|
|
1257
|
+
@github_app_client_secret = nil
|
|
793
1258
|
@trace_retention = nil
|
|
794
1259
|
@trace_owner_resolver = nil
|
|
795
1260
|
@usage_recorder = nil
|
|
@@ -803,6 +1268,7 @@ module ActionAgent
|
|
|
803
1268
|
@schema_tools_path = "app/agent_tools"
|
|
804
1269
|
@mcp_schema_tools = nil
|
|
805
1270
|
@mcp_dashboard_tools = nil
|
|
1271
|
+
@exploration_preselect_limit = nil
|
|
806
1272
|
end
|
|
807
1273
|
|
|
808
1274
|
# Host-declared schema tool classes, resolved from names and filtered to
|
|
@@ -883,9 +1349,28 @@ module ActionAgent
|
|
|
883
1349
|
|
|
884
1350
|
private
|
|
885
1351
|
|
|
1352
|
+
def resolver_accepts_actor?(resolver)
|
|
1353
|
+
parameters = resolver.respond_to?(:parameters) ? resolver.parameters : resolver.method(:call).parameters
|
|
1354
|
+
parameters.any? { |type, name| type == :keyrest || (%i[key keyreq].include?(type) && name == :actor) }
|
|
1355
|
+
end
|
|
1356
|
+
|
|
886
1357
|
def resolve_concerns(entries)
|
|
887
1358
|
Array(entries).map { |entry| entry.is_a?(Module) ? entry : entry.to_s.constantize }
|
|
888
1359
|
end
|
|
1360
|
+
|
|
1361
|
+
# What +recorder+ is called with: the quantity when there is one and it
|
|
1362
|
+
# takes a third argument, nil in its place when it requires one, and
|
|
1363
|
+
# nothing more otherwise.
|
|
1364
|
+
def usage_arguments(recorder, owner, kind, quantity)
|
|
1365
|
+
parameters = (recorder.is_a?(Proc) || recorder.is_a?(Method) ? recorder : recorder.method(:call)).parameters
|
|
1366
|
+
third =
|
|
1367
|
+
if quantity.nil?
|
|
1368
|
+
parameters.count { |type, _| type == :req } >= 3
|
|
1369
|
+
else
|
|
1370
|
+
parameters.count { |type, _| %i[req opt].include?(type) } >= 3 || parameters.any? { |type, _| type == :rest }
|
|
1371
|
+
end
|
|
1372
|
+
third ? [ owner, kind, quantity ] : [ owner, kind ]
|
|
1373
|
+
end
|
|
889
1374
|
end
|
|
890
1375
|
|
|
891
1376
|
# Set defaults
|