Module: Legate::Web::AgentRuntimeRoutes

Defined in:
lib/legate/web/routes/agent_runtime_routes.rb

Class Method Summary collapse

Class Method Details

.registered(app) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/legate/web/routes/agent_runtime_routes.rb', line 7

def self.registered(app)
  # POST /agents/:name/start/detail - Start a runtime instance (from agent detail view).
  app.post '/agents/:name/start/detail' do |name|
    content_type :html
    agent_instance = send(:_start_agent, name) # Renamed to avoid confusion

    is_running = !agent_instance.nil?
    agent_data_for_view = nil
    definition_store = instance_variable_get(:@definition_store)

    if agent_instance # Agent started successfully
      # Ensure agent_data_for_view is a Hash, similar to the else block
      definition = definition_store&.get_definition(name) # Re-fetch definition for consistency
      agent_data_for_view = if definition
                              {
                                name: name,
                                description: definition[:description],
                                running: is_running,
                                model: definition[:model],
                                tool_count: definition[:tools]&.size || 0
                              }
                            else # Should not happen if agent started, but as a fallback
                              { name: name, description: 'Started (Def error)', running: is_running,
                                model: 'N/A', tool_count: 0 }
                            end
    elsif definition_store # Agent failed to start, fetch definition for display
      begin
        definition = definition_store.get_definition(name)
        agent_data_for_view = if definition
                                {
                                  name: name, description: definition[:description],
                                  running: false, model: definition[:model],
                                  tool_count: definition[:tools]&.size || 0
                                }
                              else
                                { name: name, description: 'Error: Definition not found', running: false,
                                  model: 'N/A', tool_count: 0 }
                              end
      rescue Legate::DefinitionStore::StoreError => e
        logger.error("Store error fetching definition after failed start for '#{name}' (from AgentRuntimeRoutes): #{e.message}")
        agent_data_for_view = { name: name, description: 'Error retrieving definition', running: false,
                                model: 'N/A', tool_count: 0 }
      end
    else
      agent_data_for_view = { name: name, description: 'Error: Store unavailable', running: false,
                              model: 'N/A', tool_count: 0 }
    end

    # agent_data_for_view is now consistently a Hash
    # ... rest of the route uses agent_data_for_view[:running] ...

    status_controls_html = slim(:_agent_status_controls, layout: false,
                                                         locals: { agent_data: agent_data_for_view })

    execute_button_text = is_running ? 'Execute' : 'Execute (Requires Start)'
    disabled_attr_string = is_running ? '' : 'disabled'
    execute_button_oob_html = %(
      <button class="button is-primary" id="execute-task-button" type="submit" #{disabled_attr_string} hx-swap-oob="outerHTML">
        <span class="icon is-small"><i class="fas fa-play-circle"></i></span>
        <span>#{execute_button_text}</span>
      </button>
    )
    chat_input_oob_html = %(<input class="input" id="chat-input" type="text" name="message" placeholder="Enter your message..." required="true" autofocus #{disabled_attr_string} hx-swap-oob="outerHTML">)
    chat_button_oob_html = %(<button class="button is-link" id="send-button" type="submit" #{disabled_attr_string} hx-swap-oob="outerHTML"><span>Send</span><span class="icon is-small htmx-indicator ml-2" id="send-button-indicator"><i class="fas fa-spinner fa-pulse"></i></span></button>)

    # --- MODIFIED: Determine content for chat-status-help-container ---
    new_chat_status_help_content = ''
    if is_running # is_running reflects the new state of the agent
      # Agent is running, attempt to ensure an active session for the help text
      web_user_id = session[:web_user_id]
      session_service = instance_variable_get(:@session_service)
      active_session_object_for_help_check = nil

      if web_user_id && session_service
        # 1. Try to load session from Sinatra session store
        stored_active_session_id = session.dig(:active_agent_sessions, name)
        if stored_active_session_id
          begin
            potential_session = session_service.get_session(session_id: stored_active_session_id)
            if potential_session && potential_session.user_id == web_user_id && potential_session.app_name == name
              active_session_object_for_help_check = potential_session
              logger.debug "OOB Chat Help (Start): Found valid session '#{stored_active_session_id}' from Sinatra session for agent '#{name}'."
            else
              logger.warn "OOB Chat Help (Start): Stored session ID '#{stored_active_session_id}' invalid/mismatched for agent '#{name}'. Clearing from Sinatra session."
              session[:active_agent_sessions]&.delete(name)
            end
          rescue StandardError => e
            logger.error("OOB Chat Help (Start): Error fetching stored session '#{stored_active_session_id}' for agent '#{name}': #{e.message}")
            session[:active_agent_sessions]&.delete(name) # Clear if error
          end
        end

        # 2. If no valid session from Sinatra store, try to find latest or create new
        unless active_session_object_for_help_check
          logger.debug "OOB Chat Help (Start): No valid session from Sinatra store. Attempting to find/create for agent '#{name}', user '#{web_user_id}'."
          begin
            user_agent_sessions = session_service.list_sessions(app_name: name, user_id: web_user_id)
            if user_agent_sessions && !user_agent_sessions.empty?
              latest_session = user_agent_sessions.sort_by(&:updated_at).last
              if latest_session
                active_session_object_for_help_check = latest_session
                logger.info "OOB Chat Help (Start): Found latest existing session '#{latest_session.id}' for agent '#{name}'."
              end
            end

            unless active_session_object_for_help_check
              new_session = session_service.create_session(app_name: name, user_id: web_user_id,
                                                           initial_state: {})
              active_session_object_for_help_check = new_session
              logger.info "OOB Chat Help (Start): Created new session '#{new_session.id}' for agent '#{name}'."
            end

            # Store the found/created session ID in Sinatra session
            if active_session_object_for_help_check
              session[:active_agent_sessions] ||= {}
              session[:active_agent_sessions][name] = active_session_object_for_help_check.id
              logger.debug "OOB Chat Help (Start): Stored session '#{active_session_object_for_help_check.id}' in Sinatra session for agent '#{name}'."
            end
          rescue StandardError => e
            logger.error "OOB Chat Help (Start): Critical error finding/creating session for agent '#{name}', user '#{web_user_id}': #{e.message}"
            # If session creation fails, active_session_object_for_help_check will remain nil
          end
        end
      else
        logger.error "OOB Chat Help (Start): web_user_id or session_service not available. Cannot manage session for help text for agent '#{name}'."
      end

      # Final determination of help content based on whether an active session is now established
      new_chat_status_help_content = if active_session_object_for_help_check
                                       %(<p class="help is-hidden">No issues.</p>)
                                     else
                                       # This branch means session_service might be down, or a user_id is missing, or find/create failed critically.
                                       %(<p id="chat-no-active-session-help" class="help is-warning">Chat session not active. Please try starting a new chat or reloading the page.</p>)
                                     end
    else
      # Agent is not running
      new_chat_status_help_content = %(<p id="chat-not-running-help" class="help is-danger">Agent must be running to chat.</p>)
    end

    chat_help_oob_html = %(<div id="chat-status-help-container" hx-swap-oob="innerHTML" class="mt-2">#{new_chat_status_help_content}</div>)
    # --- END MODIFICATION ---

    chat_panel_status_html = %(
      <div id="agent-chat-panel-status-display" hx-swap-oob="innerHTML">
        <p>
          <strong>Status:</strong>
          <span class="tag ml-2 #{agent_data_for_view[:running] ? 'is-success' : 'is-danger'}">
            #{agent_data_for_view[:running] ? 'Running' : 'Stopped (Go to agent page to start)'}
          </span>
        </p>
      </div>
    )
    status 200
    status_controls_html + execute_button_oob_html + chat_input_oob_html + chat_button_oob_html + chat_help_oob_html + chat_panel_status_html
  end

  # POST /agents/:name/stop/detail - Stop a runtime instance (from agent detail view).
  app.post '/agents/:name/stop/detail' do |name|
    content_type :html
    send(:_stop_agent, name) # Call private helper

    # Explicitly set status variables after stopping
    is_running = false
    disabled_attr_string = 'disabled'
    execute_button_text = 'Execute (Requires Start)'
    # Ensure agent_data_for_view reflects running: false (already done in previous step)

    agent_data_for_view = nil
    definition_store = instance_variable_get(:@definition_store)
    if definition_store
      begin
        agent_definition = definition_store.get_definition(name)
        agent_data_for_view = if agent_definition
                                {
                                  name: name, description: agent_definition[:description],
                                  running: is_running, # Use the explicitly set 'is_running'
                                  model: agent_definition[:model],
                                  tool_count: agent_definition[:tools]&.size || 0
                                }
                              else
                                { name: name, description: 'Error: Definition not found', running: is_running,
                                  model: 'N/A', tool_count: 0 }
                              end
      rescue Legate::DefinitionStore::StoreError => e
        logger.error("Store error fetching definition after stop detail for '#{name}' (from AgentRuntimeRoutes): #{e.message}")
        agent_data_for_view = { name: name, description: 'Error retrieving definition', running: is_running,
                                model: 'N/A', tool_count: 0 }
      end
    else
      agent_data_for_view = { name: name, description: 'Error: Store unavailable', running: is_running,
                              model: 'N/A', tool_count: 0 }
    end

    status_controls_html = slim(:_agent_status_controls, layout: false,
                                                         locals: { agent_data: agent_data_for_view })

    execute_button_oob_html = %(<button class="button is-link" id="execute-task-button" type="submit" #{disabled_attr_string} hx-swap-oob="outerHTML"> <span class="icon is-small htmx-indicator mr-1"><i class="fas fa-spinner fa-pulse"></i></span> <span class="icon is-small"><i class="fas fa-play-circle"></i></span> <span>#{execute_button_text}</span> </button>)
    chat_input_oob_html = %(<input class="input" id="chat-input" type="text" name="message" placeholder="Enter your message..." required="true" autofocus #{disabled_attr_string} hx-swap-oob="outerHTML">)
    chat_button_oob_html = %(<button class="button is-link" id="send-button" type="submit" #{disabled_attr_string} hx-swap-oob="outerHTML"><span>Send</span><span class="icon is-small htmx-indicator ml-2" id="send-button-indicator"><i class="fas fa-spinner fa-pulse"></i></span></button>)

    # --- MODIFIED: Determine content for chat-status-help-container ---
    # is_running is false in this context (agent stop)
    new_chat_status_help_content = ''
    if is_running # This will be false
      # This block should ideally not be reached if is_running is false,
      # but keeping structure for clarity, will be optimized by the 'else'.
      web_user_id = session[:web_user_id]
      session_service = instance_variable_get(:@session_service)
      active_session_details = nil
      # 'name' is the agent_name from the route parameter

      if web_user_id && session_service
        active_session_id = session.dig(:active_agent_sessions, name)
        if active_session_id
          begin
            potential_session = session_service.get_session(session_id: active_session_id)
            active_session_details = potential_session if potential_session && potential_session.user_id == web_user_id && potential_session.app_name == name
          rescue StandardError => e
            logger.error("OOB Chat Help (Stop - Running Path, Unexpected): Error fetching session #{active_session_id} for agent #{name}: #{e.message}")
          end
        end
      end

      new_chat_status_help_content = if active_session_details
                                       %(<p class="help is-hidden">No issues.</p>)
                                     else
                                       %(<p id="chat-no-active-session-help" class="help is-warning">No active chat session. Please start a new chat from the sidebar.</p>)
                                     end
    else
      # Agent is not running (this is the expected path for agent stop)
      new_chat_status_help_content = %(<p id="chat-not-running-help" class="help is-danger">Agent must be running to chat.</p>)
    end

    chat_help_oob_html = %(<div id="chat-status-help-container" hx-swap-oob="innerHTML" class="mt-2">#{new_chat_status_help_content}</div>)
    # --- END MODIFICATION ---

    chat_panel_status_html = %(
      <div id="agent-chat-panel-status-display" hx-swap-oob="innerHTML">
        <p>
          <strong>Status:</strong>
          <span class="tag ml-2 #{agent_data_for_view[:running] ? 'is-success' : 'is-danger'}">
            #{agent_data_for_view[:running] ? 'Running' : 'Stopped (Go to agent page to start)'}
          </span>
        </p>
      </div>
    )
    status 200
    status_controls_html + execute_button_oob_html + chat_input_oob_html + chat_button_oob_html + chat_help_oob_html + chat_panel_status_html
  end

  # Start agent from main list view (hx-post from _agent_card.slim)
  app.post '/agents/:name/start' do |name|
    agent = send(:_start_agent, name)
    definition_store = instance_variable_get(:@definition_store)
    tool_manager = instance_variable_get(:@tool_manager)
    available_tools = tool_manager&.tools&.map { |t| { name: t.name, description: t.description } } || []

    # Re-fetch definition for display
    agent_definition = definition_store&.get_definition(name) if definition_store
    active_agents_hash = instance_variable_get(:@agents)

    agent_data = if agent_definition
                   agent_definition.dup
                 else
                   { name: name, description: 'N/A', model: 'N/A', tools: [], configured_tools: [] }
                 end

    agent_data[:configured_tools] ||= agent_data[:tools] || []
    # Running state is determined by in-memory @agents hash
    agent_data[:running] = active_agents_hash.key?(name)

    if agent
      logger.info "Agent '#{name}' started from main list (from AgentRuntimeRoutes)."
    else
      logger.error "Failed to start agent '#{name}' from main list (from AgentRuntimeRoutes)."
    end

    status 200
    content_type :html
    slim :_agent_card, layout: false, locals: { agent_info: agent_data, available_tools: available_tools }
  end

  # Stop agent from main list view (hx-post from _agent_card.slim)
  app.post '/agents/:name/stop' do |name|
    success = send(:_stop_agent, name)
    definition_store = instance_variable_get(:@definition_store)
    tool_manager = instance_variable_get(:@tool_manager)
    available_tools = tool_manager&.tools&.map { |t| { name: t.name, description: t.description } } || []

    # Re-fetch definition for display
    agent_definition = definition_store&.get_definition(name) if definition_store
    active_agents_hash = instance_variable_get(:@agents)

    agent_data = if agent_definition
                   agent_definition.dup
                 else
                   { name: name, description: 'N/A', model: 'N/A', tools: [], configured_tools: [] }
                 end

    agent_data[:configured_tools] ||= agent_data[:tools] || []
    # Running state is determined by in-memory @agents hash
    agent_data[:running] = active_agents_hash.key?(name)

    if success
      logger.info "Agent '#{name}' stopped from main list (from AgentRuntimeRoutes)."
    else
      logger.error "Failed to stop agent '#{name}' from main list (from AgentRuntimeRoutes)."
    end

    status 200
    content_type :html
    slim :_agent_card, layout: false, locals: { agent_info: agent_data, available_tools: available_tools }
  end
end