Skip to content

Commit f93d81b

Browse files
olimorrisgeorgeharker
authored andcommitted
feat(tools): better approvals (olimorris#2569)
1 parent 0d04f9a commit f93d81b

File tree

3 files changed

+73
-101
lines changed

3 files changed

+73
-101
lines changed

doc/codecompanion.txt

Lines changed: 54 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*codecompanion.txt* For NVIM v0.11 Last change: 2025 December 19
1+
*codecompanion.txt* For NVIM v0.11 Last change: 2025 December 22
22

33
==============================================================================
44
Table of Contents *codecompanion-table-of-contents*
@@ -997,10 +997,9 @@ The configuration for both types of adapters is exactly the same, however they
997997
sit within their own tables (`adapters.http.*` and `adapters.acp.*`) and have
998998
different options available. HTTP adapters use `models` to allow users to
999999
select the specific LLM they’d like to interact with. ACP adapters use
1000-
`commands` to allow users to customize their interaction with agents
1001-
(e.g. enabling `yolo` mode). As there is a lot of shared functionality between
1002-
the two adapters, it is recommend that you read this page alongside the ACP
1003-
one.
1000+
`commands` to allow users to customize their interaction with agents (e.g.�
1001+
enabling `yolo` mode). As there is a lot of shared functionality between the
1002+
two adapters, it is recommend that you read this page alongside the ACP one.
10041003

10051004

10061005
CHANGING AN ADAPTER ~
@@ -1054,7 +1053,7 @@ the adapter’s URL, headers, parameters and other fields at runtime.
10541053
<https://github.com/olimorris/codecompanion.nvim/discussions/601>
10551054
Supported `env` value types: - **Plain environment variable name (string)**: if
10561055
the value is the name of an environment variable that has already been set
1057-
(e.g. `"HOME"` or `"GEMINI_API_KEY"`), the plugin will read the value. -
1056+
(e.g.`"HOME"` or `"GEMINI_API_KEY"`), the plugin will read the value. -
10581057
**Command (string prefixed with cmd:)**: any value that starts with `cmd:` will
10591058
be executed via the shell. Example: `"cmd:op read
10601059
op://personal/Gemini/credential --no-newline"`. - **Function**: you can provide
@@ -1540,72 +1539,29 @@ dependency and want to refresh the tool availability in the chat buffer.
15401539

15411540
APPROVALS
15421541

1543-
Some tools, such as |codecompanion-usage-chat-buffer-tools.html-cmd-runner|,
1544-
require the user to approve any commands before they’re executed. This can be
1545-
changed by altering the config for each tool:
1542+
CodeCompanion allows you to apply safety mechanisms to its built-in tools prior
1543+
to execution.
15461544

1547-
>lua
1548-
require("codecompanion").setup({
1549-
interactions = {
1550-
chat = {
1551-
tools = {
1552-
["cmd_runner"] = {
1553-
opts = {
1554-
require_approval_before = false,
1555-
},
1556-
},
1557-
}
1558-
}
1559-
}
1560-
})
1561-
<
1562-
1563-
You can also force any tool to require your approval by adding in
1564-
`opts.require_approval_before = true`.
15651545

15661546

15671547
AUTO SUBMIT (RECURSION)
15681548

15691549
When a tool executes, it can be useful to automatically send its output back to
15701550
the LLM. This can be achieved by the following options in your configuration:
15711551

1572-
>lua
1573-
require("codecompanion").setup({
1574-
interactions = {
1575-
chat = {
1576-
tools = {
1577-
opts = {
1578-
auto_submit_errors = true, -- Send any errors to the LLM automatically?
1579-
auto_submit_success = true, -- Send any successful output to the LLM automatically?
1580-
},
1581-
}
1582-
}
1583-
}
1584-
})
1585-
<
1552+
`lua {6-7} require("codecompanion").setup({ interactions = { chat = { tools = {
1553+
opts = { auto_submit_errors = true, -- Send any errors to the LLM
1554+
automatically? auto_submit_success = true, -- Send any successful output to the
1555+
LLM automatically? }, } } } })`
15861556

15871557

15881558
DEFAULT TOOLS
15891559

15901560
You can configure the plugin to automatically add tools and tool groups to new
15911561
chat buffers:
15921562

1593-
>lua
1594-
require("codecompanion").setup({
1595-
interactions = {
1596-
chat = {
1597-
tools = {
1598-
opts = {
1599-
default_tools = {
1600-
"my_tool",
1601-
"my_tool_group"
1602-
}
1603-
},
1604-
}
1605-
}
1606-
}
1607-
})
1608-
<
1563+
`lua {6-9} require("codecompanion").setup({ interactions = { chat = { tools = {
1564+
opts = { default_tools = { "my_tool", "my_tool_group" } }, } } } })`
16091565

16101566
This also works for |codecompanion-configuration-extensions|.
16111567

@@ -2751,7 +2707,7 @@ The fastest way to copy an LLM’s code output is with `gy`. This will yank the
27512707
nearest codeblock.
27522708

27532709

2754-
APPLYING AN LLMS EDITS TO A BUFFER OR FILE ~
2710+
APPLYING AN LLM€�S EDITS TO A BUFFER OR FILE ~
27552711

27562712
The |codecompanion-usage-chat-buffer-tools-files| tool, combined with the
27572713
|codecompanion-usage-chat-buffer-variables.html-buffer| variable or
@@ -2856,19 +2812,8 @@ CodeCompanion provides comprehensive support for the ACP specification:
28562812

28572813
SUPPORTED ADAPTERS
28582814

2859-
-----------------------------------------------------------------------
2860-
Adapter Description Special Features
2861-
--------------- ----------------------- -------------------------------
2862-
Claude Code Anthropic’s Claude Code OAuth authentication, tool
2863-
CLI output trimming
2864-
2865-
Gemini CLI Google’s Gemini CLI Multiple auth methods (OAuth,
2866-
API key, Vertex AI), YOLO mode
2867-
2868-
Auggie CLI Augment Code CLI Standard ACP support
2815+
Please see the |codecompanion-configuration-adapters-acp| page.
28692816

2870-
Codex OpenAI Codex Full ACP implementation
2871-
-----------------------------------------------------------------------
28722817

28732818
CLIENT CAPABILITIES
28742819

@@ -3893,20 +3838,7 @@ In the `openai_responses` adapter, the following tools are available:
38933838
- `web_search` - Allow models to search the web for the latest information before generating a response.
38943839

38953840

3896-
USEFUL TIPS ~
3897-
3898-
3899-
YOLO MODE
3900-
3901-
The plugin allows you to run tools on autopilot, with YOLO mode. This
3902-
automatically approves any tool use instead of prompting the user, disables any
3903-
diffs, submits errors and success messages and automatically saves any buffers
3904-
that tools may have edited. In the chat buffer, the keymap `gty` will toggle
3905-
YOLO mode on/off. Alternatively, set the global variable
3906-
`vim.g.codecompanion_yolo_mode` to enable this or set it to `nil` to undo this.
3907-
3908-
3909-
SECURITY AND APPROVALS ~
3841+
SECURITY ~
39103842

39113843
CodeCompanion takes security very seriously, especially in a world of agentic
39123844
code development. To that end, every effort is made to ensure that LLMs are
@@ -3917,17 +3849,42 @@ is that the LLM can only work within the cwd when executing tools but will
39173849
minimize actions that are hard to recover from
39183850
<https://www.businessinsider.com/replit-ceo-apologizes-ai-coding-tool-delete-company-database-2025-7>.
39193851

3920-
The plugin also puts approvals at the heart of its workflow. Some tools, such
3921-
as the |codecompanion--cmd-runner|, require the user to approve any actions
3922-
before they can be executed. If the tool requires this a `vim.fn.confirm`
3923-
dialog will prompt you for a response. You may also
3924-
|codecompanion-configuration-chat-buffer-approvals| an approval for `any` tool.
39253852

3926-
When using CodeCompanion’s in-built tools, there are three choices:
3853+
APPROVALS
3854+
3855+
3856+
[!NOTE] This applies to CodeCompanion’s built-in tools only. ACP agents have
3857+
their own tools and approval systems.
3858+
In order to give developers the confidence to use tools, CodeCompanion has
3859+
implemented a comprehensive approval system for it’s built-in tools.
3860+
3861+
CodeCompanion segregates tool approvals by chat buffer and by tool. This means
3862+
that if you approve a tool in one chat buffer, it is `not` approved for use
3863+
anywhere else. Similarly, if you approve a tool once, you’ll be prompted to
3864+
approve it again next time it’s executed.
3865+
3866+
When prompted, the user has four options available to them:
3867+
3868+
- **Allow always** - Always allow this tool/cmd to be executed without further prompts
3869+
- **Allow once** - Allow this tool/cmd to be executed this one time
3870+
- **Reject** - Reject the execution of this tool/cmd and provide a reason
3871+
- **Cancel** - Cancel this tool execution and all other pending tool executions
3872+
3873+
Certain tools with potentially destructive capabilities have an additional
3874+
layer of protection. Instead of being approved at a tool level, these are
3875+
approved at a command level. Taking the `cmd_runner` tool as an example. If you
3876+
approve an agent to always run `make format`, if it tries to run `make test`,
3877+
you’ll be prompted to approve that command specifically.
3878+
3879+
Approvals can be reset for the given chat buffer by using the `gtx` keymap.
3880+
3881+
3882+
YOLO MODE
39273883

3928-
1. **Approve** - The tool will be executed
3929-
2. **Reject** - The tool will **NOT** be executed
3930-
3. **Cancel** - All tools in the queue will **NOT** be executed
3884+
To bypass the approval system, you can use `gty` in the chat buffer to enable
3885+
YOLO mode. This will automatically approve all tool executions without
3886+
prompting the user. However, note that some tools such as `cmd_runner` and
3887+
`delete_file` are excluded from this.
39313888

39323889

39333890
COMPATIBILITY ~
@@ -4670,7 +4627,7 @@ These handlers manage tool/function calling:
46704627
as a great reference to understand how they’re working with the output of the
46714628
API
46724629

4673-
OPENAIS API OUTPUT
4630+
OPENAI€�S API OUTPUT
46744631

46754632
If we reference the OpenAI documentation
46764633
<https://platform.openai.com/docs/guides/text-generation/chat-completions-api>
@@ -5324,7 +5281,8 @@ Let’s breakdown the prompts in that workflow:
53245281
opts = { auto_submit = false },
53255282
content = function()
53265283
-- Leverage YOLO mode which disables the requirement of approvals and automatically saves any edited buffer
5327-
vim.g.codecompanion_yolo_mode = true
5284+
local approvals = require("codecompanion.interactions.chat.tools.approvals")
5285+
approvals:toggle_yolo_mode()
53285286

53295287
-- Some clear instructions for the LLM to follow
53305288
return [[### Instructions
@@ -6400,7 +6358,7 @@ tool to function. In the case of Anthropic, we insert additional headers.
64006358
<
64016359

64026360
Some adapter tools can be a `hybrid` in terms of their implementation. That is,
6403-
they’re an adapter tool that requires a client-side component (i.e. a
6361+
they’re an adapter tool that requires a client-side component (i.e.a
64046362
built-in tool). This is the case for the
64056363
|codecompanion-usage-chat-buffer-tools-memory| tool from Anthropic. To allow
64066364
for this, ensure that the tool definition in `available_tools` has

lua/codecompanion/interactions/chat/tools/init.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---@class CodeCompanion.Tools
22
---@field adapter CodeCompanion.HTTPAdapter The adapter in use for the chat
3-
---@field tools_config table The available tools for the tool system
43
---@field aug number The augroup for the tool
54
---@field bufnr number The buffer of the chat buffer
65
---@field constants table<string, string> The constants for the tool
@@ -11,10 +10,12 @@
1110
---@field stdout table The stdout of the tool
1211
---@field stderr table The stderr of the tool
1312
---@field tool CodeCompanion.Tools.Tool The current tool that's being run
13+
---@field tools_config table The available tools for the tool system
1414
---@field tools_ns number The namespace for the virtual text that appears in the header
1515

1616
local EditTracker = require("codecompanion.interactions.chat.edit_tracker")
1717
local Orchestrator = require("codecompanion.interactions.chat.tools.orchestrator")
18+
local approvals = require("codecompanion.interactions.chat.tools.approvals")
1819
local tool_filter = require("codecompanion.interactions.chat.tools.filter")
1920

2021
local config = require("codecompanion.config")
@@ -258,7 +259,7 @@ function Tools:set_autocmds()
258259
})
259260
end
260261

261-
if vim.g.codecompanion_yolo_mode then
262+
if approvals:is_approved(self.bufnr) then
262263
return auto_submit()
263264
end
264265
if self.status == CONSTANTS.STATUS_ERROR and self.tools_config.opts.auto_submit_errors then

lua/codecompanion/interactions/chat/tools/orchestrator.lua

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local Approvals = require("codecompanion.interactions.chat.tools.approvals")
12
local Queue = require("codecompanion.interactions.chat.tools.runtime.queue")
23
local Runner = require("codecompanion.interactions.chat.tools.runtime.runner")
34
local log = require("codecompanion.utils.log")
@@ -153,11 +154,20 @@ function Orchestrator:_setup_handlers()
153154
}
154155

155156
self.output = {
156-
prompt = function()
157+
cmd_string = function()
157158
if not self.tool then
158159
return
159160
end
161+
if self.tool.output and self.tool.output.cmd_string then
162+
return self.tool.output.cmd_string(self.tool, { tools = self.tools })
163+
end
164+
return nil
165+
end,
160166

167+
prompt = function()
168+
if not self.tool then
169+
return
170+
end
161171
if self.tool.output and self.tool.output.prompt then
162172
return self.tool.output.prompt(self.tool, self.tools)
163173
end
@@ -250,7 +260,10 @@ function Orchestrator:setup_next_tool(input)
250260
log:debug("[Orchestrator::setup_next_tool] `%s` tool", self.tool.name)
251261

252262
-- Check if the tool requires approval
253-
if self.tool.opts and not vim.g.codecompanion_yolo_mode then
263+
if
264+
self.tool.opts
265+
and not Approvals:is_approved(self.tools.bufnr, { cmd = self.output.cmd_string(), tool_name = self.tool.name })
266+
then
254267
local require_approval_before = self.tool.opts.require_approval_before
255268

256269
if require_approval_before and type(require_approval_before) == "function" then
@@ -282,7 +295,7 @@ function Orchestrator:setup_next_tool(input)
282295

283296
if choice == 1 or choice == 2 then
284297
if choice == 1 then
285-
vim.g.codecompanion_yolo_mode = true
298+
Approvals:always(self.tools.bufnr, { cmd = self.output.cmd_string(), tool_name = self.tool.name })
286299
end
287300
return self:execute_tool({ cmd = cmd, input = input })
288301
elseif choice == 3 then

0 commit comments

Comments
 (0)