Class: AgentsControl::ProcessProbe
- Inherits:
-
Object
- Object
- AgentsControl::ProcessProbe
- Defined in:
- lib/agents_control/process_probe.rb
Overview
Who's actually running in each tab.
A tab's title can't be trusted: Claude Code sets it via an OSC sequence, and the title stays up after the process exits.
One ps -A call for the whole process tree instead of a separate
ps -t per tab.
Defined Under Namespace
Classes: Process
Constant Summary collapse
- NO_TTY =
Processes with no controlling terminal. This is what an agent session launched from the VS Code extension looks like: it has no tab at all.
"??"- AGENT_BINARIES =
An agent is identified by the basename of its executable: in a terminal that's
claude; in VS Code it's a long path into the extension's directory. { "claude" => :claude_code, "codex" => :codex }.freeze
Instance Method Summary collapse
-
#agent_in(tty) ⇒ Object
The live agent in a tab — or nil, whatever the title claims.
-
#cwds(pids) ⇒ Object
Working directories of processes: { pid => path }.
-
#for_tty(tty) ⇒ Object
A tab's processes, in launch order.
-
#foreground(tty) ⇒ Object
What's currently in the foreground of a tab.
-
#initialize(executor: Executor.new) ⇒ ProcessProbe
constructor
A new instance of ProcessProbe.
- #processes ⇒ Object
-
#refresh ⇒ Object
Re-reads the process tree.
-
#running?(name) ⇒ Boolean
Whether an app with this name is running.
-
#terminalless_agents ⇒ Object
Agents with no tab: VS Code sessions and anything else terminalless.
Constructor Details
#initialize(executor: Executor.new) ⇒ ProcessProbe
Returns a new instance of ProcessProbe.
35 36 37 |
# File 'lib/agents_control/process_probe.rb', line 35 def initialize(executor: Executor.new) @executor = executor end |
Instance Method Details
#agent_in(tty) ⇒ Object
The live agent in a tab — or nil, whatever the title claims.
63 64 65 |
# File 'lib/agents_control/process_probe.rb', line 63 def agent_in(tty) for_tty(tty).find(&:agent?) end |
#cwds(pids) ⇒ Object
Working directories of processes: { pid => path }.
For a tabless session this is the only way to give it a human name — there's no title and no cwd from a terminal. One lsof call for all pids at once (0.027s), not one call per pid.
87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/agents_control/process_probe.rb', line 87 def cwds(pids) pids = Array(pids).compact.uniq return {} if pids.empty? lsof = Which.find("lsof") return {} unless lsof result = @executor.run(lsof, "-a", "-d", "cwd", "-p", pids.join(","), "-Fn") # We may lack permission for someone else's processes — that's fine, return what we have. result.success? ? parse_lsof(result.stdout) : {} end |
#for_tty(tty) ⇒ Object
A tab's processes, in launch order.
52 53 54 |
# File 'lib/agents_control/process_probe.rb', line 52 def for_tty(tty) by_tty.fetch(normalize(tty), []) end |
#foreground(tty) ⇒ Object
What's currently in the foreground of a tab. This is what the user sees.
57 58 59 60 |
# File 'lib/agents_control/process_probe.rb', line 57 def foreground(tty) candidates = for_tty(tty) candidates.reverse.find(&:foreground?) || candidates.last end |
#processes ⇒ Object
47 48 49 |
# File 'lib/agents_control/process_probe.rb', line 47 def processes @processes || refresh.processes end |
#refresh ⇒ Object
Re-reads the process tree. The result is cached on the object — one probe per registry pass.
41 42 43 44 45 |
# File 'lib/agents_control/process_probe.rb', line 41 def refresh result = @executor.run(Which.find!("ps"), "-A", "-o", "tty=,pid=,stat=,comm=") @processes = result.success? ? parse(result.stdout) : [] self end |
#running?(name) ⇒ Boolean
Whether an app with this name is running.
Deliberately not via pgrep: on macOS pgrep -x matches against the
full path rather than the name, and in a restricted environment
pgrep can miss processes that ps sees fine. We already have the
list — this is a filter over existing data, not another call.
73 74 75 |
# File 'lib/agents_control/process_probe.rb', line 73 def running?(name) processes.any? { |process| process.name == name } end |
#terminalless_agents ⇒ Object
Agents with no tab: VS Code sessions and anything else terminalless.
78 79 80 |
# File 'lib/agents_control/process_probe.rb', line 78 def terminalless_agents processes.select { |process| process.agent? && !process.terminal? } end |