robot_lab 0.2.1 → 0.2.7
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 +4 -4
- data/.envrc +1 -0
- data/.loki +60 -0
- data/.quality/reek_baseline.txt +43 -0
- data/.rubocop.yml +5 -167
- data/CHANGELOG.md +54 -0
- data/README.md +185 -64
- data/Rakefile +28 -12
- data/docs/api/core/index.md +41 -14
- data/docs/api/core/memory.md +247 -29
- data/docs/api/core/network.md +285 -33
- data/docs/api/core/result.md +120 -32
- data/docs/api/core/robot.md +636 -60
- data/docs/api/core/state.md +87 -197
- data/docs/api/core/tool.md +165 -20
- data/docs/api/errors.md +152 -14
- data/docs/api/hooks.md +469 -0
- data/docs/api/index.md +83 -7
- data/docs/api/mcp/client.md +129 -35
- data/docs/api/mcp/index.md +164 -23
- data/docs/api/mcp/server.md +27 -3
- data/docs/api/mcp/transports.md +94 -22
- data/docs/api/messages/index.md +26 -3
- data/docs/api/messages/text-message.md +33 -11
- data/docs/api/messages/tool-call-message.md +27 -4
- data/docs/api/messages/tool-result-message.md +23 -4
- data/docs/api/messages/user-message.md +45 -8
- data/docs/api/skills.md +519 -0
- data/docs/api/streaming/context.md +28 -5
- data/docs/api/streaming/index.md +57 -11
- data/docs/api/support.md +846 -0
- data/docs/architecture/core-concepts.md +118 -30
- data/docs/architecture/index.md +86 -11
- data/docs/architecture/message-flow.md +66 -29
- data/docs/architecture/network-orchestration.md +153 -38
- data/docs/architecture/robot-execution.md +173 -91
- data/docs/architecture/state-management.md +31 -12
- data/docs/concepts.md +176 -21
- data/docs/examples/basic-chat.md +72 -19
- data/docs/examples/index.md +117 -31
- data/docs/examples/mcp-server.md +154 -45
- data/docs/examples/multi-robot-network.md +91 -21
- data/docs/examples/tool-usage.md +104 -37
- data/docs/getting-started/configuration.md +299 -93
- data/docs/getting-started/installation.md +53 -41
- data/docs/getting-started/quick-start.md +51 -6
- data/docs/guides/building-robots.md +296 -50
- data/docs/guides/creating-networks.md +220 -31
- data/docs/guides/hooks.md +1084 -0
- data/docs/guides/index.md +5 -0
- data/docs/guides/knowledge.md +37 -6
- data/docs/guides/mcp-integration.md +211 -44
- data/docs/guides/memory.md +103 -12
- data/docs/guides/observability.md +166 -51
- data/docs/guides/streaming.md +184 -125
- data/docs/guides/using-tools.md +300 -11
- data/docs/index.md +37 -5
- data/examples/01_simple_robot.rb +2 -2
- data/examples/02_tools.rb +14 -4
- data/examples/03_network.rb +23 -9
- data/examples/04_mcp.rb +11 -4
- data/examples/05_streaming.rb +8 -5
- data/examples/06_prompt_templates.rb +13 -9
- data/examples/07_network_memory.rb +5 -5
- data/examples/08_llm_config.rb +20 -15
- data/examples/09_chaining.rb +4 -4
- data/examples/11_network_introspection.rb +4 -4
- data/examples/12_message_bus.rb +2 -2
- data/examples/13_spawn.rb +2 -2
- data/examples/14_rusty_circuit/README.md +1 -0
- data/examples/14_rusty_circuit/comic.rb +7 -3
- data/examples/14_rusty_circuit/display.rb +14 -0
- data/examples/14_rusty_circuit/heckler.rb +8 -6
- data/examples/14_rusty_circuit/open_mic.rb +17 -6
- data/examples/14_rusty_circuit/scout.rb +17 -10
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
- data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
- data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
- data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
- data/examples/16_writers_room/writer.rb +22 -22
- data/examples/16_writers_room/writers_room.rb +2 -0
- data/examples/17_skills.rb +14 -13
- data/examples/18_rails/README.md +20 -1
- data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
- data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
- data/examples/18_rails/app/robots/chat_robot.rb +11 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
- data/examples/19_token_tracking.rb +25 -9
- data/examples/20_circuit_breaker.rb +10 -7
- data/examples/21_learning_loop.rb +42 -16
- data/examples/22_context_compression.rb +23 -23
- data/examples/23_convergence.rb +24 -17
- data/examples/24_structured_delegation.rb +13 -8
- data/examples/25_history_search.rb +12 -8
- data/examples/27_incident_response/incident_response.rb +31 -13
- data/examples/28_mcp_discovery.rb +17 -13
- data/examples/29_ractor_tools.rb +4 -2
- data/examples/30_ractor_network.rb +22 -17
- data/examples/31_launch_assessment.rb +20 -9
- data/examples/32_newsletter_reader.rb +7 -2
- data/examples/33_stock_predictor.rb +34 -13
- data/examples/34_agentskills.rb +7 -3
- data/examples/35_hooks.rb +266 -0
- data/examples/README.md +203 -42
- data/examples/common.rb +79 -7
- data/examples/xyzzy.rb +97 -0
- data/lib/robot_lab/agent_skill.rb +5 -4
- data/lib/robot_lab/budget/ledger.rb +98 -0
- data/lib/robot_lab/capabilities.rb +84 -0
- data/lib/robot_lab/config/defaults.yml +10 -0
- data/lib/robot_lab/config.rb +14 -9
- data/lib/robot_lab/error.rb +34 -4
- data/lib/robot_lab/errors.rb +45 -0
- data/lib/robot_lab/hook.rb +79 -0
- data/lib/robot_lab/hook_context.rb +194 -0
- data/lib/robot_lab/hook_registry.rb +55 -0
- data/lib/robot_lab/hooks.rb +87 -0
- data/lib/robot_lab/mcp/connection_poller.rb +2 -2
- data/lib/robot_lab/names.rb +402 -0
- data/lib/robot_lab/narrator.rb +87 -0
- data/lib/robot_lab/network.rb +77 -20
- data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
- data/lib/robot_lab/robot/budget.rb +89 -0
- data/lib/robot_lab/robot/bus_messaging.rb +90 -18
- data/lib/robot_lab/robot/hooking.rb +56 -0
- data/lib/robot_lab/robot/mcp_management.rb +8 -2
- data/lib/robot_lab/robot/template_rendering.rb +18 -5
- data/lib/robot_lab/robot.rb +233 -89
- data/lib/robot_lab/run_config.rb +24 -5
- data/lib/robot_lab/runnable.rb +51 -0
- data/lib/robot_lab/sandbox/null.rb +13 -0
- data/lib/robot_lab/sandbox/seatbelt.rb +104 -0
- data/lib/robot_lab/sandbox.rb +52 -0
- data/lib/robot_lab/script_tool.rb +60 -2
- data/lib/robot_lab/task.rb +26 -20
- data/lib/robot_lab/tool.rb +52 -11
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +70 -15
- data/mkdocs.yml +7 -1
- metadata +41 -99
- data/examples/temp.md +0 -51
- data/site/404.html +0 -2300
- data/site/api/core/index.html +0 -2706
- data/site/api/core/memory/index.html +0 -3793
- data/site/api/core/network/index.html +0 -3500
- data/site/api/core/robot/index.html +0 -4566
- data/site/api/core/state/index.html +0 -3390
- data/site/api/core/tool/index.html +0 -3843
- data/site/api/index.html +0 -2635
- data/site/api/mcp/client/index.html +0 -3435
- data/site/api/mcp/index.html +0 -2783
- data/site/api/mcp/server/index.html +0 -3252
- data/site/api/mcp/transports/index.html +0 -3352
- data/site/api/messages/index.html +0 -2641
- data/site/api/messages/text-message/index.html +0 -3087
- data/site/api/messages/tool-call-message/index.html +0 -3159
- data/site/api/messages/tool-result-message/index.html +0 -3252
- data/site/api/messages/user-message/index.html +0 -3212
- data/site/api/streaming/context/index.html +0 -3282
- data/site/api/streaming/events/index.html +0 -3347
- data/site/api/streaming/index.html +0 -2738
- data/site/architecture/core-concepts/index.html +0 -3757
- data/site/architecture/index.html +0 -2797
- data/site/architecture/message-flow/index.html +0 -3238
- data/site/architecture/network-orchestration/index.html +0 -3433
- data/site/architecture/robot-execution/index.html +0 -3140
- data/site/architecture/state-management/index.html +0 -3498
- data/site/assets/css/custom.css +0 -56
- data/site/assets/images/favicon.png +0 -0
- data/site/assets/images/robot_lab.jpg +0 -0
- data/site/assets/javascripts/bundle.79ae519e.min.js +0 -16
- data/site/assets/javascripts/bundle.79ae519e.min.js.map +0 -7
- data/site/assets/javascripts/lunr/min/lunr.ar.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.da.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.de.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.du.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.el.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.es.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.fi.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.fr.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.he.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.hi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.hu.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.hy.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.it.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ja.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.jp.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.kn.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.ko.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.multi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.nl.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.no.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.pt.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ro.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ru.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.sa.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.sv.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ta.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.te.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.th.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.tr.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.vi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.zh.min.js +0 -1
- data/site/assets/javascripts/lunr/tinyseg.js +0 -206
- data/site/assets/javascripts/lunr/wordcut.js +0 -6708
- data/site/assets/javascripts/workers/search.2c215733.min.js +0 -42
- data/site/assets/javascripts/workers/search.2c215733.min.js.map +0 -7
- data/site/assets/stylesheets/main.484c7ddc.min.css +0 -1
- data/site/assets/stylesheets/main.484c7ddc.min.css.map +0 -1
- data/site/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
- data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -1
- data/site/concepts/index.html +0 -3455
- data/site/examples/basic-chat/index.html +0 -2880
- data/site/examples/index.html +0 -2907
- data/site/examples/mcp-server/index.html +0 -3018
- data/site/examples/multi-robot-network/index.html +0 -3131
- data/site/examples/rails-application/index.html +0 -3329
- data/site/examples/tool-usage/index.html +0 -3085
- data/site/getting-started/configuration/index.html +0 -3745
- data/site/getting-started/index.html +0 -2572
- data/site/getting-started/installation/index.html +0 -2981
- data/site/getting-started/quick-start/index.html +0 -2942
- data/site/guides/building-robots/index.html +0 -4290
- data/site/guides/creating-networks/index.html +0 -3858
- data/site/guides/index.html +0 -2586
- data/site/guides/mcp-integration/index.html +0 -3581
- data/site/guides/memory/index.html +0 -3586
- data/site/guides/rails-integration/index.html +0 -4019
- data/site/guides/streaming/index.html +0 -3157
- data/site/guides/using-tools/index.html +0 -3802
- data/site/index.html +0 -2671
- data/site/search/search_index.json +0 -1
- data/site/sitemap.xml +0 -183
- data/site/sitemap.xml.gz +0 -0
- data/site/tags.json +0 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
class HookContext
|
|
5
|
+
attr_reader :event, :metadata
|
|
6
|
+
|
|
7
|
+
def initialize(event:, metadata: nil)
|
|
8
|
+
@event = event.to_sym
|
|
9
|
+
@metadata = metadata || ExtensionState.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ext(name)
|
|
13
|
+
metadata.ext(name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def local
|
|
17
|
+
raise ArgumentError, "No hook namespace active" unless @namespace
|
|
18
|
+
|
|
19
|
+
ext(@namespace)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def with_namespace(namespace)
|
|
23
|
+
previous = @namespace
|
|
24
|
+
@namespace = namespace&.to_sym
|
|
25
|
+
yield self
|
|
26
|
+
ensure
|
|
27
|
+
@namespace = previous
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_h
|
|
31
|
+
public_snapshot.merge(metadata: metadata.to_h)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def public_snapshot
|
|
37
|
+
public_methods(false)
|
|
38
|
+
.grep_v(/=\z/)
|
|
39
|
+
.reject { |name| %i[to_h local].include?(name) }
|
|
40
|
+
.sort
|
|
41
|
+
.to_h { |name| [name, public_send(name)] }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class RunHookContext < HookContext
|
|
46
|
+
attr_reader :robot, :network, :task, :memory, :config
|
|
47
|
+
attr_accessor :request, :response, :error
|
|
48
|
+
|
|
49
|
+
def initialize(robot:, request:, network: nil, task: nil, memory: nil, config: nil, response: nil, error: nil, **)
|
|
50
|
+
super(event: :run, **)
|
|
51
|
+
@robot = robot
|
|
52
|
+
@network = network
|
|
53
|
+
@task = task
|
|
54
|
+
@memory = memory
|
|
55
|
+
@config = config
|
|
56
|
+
@request = request
|
|
57
|
+
@response = response
|
|
58
|
+
@error = error
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class LlmGenerationHookContext < RunHookContext
|
|
63
|
+
attr_reader :iteration
|
|
64
|
+
attr_accessor :generation_response
|
|
65
|
+
|
|
66
|
+
def initialize(iteration: 0, generation_response: nil, **)
|
|
67
|
+
super(**)
|
|
68
|
+
@iteration = iteration
|
|
69
|
+
@generation_response = generation_response
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class ToolCallHookContext < HookContext
|
|
74
|
+
attr_reader :tool, :tool_name, :tool_args, :robot
|
|
75
|
+
attr_accessor :tool_result, :tool_error
|
|
76
|
+
|
|
77
|
+
def initialize(tool:, tool_args:, robot: nil, tool_result: nil, tool_error: nil, **)
|
|
78
|
+
super(event: :tool_call, **)
|
|
79
|
+
@tool = tool
|
|
80
|
+
@tool_name = tool.respond_to?(:name) ? tool.name : tool.class.name
|
|
81
|
+
@tool_args = tool_args
|
|
82
|
+
@robot = robot
|
|
83
|
+
@tool_result = tool_result
|
|
84
|
+
@tool_error = tool_error
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class NetworkRunHookContext < HookContext
|
|
89
|
+
attr_reader :network, :memory, :config
|
|
90
|
+
attr_accessor :context, :result, :error
|
|
91
|
+
|
|
92
|
+
def initialize(network:, context:, memory: nil, config: nil, result: nil, error: nil, **)
|
|
93
|
+
super(event: :network_run, **)
|
|
94
|
+
@network = network
|
|
95
|
+
@context = context
|
|
96
|
+
@memory = memory
|
|
97
|
+
@config = config
|
|
98
|
+
@result = result
|
|
99
|
+
@error = error
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class TaskHookContext < HookContext
|
|
104
|
+
attr_reader :network, :task, :task_name, :robot, :memory, :config
|
|
105
|
+
attr_accessor :result, :error
|
|
106
|
+
|
|
107
|
+
def initialize(task:, network: nil, robot: nil, memory: nil, config: nil, result: nil, error: nil, **)
|
|
108
|
+
super(event: :task, **)
|
|
109
|
+
@network = network
|
|
110
|
+
@task = task
|
|
111
|
+
@task_name = task.name
|
|
112
|
+
@robot = robot || task.robot
|
|
113
|
+
@memory = memory
|
|
114
|
+
@config = config
|
|
115
|
+
@result = result
|
|
116
|
+
@error = error
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class LearnHookContext < HookContext
|
|
121
|
+
attr_reader :robot, :text, :learnings_before
|
|
122
|
+
attr_accessor :stored, :error
|
|
123
|
+
|
|
124
|
+
def initialize(robot:, text:, learnings_before:, **)
|
|
125
|
+
super(event: :learn, **)
|
|
126
|
+
@robot = robot
|
|
127
|
+
@text = text
|
|
128
|
+
@learnings_before = learnings_before.freeze
|
|
129
|
+
@stored = false
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class CompactionHookContext < HookContext
|
|
134
|
+
attr_reader :robot, :messages_before, :config, :strategy
|
|
135
|
+
attr_accessor :compacted_messages, :error
|
|
136
|
+
|
|
137
|
+
def initialize(robot:, messages_before:, config:, strategy:, **)
|
|
138
|
+
super(event: :compaction, **)
|
|
139
|
+
@robot = robot
|
|
140
|
+
@messages_before = messages_before.freeze
|
|
141
|
+
@config = config
|
|
142
|
+
@strategy = strategy
|
|
143
|
+
@compacted_messages = nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# True once an on_compaction handler has supplied a replacement message set.
|
|
147
|
+
def handled?
|
|
148
|
+
!@compacted_messages.nil?
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class ExtensionState
|
|
153
|
+
def initialize
|
|
154
|
+
@states = Hash.new { |h, key| h[key] = DotState.new }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def ext(name)
|
|
158
|
+
@states[name.to_sym]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def to_h
|
|
162
|
+
@states.transform_values(&:to_h)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
class DotState
|
|
167
|
+
def initialize
|
|
168
|
+
@data = {}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def merge_defaults(hash)
|
|
172
|
+
hash.each { |k, v| @data[k.to_sym] = v unless @data.key?(k.to_sym) }
|
|
173
|
+
self
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def method_missing(name, *args)
|
|
177
|
+
key = name.to_s.delete_suffix('=').to_sym
|
|
178
|
+
|
|
179
|
+
if name.to_s.end_with?('=')
|
|
180
|
+
@data[key] = args.first
|
|
181
|
+
else
|
|
182
|
+
@data[key]
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def respond_to_missing?(*)
|
|
187
|
+
true
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def to_h
|
|
191
|
+
@data.dup
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
class HookRegistry
|
|
5
|
+
Registration = Data.define(:handler_class, :context) do
|
|
6
|
+
def initialize(handler_class:, context: nil)
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def namespace
|
|
11
|
+
handler_class.namespace
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@registrations = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Register a hook handler class.
|
|
20
|
+
#
|
|
21
|
+
# @param handler_class [Class] a RobotLab::Hook subclass
|
|
22
|
+
# @param context [Hash, nil] optional default values merged into ctx.local on each call
|
|
23
|
+
# @return [Registration]
|
|
24
|
+
def on(handler_class, context: nil)
|
|
25
|
+
unless handler_class.is_a?(Class) && handler_class < RobotLab::Hook
|
|
26
|
+
raise ArgumentError, "#{handler_class.inspect} must be a RobotLab::Hook subclass"
|
|
27
|
+
end
|
|
28
|
+
raise ArgumentError, "#{handler_class} has no namespace" if handler_class.namespace.nil?
|
|
29
|
+
|
|
30
|
+
registration = Registration.new(handler_class: handler_class, context: context)
|
|
31
|
+
@registrations << registration
|
|
32
|
+
registration
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns registrations whose handler implements the given hook method.
|
|
36
|
+
#
|
|
37
|
+
# @param hook_name [Symbol]
|
|
38
|
+
# @return [Array<Registration>]
|
|
39
|
+
def registrations_for(hook_name)
|
|
40
|
+
hook_sym = hook_name.to_sym
|
|
41
|
+
@registrations.select { |reg| reg.handler_class.singleton_class.public_method_defined?(hook_sym) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns all registrations (defensive copy).
|
|
45
|
+
#
|
|
46
|
+
# @return [Array<Registration>]
|
|
47
|
+
def registrations
|
|
48
|
+
@registrations.dup
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def clear
|
|
52
|
+
@registrations.clear
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
module Hooks
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def run(family, context, registries:, per_run_hooks: nil, &)
|
|
8
|
+
before = registrations(:"before_#{family}", registries, per_run_hooks)
|
|
9
|
+
around = registrations(:"around_#{family}", registries, per_run_hooks)
|
|
10
|
+
after = registrations(:"after_#{family}", registries, per_run_hooks)
|
|
11
|
+
errors = error_registrations(family, registries, per_run_hooks)
|
|
12
|
+
|
|
13
|
+
call_all(before, :"before_#{family}", context)
|
|
14
|
+
result = call_around(around, :"around_#{family}", context, &)
|
|
15
|
+
set_result(context, family, result)
|
|
16
|
+
call_all(after, :"after_#{family}", context)
|
|
17
|
+
result
|
|
18
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
19
|
+
context.error = e if context.respond_to?(:error=)
|
|
20
|
+
call_all(errors, :on_error, context)
|
|
21
|
+
raise
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call(hook_name, context, registries:, per_run_hooks: nil)
|
|
25
|
+
call_all(registrations(hook_name, registries, per_run_hooks), hook_name, context)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def around(hook_name, context, registries:, per_run_hooks: nil, &)
|
|
29
|
+
call_around(registrations(hook_name, registries, per_run_hooks), hook_name, context, &)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def registrations(hook_name, registries, per_run_hooks)
|
|
33
|
+
registry_entries = registries.compact.flat_map { |registry| registry.registrations_for(hook_name) }
|
|
34
|
+
registry_entries + per_run_entries(hook_name, per_run_hooks)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def error_registrations(family, registries, per_run_hooks)
|
|
38
|
+
return [] unless %i[run network_run task].include?(family.to_sym)
|
|
39
|
+
|
|
40
|
+
registrations(:on_error, registries, per_run_hooks)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def call_all(registrations, hook_name, context)
|
|
44
|
+
registrations.each { |registration| call_registration(registration, hook_name, context) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def call_around(registrations, hook_name, context, &block)
|
|
48
|
+
chain = registrations.reverse.inject(block) do |next_link, registration|
|
|
49
|
+
proc { call_registration(registration, hook_name, context, &next_link) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
chain.call
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def call_registration(registration, hook_name, context, &)
|
|
56
|
+
context.with_namespace(registration.namespace) do
|
|
57
|
+
if registration.context && registration.namespace
|
|
58
|
+
context.ext(registration.namespace).merge_defaults(registration.context)
|
|
59
|
+
end
|
|
60
|
+
registration.handler_class.call(hook_name, context, &)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# per_run_hooks is now a single handler class or an array of handler classes.
|
|
65
|
+
def per_run_entries(hook_name, hooks)
|
|
66
|
+
return [] unless hooks
|
|
67
|
+
|
|
68
|
+
hook_sym = hook_name.to_sym
|
|
69
|
+
Array(hooks).filter_map do |handler_class|
|
|
70
|
+
next unless handler_class.singleton_class.public_method_defined?(hook_sym)
|
|
71
|
+
|
|
72
|
+
HookRegistry::Registration.new(handler_class: handler_class)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def set_result(context, family, result)
|
|
77
|
+
case family.to_sym
|
|
78
|
+
when :run
|
|
79
|
+
context.response = result if context.respond_to?(:response=)
|
|
80
|
+
when :network_run, :task
|
|
81
|
+
context.result = result if context.respond_to?(:result=)
|
|
82
|
+
when :llm_generation
|
|
83
|
+
context.generation_response = result if context.respond_to?(:generation_response=)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -115,7 +115,7 @@ module RobotLab
|
|
|
115
115
|
client.transport.stdin.flush
|
|
116
116
|
rescue Errno::EPIPE, IOError => e
|
|
117
117
|
@mutex.synchronize { @clients[io][:queue] = nil }
|
|
118
|
-
raise MCPError
|
|
118
|
+
raise MCPError.new("MCP connection lost: #{e.message}", retryable: true)
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
response = Timeout.timeout(timeout) { queue.pop }
|
|
@@ -127,7 +127,7 @@ module RobotLab
|
|
|
127
127
|
response
|
|
128
128
|
rescue Timeout::Error
|
|
129
129
|
@mutex.synchronize { @clients[io]&.[]= :queue, nil }
|
|
130
|
-
raise MCPError
|
|
130
|
+
raise MCPError.new("MCP server did not respond within #{timeout}s", retryable: true)
|
|
131
131
|
ensure
|
|
132
132
|
@mutex.synchronize { @clients[io]&.[]= :queue, nil }
|
|
133
133
|
end
|