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.
Files changed (236) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.loki +60 -0
  4. data/.quality/reek_baseline.txt +43 -0
  5. data/.rubocop.yml +5 -167
  6. data/CHANGELOG.md +54 -0
  7. data/README.md +185 -64
  8. data/Rakefile +28 -12
  9. data/docs/api/core/index.md +41 -14
  10. data/docs/api/core/memory.md +247 -29
  11. data/docs/api/core/network.md +285 -33
  12. data/docs/api/core/result.md +120 -32
  13. data/docs/api/core/robot.md +636 -60
  14. data/docs/api/core/state.md +87 -197
  15. data/docs/api/core/tool.md +165 -20
  16. data/docs/api/errors.md +152 -14
  17. data/docs/api/hooks.md +469 -0
  18. data/docs/api/index.md +83 -7
  19. data/docs/api/mcp/client.md +129 -35
  20. data/docs/api/mcp/index.md +164 -23
  21. data/docs/api/mcp/server.md +27 -3
  22. data/docs/api/mcp/transports.md +94 -22
  23. data/docs/api/messages/index.md +26 -3
  24. data/docs/api/messages/text-message.md +33 -11
  25. data/docs/api/messages/tool-call-message.md +27 -4
  26. data/docs/api/messages/tool-result-message.md +23 -4
  27. data/docs/api/messages/user-message.md +45 -8
  28. data/docs/api/skills.md +519 -0
  29. data/docs/api/streaming/context.md +28 -5
  30. data/docs/api/streaming/index.md +57 -11
  31. data/docs/api/support.md +846 -0
  32. data/docs/architecture/core-concepts.md +118 -30
  33. data/docs/architecture/index.md +86 -11
  34. data/docs/architecture/message-flow.md +66 -29
  35. data/docs/architecture/network-orchestration.md +153 -38
  36. data/docs/architecture/robot-execution.md +173 -91
  37. data/docs/architecture/state-management.md +31 -12
  38. data/docs/concepts.md +176 -21
  39. data/docs/examples/basic-chat.md +72 -19
  40. data/docs/examples/index.md +117 -31
  41. data/docs/examples/mcp-server.md +154 -45
  42. data/docs/examples/multi-robot-network.md +91 -21
  43. data/docs/examples/tool-usage.md +104 -37
  44. data/docs/getting-started/configuration.md +299 -93
  45. data/docs/getting-started/installation.md +53 -41
  46. data/docs/getting-started/quick-start.md +51 -6
  47. data/docs/guides/building-robots.md +296 -50
  48. data/docs/guides/creating-networks.md +220 -31
  49. data/docs/guides/hooks.md +1084 -0
  50. data/docs/guides/index.md +5 -0
  51. data/docs/guides/knowledge.md +37 -6
  52. data/docs/guides/mcp-integration.md +211 -44
  53. data/docs/guides/memory.md +103 -12
  54. data/docs/guides/observability.md +166 -51
  55. data/docs/guides/streaming.md +184 -125
  56. data/docs/guides/using-tools.md +300 -11
  57. data/docs/index.md +37 -5
  58. data/examples/01_simple_robot.rb +2 -2
  59. data/examples/02_tools.rb +14 -4
  60. data/examples/03_network.rb +23 -9
  61. data/examples/04_mcp.rb +11 -4
  62. data/examples/05_streaming.rb +8 -5
  63. data/examples/06_prompt_templates.rb +13 -9
  64. data/examples/07_network_memory.rb +5 -5
  65. data/examples/08_llm_config.rb +20 -15
  66. data/examples/09_chaining.rb +4 -4
  67. data/examples/11_network_introspection.rb +4 -4
  68. data/examples/12_message_bus.rb +2 -2
  69. data/examples/13_spawn.rb +2 -2
  70. data/examples/14_rusty_circuit/README.md +1 -0
  71. data/examples/14_rusty_circuit/comic.rb +7 -3
  72. data/examples/14_rusty_circuit/display.rb +14 -0
  73. data/examples/14_rusty_circuit/heckler.rb +8 -6
  74. data/examples/14_rusty_circuit/open_mic.rb +17 -6
  75. data/examples/14_rusty_circuit/scout.rb +17 -10
  76. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
  77. data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
  78. data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
  79. data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
  80. data/examples/16_writers_room/writer.rb +22 -22
  81. data/examples/16_writers_room/writers_room.rb +2 -0
  82. data/examples/17_skills.rb +14 -13
  83. data/examples/18_rails/README.md +20 -1
  84. data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
  85. data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
  86. data/examples/18_rails/app/robots/chat_robot.rb +11 -0
  87. data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
  88. data/examples/19_token_tracking.rb +25 -9
  89. data/examples/20_circuit_breaker.rb +10 -7
  90. data/examples/21_learning_loop.rb +42 -16
  91. data/examples/22_context_compression.rb +23 -23
  92. data/examples/23_convergence.rb +24 -17
  93. data/examples/24_structured_delegation.rb +13 -8
  94. data/examples/25_history_search.rb +12 -8
  95. data/examples/27_incident_response/incident_response.rb +31 -13
  96. data/examples/28_mcp_discovery.rb +17 -13
  97. data/examples/29_ractor_tools.rb +4 -2
  98. data/examples/30_ractor_network.rb +22 -17
  99. data/examples/31_launch_assessment.rb +20 -9
  100. data/examples/32_newsletter_reader.rb +7 -2
  101. data/examples/33_stock_predictor.rb +34 -13
  102. data/examples/34_agentskills.rb +7 -3
  103. data/examples/35_hooks.rb +266 -0
  104. data/examples/README.md +203 -42
  105. data/examples/common.rb +79 -7
  106. data/examples/xyzzy.rb +97 -0
  107. data/lib/robot_lab/agent_skill.rb +5 -4
  108. data/lib/robot_lab/budget/ledger.rb +98 -0
  109. data/lib/robot_lab/capabilities.rb +84 -0
  110. data/lib/robot_lab/config/defaults.yml +10 -0
  111. data/lib/robot_lab/config.rb +14 -9
  112. data/lib/robot_lab/error.rb +34 -4
  113. data/lib/robot_lab/errors.rb +45 -0
  114. data/lib/robot_lab/hook.rb +79 -0
  115. data/lib/robot_lab/hook_context.rb +194 -0
  116. data/lib/robot_lab/hook_registry.rb +55 -0
  117. data/lib/robot_lab/hooks.rb +87 -0
  118. data/lib/robot_lab/mcp/connection_poller.rb +2 -2
  119. data/lib/robot_lab/names.rb +402 -0
  120. data/lib/robot_lab/narrator.rb +87 -0
  121. data/lib/robot_lab/network.rb +77 -20
  122. data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
  123. data/lib/robot_lab/robot/budget.rb +89 -0
  124. data/lib/robot_lab/robot/bus_messaging.rb +90 -18
  125. data/lib/robot_lab/robot/hooking.rb +56 -0
  126. data/lib/robot_lab/robot/mcp_management.rb +8 -2
  127. data/lib/robot_lab/robot/template_rendering.rb +18 -5
  128. data/lib/robot_lab/robot.rb +233 -89
  129. data/lib/robot_lab/run_config.rb +24 -5
  130. data/lib/robot_lab/runnable.rb +51 -0
  131. data/lib/robot_lab/sandbox/null.rb +13 -0
  132. data/lib/robot_lab/sandbox/seatbelt.rb +104 -0
  133. data/lib/robot_lab/sandbox.rb +52 -0
  134. data/lib/robot_lab/script_tool.rb +60 -2
  135. data/lib/robot_lab/task.rb +26 -20
  136. data/lib/robot_lab/tool.rb +52 -11
  137. data/lib/robot_lab/version.rb +1 -1
  138. data/lib/robot_lab.rb +70 -15
  139. data/mkdocs.yml +7 -1
  140. metadata +41 -99
  141. data/examples/temp.md +0 -51
  142. data/site/404.html +0 -2300
  143. data/site/api/core/index.html +0 -2706
  144. data/site/api/core/memory/index.html +0 -3793
  145. data/site/api/core/network/index.html +0 -3500
  146. data/site/api/core/robot/index.html +0 -4566
  147. data/site/api/core/state/index.html +0 -3390
  148. data/site/api/core/tool/index.html +0 -3843
  149. data/site/api/index.html +0 -2635
  150. data/site/api/mcp/client/index.html +0 -3435
  151. data/site/api/mcp/index.html +0 -2783
  152. data/site/api/mcp/server/index.html +0 -3252
  153. data/site/api/mcp/transports/index.html +0 -3352
  154. data/site/api/messages/index.html +0 -2641
  155. data/site/api/messages/text-message/index.html +0 -3087
  156. data/site/api/messages/tool-call-message/index.html +0 -3159
  157. data/site/api/messages/tool-result-message/index.html +0 -3252
  158. data/site/api/messages/user-message/index.html +0 -3212
  159. data/site/api/streaming/context/index.html +0 -3282
  160. data/site/api/streaming/events/index.html +0 -3347
  161. data/site/api/streaming/index.html +0 -2738
  162. data/site/architecture/core-concepts/index.html +0 -3757
  163. data/site/architecture/index.html +0 -2797
  164. data/site/architecture/message-flow/index.html +0 -3238
  165. data/site/architecture/network-orchestration/index.html +0 -3433
  166. data/site/architecture/robot-execution/index.html +0 -3140
  167. data/site/architecture/state-management/index.html +0 -3498
  168. data/site/assets/css/custom.css +0 -56
  169. data/site/assets/images/favicon.png +0 -0
  170. data/site/assets/images/robot_lab.jpg +0 -0
  171. data/site/assets/javascripts/bundle.79ae519e.min.js +0 -16
  172. data/site/assets/javascripts/bundle.79ae519e.min.js.map +0 -7
  173. data/site/assets/javascripts/lunr/min/lunr.ar.min.js +0 -1
  174. data/site/assets/javascripts/lunr/min/lunr.da.min.js +0 -18
  175. data/site/assets/javascripts/lunr/min/lunr.de.min.js +0 -18
  176. data/site/assets/javascripts/lunr/min/lunr.du.min.js +0 -18
  177. data/site/assets/javascripts/lunr/min/lunr.el.min.js +0 -1
  178. data/site/assets/javascripts/lunr/min/lunr.es.min.js +0 -18
  179. data/site/assets/javascripts/lunr/min/lunr.fi.min.js +0 -18
  180. data/site/assets/javascripts/lunr/min/lunr.fr.min.js +0 -18
  181. data/site/assets/javascripts/lunr/min/lunr.he.min.js +0 -1
  182. data/site/assets/javascripts/lunr/min/lunr.hi.min.js +0 -1
  183. data/site/assets/javascripts/lunr/min/lunr.hu.min.js +0 -18
  184. data/site/assets/javascripts/lunr/min/lunr.hy.min.js +0 -1
  185. data/site/assets/javascripts/lunr/min/lunr.it.min.js +0 -18
  186. data/site/assets/javascripts/lunr/min/lunr.ja.min.js +0 -1
  187. data/site/assets/javascripts/lunr/min/lunr.jp.min.js +0 -1
  188. data/site/assets/javascripts/lunr/min/lunr.kn.min.js +0 -1
  189. data/site/assets/javascripts/lunr/min/lunr.ko.min.js +0 -1
  190. data/site/assets/javascripts/lunr/min/lunr.multi.min.js +0 -1
  191. data/site/assets/javascripts/lunr/min/lunr.nl.min.js +0 -18
  192. data/site/assets/javascripts/lunr/min/lunr.no.min.js +0 -18
  193. data/site/assets/javascripts/lunr/min/lunr.pt.min.js +0 -18
  194. data/site/assets/javascripts/lunr/min/lunr.ro.min.js +0 -18
  195. data/site/assets/javascripts/lunr/min/lunr.ru.min.js +0 -18
  196. data/site/assets/javascripts/lunr/min/lunr.sa.min.js +0 -1
  197. data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -1
  198. data/site/assets/javascripts/lunr/min/lunr.sv.min.js +0 -18
  199. data/site/assets/javascripts/lunr/min/lunr.ta.min.js +0 -1
  200. data/site/assets/javascripts/lunr/min/lunr.te.min.js +0 -1
  201. data/site/assets/javascripts/lunr/min/lunr.th.min.js +0 -1
  202. data/site/assets/javascripts/lunr/min/lunr.tr.min.js +0 -18
  203. data/site/assets/javascripts/lunr/min/lunr.vi.min.js +0 -1
  204. data/site/assets/javascripts/lunr/min/lunr.zh.min.js +0 -1
  205. data/site/assets/javascripts/lunr/tinyseg.js +0 -206
  206. data/site/assets/javascripts/lunr/wordcut.js +0 -6708
  207. data/site/assets/javascripts/workers/search.2c215733.min.js +0 -42
  208. data/site/assets/javascripts/workers/search.2c215733.min.js.map +0 -7
  209. data/site/assets/stylesheets/main.484c7ddc.min.css +0 -1
  210. data/site/assets/stylesheets/main.484c7ddc.min.css.map +0 -1
  211. data/site/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
  212. data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -1
  213. data/site/concepts/index.html +0 -3455
  214. data/site/examples/basic-chat/index.html +0 -2880
  215. data/site/examples/index.html +0 -2907
  216. data/site/examples/mcp-server/index.html +0 -3018
  217. data/site/examples/multi-robot-network/index.html +0 -3131
  218. data/site/examples/rails-application/index.html +0 -3329
  219. data/site/examples/tool-usage/index.html +0 -3085
  220. data/site/getting-started/configuration/index.html +0 -3745
  221. data/site/getting-started/index.html +0 -2572
  222. data/site/getting-started/installation/index.html +0 -2981
  223. data/site/getting-started/quick-start/index.html +0 -2942
  224. data/site/guides/building-robots/index.html +0 -4290
  225. data/site/guides/creating-networks/index.html +0 -3858
  226. data/site/guides/index.html +0 -2586
  227. data/site/guides/mcp-integration/index.html +0 -3581
  228. data/site/guides/memory/index.html +0 -3586
  229. data/site/guides/rails-integration/index.html +0 -4019
  230. data/site/guides/streaming/index.html +0 -3157
  231. data/site/guides/using-tools/index.html +0 -3802
  232. data/site/index.html +0 -2671
  233. data/site/search/search_index.json +0 -1
  234. data/site/sitemap.xml +0 -183
  235. data/site/sitemap.xml.gz +0 -0
  236. 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, "MCP connection lost: #{e.message}"
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, "MCP server did not respond within #{timeout}s"
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