mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
fix(no-yolo): don't auto-refuse tool calls in non-interactive modes
Previously --no-yolo in -p / --json / rpc modes auto-refused every
tool call. That made the flag dangerous to pass to scripts: a
single --no-yolo in a shell config or wrapper script would silently
break any tool-using prompt.
New behaviour:
- Default: every mode is yolo (tools run freely, no prompts).
- --no-yolo + interactive TUI: confirm dialog before each tool.
- --no-yolo + -p / --json / rpc: stderr warning and ignore the
flag. Tools run freely; scripts keep working.
The TUI confirm dialog and /yolo runtime toggle still work as
before. Also removed the unused wireNoYoloAutoRefuse helper and
simplified core.NewConfirmGate's doc comment.
This commit is contained in:
parent
ac6d556f0a
commit
e2f2092478
5 changed files with 22 additions and 36 deletions
|
|
@ -152,7 +152,7 @@ zot --help
|
|||
| `--no-ext` | Skip extension discovery for this run. `--ext` still works on top, so `--no-ext --ext ./x` runs only `x`. |
|
||||
| `--with-skills` | Also load user-installed skills. Without this, only the built-in skills shipped in the binary are loaded. |
|
||||
| `--no-skill` | Disable all skills, including built-ins. No `skill` tool is registered and the system prompt has no skill manifest. |
|
||||
| `--no-yolo` | Confirm every tool call before it runs. In the TUI, a dialog shows the tool name and a one-line preview of its args with four choices: yes, yes-always-this-tool-this-session, yes-always-this-session, no. In print / json / rpc modes (no interactive prompt) every tool call is auto-refused with a reason the model can learn from. Type `/yolo` in the TUI to disable the gate for the rest of the session. |
|
||||
| `--no-yolo` | Confirm every tool call before it runs (interactive TUI only). A dialog shows the tool name and a one-line preview of its args with four choices: yes, yes-always-this-tool-this-session, yes-always-this-session, no. Ignored with a stderr warning in print / json / rpc modes, where tools still run freely so scripts and automation keep working. Type `/yolo` in the TUI to disable the gate for the rest of the session. |
|
||||
|
||||
## Tools
|
||||
|
||||
|
|
|
|||
|
|
@ -69,9 +69,12 @@ type Args struct {
|
|||
// and waits for an explicit yes/no. The user can also pick
|
||||
// "always for this tool this session" or "always for anything
|
||||
// this session" to stop being prompted again. Defaults off
|
||||
// (yolo mode): tools run without asking. In -p / --json / rpc
|
||||
// modes there's no interactive TUI, so --no-yolo auto-refuses
|
||||
// every tool call with a reason the model can learn from.
|
||||
// (yolo mode): tools run without asking.
|
||||
//
|
||||
// No effect in -p / --json / rpc modes, which have no
|
||||
// interactive prompt. A warning is printed to stderr on startup
|
||||
// so scripts know the flag is ignored, but tools still run
|
||||
// freely so automated workflows keep working.
|
||||
NoYolo bool
|
||||
|
||||
ListModels bool
|
||||
|
|
@ -287,8 +290,8 @@ flags:
|
|||
default: only built-in skills load
|
||||
|
||||
--no-yolo ask before running every tool call
|
||||
(interactive mode only; in -p / --json
|
||||
/ rpc this refuses every tool call)
|
||||
(interactive tui only; ignored with
|
||||
a stderr warning in -p / --json / rpc)
|
||||
|
||||
--max-steps N agent loop iteration cap (default 50)
|
||||
--list-models print known models and exit
|
||||
|
|
|
|||
|
|
@ -184,12 +184,14 @@ func Run(rawArgs []string, version string) error {
|
|||
// ---- print / json modes: require credentials, run single-shot ----
|
||||
|
||||
func runPrintMode(ctx context.Context, args Args, version string) error {
|
||||
if args.NoYolo {
|
||||
fmt.Fprintln(os.Stderr, "warning: --no-yolo has no effect in print mode (no interactive prompt available); tools will run without confirmation")
|
||||
}
|
||||
r, err := Resolve(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ag := r.NewAgent()
|
||||
wireNoYoloAutoRefuse(ag, args)
|
||||
sess, _ := openOrCreateSession(args, r, ag, version)
|
||||
defer sess.Close()
|
||||
|
||||
|
|
@ -208,36 +210,15 @@ func runPrintMode(ctx context.Context, args Args, version string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// wireNoYoloAutoRefuse installs a BeforeToolExecute hook that
|
||||
// refuses every tool call when --no-yolo is active but there's no
|
||||
// interactive UI to prompt (print / json / rpc modes). The reason
|
||||
// is written in a way the model can learn from so it proposes a
|
||||
// different action rather than looping on the same tool.
|
||||
func wireNoYoloAutoRefuse(ag *core.Agent, args Args) {
|
||||
if !args.NoYolo || ag == nil {
|
||||
return
|
||||
}
|
||||
gate := core.NewConfirmGate(nil) // nil inner = auto-refuse
|
||||
prev := ag.BeforeToolExecute
|
||||
ag.BeforeToolExecute = func(call provider.ToolCallBlock) (bool, string, json.RawMessage) {
|
||||
ok, reason, _ := gate.Check(call.Name, core.BuildPreview(call.Arguments, 120))
|
||||
if !ok {
|
||||
return false, reason, nil
|
||||
}
|
||||
if prev != nil {
|
||||
return prev(call)
|
||||
}
|
||||
return true, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
func runJSONMode(ctx context.Context, args Args, version string) error {
|
||||
if args.NoYolo {
|
||||
fmt.Fprintln(os.Stderr, "warning: --no-yolo has no effect in json mode (no interactive prompt available); tools will run without confirmation")
|
||||
}
|
||||
r, err := Resolve(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ag := r.NewAgent()
|
||||
wireNoYoloAutoRefuse(ag, args)
|
||||
sess, _ := openOrCreateSession(args, r, ag, version)
|
||||
defer sess.Close()
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ import (
|
|||
// Auth: if $ZOTCORE_RPC_TOKEN is set, the first command must be
|
||||
// {"type":"hello","token":"..."} or the connection is closed.
|
||||
func runRPCMode(ctx context.Context, args Args, version string) error {
|
||||
if args.NoYolo {
|
||||
fmt.Fprintln(os.Stderr, "warning: --no-yolo has no effect in rpc mode (no interactive prompt available); tools will run without confirmation")
|
||||
}
|
||||
r, err := Resolve(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -53,11 +53,10 @@ type ConfirmGate struct {
|
|||
allowedTool map[string]bool
|
||||
}
|
||||
|
||||
// NewConfirmGate returns a gate backed by inner. When inner is nil,
|
||||
// the gate operates in auto-refuse mode: every tool call is denied
|
||||
// with a fixed reason. That's what non-interactive modes (-p /
|
||||
// --json / rpc) use when --no-yolo is on, since they have no way
|
||||
// to prompt the user.
|
||||
// NewConfirmGate returns a gate backed by inner. Inner can be nil;
|
||||
// in that case every not-yet-allowed tool call is refused with a
|
||||
// fixed reason (the gate is effectively a blocker until AllowAll /
|
||||
// SetConfirmer is called).
|
||||
func NewConfirmGate(inner Confirmer) *ConfirmGate {
|
||||
return &ConfirmGate{
|
||||
inner: inner,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue