Class: Rubino::Tools::Registry
- Inherits:
-
Object
- Object
- Rubino::Tools::Registry
- Defined in:
- lib/rubino/tools/registry.rb
Overview
Singleton registry for all available tools. Tools register themselves and can be looked up by name.
Constant Summary collapse
- TASK_POLL_TOOLS =
The delegate+poll toolset that MUST travel with ‘task` (spawn). The `task` tool’s own description tells the model it can “fetch the result anytime with ‘task_result(<id>)` or stop it with `task_stop(<id>)`”, and `probe` is the read-only check-on-a-child companion. If we hid these behind `any_subagent?` (the #313 token-saving gate) the model would be PROMISED a tool that is absent from its function list — it then concludes “I have no way to poll/verify my subagents” and the delegate->poll->collect flow breaks. So we deliberately trade the ~2k-token saving on these poll tools for correctness: they are exposed whenever `task` itself is (i.e. only gated by `tools.task`, NOT by a live child). The model needs the full delegate+poll toolset present to plan delegation in the first place. (#313)
%w[task_result task_stop probe].freeze
- TASK_DEPENDENT_TOOLS =
Tools that act ON a LIVE child and are NOT named in the ‘task` description — they only make sense once a child SUBAGENT exists, so they stay gated on `any_subagent?`. Before any task is spawned a `steer` with no child just errors (“not your child”), so hiding it costs no promised capability and keeps the common-turn schema lean. `task` itself (spawn) stays always-on. (#313)
%w[steer].freeze
- SHELL_DEPENDENT_TOOLS =
Tools that ONLY make sense once a background SHELL exists this session —the shell-management channels. Before any ‘shell run_in_background:true` they have no handle to act on. `shell` itself stays always-on. (#313)
%w[shell_input shell_output shell_tail shell_kill].freeze
Class Method Summary collapse
-
.all ⇒ Object
Returns all registered tools.
-
.display_label(name) ⇒ Object
The DISPLAY label for a registered tool name — the single resolution point both the live tool card and the approval card route through, so an MCP tool shows its ‘<bare> (mcp:<server>)` source while a built-in renders unchanged.
-
.enabled_tools ⇒ Object
Returns only enabled tools based on configuration AND the active mode (Modes.current).
-
.find(name) ⇒ Object
Finds a tool by name.
-
.instance ⇒ Object
Returns the singleton instance.
-
.register(tool) ⇒ Object
Registers a tool instance.
-
.register_defaults! ⇒ Object
Registers all default tools.
-
.reset! ⇒ Object
Clears all registered tools (useful for testing).
-
.tool_definitions ⇒ Object
Returns tool definitions for LLM registration.
-
.tool_output_compression_enabled_default? ⇒ Boolean
True when compression is enabled in the resolved config, used to gate the retrieve_output tool’s registration.
-
.unregister(name) ⇒ Object
Removes a tool by name (#182): stopping an MCP server must also drop its MCPToolWrapper instances, or the model keeps seeing tools whose client is gone and every call fails.
Class Method Details
.all ⇒ Object
Returns all registered tools
50 51 52 |
# File 'lib/rubino/tools/registry.rb', line 50 def all @tools.values end |
.display_label(name) ⇒ Object
The DISPLAY label for a registered tool name — the single resolution point both the live tool card and the approval card route through, so an MCP tool shows its ‘<bare> (mcp:<server>)` source while a built-in renders unchanged. Detection is driven off the registered object being an MCP wrapper (#mcp?), NEVER off the name’s shape, so a built-in whose name legitimately contains an underscore (read_attachment, shell_output) is never mistaken for a ‘<server>_<tool>` MCP name. Falls back to the bare name when the tool isn’t registered (defensive — the model-facing name is always a safe label).
35 36 37 38 39 40 |
# File 'lib/rubino/tools/registry.rb', line 35 def display_label(name) tool = find(name) return name.to_s unless tool.respond_to?(:mcp?) && tool.mcp? tool.display_name end |
.enabled_tools ⇒ Object
Returns only enabled tools based on configuration AND the active mode (Modes.current). Plan mode pares the registry down to its read-only whitelist so the model literally has no ‘edit`/`shell` definition in the request — it can’t even propose a mutating tool call. Yolo and default leave everything through; their difference is on the approval path, not the registry.
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/rubino/tools/registry.rb', line 60 def enabled_tools config = Rubino.configuration disabled = config.agent_disabled_toolsets @tools.values.reject do |tool| disabled.include?(tool.name) || !tool_enabled_in_config?(tool, config) || !Rubino::Modes.allows_tool?(tool.name) || !aux_dependency_satisfied?(tool, config) || situational_tool_hidden?(tool) end end |
.find(name) ⇒ Object
Finds a tool by name
22 23 24 |
# File 'lib/rubino/tools/registry.rb', line 22 def find(name) @tools[name.to_s] end |
.instance ⇒ Object
Returns the singleton instance
12 13 14 |
# File 'lib/rubino/tools/registry.rb', line 12 def instance self end |
.register(tool) ⇒ Object
Registers a tool instance
17 18 19 |
# File 'lib/rubino/tools/registry.rb', line 17 def register(tool) @tools[tool.name] = tool end |
.register_defaults! ⇒ Object
Registers all default tools
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/rubino/tools/registry.rb', line 87 def register_defaults! register(Rubino::Tools::ReadTool.new) register(Rubino::Tools::SummarizeFileTool.new) register(Rubino::Tools::WriteTool.new) register(Rubino::Tools::EditTool.new) register(Rubino::Tools::MultiEditTool.new) register(Rubino::Tools::GrepTool.new) register(Rubino::Tools::GlobTool.new) register(Rubino::Tools::ShellTool.new) register(Rubino::Tools::ShellOutputTool.new) register(Rubino::Tools::ShellTailTool.new) register(Rubino::Tools::ShellInputTool.new) register(Rubino::Tools::ShellKillTool.new) register(Rubino::Tools::RubyTool.new) register(Rubino::Tools::PatchTool.new) register(Rubino::Tools::WebFetchTool.new) register(Rubino::Tools::WebSearchTool.new) register(Rubino::Tools::QuestionTool.new) register(Rubino::Tools::TodoTool.new) register(Rubino::Tools::MemoryTool.new) register(Rubino::Tools::SessionSearchTool.new) register(Rubino::Tools::AttachFileTool.new) # Gated, on-demand attachment reader (#6): converts a document to # Markdown IN-PROCESS (Rubino::Documents) and frames it as untrusted # data, so attachment bytes enter context only when the model asks. register(Rubino::Tools::ReadAttachmentTool.new) register(Rubino::Tools::VisionTool.new) # Skills tool: loads a skill body (Level 2) and bundled files # (Level 3) on demand. Gated like any tool via `tools.skill`. register(Rubino::Skills::SkillTool.new) # Delegation tool: lets the model spawn an isolated subagent run. # Gated like any other tool (tools.task in config). Subagents now KEEP # it (scoped nesting, S1) — a subagent can spawn its own subagents, # bounded by the depth / fan-out / global caps in BackgroundTasks#reserve. register(Rubino::Tools::TaskTool.new) # Companion poll/stop tools for background subagents (the default # path of `task`). Mirror the shell_output/shell_kill trio. Gated by # the same tools.task key — disabling delegation disables these too. register(Rubino::Tools::TaskResultTool.new) register(Rubino::Tools::TaskStopTool.new) # steer / probe (S2/S3): the MODEL-callable parent->child channels, # registered for ALL agents and AUTHORIZED by ownership at call time # (a node with no children just gets a "not your child" error). NOT on # any strip list — scoping happens inside the tool, not in the registry. register(Rubino::Tools::SteerTool.new) register(Rubino::Tools::ProbeTool.new) # retrieve_output: the ONLY recovery path for compressed tool output. # Registered solely when tool_output_compression is enabled (the # default is OFF), so the shipped registry count is unchanged. When on, # the compression pointer carries an `id=…` and this tool reads the # spilled original back — deliberately NO cat-able path is printed, so # a small model can't shell-re-inflate the output compression shrank. register(Rubino::Tools::RetrieveOutputTool.new) if tool_output_compression_enabled_default? end |
.reset! ⇒ Object
Clears all registered tools (useful for testing)
79 80 81 82 83 84 |
# File 'lib/rubino/tools/registry.rb', line 79 def reset! @tools = {} # Drop the memoized web-capability probe (#411) so a fresh test run # re-evaluates it rather than inheriting a prior process's verdict. @web_backend_available = nil end |
.tool_definitions ⇒ Object
Returns tool definitions for LLM registration
74 75 76 |
# File 'lib/rubino/tools/registry.rb', line 74 def tool_definitions enabled_tools.map(&:to_tool_definition) end |
.tool_output_compression_enabled_default? ⇒ Boolean
True when compression is enabled in the resolved config, used to gate the retrieve_output tool’s registration. Best-effort: any config error falls back to OFF (matching the shipped default), so a broken config never silently adds a tool that wouldn’t otherwise be present.
146 147 148 149 150 |
# File 'lib/rubino/tools/registry.rb', line 146 def tool_output_compression_enabled_default? Rubino.configuration.tool_output_compression_enabled? rescue StandardError false end |
.unregister(name) ⇒ Object
Removes a tool by name (#182): stopping an MCP server must also drop its MCPToolWrapper instances, or the model keeps seeing tools whose client is gone and every call fails.
45 46 47 |
# File 'lib/rubino/tools/registry.rb', line 45 def unregister(name) @tools.delete(name.to_s) end |