Class: ShellSession
- Inherits:
-
Object
- Object
- ShellSession
- Defined in:
- lib/shell_session.rb
Overview
Persistent shell session backed by a PTY with FIFO-based stderr separation. Commands share working directory, environment variables, and shell history within a conversation. Multiple tools share the same session.
Constant Summary collapse
- COMMAND_TIMEOUT =
30- MAX_OUTPUT_BYTES =
100_000
Instance Attribute Summary collapse
-
#pwd ⇒ String?
readonly
Current working directory of the shell process.
Class Method Summary collapse
-
.cleanup_all ⇒ Object
Finalize all live sessions.
-
.cleanup_orphans ⇒ Object
Remove stale FIFO files left by crashed processes.
- .register(session) ⇒ Object private
- .unregister(session) ⇒ Object private
Instance Method Summary collapse
-
#alive? ⇒ Boolean
Whether the shell process is still running.
-
#finalize ⇒ Object
Clean up PTY, FIFO, and child process.
-
#initialize(session_id:) ⇒ ShellSession
constructor
A new instance of ShellSession.
-
#run(command) ⇒ Hash
Execute a command in the persistent shell.
Constructor Details
#initialize(session_id:) ⇒ ShellSession
Returns a new instance of ShellSession.
26 27 28 29 30 31 32 33 34 35 |
# File 'lib/shell_session.rb', line 26 def initialize(session_id:) @session_id = session_id @mutex = Mutex.new @fifo_path = File.join(Dir.tmpdir, "anima-stderr-#{Process.pid}-#{SecureRandom.hex(8)}") @alive = false @pwd = nil self.class.cleanup_orphans start self.class.register(self) end |
Instance Attribute Details
#pwd ⇒ String? (readonly)
Returns current working directory of the shell process.
23 24 25 |
# File 'lib/shell_session.rb', line 23 def pwd @pwd end |
Class Method Details
.cleanup_all ⇒ Object
Finalize all live sessions. Called automatically via at_exit.
77 78 79 80 81 82 |
# File 'lib/shell_session.rb', line 77 def cleanup_all @sessions_mutex.synchronize do @sessions.each { |session| session.send(:shutdown) } @sessions.clear end end |
.cleanup_orphans ⇒ Object
Remove stale FIFO files left by crashed processes. FIFO naming format: anima-stderr-pid-hex
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/shell_session.rb', line 86 def cleanup_orphans Dir.glob(File.join(Dir.tmpdir, "anima-stderr-*")).each do |path| match = File.basename(path).match(/\Aanima-stderr-(\d+)-/) next unless match pid = match[1].to_i next if pid <= 0 begin Process.kill(0, pid) rescue Errno::ESRCH begin File.delete(path) rescue SystemCallError # Best-effort cleanup end rescue Errno::EPERM # Process exists but we can't signal it — leave it end end end |
.register(session) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
67 68 69 |
# File 'lib/shell_session.rb', line 67 def register(session) @sessions_mutex.synchronize { @sessions << session } end |
.unregister(session) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
72 73 74 |
# File 'lib/shell_session.rb', line 72 def unregister(session) @sessions_mutex.synchronize { @sessions.delete(session) } end |
Instance Method Details
#alive? ⇒ Boolean
Returns whether the shell process is still running.
56 57 58 |
# File 'lib/shell_session.rb', line 56 def alive? @mutex.synchronize { @alive } end |
#finalize ⇒ Object
Clean up PTY, FIFO, and child process.
50 51 52 53 |
# File 'lib/shell_session.rb', line 50 def finalize @mutex.synchronize { shutdown } self.class.unregister(self) end |
#run(command) ⇒ Hash
Execute a command in the persistent shell.
42 43 44 45 46 47 |
# File 'lib/shell_session.rb', line 42 def run(command) @mutex.synchronize do return {error: "Shell session is not running"} unless @alive execute_in_pty(command) end end |