Class: Rubino::Tools::Registry

Inherits:
Object
  • Object
show all
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

Class Method Details

.allObject

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_toolsObject

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

.instanceObject

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_definitionsObject

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.

Returns:

  • (Boolean)


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