Commit Graph

118 Commits

Author SHA1 Message Date
NCBM 8ec63aa6a2 core/agent: let YOLO mode skip YOLO-tagged soft confirms
`--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.
2026-10-21 19:10:46 +08:00
NCBM 154509b929 core/cli+config: attach files to a turn with @path mentions
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.
2026-10-21 18:53:47 +08:00
NCBM e9ee71e853 core/tools: show images instead of describing them
New `view_image` reads a local picture (JPEG, PNG, GIF, WebP, sniffed from its
bytes) and hands it to the model as an image part, with an optional `detail`
level — `low` measured 3.4x cheaper on a 1200x900 picture. `read_file` now
points at it instead of returning decode noise.

`fetch_local` returns an image response as the picture itself, and MCP image
content blocks become image parts instead of `[image: ...]` placeholders, so a
screenshot server finally reaches the model.
2026-10-21 18:43:29 +08:00
NCBM 0dd70ff31b core/cli: prompt for a round's tool calls once
prompt_confirm_batch_async answers a ToolBatchConfirmHook with one box that numbers every call the round wants to make (continuation lines stay under their entry). The prompt is the plain ``Allow these N tool calls?`` confirm: a deny offers the same one comment for all of them, a non-interactive host denies, and the next round asks again.

The ReplState registry installs the hook next to the per-call one, and format_tool_confirm_box now shares the box drawing with the batch header.
2026-10-17 17:32:44 +08:00
NCBM cfe36b024b core/tools+cli: split fetch into remote fetch and fetch_local 2026-10-17 17:06:43 +08:00
NCBM 3cd95504c6 core/agent+cli: keep a round cut off at the output limit 2026-10-16 22:29:25 +08:00
NCBM 6232925c2c cli/display: list a batch's commands above the '* Run batch' row 2026-10-16 19:08:14 +08:00
NCBM 2eb99a079d core/agent+tools+cli: number todos by listing order, not the internal id 2026-10-16 18:52:36 +08:00
NCBM 8729330120 core/skills: announce the skill tools in a notice, not a catalog 2026-10-09 21:40:45 +08:00
NCBM e72d2359d7 core/skills: read the filesystem live, drop the cache and reload 2026-10-09 21:40:38 +08:00
NCBM 1f04889449 cli/skills: open the skill directory instead of printing SKILL.md 2026-10-09 21:40:27 +08:00
NCBM 6807055b9f cli/slash: remove the /clear command 2026-10-09 21:15:48 +08:00
NCBM 4b5f67f5f3 cli/display: render web_search as a pretty line
``web_search`` is composed by the CLI host rather than the builtin catalog, so it had no ``_PRETTY_LINES`` entry and fell back to the ``[tool] …`` / ``[tool ok] …`` whole lines. It now leads with ``* Web Search '<query>' `` (the query as a 60-character preview, like the ask and skill subjects) and lands on the hit count — or on ``(no results)`` when every source came up empty, an emptiness the tool reports as ``error: the search found nothing`` and which is therefore not shown as a failure. A real error still prints in red with its message.

The guard test that keeps a builtin tool from shipping without a renderer now checks this host-composed tool too, since a catalog walk cannot see it.
2026-10-07 00:20:41 +08:00
NCBM 768b310a0b cli/display: hold a batch's later prefixes until the open row is done
A round that asked for several calls printed the first one as an abandoned
half-line: the next prefix closed the still-open row (a bare newline), the
result then printed whole and repeated that prefix, and the rest of the batch
was suppressed. The model edits files in batches, so the duplicate row showed
up there most. Queue the later prefixes instead: each one opens its own row
once the row before it has taken its detail, which keeps one finished row per
call in call order and leaves the bytes identical to the non-interactive path.
Only output that genuinely takes the line — a prompt box, streamed text, an
error — still ends the open prefix as its own line. The cursor is still never
moved.
2026-10-06 21:38:03 +08:00
NCBM 60bc5957a7 cli: resolve thinking settings for the chat session 2026-10-06 21:11:28 +08:00
NCBM 7cd5fcc576 cli/display: merge a batch of todo calls into one line
A round that updated its todo list printed one header plus a full stack
render per call, so the same list appeared several times. When a batch only
touches todo tools, print one `* Todo x<N>: Push, Update` line and the last
result, whose text already ends with the final stack. A batch holding any
other tool keeps the per-call lines, and a todo call never prints a prefix
at call time (it would dangle when the batch is merged).
2026-10-06 20:51:07 +08:00
NCBM 1068ea0025 prompting: end an open tool line before writing a prompt 2026-10-06 19:06:22 +08:00
NCBM a81b138bf8 cli/display: close an interrupted pretty line instead of erasing it 2026-10-06 19:01:42 +08:00
NCBM 7127190b9a cli/display: label regex_files lines with Search 2026-09-22 23:22:29 +08:00
NCBM de03e5932b core/tools: rename grep_files to regex_files (regex pattern) 2026-09-22 23:22:13 +08:00
NCBM 9b365403d7 cli/history: print a row's reasoning before its answer
A reasoning trace was appended after the body it produced, which read as if the
answer had come first and the thinking had followed it. It now leads the row,
under its own `reasoning:` label with a blank line before the answer - the order
the live stream uses. The trace only prints where it already did: a turn's ends
(the default view) and -vv; previews stay silent.
2026-09-22 23:05:59 +08:00
NCBM 90174340d7 cli/skills: expose skills to the session
ReplState builds the store from [skills] against the current workspace and
publishes its roots as static read-only roots, so read_file/grep_files see a
skill directory while writing stays denied; the roots are rebuilt when the
workspace changes, since the project-scope directories hang off it. Discovered
skills are folded into the system prompt the way MCP instructions are, with the
catalog cap and its 'use skill_search' hint, and only when tools are on and
injection is not switched off; [skills].enabled = false drops the tools from the
registry entirely.

The write policy is installed with the CLI confirm: once, session, or no, timed
with a default of no and independent of YOLO, because a skill directory is not a
grant. A library host that installs no hook keeps the deny default. /skills
lists, searches, reads, and reloads; /status counts what was found.
2026-09-22 22:55:24 +08:00
NCBM c7441d4a43 tools/skills: create and modify skills behind a grant
skill_create writes a SKILL.md (name, description, body) plus whatever bundled
files the model lists, and skill_edit changes one file of an existing skill
either by literal replacement, by appending, or by rewriting it. Both resolve
every path inside the skill directory first, so a bad argument is answered
before anything is written and before the human is asked anything; a rewritten
SKILL.md is re-parsed so a skill that lost its description (or its frontmatter)
says so instead of failing quietly. The store is reloaded after a write, which
makes a new skill visible to the very next skill_list.

Writing is a host policy, not a directory-access grant: the denylist wins
first, then a session grant, then [skills].allow_write, then the host's confirm
hook — asked once per skill directory, so one answer covers its files. A host
without a hook denies, which keeps the library default safe; the confirm can
also answer 'session' to cover the rest of the chat.
2026-09-22 22:46:21 +08:00
NCBM 1d05da62c2 tools/skills: list, read, and search skills
skill_list shows what was discovered (name, source root, directory, description)
together with the copies a name collision shadowed and the notes from repaired
frontmatter, so a third-party skill never disappears quietly. skill_read
addresses a skill by name and defaults to its SKILL.md, reusing read_file for
ranges, line numbers, and the continuation token; a bundled file is resolved
inside the skill directory only, symlinks included. skill_search greps every
visible skill (or one) and rebases each hit on its skill, which makes a match
attributable without absolute paths.

Reads need no grant: the host registers the roots as static read-only roots on
the workspace policy, so read_file/grep_files see skill directories too while
write access stays denied — skill writes will go through their own confirm.
Grep used to drop every file outside the primary workspace root, which also hid
hits under a granted directory; those now come back as absolute paths.
2026-09-22 22:42:45 +08:00
NCBM d6c4aa0d4f cli/display: prettify the remaining tools
PTY, human-prompt, temp-dir, truncate, and the rest of the VCS/todo family still
printed raw "[tool] name(args)" lines while every other tool had a syntax. Each
one now has a prefix that appears as the call starts and an outcome parsed from
its own result shape: session ids and alive/exit state for PTY, answered/
selected/count for the ask family, granted or denied for access requests, the
new path for a temp dir, changed files with line counts for vcs_diff, and the
rendered stack for the remaining todo commands. ask_into_pty reports only that
the answer was written.

Resolution is no longer a dict lookup: namespaced MCP tools are built from
their ``mcp__<server>__<tool>`` name so a newly connected server gets the same
line without registration, and the resolved renderer is carried from call to
result instead of being looked up twice. Plugin tools and custom calls keep the
whole-line fallback. The shared key=value parser and the ranged-read detail are
reused by run_argv, fetch, read_file, and get_truncated rather than copied.

Tests cover every added line (including the error paths), the MCP naming rule,
and an invariant that each builtin tool resolves to a renderer.
2026-09-22 22:25:34 +08:00
NCBM 221d5d5b68 cli/display: keep the line above an erased tool prefix
Erasing the in-flight prefix reused a helper that cleared one row more than
requested, so a parallel tool batch also erased the line above the prefix -
the streamed reasoning text that sits there when a model reasons and then
calls tools. The helper now clears exactly the requested rows and takes
explicit control of where the cursor lands; the parallel fallback erases its
own line and lets the leading newline of the whole-line write reuse it.

Row counts are computed from physical terminal rows instead of logical
newlines, so an assistant segment wider than the terminal is fully erased
before the markdown re-render rather than leaving a stray copy behind.

Tests: a byte-level regression for reasoning before a parallel batch, unit
coverage for the row-counting and erase primitives, and a wrapped-line flush.
2026-09-22 22:13:24 +08:00
NCBM 836ac8d6f8 cli/history: never collapse a turn's ends
The mixed policy previewed the user row, so a long prompt came back truncated
even though it is half of what the turn is about. Both ends - the human's
message and the model's answer - now print in full at every verbosity level;
--preview remains the explicit one-line skimmer that collapses them too.

Tests cover the ends staying full in the default and -v views (and the rounds
between them staying one-liners) plus a long-prompt regression.
2026-09-22 21:15:07 +08:00
NCBM 76e5b8ac0f cli/history: group /history by turn
/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.
2026-09-22 21:01:49 +08:00
NCBM dfd45a1d57 cli/display: show a cancelled wait distinctly
The wait line rendered every non-error/non-slept outcome as "(disturbed)", so a
Ctrl+C cancellation of the wait prompt looked like the human disturbed it. Print
"(cancelled)" for the tool's "cancelled by user" result.
2026-09-22 19:44:19 +08:00
NCBM 27bd7b46f7 prompting: let Ctrl+C cancel poll-based prompt reads
The wait tool's prompt runs off-loop on a worker thread, but its read polls
select/msvcrt, so it can act on a cancel: the SIGINT target now sets a flag the
reader checks between polls and raises PromptCancelledError with, which the tool
reports as "cancelled by user" while the turn continues. run_prompt_async /
run_cancellable_prompt_async hand the matching hook to the CLI guard; a stale
cancel never leaks into the next prompt read.
2026-09-22 19:28:03 +08:00
NCBM 6ec7b4de12 cli/interrupt: route SIGINT through a session handler
A SIGINT delivered while the event loop is parked in its selector is raised in
the loop's own frame: it escapes run_until_complete, the Runner cancels the main
task, and the REPL is left half-shut-down (no prompt, and the next line the
human types is eaten by an abandoned prompt reader) until the following Ctrl+C
kills the process. Off-loop prompts (ask_user_* / wait / tool confirms run in a
worker thread via asyncio.to_thread) and the auto-retry countdown are the usual
windows.

Keep one asyncio-level SIGINT handler installed for the whole session instead:
it cancels the innermost registered target (run_cancellable registers the turn
task, sleep_cancellable the retry countdown, off_loop_prompt the waiting prompt)
and never raises. Reads that block on the main thread inside a coroutine (the
REPL entry prompt, synchronous click prompts) suspend the router and take SIGINT
as a KeyboardInterrupt. Off-loop readline prompts cannot be interrupted without
abandoning their reader thread on stdin, so they ignore SIGINT with a one-line
hint.
2026-09-22 19:16:05 +08:00
NCBM 8b9233090f cli/btw: tell the model side questions are unsaved and tool-scoped 2026-09-19 09:24:38 +08:00
NCBM 8a8de1b018 cli: notify the model when a session resumes after a restart 2026-09-19 09:20:59 +08:00
NCBM 47331fed48 cli/retry: reset the auto-retry budget after a recovered round 2026-09-14 22:09:53 +08:00
NCBM e50cbfb46f cli/display: show pretty tool calls while they run 2026-09-14 22:01:20 +08:00
NCBM 18cbcf6835 cli/config: --auto-continue flag and [agent] auto_continue_limits 2026-09-09 22:47:39 +08:00
NCBM aba3765def cli: reset auto-continue at turn boundaries 2026-09-09 22:46:19 +08:00
NCBM 685370d3cb cli/limits: yyy turn-scoped auto-continue for limit prompts 2026-09-09 22:44:20 +08:00
NCBM a1f106a5e2 cli: directory access confirm, /grants, and session grants 2026-09-09 22:23:25 +08:00
NCBM 4f142f1db9 cli: fold MCP initialize instructions into agent prompt and /mcp 2026-09-08 13:09:32 +08:00
NCBM 4916494e44 cli: start MCP servers around chat and add /mcp slash command 2026-09-08 12:18:35 +08:00
NCBM 0a0f05ad14 cli: restore line range in read_file pretty summary 2026-08-17 17:33:55 +08:00
NCBM 95a48873c1 cli: pretty printed summary for wait tool 2026-08-15 18:42:31 +08:00
NCBM e27effcefe cli: pretty printed summaries for vcs_status and vcs_log 2026-08-15 18:37:12 +08:00
NCBM 8e8a10a05e cli: reduce pretty summaries to minimal done/error semantics 2026-08-15 18:36:21 +08:00
NCBM 5344a20b67 cli: pretty printed summaries for file mutation tools 2026-08-15 18:09:52 +08:00
NCBM aeae99a496 cli: pretty printed summary for fetch with HTTP status coloring 2026-08-15 18:09:05 +08:00
NCBM 259dd87c43 cli: pretty printed summaries for run_argv and run_argv_batch 2026-08-15 18:08:28 +08:00
NCBM 6f939e52b2 cli: pretty printed summaries for listdir, glob_paths and grep_files 2026-08-15 18:06:36 +08:00
NCBM 41ce98bf42 cli: style pretty tool summaries, verbose shows full results, preview shows first line 2026-08-15 18:05:40 +08:00