Class: Woods::Console::ConnectionManager

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/console/connection_manager.rb

Overview

Manages the bridge process connection via Docker exec, direct spawn, or SSH.

Spawns and manages the bridge process, sends JSON-lines requests, receives responses. Implements heartbeat (30s) and reconnect with exponential backoff (max 5 retries).

Examples:

manager = ConnectionManager.new(config: {
  'mode' => 'direct',
  'command' => 'bundle exec rails runner bridge.rb'
})
manager.connect!
response = manager.send_request({ 'id' => 'r1', 'tool' => 'status', 'params' => {} })
manager.disconnect!

Constant Summary collapse

MAX_RETRIES =
5
HEARTBEAT_INTERVAL =
30

Instance Method Summary collapse

Constructor Details

#initialize(config:) ⇒ ConnectionManager

Returns a new instance of ConnectionManager.

Parameters:

  • config (Hash)

    Connection configuration

Options Hash (config:):

  • 'mode' (String)

    Connection mode: ‘docker’, ‘direct’, or ‘ssh’

  • 'command' (String)

    Command to run the bridge

  • 'container' (String)

    Docker container name (docker mode)

  • 'directory' (String)

    Working directory (direct mode)

  • 'host' (String)

    SSH host (ssh mode)

  • 'user' (String)

    SSH user (ssh mode)



40
41
42
43
44
45
46
47
48
49
# File 'lib/woods/console/connection_manager.rb', line 40

def initialize(config:)
  @config = config
  @mode = config['mode'] || 'direct'
  @command = config['command'] || 'bundle exec rails runner lib/woods/console/bridge.rb'
  @stdin = nil
  @stdout = nil
  @wait_thread = nil
  @retries = 0
  @last_heartbeat = nil
end

Instance Method Details

#alive?Boolean

Check if the bridge process is alive.

Returns:

  • (Boolean)


104
105
106
107
108
# File 'lib/woods/console/connection_manager.rb', line 104

def alive?
  return false unless @wait_thread

  @wait_thread.alive?
end

#connect!void

This method returns an undefined value.

Spawn the bridge process.

Raises:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/woods/console/connection_manager.rb', line 55

def connect!
  cmd = build_command
  if @mode == 'direct' && @config['directory']
    Dir.chdir(@config['directory']) do
      @stdin, @stdout, @wait_thread = Open3.popen2(*cmd)
    end
  else
    @stdin, @stdout, @wait_thread = Open3.popen2(*cmd)
  end
  @last_heartbeat = Time.now
  @retries = 0
rescue StandardError => e
  raise ConnectionError, "Failed to connect (#{@mode}): #{e.message}"
end

#disconnect!void

This method returns an undefined value.

Terminate the bridge process.



73
74
75
76
77
78
79
80
# File 'lib/woods/console/connection_manager.rb', line 73

def disconnect!
  @stdin&.close
  @stdout&.close
  @wait_thread&.value
  @stdin = nil
  @stdout = nil
  @wait_thread = nil
end

#heartbeat_needed?Boolean

Check if a heartbeat is needed (30s since last communication).

Returns:

  • (Boolean)


113
114
115
116
117
# File 'lib/woods/console/connection_manager.rb', line 113

def heartbeat_needed?
  return false unless @last_heartbeat

  (Time.now - @last_heartbeat) >= HEARTBEAT_INTERVAL
end

#send_request(request) ⇒ Hash

Send a request to the bridge and read the response.

Parameters:

  • request (Hash)

    JSON-serializable request hash

Returns:

  • (Hash)

    Parsed response hash

Raises:



87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/woods/console/connection_manager.rb', line 87

def send_request(request)
  ensure_connected!
  @stdin.puts(JSON.generate(request))
  @stdin.flush
  line = @stdout.gets
  raise ConnectionError, 'Bridge process closed unexpectedly' unless line

  @last_heartbeat = Time.now
  JSON.parse(line)
rescue IOError, Errno::EPIPE, Errno::ECONNRESET => e
  reconnect_or_raise!(e)
  retry
end