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.
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.
23 24 25 26 27 28 29 30 31 32 |
# File 'lib/shell_session.rb', line 23 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.
20 21 22 |
# File 'lib/shell_session.rb', line 20 def pwd @pwd end |
Class Method Details
.cleanup_all ⇒ Object
Finalize all live sessions. Called automatically via at_exit.
74 75 76 77 78 79 |
# File 'lib/shell_session.rb', line 74 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
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/shell_session.rb', line 83 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.
64 65 66 |
# File 'lib/shell_session.rb', line 64 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.
69 70 71 |
# File 'lib/shell_session.rb', line 69 def unregister(session) @sessions_mutex.synchronize { @sessions.delete(session) } end |
Instance Method Details
#alive? ⇒ Boolean
Returns whether the shell process is still running.
53 54 55 |
# File 'lib/shell_session.rb', line 53 def alive? @mutex.synchronize { @alive } end |
#finalize ⇒ Object
Clean up PTY, FIFO, and child process.
47 48 49 50 |
# File 'lib/shell_session.rb', line 47 def finalize @mutex.synchronize { shutdown } self.class.unregister(self) end |
#run(command) ⇒ Hash
Execute a command in the persistent shell.
39 40 41 42 43 44 |
# File 'lib/shell_session.rb', line 39 def run(command) @mutex.synchronize do return {error: "Shell session is not running"} unless @alive execute_in_pty(command) end end |