feat: In safe-mode, asked the user whether they want to continue or cancel before any possible changes are made#122
feat: In safe-mode, asked the user whether they want to continue or cancel before any possible changes are made#122lifeizhou-ap wants to merge 7 commits intomainfrom
Conversation
…e running shell command
|
related: it would be nice for toolkits to have a means to declare the system packages they depend on. In this way, we could test for the binary, like |
Hey @codefromthecrypt Thanks for the feedback. Just to confirm whether I understand your comments correctly. Do you mean we can have something smart in toolkit that they can know the packages on my local machine. For example, if I have already have packages on my local machine to achieve the task, the plan won't install another tool to achieve the same task. Is my understanding correct? |
yeah right now, you can run a command and it might try to apt-install a system package. If the toolkits expose any binaries, or "exit code zero" commands they need, goose could obviate attempts to load that toolkit. OTOH, this won't prevent normal prompts from also attempting the same thing in a toolkit. Here's pseudocode of what it might look like --- a/src/goose/toolkit/github.py
+++ b/src/goose/toolkit/github.py
@@ -9,3 +9,7 @@ class Github(Toolkit):
def system(self) -> str:
"""Retrieve detailed configuration and procedural guidelines for GitHub operations"""
return Message.load("prompts/github.jinja").text
+
+ def system_prereqs(self) -> Tuple(str):
+ """List of commands that must complete success for this toolkit to operate"""
+ return ("gh", "gh status")
|
| @click.option("--plan", type=click.Path(exists=True)) | ||
| @click.option("--log-level", type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]), default="INFO") | ||
| def session_start(name: Optional[str], profile: str, log_level: str, plan: Optional[str] = None) -> None: | ||
| @click.option("--safe-mode", is_flag=True, help="Prompt before executing potentially unsafe commands", default=False) |
There was a problem hiding this comment.
wonder if we can borrow from other tools. I think sometimes, if prompt is default, -Y is overriding it, like apt
-y, --yes, --assume-yes
Automatic yes to prompts; assume "yes" as answer to all prompts and run
non-interactively. If an undesirable situation, such as changing a held package,
trying to install a unauthenticated package or removing an essential package occurs
then apt-get will abort. Configuration Item: APT::Get::Assume-Yes.
--assume-no
Automatic "no" to all prompts. Configuration Item: APT::Get::Assume-No.
Usually, when choosing to name things, I try to borrow from another tool's name that's well used, whether it is vocab or a flag, then you can blame it on them :)
There was a problem hiding this comment.
haha, naming is always the hardest part to me. Thank you so much for your advice!
| elif isinstance(tool_use.parameters, list): | ||
| output = json.dumps(tool.function(*tool_use.parameters)) | ||
| if isinstance(tool_use.parameters, dict) or isinstance(tool_use.parameters, list): | ||
| function_return_value = tool.function(**tool_use.parameters) |
There was a problem hiding this comment.
is this intentional? i think trying to unpack a list (*) will throw an error since it's positional arguments not keyword arguments
| if isinstance(tool_use.parameters, dict) or isinstance(tool_use.parameters, list): | ||
| function_return_value = tool.function(**tool_use.parameters) | ||
| if isinstance(function_return_value, dict): | ||
| user_decision = function_return_value['user_decision'] if 'user_decision' in function_return_value else None |
There was a problem hiding this comment.
i feel this is easier to follow if we flatten out the conditionals
if isinstance(parameters, dict):
function_return_value = tool.function(**parameters)
elif isinstance(parameters, list):
function_return_value = tool.function(*parameters)
else:
raise ValueError(
f"The provided tool parameters '{parameters}' "
"could not be interpreted as arguments."
)
user_decision = None
if isinstance(function_return_value, dict):
user_decision = function_return_value.get("user_decision")There was a problem hiding this comment.
call_function also still has a lot of responsibility, which makes it hard to reason/test. i'd take it one step further by separating out each chunk
_execute_tool_function(tool, parameters)_extract_user_decision(function_return_value)
so then the final resulting ToolResult reads like this
function_return_value = _execute_tool_function(tool, tool_use.parameters)
user_decision = _extract_user_decision(function_return_value)
output = json.dumps(function_return_value)| RULESTYLE = "bold" | ||
| RULEPREFIX = f"[{RULESTYLE}]───[/] " | ||
|
|
||
| TASKS_WITH_EMOJI = {"planned": "⏳", "complete": "✅", "failed": "❌", "in-progress": "🕑", "cancelled": "🚫", "skipped": "⏩"} |
There was a problem hiding this comment.
:nit: could we add a trailing comma so this dict prints out each line so it's easier to read?
| _path.write_text(content) | ||
| return "Successfully replaced before with after." | ||
| else: | ||
| return {"result": "User chooses not to make the change on this file. skip the change", "task_status": "skipped"} |
There was a problem hiding this comment.
:nit: i like active voice for this User rejected suggested changes to {file}. Skipping edits.
| return None | ||
|
|
||
| def is_in_safe_mode() -> bool: | ||
| return os.getenv(GOOSE_SAFE_MODE_ENV, "false") == "true" |
There was a problem hiding this comment.
just an opinion i think adding "true" "false" clutters the environment. imo we should should only check if it exists or not (e.g. unset GOOSE_SAFE_MODE would disable safe mode).
this minimizes user error
|
Hi @lamchau, Thanks for the review! The code is a bit unorganised now. At the moment with this draft PR I would like to get the feedback about this feature first, like whether it will be useful or it is worthwhile for experiment. If we think it would be useful, I will continue this PR to polish the code. |
|
@lifeizhou-ap it's worth experimenting with! i currently do this with a |
|
I think some people would appreciate this feature, as it can be a surprise how "biased to action" goose is out of the box (I like it myself so likely wouldn't use this mode, but if it isn't adding a lot of complexity to it it is an interesting feature to add in as an option) |
Resolves unicode-width dependency conflict between pctx (needs ^0.2.1 via deno/swc) and ratatui 0.29 (pins =0.2.0) by: - Using tui-textarea from PR #122 (ratatui 0.30 support) pinned to commit 48792febd9d454fefdd869de0e75849cad6e6007 rhysd/tui-textarea#122 - Upgrading goose-tui to ratatui 0.30, crossterm 0.29, ansi-to-tui 8.0.1 - Fixing type ambiguity in chat.rs/info.rs (multiple unicode-width versions) - Updating tests for CallToolRequestParams API change (added meta field)
Why
At the moment, when the plan is kicked off, the tasks are executed automatically. Users do not have much control to review potential changes that the task will make. It is hard for user to make the decision whether they want to proceed, skip the task or cancel the plan except using
CTRL-CWhat
Enable a
safe-mode(can be renamed) to provide some control for the user to decide whether to continue before running the tools ofshellpatch_filewrite_fileMake the diff more readable
Shell
Before the shell script is executed, the user is shown with the explanation of the command. The ai will also determine whether the command will do any changes on the users' computer(via prompt). If it does, the user is will be prompted whether they want to continue to execute the command. If yes, command will be executed. If no, the task will be cancelled and all the subsequence task will be cancelled. (We can also provide
skipoption so that that specific task can be skipped, and polish the experiences)patch_file and write_file
Before changing the file, user are show the diff of the changes in the similar way of
git diff. The user will be prompted whether they want to change the file. If yes, file will be changed. If no, file won't be changed and will move to the next task. (We can addyes to alloptions later on, so goose can remember the user decision in the plan)shell_command.mov
patch_file.mov
write_file.mov
Note
This PR is a draft PR and the changes is a bit unorganised at the moment. The main purpose is to get your feedback about whether the
safe-modeis useful for our users. I can polish and refactor the code afterwards.