actionagent 1.9.2 → 1.9.4

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -0
  3. data/app/assets/builds/action_agent.css +1 -1
  4. data/app/assets/builds/action_agent.js +61 -61
  5. data/app/controllers/action_agent/api/agents_controller.rb +23 -0
  6. data/app/controllers/action_agent/api/code_sessions_controller.rb +49 -10
  7. data/app/controllers/action_agent/api/evaluation_reports_controller.rb +66 -2
  8. data/app/controllers/action_agent/api/evaluations_controller.rb +41 -3
  9. data/app/controllers/action_agent/api/interactions_controller.rb +55 -1
  10. data/app/controllers/action_agent/api/sandboxes_controller.rb +13 -2
  11. data/app/controllers/action_agent/api/trace_reports_controller.rb +11 -6
  12. data/app/controllers/action_agent/dashboard_controller.rb +1 -0
  13. data/app/controllers/action_agent/traces_controller.rb +6 -0
  14. data/app/jobs/action_agent/code_session_job.rb +19 -2
  15. data/app/models/action_agent/agent.rb +54 -1
  16. data/app/models/action_agent/code_session.rb +80 -0
  17. data/app/models/action_agent/evaluation_run.rb +17 -2
  18. data/app/models/action_agent/provider_key.rb +5 -1
  19. data/app/queries/action_agent/trace_window_report.rb +60 -0
  20. data/app/serializers/action_agent/code_conversation_serializer.rb +86 -0
  21. data/app/serializers/action_agent/evaluation_serializer.rb +13 -2
  22. data/app/services/action_agent/agent_execution_service.rb +37 -0
  23. data/app/services/action_agent/agent_tool_roster.rb +53 -18
  24. data/app/services/action_agent/agent_toolbox.rb +156 -3
  25. data/app/services/action_agent/browser_client_token.rb +86 -0
  26. data/app/services/action_agent/checkout_tools.rb +154 -0
  27. data/app/services/action_agent/code_conversation.rb +133 -0
  28. data/app/services/action_agent/codex_auth.rb +81 -0
  29. data/app/services/action_agent/dashboard_assistant_service.rb +19 -1
  30. data/app/services/action_agent/diff_stat.rb +27 -0
  31. data/app/services/action_agent/evaluation_report_export.rb +175 -0
  32. data/app/services/action_agent/evaluation_report_import.rb +40 -17
  33. data/app/services/action_agent/evaluation_runner_service.rb +48 -12
  34. data/app/services/action_agent/local_sandbox_backend.rb +295 -30
  35. data/app/services/action_agent/mcp_catalog.rb +25 -0
  36. data/app/services/action_agent/mcp_client.rb +1 -0
  37. data/app/services/action_agent/mcp_tool_dispatcher.rb +36 -1
  38. data/app/services/action_agent/project_boot_fix.rb +10 -3
  39. data/app/services/action_agent/sandbox_orchestrator.rb +41 -1
  40. data/app/services/action_agent/scenario_evaluation_runner.rb +1 -0
  41. data/app/services/action_agent/slack_tools.rb +101 -0
  42. data/app/views/action_agent/traces/_styles.html.erb +45 -0
  43. data/app/views/action_agent/traces/_summary.html.erb +8 -0
  44. data/app/views/action_agent/traces/_trace_detail.html.erb +33 -96
  45. data/app/views/action_agent/traces/index.html.erb +25 -134
  46. data/app/views/action_agent/traces/metrics.html.erb +22 -140
  47. data/app/views/action_agent/traces/show.html.erb +7 -36
  48. data/app/views/layouts/action_agent/application.html.erb +26 -86
  49. data/config/routes.rb +10 -1
  50. data/lib/action_agent/engine.rb +6 -0
  51. data/lib/action_agent/version.rb +1 -1
  52. data/lib/action_agent.rb +67 -2
  53. data/lib/generators/action_agent/templates/action_agent.rb.erb +4 -3
  54. data/lib/generators/action_agent/templates/migrations/028_add_conversations_to_code_sessions.rb.erb +18 -0
  55. data/lib/tasks/evaluations.rake +78 -0
  56. metadata +15 -2
@@ -1,36 +1,7 @@
1
- <div class="mb-4">
2
- <%= link_to "← Back to traces", traces_path, class: "text-indigo-600 hover:text-indigo-800 text-sm" %>
3
- </div>
4
-
5
- <div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
6
- <div class="px-4 py-4 border-b border-gray-200 dark:border-gray-700">
7
- <div class="flex items-center justify-between">
8
- <div>
9
- <h1 class="text-xl font-semibold text-gray-900 dark:text-white">
10
- <%= @trace.display_name %>
11
- </h1>
12
- <p class="text-sm text-gray-500 dark:text-gray-400 font-mono">
13
- <%= @trace.trace_id %>
14
- </p>
15
- </div>
16
- <div class="flex items-center gap-4">
17
- <% if @trace.error? %>
18
- <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200">
19
- Error
20
- </span>
21
- <% else %>
22
- <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
23
- Success
24
- </span>
25
- <% end %>
26
- <span class="text-sm text-gray-500">
27
- <%= @trace.timestamp&.strftime("%Y-%m-%d %H:%M:%S") %>
28
- </span>
29
- </div>
30
- </div>
31
- </div>
32
-
33
- <div class="p-4">
34
- <%= render partial: "trace_detail", locals: { trace: @trace } %>
35
- </div>
36
- </div>
1
+ <p><%= link_to "Back to traces", traces_path %></p>
2
+ <article class="panel">
3
+ <h1><%= @trace.display_name %></h1>
4
+ <p class="badge <%= @trace.error? ? 'error' : 'success' %>"><%= @trace.error? ? "Error" : "Success" %></p>
5
+ <p><time><%= @trace.timestamp&.strftime("%Y-%m-%d %H:%M:%S") %></time></p>
6
+ <%= render "trace_detail", trace: @trace %>
7
+ </article>
@@ -1,94 +1,34 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" class="h-full">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>ActiveAgent Dashboard</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://unpkg.com/@hotwired/turbo@7.3.0/dist/turbo.es2017-esm.js" type="module"></script>
9
- <script src="https://unpkg.com/@hotwired/stimulus@3.2.2/dist/stimulus.umd.js"></script>
10
- <style>
11
- [data-turbo-preview] { opacity: 0.5; }
12
- .span-bar { transition: all 0.2s ease; }
13
- .span-bar:hover { transform: scaleY(1.1); }
14
- </style>
15
- <script>
16
- // Initialize Stimulus
17
- const application = Stimulus.Application.start();
18
-
19
- // Traces controller for expandable rows
20
- application.register("traces", class extends Stimulus.Controller {
21
- static targets = ["row", "detail"]
22
-
23
- toggle(event) {
24
- const traceId = event.currentTarget.dataset.traceId;
25
- const detail = document.getElementById(`detail-${traceId}`);
26
- if (detail) {
27
- detail.classList.toggle("hidden");
28
- }
29
- }
30
- });
31
-
32
- // Auto-refresh controller
33
- application.register("refresh", class extends Stimulus.Controller {
34
- static values = { interval: Number }
35
-
36
- connect() {
37
- if (this.intervalValue > 0) {
38
- this.startRefresh();
39
- }
40
- }
41
-
42
- disconnect() {
43
- this.stopRefresh();
44
- }
45
-
46
- startRefresh() {
47
- this.timer = setInterval(() => {
48
- Turbo.visit(window.location.href, { action: "replace" });
49
- }, this.intervalValue);
50
- }
51
-
52
- stopRefresh() {
53
- if (this.timer) clearInterval(this.timer);
54
- }
55
- });
56
- </script>
6
+ <title>ActiveAgent Trace Console</title>
7
+ <% if @refresh_interval %>
8
+ <meta http-equiv="refresh" content="<%= @refresh_interval %>">
9
+ <% end %>
10
+ <%= render "action_agent/traces/styles" %>
57
11
  </head>
58
- <body class="h-full bg-gray-50 dark:bg-gray-900">
59
- <div class="min-h-full">
60
- <!-- Header -->
61
- <nav class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
62
- <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
63
- <div class="flex h-16 items-center justify-between">
64
- <div class="flex items-center">
65
- <div class="flex-shrink-0">
66
- <span class="text-xl font-bold text-indigo-600 dark:text-indigo-400">
67
- ActiveAgent
68
- </span>
69
- </div>
70
- <div class="ml-10 flex items-baseline space-x-4">
71
- <%= link_to "Traces",
72
- traces_path,
73
- class: "px-3 py-2 rounded-md text-sm font-medium #{request.path == traces_path ? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-200' : 'text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'}" %>
74
- <%= link_to "Metrics",
75
- metrics_traces_path,
76
- class: "px-3 py-2 rounded-md text-sm font-medium #{request.path == metrics_traces_path ? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-200' : 'text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'}" %>
77
- </div>
78
- </div>
79
- <div class="text-sm text-gray-500 dark:text-gray-400">
80
- Local Telemetry
81
- </div>
82
- </div>
83
- </div>
12
+ <body class="trace-console">
13
+ <header>
14
+ <nav aria-label="Trace console">
15
+ <strong>ActiveAgent</strong>
16
+ <%= link_to "Traces", traces_path %>
17
+ <%= link_to "Metrics", metrics_traces_path %>
18
+ <span class="muted">Local Telemetry</span>
84
19
  </nav>
85
-
86
- <!-- Main content -->
87
- <main>
88
- <div class="mx-auto max-w-7xl py-6 px-4 sm:px-6 lg:px-8">
89
- <%= yield %>
90
- </div>
91
- </main>
92
- </div>
20
+ </header>
21
+ <main>
22
+ <div class="refresh-controls">
23
+ <% if @refresh_interval %>
24
+ <span>Refreshes every <%= @refresh_interval %> seconds.</span>
25
+ <%= link_to "Stop refreshing", "#{request.path}?#{request.query_parameters.except('refresh').to_query}" %>
26
+ <% else %>
27
+ <%= link_to "Auto-refresh", "#{request.path}?#{request.query_parameters.merge('refresh' => '30').to_query}" %>
28
+ <% end %>
29
+ <%= link_to "Refresh now", request.path + (request.query_string.present? ? "?#{request.query_string}" : "") %>
30
+ </div>
31
+ <%= yield %>
32
+ </main>
93
33
  </body>
94
34
  </html>
data/config/routes.rb CHANGED
@@ -29,8 +29,13 @@ ActionAgent::Engine.routes.draw do
29
29
 
30
30
  # The collector for evaluation reports an application ran itself
31
31
  # (ActiveAgent::Evals::Publisher), at <mount>/api/evaluation_reports.
32
- # Authenticated like trace ingest, with a bearer token.
32
+ # Authenticated like trace ingest, with a bearer token. GET
33
+ # <mount>/api/evaluation_reports/:run_id exports a stored run in the
34
+ # envelope the collector takes. A run_id may hold dots, so the path has
35
+ # no format extension.
33
36
  resources :evaluation_reports, only: [ :create ]
37
+ get "evaluation_reports/:run_id", to: "evaluation_reports#show", as: :evaluation_report,
38
+ constraints: { run_id: %r{[^/]+} }, format: false
34
39
 
35
40
  post "feedback", to: "feedback_ingest#create"
36
41
  get "feedback", to: "feedbacks#index"
@@ -59,6 +64,8 @@ ActionAgent::Engine.routes.draw do
59
64
  end
60
65
  collection do
61
66
  get :presets
67
+ # The builder's roster for an agent not created yet.
68
+ get :tool_roster, action: :new_tool_roster
62
69
  end
63
70
  end
64
71
 
@@ -101,6 +108,8 @@ ActionAgent::Engine.routes.draw do
101
108
  resources :code_sessions, only: [ :index, :create, :show ] do
102
109
  member do
103
110
  post :cancel
111
+ # Another turn of the session's conversation.
112
+ post :messages
104
113
  end
105
114
  end
106
115
  resource :claude_login, only: [ :show, :create, :destroy ], controller: "claude_logins" do
@@ -80,6 +80,12 @@ module ActionAgent
80
80
  app.config.filter_parameters += [ :credential, :api_key, :access_token, :password, :claude_login_code, /\Arecording_events\z/, /(?:\A|\.)(?:answer|value)\z/i ]
81
81
  end
82
82
 
83
+ # Evaluation run reports are served as Markdown (`report.md`). Rails
84
+ # registers text/markdown as :md from 7.1; an older host gets it here.
85
+ initializer "action_agent.markdown_mime_type" do
86
+ Mime::Type.register "text/markdown", :md, [], %w[md markdown] unless Mime[:md]
87
+ end
88
+
83
89
  # The project setup assistant's request_secret answers go to its
84
90
  # handler. Registered on every reload, so the handler is always the
85
91
  # current class's.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionAgent
4
- VERSION = "1.9.2"
4
+ VERSION = "1.9.4"
5
5
  end
data/lib/action_agent.rb CHANGED
@@ -111,6 +111,9 @@ module ActionAgent
111
111
  # What ActionAgent.claude_code_auth may be set to.
112
112
  CLAUDE_CODE_AUTH_MODES = %i[api_key local_login sandbox_login].freeze
113
113
 
114
+ # What ActionAgent.codex_auth may be set to.
115
+ CODEX_AUTH_MODES = %i[api_key local_login].freeze
116
+
114
117
  # What ActionAgent.provider_key_scope may be set to.
115
118
  PROVIDER_KEY_SCOPES = %i[organization personal_override].freeze
116
119
 
@@ -134,6 +137,9 @@ module ActionAgent
134
137
  # they are handed to, or delete a project that has
135
138
  # them
136
139
  # :take_over_browser drive a run's browser by hand
140
+ # :chat_code_session send a message to a Claude Code or Codex
141
+ # conversation someone may have started (the
142
+ # subject is its first session)
137
143
  # :manage_recordings delete a session recording
138
144
  # :replace_scenarios replace or merge an evaluation's scenarios
139
145
  PERMISSION_ACTIONS = %i[
@@ -144,6 +150,7 @@ module ActionAgent
144
150
  answer_input_request
145
151
  manage_project_secrets
146
152
  take_over_browser
153
+ chat_code_session
147
154
  manage_recordings
148
155
  replace_scenarios
149
156
  ].freeze
@@ -589,10 +596,35 @@ module ActionAgent
589
596
  # @return [Integer]
590
597
  attr_accessor :claude_code_timeout
591
598
 
592
- # Codex CLI sessions use the owner's OpenAI API key and workspace-write
593
- # sandboxing. They share checkout lifecycle and cancellation with Claude.
599
+ # Codex CLI sessions run with workspace-write sandboxing, and share the
600
+ # checkout lifecycle and cancellation with Claude Code.
594
601
  attr_accessor :codex_command, :codex_timeout
595
602
 
603
+ # How Codex sessions authenticate.
604
+ #
605
+ # :api_key (the default) runs them on the OpenAI API key the owner
606
+ # connected in Settings -> Integrations, handed to the session as
607
+ # CODEX_API_KEY with a Codex home of the sandbox's own. Usage is billed
608
+ # to that key's project.
609
+ #
610
+ # :local_login runs `codex` on whatever login this machine's user set up
611
+ # with `codex login` (a ChatGPT plan, or a key given to the CLI), which
612
+ # Codex keeps under ~/.codex. The dashboard never reads, copies or
613
+ # stores it; it only asks `codex login status` whether there is one.
614
+ # That login is the dashboard user's own, so this works with the :local
615
+ # sandbox backend only, and other backends refuse Codex sessions.
616
+ # @return [Symbol] :api_key or :local_login
617
+ attr_reader :codex_auth
618
+
619
+ def codex_auth=(value)
620
+ mode = value.to_s.to_sym
621
+ unless CODEX_AUTH_MODES.include?(mode)
622
+ raise ArgumentError, "ActionAgent.codex_auth must be :api_key or :local_login, not #{value.inspect}"
623
+ end
624
+
625
+ @codex_auth = mode
626
+ end
627
+
596
628
  # How Claude Code sessions authenticate.
597
629
  #
598
630
  # :api_key (the default) runs them on the Anthropic API key the owner
@@ -719,6 +751,16 @@ module ActionAgent
719
751
  # @return [String, nil]
720
752
  attr_accessor :sign_out_path
721
753
 
754
+ # Items the host app adds to the header's user menu, above "Sign out":
755
+ # its workspace and member pages, its account settings, and for platform
756
+ # admins its admin pages. A Proc receives the signed-in user and the
757
+ # current owner and returns an Array of { label:, href: } Hashes (an
758
+ # Array is used as is), so the host decides per user what to show. The
759
+ # engine has no pages of its own to link here; unset, or when the Proc
760
+ # raises (logged), the menu holds "Sign out" alone.
761
+ # @return [Proc, Array<Hash>, nil]
762
+ attr_accessor :user_menu_links
763
+
722
764
  # Where a browser is sent when it asks for a dashboard page without a
723
765
  # valid session — the host app's sign-in page. Unset, an unauthenticated
724
766
  # page request gets a minimal session-expired page instead of a bare
@@ -1151,6 +1193,27 @@ module ActionAgent
1151
1193
 
1152
1194
  # The tenant +owner+ belongs to. Identity unless the host app says
1153
1195
  # otherwise, which is right for every single-tenant install.
1196
+ # The host's user menu items for +user+ in +owner+'s dashboard, as
1197
+ # [{ label:, href: }] with blank or malformed entries dropped. Empty when
1198
+ # user_menu_links is unset or raises, which is logged, not raised: a
1199
+ # broken menu seam must not take the dashboard down with it.
1200
+ # @return [Array<Hash{Symbol => String}>]
1201
+ def user_menu_links_for(user, owner)
1202
+ links = user_menu_links
1203
+ links = links.call(user, owner) if links.respond_to?(:call)
1204
+ Array(links).filter_map do |link|
1205
+ label = link[:label] || link["label"]
1206
+ href = link[:href] || link["href"]
1207
+ next if label.blank? || href.blank?
1208
+ next unless href.to_s.match?(%r{\A(/|https?://)})
1209
+
1210
+ { label: label.to_s, href: href.to_s }
1211
+ end
1212
+ rescue StandardError => error
1213
+ Rails.logger.warn("[ActionAgent] user_menu_links failed: #{error.class}: #{error.message}")
1214
+ []
1215
+ end
1216
+
1154
1217
  def tenant_for(owner)
1155
1218
  return owner if tenant_resolver.nil?
1156
1219
 
@@ -1248,6 +1311,7 @@ module ActionAgent
1248
1311
  @claude_code_timeout = 1800
1249
1312
  @codex_command = "codex"
1250
1313
  @codex_timeout = 1800
1314
+ @codex_auth = :api_key
1251
1315
  @claude_code_auth = :api_key
1252
1316
  @claude_code_hosted_login_enabled = false
1253
1317
  @claude_code_login_timeout = 300
@@ -1276,6 +1340,7 @@ module ActionAgent
1276
1340
  @usage_resolver = nil
1277
1341
  @upgrade_url = nil
1278
1342
  @sign_out_path = nil
1343
+ @user_menu_links = nil
1279
1344
  @sign_in_path = nil
1280
1345
  @mcp_catalog = []
1281
1346
  @agent_actor_resolver = nil
@@ -315,9 +315,10 @@ ActionAgent.configure do |config|
315
315
  # UI
316
316
  # ==========================================================================
317
317
  #
318
- # Custom layout for dashboard views (the default layout loads Tailwind,
319
- # Turbo and Stimulus from CDNs — override for CSP-strict or air-gapped
320
- # environments).
318
+ # Custom layout for dashboard views. The default layouts load nothing
319
+ # from external hosts, so this is only needed to wrap the dashboard in
320
+ # your application's own chrome. A custom layout replaces the trace
321
+ # console's built-in styles and refresh controls.
321
322
  #
322
323
  # config.layout = "application"
323
324
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Makes Claude Code and Codex sessions conversations: each session is one
4
+ # turn, and a turn after the first names the conversation's first session as
5
+ # its root. A conversation resumes the same CLI session (resume_session_id),
6
+ # and a turn may carry a note from the dashboard ahead of the person's
7
+ # message (context_note), such as who drove the browser since the last turn.
8
+ class AddConversationsToCodeSessions < ActiveRecord::Migration<%= migration_version %>
9
+ def change
10
+ sessions = "#{ActionAgent.table_name_prefix}code_sessions"
11
+ add_column sessions, :conversation_root_id, :bigint
12
+ add_column sessions, :turn_index, :integer, default: 0, null: false
13
+ add_column sessions, :origin, :string
14
+ add_column sessions, :context_note, :text
15
+ add_column sessions, :resume_session_id, :string
16
+ add_index sessions, :conversation_root_id
17
+ end
18
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Evaluation run reports from the command line: a run out as the version-1
4
+ # envelope the evaluation report collector takes (or as its HTML report page,
5
+ # or as GitHub-flavored Markdown for a pull request), and an envelope in
6
+ # through the collector's own import. On a multi-tenant install name the
7
+ # tenant with ACCOUNT_ID=<id>.
8
+ #
9
+ # bin/rails action_agent:evaluations:export RUN=dashboard-run-12-20261021093000 FILE=tmp/run.json
10
+ # bin/rails action_agent:evaluations:export EVALUATION=3 RUN=12 FORMAT=html FILE=tmp/run.html
11
+ # bin/rails action_agent:evaluations:export EVALUATION=3 RUN=12 FORMAT=md > tmp/run.md
12
+ # bin/rails action_agent:evaluations:import FILE=tmp/run.json
13
+ namespace :action_agent do
14
+ namespace :evaluations do
15
+ desc "Write a run's report (RUN=run_id, or EVALUATION=id RUN=id; FORMAT=json|html|md; FILE=path, standard output without FILE)"
16
+ task export: :environment do
17
+ run = action_agent_evaluation_run!
18
+ export = ActionAgent::EvaluationReportExport.new(run)
19
+ format = ENV.fetch("FORMAT", "json").downcase
20
+ body =
21
+ case format
22
+ when "json" then export.to_json
23
+ when "html" then run.to_report.to_html
24
+ when "md", "markdown" then export.markdown
25
+ else abort "FORMAT is json, html or md"
26
+ end
27
+
28
+ if ENV["FILE"].present?
29
+ File.write(ENV["FILE"], body)
30
+ puts "Wrote run #{export.run_id} of #{run.evaluation.name} (#{format}) to #{ENV['FILE']}"
31
+ else
32
+ puts body
33
+ end
34
+ rescue ActionAgent::EvaluationReportExport::NotExportable => e
35
+ abort "Export refused: #{e.message}"
36
+ end
37
+
38
+ desc "Store a version-1 evaluation report envelope (FILE=path [ACCOUNT_ID=id]); importing it again changes nothing"
39
+ task import: :environment do
40
+ path = ENV.fetch("FILE") { abort "Name the file: FILE=tmp/run.json" }
41
+ abort "No file at #{path}" unless File.file?(path)
42
+ abort "Unavailable: #{ActionAgent::EvaluationReportImport.unavailable_reason}" if ActionAgent::EvaluationReportImport.unavailable_reason
43
+
44
+ account = action_agent_evaluation_account
45
+ run, duplicate = ActionAgent::EvaluationReportImport.call(account: account, payload: JSON.parse(File.read(path)))
46
+ ActionAgent.record_usage(account, :evaluation_report) unless duplicate
47
+ puts "#{duplicate ? 'Already stored (identical retry)' : 'Stored'}: run #{ActionAgent::EvaluationReportExport.run_id_for(run)} " \
48
+ "as run ##{run.id} of evaluation ##{run.evaluation_id} (#{run.evaluation.name}), #{run.status}"
49
+ rescue JSON::ParserError
50
+ abort "Import refused: #{path} is not JSON"
51
+ rescue ActionAgent::EvaluationReportImport::Error, ActiveRecord::RecordInvalid => e
52
+ abort "Import refused: #{e.message}"
53
+ end
54
+ end
55
+ end
56
+
57
+ # The tenant, from ACCOUNT_ID (ACCOUNT also works); nil on a single-tenant
58
+ # install.
59
+ def action_agent_evaluation_account
60
+ id = ENV["ACCOUNT_ID"].presence || ENV["ACCOUNT"].presence
61
+ return nil unless id
62
+
63
+ abort "ACCOUNT_ID names a tenant, which only a multi-tenant install has" unless ActionAgent.multi_tenant?
64
+ ActionAgent.account_class.to_s.constantize.find_by(id: id) || abort("No account #{id}")
65
+ end
66
+
67
+ # The run RUN names: with EVALUATION, a run id of that evaluation; without,
68
+ # a run_id as the export endpoint takes it, within the tenant.
69
+ def action_agent_evaluation_run!
70
+ run_id = ENV.fetch("RUN") { abort "Name the run: RUN=<run_id>, or EVALUATION=<id> RUN=<id>" }
71
+ if ENV["EVALUATION"].present?
72
+ evaluation = ActionAgent::Evaluation.find_by(id: ENV["EVALUATION"]) || abort("No evaluation #{ENV['EVALUATION']}")
73
+ evaluation.evaluation_runs.find_by(id: run_id) || abort("No run #{run_id} in evaluation #{evaluation.id}")
74
+ else
75
+ ActionAgent::EvaluationReportExport.find(run_id, account: action_agent_evaluation_account) ||
76
+ abort("No run with run_id #{run_id.inspect}#{' for that account' if ENV['ACCOUNT_ID'].present? || ENV['ACCOUNT'].present?}")
77
+ end
78
+ 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.2
4
+ version: 1.9.4
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-20 00:00:00.000000000 Z
11
+ date: 2026-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeagent
@@ -246,7 +246,9 @@ files:
246
246
  - app/models/concerns/action_agent/session_recordable.rb
247
247
  - app/queries/action_agent/agent_executions.rb
248
248
  - app/queries/action_agent/metrics_report.rb
249
+ - app/queries/action_agent/trace_window_report.rb
249
250
  - app/serializers/action_agent/agent_message_serializer.rb
251
+ - app/serializers/action_agent/code_conversation_serializer.rb
250
252
  - app/serializers/action_agent/evaluation_serializer.rb
251
253
  - app/serializers/action_agent/input_request_serializer.rb
252
254
  - app/serializers/action_agent/interaction_preview.rb
@@ -261,17 +263,23 @@ files:
261
263
  - app/services/action_agent/agent_sync.rb
262
264
  - app/services/action_agent/agent_tool_roster.rb
263
265
  - app/services/action_agent/agent_toolbox.rb
266
+ - app/services/action_agent/browser_client_token.rb
264
267
  - app/services/action_agent/browser_live_ticket.rb
265
268
  - app/services/action_agent/browser_sidecar.rb
266
269
  - app/services/action_agent/browser_sign_in.rb
267
270
  - app/services/action_agent/browser_tool_redaction.rb
271
+ - app/services/action_agent/checkout_tools.rb
268
272
  - app/services/action_agent/claude_code_auth.rb
273
+ - app/services/action_agent/code_conversation.rb
274
+ - app/services/action_agent/codex_auth.rb
269
275
  - app/services/action_agent/codex_session_events.rb
270
276
  - app/services/action_agent/dashboard_assistant_service.rb
277
+ - app/services/action_agent/diff_stat.rb
271
278
  - app/services/action_agent/draft_pull_request_publisher.rb
272
279
  - app/services/action_agent/evaluation_evidence.rb
273
280
  - app/services/action_agent/evaluation_fix.rb
274
281
  - app/services/action_agent/evaluation_fix_comparison.rb
282
+ - app/services/action_agent/evaluation_report_export.rb
275
283
  - app/services/action_agent/evaluation_report_import.rb
276
284
  - app/services/action_agent/evaluation_run_cost.rb
277
285
  - app/services/action_agent/evaluation_runner_service.rb
@@ -316,10 +324,13 @@ files:
316
324
  - app/services/action_agent/session_index.rb
317
325
  - app/services/action_agent/session_recording_service.rb
318
326
  - app/services/action_agent/session_timeline.rb
327
+ - app/services/action_agent/slack_tools.rb
319
328
  - app/services/action_agent/tool_discovery.rb
320
329
  - app/views/action_agent/dashboard/index.html.erb
321
330
  - app/views/action_agent/github_app_manifests/created.html.erb
322
331
  - app/views/action_agent/session_player/show.html.erb
332
+ - app/views/action_agent/traces/_styles.html.erb
333
+ - app/views/action_agent/traces/_summary.html.erb
323
334
  - app/views/action_agent/traces/_trace_detail.html.erb
324
335
  - app/views/action_agent/traces/index.html.erb
325
336
  - app/views/action_agent/traces/metrics.html.erb
@@ -360,9 +371,11 @@ files:
360
371
  - lib/generators/action_agent/templates/migrations/025_create_active_agent_scenario_catalogs.rb.erb
361
372
  - lib/generators/action_agent/templates/migrations/026_add_evaluation_fixes_to_code_sessions.rb.erb
362
373
  - lib/generators/action_agent/templates/migrations/027_create_active_agent_interaction_feedbacks.rb.erb
374
+ - lib/generators/action_agent/templates/migrations/028_add_conversations_to_code_sessions.rb.erb
363
375
  - lib/tasks/action_agent.rake
364
376
  - lib/tasks/browser.rake
365
377
  - lib/tasks/claude_code.rake
378
+ - lib/tasks/evaluations.rake
366
379
  - lib/tasks/sandbox.rake
367
380
  - lib/tasks/scenario_catalogs.rake
368
381
  homepage: https://activeagents.ai