Class: ActionAgent::SessionRecordingService

Inherits:
Object
  • Object
show all
Defined in:
app/services/action_agent/session_recording_service.rb

Overview

Service for managing browser session recordings Captures agent browser interactions for replay and handoff to human users

Defined Under Namespace

Classes: RecordingError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(recording = nil) ⇒ SessionRecordingService

Returns a new instance of SessionRecordingService.



11
12
13
# File 'app/services/action_agent/session_recording_service.rb', line 11

def initialize(recording = nil)
  @recording = recording
end

Instance Attribute Details

#recordingObject (readonly)

Returns the value of attribute recording.



9
10
11
# File 'app/services/action_agent/session_recording_service.rb', line 9

def recording
  @recording
end

Class Method Details

.fetch_snapshot(storage_key) ⇒ Object

Class method: Fetch snapshot content



202
203
204
205
206
207
208
209
210
# File 'app/services/action_agent/session_recording_service.rb', line 202

def self.fetch_snapshot(storage_key)
  snapshot = RecordingSnapshot.find_by(storage_key: storage_key)
  return nil unless snapshot&.file&.attached?

  snapshot.file.download
rescue StandardError => e
  Rails.logger.error "Failed to fetch snapshot #{storage_key}: #{e.message}"
  nil
end

.for_context(agent_run: nil, sandbox_session: nil) ⇒ Object

Find or create a recording for a context



27
28
29
30
31
32
33
34
# File 'app/services/action_agent/session_recording_service.rb', line 27

def self.for_context(agent_run: nil, sandbox_session: nil)
  recording = SessionRecording.recording.find_by(
    agent_run: agent_run,
    sandbox_session: sandbox_session
  )

  recording ? new(recording) : start(agent_run: agent_run, sandbox_session: sandbox_session)
end

.signed_url_for(storage_key, expires_in: 15.minutes) ⇒ Object

Class method: Generate signed URL for stored snapshot



196
197
198
199
# File 'app/services/action_agent/session_recording_service.rb', line 196

def self.signed_url_for(storage_key, expires_in: 15.minutes)
  snapshot = RecordingSnapshot.find_by(storage_key: storage_key)
  snapshot&.signed_url(expires_in: expires_in)
end

.start(agent_run: nil, sandbox_session: nil, name: nil) ⇒ Object

Start a new recording session



16
17
18
19
20
21
22
23
24
# File 'app/services/action_agent/session_recording_service.rb', line 16

def self.start(agent_run: nil, sandbox_session: nil, name: nil)
  recording = SessionRecording.start!(
    agent_run: agent_run,
    sandbox_session: sandbox_session,
    name: name
  )

  new(recording)
end

Instance Method Details

#capture_handoff_state(url:, cookies: nil, local_storage: nil, session_storage: nil, form_values: nil) ⇒ Object

Capture browser state for handoff



158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'app/services/action_agent/session_recording_service.rb', line 158

def capture_handoff_state(url:, cookies: nil, local_storage: nil, session_storage: nil, form_values: nil)
  @recording.update!(
    metadata: @recording..merge(
      handoff_state: {
        url: url,
        cookies: cookies,
        local_storage: local_storage,
        session_storage: session_storage,
        form_values: form_values,
        captured_at: Time.current.iso8601
      }
    )
  )
end

#capture_snapshot(screenshot: nil, dom: nil, full_page: false) ⇒ Object

Capture a manual snapshot (screenshot + DOM)



146
147
148
149
150
151
152
153
154
155
# File 'app/services/action_agent/session_recording_service.rb', line 146

def capture_snapshot(screenshot: nil, dom: nil, full_page: false)
  snapshot_type = full_page ? "full_page" : "snapshot"

  record_action(
    action_type: "snapshot",
    screenshot: screenshot,
    dom_snapshot: dom,
    metadata: { full_page: full_page }
  )
end

#click(selector:, screenshot: nil, metadata: {}) ⇒ Object

Record a click action



47
48
49
50
51
52
53
54
# File 'app/services/action_agent/session_recording_service.rb', line 47

def click(selector:, screenshot: nil, metadata: {})
  record_action(
    action_type: "click",
    selector: selector,
    screenshot: screenshot,
    metadata: .merge(element: selector)
  )
end

#complete!Object

Complete the recording



174
175
176
177
# File 'app/services/action_agent/session_recording_service.rb', line 174

def complete!
  @recording.complete!
  @recording
end

#dialog(accept:, text: nil, screenshot: nil) ⇒ Object

Record a dialog interaction



126
127
128
129
130
131
132
133
# File 'app/services/action_agent/session_recording_service.rb', line 126

def dialog(accept:, text: nil, screenshot: nil)
  record_action(
    action_type: "dialog",
    value: text,
    screenshot: screenshot,
    metadata: { accepted: accept, prompt_text: text }
  )
end

#evaluate(function:, result: nil, screenshot: nil) ⇒ Object

Record a JavaScript evaluation



136
137
138
139
140
141
142
143
# File 'app/services/action_agent/session_recording_service.rb', line 136

def evaluate(function:, result: nil, screenshot: nil)
  record_action(
    action_type: "evaluate",
    value: function,
    screenshot: screenshot,
    metadata: { result: result&.to_s&.truncate(1000) }
  )
end

#fail!(error_message = nil) ⇒ Object

Mark recording as failed



180
181
182
183
# File 'app/services/action_agent/session_recording_service.rb', line 180

def fail!(error_message = nil)
  @recording.fail!(error_message)
  @recording
end

#file_upload(paths:, screenshot: nil) ⇒ Object

Record a file upload



116
117
118
119
120
121
122
123
# File 'app/services/action_agent/session_recording_service.rb', line 116

def file_upload(paths:, screenshot: nil)
  record_action(
    action_type: "file_upload",
    value: paths.join(", "),
    screenshot: screenshot,
    metadata: { paths: paths }
  )
end

#fill_form(fields:, screenshot: nil) ⇒ Object

Record a form fill (multiple fields)



68
69
70
71
72
73
74
75
# File 'app/services/action_agent/session_recording_service.rb', line 68

def fill_form(fields:, screenshot: nil)
  record_action(
    action_type: "form_fill",
    value: fields.to_json,
    screenshot: screenshot,
    metadata: { fields: fields, field_count: fields.size }
  )
end

#handoff_stateObject

Get handoff state for resuming session



191
192
193
# File 'app/services/action_agent/session_recording_service.rb', line 191

def handoff_state
  @recording.["handoff_state"]
end

#hover(selector:, screenshot: nil) ⇒ Object

Record a hover action



96
97
98
99
100
101
102
# File 'app/services/action_agent/session_recording_service.rb', line 96

def hover(selector:, screenshot: nil)
  record_action(
    action_type: "hover",
    selector: selector,
    screenshot: screenshot
  )
end

#key_press(key:, screenshot: nil) ⇒ Object

Record a key press



78
79
80
81
82
83
84
# File 'app/services/action_agent/session_recording_service.rb', line 78

def key_press(key:, screenshot: nil)
  record_action(
    action_type: "key_press",
    value: key,
    screenshot: screenshot
  )
end

Record a browser navigation



37
38
39
40
41
42
43
44
# File 'app/services/action_agent/session_recording_service.rb', line 37

def navigate(url:, screenshot: nil)
  record_action(
    action_type: "navigate",
    value: url,
    screenshot: screenshot,
    metadata: { url: url }
  )
end

#scroll(position:, screenshot: nil) ⇒ Object

Record a scroll action



87
88
89
90
91
92
93
# File 'app/services/action_agent/session_recording_service.rb', line 87

def scroll(position:, screenshot: nil)
  record_action(
    action_type: "scroll",
    metadata: { scroll_position: position },
    screenshot: screenshot
  )
end

#select_option(selector:, values:, screenshot: nil) ⇒ Object

Record a select option action



105
106
107
108
109
110
111
112
113
# File 'app/services/action_agent/session_recording_service.rb', line 105

def select_option(selector:, values:, screenshot: nil)
  record_action(
    action_type: "select",
    selector: selector,
    value: values.join(", "),
    screenshot: screenshot,
    metadata: { values: values }
  )
end

#timelineObject

Get the recording timeline for playback



186
187
188
# File 'app/services/action_agent/session_recording_service.rb', line 186

def timeline
  @recording.timeline
end

#type(selector:, text:, screenshot: nil, metadata: {}) ⇒ Object

Record a type/input action



57
58
59
60
61
62
63
64
65
# File 'app/services/action_agent/session_recording_service.rb', line 57

def type(selector:, text:, screenshot: nil, metadata: {})
  record_action(
    action_type: "type",
    selector: selector,
    value: text,
    screenshot: screenshot,
    metadata: 
  )
end