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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionAgent
|
|
4
|
+
# Used to store a batch of events a browser posted to a session recording.
|
|
5
|
+
#
|
|
6
|
+
# A batch is a JSON object:
|
|
7
|
+
#
|
|
8
|
+
# {
|
|
9
|
+
# "sent_at": 1767225600000,
|
|
10
|
+
# "recording_events": [
|
|
11
|
+
# { "kind": "rrweb", "timestamp": 1767225599500, "data": { "type": 3, "data": {} } },
|
|
12
|
+
# { "kind": "console", "timestamp": 1767225599800, "data": { "level": "error", "message": "boom" } }
|
|
13
|
+
# ]
|
|
14
|
+
# }
|
|
15
|
+
#
|
|
16
|
+
# `sent_at` is the client's clock when it sent the batch, and `timestamp`
|
|
17
|
+
# the client's clock when the event happened, both in epoch milliseconds
|
|
18
|
+
# (`sent_at` may also be ISO 8601). The batch's clock offset, receive time
|
|
19
|
+
# minus `sent_at`, is added to every timestamp, so a batch from a client
|
|
20
|
+
# whose clock is wrong is still stored in server time. An event without a
|
|
21
|
+
# timestamp is placed at `sent_at`. A timestamp more than EVENT_WINDOW_BEFORE
|
|
22
|
+
# before `sent_at`, or more than EVENT_WINDOW_AFTER after it, refuses the
|
|
23
|
+
# batch.
|
|
24
|
+
#
|
|
25
|
+
# The batch is stored whole or not at all. RecordingEvent.limits caps it,
|
|
26
|
+
# and caps the recording's totals. A refused batch over a cap is counted
|
|
27
|
+
# in the recording's dropped_event_count.
|
|
28
|
+
#
|
|
29
|
+
# Given +secrets+, every string in the batch's events has each of them
|
|
30
|
+
# masked (SecretScrubber) before it is stored.
|
|
31
|
+
class RecordingEventIngest
|
|
32
|
+
# The outcome as an HTTP response: +status+ and the JSON +body+.
|
|
33
|
+
Result = Struct.new(:status, :body, keyword_init: true)
|
|
34
|
+
|
|
35
|
+
PAYLOAD_TOO_LARGE = 413
|
|
36
|
+
|
|
37
|
+
# The key a batch carries its events under. The engine's filter_parameters
|
|
38
|
+
# initializer keeps it out of request logs, so it is a name no host
|
|
39
|
+
# parameter is likely to share.
|
|
40
|
+
BATCH_KEY = "recording_events"
|
|
41
|
+
|
|
42
|
+
EVENT_WINDOW_BEFORE = 24.hours
|
|
43
|
+
EVENT_WINDOW_AFTER = 1.minute
|
|
44
|
+
# 1970 through the end of 9999, in epoch milliseconds: what every
|
|
45
|
+
# supported database can store.
|
|
46
|
+
SENT_AT_RANGE = (0..253_402_300_799_999).freeze
|
|
47
|
+
|
|
48
|
+
# @param recording [SessionRecording] the recording the batch was posted to
|
|
49
|
+
# @param body [String] the request body
|
|
50
|
+
# @param kinds [Array<String>] the event kinds the caller may write
|
|
51
|
+
# @param secrets [Array<String>] values to mask in the events
|
|
52
|
+
# @return [Result]
|
|
53
|
+
def self.call(recording, body, received_at: Time.current, kinds: RecordingEvent::CLIENT_KINDS, secrets: [])
|
|
54
|
+
new(recording, body, received_at: received_at, kinds: kinds, secrets: secrets).call
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize(recording, body, received_at:, kinds:, secrets: [])
|
|
58
|
+
@recording = recording
|
|
59
|
+
@body = body.to_s
|
|
60
|
+
@received_at = received_at
|
|
61
|
+
@kinds = kinds.map(&:to_s)
|
|
62
|
+
@secrets = secrets
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def call
|
|
66
|
+
batch = parse_body
|
|
67
|
+
return invalid("The batch nests deeper than #{RecordingEvent::MAX_NESTING} levels") if batch == :too_deep
|
|
68
|
+
|
|
69
|
+
events = batch.is_a?(Hash) ? batch[BATCH_KEY] : nil
|
|
70
|
+
return invalid("The body must be a JSON object with a non-empty #{BATCH_KEY} array") unless events.is_a?(Array) && events.any?
|
|
71
|
+
|
|
72
|
+
limits = RecordingEvent.limits
|
|
73
|
+
if events.size > limits[:batch_events] || @body.bytesize > limits[:batch_bytes]
|
|
74
|
+
return too_large(events.size, "The batch is over the limit of #{limits[:batch_events]} events or #{limits[:batch_bytes]} bytes")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
sent_at = epoch_milliseconds(batch["sent_at"])
|
|
78
|
+
return invalid("sent_at must be the client's send time, in epoch milliseconds or ISO 8601") unless sent_at
|
|
79
|
+
|
|
80
|
+
problem = event_problem(events, sent_at)
|
|
81
|
+
return invalid(problem) if problem
|
|
82
|
+
|
|
83
|
+
offset = RecordingEvent.milliseconds(@received_at) - sent_at
|
|
84
|
+
store(build_rows(SecretScrubber.scrub(events, @secrets), sent_at, offset), offset, limits)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# The parsed body, :too_deep when it nests past RecordingEvent::MAX_NESTING,
|
|
90
|
+
# or nil when it is not JSON.
|
|
91
|
+
def parse_body
|
|
92
|
+
RecordingEvent.parse_json(@body)
|
|
93
|
+
rescue JSON::NestingError
|
|
94
|
+
:too_deep
|
|
95
|
+
rescue JSON::ParserError
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Why +events+ cannot be stored, or nil when they can.
|
|
100
|
+
def event_problem(events, sent_at)
|
|
101
|
+
return "Every event must be a JSON object" unless events.all?(Hash)
|
|
102
|
+
|
|
103
|
+
refused = events.map { |event| event["kind"].to_s.presence || "(missing)" }.uniq - @kinds
|
|
104
|
+
return "Event kinds not accepted here: #{refused.join(', ')}. Accepted: #{@kinds.join(', ')}" if refused.any?
|
|
105
|
+
|
|
106
|
+
return "An event timestamp must be epoch milliseconds" unless events.all? { |event| event["timestamp"].nil? || event["timestamp"].is_a?(Numeric) }
|
|
107
|
+
|
|
108
|
+
window = (sent_at - EVENT_WINDOW_BEFORE.in_milliseconds)..(sent_at + EVENT_WINDOW_AFTER.in_milliseconds)
|
|
109
|
+
unless events.all? { |event| event["timestamp"].nil? || window.cover?(event["timestamp"]) }
|
|
110
|
+
return "An event timestamp must be within #{EVENT_WINDOW_BEFORE.inspect} before sent_at and #{EVENT_WINDOW_AFTER.inspect} after it"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# One unsaved row per kind, holding that kind's events in batch order.
|
|
117
|
+
def build_rows(events, sent_at, offset)
|
|
118
|
+
grouped = {}
|
|
119
|
+
events.each_with_index do |event, index|
|
|
120
|
+
group = (grouped[event["kind"].to_s] ||= { index: index, events: [] })
|
|
121
|
+
client_time = event["timestamp"] || sent_at
|
|
122
|
+
group[:events] << { "at" => client_time.floor + offset, "data" => event["data"] }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
grouped.map do |kind, group|
|
|
126
|
+
row = RecordingEvent.new(session_recording: @recording, kind: kind, batch_index: group[:index], clock_offset_ms: offset)
|
|
127
|
+
row.events = group[:events]
|
|
128
|
+
row
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def store(rows, offset, limits)
|
|
133
|
+
count = rows.sum(&:event_count)
|
|
134
|
+
bytes = rows.sum(&:byte_size)
|
|
135
|
+
outcome = nil
|
|
136
|
+
|
|
137
|
+
@recording.with_lock do
|
|
138
|
+
outcome =
|
|
139
|
+
if !@recording.recording?
|
|
140
|
+
:finished
|
|
141
|
+
elsif @recording.event_count + count > limits[:recording_events] || @recording.event_bytes + bytes > limits[:recording_bytes]
|
|
142
|
+
:over_cap
|
|
143
|
+
else
|
|
144
|
+
rows.each(&:save!)
|
|
145
|
+
@recording.update_columns(event_count: @recording.event_count + count, event_bytes: @recording.event_bytes + bytes)
|
|
146
|
+
:stored
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
case outcome
|
|
151
|
+
when :finished then invalid("Recording already completed")
|
|
152
|
+
when :over_cap
|
|
153
|
+
too_large(count, "The recording is over the limit of #{limits[:recording_events]} events or #{limits[:recording_bytes]} bytes")
|
|
154
|
+
else
|
|
155
|
+
Result.new(status: :created, body: { stored: count, clock_offset_ms: offset })
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# +value+ in epoch milliseconds, or nil when it is not a time in
|
|
160
|
+
# SENT_AT_RANGE.
|
|
161
|
+
def epoch_milliseconds(value)
|
|
162
|
+
milliseconds =
|
|
163
|
+
case value
|
|
164
|
+
when Numeric then value.floor if value.finite?
|
|
165
|
+
when String then RecordingEvent.milliseconds(Time.iso8601(value))
|
|
166
|
+
end
|
|
167
|
+
milliseconds if SENT_AT_RANGE.cover?(milliseconds)
|
|
168
|
+
rescue ArgumentError
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def invalid(message)
|
|
173
|
+
Result.new(status: :unprocessable_entity, body: { error: message })
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def too_large(dropped, message)
|
|
177
|
+
SessionRecording.update_counters(@recording.id, dropped_event_count: dropped)
|
|
178
|
+
Result.new(status: PAYLOAD_TOO_LARGE, body: { error: message, code: "recording_limit", dropped: dropped })
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionAgent
|
|
4
|
+
# Used to list the tools an agent can call in a run: the engine's toolbox
|
|
5
|
+
# tools its saved +tools+ name, and whatever its MCP servers and the run's
|
|
6
|
+
# extra checkout sandboxes and browsers serve (MCPToolDispatcher). When a
|
|
7
|
+
# sandbox's browser is attached, the toolbox's own browser tools drop. A scenario run's
|
|
8
|
+
# diagnosis and an exploration's answerability verdicts read the same list.
|
|
9
|
+
#
|
|
10
|
+
# AgentToolRoster is a different list: the agent editor's view of every
|
|
11
|
+
# tool and service the agent could be given, with usage.
|
|
12
|
+
class RuntimeToolRoster
|
|
13
|
+
attr_reader :agent, :dispatcher
|
|
14
|
+
|
|
15
|
+
# @param agent [Agent]
|
|
16
|
+
# @param extra_server_keys [Array<String>] "sandbox:<session_id>" and
|
|
17
|
+
# "browser:<session_id>" keys the run reaches beside the agent's own
|
|
18
|
+
# servers
|
|
19
|
+
def initialize(agent, extra_server_keys: [])
|
|
20
|
+
@agent = agent
|
|
21
|
+
@dispatcher = MCPToolDispatcher.new(agent, extra_server_keys: extra_server_keys)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns { name => description } for every tool the agent can call. A
|
|
25
|
+
# server that fails discovery contributes nothing and is named in
|
|
26
|
+
# #discovery_errors.
|
|
27
|
+
#
|
|
28
|
+
# @raise [MCPToolDispatcher::SandboxUnavailable] when an extra sandbox is
|
|
29
|
+
# not live or does not answer discovery
|
|
30
|
+
# @return [Hash{String => String}]
|
|
31
|
+
def tools
|
|
32
|
+
@tools ||= (dispatcher.tool_definitions +
|
|
33
|
+
AgentToolbox.definitions_for(agent.tools, browser_attached: dispatcher.browser_attached?))
|
|
34
|
+
.to_h { |definition| [ definition[:name].to_s, definition[:description].to_s ] }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Why each of the agent's own servers contributed no tools, keyed by
|
|
38
|
+
# server; empty when every one answered.
|
|
39
|
+
#
|
|
40
|
+
# @return [Hash{String => String}]
|
|
41
|
+
def discovery_errors
|
|
42
|
+
tools
|
|
43
|
+
dispatcher.discovery_errors
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Whether every server the agent declares failed discovery.
|
|
47
|
+
def all_servers_failed?
|
|
48
|
+
tools
|
|
49
|
+
dispatcher.all_servers_failed?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|