legate 0.1.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 (317) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +345 -0
  4. data/bin/legate +13 -0
  5. data/examples/00_quickstart.rb +51 -0
  6. data/examples/01_simple_agent.rb +105 -0
  7. data/examples/02_multi_tool_agent.rb +140 -0
  8. data/examples/03_custom_tool.rb +93 -0
  9. data/examples/04_agent_instructions.rb +84 -0
  10. data/examples/05_state_and_sessions.rb +91 -0
  11. data/examples/06_callbacks.rb +186 -0
  12. data/examples/07_async_jobs.rb +112 -0
  13. data/examples/08_loop_agent.rb +197 -0
  14. data/examples/09_sequential_workflow.rb +40 -0
  15. data/examples/10_parallel_workflow.rb +34 -0
  16. data/examples/11_agent_delegation.rb +24 -0
  17. data/examples/12_http_client_tool.rb +156 -0
  18. data/examples/13_authentication.rb +220 -0
  19. data/examples/14_mcp_client.rb +154 -0
  20. data/examples/15_mcp_server.rb +79 -0
  21. data/examples/16_webhooks.rb +91 -0
  22. data/examples/README_sequential_agents.md +164 -0
  23. data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
  24. data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
  25. data/examples/advanced/auth/excon_middleware.rb +317 -0
  26. data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
  27. data/examples/advanced/auth/fiber_auth_example.rb +281 -0
  28. data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
  29. data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
  30. data/examples/advanced/auth/oauth2_auth.rb +419 -0
  31. data/examples/advanced/auth/oidc_auth.rb +514 -0
  32. data/examples/advanced/auth/openweather_api.rb +251 -0
  33. data/examples/advanced/auth/openweather_tool.rb +153 -0
  34. data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
  35. data/examples/advanced/auth/service_account.rb +135 -0
  36. data/examples/advanced/auth/test_with_httpbin.rb +202 -0
  37. data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
  38. data/examples/advanced/callback_monitoring.rb +679 -0
  39. data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
  40. data/examples/advanced/mas/loop_workflow.rb +28 -0
  41. data/examples/advanced/mas/mock_planner.rb +77 -0
  42. data/examples/advanced/mas/proper_delegation_example.rb +276 -0
  43. data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
  44. data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
  45. data/examples/advanced/mcp/mcp_server_async.rb +76 -0
  46. data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
  47. data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
  48. data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
  49. data/examples/advanced/random_calculator.rb +104 -0
  50. data/examples/advanced/sleep_agent.rb +153 -0
  51. data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
  52. data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
  53. data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
  54. data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
  55. data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
  56. data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
  57. data/examples/tools/oauth2_example.rb +136 -0
  58. data/examples/tools/sleepy_tool.rb +42 -0
  59. data/lib/legate/activity_log.rb +71 -0
  60. data/lib/legate/agent.rb +959 -0
  61. data/lib/legate/agent_code_generator.rb +185 -0
  62. data/lib/legate/agent_definition.rb +812 -0
  63. data/lib/legate/agentic/decision.rb +49 -0
  64. data/lib/legate/agentic/loop.rb +134 -0
  65. data/lib/legate/agentic.rb +5 -0
  66. data/lib/legate/agents/loop_agent.rb +248 -0
  67. data/lib/legate/agents/parallel_agent.rb +163 -0
  68. data/lib/legate/agents/sequential_agent.rb +190 -0
  69. data/lib/legate/agents.rb +14 -0
  70. data/lib/legate/auth/config.rb +148 -0
  71. data/lib/legate/auth/coordinator.rb +218 -0
  72. data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
  73. data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
  74. data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
  75. data/lib/legate/auth/credential.rb +157 -0
  76. data/lib/legate/auth/encryption.rb +108 -0
  77. data/lib/legate/auth/error.rb +94 -0
  78. data/lib/legate/auth/exchanged_credential.rb +180 -0
  79. data/lib/legate/auth/excon_middleware.rb +285 -0
  80. data/lib/legate/auth/http_client_utils.rb +364 -0
  81. data/lib/legate/auth/manager.rb +531 -0
  82. data/lib/legate/auth/manager_store.rb +394 -0
  83. data/lib/legate/auth/middleware_factory.rb +290 -0
  84. data/lib/legate/auth/runner.rb +279 -0
  85. data/lib/legate/auth/scheme.rb +125 -0
  86. data/lib/legate/auth/schemes/api_key.rb +212 -0
  87. data/lib/legate/auth/schemes/google_service_account.rb +108 -0
  88. data/lib/legate/auth/schemes/http_bearer.rb +98 -0
  89. data/lib/legate/auth/schemes/oauth2.rb +396 -0
  90. data/lib/legate/auth/schemes/openid_connect.rb +346 -0
  91. data/lib/legate/auth/schemes/service_account.rb +388 -0
  92. data/lib/legate/auth/schemes.rb +40 -0
  93. data/lib/legate/auth/token_manager.rb +362 -0
  94. data/lib/legate/auth/token_store.rb +86 -0
  95. data/lib/legate/auth/tool_context_extension.rb +97 -0
  96. data/lib/legate/auth/tool_integration.rb +188 -0
  97. data/lib/legate/auth/url_guard.rb +81 -0
  98. data/lib/legate/auth.rb +453 -0
  99. data/lib/legate/callbacks/callback_context.rb +71 -0
  100. data/lib/legate/cli/agent_commands.rb +950 -0
  101. data/lib/legate/cli/auth_commands.rb +520 -0
  102. data/lib/legate/cli/base_command.rb +24 -0
  103. data/lib/legate/cli/deployment_commands.rb +934 -0
  104. data/lib/legate/cli/output_helper.rb +108 -0
  105. data/lib/legate/cli/session_commands.rb +138 -0
  106. data/lib/legate/cli/skaffold_commands.rb +223 -0
  107. data/lib/legate/cli/tool_commands.rb +261 -0
  108. data/lib/legate/cli/web_commands.rb +182 -0
  109. data/lib/legate/cli.rb +40 -0
  110. data/lib/legate/configuration/webhooks.rb +113 -0
  111. data/lib/legate/configuration.rb +39 -0
  112. data/lib/legate/definition_store.rb +23 -0
  113. data/lib/legate/errors.rb +118 -0
  114. data/lib/legate/event.rb +161 -0
  115. data/lib/legate/gemini_ai_beta_patch.rb +39 -0
  116. data/lib/legate/generators/agent_generator.rb +412 -0
  117. data/lib/legate/generators/code_validator.rb +48 -0
  118. data/lib/legate/generators/legate/install_generator.rb +35 -0
  119. data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
  120. data/lib/legate/generators/legate/templates/initializer.rb +18 -0
  121. data/lib/legate/generators/runtime_tool_loader.rb +76 -0
  122. data/lib/legate/generators/tool_generator.rb +408 -0
  123. data/lib/legate/generators.rb +11 -0
  124. data/lib/legate/global_definition_registry.rb +506 -0
  125. data/lib/legate/global_tool_manager.rb +135 -0
  126. data/lib/legate/llm/adapter.rb +69 -0
  127. data/lib/legate/llm/gemini.rb +172 -0
  128. data/lib/legate/llm/ollama.rb +80 -0
  129. data/lib/legate/llm.rb +34 -0
  130. data/lib/legate/mcp/client.rb +320 -0
  131. data/lib/legate/mcp/connection/sse.rb +292 -0
  132. data/lib/legate/mcp/connection/stdio.rb +273 -0
  133. data/lib/legate/mcp/connection_manager.rb +103 -0
  134. data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
  135. data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
  136. data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
  137. data/lib/legate/mcp/tool_wrapper.rb +138 -0
  138. data/lib/legate/mcp/util/schema_converter.rb +134 -0
  139. data/lib/legate/mcp.rb +23 -0
  140. data/lib/legate/plan_executor.rb +375 -0
  141. data/lib/legate/planner.rb +839 -0
  142. data/lib/legate/rails/railtie.rb +43 -0
  143. data/lib/legate/rails.rb +9 -0
  144. data/lib/legate/redaction.rb +32 -0
  145. data/lib/legate/session.rb +299 -0
  146. data/lib/legate/session_service/active_record.rb +300 -0
  147. data/lib/legate/session_service/base.rb +68 -0
  148. data/lib/legate/session_service/event_broadcast.rb +74 -0
  149. data/lib/legate/session_service/in_memory.rb +188 -0
  150. data/lib/legate/tool/metadata_dsl.rb +122 -0
  151. data/lib/legate/tool.rb +276 -0
  152. data/lib/legate/tool_code_generator.rb +103 -0
  153. data/lib/legate/tool_context.rb +350 -0
  154. data/lib/legate/tool_loader.rb +39 -0
  155. data/lib/legate/tool_registry.rb +73 -0
  156. data/lib/legate/tool_result.rb +61 -0
  157. data/lib/legate/tools/agent_tool.rb +187 -0
  158. data/lib/legate/tools/base/http_client.rb +319 -0
  159. data/lib/legate/tools/base/safe_url.rb +56 -0
  160. data/lib/legate/tools/base_async_job_tool.rb +91 -0
  161. data/lib/legate/tools/calculator.rb +89 -0
  162. data/lib/legate/tools/cat_facts.rb +81 -0
  163. data/lib/legate/tools/check_job_status_tool.rb +48 -0
  164. data/lib/legate/tools/current_time_tool.rb +64 -0
  165. data/lib/legate/tools/echo.rb +43 -0
  166. data/lib/legate/tools/http_request_tool.rb +105 -0
  167. data/lib/legate/tools/random_number_tool.rb +64 -0
  168. data/lib/legate/tools/read_webpage_tool.rb +92 -0
  169. data/lib/legate/tools/sleepy_tool.rb +74 -0
  170. data/lib/legate/tools/webhook_tool.rb +146 -0
  171. data/lib/legate/version.rb +5 -0
  172. data/lib/legate/web/app.rb +984 -0
  173. data/lib/legate/web/public/css/main.css +4980 -0
  174. data/lib/legate/web/public/images/favicon-256.png +0 -0
  175. data/lib/legate/web/public/images/favicon-32.png +0 -0
  176. data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
  177. data/lib/legate/web/public/images/legate-logo-light.png +0 -0
  178. data/lib/legate/web/public/js/legate.js +616 -0
  179. data/lib/legate/web/public/styles/main.scss +4402 -0
  180. data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
  181. data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
  182. data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
  183. data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
  184. data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
  185. data/lib/legate/web/routes/api_routes.rb +56 -0
  186. data/lib/legate/web/routes/authentication_routes.rb +1541 -0
  187. data/lib/legate/web/routes/core_routes.rb +111 -0
  188. data/lib/legate/web/routes/documentation_routes.rb +220 -0
  189. data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
  190. data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
  191. data/lib/legate/web/sass_compiler.rb +73 -0
  192. data/lib/legate/web/views/_active_session_info.slim +25 -0
  193. data/lib/legate/web/views/_activity_list.slim +55 -0
  194. data/lib/legate/web/views/_agent_card.slim +56 -0
  195. data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
  196. data/lib/legate/web/views/_agent_status_controls.slim +71 -0
  197. data/lib/legate/web/views/_agent_tool_table.slim +74 -0
  198. data/lib/legate/web/views/_chat_message.slim +95 -0
  199. data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
  200. data/lib/legate/web/views/_display_agent_description.slim +11 -0
  201. data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
  202. data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
  203. data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
  204. data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
  205. data/lib/legate/web/views/_display_agent_model.slim +17 -0
  206. data/lib/legate/web/views/_display_agent_name.slim +42 -0
  207. data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
  208. data/lib/legate/web/views/_display_agent_type.slim +65 -0
  209. data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
  210. data/lib/legate/web/views/_edit_agent_description.slim +16 -0
  211. data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
  212. data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
  213. data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
  214. data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
  215. data/lib/legate/web/views/_edit_agent_model.slim +23 -0
  216. data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
  217. data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
  218. data/lib/legate/web/views/_edit_agent_type.slim +67 -0
  219. data/lib/legate/web/views/_session_error.slim +4 -0
  220. data/lib/legate/web/views/_skeleton.slim +69 -0
  221. data/lib/legate/web/views/_tool_card.slim +9 -0
  222. data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
  223. data/lib/legate/web/views/agent.slim +436 -0
  224. data/lib/legate/web/views/agent_auth.slim +562 -0
  225. data/lib/legate/web/views/agents.slim +369 -0
  226. data/lib/legate/web/views/auth.slim +112 -0
  227. data/lib/legate/web/views/auth_credential_detail.slim +327 -0
  228. data/lib/legate/web/views/auth_credentials.slim +261 -0
  229. data/lib/legate/web/views/auth_debug.slim +94 -0
  230. data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
  231. data/lib/legate/web/views/auth_mapping_new.slim +123 -0
  232. data/lib/legate/web/views/auth_mappings.slim +120 -0
  233. data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
  234. data/lib/legate/web/views/auth_schemes.slim +259 -0
  235. data/lib/legate/web/views/auth_test.slim +418 -0
  236. data/lib/legate/web/views/chat.slim +192 -0
  237. data/lib/legate/web/views/docs_index.slim +105 -0
  238. data/lib/legate/web/views/docs_show.slim +105 -0
  239. data/lib/legate/web/views/error_404.slim +5 -0
  240. data/lib/legate/web/views/index.slim +148 -0
  241. data/lib/legate/web/views/layout.slim +144 -0
  242. data/lib/legate/web/views/tool_detail.slim +87 -0
  243. data/lib/legate/web/views/tools.slim +50 -0
  244. data/lib/legate/web/webhook_listener.rb +367 -0
  245. data/lib/legate/web.rb +9 -0
  246. data/lib/legate.rb +220 -0
  247. data/public/docs/advanced/callbacks.md +828 -0
  248. data/public/docs/advanced/mcp_schema_conversion.md +59 -0
  249. data/public/docs/authentication/api_reference/config.md +210 -0
  250. data/public/docs/authentication/api_reference/credential.md +246 -0
  251. data/public/docs/authentication/api_reference/encryption.md +218 -0
  252. data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
  253. data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
  254. data/public/docs/authentication/api_reference/index.md +30 -0
  255. data/public/docs/authentication/api_reference/scheme.md +250 -0
  256. data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
  257. data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
  258. data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
  259. data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
  260. data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
  261. data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
  262. data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
  263. data/public/docs/authentication/api_reference/token_manager.md +221 -0
  264. data/public/docs/authentication/api_reference/token_store.md +146 -0
  265. data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
  266. data/public/docs/authentication/guides/api_key.md +190 -0
  267. data/public/docs/authentication/guides/bearer.md +172 -0
  268. data/public/docs/authentication/guides/configuration.md +255 -0
  269. data/public/docs/authentication/guides/custom_flow.md +523 -0
  270. data/public/docs/authentication/guides/index.md +24 -0
  271. data/public/docs/authentication/guides/migration.md +435 -0
  272. data/public/docs/authentication/guides/oauth2.md +252 -0
  273. data/public/docs/authentication/guides/oidc.md +241 -0
  274. data/public/docs/authentication/guides/overview.md +155 -0
  275. data/public/docs/authentication/guides/secure_storage.md +301 -0
  276. data/public/docs/authentication/guides/service_account.md +228 -0
  277. data/public/docs/authentication/guides/token_lifecycle.md +295 -0
  278. data/public/docs/authentication/guides/web_ui_integration.md +504 -0
  279. data/public/docs/authentication/index.md +58 -0
  280. data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
  281. data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
  282. data/public/docs/authentication/troubleshooting/index.md +11 -0
  283. data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
  284. data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
  285. data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
  286. data/public/docs/cli/legate_cli_usage.md +363 -0
  287. data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
  288. data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
  289. data/public/docs/core_concepts/legate_configuration.md +116 -0
  290. data/public/docs/core_concepts/legate_definition_store.md +102 -0
  291. data/public/docs/core_concepts/legate_planner.md +94 -0
  292. data/public/docs/core_concepts/legate_session_service.md +104 -0
  293. data/public/docs/error_handling/legate_error_handling.md +122 -0
  294. data/public/docs/examples.md +199 -0
  295. data/public/docs/getting_started.md +111 -0
  296. data/public/docs/guides/agentic_agents.md +137 -0
  297. data/public/docs/guides/ai_code_generators.md +437 -0
  298. data/public/docs/guides/auto_loading.md +326 -0
  299. data/public/docs/guides/configuring_agent_webhooks.md +219 -0
  300. data/public/docs/guides/http_client_usage.md +264 -0
  301. data/public/docs/guides/llm_providers.md +137 -0
  302. data/public/docs/guides/mcp_client_integration.md +232 -0
  303. data/public/docs/guides/mcp_server_exposure.md +206 -0
  304. data/public/docs/guides/rails_integration.md +128 -0
  305. data/public/docs/guides/sending_outbound_webhooks.md +227 -0
  306. data/public/docs/guides/streaming.md +112 -0
  307. data/public/docs/guides/webhooks.md +288 -0
  308. data/public/docs/introduction.md +51 -0
  309. data/public/docs/multi_agent_systems/advanced_features.md +57 -0
  310. data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
  311. data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
  312. data/public/docs/multi_agent_systems/state_management.md +47 -0
  313. data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
  314. data/public/docs/tools/legate_built_in_tools.md +332 -0
  315. data/public/docs/tools/legate_tools_and_registry.md +263 -0
  316. data/public/docs/web_ui/legate_web_ui.md +137 -0
  317. metadata +823 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sass-embedded'
4
+ require 'fileutils'
5
+ require 'logger'
6
+
7
+ module Legate
8
+ module Web
9
+ # Sass compiler for the web interface
10
+ class SassCompiler
11
+ class << self
12
+ def compile_all
13
+ new.compile_all
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ @logger = Logger.new($stdout)
19
+ @logger.level = Logger::INFO
20
+ end
21
+
22
+ def compile_all
23
+ @logger.info('Compiling Sass files...')
24
+
25
+ # Ensure the output directory exists
26
+ FileUtils.mkdir_p(output_dir)
27
+
28
+ # Find all Sass files
29
+ sass_files = Dir.glob(File.join(source_dir, '**', '*.scss'))
30
+
31
+ sass_files.each do |sass_file|
32
+ compile_file(sass_file)
33
+ end
34
+
35
+ @logger.info('Sass compilation complete!')
36
+ end
37
+
38
+ def compile_file(sass_file)
39
+ relative_path = sass_file.sub("#{source_dir}/", '')
40
+ output_file = File.join(output_dir, relative_path.sub('.scss', '.css'))
41
+
42
+ # Ensure the output directory exists
43
+ FileUtils.mkdir_p(File.dirname(output_file))
44
+
45
+ @logger.info("Compiling #{relative_path} to #{output_file.sub("#{output_dir}/", '')}")
46
+
47
+ begin
48
+ # Compile the Sass file
49
+ input = File.read(sass_file)
50
+ result = Sass.compile_string(input, style: 'expanded', load_paths: [File.dirname(sass_file)])
51
+
52
+ # Write the compiled CSS to the output file
53
+ File.write(output_file, result.css)
54
+
55
+ @logger.info("Successfully compiled #{relative_path}")
56
+ rescue StandardError => e
57
+ @logger.error("Error compiling #{relative_path}: #{e.message}")
58
+ @logger.error(e.backtrace.join("\n"))
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def source_dir
65
+ @source_dir ||= File.expand_path('public/styles', __dir__)
66
+ end
67
+
68
+ def output_dir
69
+ @output_dir ||= File.expand_path('public/css', __dir__)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,25 @@
1
+ / File: lib/legate/web/views/_active_session_info.slim
2
+ / Renders the Run Details "session" block inside the chat inspector.
3
+ / Keeps id + hx-swap-oob so session new/switch/delete can update it out-of-band.
4
+
5
+ div#active-session-info.inspector-session(hx-swap-oob="outerHTML")
6
+ - if active_session_details
7
+ .inspector-section
8
+ .inspector-label Session ID
9
+ .inspector-session-id
10
+ span.inspector-session-mono(title=active_session_details.id)= active_session_details.id
11
+ button.inspector-copy(type="button" title="Copy session ID" onclick="navigator.clipboard && navigator.clipboard.writeText('#{active_session_details.id}')")
12
+ i.fas.fa-copy
13
+ .inspector-section
14
+ .inspector-meta-row
15
+ span.inspector-meta-key Created
16
+ span.inspector-meta-val= active_session_details.created_at.strftime("%b %d, %H:%M")
17
+ .inspector-meta-row
18
+ span.inspector-meta-key Last activity
19
+ span.inspector-meta-val= active_session_details.updated_at.strftime("%b %d, %H:%M")
20
+ .inspector-meta-row
21
+ span.inspector-meta-key Events
22
+ span.inspector-meta-val= active_session_details.events&.count || 0
23
+ - else
24
+ .inspector-section
25
+ p.inspector-empty No active session.
@@ -0,0 +1,55 @@
1
+ / File: lib/legate/web/views/_activity_list.slim
2
+ / Partial for rendering the activity stream list
3
+
4
+ - if @recent_activity && @recent_activity.any?
5
+ .activity-list
6
+ - @recent_activity.each do |event|
7
+ .activity-item
8
+ - icon_class = case event[:type]
9
+ - when :agent_started then 'fa-play-circle has-text-success'
10
+ - when :agent_stopped then 'fa-stop-circle has-text-danger'
11
+ - when :agent_created then 'fa-plus-circle has-text-info'
12
+ - when :agent_deleted then 'fa-trash has-text-danger'
13
+ - when :task_executed then 'fa-bolt has-text-warning'
14
+ - when :app_started then 'fa-rocket has-text-primary'
15
+ - else 'fa-info-circle has-text-grey'
16
+ span.activity-icon
17
+ i.fas(class=icon_class)
18
+ span.activity-text
19
+ - case event[:type]
20
+ - when :agent_started
21
+ | Agent
22
+ strong= event[:details][:name]
23
+ | started
24
+ - when :agent_stopped
25
+ | Agent
26
+ strong= event[:details][:name]
27
+ | stopped
28
+ - when :agent_created
29
+ | Agent
30
+ strong= event[:details][:name]
31
+ | created
32
+ - when :agent_deleted
33
+ | Agent
34
+ strong= event[:details][:name]
35
+ | deleted
36
+ - when :task_executed
37
+ | Task executed on
38
+ strong= event[:details][:agent]
39
+ - when :app_started
40
+ | Application started
41
+ - else
42
+ = event[:type].to_s.tr('_', ' ').capitalize
43
+ span.activity-time= time_ago_in_words(event[:timestamp])
44
+ - else
45
+ .has-text-centered.has-text-grey.py-5
46
+ span.icon.is-large
47
+ i.fas.fa-clock.fa-2x style="opacity: 0.3;"
48
+ p.mt-3 No recent activity
49
+ p.is-size-7 Activity will appear here as you interact with agents
50
+
51
+
52
+
53
+
54
+
55
+
@@ -0,0 +1,56 @@
1
+ / File: lib/legate/web/views/_agent_card.slim
2
+ / Renders one agent as a "Legion" command-console card.
3
+ / Expects 'agent_info' (hash: :name, :description, :running, :configured_tools,
4
+ / :agent_type, :model) and 'available_tools' (list). Replaces the old table row;
5
+ / HTMX start/stop/delete swap `closest .agent-card` with this same partial.
6
+
7
+ - agent_name = agent_info[:name]
8
+ - encoded_agent_name = URI.encode_www_form_component(agent_name.to_s).gsub('+', '%20')
9
+ - safe_agent_id = agent_name.to_s.gsub(/[^a-zA-Z0-9_-]/, '-')
10
+ - is_running = agent_info[:running]
11
+ - model_display = (agent_info[:model] && !agent_info[:model].to_s.empty?) ? agent_info[:model] : Legate::Agent::DEFAULT_MODEL
12
+ - card_id = "agent-card-#{safe_agent_id}"
13
+ - is_new_card = locals.fetch(:agent_info, {}).fetch(:is_new, false)
14
+ - agent_type = agent_info[:agent_type]&.to_sym || :llm
15
+ - configured_tools = agent_info[:configured_tools] || []
16
+ - find_tool_desc = lambda { |name| available_tools&.find { |t| t[:name].to_s == name.to_s }&.[](:description) || name.to_s }
17
+ - type_label = { sequential: 'Sequential', parallel: 'Parallel', loop: 'Loop' }[agent_type] || 'LLM'
18
+ - search_blob = [agent_name, agent_info[:description], configured_tools.join(' '), model_display].join(' ').downcase
19
+
20
+ article.agent-card(id=card_id data-agent-name=agent_name data-search=search_blob class=("is-running" if is_running) class=("is-newly-added" if is_new_card))
21
+ header.agent-card-head
22
+ .agent-card-titles
23
+ a.agent-card-name(href="/agents/#{encoded_agent_name}")= agent_name
24
+ span.agent-type-chip= type_label
25
+ span.status-pill(class=(is_running ? 'is-running' : 'is-idle'))
26
+ span.status-dot
27
+ span= is_running ? 'Running' : 'Idle'
28
+
29
+ p.agent-card-desc= agent_info[:description]
30
+
31
+ .agent-card-meta
32
+ span.mono-chip.model-chip(title="Language model")
33
+ span.icon.is-small
34
+ i.fas.fa-microchip
35
+ span= model_display
36
+
37
+ .agent-card-tools
38
+ - if configured_tools.any?
39
+ - configured_tools.each do |tool_name|
40
+ a.tool-chip(href="/tools/#{tool_name}" title=find_tool_desc.call(tool_name))= tool_name
41
+ - else
42
+ span.tool-chip.is-empty no tools configured
43
+
44
+ footer.agent-card-foot
45
+ - unless is_running
46
+ button.legion-action.is-run(type="button" title="Start agent" hx-post="/agents/#{encoded_agent_name}/start" hx-target="closest .agent-card" hx-swap="outerHTML")
47
+ i.fas.fa-play
48
+ span Run
49
+ - if is_running
50
+ button.legion-action.is-stop(type="button" title="Stop agent" hx-post="/agents/#{encoded_agent_name}/stop" hx-target="closest .agent-card" hx-swap="outerHTML")
51
+ i.fas.fa-stop
52
+ span Stop
53
+ a.legion-action.is-icon.is-details(href="/agents/#{encoded_agent_name}" title="View details")
54
+ i.fas.fa-external-link-alt
55
+ button.legion-action.is-icon.is-danger(type="button" title="Delete agent" hx-delete="/agents/#{encoded_agent_name}" hx-target="closest .agent-card" hx-swap="outerHTML" hx-confirm="Are you sure you want to delete '#{agent_name}'?")
56
+ i.fas.fa-trash-alt
@@ -0,0 +1,382 @@
1
+ / File: lib/legate/web/views/_agent_generator_modal.slim
2
+ / AI agent generator: describe → review editable fields → Add to Legion (live).
3
+ / Generates a STRUCTURED definition (no file/restart); .rb export kept for power users.
4
+
5
+ #agent-generator-modal.modal
6
+ .modal-background onclick="closeAgentGeneratorModal()"
7
+ .modal-card style="width: 92%; max-width: 920px; max-height: 92vh;"
8
+ header.modal-card-head
9
+ p.modal-card-title
10
+ span.icon.mr-2
11
+ i.fas.fa-wand-magic-sparkles
12
+ | Generate Agent with AI
13
+ button.delete aria-label="close" onclick="closeAgentGeneratorModal()"
14
+
15
+ section.modal-card-body style="display: flex; flex-direction: column; gap: 1.25rem;"
16
+ / Input
17
+ #agent-generator-input-section
18
+ .field
19
+ label.label
20
+ span.icon.is-small.mr-1
21
+ i.fas.fa-comment-dots
22
+ | Describe your agent
23
+ .control
24
+ textarea#agent-generator-description.textarea(
25
+ rows="4"
26
+ placeholder="e.g. 'An agent that summarizes customer feedback', 'A support assistant that can echo and look things up'"
27
+ )
28
+ p.help Describe what the agent should do. The AI fills in a configuration you can review and tweak before adding it.
29
+ .field
30
+ .control
31
+ button#agent-generator-btn.button.is-primary(onclick="generateAgentDefinition()")
32
+ span.icon
33
+ i.fas.fa-wand-magic-sparkles
34
+ span Generate
35
+ span#agent-generator-loading.ml-3 style="display: none;"
36
+ span.icon.has-text-primary
37
+ i.fas.fa-spinner.fa-spin
38
+ span.has-text-grey Generating…
39
+
40
+ / Error
41
+ #agent-generator-error.notification.is-danger.is-light style="display: none;"
42
+ button.delete onclick="this.parentElement.style.display='none'"
43
+ span#agent-generator-error-message
44
+
45
+ / Review (editable, hidden until generated)
46
+ #agent-generator-review-section style="display: none;"
47
+ .agent-gen-review-banner
48
+ span.icon.has-text-primary
49
+ i.fas.fa-circle-check
50
+ span Review and tweak the configuration, then add it to your legion.
51
+
52
+ .field
53
+ label.label Name
54
+ .control.has-icons-left
55
+ input#agent-gen-name.input type="text" placeholder="snake_case_name"
56
+ span.icon.is-small.is-left
57
+ i.fas.fa-robot
58
+ .columns.is-variable.is-3
59
+ .column.is-half
60
+ .field
61
+ label.label Model
62
+ .control
63
+ .select.is-fullwidth
64
+ select#agent-gen-model
65
+ - (@available_models || []).each do |m|
66
+ option value=m = m
67
+ .column.is-half
68
+ .field
69
+ label.label Type
70
+ .control
71
+ .select.is-fullwidth
72
+ select#agent-gen-type
73
+ option value="llm" LLM Agent
74
+ option value="sequential" Sequential Workflow
75
+ option value="parallel" Parallel Workflow
76
+ option value="loop" Loop Workflow
77
+ .field
78
+ label.label Description
79
+ .control
80
+ textarea#agent-gen-description-field.textarea rows="2"
81
+ .field
82
+ label.label Instruction (System Prompt)
83
+ .control
84
+ textarea#agent-gen-instruction.textarea rows="5"
85
+ .field
86
+ label.label Tools
87
+ .agent-gen-tools
88
+ - if @available_tools && !@available_tools.empty?
89
+ - @available_tools.each do |t|
90
+ label.checkbox.agent-gen-tool-item
91
+ input.agent-gen-tool type="checkbox" value=t[:name]
92
+ span.agent-gen-tool-name= t[:name]
93
+ - else
94
+ p.has-text-grey No tools available to select.
95
+ .field
96
+ label.label Output Key (optional)
97
+ .control
98
+ input#agent-gen-output-key.input type="text" placeholder="result_key"
99
+
100
+ / Suggested tools to create (capabilities no installed tool provides)
101
+ .field#agent-gen-suggested-field style="display: none;"
102
+ label.label
103
+ span.icon.is-small.mr-1
104
+ i.fas.fa-lightbulb
105
+ | Suggested tools to create
106
+ p.help.mb-2 This agent could use capabilities no installed tool provides yet. Build them with the tool generator — they’ll be available to add next time.
107
+ #agent-gen-suggested.agent-gen-suggested
108
+
109
+ details.agent-gen-code-details
110
+ summary
111
+ span.icon.is-small
112
+ i.fas.fa-code
113
+ | View / export generated Ruby
114
+ .buttons.mt-2
115
+ button.button.is-small.is-light(onclick="copyAgentCode()" title="Copy code")
116
+ span.icon.is-small
117
+ i.fas.fa-copy
118
+ span Copy
119
+ button.button.is-small.is-light(onclick="downloadAgentCode()" title="Download as .rb")
120
+ span.icon.is-small
121
+ i.fas.fa-download
122
+ span Export .rb
123
+ .code-preview-container style="max-height: 280px; overflow: auto; border-radius: 6px;"
124
+ pre
125
+ code#agent-generator-code.language-ruby
126
+
127
+ footer.modal-card-foot style="justify-content: space-between;"
128
+ button.button onclick="closeAgentGeneratorModal()" Close
129
+ .buttons.mb-0
130
+ button#agent-gen-regenerate.button.is-light style="display: none;" onclick="generateAgentDefinition()"
131
+ span.icon.is-small
132
+ i.fas.fa-sync
133
+ span Regenerate
134
+ button#agent-gen-add-btn.button.is-primary style="display: none;" onclick="addAgentToLegion()"
135
+ span.icon
136
+ i.fas.fa-circle-plus
137
+ span Add to Legion
138
+
139
+ javascript:
140
+ let generatedAgentCode = '';
141
+ let suggestedAgentName = 'generated_agent';
142
+
143
+ function openAgentGeneratorModal() {
144
+ document.getElementById('agent-generator-modal').classList.add('is-active');
145
+ document.getElementById('agent-generator-description').focus();
146
+ }
147
+
148
+ function closeAgentGeneratorModal() {
149
+ document.getElementById('agent-generator-modal').classList.remove('is-active');
150
+ document.getElementById('agent-generator-error').style.display = 'none';
151
+ }
152
+
153
+ function showAgentGeneratorError(message) {
154
+ document.getElementById('agent-generator-error-message').textContent = message;
155
+ document.getElementById('agent-generator-error').style.display = 'block';
156
+ }
157
+
158
+ function agSetSelect(id, value) {
159
+ const sel = document.getElementById(id);
160
+ if (!sel) return;
161
+ if (Array.from(sel.options).some(o => o.value === value)) sel.value = value;
162
+ }
163
+
164
+ async function generateAgentDefinition() {
165
+ const description = document.getElementById('agent-generator-description').value.trim();
166
+ if (!description) {
167
+ showAgentGeneratorError('Please describe the agent you want to create.');
168
+ return;
169
+ }
170
+ const btn = document.getElementById('agent-generator-btn');
171
+ const loading = document.getElementById('agent-generator-loading');
172
+ btn.disabled = true;
173
+ loading.style.display = 'inline';
174
+ document.getElementById('agent-generator-error').style.display = 'none';
175
+
176
+ try {
177
+ const tokenEl = document.querySelector('meta[name="csrf-token"]');
178
+ const response = await fetch('/agents/generate/definition', {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Content-Type': 'application/json',
182
+ 'X-CSRF-Token': tokenEl ? tokenEl.content : ''
183
+ },
184
+ body: JSON.stringify({ description })
185
+ });
186
+ const data = await response.json();
187
+ if (!response.ok) throw new Error(data.error || 'Generation failed');
188
+ populateAgentReview(data);
189
+ document.getElementById('agent-generator-review-section').style.display = 'block';
190
+ document.getElementById('agent-gen-add-btn').style.display = '';
191
+ document.getElementById('agent-gen-regenerate').style.display = '';
192
+ } catch (error) {
193
+ showAgentGeneratorError(error.message);
194
+ } finally {
195
+ btn.disabled = false;
196
+ loading.style.display = 'none';
197
+ }
198
+ }
199
+
200
+ function populateAgentReview(data) {
201
+ document.getElementById('agent-gen-name').value = data.name || '';
202
+ agSetSelect('agent-gen-model', data.model);
203
+ agSetSelect('agent-gen-type', data.agent_type || 'llm');
204
+ document.getElementById('agent-gen-description-field').value = data.description || '';
205
+ document.getElementById('agent-gen-instruction').value = data.instruction || '';
206
+ document.getElementById('agent-gen-output-key').value = data.output_key || '';
207
+
208
+ const tools = data.tools || [];
209
+ document.querySelectorAll('.agent-gen-tool').forEach(cb => { cb.checked = tools.includes(cb.value); });
210
+
211
+ renderSuggestedTools(data.suggested_tools || []);
212
+
213
+ generatedAgentCode = data.code || '';
214
+ suggestedAgentName = data.name || 'generated_agent';
215
+ const codeEl = document.getElementById('agent-generator-code');
216
+ codeEl.classList.remove('hljs');
217
+ delete codeEl.dataset.highlighted;
218
+ codeEl.textContent = generatedAgentCode;
219
+ if (typeof hljs !== 'undefined') hljs.highlightElement(codeEl);
220
+ }
221
+
222
+ function renderSuggestedTools(suggested) {
223
+ const field = document.getElementById('agent-gen-suggested-field');
224
+ const list = document.getElementById('agent-gen-suggested');
225
+ list.innerHTML = '';
226
+ if (!suggested.length) { field.style.display = 'none'; return; }
227
+
228
+ suggested.forEach(t => {
229
+ const item = document.createElement('div');
230
+ item.className = 'agent-gen-suggested-item';
231
+
232
+ const info = document.createElement('div');
233
+ info.className = 'agent-gen-suggested-info';
234
+ const nm = document.createElement('span');
235
+ nm.className = 'agent-gen-suggested-name';
236
+ nm.textContent = t.name;
237
+ info.appendChild(nm);
238
+ if (t.description) {
239
+ const d = document.createElement('span');
240
+ d.className = 'agent-gen-suggested-desc';
241
+ d.textContent = t.description;
242
+ info.appendChild(d);
243
+ }
244
+
245
+ const btn = document.createElement('button');
246
+ btn.type = 'button';
247
+ btn.className = 'button is-small is-primary is-light agent-gen-build-btn';
248
+ btn.innerHTML = '<span class="icon is-small"><i class="fas fa-wand-magic-sparkles"></i></span><span>Build&nbsp;→</span>';
249
+ btn.addEventListener('click', () => buildSuggestedTool(t.name, t.description));
250
+
251
+ item.appendChild(info);
252
+ item.appendChild(btn);
253
+ list.appendChild(item);
254
+ });
255
+ field.style.display = 'block';
256
+ }
257
+
258
+ function buildSuggestedTool(name, description) {
259
+ if (typeof openToolGeneratorModal !== 'function') {
260
+ showAgentGeneratorError('The tool generator is not available here.');
261
+ return;
262
+ }
263
+ // Remember to come back to the agent builder once this tool is installed,
264
+ // so the new tool gets added + checked (it didn't exist at page load).
265
+ window.legateReturnToAgentBuilder = name;
266
+ closeAgentGeneratorModal();
267
+ const field = document.getElementById('tool-generator-description');
268
+ if (field) {
269
+ const base = (description && description.trim()) ? description.trim() : ('A tool named "' + name + '"');
270
+ field.value = base + (name ? ('\n\nName it "' + name + '".') : '');
271
+ }
272
+ openToolGeneratorModal();
273
+ }
274
+
275
+ // Called by the tool modal after a suggested tool is installed live. Adds the
276
+ // newly-registered tool to the review form (checked), drops the suggestion, and
277
+ // re-opens the agent builder so "Add to Legion" includes the new tool.
278
+ function returnToAgentBuilderWithTool(installedName, originalSuggestion) {
279
+ const list = document.querySelector('.agent-gen-tools');
280
+ if (installedName && list) {
281
+ let cb = Array.from(document.querySelectorAll('.agent-gen-tool')).find(c => c.value === installedName);
282
+ if (!cb) {
283
+ const label = document.createElement('label');
284
+ label.className = 'checkbox agent-gen-tool-item';
285
+ cb = document.createElement('input');
286
+ cb.type = 'checkbox';
287
+ cb.className = 'agent-gen-tool';
288
+ cb.value = installedName;
289
+ const span = document.createElement('span');
290
+ span.className = 'agent-gen-tool-name';
291
+ span.textContent = installedName;
292
+ label.appendChild(cb);
293
+ label.appendChild(document.createTextNode(' '));
294
+ label.appendChild(span);
295
+ list.appendChild(label);
296
+ }
297
+ cb.checked = true;
298
+ }
299
+ // Remove the suggestion we just satisfied (match the original suggestion name).
300
+ document.querySelectorAll('.agent-gen-suggested-item').forEach(item => {
301
+ const nm = item.querySelector('.agent-gen-suggested-name');
302
+ if (nm && (nm.textContent === originalSuggestion || nm.textContent === installedName)) item.remove();
303
+ });
304
+ const field = document.getElementById('agent-gen-suggested-field');
305
+ if (field && !document.querySelector('.agent-gen-suggested-item')) field.style.display = 'none';
306
+
307
+ if (typeof openAgentGeneratorModal === 'function') openAgentGeneratorModal();
308
+ }
309
+
310
+ async function addAgentToLegion() {
311
+ const name = document.getElementById('agent-gen-name').value.trim();
312
+ if (!name) { showAgentGeneratorError('The agent needs a name.'); return; }
313
+
314
+ const params = new URLSearchParams();
315
+ params.append('name', name);
316
+ params.append('description', document.getElementById('agent-gen-description-field').value.trim());
317
+ params.append('instruction', document.getElementById('agent-gen-instruction').value);
318
+ params.append('model', document.getElementById('agent-gen-model').value);
319
+ params.append('agent_type', document.getElementById('agent-gen-type').value);
320
+ const ok = document.getElementById('agent-gen-output-key').value.trim();
321
+ if (ok) params.append('output_key', ok);
322
+ document.querySelectorAll('.agent-gen-tool:checked').forEach(cb => params.append('tools[]', cb.value));
323
+
324
+ const addBtn = document.getElementById('agent-gen-add-btn');
325
+ addBtn.classList.add('is-loading');
326
+ try {
327
+ const tokenEl = document.querySelector('meta[name="csrf-token"]');
328
+ const response = await fetch('/agents', {
329
+ method: 'POST',
330
+ headers: {
331
+ 'Content-Type': 'application/x-www-form-urlencoded',
332
+ 'X-CSRF-Token': tokenEl ? tokenEl.content : ''
333
+ },
334
+ body: params.toString()
335
+ });
336
+ const html = await response.text();
337
+ if (!response.ok) throw new Error('Could not add the agent. ' + (html.replace(/<[^>]*>/g, '').trim() || ''));
338
+
339
+ const grid = document.getElementById('agent-list-grid');
340
+ if (grid) {
341
+ const tmp = document.createElement('div');
342
+ tmp.innerHTML = html;
343
+ const card = tmp.querySelector('.agent-card');
344
+ if (card) grid.appendChild(card);
345
+ }
346
+ const empty = document.getElementById('no-agents-state');
347
+ if (empty) empty.classList.add('is-hidden');
348
+ if (typeof filterAgents === 'function') filterAgents();
349
+ closeAgentGeneratorModal();
350
+ } catch (error) {
351
+ showAgentGeneratorError(error.message);
352
+ } finally {
353
+ addBtn.classList.remove('is-loading');
354
+ }
355
+ }
356
+
357
+ async function copyAgentCode() {
358
+ try {
359
+ await navigator.clipboard.writeText(generatedAgentCode);
360
+ } catch (err) {
361
+ showAgentGeneratorError('Failed to copy to clipboard');
362
+ }
363
+ }
364
+
365
+ function downloadAgentCode() {
366
+ const blob = new Blob([generatedAgentCode], { type: 'text/plain' });
367
+ const url = URL.createObjectURL(blob);
368
+ const a = document.createElement('a');
369
+ a.href = url;
370
+ a.download = `${suggestedAgentName}.rb`;
371
+ document.body.appendChild(a);
372
+ a.click();
373
+ document.body.removeChild(a);
374
+ URL.revokeObjectURL(url);
375
+ }
376
+
377
+ document.addEventListener('keydown', function(e) {
378
+ if (e.key === 'Escape') {
379
+ const modal = document.getElementById('agent-generator-modal');
380
+ if (modal && modal.classList.contains('is-active')) closeAgentGeneratorModal();
381
+ }
382
+ });
@@ -0,0 +1,71 @@
1
+ / File: lib/legate/web/views/_agent_status_controls.slim
2
+ - agent_name = agent_data.is_a?(Hash) ? agent_data[:name] : agent_data.name
3
+ - is_running = agent_data.is_a?(Hash) ? agent_data[:running] : agent_data.running?
4
+ - tool_count = agent_data.is_a?(Hash) ? (agent_data[:tool_count] || 0) : 0
5
+ - container_id = "agent-status-controls-#{agent_name.gsub(/[^a-zA-Z0-9_-]/, '-')}"
6
+ - encoded_agent_name = URI.encode_www_form_component(agent_name.to_s).gsub('+', '%20')
7
+
8
+ div.agent-header-controls(id=container_id)
9
+ / Status and action on same row
10
+ .agent-status-action-row
11
+ / Status badge
12
+ span.tag.is-medium.is-rounded.agent-status-badge class=(is_running ? 'is-success is-running' : 'is-danger')
13
+ span.icon.is-small
14
+ i(class="fas #{is_running ? 'fa-circle' : 'fa-circle'}")
15
+ span= is_running ? 'Running' : 'Stopped'
16
+
17
+ / Action button - compact
18
+ - if is_running
19
+ button.button.is-danger.agent-action-btn(
20
+ hx-post="/agents/#{encoded_agent_name}/stop/detail"
21
+ hx-target="##{container_id}"
22
+ hx-swap="innerHTML")
23
+ span.icon.is-small.htmx-indicator
24
+ i.fas.fa-spinner.fa-spin
25
+ span.icon.is-small
26
+ i.fas.fa-stop
27
+ span Stop
28
+ - else
29
+ button.button.is-success.agent-action-btn(
30
+ hx-post="/agents/#{encoded_agent_name}/start/detail"
31
+ hx-target="##{container_id}"
32
+ hx-swap="innerHTML")
33
+ span.icon.is-small.htmx-indicator
34
+ i.fas.fa-spinner.fa-spin
35
+ span.icon.is-small
36
+ i.fas.fa-play
37
+ span Start
38
+
39
+ / Quick actions menu
40
+ .dropdown.is-right.agent-quick-actions-dropdown
41
+ .dropdown-trigger
42
+ button.button.is-light.agent-quick-actions-btn(type="button" aria-haspopup="true" aria-controls="agent-actions-menu")
43
+ span.icon.is-small
44
+ i.fas.fa-ellipsis-v
45
+ .dropdown-menu#agent-actions-menu(role="menu")
46
+ .dropdown-content
47
+ a.dropdown-item(href="#config" data-tab-link="config")
48
+ span.icon.is-small
49
+ i.fas.fa-cog
50
+ span Edit Configuration
51
+ a.dropdown-item(href="/agents/#{encoded_agent_name}/duplicate" hx-post="/agents/#{encoded_agent_name}/duplicate" hx-swap="none")
52
+ span.icon.is-small
53
+ i.fas.fa-copy
54
+ span Duplicate Agent
55
+ a.dropdown-item(href="/agents/#{encoded_agent_name}/export" download="#{agent_name}.json")
56
+ span.icon.is-small
57
+ i.fas.fa-download
58
+ span Export JSON
59
+ a.dropdown-item(href="/agents/#{encoded_agent_name}/download" download="#{agent_name}.rb")
60
+ span.icon.is-small
61
+ i.fas.fa-file-code
62
+ span Download Ruby
63
+ a.dropdown-item(onclick="saveAgentToDisk('#{encoded_agent_name}')" style="cursor: pointer;" title="Write agents/#{agent_name}.rb so this agent survives a restart")
64
+ span.icon.is-small
65
+ i.fas.fa-hard-drive
66
+ span Save to agents/
67
+ hr.dropdown-divider
68
+ a.dropdown-item.has-text-danger(href="#" data-action="delete-agent" data-agent-name=agent_name)
69
+ span.icon.is-small
70
+ i.fas.fa-trash-alt
71
+ span Delete Agent