Class: Rubino::Tools::ShellOutputTool

Inherits:
Base
  • Object
show all
Defined in:
lib/rubino/tools/shell_output_tool.rb

Overview

Reads stdout/stderr accumulated by a background shell (registered by ShellTool when run_in_background: true).

By default returns only the bytes produced since the last call —repeated polling shows incremental progress like ‘tail -F`. Pass `mode: “all”` for the full buffer (bounded by ShellRegistry::RING_BYTES).

Instance Attribute Summary

Attributes inherited from Base

#cancel_token, #read_tracker, #stream_chunk, #stream_kind

Instance Method Summary collapse

Methods inherited from Base

#cancellation_requested?, #config_key, #display_name, #emit_chunk, #mcp?, #risky?, #to_tool_definition, workspace_root, workspace_roots

Instance Method Details

#call(arguments) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/rubino/tools/shell_output_tool.rb', line 44

def call(arguments)
  run_id = arguments["run_id"] || arguments[:run_id]
  mode   = (arguments["mode"]  || arguments[:mode] || "new").to_s

  return "Error: run_id is required" if run_id.nil? || run_id.to_s.empty?

  registry = ShellRegistry.instance
  entry    = registry.find(run_id)
  return "Error: no background shell with run_id=#{run_id}" unless entry

  body = mode == "all" ? registry.read_all(entry) : registry.read_new(entry)
  # Redact credential values from background shell output too — same seam
  # as foreground shell (Hermes terminal_tool redacts all command output).
  body = Security::Redactor.redact_sensitive_text(body)
  status = registry.status(entry)
  exit_code = registry.exit_code(entry)

  header = "[#{run_id}] status=#{status}"
  header << " exit=#{exit_code}" if exit_code
  header << " (#{body.bytesize} bytes #{mode == "all" ? "total" : "new"})"

  # Retire (don't drop) a finished shell so its captured output stays
  # retrievable on a later turn and the shell-management tools stay
  # exposed — a SHORT bg command finishes before the next turn (#78).
  registry.retire(run_id) unless status == :running

  if body.empty?
    status == :running ? "#{header}\n(no new output)" : header
  else
    "#{header}\n#{body}"
  end
end

#descriptionObject



16
17
18
19
20
# File 'lib/rubino/tools/shell_output_tool.rb', line 16

def description
  "Read output from a background shell started via `shell` with " \
    "run_in_background: true. By default returns only new bytes since " \
    "the previous read. Pass mode: 'all' for the full buffered output."
end

#input_schemaObject



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/rubino/tools/shell_output_tool.rb', line 22

def input_schema
  {
    type: "object",
    properties: {
      run_id: {
        type: "string",
        description: "The run_id returned by `shell` when launched in background"
      },
      mode: {
        type: "string",
        enum: %w[new all],
        description: "'new' (default) = bytes since last read; 'all' = full buffer"
      }
    },
    required: %w[run_id]
  }
end

#nameObject



12
13
14
# File 'lib/rubino/tools/shell_output_tool.rb', line 12

def name
  "shell_output"
end

#risk_levelObject



40
41
42
# File 'lib/rubino/tools/shell_output_tool.rb', line 40

def risk_level
  :low
end