Class: Binocs::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/binocs/agent.rb

Constant Summary collapse

STATUSES =
%i[pending running completed failed stopped].freeze
TOOLS =
%i[claude_code opencode].freeze
@@agents =
[]
@@mutex =
Mutex.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs = {}) ⇒ Agent

Returns a new instance of Agent.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/binocs/agent.rb', line 18

def initialize(attrs = {})
  @id = attrs[:id] || SecureRandom.uuid[0, 8]
  @status = attrs[:status] || :pending
  @tool = attrs[:tool] || Binocs.configuration.agent_tool
  @worktree_path = attrs[:worktree_path]
  @pid = attrs[:pid]
  @request_id = attrs[:request_id]
  @prompt = attrs[:prompt]
  @created_at = attrs[:created_at] || Time.now
  @output_file = attrs[:output_file]
  @branch_name = attrs[:branch_name]
  @request_context = attrs[:request_context]
  @exit_code = nil
end

Instance Attribute Details

#branch_nameObject

Returns the value of attribute branch_name.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def branch_name
  @branch_name
end

#created_atObject

Returns the value of attribute created_at.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def created_at
  @created_at
end

#exit_codeObject

Returns the value of attribute exit_code.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def exit_code
  @exit_code
end

#idObject

Returns the value of attribute id.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def id
  @id
end

#output_fileObject

Returns the value of attribute output_file.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def output_file
  @output_file
end

#pidObject

Returns the value of attribute pid.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def pid
  @pid
end

#promptObject

Returns the value of attribute prompt.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def prompt
  @prompt
end

#request_contextObject

Returns the value of attribute request_context.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def request_context
  @request_context
end

#request_idObject

Returns the value of attribute request_id.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def request_id
  @request_id
end

#statusObject

Returns the value of attribute status.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def status
  @status
end

#toolObject

Returns the value of attribute tool.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def tool
  @tool
end

#worktree_pathObject

Returns the value of attribute worktree_path.



11
12
13
# File 'lib/binocs/agent.rb', line 11

def worktree_path
  @worktree_path
end

Class Method Details

.add(agent) ⇒ Object



125
126
127
128
# File 'lib/binocs/agent.rb', line 125

def add(agent)
  @@mutex.synchronize { @@agents << agent }
  agent
end

.allObject



113
114
115
# File 'lib/binocs/agent.rb', line 113

def all
  @@mutex.synchronize { @@agents.dup }
end

.clear_completedObject



146
147
148
149
150
# File 'lib/binocs/agent.rb', line 146

def clear_completed
  @@mutex.synchronize do
    @@agents.reject! { |a| a.completed? || a.failed? || a.stopped? }
  end
end

.countObject



138
139
140
# File 'lib/binocs/agent.rb', line 138

def count
  @@mutex.synchronize { @@agents.length }
end

.find(id) ⇒ Object



121
122
123
# File 'lib/binocs/agent.rb', line 121

def find(id)
  @@mutex.synchronize { @@agents.find { |a| a.id == id } }
end

.for_request(request_id) ⇒ Object



134
135
136
# File 'lib/binocs/agent.rb', line 134

def for_request(request_id)
  all.select { |a| a.request_id == request_id }
end

.remove(agent) ⇒ Object



130
131
132
# File 'lib/binocs/agent.rb', line 130

def remove(agent)
  @@mutex.synchronize { @@agents.delete(agent) }
end

.runningObject



117
118
119
# File 'lib/binocs/agent.rb', line 117

def running
  all.select(&:running?)
end

.running_countObject



142
143
144
# File 'lib/binocs/agent.rb', line 142

def running_count
  running.length
end

Instance Method Details

#completed?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/binocs/agent.rb', line 37

def completed?
  @status == :completed
end

#durationObject



84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/binocs/agent.rb', line 84

def duration
  return nil unless @created_at

  elapsed = Time.now - @created_at
  if elapsed < 60
    "#{elapsed.to_i}s"
  elsif elapsed < 3600
    "#{(elapsed / 60).to_i}m"
  else
    "#{(elapsed / 3600).to_i}h #{((elapsed % 3600) / 60).to_i}m"
  end
end

#failed?Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/binocs/agent.rb', line 41

def failed?
  @status == :failed
end

#outputObject



72
73
74
75
76
# File 'lib/binocs/agent.rb', line 72

def output
  return '' unless @output_file && File.exist?(@output_file)

  File.read(@output_file)
end

#output_tail(lines = 50) ⇒ Object



78
79
80
81
82
# File 'lib/binocs/agent.rb', line 78

def output_tail(lines = 50)
  return '' unless @output_file && File.exist?(@output_file)

  `tail -n #{lines} #{@output_file.shellescape}`.strip
end

#process_alive?Boolean

Returns:

  • (Boolean)


49
50
51
52
53
54
55
56
# File 'lib/binocs/agent.rb', line 49

def process_alive?
  return false unless @pid

  Process.kill(0, @pid)
  true
rescue Errno::ESRCH, Errno::EPERM
  false
end

#running?Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/binocs/agent.rb', line 33

def running?
  @status == :running && @pid && process_alive?
end

#short_prompt(max_length = 50) ⇒ Object



105
106
107
108
109
# File 'lib/binocs/agent.rb', line 105

def short_prompt(max_length = 50)
  return '' unless @prompt

  @prompt.length > max_length ? "#{@prompt[0, max_length - 3]}..." : @prompt
end

#stop!Object



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/binocs/agent.rb', line 58

def stop!
  return unless running?

  begin
    Process.kill('TERM', @pid)
    sleep 0.5
    Process.kill('KILL', @pid) if process_alive?
  rescue Errno::ESRCH, Errno::EPERM
    # Process already dead
  end

  @status = :stopped
end

#stopped?Boolean

Returns:

  • (Boolean)


45
46
47
# File 'lib/binocs/agent.rb', line 45

def stopped?
  @status == :stopped
end

#tool_commandObject



97
98
99
100
101
102
103
# File 'lib/binocs/agent.rb', line 97

def tool_command
  case @tool
  when :claude_code then 'claude'
  when :opencode then 'opencode'
  else 'claude'
  end
end