robot_lab 0.0.1
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.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/.github/workflows/deploy-yard-docs.yml +52 -0
- data/CHANGELOG.md +55 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +332 -0
- data/Rakefile +67 -0
- data/docs/api/adapters/anthropic.md +121 -0
- data/docs/api/adapters/gemini.md +133 -0
- data/docs/api/adapters/index.md +104 -0
- data/docs/api/adapters/openai.md +134 -0
- data/docs/api/core/index.md +113 -0
- data/docs/api/core/memory.md +314 -0
- data/docs/api/core/network.md +291 -0
- data/docs/api/core/robot.md +273 -0
- data/docs/api/core/state.md +273 -0
- data/docs/api/core/tool.md +353 -0
- data/docs/api/history/active-record-adapter.md +195 -0
- data/docs/api/history/config.md +191 -0
- data/docs/api/history/index.md +132 -0
- data/docs/api/history/thread-manager.md +144 -0
- data/docs/api/index.md +82 -0
- data/docs/api/mcp/client.md +221 -0
- data/docs/api/mcp/index.md +111 -0
- data/docs/api/mcp/server.md +225 -0
- data/docs/api/mcp/transports.md +264 -0
- data/docs/api/messages/index.md +67 -0
- data/docs/api/messages/text-message.md +102 -0
- data/docs/api/messages/tool-call-message.md +144 -0
- data/docs/api/messages/tool-result-message.md +154 -0
- data/docs/api/messages/user-message.md +171 -0
- data/docs/api/streaming/context.md +174 -0
- data/docs/api/streaming/events.md +237 -0
- data/docs/api/streaming/index.md +108 -0
- data/docs/architecture/core-concepts.md +243 -0
- data/docs/architecture/index.md +138 -0
- data/docs/architecture/message-flow.md +320 -0
- data/docs/architecture/network-orchestration.md +216 -0
- data/docs/architecture/robot-execution.md +243 -0
- data/docs/architecture/state-management.md +323 -0
- data/docs/assets/css/custom.css +56 -0
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +216 -0
- data/docs/examples/basic-chat.md +193 -0
- data/docs/examples/index.md +129 -0
- data/docs/examples/mcp-server.md +290 -0
- data/docs/examples/multi-robot-network.md +312 -0
- data/docs/examples/rails-application.md +420 -0
- data/docs/examples/tool-usage.md +310 -0
- data/docs/getting-started/configuration.md +230 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +179 -0
- data/docs/getting-started/quick-start.md +203 -0
- data/docs/guides/building-robots.md +376 -0
- data/docs/guides/creating-networks.md +366 -0
- data/docs/guides/history.md +359 -0
- data/docs/guides/index.md +68 -0
- data/docs/guides/mcp-integration.md +356 -0
- data/docs/guides/memory.md +309 -0
- data/docs/guides/rails-integration.md +432 -0
- data/docs/guides/streaming.md +314 -0
- data/docs/guides/using-tools.md +394 -0
- data/docs/index.md +160 -0
- data/examples/01_simple_robot.rb +38 -0
- data/examples/02_tools.rb +106 -0
- data/examples/03_network.rb +103 -0
- data/examples/04_mcp.rb +219 -0
- data/examples/05_streaming.rb +124 -0
- data/examples/06_prompt_templates.rb +324 -0
- data/examples/07_network_memory.rb +329 -0
- data/examples/prompts/assistant/system.txt.erb +2 -0
- data/examples/prompts/assistant/user.txt.erb +1 -0
- data/examples/prompts/billing/system.txt.erb +7 -0
- data/examples/prompts/billing/user.txt.erb +1 -0
- data/examples/prompts/classifier/system.txt.erb +4 -0
- data/examples/prompts/classifier/user.txt.erb +1 -0
- data/examples/prompts/entity_extractor/system.txt.erb +11 -0
- data/examples/prompts/entity_extractor/user.txt.erb +3 -0
- data/examples/prompts/escalation/system.txt.erb +35 -0
- data/examples/prompts/escalation/user.txt.erb +34 -0
- data/examples/prompts/general/system.txt.erb +4 -0
- data/examples/prompts/general/user.txt.erb +1 -0
- data/examples/prompts/github_assistant/system.txt.erb +6 -0
- data/examples/prompts/github_assistant/user.txt.erb +1 -0
- data/examples/prompts/helper/system.txt.erb +1 -0
- data/examples/prompts/helper/user.txt.erb +1 -0
- data/examples/prompts/keyword_extractor/system.txt.erb +8 -0
- data/examples/prompts/keyword_extractor/user.txt.erb +3 -0
- data/examples/prompts/order_support/system.txt.erb +27 -0
- data/examples/prompts/order_support/user.txt.erb +22 -0
- data/examples/prompts/product_support/system.txt.erb +30 -0
- data/examples/prompts/product_support/user.txt.erb +32 -0
- data/examples/prompts/sentiment_analyzer/system.txt.erb +9 -0
- data/examples/prompts/sentiment_analyzer/user.txt.erb +3 -0
- data/examples/prompts/synthesizer/system.txt.erb +14 -0
- data/examples/prompts/synthesizer/user.txt.erb +15 -0
- data/examples/prompts/technical/system.txt.erb +7 -0
- data/examples/prompts/technical/user.txt.erb +1 -0
- data/examples/prompts/triage/system.txt.erb +16 -0
- data/examples/prompts/triage/user.txt.erb +17 -0
- data/lib/generators/robot_lab/install_generator.rb +78 -0
- data/lib/generators/robot_lab/robot_generator.rb +55 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +41 -0
- data/lib/generators/robot_lab/templates/migration.rb.tt +32 -0
- data/lib/generators/robot_lab/templates/result_model.rb.tt +52 -0
- data/lib/generators/robot_lab/templates/robot.rb.tt +46 -0
- data/lib/generators/robot_lab/templates/robot_test.rb.tt +32 -0
- data/lib/generators/robot_lab/templates/routing_robot.rb.tt +53 -0
- data/lib/generators/robot_lab/templates/thread_model.rb.tt +40 -0
- data/lib/robot_lab/adapters/anthropic.rb +163 -0
- data/lib/robot_lab/adapters/base.rb +85 -0
- data/lib/robot_lab/adapters/gemini.rb +193 -0
- data/lib/robot_lab/adapters/openai.rb +159 -0
- data/lib/robot_lab/adapters/registry.rb +81 -0
- data/lib/robot_lab/configuration.rb +143 -0
- data/lib/robot_lab/error.rb +32 -0
- data/lib/robot_lab/errors.rb +70 -0
- data/lib/robot_lab/history/active_record_adapter.rb +146 -0
- data/lib/robot_lab/history/config.rb +115 -0
- data/lib/robot_lab/history/thread_manager.rb +93 -0
- data/lib/robot_lab/mcp/client.rb +210 -0
- data/lib/robot_lab/mcp/server.rb +84 -0
- data/lib/robot_lab/mcp/transports/base.rb +56 -0
- data/lib/robot_lab/mcp/transports/sse.rb +117 -0
- data/lib/robot_lab/mcp/transports/stdio.rb +133 -0
- data/lib/robot_lab/mcp/transports/streamable_http.rb +139 -0
- data/lib/robot_lab/mcp/transports/websocket.rb +108 -0
- data/lib/robot_lab/memory.rb +882 -0
- data/lib/robot_lab/memory_change.rb +123 -0
- data/lib/robot_lab/message.rb +357 -0
- data/lib/robot_lab/network.rb +350 -0
- data/lib/robot_lab/rails/engine.rb +29 -0
- data/lib/robot_lab/rails/railtie.rb +42 -0
- data/lib/robot_lab/robot.rb +560 -0
- data/lib/robot_lab/robot_result.rb +205 -0
- data/lib/robot_lab/robotic_model.rb +324 -0
- data/lib/robot_lab/state_proxy.rb +188 -0
- data/lib/robot_lab/streaming/context.rb +144 -0
- data/lib/robot_lab/streaming/events.rb +95 -0
- data/lib/robot_lab/streaming/sequence_counter.rb +48 -0
- data/lib/robot_lab/task.rb +117 -0
- data/lib/robot_lab/tool.rb +223 -0
- data/lib/robot_lab/tool_config.rb +112 -0
- data/lib/robot_lab/tool_manifest.rb +234 -0
- data/lib/robot_lab/user_message.rb +118 -0
- data/lib/robot_lab/version.rb +5 -0
- data/lib/robot_lab/waiter.rb +73 -0
- data/lib/robot_lab.rb +195 -0
- data/mkdocs.yml +214 -0
- data/sig/robot_lab.rbs +4 -0
- metadata +442 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "simple_flow"
|
|
4
|
+
|
|
5
|
+
module RobotLab
|
|
6
|
+
# Orchestrates multiple robots in a pipeline workflow
|
|
7
|
+
#
|
|
8
|
+
# Network is a thin wrapper around SimpleFlow::Pipeline that provides
|
|
9
|
+
# a clean DSL for defining robot workflows with sequential, parallel,
|
|
10
|
+
# and conditional execution.
|
|
11
|
+
#
|
|
12
|
+
# == Shared Memory
|
|
13
|
+
#
|
|
14
|
+
# Networks provide a shared reactive memory that all robots can read and write.
|
|
15
|
+
# Robots can subscribe to memory keys and be notified when values change,
|
|
16
|
+
# or use blocking reads to wait for values from other robots.
|
|
17
|
+
#
|
|
18
|
+
# == Broadcast Messages
|
|
19
|
+
#
|
|
20
|
+
# Networks support a broadcast channel for network-wide announcements.
|
|
21
|
+
# Use `broadcast` to send messages to all robots, and `on_broadcast` to
|
|
22
|
+
# register handlers for incoming broadcasts.
|
|
23
|
+
#
|
|
24
|
+
# @example Sequential execution
|
|
25
|
+
# network = RobotLab.create_network(name: "pipeline") do
|
|
26
|
+
# task :analyst, analyst_robot, depends_on: :none
|
|
27
|
+
# task :writer, writer_robot, depends_on: [:analyst]
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# @example With per-task context
|
|
31
|
+
# network = RobotLab.create_network(name: "support") do
|
|
32
|
+
# task :classifier, classifier_robot, depends_on: :none
|
|
33
|
+
# task :billing, billing_robot,
|
|
34
|
+
# context: { department: "billing" },
|
|
35
|
+
# tools: [RefundTool],
|
|
36
|
+
# depends_on: :optional
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# @example Parallel execution with shared memory
|
|
40
|
+
# network = RobotLab.create_network(name: "analysis") do
|
|
41
|
+
# task :fetch, fetcher_robot, depends_on: :none
|
|
42
|
+
# task :sentiment, sentiment_robot, depends_on: [:fetch]
|
|
43
|
+
# task :entities, entity_robot, depends_on: [:fetch]
|
|
44
|
+
# task :summarize, summary_robot, depends_on: [:sentiment, :entities]
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# # In sentiment_robot:
|
|
48
|
+
# memory.set(:sentiment, analyze_sentiment(text))
|
|
49
|
+
#
|
|
50
|
+
# # In summarize_robot:
|
|
51
|
+
# results = memory.get(:sentiment, :entities, wait: 60)
|
|
52
|
+
#
|
|
53
|
+
# @example Broadcasting
|
|
54
|
+
# network.on_broadcast do |message|
|
|
55
|
+
# puts "Received: #{message[:event]}"
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# network.broadcast(event: :pause, reason: "rate limit")
|
|
59
|
+
#
|
|
60
|
+
class Network
|
|
61
|
+
# Reserved key for broadcast messages in memory
|
|
62
|
+
BROADCAST_KEY = :_network_broadcast
|
|
63
|
+
|
|
64
|
+
# @!attribute [r] name
|
|
65
|
+
# @return [String] unique identifier for the network
|
|
66
|
+
# @!attribute [r] pipeline
|
|
67
|
+
# @return [SimpleFlow::Pipeline] the underlying pipeline
|
|
68
|
+
# @!attribute [r] robots
|
|
69
|
+
# @return [Hash<String, Robot>] robots in this network, keyed by name
|
|
70
|
+
# @!attribute [r] memory
|
|
71
|
+
# @return [Memory] shared memory for all robots in the network
|
|
72
|
+
attr_reader :name, :pipeline, :robots, :memory
|
|
73
|
+
|
|
74
|
+
# Creates a new Network instance.
|
|
75
|
+
#
|
|
76
|
+
# @param name [String] unique identifier for the network
|
|
77
|
+
# @param concurrency [Symbol] concurrency model (:auto, :threads, :async)
|
|
78
|
+
# @param memory [Memory, nil] optional pre-configured memory instance
|
|
79
|
+
# @yield Block for defining pipeline tasks
|
|
80
|
+
#
|
|
81
|
+
# @example
|
|
82
|
+
# network = Network.new(name: "support") do
|
|
83
|
+
# task :classifier, classifier, depends_on: :none
|
|
84
|
+
# task :billing, billing_robot, context: { dept: "billing" }, depends_on: :optional
|
|
85
|
+
# end
|
|
86
|
+
#
|
|
87
|
+
def initialize(name:, concurrency: :auto, memory: nil, &block)
|
|
88
|
+
@name = name.to_s
|
|
89
|
+
@robots = {}
|
|
90
|
+
@tasks = {}
|
|
91
|
+
@pipeline = SimpleFlow::Pipeline.new(concurrency: concurrency)
|
|
92
|
+
@memory = memory || Memory.new(network_name: @name)
|
|
93
|
+
@broadcast_handlers = []
|
|
94
|
+
|
|
95
|
+
instance_eval(&block) if block_given?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Add a robot as a pipeline task with optional per-task configuration
|
|
99
|
+
#
|
|
100
|
+
# @param name [Symbol] task name
|
|
101
|
+
# @param robot [Robot] the robot instance
|
|
102
|
+
# @param context [Hash] task-specific context (deep-merged with run params)
|
|
103
|
+
# @param mcp [Symbol, Array] MCP server config (:none, :inherit, or array)
|
|
104
|
+
# @param tools [Symbol, Array] tools config (:none, :inherit, or array)
|
|
105
|
+
# @param memory [Memory, Hash, nil] task-specific memory
|
|
106
|
+
# @param depends_on [Symbol, Array<Symbol>] dependencies (:none, :optional, or task names)
|
|
107
|
+
# @return [self]
|
|
108
|
+
#
|
|
109
|
+
# @example Entry point task
|
|
110
|
+
# task :classifier, classifier_robot, depends_on: :none
|
|
111
|
+
#
|
|
112
|
+
# @example Task with context and tools
|
|
113
|
+
# task :billing, billing_robot,
|
|
114
|
+
# context: { department: "billing", escalation: 2 },
|
|
115
|
+
# tools: [RefundTool, InvoiceTool],
|
|
116
|
+
# depends_on: :optional
|
|
117
|
+
#
|
|
118
|
+
# @example Task with dependencies
|
|
119
|
+
# task :writer, writer_robot, depends_on: [:analyst]
|
|
120
|
+
#
|
|
121
|
+
def task(name, robot, context: {}, mcp: :none, tools: :none, memory: nil, depends_on: :none)
|
|
122
|
+
task_wrapper = Task.new(
|
|
123
|
+
name: name,
|
|
124
|
+
robot: robot,
|
|
125
|
+
context: context,
|
|
126
|
+
mcp: mcp,
|
|
127
|
+
tools: tools,
|
|
128
|
+
memory: memory
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@robots[name.to_s] = robot
|
|
132
|
+
@tasks[name.to_s] = task_wrapper
|
|
133
|
+
@pipeline.step(name, task_wrapper, depends_on: depends_on)
|
|
134
|
+
self
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Define a parallel execution block
|
|
138
|
+
#
|
|
139
|
+
# @param name [Symbol, nil] optional name for the parallel group
|
|
140
|
+
# @param depends_on [Symbol, Array] dependencies for this group
|
|
141
|
+
# @yield Block containing task definitions
|
|
142
|
+
# @return [self]
|
|
143
|
+
#
|
|
144
|
+
# @example Named parallel group
|
|
145
|
+
# parallel :fetch_data, depends_on: :validate do
|
|
146
|
+
# task :fetch_orders, orders_robot
|
|
147
|
+
# task :fetch_products, products_robot
|
|
148
|
+
# end
|
|
149
|
+
# task :process, processor, depends_on: :fetch_data
|
|
150
|
+
#
|
|
151
|
+
def parallel(name = nil, depends_on: :none, &block)
|
|
152
|
+
@pipeline.parallel(name, depends_on: depends_on, &block)
|
|
153
|
+
self
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Run the network with the given context
|
|
157
|
+
#
|
|
158
|
+
# All robots share the network's memory during execution. The memory
|
|
159
|
+
# is passed to each robot and can be used for inter-robot communication.
|
|
160
|
+
#
|
|
161
|
+
# @param run_context [Hash] context passed to all robots (message:, user_id:, etc.)
|
|
162
|
+
# @return [SimpleFlow::Result] final pipeline result
|
|
163
|
+
#
|
|
164
|
+
# @example
|
|
165
|
+
# result = network.run(message: "I need help with billing", user_id: 123)
|
|
166
|
+
# result.value # => RobotResult from last robot
|
|
167
|
+
# result.context[:classifier] # => RobotResult from classifier
|
|
168
|
+
#
|
|
169
|
+
def run(**run_context)
|
|
170
|
+
# Include shared memory in run params so robots can access it
|
|
171
|
+
run_context[:network_memory] = @memory
|
|
172
|
+
|
|
173
|
+
initial_result = SimpleFlow::Result.new(
|
|
174
|
+
run_context,
|
|
175
|
+
context: { run_params: run_context }
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@pipeline.call_parallel(initial_result)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Broadcast a message to all robots in the network.
|
|
182
|
+
#
|
|
183
|
+
# This sends a network-wide message that all robots subscribed via
|
|
184
|
+
# `on_broadcast` will receive asynchronously.
|
|
185
|
+
#
|
|
186
|
+
# @param payload [Hash] the message payload
|
|
187
|
+
# @return [self]
|
|
188
|
+
#
|
|
189
|
+
# @example Pause all robots
|
|
190
|
+
# network.broadcast(event: :pause, reason: "rate limit hit")
|
|
191
|
+
#
|
|
192
|
+
# @example Signal completion
|
|
193
|
+
# network.broadcast(event: :phase_complete, phase: "analysis")
|
|
194
|
+
#
|
|
195
|
+
def broadcast(payload)
|
|
196
|
+
message = {
|
|
197
|
+
payload: payload,
|
|
198
|
+
network: @name,
|
|
199
|
+
timestamp: Time.now
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Notify handlers asynchronously
|
|
203
|
+
@broadcast_handlers.each do |handler|
|
|
204
|
+
dispatch_async { handler.call(message) }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Also set in memory so robots can subscribe via memory.subscribe
|
|
208
|
+
@memory.set(BROADCAST_KEY, message)
|
|
209
|
+
|
|
210
|
+
self
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Register a handler for broadcast messages.
|
|
214
|
+
#
|
|
215
|
+
# The handler is called asynchronously whenever `broadcast` is called.
|
|
216
|
+
#
|
|
217
|
+
# @yield [Hash] the broadcast message with :payload, :network, :timestamp
|
|
218
|
+
# @return [self]
|
|
219
|
+
#
|
|
220
|
+
# @example
|
|
221
|
+
# network.on_broadcast do |message|
|
|
222
|
+
# case message[:payload][:event]
|
|
223
|
+
# when :pause
|
|
224
|
+
# pause_current_work
|
|
225
|
+
# when :resume
|
|
226
|
+
# resume_work
|
|
227
|
+
# end
|
|
228
|
+
# end
|
|
229
|
+
#
|
|
230
|
+
def on_broadcast(&block)
|
|
231
|
+
raise ArgumentError, "Block required for on_broadcast" unless block_given?
|
|
232
|
+
|
|
233
|
+
@broadcast_handlers << block
|
|
234
|
+
self
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Reset the shared memory.
|
|
238
|
+
#
|
|
239
|
+
# Clears all values in the network's shared memory. This is useful
|
|
240
|
+
# between runs if you want to start with a fresh memory state.
|
|
241
|
+
#
|
|
242
|
+
# @return [self]
|
|
243
|
+
#
|
|
244
|
+
def reset_memory
|
|
245
|
+
@memory.reset
|
|
246
|
+
self
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Get a robot by name
|
|
250
|
+
#
|
|
251
|
+
# @param name [String, Symbol]
|
|
252
|
+
# @return [Robot, nil]
|
|
253
|
+
#
|
|
254
|
+
def robot(name)
|
|
255
|
+
@robots[name.to_s]
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# @!method [](name)
|
|
259
|
+
# Alias for {#robot}.
|
|
260
|
+
# @param name [String, Symbol] the robot name
|
|
261
|
+
# @return [Robot, nil]
|
|
262
|
+
alias [] robot
|
|
263
|
+
|
|
264
|
+
# Get all robots in the network
|
|
265
|
+
#
|
|
266
|
+
# @return [Array<Robot>]
|
|
267
|
+
#
|
|
268
|
+
def available_robots
|
|
269
|
+
@robots.values
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Add a robot to the network without adding it as a task
|
|
273
|
+
#
|
|
274
|
+
# Useful for dynamically adding robots that will be referenced later.
|
|
275
|
+
#
|
|
276
|
+
# @param robot [Robot] the robot instance to add
|
|
277
|
+
# @return [self]
|
|
278
|
+
# @raise [ArgumentError] if a robot with the same name already exists
|
|
279
|
+
#
|
|
280
|
+
def add_robot(robot)
|
|
281
|
+
if @robots.key?(robot.name)
|
|
282
|
+
raise ArgumentError, "Robot '#{robot.name}' already exists in network '#{@name}'"
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
@robots[robot.name] = robot
|
|
286
|
+
self
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Visualize the pipeline as ASCII
|
|
290
|
+
#
|
|
291
|
+
# @return [String, nil]
|
|
292
|
+
#
|
|
293
|
+
def visualize
|
|
294
|
+
@pipeline.visualize_ascii
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Export pipeline to Mermaid format
|
|
298
|
+
#
|
|
299
|
+
# @return [String, nil]
|
|
300
|
+
#
|
|
301
|
+
def to_mermaid
|
|
302
|
+
@pipeline.visualize_mermaid
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Export pipeline to DOT format (Graphviz)
|
|
306
|
+
#
|
|
307
|
+
# @return [String, nil]
|
|
308
|
+
#
|
|
309
|
+
def to_dot
|
|
310
|
+
@pipeline.visualize_dot
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Get the execution plan
|
|
314
|
+
#
|
|
315
|
+
# @return [String, nil]
|
|
316
|
+
#
|
|
317
|
+
def execution_plan
|
|
318
|
+
@pipeline.execution_plan
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Converts the network to a hash representation
|
|
322
|
+
#
|
|
323
|
+
# @return [Hash]
|
|
324
|
+
#
|
|
325
|
+
def to_h
|
|
326
|
+
{
|
|
327
|
+
name: name,
|
|
328
|
+
robots: @robots.keys,
|
|
329
|
+
tasks: @tasks.keys,
|
|
330
|
+
optional_tasks: @pipeline.optional_steps.to_a
|
|
331
|
+
}.compact
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
private
|
|
335
|
+
|
|
336
|
+
def dispatch_async(&block)
|
|
337
|
+
# Use Async if available (preferred for fiber-based concurrency)
|
|
338
|
+
if defined?(Async) && Async::Task.current?
|
|
339
|
+
Async { block.call }
|
|
340
|
+
else
|
|
341
|
+
# Fall back to Thread for basic async dispatch
|
|
342
|
+
Thread.new do
|
|
343
|
+
block.call
|
|
344
|
+
rescue StandardError => e
|
|
345
|
+
warn "Network broadcast handler error: #{e.message}"
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
module Rails
|
|
5
|
+
# Rails Engine for RobotLab integration
|
|
6
|
+
#
|
|
7
|
+
# Provides automatic loading of RobotLab components and
|
|
8
|
+
# integration with Rails applications.
|
|
9
|
+
#
|
|
10
|
+
class Engine < ::Rails::Engine
|
|
11
|
+
isolate_namespace RobotLab
|
|
12
|
+
|
|
13
|
+
initializer "robot_lab.configure" do |app|
|
|
14
|
+
# Load configuration from Rails config
|
|
15
|
+
app.config.robot_lab ||= ActiveSupport::OrderedOptions.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer "robot_lab.add_autoload_paths", before: :set_autoload_paths do |app|
|
|
19
|
+
app.config.autoload_paths << root.join("app", "robots")
|
|
20
|
+
app.config.autoload_paths << root.join("app", "tools")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
config.generators do |g|
|
|
24
|
+
g.test_framework :minitest, fixture: false
|
|
25
|
+
g.fixture_replacement nil
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
module Rails
|
|
5
|
+
# Railtie for RobotLab Rails integration
|
|
6
|
+
#
|
|
7
|
+
# Provides configuration hooks and initialization for
|
|
8
|
+
# Rails applications using RobotLab.
|
|
9
|
+
#
|
|
10
|
+
class Railtie < ::Rails::Railtie
|
|
11
|
+
config.robot_lab = ActiveSupport::OrderedOptions.new
|
|
12
|
+
|
|
13
|
+
initializer "robot_lab.configuration" do |app|
|
|
14
|
+
RobotLab.configure do |config|
|
|
15
|
+
# Apply Rails-specific configuration
|
|
16
|
+
rails_config = app.config.robot_lab
|
|
17
|
+
|
|
18
|
+
config.default_model = rails_config.default_model if rails_config.default_model
|
|
19
|
+
config.default_provider = rails_config.default_provider if rails_config.default_provider
|
|
20
|
+
config.logger = ::Rails.logger
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
initializer "robot_lab.active_record" do
|
|
25
|
+
ActiveSupport.on_load(:active_record) do
|
|
26
|
+
# Extend ActiveRecord with RobotLab concerns if needed
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
rake_tasks do
|
|
31
|
+
# Load RobotLab rake tasks
|
|
32
|
+
path = File.expand_path("../tasks", __dir__)
|
|
33
|
+
Dir.glob("#{path}/**/*.rake").each { |f| load f }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
generators do
|
|
37
|
+
require "generators/robot_lab/install_generator"
|
|
38
|
+
require "generators/robot_lab/robot_generator"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|