`--yes` / `/yolo on|off|once` clears the prompt of a `YOLO`-tagged tool: interpreter runs (`run_argv`, `run_argv_batch`, `open_pty`), the file mutations (overwrite / copy / move / delete) and `fetch_local`'s cleartext and mutating requests. A tool without the tag still asks, and hard path/command denylists stay untouched.
`classify_danger` now answers with a `DangerReason` — the reason text plus `yolo_skip` — so one call can refuse that skip: a command that runs through `sudo` / `su` / `doas` / `pkexec` (as the program or a wrapper) keeps its confirm whatever YOLO mode says. It never adds a reason of its own, so the program the denylist gate already asks about is not asked twice.
`prepare_batch_confirm` leaves the covered calls out, so a skipped call neither prompts on its own nor takes a slot in the round's aggregated prompt, and one escalating step keeps the whole batch in front of the human.
Typing `@shot.png` in the line attaches that picture to the turn — no separate
command — and `@notes.txt` appends the file as text under a `--- @path ---`
header, capped like a tool result with a resumable `truncate_token`.
`@"my shot.png"` is the quoted form, and Tab completes the path after `@`
(directories end in `/`, a name with a space completes quoted).
An image is skipped, with a note naming the key to set, on a model without
`image_in`; a missing or denylisted path is a note too, and the turn still goes.
The default catalogs mark what can take a picture (`deepseek-flash` yes,
`deepseek-v4-pro` no, the OpenAI defaults yes).
`Agent.run` takes the parts, so the turn is stored with its images and shows up
as `[image]` wherever only prose is wanted.
/history counted raw message rows, so /history 1 printed whatever row happened
to be last (usually a tool result in a tool-heavy session) and "one row means
full body" was a proxy for "show me that one thing".
A turn is what a human reviews: /history now prints the last turn's two ends
(the user message and the model's answer, markdown for the latter), -v prints
every row of the selected turn(s), -vv prints full bodies plus the local system
row, and --preview collapses every row to one line. N addresses turn N,
last [N] the last N turns, --message N a single row.
Numbering is a display ordinal over conversation rows (local-only rows print as
local) and deliberately never a database row number - seq is an implementation
detail that shifts with /clear. Grouping and rendering live in the new
cli/transcript.py.
/btw now defaults to --tools=read: the side turn gets only READ_ONLY
tools under a read-only context (fetch restricted to GET), with the main
session view so read-only reads (todo_list) see real state. --tools=full
keeps the previous whole-registry clone with a fresh session; --tools=no
turns tools off. When tools are off, the default read silently falls back
to no tools, while an explicit --tools read|full errors as before.
After a turn the run_cancellable SIGINT handler is removed, so a Ctrl+C
landing between turns (echoing user text, expire_yolo_once, gaps between
statements, or re-entering _echo_interrupted) raised KeyboardInterrupt at
an arbitrary bytecode and escaped run_repl, killing the whole REPL. Wrap
the loop body in one try/except KeyboardInterrupt that re-prompts, and
make _echo_interrupted itself suppress a further interrupt.
Map CancelledError and TimeoutError from models() to RuntimeError so
merged_model_choices falls back to config/cache. Stub remote fetch in
/provider unit tests so they never hit the network.
Reject providers with no models into bad_providers and warn with reasons
on chat/providers. When /provider changes and the current model is not
listed, prompt for a new model (keep it when still supported).
Store provider_name/model on sessions (SQLite migrate); restore on
resume and persist on create//model//provider. Startup prefers session
selection over re-asking. ask/choose and provider/model pick use
readline with Tab completion when available.
Register REPL slash commands as a Click Group (params, UsageError, help).
Dispatch via awaitlet.async_def so sync command bodies can awaitlet()
async memory/confirm work. Completer and /help derive from the registry.
Decode and stream reasoning_content as ReasoningDeltaEvent; keep it on
assistant messages. CLI shows dim reasoning, toggles stream/verbose, and
prints full tool results when verbose.