Class: RubynCode::IDE::Adapters::ToolOutput
- Inherits:
-
Object
- Object
- RubynCode::IDE::Adapters::ToolOutput
- Defined in:
- lib/rubyn_code/ide/adapters/tool_output.rb
Overview
Wraps every tool invocation in IDE mode. Emits JSON-RPC notifications that the VS Code extension consumes, precomputes file edits so the editor can render a diff before any write touches disk, and gates mutating operations behind acceptance/approval from the IDE client.
Gating policy depends on the permission mode:
:default → approve every mutating tool + every file edit
:accept_edits → auto-approve file edits, prompt for bash/other
:plan_only → read-only, block all writes
:auto → auto-approve everything except deny-listed
:dont_ask → auto-deny all non-read-only tools
:bypass → no checks (legacy yolo)
In all modes the adapter emits notifications so the UI reflects what’s happening.
Constant Summary collapse
- WAIT_POLL_INTERVAL =
Wake up periodically to check for thread interrupts (cancel). No auto-deny — waits indefinitely until the user decides.
5- READ_ONLY_TOOLS =
seconds
%w[ read_file glob grep git_status git_diff git_log git_commit memory_search web_fetch web_search run_specs ].freeze
- FILE_WRITE_TOOLS =
%w[write_file edit_file].freeze
- VALID_PERMISSION_MODES =
%i[default accept_edits plan_only auto dont_ask bypass].freeze
Instance Attribute Summary collapse
-
#permission_mode ⇒ Object
Returns the value of attribute permission_mode.
Instance Method Summary collapse
-
#initialize(server, permission_mode: :default, yolo: false, hook_runner: nil) ⇒ ToolOutput
constructor
A new instance of ToolOutput.
-
#resolve_approval(request_id, approved) ⇒ Object
Called by ApproveToolUseHandler when the IDE client responds.
-
#resolve_edit(edit_id, accepted) ⇒ Object
Called by AcceptEditHandler when the IDE client responds.
-
#wrap_execution(tool_name, args, &block) ⇒ Object
Main entry point.
Constructor Details
#initialize(server, permission_mode: :default, yolo: false, hook_runner: nil) ⇒ ToolOutput
Returns a new instance of ToolOutput.
41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/rubyn_code/ide/adapters/tool_output.rb', line 41 def initialize(server, permission_mode: :default, yolo: false, hook_runner: nil) @server = server @permission_mode = yolo ? :bypass : .to_sym @hook_runner = hook_runner @mutex = Mutex.new # { request_id => { cv: ConditionVariable, approved: nil|true|false } } @pending_approvals = {} # { edit_id => { cv: ConditionVariable, accepted: nil|true|false } } @pending_edits = {} end |
Instance Attribute Details
#permission_mode ⇒ Object
Returns the value of attribute permission_mode.
39 40 41 |
# File 'lib/rubyn_code/ide/adapters/tool_output.rb', line 39 def @permission_mode end |
Instance Method Details
#resolve_approval(request_id, approved) ⇒ Object
Called by ApproveToolUseHandler when the IDE client responds.
98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rubyn_code/ide/adapters/tool_output.rb', line 98 def resolve_approval(request_id, approved) @mutex.synchronize do pending = @pending_approvals[request_id] return false unless pending pending[:approved] = approved pending[:cv].signal true end end |
#resolve_edit(edit_id, accepted) ⇒ Object
Called by AcceptEditHandler when the IDE client responds.
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/rubyn_code/ide/adapters/tool_output.rb', line 110 def resolve_edit(edit_id, accepted) @mutex.synchronize do pending = @pending_edits[edit_id] return false unless pending pending[:accepted] = accepted pending[:cv].signal true end end |
#wrap_execution(tool_name, args, &block) ⇒ Object
Main entry point. Wraps a tool call, emitting IDE notifications and gating execution behind acceptance when required.
adapter.wrap_execution("write_file", { path: "foo.rb", content: "..." }) do
executor.execute("write_file", params)
end
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/rubyn_code/ide/adapters/tool_output.rb', line 61 def wrap_execution(tool_name, args, &block) request_id = generate_id args = stringify_keys(args) return execute_and_notify(request_id, tool_name, args, &block) if read_only?(tool_name) # Non-read-only tools: gating depends on permission mode case @permission_mode when :bypass, :auto # Auto-approve everything — run without waiting execute_and_notify(request_id, tool_name, args, &block) when :plan_only emit_tool_use(request_id, tool_name, args, requires_approval: false) msg = 'Plan mode: write operations blocked' emit_tool_result(request_id, tool_name, msg, success: false, args: args) raise RubynCode::UserDeniedError, msg when :dont_ask emit_tool_use(request_id, tool_name, args, requires_approval: false) msg = 'Auto-denied: permission mode is dont_ask' emit_tool_result(request_id, tool_name, msg, success: false, args: args) raise RubynCode::UserDeniedError, msg when :accept_edits if file_write?(tool_name) execute_with_edit_gate(request_id, tool_name, args, &block) else execute_with_approval(request_id, tool_name, args, &block) end else # :default if file_write?(tool_name) execute_with_edit_gate(request_id, tool_name, args, &block) else execute_with_approval(request_id, tool_name, args, &block) end end end |