feat(cli): wire confirm_callback for jarvis agents ask (with --yes/--no-yes)

`jarvis agents ask` is a non-interactive CLI path, so the AgentExecutor's
ToolExecutor never had a confirm_callback wired. Tools whose ToolSpec
sets requires_confirmation=True (shell_exec, git_*, ...) returned
"requires confirmation but no confirmation callback is available" and
the agent relayed that back as natural language, never executing.

This adds a --yes/--no-yes flag (default --yes):
- --yes: auto-approve (lambda _prompt: True). Suited for CLI runs where
  the operator already authorized the engagement scope.
- --no-yes: prompt on TTY via click.confirm.

The callback is set on the AgentExecutor itself; executor.py:_invoke_agent
reads it and forwards it to the constructed agent through agent_kwargs
(both `interactive=True` and `confirm_callback=...`).
This commit is contained in:
Gilles Ceyssat
2026-05-20 20:20:42 +00:00
committed by krypticmouse
parent 4c37aa83df
commit 333cb9a370
+20 -2
View File
@@ -702,16 +702,34 @@ def errors():
@agent.command("ask")
@click.argument("agent_id")
@click.argument("message")
def ask(agent_id, message):
@click.option(
"--yes/--no-yes",
"auto_approve",
default=True,
help="Auto-approve tool execution that would otherwise need confirmation. "
"Default: on (suits non-interactive CLI use). Pass --no-yes to require a "
"TTY prompt for tools whose ToolSpec sets requires_confirmation=True.",
)
def ask(agent_id, message, auto_approve):
"""Ask an agent a question (immediate response)."""
manager = _get_manager()
agent_id = _resolve_agent_id(manager, agent_id)
manager.send_message(agent_id, message, mode="immediate")
click.echo("Asking agent...")
_, executor, _ = _get_scheduler_and_executor()
_, executor, system = _get_scheduler_and_executor()
if executor is None:
click.echo("Executor not available", err=True)
raise SystemExit(1)
# Wire a confirmation callback so the agent's own ToolExecutor can actually
# run tools whose ToolSpec sets requires_confirmation=True (e.g. shell_exec,
# git_*). `executor` is the AgentExecutor; the callback is read in
# _invoke_agent and propagated to the constructed agent via agent_kwargs.
if auto_approve:
executor._confirm_callback = lambda _prompt: True
else:
executor._confirm_callback = (
lambda prompt: click.confirm(f"\n{prompt}", default=False)
)
executor.execute_tick(agent_id)
msgs = manager.list_messages(agent_id)
responses = [m for m in msgs if m["direction"] == "agent_to_user"]