Class: AgentsControl::Agents::ClaudeCode
- Defined in:
- lib/agents_control/agents/claude_code.rb
Overview
Claude Code.
{"decision":"block","reason":"..."} returned to a Stop hook comes
back to the agent as user input, and the agent continues working
with that text — this is what the whole idea of answering from
Telegram rests on.
Constant Summary collapse
- EVENTS =
The events we care about. Others arrive too but are ignored: subscribing to everything is extra overhead on every agent call.
%w[SessionStart SessionEnd PermissionRequest Stop Notification StopFailure].freeze
Class Method Summary collapse
- .binaries ⇒ Object
- .handles?(payload) ⇒ Boolean
- .key ⇒ Object
-
.settings_path ⇒ Object
Claude Code's config directory honors CLAUDE_CONFIG_DIR — just like the agent itself.
Instance Method Summary collapse
- #capabilities ⇒ Object
-
#initialize(settings_path: self.class.settings_path) ⇒ ClaudeCode
constructor
A new instance of ClaudeCode.
-
#install!(url, secret: nil, timeout: 660) ⇒ Object
Merge, not overwrite: settings.json holds other settings we don't own, and they must not be clobbered.
- #installed? ⇒ Boolean
- #to_event(payload) ⇒ Object
-
#to_response(event, reply) ⇒ Object
Reply → the hook response body.
- #uninstall! ⇒ Object
Methods inherited from Base
Constructor Details
#initialize(settings_path: self.class.settings_path) ⇒ ClaudeCode
Returns a new instance of ClaudeCode.
39 40 41 |
# File 'lib/agents_control/agents/claude_code.rb', line 39 def initialize(settings_path: self.class.settings_path) @settings_path = settings_path end |
Class Method Details
.binaries ⇒ Object
32 |
# File 'lib/agents_control/agents/claude_code.rb', line 32 def binaries = %w[claude] |
.handles?(payload) ⇒ Boolean
34 35 36 |
# File 'lib/agents_control/agents/claude_code.rb', line 34 def handles?(payload) payload.is_a?(Hash) && payload.key?("hook_event_name") end |
.key ⇒ Object
30 |
# File 'lib/agents_control/agents/claude_code.rb', line 30 def key = :claude_code |
.settings_path ⇒ Object
Claude Code's config directory honors CLAUDE_CONFIG_DIR — just like the agent itself. This matters both for anyone keeping settings outside the home directory, and for tests: without this override a test run would edit the user's real file.
19 20 21 22 23 |
# File 'lib/agents_control/agents/claude_code.rb', line 19 def self.settings_path base = ENV["CLAUDE_CONFIG_DIR"] || File.("~/.claude") File.join(base, "settings.json") end |
Instance Method Details
#capabilities ⇒ Object
43 |
# File 'lib/agents_control/agents/claude_code.rb', line 43 def capabilities = %i[push blocking_reply] |
#install!(url, secret: nil, timeout: 660) ⇒ Object
Merge, not overwrite: settings.json holds other settings we don't own, and they must not be clobbered. The timeout is set explicitly: Claude Code waits longer than the documented 600 seconds, and how long to wait is our call.
73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/agents_control/agents/claude_code.rb', line 73 def install!(url, secret: nil, timeout: 660) settings = read_settings settings["hooks"] ||= {} EVENTS.each do |name| settings["hooks"][name] = merge_hook(settings["hooks"][name], hook_entry(url, name, secret, timeout)) end write_settings(settings) end |
#installed? ⇒ Boolean
104 105 106 107 108 |
# File 'lib/agents_control/agents/claude_code.rb', line 104 def installed? hooks = read_settings["hooks"] || {} EVENTS.all? { |name| Array(hooks[name]).any? { |group| ours?(group) } } end |
#to_event(payload) ⇒ Object
45 46 47 48 49 50 51 52 53 54 |
# File 'lib/agents_control/agents/claude_code.rb', line 45 def to_event(payload) case payload["hook_event_name"] when "SessionStart" then build(payload, :started) when "SessionEnd" then build(payload, :ended) when "PermissionRequest" then (payload) when "Stop" then stop_event(payload) when "Notification" then notification_event(payload) when "StopFailure" then build(payload, :error, text: payload["error"]) end end |
#to_response(event, reply) ⇒ Object
Reply → the hook response body. The shape depends on the event: granting permission and continuing a dialog are structured differently in the API.
59 60 61 62 63 64 65 |
# File 'lib/agents_control/agents/claude_code.rb', line 59 def to_response(event, reply) case event.raw["hook_event_name"] when "PermissionRequest" then (event, reply) when "Stop" then stop_response(reply) else {} end end |
#uninstall! ⇒ Object
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/agents_control/agents/claude_code.rb', line 85 def uninstall! settings = read_settings hooks = settings["hooks"] || {} EVENTS.each do |name| next unless hooks[name] hooks[name] = hooks[name].filter_map { |group| without_ours(group) } hooks.delete(name) if hooks[name].empty? end # If the key wasn't there before us, it shouldn't be there after # us either: cleaning up after ourselves means not leaving an # empty shell behind. settings.delete("hooks") if hooks.empty? write_settings(settings) end |