activeagent 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/README.md +24 -14
  4. data/lib/active_agent/telemetry/configuration.rb +13 -9
  5. data/lib/active_agent/telemetry/instrumentation.rb +4 -0
  6. data/lib/active_agent/telemetry/tool_origin.rb +90 -0
  7. data/lib/active_agent/telemetry.rb +1 -0
  8. data/lib/active_agent/version.rb +1 -1
  9. data/lib/active_agent.rb +0 -5
  10. metadata +22 -32
  11. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +0 -138
  12. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +0 -64
  13. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +0 -129
  14. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +0 -123
  15. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +0 -56
  16. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +0 -14
  17. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +0 -49
  18. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +0 -65
  19. data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +0 -86
  20. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +0 -256
  21. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +0 -113
  22. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +0 -208
  23. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +0 -60
  24. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +0 -46
  25. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +0 -125
  26. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +0 -83
  27. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +0 -52
  28. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +0 -169
  29. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +0 -193
  30. data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +0 -214
  31. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +0 -117
  32. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +0 -135
  33. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +0 -145
  34. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +0 -36
  35. data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +0 -94
  36. data/lib/active_agent/dashboard/config/routes.rb +0 -19
  37. data/lib/active_agent/dashboard/engine.rb +0 -43
  38. data/lib/active_agent/dashboard.rb +0 -161
  39. data/lib/generators/active_agent/dashboard/install_generator.rb +0 -92
  40. data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +0 -67
  41. data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f352ce28ead5e975e8d64c76c4a263c1ac7cac3b202ea6fef1a137f899e3aea0
4
- data.tar.gz: d163e4360d38fdf440465ec0caf76dc9c196cab342de0de4cf67de020b31d0e2
3
+ metadata.gz: 867cf01603bb86b99c7abdbe6cc9e731264dc60bfdf3e89ca19a4d25fca5ea3d
4
+ data.tar.gz: c8b86f8988ff27931f4a82f8aa71aa04168bceae00f8f9cab6b98507dd3d2b94
5
5
  SHA512:
6
- metadata.gz: 8aed9bb0e1d7bec5b5e178940e4e43c8a117c82d36db703d34cbef485b0492d29d628350bb956cf709ee504a81327e152d5510d3c1b81cf43d5ecc70f33ffae8
7
- data.tar.gz: 6d8b3c9b6d6f7286985206a42db8949067dbb46bc39db67e4eb507ccc1a9dd405ed46720c06878c4d5ea748f8da184072ce4e9bf9951361d4fe5172ae65770d8
6
+ metadata.gz: 8c81f0537fddda60bbe3049cdb36b0478130d969ba0d35af408f3a5c527e2448d8e733638cd94b6c72c8f4b8d1ec13da32877baae3101e0373cc7d173253d06c
7
+ data.tar.gz: 6af0f6a1879a9e291581a51beb5b6b96ac2ec641a9ebc007cb2345c695e1f746741fbb667bd761f889591d15c7ffeb917e7ecb7c5d1151f4d6687af3f87a3930
data/CHANGELOG.md CHANGED
@@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.0] - Unreleased
9
+
10
+ ### ⚠️ The dashboard has moved to its own gem
11
+
12
+ The dashboard engine that shipped inside `activeagent` is now a separate
13
+ gem, **`actionagent`**. Nothing is gone — the dashboard is the same
14
+ dashboard, and it gained a great deal in this release — but it comes from a
15
+ different gem now. `activeagent` is the framework alone: it no longer
16
+ defines `ActiveAgent::Dashboard`, and no longer pulls Active Record into
17
+ apps that do not use it.
18
+
19
+ **If you mount the dashboard, add the new gem in the same change that
20
+ upgrades `activeagent`:**
21
+
22
+ ```ruby
23
+ gem "activeagent", "~> 1.2"
24
+ gem "actionagent", "~> 1.2" # required if you mount the dashboard
25
+ ```
26
+
27
+ This is a minor version, so a `~> 1.0` or `~> 1.1` constraint **will** pick
28
+ it up on the next `bundle update`. If you mount the dashboard and do not add
29
+ `actionagent` at the same time, the app fails at boot with
30
+ `NameError: uninitialized constant ActiveAgent::Dashboard`, raised by your
31
+ own initializer or by the `mount ActiveAgent::Dashboard::Engine` line in
32
+ `config/routes.rb`. Adding the gem is the whole fix — your existing
33
+ configuration keeps working through the compatibility shims below.
34
+
35
+ If you do not mount the dashboard, there is nothing to do: the framework API
36
+ is unchanged, and the gem is 95% smaller.
37
+
38
+ With `actionagent` installed, the old constants keep resolving through
39
+ `ActionAgent::Compatibility` with a deprecation warning:
40
+
41
+ - `ActiveAgent::Dashboard` → `ActionAgent`
42
+ - `ActiveAgent::TelemetryTrace` → `ActionAgent::TelemetryTrace`
43
+ - `ActiveAgent::ProcessTelemetryTracesJob` → `ActionAgent::ProcessTelemetryTracesJob`
44
+
45
+ That last one matters beyond tidiness: Active Job serializes the class name
46
+ into the queue payload, so jobs enqueued before the upgrade still resolve
47
+ after it.
48
+
49
+ Other changes for mounted installs:
50
+
51
+ - **The server-rendered traces console moves from `/traces` to
52
+ `/console/traces`.** `/traces` is now the React traces view — the same
53
+ data, with more of it.
54
+ - **The mount is authenticated everywhere but development and test.** The
55
+ sandbox API, the session-recording capture endpoints and the template
56
+ endpoints previously allowed anonymous access; they no longer do. The
57
+ `GET /api/session_recordings/demo` endpoint is removed.
58
+ - **`current_user_method` / `current_account_method` are superseded by
59
+ `current_user_resolver` / `current_account_resolver`.** The engine's
60
+ controllers are their own base class, so a host app's `current_user`
61
+ helper is not available to them.
62
+ - **An unresolved owner now scopes to nothing rather than to everything.**
63
+ If you configure `user_class` or `account_class`, make sure the matching
64
+ resolver actually returns a record, or the dashboard will show no data.
65
+ - Existing installs upgrading from the in-gem dashboard: re-run
66
+ `rails generate action_agent:install`. It detects the migrations you
67
+ already have and emits only what is missing.
68
+
8
69
  ## [1.1.0] - 2026-08-12
9
70
 
10
71
  ### Dashboard — self-hosted (enterprise) mount readiness
data/README.md CHANGED
@@ -25,6 +25,10 @@ Use bundler to add activeagent to your Gemfile and install:
25
25
  bundle add activeagent
26
26
  ```
27
27
 
28
+ That is the framework — agents, providers, generation and telemetry
29
+ reporting. The dashboard is a second gem, `actionagent`, added separately
30
+ when you want it; see [Dashboard & Dev Console](#dashboard--dev-console).
31
+
28
32
  Add the generation provider gem you want to use:
29
33
 
30
34
  ```bash
@@ -127,14 +131,19 @@ development:
127
131
  service: "RubyLLM"
128
132
  ```
129
133
 
130
- ## Dev Console & Observability
134
+ ## Dashboard & Dev Console
131
135
 
132
- Active Agent includes a local dev console traces with span waterfalls,
133
- token usage, and per-agent metrics as a mountable Rails engine, so you
134
- can watch your agents while you build:
136
+ The dashboard is its own gem, `actionagent`: a mountable Rails engine with
137
+ traces and span waterfalls, token usage and per-agent metrics, plus the agent
138
+ builder, runs, conversations, evaluations, scorecards and cost estimates — so
139
+ you can watch and drive your agents while you build. It ships separately
140
+ because its models are Active Record models and it runs agents through
141
+ [solid_agent](https://github.com/activeagents/solid_agent) — neither of which
142
+ `activeagent` depends on, so an app that only runs agents installs neither.
135
143
 
136
144
  ```bash
137
- rails generate active_agent:dashboard:install
145
+ bundle add actionagent
146
+ rails generate action_agent:install
138
147
  rails db:migrate
139
148
  ```
140
149
 
@@ -145,13 +154,14 @@ telemetry:
145
154
  local_storage: true
146
155
  ```
147
156
 
148
- Open `/activeagents` and every generation appears as a trace. See
157
+ The generator mounts the engine at `/activeagents` open it and every
158
+ generation appears as a trace. See
149
159
  [docs/framework/dashboard.md](docs/framework/dashboard.md) for
150
- authentication, remote ingestion, and multi-tenant mode. For production
151
- monitoring evaluations, cost estimates, retention, team workspaces —
152
- point telemetry at the hosted platform at
153
- [activeagents.ai](https://activeagents.ai); every workspace starts with a
154
- free low-volume trial.
160
+ authentication, remote ingestion, and multi-tenant mode. The hosted
161
+ platform at [activeagents.ai](https://activeagents.ai) runs this same
162
+ engine multi-tenant, adding what a hosted product has to have — accounts,
163
+ plans, billing, quotas and managed sandboxes; every workspace starts with
164
+ a free low-volume trial.
155
165
 
156
166
  ## Features
157
167
 
@@ -199,12 +209,12 @@ response = prompt.generate_now
199
209
 
200
210
  - [Documentation](https://docs.activeagents.ai)
201
211
  - [Getting Started Guide](https://docs.activeagents.ai/getting_started)
202
- - [API Reference](https://docs.activeagents.ai/docs/framework)
203
- - [Examples](https://docs.activeagents.ai/docs/agents)
212
+ - [API Reference](https://docs.activeagents.ai/framework)
213
+ - [Examples](https://docs.activeagents.ai/agents)
204
214
 
205
215
  ## Contributing
206
216
 
207
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
217
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.MD) for details.
208
218
 
209
219
  ## License
210
220
 
@@ -78,15 +78,19 @@ module ActiveAgent
78
78
  private
79
79
 
80
80
  # Persists a trace through the dashboard's trace model, honoring
81
- # ActiveAgent::Dashboard.trace_model_class overrides. Idempotent on
82
- # trace_id, as HTTP ingest is.
81
+ # ActionAgent.trace_model_class overrides. Idempotent on trace_id, as
82
+ # HTTP ingest is.
83
+ #
84
+ # The dashboard is the `actionagent` gem, which the framework does not
85
+ # depend on — every reference to it here is guarded, so local_storage
86
+ # simply reports that it has nowhere to write when it isn't installed.
83
87
  def dashboard_store
84
88
  @dashboard_store ||= lambda do |trace, sdk|
85
89
  model = local_trace_model
86
90
  unless model
87
91
  resolved_logger.error(
88
92
  "[ActiveAgent::Telemetry] local_storage is enabled but no trace model is available — " \
89
- "run `rails generate active_agent:dashboard:install` first"
93
+ "add the actionagent gem and run `rails generate action_agent:install` first"
90
94
  )
91
95
  next
92
96
  end
@@ -98,10 +102,10 @@ module ActiveAgent
98
102
  end
99
103
 
100
104
  def local_trace_model
101
- if defined?(ActiveAgent::Dashboard) && ActiveAgent::Dashboard.respond_to?(:trace_model)
102
- ActiveAgent::Dashboard.trace_model
103
- elsif defined?(ActiveAgent::TelemetryTrace)
104
- ActiveAgent::TelemetryTrace
105
+ if defined?(::ActionAgent) && ::ActionAgent.respond_to?(:trace_model)
106
+ ::ActionAgent.trace_model
107
+ elsif defined?(::ActionAgent::TelemetryTrace)
108
+ ::ActionAgent::TelemetryTrace
105
109
  end
106
110
  rescue NameError
107
111
  nil
@@ -124,10 +128,10 @@ module ActiveAgent
124
128
  # isn't mounted there. Separate from #dashboard_mount_path so it can be
125
129
  # exercised against a route set directly.
126
130
  public def mount_path_in(route_set)
127
- return nil unless defined?(::ActiveAgent::Dashboard::Engine)
131
+ return nil unless defined?(::ActionAgent::Engine)
128
132
 
129
133
  route = route_set.routes.find do |candidate|
130
- mounted_engine(candidate.app) == ::ActiveAgent::Dashboard::Engine
134
+ mounted_engine(candidate.app) == ::ActionAgent::Engine
131
135
  end
132
136
  return nil unless route
133
137
 
@@ -147,6 +147,7 @@ module ActiveAgent
147
147
  tool_span = span.add_span("tool.#{tool_call[:name]}", span_type: :tool)
148
148
  tool_span.set_attribute("tool.name", tool_call[:name])
149
149
  tool_span.set_attribute("tool.id", tool_call[:id]) if tool_call[:id]
150
+ ToolOrigin.annotate(tool_span, tool_call[:name])
150
151
  tool_span.finish
151
152
  end
152
153
  end
@@ -202,6 +203,9 @@ module ActiveAgent
202
203
 
203
204
  tool_span = parent.add_span("tool.#{tool_name}", span_type: :tool)
204
205
  tool_span.set_attribute("tool.name", tool_name.to_s)
206
+ # Records which MCP server (if any) serves this tool, so tool
207
+ # traffic can be grouped by service downstream.
208
+ ToolOrigin.annotate(tool_span, tool_name)
205
209
  arguments = kwargs.presence || (args.length == 1 ? args.first : args.presence)
206
210
  if arguments.present?
207
211
  tool_span.set_attribute("tool.input.args", agent.send(:telemetry_truncate, JSON.generate(arguments)))
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAgent
4
+ module Telemetry
5
+ # Classifies where a tool call came from, so dashboards can group tool
6
+ # traffic by the service that serves it instead of showing a flat list
7
+ # of names.
8
+ #
9
+ # Telemetry only ever sees the name a provider used to invoke the tool,
10
+ # so the origin has to be recovered from naming conventions. The MCP
11
+ # ecosystem settled on a namespaced form — +mcp__<server>__<tool>+ —
12
+ # which most clients (Claude Code, Cursor, the Ruby MCP clients) emit
13
+ # verbatim, and that is the strongest signal available. Everything else
14
+ # falls back to "the agent class defines this method".
15
+ #
16
+ # Classification happens at instrumentation time rather than at read
17
+ # time so the attribution is recorded in the span itself: a trace stays
18
+ # self-describing, and consumers (the gem dashboard, activeagents.ai,
19
+ # any OTLP exporter) all read the same fields instead of each
20
+ # re-implementing the guess.
21
+ #
22
+ # @example Namespaced MCP tool
23
+ # ToolOrigin.classify("mcp__playwright__browser_navigate")
24
+ # # => { origin: "mcp", server: "playwright", tool: "browser_navigate" }
25
+ #
26
+ # @example Agent-defined method
27
+ # ToolOrigin.classify("lookup_order")
28
+ # # => { origin: "agent", server: nil, tool: "lookup_order" }
29
+ module ToolOrigin
30
+ # +mcp__<server>__<tool>+. The tool half may itself contain "__", so
31
+ # only the first two segments are structural.
32
+ MCP_PATTERN = /\Amcp__([^_]+(?:_[^_]+)*?)__(.+)\z/
33
+
34
+ # Origin values written to +tool.origin+.
35
+ MCP = "mcp"
36
+ AGENT = "agent"
37
+
38
+ module_function
39
+
40
+ # Classifies a tool name.
41
+ #
42
+ # @param name [String, Symbol] the tool name as the provider invoked it
43
+ # @return [Hash] +:origin+ ("mcp" or "agent"), +:server+ (MCP server
44
+ # name or nil), and +:tool+ (the bare tool name with any namespace
45
+ # stripped)
46
+ def classify(name)
47
+ name = name.to_s
48
+
49
+ if (match = MCP_PATTERN.match(name))
50
+ { origin: MCP, server: match[1], tool: match[2] }
51
+ else
52
+ { origin: AGENT, server: nil, tool: name }
53
+ end
54
+ end
55
+
56
+ # Whether a tool name is namespaced to an MCP server.
57
+ #
58
+ # @param name [String, Symbol]
59
+ # @return [Boolean]
60
+ def mcp?(name)
61
+ MCP_PATTERN.match?(name.to_s)
62
+ end
63
+
64
+ # The MCP server a tool belongs to, if any.
65
+ #
66
+ # @param name [String, Symbol]
67
+ # @return [String, nil]
68
+ def server_for(name)
69
+ classify(name)[:server]
70
+ end
71
+
72
+ # Writes the classification onto a span. Only sets +tool.mcp_server+
73
+ # when there is a server to name, so agent-defined tools don't carry
74
+ # an empty attribute.
75
+ #
76
+ # @param span [ActiveAgent::Telemetry::Span]
77
+ # @param name [String, Symbol] the tool name
78
+ # @return [Hash] the classification, for callers that also want it
79
+ def annotate(span, name)
80
+ classification = classify(name)
81
+ span.set_attribute("tool.origin", classification[:origin])
82
+ if classification[:server]
83
+ span.set_attribute("tool.mcp_server", classification[:server])
84
+ span.set_attribute("tool.base_name", classification[:tool])
85
+ end
86
+ classification
87
+ end
88
+ end
89
+ end
90
+ end
@@ -54,6 +54,7 @@ module ActiveAgent
54
54
  autoload :Span
55
55
  autoload :Reporter
56
56
  autoload :Instrumentation
57
+ autoload :ToolOrigin
57
58
 
58
59
  class << self
59
60
  # Returns the telemetry configuration instance.
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/active_agent.rb CHANGED
@@ -16,10 +16,6 @@ require "active_support/lazy_load_hooks"
16
16
  # Module Level Extensions
17
17
  require "active_agent/configuration"
18
18
  require "active_agent/railtie" if defined?(Rails)
19
- # The dashboard is a Rails engine; engines must be defined when the gem is
20
- # required (before the host app collects railtie initializers), so it
21
- # cannot be left to the Dashboard autoload above.
22
- require "active_agent/dashboard" if defined?(Rails)
23
19
 
24
20
  # ActiveAgent is a framework for building AI agents with Rails-like conventions.
25
21
  #
@@ -112,7 +108,6 @@ module ActiveAgent
112
108
  autoload :Tooling, "active_agent/concerns/tooling"
113
109
  autoload :View, "active_agent/concerns/view"
114
110
  autoload :Telemetry
115
- autoload :Dashboard
116
111
 
117
112
  class << self
118
113
  # Eagerly loads all ActiveAgent components and descendant agent classes.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
@@ -109,6 +109,26 @@ dependencies:
109
109
  - - "<="
110
110
  - !ruby/object:Gem::Version
111
111
  version: '9.0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: railties
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '7.2'
119
+ - - "<="
120
+ - !ruby/object:Gem::Version
121
+ version: '9.0'
122
+ type: :runtime
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '7.2'
129
+ - - "<="
130
+ - !ruby/object:Gem::Version
131
+ version: '9.0'
112
132
  - !ruby/object:Gem::Dependency
113
133
  name: activeagents-telemetry
114
134
  requirement: !ruby/object:Gem::Requirement
@@ -400,34 +420,6 @@ files:
400
420
  - lib/active_agent/concerns/tooling.rb
401
421
  - lib/active_agent/concerns/view.rb
402
422
  - lib/active_agent/configuration.rb
403
- - lib/active_agent/dashboard.rb
404
- - lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb
405
- - lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb
406
- - lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb
407
- - lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb
408
- - lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb
409
- - lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb
410
- - lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb
411
- - lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb
412
- - lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb
413
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb
414
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb
415
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb
416
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb
417
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb
418
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb
419
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb
420
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb
421
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb
422
- - lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb
423
- - lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb
424
- - lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb
425
- - lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb
426
- - lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb
427
- - lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb
428
- - lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb
429
- - lib/active_agent/dashboard/config/routes.rb
430
- - lib/active_agent/dashboard/engine.rb
431
423
  - lib/active_agent/deprecator.rb
432
424
  - lib/active_agent/generation.rb
433
425
  - lib/active_agent/generation_job.rb
@@ -551,14 +543,12 @@ files:
551
543
  - lib/active_agent/telemetry/instrumentation.rb
552
544
  - lib/active_agent/telemetry/reporter.rb
553
545
  - lib/active_agent/telemetry/span.rb
546
+ - lib/active_agent/telemetry/tool_origin.rb
554
547
  - lib/active_agent/telemetry/tracer.rb
555
548
  - lib/active_agent/version.rb
556
549
  - lib/activeagent.rb
557
550
  - lib/generators/active_agent/agent/USAGE
558
551
  - lib/generators/active_agent/agent/agent_generator.rb
559
- - lib/generators/active_agent/dashboard/install_generator.rb
560
- - lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb
561
- - lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb
562
552
  - lib/generators/active_agent/install/USAGE
563
553
  - lib/generators/active_agent/install/install_generator.rb
564
554
  - lib/generators/active_agent/templates/active_agent.yml
@@ -1,138 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- module Api
6
- # Telemetry ingestion endpoint.
7
- #
8
- # Receives traces from ActiveAgent::Telemetry::Reporter and stores them
9
- # for analysis and visualization in the dashboard.
10
- #
11
- # Supports two modes:
12
- # - Local mode: synchronous processing; unauthenticated unless
13
- # ActiveAgent::Dashboard.ingest_api_key is set (set it whenever the
14
- # mount is reachable beyond your own machine)
15
- # - Multi-tenant mode: per-account Bearer token auth, async processing
16
- # via job
17
- #
18
- # @example Local mode request
19
- # POST <mount>/api/traces (e.g. /activeagents/api/traces)
20
- # Content-Type: application/json
21
- #
22
- # {
23
- # "traces": [...],
24
- # "sdk": { "name": "activeagent", "version": "0.5.0" }
25
- # }
26
- #
27
- # @example Multi-tenant mode request
28
- # POST <mount>/api/traces (e.g. /activeagents/api/traces)
29
- # Authorization: Bearer <api_key>
30
- # Content-Type: application/json
31
- #
32
- # {
33
- # "traces": [...],
34
- # "sdk": { "name": "activeagent", "version": "0.5.0" }
35
- # }
36
- #
37
- class TracesController < ActionController::API
38
- before_action :authenticate_api_key!, if: -> { ActiveAgent::Dashboard.multi_tenant? }
39
- before_action :authenticate_ingest_key!, unless: -> { ActiveAgent::Dashboard.multi_tenant? }
40
-
41
- # Maximum traces accepted per request (mirrors
42
- # ProcessTelemetryTracesJob::MAX_TRACES_PER_JOB).
43
- MAX_TRACES_PER_REQUEST = 100
44
-
45
- # POST <mount>/api/traces (e.g. /activeagents/api/traces)
46
- def create
47
- traces = Array(params[:traces]).take(MAX_TRACES_PER_REQUEST)
48
- sdk_info = params[:sdk] || {}
49
-
50
- return head :accepted if traces.empty?
51
-
52
- if ActiveAgent::Dashboard.multi_tenant?
53
- # Multi-tenant mode: process in background
54
- ActiveAgent::ProcessTelemetryTracesJob.perform_later(
55
- account_id: @account&.id,
56
- traces: traces.as_json,
57
- sdk_info: sdk_info.as_json,
58
- received_at: Time.current.iso8601(6)
59
- )
60
- else
61
- # Local mode: process synchronously
62
- process_traces_synchronously(traces, sdk_info)
63
- end
64
-
65
- head :accepted
66
- rescue ActionController::ParameterMissing => e
67
- render json: { error: e.message }, status: :bad_request
68
- rescue StandardError => e
69
- Rails.logger.error("[ActiveAgent::Dashboard] Trace ingestion error: #{e.message}")
70
- render json: { error: "Internal server error" }, status: :internal_server_error
71
- end
72
-
73
- private
74
-
75
- # Authenticates the request using Bearer token from Authorization header.
76
- # Only used in multi-tenant mode.
77
- def authenticate_api_key!
78
- token = extract_bearer_token
79
-
80
- if token.blank?
81
- render json: { error: "Missing Authorization header" }, status: :unauthorized
82
- return
83
- end
84
-
85
- account_class = ActiveAgent::Dashboard.account_class.constantize
86
- @account = account_class.find_by(telemetry_api_key: token)
87
-
88
- if @account.nil?
89
- render json: { error: "Invalid API key" }, status: :unauthorized
90
- return
91
- end
92
-
93
- # Track usage for rate limiting (if the account responds to it)
94
- @account.increment_telemetry_usage! if @account.respond_to?(:increment_telemetry_usage!)
95
- end
96
-
97
- # Requires the configured single-tenant ingest key when one is set.
98
- # The telemetry reporter and ruby_llm_telemetry both send their
99
- # api_key as a Bearer header, so remote apps work unchanged.
100
- def authenticate_ingest_key!
101
- expected = ActiveAgent::Dashboard.ingest_api_key
102
- return if expected.blank?
103
-
104
- token = extract_bearer_token
105
- return if token.present? && ActiveSupport::SecurityUtils.secure_compare(token, expected)
106
-
107
- render json: { error: "Invalid API key" }, status: :unauthorized
108
- end
109
-
110
- # Extracts Bearer token from Authorization header.
111
- def extract_bearer_token
112
- auth_header = request.headers["Authorization"]
113
- return nil if auth_header.blank?
114
-
115
- match = auth_header.match(/^Bearer\s+(.+)$/i)
116
- match[1] if match
117
- end
118
-
119
- # Process traces synchronously for local development.
120
- def process_traces_synchronously(traces, sdk_info)
121
- model = ActiveAgent::Dashboard.trace_model
122
-
123
- traces.each do |trace|
124
- # Skip if trace already exists (idempotency)
125
- next if model.exists?(trace_id: trace["trace_id"])
126
-
127
- model.create_from_payload(trace, sdk_info)
128
- rescue StandardError => e
129
- Rails.logger.error(
130
- "[ActiveAgent::Dashboard] Failed to process trace #{trace['trace_id']}: " \
131
- "#{e.class} - #{e.message}"
132
- )
133
- end
134
- end
135
- end
136
- end
137
- end
138
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Base controller for the ActiveAgent Dashboard.
6
- #
7
- # Handles authentication and provides helper methods for multi-tenant mode.
8
- class ApplicationController < ActionController::Base
9
- protect_from_forgery with: :exception
10
-
11
- before_action :authenticate_dashboard!
12
-
13
- # Add the engine's view path
14
- prepend_view_path ActiveAgent::Dashboard::Engine.dashboard_root.join("app", "views").to_s
15
-
16
- # Use custom layout if configured, otherwise use engine layout
17
- layout -> { ActiveAgent::Dashboard.layout || "active_agent/dashboard/application" }
18
-
19
- helper_method :current_user, :current_owner
20
-
21
- private
22
-
23
- def authenticate_dashboard!
24
- if ActiveAgent::Dashboard.authentication_method.nil?
25
- # Traces contain prompts, outputs, and error backtraces. Refuse to
26
- # serve them unauthenticated in production rather than exposing
27
- # them to the internet by default.
28
- if Rails.env.production?
29
- render plain: "ActiveAgent Dashboard: set ActiveAgent::Dashboard.authentication_method " \
30
- "(see docs/framework/dashboard.md) to enable access in production.",
31
- status: :forbidden
32
- end
33
- return
34
- end
35
-
36
- result = ActiveAgent::Dashboard.authentication_method.call(self)
37
- head :unauthorized unless result
38
- rescue StandardError => e
39
- Rails.logger.error("[ActiveAgent::Dashboard] Authentication error: #{e.message}")
40
- head :unauthorized
41
- end
42
-
43
- # Returns the current user from the host application.
44
- def current_user
45
- return nil unless ActiveAgent::Dashboard.current_user_method
46
-
47
- send(ActiveAgent::Dashboard.current_user_method)
48
- rescue NoMethodError
49
- nil
50
- end
51
-
52
- # Returns the current owner (account in multi-tenant, user otherwise).
53
- def current_owner
54
- if ActiveAgent::Dashboard.multi_tenant? && ActiveAgent::Dashboard.current_account_method
55
- send(ActiveAgent::Dashboard.current_account_method)
56
- else
57
- current_user
58
- end
59
- rescue NoMethodError
60
- nil
61
- end
62
- end
63
- end
64
- end