actionagent 1.9.4 → 1.9.5

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -1
  3. data/app/assets/builds/action_agent.js +61 -61
  4. data/app/controllers/action_agent/api/agents_controller.rb +1 -17
  5. data/app/controllers/action_agent/api/evaluation_reports_controller.rb +11 -7
  6. data/app/controllers/action_agent/api/github_app_webhooks_controller.rb +98 -0
  7. data/app/controllers/action_agent/api/github_installations_controller.rb +9 -1
  8. data/app/controllers/action_agent/api/mcp_controller.rb +5 -2
  9. data/app/controllers/action_agent/api/projects_controller.rb +119 -40
  10. data/app/controllers/action_agent/dashboard_controller.rb +26 -0
  11. data/app/controllers/concerns/action_agent/api/mcp_dashboard_tools.rb +199 -24
  12. data/app/jobs/action_agent/github_webhook_job.rb +102 -0
  13. data/app/models/action_agent/github_connection.rb +21 -0
  14. data/app/models/action_agent/github_installation.rb +28 -1
  15. data/app/models/action_agent/github_webhook_delivery.rb +45 -0
  16. data/app/models/action_agent/provider_key.rb +8 -1
  17. data/app/models/action_agent/scenario_catalog.rb +8 -2
  18. data/app/serializers/action_agent/agent_version_serializer.rb +29 -0
  19. data/app/serializers/action_agent/evaluation_serializer.rb +9 -0
  20. data/app/services/action_agent/evaluation_report_import.rb +112 -19
  21. data/app/services/action_agent/github_client.rb +11 -2
  22. data/app/services/action_agent/local_sandbox_claude_login.rb +1 -1
  23. data/app/services/action_agent/project_capabilities.rb +6 -3
  24. data/config/routes.rb +15 -0
  25. data/lib/action_agent/claude_login_process.rb +1 -1
  26. data/lib/action_agent/version.rb +1 -1
  27. data/lib/action_agent.rb +15 -0
  28. data/lib/generators/action_agent/templates/action_agent.rb.erb +6 -0
  29. data/lib/generators/action_agent/templates/migrations/029_create_active_agent_github_webhook_deliveries.rb.erb +23 -0
  30. metadata +7 -2
@@ -17,9 +17,18 @@ module ActionAgent
17
17
  # evaluation — that agent's evaluation for the `suite` and the report's
18
18
  # scope (SCOPE_KEYS in its metadata), named for both
19
19
  # scenarios — one per reported scenario key, updated to the reported prompt
20
- # run — one complete EvaluationRun per tenant and run_id, with one
20
+ # run — one EvaluationRun per tenant and run_id, with one
21
21
  # EvaluationScenarioResult per scenario and model
22
22
  #
23
+ # An envelope's `status` is "complete" when absent. A "running" envelope
24
+ # stores the results so far as a running run, and each later delivery under
25
+ # its run_id replaces them, until a "complete" one finishes the run:
26
+ # running — "running" or "complete" replaces the stored results
27
+ # complete — "complete" is an identical retry or a Conflict, as for any
28
+ # stored run; "running" changes nothing, so a progress
29
+ # delivery that lands after the final one is harmless
30
+ # Only the first delivery under a run_id is admitted and counted.
31
+ #
23
32
  # The run's per-model summary, criterion scores and recommendations are
24
33
  # computed from the stored results, not taken from the report. The judge's
25
34
  # verdict and label are taken from it, and so is what the judge spent —
@@ -85,6 +94,11 @@ module ActionAgent
85
94
  MAX_JUDGE_KINDS = 10
86
95
  SCOPE_PATTERN = %r{\A[\w .:/@-]{1,100}\z}
87
96
  STATUSES = %w[passed failed errored].freeze
97
+ # The envelope statuses a delivery may name.
98
+ RUN_STATUSES = %w[running complete].freeze
99
+ # What this collector accepts beyond a version-1 report, sent with its
100
+ # refusal of a payload that is not one so a publisher can ask before a run.
101
+ CAPABILITIES = %w[progress].freeze
88
102
  # Report metadata that tells one evaluation of a suite from another, in the
89
103
  # order it is written into the evaluation's name.
90
104
  SCOPE_KEYS = %w[scope environment role].freeze
@@ -102,20 +116,22 @@ module ActionAgent
102
116
  # The evaluation_runs columns an import writes that later migrations added.
103
117
  REQUIRED_RUN_COLUMNS = %w[external_tenant external_run_id external_report_digest agent_version_id].freeze
104
118
 
105
- # Returns `[run, duplicate]`: the stored EvaluationRun, and whether an
106
- # earlier identical delivery had already stored it. `account` is the
107
- # tenant, or nil on a single-tenant install.
119
+ # Returns `[run, duplicate, progressed]`: the stored EvaluationRun, whether
120
+ # an earlier delivery had already stored this report, and whether this
121
+ # delivery replaced the results of a running run. `account` is the tenant,
122
+ # or nil on a single-tenant install.
108
123
  #
109
- # `admit` is asked only for a report that would be stored, never for an
110
- # identical retry. It returns nil to allow the report, or a denial, which
111
- # is raised as Denied.
124
+ # `admit` is asked only for a run_id the tenant holds no run under. It
125
+ # returns nil to allow the report, or a denial, which is raised as Denied.
112
126
  #
113
127
  # The envelope's identities are validated as sent. NUL characters, which
114
128
  # PostgreSQL cannot store, are removed from every string inside `report`.
115
129
  #
116
130
  # Raises Invalid for a payload that is not a valid version-1 report, or an
117
131
  # evaluation name the agent already holds for another suite or scope;
118
- # Conflict for different content under a stored run_id; Refused (and
132
+ # Conflict for different content under a complete run_id, or a delivery
133
+ # under a running run_id that the dashboard executed or that names another
134
+ # agent, suite or scope; Refused (and
119
135
  # LimitExceeded) when storing needs an operator to act first; Denied; and
120
136
  # Busy.
121
137
  def self.call(payload:, account: nil, admit: nil)
@@ -162,24 +178,25 @@ module ActionAgent
162
178
  validate!
163
179
  digest = self.class.digest(@payload)
164
180
  existing = stored_run
165
- return [ identical!(existing, digest), true ] if existing
181
+ return settled(existing, digest) if settles?(existing, digest)
166
182
 
167
183
  attempts = 0
168
184
  begin
169
185
  OwnerLock.new(EvaluationRun.connection, owner_lock_key).synchronize do
170
186
  EvaluationRun.transaction(requires_new: true) do
171
187
  existing = stored_run
172
- next [ identical!(existing, digest), true ] if existing
188
+ next settled(existing, digest) if settles?(existing, digest)
189
+ next [ progress(existing, digest), false, true ] if existing
173
190
 
174
191
  admit!
175
- [ import(digest), false ]
192
+ [ import(digest), false, false ]
176
193
  end
177
194
  end
178
195
  rescue ActiveRecord::RecordNotUnique
179
196
  # A concurrent delivery committed this run, or an agent, evaluation or
180
197
  # scenario this import was creating, first. Reading again sees it.
181
198
  existing = stored_run
182
- return [ identical!(existing, digest), true ] if existing
199
+ return settled(existing, digest) if settles?(existing, digest)
183
200
 
184
201
  attempts += 1
185
202
  retry if attempts < 2
@@ -265,6 +282,29 @@ module ActionAgent
265
282
  EvaluationReportExport.find(@payload["run_id"], account: @account)
266
283
  end
267
284
 
285
+ def running_delivery?
286
+ @payload["status"] == "running"
287
+ end
288
+
289
+ # Whether a delivery under a stored run's run_id leaves its results as
290
+ # they are. Only a running run this collector imported takes new ones.
291
+ def settles?(run, digest)
292
+ return false unless run
293
+ return true unless run.running? && run.imported?
294
+
295
+ run.external_report_digest == digest
296
+ end
297
+
298
+ # Returns `[run, true, false]` for a delivery that leaves the stored run as
299
+ # it is. Raises Conflict for a run the dashboard is executing, and for a
300
+ # different "complete" report under a run that has finished.
301
+ def settled(run, digest)
302
+ raise Conflict, "run_id names a run the dashboard is executing" if run.running? && !run.imported?
303
+ return [ run, true, false ] if running_delivery? || run.running?
304
+
305
+ [ identical!(run, digest), true, false ]
306
+ end
307
+
268
308
  # The stored run, when the payload is the report it was stored from or
269
309
  # the run's own export (EvaluationReportExport), which is how an
270
310
  # exported run posted back to its install is told from a different
@@ -286,26 +326,60 @@ module ActionAgent
286
326
  raise Denied, denial if denial.present?
287
327
  end
288
328
 
329
+ # Replaces a running run's results with the delivery's.
330
+ def progress(run, digest)
331
+ agent = find_or_create_agent
332
+ evaluation = find_or_initialize_evaluation(agent)
333
+ raise Conflict, "run_id already exists for another agent, suite or scope" unless evaluation.id == run.evaluation_id
334
+
335
+ scenarios = upsert_scenarios(evaluation)
336
+ run.scenario_results.destroy_all
337
+ run.update!(run_attributes(agent, digest))
338
+ store_results(run, scenarios, agent)
339
+ end
340
+
341
+ # A new run is dated by the time its report says it finished, when it
342
+ # names one (see #finished_at), so a run exported from one install and
343
+ # imported into another keeps its place in the run history.
289
344
  def import(digest)
290
345
  agent = find_or_create_agent
291
346
  evaluation = find_or_initialize_evaluation(agent)
292
347
  scenarios = upsert_scenarios(evaluation)
293
348
  run = evaluation.evaluation_runs.create!(
294
- external_tenant: tenant_key,
295
- external_run_id: @payload["run_id"],
349
+ run_attributes(agent, digest).merge(external_tenant: tenant_key, external_run_id: @payload["run_id"], created_at: finished_at)
350
+ )
351
+ store_results(run, scenarios, agent)
352
+ end
353
+
354
+ def run_attributes(agent, digest)
355
+ {
296
356
  external_report_digest: digest,
297
357
  agent_version: reported_release_version(agent),
298
- status: :complete,
358
+ status: running_delivery? ? :running : :complete,
299
359
  selection: selection,
300
360
  scores: recorded_scores,
301
361
  samples_evaluated: results.size,
302
362
  samples_passed: results.count { |result| result["status"] == "passed" },
303
- completed_at: Time.current
304
- )
363
+ completed_at: running_delivery? ? nil : finished_at
364
+ }
365
+ end
366
+
367
+ # The report's `metadata.finished`, which EvaluationRun#to_report records
368
+ # as the run's completion time, or now when the report names none. A
369
+ # time later than now is read as now.
370
+ def finished_at
371
+ @finished_at ||= begin
372
+ now = Time.current
373
+ reported = metadata["finished"] && Time.iso8601(metadata["finished"])
374
+ reported && reported < now ? reported : now
375
+ end
376
+ end
377
+
378
+ def store_results(run, scenarios, agent)
305
379
  results.each { |result| persist(run, scenarios.fetch(result["scenario_key"]), result) }
306
380
  run.update!(scores: summarized_scores(run))
307
- evaluation.touch
308
- agent.update_columns(last_observed_at: run.completed_at, updated_at: Time.current)
381
+ run.evaluation.touch
382
+ agent.update_columns(last_observed_at: Time.current, updated_at: Time.current)
309
383
  run
310
384
  end
311
385
 
@@ -621,6 +695,9 @@ module ActionAgent
621
695
  object!(@payload, "payload")
622
696
  validate_json!(@payload)
623
697
  raise Invalid, "version must be 1" unless @payload["version"] == 1
698
+ unless @payload["status"].nil? || RUN_STATUSES.include?(@payload["status"])
699
+ raise Invalid, "status must be one of #{RUN_STATUSES.join(', ')}"
700
+ end
624
701
 
625
702
  %w[run_id source suite].each { |key| identifier!(@payload[key], key) }
626
703
  unless @payload["agent_name"].is_a?(String) && AGENT_NAME_PATTERN.match?(@payload["agent_name"]) && @payload["agent_name"].strip.length >= 2
@@ -634,6 +711,7 @@ module ActionAgent
634
711
  raise Invalid, "suite and scope name an evaluation longer than #{MAX_EVALUATION_NAME} characters"
635
712
  end
636
713
 
714
+ validate_finished!
637
715
  judge_trace_ids!(metadata["judge_trace_ids"])
638
716
  string!(report["judge"], "report.judge", 200)
639
717
  validate_release!
@@ -736,6 +814,21 @@ module ActionAgent
736
814
  string!(judge.dig("suggested_tool", "name"), "result.diagnosis.judge.suggested_tool.name", 200)
737
815
  end
738
816
 
817
+ def validate_finished!
818
+ finished = metadata["finished"]
819
+ return if finished.nil?
820
+ return if finished.is_a?(String) && finished.length <= 40 && iso8601?(finished)
821
+
822
+ raise Invalid, "report.metadata.finished must be an ISO 8601 time"
823
+ end
824
+
825
+ def iso8601?(value)
826
+ Time.iso8601(value)
827
+ true
828
+ rescue ArgumentError
829
+ false
830
+ end
831
+
739
832
  # The release the report says it evaluated: a digest the dashboard can
740
833
  # match to a version cut on deploy, and the deploy and label as text.
741
834
  def validate_release!
@@ -60,6 +60,10 @@ module ActionAgent
60
60
  # cannot see, which GitHub does not tell apart. Also 409, which GitHub
61
61
  # answers for the commits and trees of a repository with no commits.
62
62
  class NotFound < Error; end
63
+ # GitHub answered 403 to an organization membership lookup: the GitHub
64
+ # App lacks the organization permission Members (read), or the
65
+ # organization's SAML single sign-on has not authorized the token.
66
+ class MembershipForbidden < Error; end
63
67
  # GitHub minted no token because the installation was removed from its
64
68
  # account (404) or suspended (403). +reason+ is :removed or :suspended.
65
69
  class InstallationUnavailable < Error
@@ -323,14 +327,19 @@ module ActionAgent
323
327
 
324
328
  # The user's membership in +organization+ ({ "state" =>, "role" => }), or
325
329
  # nil when GitHub reports none.
330
+ #
331
+ # @raise [MembershipForbidden] when GitHub answers 403, as it does to a
332
+ # GitHub App user token while the App lacks Members (read)
326
333
  def organization_membership(organization)
327
334
  raise Error, "#{organization.inspect} is not a GitHub login" unless organization.is_a?(String) && organization.match?(LOGIN)
328
335
 
329
336
  get("/user/memberships/orgs/#{organization}")
337
+ rescue NotFound
338
+ nil
330
339
  rescue Error => e
331
- raise unless e.status.in?([ 403, 404 ])
340
+ raise unless e.status == 403
332
341
 
333
- nil
342
+ raise MembershipForbidden.new("GitHub refused to show the membership in #{organization}: #{e.message}", status: 403)
334
343
  end
335
344
 
336
345
  # The repositories an installation token reaches, reduced like
@@ -108,7 +108,7 @@ module ActionAgent
108
108
  data = JSON.parse(File.read(path, 8192))
109
109
  status = data["status"].to_s
110
110
  result = { status: %w[starting awaiting_code submitted completed failed expired cancelled].include?(status) ? status : "failed" }
111
- if status == "awaiting_code" && data["authorize_url"].to_s.match?(%r{\Ahttps://(?:claude\.ai|platform\.claude\.com)/oauth/authorize\?[^\s]+\z})
111
+ if status == "awaiting_code" && data["authorize_url"].to_s.match?(%r{\Ahttps://(?:claude\.ai|claude\.com|platform\.claude\.com)/(?:cai/)?oauth/authorize\?[^\s]+\z})
112
112
  result[:authorize_url] = data["authorize_url"]
113
113
  end
114
114
  result
@@ -14,7 +14,8 @@ module ActionAgent
14
14
  # project's secrets and bootstrap reach it
15
15
  # execution agent execution is enabled
16
16
  # github a GitHub App or OAuth app is configured
17
- # github_connection the owner connected GitHub
17
+ # github_connection the owner linked a usable GitHub App installation
18
+ # or connected GitHub through OAuth
18
19
  # model_credentials credentials for the model the project's agent
19
20
  # runs on (not blocking)
20
21
  # browser a browser for the project's sandbox, started and
@@ -24,7 +25,8 @@ module ActionAgent
24
25
  Item = Struct.new(:key, :label, :ok, :blocking, :detail, :fix, keyword_init: true)
25
26
 
26
27
  # @param owner [Object, nil] the caller's owner, for its credentials
27
- # @param github_connected [Boolean]
28
+ # @param github_connected [Boolean] whether the owner has a usable GitHub
29
+ # App installation or an OAuth connection
28
30
  # @param base_url [String] the engine's absolute mount URL, for callback URLs
29
31
  # @param environment [ActiveSupport::EnvironmentInquirer]
30
32
  def initialize(owner:, github_connected:, base_url:, environment: Rails.env)
@@ -137,9 +139,10 @@ module ActionAgent
137
139
  end
138
140
 
139
141
  def github_connection_item
142
+ fix = github[:mode] == "app" ? "Install the GitHub App in Settings → Integrations" : "Connect GitHub in Settings → Integrations"
140
143
  Item.new(key: "github_connection", label: "GitHub connection", ok: @github_connected, blocking: true,
141
144
  detail: @github_connected ? "Connected" : "GitHub is not connected",
142
- fix: @github_connected ? nil : "Connect GitHub in Settings → Integrations")
145
+ fix: @github_connected ? nil : fix)
143
146
  end
144
147
 
145
148
  def default_model
data/config/routes.rb CHANGED
@@ -134,6 +134,7 @@ ActionAgent::Engine.routes.draw do
134
134
  resources :projects, only: [ :index, :show, :create, :update, :destroy ] do
135
135
  collection do
136
136
  get :capabilities
137
+ get :repositories
137
138
  get :preflight
138
139
  get :discover_secrets
139
140
  end
@@ -318,6 +319,11 @@ ActionAgent::Engine.routes.draw do
318
319
  end
319
320
  end
320
321
 
322
+ # The GitHub App's webhook deliveries. Authenticated by GitHub's
323
+ # signature (github_app_webhook_secret), not a session; the App's
324
+ # Webhook URL is <mount>/api/github_app/webhooks.
325
+ post "github_app/webhooks", to: "github_app_webhooks#create", as: :github_app_webhooks
326
+
321
327
  # Creating the GitHub App from a manifest on a self-hosted dashboard.
322
328
  resource :github_app_manifest, only: [ :create ] do
323
329
  get :callback
@@ -365,6 +371,15 @@ ActionAgent::Engine.routes.draw do
365
371
  # data. See SessionPlayerController.
366
372
  get "session_player", to: "session_player#show", as: :session_player
367
373
 
374
+ # Shareable evaluation links: an evaluation, one of its runs, or one
375
+ # scenario result of a run. The React app reads these paths like any other
376
+ # client-side route; they are matched here first so the page itself is
377
+ # checked against the caller's scope, and a link to an evaluation outside
378
+ # it answers 404 whether or not the evaluation exists.
379
+ get "evaluations/:evaluation_id(/runs/:run_id(/results/:result_id))", to: "dashboard#evaluation",
380
+ as: :evaluation_link, format: false,
381
+ constraints: { evaluation_id: /\d+/, run_id: /\d+/, result_id: /\d+/ }
382
+
368
383
  # Everything else under the mount is a client-side route: render the
369
384
  # dashboard and let the browser resolve it. Anchored last so it can only
370
385
  # ever catch what the routes above did not, and refuses /api paths so a
@@ -76,7 +76,7 @@ begin
76
76
  buffer << chunk.force_encoding("UTF-8").scrub
77
77
  buffer = buffer[-32_768..] || buffer
78
78
  clean = buffer.gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
79
- url = clean[%r{https://(?:claude\.ai|platform\.claude\.com)/oauth/authorize\?[^\s\e]+}]
79
+ url = clean[%r{https://(?:claude\.ai|claude\.com|platform\.claude\.com)/(?:cai/)?oauth/authorize\?[^\s\e\a]+}]
80
80
  if url && URI.parse(url).query.to_s.include?("client_id=")
81
81
  write_status.call("awaiting_code", url)
82
82
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionAgent
4
- VERSION = "1.9.4"
4
+ VERSION = "1.9.5"
5
5
  end
data/lib/action_agent.rb CHANGED
@@ -867,6 +867,16 @@ module ActionAgent
867
867
  attr_writer :github_app_id, :github_app_private_key, :github_app_slug,
868
868
  :github_app_client_id, :github_app_client_secret
869
869
 
870
+ # The secret GitHub signs the App's webhook deliveries with, which
871
+ # POST <mount>/api/github_app/webhooks verifies. Falls back to
872
+ # GITHUB_APP_WEBHOOK_SECRET. Not one of the settings
873
+ # github_app_configured? needs: without it the endpoint answers 404 and
874
+ # the dashboard learns of installation changes only when a token mint is
875
+ # refused. Set the App's Webhook URL to <mount>/api/github_app/webhooks
876
+ # and subscribe it to Installation and Installation repositories.
877
+ # @return [String, nil]
878
+ attr_writer :github_app_webhook_secret
879
+
870
880
  # MCP servers the host app itself serves or connects, appended to the
871
881
  # built-in catalog (MCPCatalog) so the MCP Services view lists them and
872
882
  # telemetry traffic attributes to them. Each entry is a hash shaped like
@@ -971,6 +981,10 @@ module ActionAgent
971
981
  @github_app_client_secret.presence || ENV["GITHUB_APP_CLIENT_SECRET"].presence
972
982
  end
973
983
 
984
+ def github_app_webhook_secret
985
+ @github_app_webhook_secret.presence || ENV["GITHUB_APP_WEBHOOK_SECRET"].presence
986
+ end
987
+
974
988
  # Whether the GitHub App installation flow can run on this install: every
975
989
  # github_app_* setting is present.
976
990
  # @return [Boolean]
@@ -1334,6 +1348,7 @@ module ActionAgent
1334
1348
  @github_app_slug = nil
1335
1349
  @github_app_client_id = nil
1336
1350
  @github_app_client_secret = nil
1351
+ @github_app_webhook_secret = nil
1337
1352
  @trace_retention = nil
1338
1353
  @trace_owner_resolver = nil
1339
1354
  @usage_recorder = nil
@@ -215,6 +215,12 @@ ActionAgent.configure do |config|
215
215
  # config.github_app_client_secret = Rails.application.credentials.dig(:github_app, :client_secret)
216
216
  # config.github_app_private_key = Rails.application.credentials.dig(:github_app, :private_key)
217
217
  #
218
+ # With the App's webhook pointed at <mount>/api/github_app/webhooks, the
219
+ # secret its deliveries are signed with (GITHUB_APP_WEBHOOK_SECRET when
220
+ # unset). Settings then learns of an uninstall or suspension when it happens.
221
+ #
222
+ # config.github_app_webhook_secret = Rails.application.credentials.dig(:github_app, :webhook_secret)
223
+ #
218
224
  # Or an OAuth App, connected per owner with the callback URL
219
225
  # <mount>/api/github_connection/callback (GITHUB_CLIENT_ID and
220
226
  # GITHUB_CLIENT_SECRET when unset).
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The GitHub App webhook deliveries the dashboard accepted, kept briefly so a
4
+ # redelivery of the same X-GitHub-Delivery is a no-op (see
5
+ # ActionAgent::GithubWebhookDelivery). Nothing here is a credential or a
6
+ # payload.
7
+ class CreateActiveAgentGithubWebhookDeliveries < ActiveRecord::Migration<%= migration_version %>
8
+ def change
9
+ prefix = ActionAgent.table_name_prefix
10
+
11
+ create_table "#{prefix}github_webhook_deliveries" do |t|
12
+ # GitHub's X-GitHub-Delivery, a GUID. Unique: a second insert of the
13
+ # same delivery is how a redelivery is recognized.
14
+ t.string :delivery_id, null: false, limit: 64
15
+ t.string :event, null: false, limit: 64
16
+ t.string :event_action, limit: 64
17
+ t.bigint :installation_id
18
+ t.datetime :created_at, null: false
19
+ t.index :delivery_id, unique: true, name: "index_#{prefix}webhook_deliveries_on_delivery"
20
+ t.index :created_at, name: "index_#{prefix}webhook_deliveries_on_created_at"
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.4
4
+ version: 1.9.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-25 00:00:00.000000000 Z
11
+ date: 2026-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeagent
@@ -142,6 +142,7 @@ files:
142
142
  - app/controllers/action_agent/api/feedback_ingest_controller.rb
143
143
  - app/controllers/action_agent/api/feedbacks_controller.rb
144
144
  - app/controllers/action_agent/api/github_app_manifests_controller.rb
145
+ - app/controllers/action_agent/api/github_app_webhooks_controller.rb
145
146
  - app/controllers/action_agent/api/github_connections_controller.rb
146
147
  - app/controllers/action_agent/api/github_installations_controller.rb
147
148
  - app/controllers/action_agent/api/input_requests_controller.rb
@@ -194,6 +195,7 @@ files:
194
195
  - app/jobs/action_agent/evaluation_replay_job.rb
195
196
  - app/jobs/action_agent/evaluation_run_job.rb
196
197
  - app/jobs/action_agent/exploration_job.rb
198
+ - app/jobs/action_agent/github_webhook_job.rb
197
199
  - app/jobs/action_agent/input_request_expiry_job.rb
198
200
  - app/jobs/action_agent/process_telemetry_traces_job.rb
199
201
  - app/jobs/action_agent/project_evaluation_job.rb
@@ -224,6 +226,7 @@ files:
224
226
  - app/models/action_agent/exploration.rb
225
227
  - app/models/action_agent/github_connection.rb
226
228
  - app/models/action_agent/github_installation.rb
229
+ - app/models/action_agent/github_webhook_delivery.rb
227
230
  - app/models/action_agent/input_request.rb
228
231
  - app/models/action_agent/interaction_feedback.rb
229
232
  - app/models/action_agent/model_pricing.rb
@@ -248,6 +251,7 @@ files:
248
251
  - app/queries/action_agent/metrics_report.rb
249
252
  - app/queries/action_agent/trace_window_report.rb
250
253
  - app/serializers/action_agent/agent_message_serializer.rb
254
+ - app/serializers/action_agent/agent_version_serializer.rb
251
255
  - app/serializers/action_agent/code_conversation_serializer.rb
252
256
  - app/serializers/action_agent/evaluation_serializer.rb
253
257
  - app/serializers/action_agent/input_request_serializer.rb
@@ -372,6 +376,7 @@ files:
372
376
  - lib/generators/action_agent/templates/migrations/026_add_evaluation_fixes_to_code_sessions.rb.erb
373
377
  - lib/generators/action_agent/templates/migrations/027_create_active_agent_interaction_feedbacks.rb.erb
374
378
  - lib/generators/action_agent/templates/migrations/028_add_conversations_to_code_sessions.rb.erb
379
+ - lib/generators/action_agent/templates/migrations/029_create_active_agent_github_webhook_deliveries.rb.erb
375
380
  - lib/tasks/action_agent.rake
376
381
  - lib/tasks/browser.rake
377
382
  - lib/tasks/claude_code.rake