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
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Represents a single browser action within a session recording.
6
- #
7
- # Actions capture user and agent interactions like clicks, typing,
8
- # navigation, and form submissions.
9
- #
10
- class RecordingAction < ApplicationRecord
11
- belongs_to :session_recording, class_name: "ActiveAgent::Dashboard::SessionRecording"
12
- has_one :snapshot, class_name: "ActiveAgent::Dashboard::RecordingSnapshot", dependent: :nullify
13
-
14
- ACTION_TYPES = %w[
15
- navigate
16
- click
17
- type
18
- scroll
19
- snapshot
20
- select
21
- hover
22
- drag
23
- file_upload
24
- dialog
25
- evaluate
26
- wait
27
- form_fill
28
- key_press
29
- focus
30
- submit
31
- handoff
32
- user_action
33
- completion
34
- ].freeze
35
-
36
- validates :action_type, presence: true, inclusion: { in: ACTION_TYPES }
37
- validates :sequence, presence: true, uniqueness: { scope: :session_recording_id }
38
- validates :timestamp_ms, presence: true
39
-
40
- scope :ordered, -> { order(:sequence) }
41
- scope :with_screenshots, -> { where.not(screenshot_key: nil) }
42
-
43
- # Get the screenshot URL (signed URL from storage)
44
- def screenshot_url(expires_in: 15.minutes)
45
- return nil unless screenshot_key.present?
46
-
47
- ActiveAgent::Dashboard.storage_service&.signed_url_for(screenshot_key, expires_in: expires_in)
48
- end
49
-
50
- # Get the DOM snapshot content
51
- def dom_snapshot_content
52
- return nil unless dom_snapshot_key.present?
53
-
54
- ActiveAgent::Dashboard.storage_service&.fetch_snapshot(dom_snapshot_key)
55
- end
56
-
57
- # Format for API response
58
- def as_json_for_api
59
- {
60
- id: id,
61
- action_type: action_type,
62
- sequence: sequence,
63
- timestamp_ms: timestamp_ms,
64
- selector: selector,
65
- value: redacted_value,
66
- screenshot_url: screenshot_url,
67
- has_dom_snapshot: dom_snapshot_key.present?,
68
- metadata: safe_metadata,
69
- created_at: created_at.iso8601
70
- }
71
- end
72
-
73
- # Get browser state at this action (for handoff)
74
- def browser_state
75
- {
76
- url: extract_url,
77
- form_values: metadata["form_values"],
78
- scroll_position: metadata["scroll_position"],
79
- active_element: selector,
80
- action_type: action_type
81
- }
82
- end
83
-
84
- private
85
-
86
- def redacted_value
87
- return value unless should_redact?
88
-
89
- "[REDACTED]"
90
- end
91
-
92
- def should_redact?
93
- return false unless value.present?
94
-
95
- sensitive_patterns = [
96
- /password/i,
97
- /credit.?card/i,
98
- /cvv/i,
99
- /ssn/i,
100
- /social.?security/i,
101
- /\b\d{16}\b/,
102
- /\b\d{3}-\d{2}-\d{4}\b/
103
- ]
104
-
105
- selector_is_sensitive = sensitive_patterns.any? { |p| selector&.match?(p) }
106
- value_is_sensitive = sensitive_patterns.any? { |p| value.match?(p) }
107
-
108
- selector_is_sensitive || value_is_sensitive
109
- end
110
-
111
- def safe_metadata
112
- metadata.except("password", "credit_card", "cvv", "ssn")
113
- end
114
-
115
- def extract_url
116
- case action_type
117
- when "navigate"
118
- value
119
- else
120
- metadata["url"] || metadata["page_url"]
121
- end
122
- end
123
- end
124
- end
125
- end
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Stores screenshots and DOM snapshots from session recordings.
6
- #
7
- # Supports Active Storage for file attachment and provides
8
- # signed URLs for secure access.
9
- #
10
- class RecordingSnapshot < ApplicationRecord
11
- belongs_to :session_recording, class_name: "ActiveAgent::Dashboard::SessionRecording"
12
- belongs_to :recording_action, class_name: "ActiveAgent::Dashboard::RecordingAction", optional: true
13
-
14
- has_one_attached :file if defined?(ActiveStorage)
15
-
16
- SNAPSHOT_TYPES = %w[screenshot dom full_page].freeze
17
-
18
- validates :storage_key, presence: true, uniqueness: true
19
- validates :snapshot_type, presence: true, inclusion: { in: SNAPSHOT_TYPES }
20
-
21
- scope :screenshots, -> { where(snapshot_type: "screenshot") }
22
- scope :dom_snapshots, -> { where(snapshot_type: "dom") }
23
- scope :ordered, -> { order(:created_at) }
24
-
25
- # Get a signed URL for the file
26
- def signed_url(expires_in: 15.minutes)
27
- return nil unless respond_to?(:file) && file.attached?
28
-
29
- file.url(expires_in: expires_in)
30
- rescue StandardError
31
- nil
32
- end
33
-
34
- # Store file data
35
- def store!(data, filename: nil, content_type: nil)
36
- return unless respond_to?(:file)
37
-
38
- content_type ||= infer_content_type
39
- filename ||= generate_filename
40
-
41
- file.attach(
42
- io: StringIO.new(data),
43
- filename: filename,
44
- content_type: content_type
45
- )
46
-
47
- update!(file_size_bytes: data.bytesize)
48
- end
49
-
50
- # For API response
51
- def as_json_for_api
52
- {
53
- id: id,
54
- storage_key: storage_key,
55
- snapshot_type: snapshot_type,
56
- width: width,
57
- height: height,
58
- file_size_bytes: file_size_bytes,
59
- url: signed_url,
60
- created_at: created_at.iso8601
61
- }
62
- end
63
-
64
- private
65
-
66
- def infer_content_type
67
- case snapshot_type
68
- when "screenshot", "full_page"
69
- "image/png"
70
- when "dom"
71
- "text/html"
72
- else
73
- "application/octet-stream"
74
- end
75
- end
76
-
77
- def generate_filename
78
- ext = snapshot_type == "dom" ? "html" : "png"
79
- "#{storage_key.tr('/', '_')}.#{ext}"
80
- end
81
- end
82
- end
83
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Tracks individual task executions within a sandbox session.
6
- #
7
- # Each sandbox run represents a single agent task execution,
8
- # capturing input, output, timing, and any errors.
9
- #
10
- class SandboxRun < ApplicationRecord
11
- belongs_to :sandbox_session, class_name: "ActiveAgent::Dashboard::SandboxSession", optional: true
12
-
13
- enum :status, {
14
- pending: 0,
15
- running: 1,
16
- completed: 2,
17
- failed: 3,
18
- cancelled: 4
19
- }
20
-
21
- validates :task, presence: true
22
- validates :status, presence: true
23
-
24
- scope :recent, -> { order(created_at: :desc) }
25
- scope :completed_runs, -> { where(status: [ :completed, :failed ]) }
26
-
27
- # Summary for API responses
28
- def summary
29
- {
30
- id: id,
31
- task: task.truncate(100),
32
- status: status,
33
- duration_ms: duration_ms,
34
- tokens_used: tokens_used,
35
- created_at: created_at&.iso8601,
36
- completed_at: completed_at&.iso8601
37
- }
38
- end
39
-
40
- # Detailed info including full result
41
- def details
42
- summary.merge(
43
- task: task,
44
- result: result,
45
- error: error,
46
- screenshots: screenshots || [],
47
- started_at: started_at&.iso8601
48
- )
49
- end
50
- end
51
- end
52
- end
@@ -1,169 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Manages sandbox execution sessions for agent runs.
6
- #
7
- # Sandbox sessions provide isolated execution environments for running
8
- # agents with tools like browser automation, file system access, etc.
9
- #
10
- # Supports both local (Docker/Incus) and cloud (Cloud Run) sandbox providers.
11
- #
12
- # @example Creating a sandbox session
13
- # session = ActiveAgent::Dashboard::SandboxSession.create!(
14
- # sandbox_type: "playwright_mcp"
15
- # )
16
- # session.provision!
17
- #
18
- class SandboxSession < ApplicationRecord
19
- # Owner associations - optional to support anonymous users
20
- belongs_to :user, class_name: ActiveAgent::Dashboard.user_class, optional: true if ActiveAgent::Dashboard.user_class
21
- belongs_to :account, class_name: ActiveAgent::Dashboard.account_class, optional: true if ActiveAgent::Dashboard.multi_tenant?
22
- belongs_to :agent_template, class_name: "ActiveAgent::Dashboard::AgentTemplate", optional: true
23
-
24
- has_many :sandbox_runs, class_name: "ActiveAgent::Dashboard::SandboxRun", dependent: :destroy
25
- has_one :session_recording, class_name: "ActiveAgent::Dashboard::SessionRecording", dependent: :nullify
26
-
27
- # Session statuses
28
- enum :status, {
29
- pending: 0,
30
- provisioning: 1,
31
- ready: 2,
32
- running: 3,
33
- completed: 4,
34
- expired: 5,
35
- failed: 6
36
- }
37
-
38
- # Sandbox types
39
- SANDBOX_TYPES = %w[playwright_mcp terminal research].freeze
40
-
41
- # Default limits (can be overridden by platform tier limits)
42
- DEFAULT_LIMITS = {
43
- max_runs: 10,
44
- timeout_seconds: 300,
45
- max_tokens: 50_000,
46
- session_duration_minutes: 15
47
- }.freeze
48
-
49
- # Validations
50
- validates :session_id, presence: true, uniqueness: true
51
- validates :sandbox_type, inclusion: { in: SANDBOX_TYPES }
52
-
53
- # Callbacks
54
- before_validation :generate_session_id, on: :create
55
- before_create :set_defaults
56
-
57
- # Scopes
58
- scope :active, -> { where(status: [ :pending, :provisioning, :ready, :running ]) }
59
- scope :expired_sessions, -> { where("expires_at < ?", Time.current) }
60
- scope :by_type, ->(type) { where(sandbox_type: type) }
61
- scope :anonymous, -> { where(user_id: nil) }
62
- scope :recent, -> { order(created_at: :desc) }
63
-
64
- # Check if session is still valid
65
- def active?
66
- !expired? && !failed? && !completed? && expires_at > Time.current
67
- end
68
-
69
- # Check if can run more tasks
70
- def can_run?
71
- active? && runs_count < max_runs
72
- end
73
-
74
- # Record a new run (thread-safe for parallel execution)
75
- def record_run!(task:, result:, duration_ms:, tokens:, screenshots: [], provider: nil)
76
- run = {
77
- id: SecureRandom.uuid,
78
- task: task,
79
- result: result,
80
- duration_ms: duration_ms,
81
- tokens: tokens,
82
- screenshots: screenshots,
83
- provider: provider,
84
- status: "completed",
85
- created_at: Time.current.iso8601
86
- }
87
-
88
- with_lock do
89
- reload
90
- self.runs = runs + [ run ]
91
- self.runs_count = runs.size
92
- self.total_tokens += tokens
93
- self.total_duration_ms += duration_ms
94
- self.last_activity_at = Time.current
95
- save!
96
- end
97
-
98
- run
99
- end
100
-
101
- # Provision the sandbox
102
- def provision!
103
- return if provisioning? || ready?
104
-
105
- update!(status: :provisioning)
106
-
107
- # Use configured sandbox service
108
- if Rails.env.development? || Rails.env.test?
109
- ActiveAgent::Dashboard::SandboxProvisionJob.perform_now(id)
110
- else
111
- ActiveAgent::Dashboard::SandboxProvisionJob.perform_later(id)
112
- end
113
- end
114
-
115
- # Mark as ready with sandbox URL
116
- def mark_ready!(sandbox_url:, sandbox_job_id: nil)
117
- update!(
118
- status: :ready,
119
- cloud_run_url: sandbox_url,
120
- cloud_run_job_id: sandbox_job_id
121
- )
122
- end
123
-
124
- # Expire the session
125
- def expire!
126
- update!(status: :expired)
127
- ActiveAgent::Dashboard::SandboxCleanupJob.perform_later(id) if cloud_run_job_id.present?
128
- end
129
-
130
- # Summary for API responses
131
- def summary
132
- {
133
- id: id,
134
- session_id: session_id,
135
- sandbox_type: sandbox_type,
136
- status: status,
137
- runs_count: runs_count,
138
- max_runs: max_runs,
139
- total_tokens: total_tokens,
140
- expires_at: expires_at&.iso8601,
141
- created_at: created_at.iso8601,
142
- cloud_run_url: cloud_run_url
143
- }
144
- end
145
-
146
- # Detailed info including runs
147
- def details
148
- summary.merge(
149
- runs: runs,
150
- total_duration_ms: total_duration_ms,
151
- last_activity_at: last_activity_at&.iso8601
152
- )
153
- end
154
-
155
- private
156
-
157
- def generate_session_id
158
- self.session_id ||= SecureRandom.uuid
159
- end
160
-
161
- def set_defaults
162
- limits = ActiveAgent::Dashboard.sandbox_limits || DEFAULT_LIMITS
163
- self.expires_at ||= limits[:session_duration_minutes].minutes.from_now
164
- self.max_runs ||= limits[:max_runs]
165
- self.timeout_seconds ||= limits[:timeout_seconds]
166
- end
167
- end
168
- end
169
- end
@@ -1,193 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Records browser sessions for playback and analysis.
6
- #
7
- # Session recordings capture the sequence of actions taken during
8
- # an agent run or sandbox session, including screenshots, DOM snapshots,
9
- # and timing information.
10
- #
11
- # @example Starting a recording
12
- # recording = ActiveAgent::Dashboard::SessionRecording.start!(
13
- # agent_run: run,
14
- # name: "checkout_flow"
15
- # )
16
- #
17
- # @example Recording an action
18
- # recording.record_action!(
19
- # action_type: "click",
20
- # selector: "button.submit",
21
- # screenshot: screenshot_data
22
- # )
23
- #
24
- class SessionRecording < ApplicationRecord
25
- belongs_to :agent_run, class_name: "ActiveAgent::Dashboard::AgentRun", optional: true
26
- belongs_to :sandbox_session, class_name: "ActiveAgent::Dashboard::SandboxSession", optional: true
27
-
28
- has_many :recording_actions, class_name: "ActiveAgent::Dashboard::RecordingAction", dependent: :destroy
29
- has_many :recording_snapshots, class_name: "ActiveAgent::Dashboard::RecordingSnapshot", dependent: :destroy
30
-
31
- enum :status, { recording: 0, completed: 1, failed: 2 }
32
-
33
- validates :status, presence: true
34
- validate :must_have_parent, unless: -> { demo_recording? || user_session? }
35
-
36
- scope :recent, -> { order(created_at: :desc) }
37
- scope :for_agent, ->(agent_id) { joins(:agent_run).where(agent_runs: { agent_id: agent_id }) }
38
- scope :demo, -> { where(name: "lander_demo") }
39
- scope :user_sessions, -> { where("name LIKE ?", "user_takeover_%") }
40
-
41
- # Check if this is a demo recording (doesn't require parent)
42
- def demo_recording?
43
- name&.start_with?("lander_") || name == "demo"
44
- end
45
-
46
- # Check if this is a user takeover session (doesn't require parent)
47
- def user_session?
48
- name&.start_with?("user_takeover_")
49
- end
50
-
51
- # Start a new recording session
52
- def self.start!(agent_run: nil, sandbox_session: nil, name: nil)
53
- create!(
54
- agent_run: agent_run,
55
- sandbox_session: sandbox_session,
56
- name: name || generate_name(agent_run, sandbox_session),
57
- status: :recording,
58
- metadata: { started_at: Time.current.iso8601 }
59
- )
60
- end
61
-
62
- # Start a user takeover session (for lander demo analytics)
63
- def self.start_user_session!(visitor_id: nil, parent_demo_id: nil, page_url: nil)
64
- create!(
65
- name: "user_takeover_#{Time.current.strftime('%Y%m%d_%H%M%S')}_#{SecureRandom.hex(4)}",
66
- status: :recording,
67
- metadata: {
68
- started_at: Time.current.iso8601,
69
- session_type: "user_takeover",
70
- visitor_id: visitor_id,
71
- parent_demo_id: parent_demo_id,
72
- page_url: page_url,
73
- user_agent: nil
74
- }
75
- )
76
- end
77
-
78
- # Record a browser action
79
- def record_action!(action_type:, selector: nil, value: nil, screenshot: nil, dom_snapshot: nil, metadata: {})
80
- raise "Recording already completed" unless recording?
81
-
82
- action = recording_actions.create!(
83
- action_type: action_type,
84
- sequence: next_sequence,
85
- timestamp_ms: elapsed_ms,
86
- selector: selector,
87
- value: value,
88
- metadata: metadata
89
- )
90
-
91
- if screenshot.present?
92
- snapshot = store_snapshot(screenshot, :screenshot, action)
93
- action.update!(screenshot_key: snapshot.storage_key)
94
- end
95
-
96
- if dom_snapshot.present?
97
- snapshot = store_snapshot(dom_snapshot, :dom, action)
98
- action.update!(dom_snapshot_key: snapshot.storage_key)
99
- end
100
-
101
- increment!(:action_count)
102
- action
103
- end
104
-
105
- # Complete the recording
106
- def complete!
107
- return unless recording?
108
-
109
- update!(
110
- status: :completed,
111
- duration_ms: elapsed_ms,
112
- metadata: metadata.merge(completed_at: Time.current.iso8601)
113
- )
114
- end
115
-
116
- # Mark recording as failed
117
- def fail!(error_message = nil)
118
- return unless recording?
119
-
120
- update!(
121
- status: :failed,
122
- duration_ms: elapsed_ms,
123
- metadata: metadata.merge(
124
- failed_at: Time.current.iso8601,
125
- error: error_message
126
- )
127
- )
128
- end
129
-
130
- # Get timeline data for playback
131
- def timeline
132
- recording_actions.order(:sequence).map do |action|
133
- {
134
- id: action.id,
135
- type: action.action_type,
136
- sequence: action.sequence,
137
- timestamp_ms: action.timestamp_ms,
138
- selector: action.selector,
139
- value: action.value,
140
- screenshot_key: action.screenshot_key,
141
- metadata: action.metadata
142
- }
143
- end
144
- end
145
-
146
- private
147
-
148
- def must_have_parent
149
- return if agent_run.present? || sandbox_session.present?
150
-
151
- errors.add(:base, "must belong to an agent_run or sandbox_session")
152
- end
153
-
154
- def self.generate_name(agent_run, sandbox_session)
155
- prefix = if agent_run&.agent
156
- agent_run.agent.name.parameterize
157
- elsif sandbox_session&.agent_template
158
- sandbox_session.agent_template.name.parameterize
159
- else
160
- "session"
161
- end
162
-
163
- "#{prefix}_#{Time.current.strftime('%Y%m%d_%H%M%S')}"
164
- end
165
-
166
- def next_sequence
167
- (recording_actions.maximum(:sequence) || 0) + 1
168
- end
169
-
170
- def elapsed_ms
171
- ((Time.current - created_at) * 1000).to_i
172
- end
173
-
174
- def store_snapshot(data, snapshot_type, action = nil)
175
- storage_key = generate_storage_key(snapshot_type, action&.sequence)
176
-
177
- recording_snapshots.create!(
178
- recording_action: action,
179
- storage_key: storage_key,
180
- snapshot_type: snapshot_type,
181
- file_size_bytes: data.bytesize
182
- )
183
- end
184
-
185
- def generate_storage_key(snapshot_type, sequence = nil)
186
- parts = [ "recordings", id, snapshot_type.to_s ]
187
- parts << sequence.to_s if sequence
188
- parts << SecureRandom.hex(8)
189
- parts.join("/")
190
- end
191
- end
192
- end
193
- end