Class: Rubino::Tools::ShellInputTool

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

Overview

Feeds input to a background shell’s stdin (registered by ShellTool when run_in_background: true). This is how the agent answers an interactive prompt a running command emits — Y/N confirmations, “select region” menus, apt-style questions — without having to pre-bake the answer at spawn time (‘echo y | cmd`, `-y`, heredoc).

Typical loop: shell(run_in_background: true) → shell_output (see the prompt) → shell_input(run_id:, text: “y”) → shell_output (see the result).

By default a newline is appended (like pressing Enter). Pass ‘enter: false` to send raw bytes without a newline. Pass `eof: true` to close stdin (send EOF) after writing — for commands that read until EOF.

Works for line-oriented prompts. Full-screen TTY programs (vim, REPLs that require a real terminal) are out of scope: the background shell uses a plain pipe, not a pseudo-terminal.

Instance Attribute Summary

Attributes inherited from Base

#cancel_token, #read_tracker, #stream_chunk

Instance Method Summary collapse

Methods inherited from Base

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

Instance Method Details

#call(arguments) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/rubino/tools/shell_input_tool.rb', line 64

def call(arguments)
  run_id = arguments["run_id"] || arguments[:run_id]
  text   = arguments["text"]   || arguments[:text] || ""
  enter  = arguments.fetch("enter", arguments.fetch(:enter, true))
  eof    = arguments["eof"] || arguments[:eof] || false

  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

  unless entry.wait_thr.alive?
    return "Error: [#{run_id}] already exited (exit=#{registry.exit_code(entry)}) — cannot send input"
  end

  written =
    begin
      registry.write_input(entry, text, enter: enter)
    rescue IOError, Errno::EPIPE => e
      return "Error: [#{run_id}] stdin is closed (#{e.message})"
    end

  registry.close_stdin(entry) if eof

  msg = "[#{run_id}] wrote #{written} byte#{"s" unless written == 1} to stdin"
  msg << " (EOF sent)" if eof
  msg << "\nRead the result: shell_output run_id=#{run_id}"
  msg
end

#descriptionObject



26
27
28
29
30
31
32
33
# File 'lib/rubino/tools/shell_input_tool.rb', line 26

def description
  "Send input to a background shell started via `shell` with " \
    "run_in_background: true — answer an interactive prompt (Y/N, menu " \
    "selection, password) of a running command. A newline is appended by " \
    "default (like pressing Enter); pass enter: false for raw bytes, or " \
    "eof: true to close stdin (EOF). Read the prompt and the result with " \
    "`shell_output`."
end

#input_schemaObject



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/rubino/tools/shell_input_tool.rb', line 35

def input_schema
  {
    type: "object",
    properties: {
      run_id: {
        type: "string",
        description: "The run_id returned by `shell` when launched in background"
      },
      text: {
        type: "string",
        description: "The text to write to the process's stdin (e.g. \"y\", \"2\")"
      },
      enter: {
        type: "boolean",
        description: "Append a newline like pressing Enter (default true)"
      },
      eof: {
        type: "boolean",
        description: "Close stdin / send EOF after writing (default false)"
      }
    },
    required: %w[run_id]
  }
end

#nameObject



22
23
24
# File 'lib/rubino/tools/shell_input_tool.rb', line 22

def name
  "shell_input"
end

#risk_levelObject



60
61
62
# File 'lib/rubino/tools/shell_input_tool.rb', line 60

def risk_level
  :medium
end