Commit Graph
1 Commits
Author SHA1 Message Date
KRATOS efb6b79164 web_search robustness + chat UX: collapse, clipboard, scroll-pin, proactive search (#218)
* web_search: fix #209 follow-ups (tolerant score, render gate, redaction, FQDN)

Verified-and-fixed defects from a review of #209:
  - Tolerant score decode (lenientScore): a stringified/odd/null "score" no longer
    makes json.Unmarshal discard the entire response; it degrades to absent.
  - Score render gate rounds first (>= 0.01) so tiny positives and negatives no
    longer print a noisy "score 0.00".
  - Domain-filter error now goes through redactWebSearchText, matching the other
    error paths.
  - Trailing-dot FQDN symmetry: "react.dev." and "react.dev" normalize the same
    on both the allowlist and result sides.

Left as-is (intended, tested): scheme-less host:port allowlist entries. Includes
mixed-type domains-array tests.

* web_search: only register the built-in tool when a backend is configured

When no search backend is set (ZERO_WEBSEARCH_BASE_URL / ZERO_WEBSEARCH_BACKEND),
CoreNetworkTools no longer offers the built-in web_search. Previously it was always
registered, so a model with an MCP search tool (e.g. Exa) would burn several calls
+ high-risk permission prompts on the built-in tool — which can only return
"no backend configured" — before falling back to the working MCP tool.

Now: no backend -> only web_fetch (+ any MCP search tool) is offered, so the model
uses the MCP search directly. A configured backend behaves exactly as before.

Tests: registry/web_search/specialist tests that asserted web_search is always in
CoreTools now configure a backend; added a test that it is omitted when none is set.

(Pre-existing, unrelated: internal/cli doctor/exec tests fail in the sandbox on
origin/main too — network/provider-dependent, not touched here.)

* tui: copy selected transcript text to the native OS clipboard

Selecting transcript text auto-copies on mouse release, but the copy went only
through OSC52 (ansi.SetSystemClipboard), which macOS Terminal.app doesn't support
at all and iTerm2 / some Windows Terminal builds have off by default — so the
selection silently never reached the clipboard.

Copy via the native clipboard (pbcopy / clip.exe / xclip) first, which works on
local terminals regardless of OSC52, and fall back to OSC52 for remote/SSH
sessions where no local clipboard utility is reachable. atotto/clipboard was
already in the module graph (indirect); promoted to a direct import — no new
dependency.

Gates: gofmt/vet/build(host+linux+windows)/tui test -race/staticcheck all pass.

* tui: collapse long tool output by default with click-to-expand

Long, noisy tool output (web-search / MCP / read dumps) flooded the transcript.
Now a tool result whose output exceeds the live body cap renders collapsed by
default — head + a "▸ N lines — click to expand" footer — and you click the card
(while it is live) to expand (▾) / collapse. Mirrors the existing reasoning-row
collapse, so collapsed rows also flush to scrollback clean.

Scoped to avoid hiding what matters:
  - diff tools (edit_file / apply_patch / write_file) never collapse — their body
    must stay reviewable (matches the deeper flush cap intent);
  - the uncapped detailed transcript view (Ctrl+O, bodyCap==0) never collapses —
    it still shows full output;
  - short output (<= cardBodyMaxLines) stays inline as before.

Reuses transcriptRow.expanded + the row toggle (toggleReasoningRow generalized to
toggleTranscriptRow); the card head is a clickable toggle line. Running tool
cards (spinner) are unchanged.

Tests: collapse/expand, short-output inline, diff tools never collapse, toggle,
and the head exposes a click toggle; existing diff + detailed-view tests updated.
Gates: gofmt/vet/build(host+linux+windows)/tui -race/staticcheck all clean.

* cli: fall back to a usable saved provider instead of re-running onboarding

If the active provider lacked a credential (e.g. it relies on an env var that
isn't set, or an OAuth login that isn't stored), the TUI forced first-run
onboarding on every launch — even when other saved providers were fully
configured. Each onboarding then appended yet another provider.

Now, when the active provider isn't usable, fall back to the first usable saved
provider (prefer a remote keyed one over a local endpoint) for the session, and
skip onboarding. Onboarding only runs when no configured provider is usable — a
genuinely fresh setup. Saved logins persist; switch the active provider with
`zero provider use <name>`.

Tests: firstUsableProvider prefers remote keyed, falls back to local, accepts a
keyless local proxy, and reports none-usable. Gates: gofmt/vet/build
(host+linux+windows)/staticcheck clean; the only failing cli tests
(doctor/exec, network-dependent) are pre-existing on origin/main.

* tui: hold the scroll position while output streams in

The chat viewport scroll offset is measured from the bottom, so when the
transcript grew (a streaming turn) the window followed the new bottom and yanked
the user off whatever they had scrolled up to read.

syncChatScroll now pins the viewport: while the user is scrolled up, it bumps the
offset by however many lines the body grew, so the absolute view holds where they
are reading; at the bottom (offset 0) it follows the tail as before. Hooked into
the single Update funnel; only the scrolled-up path renders the body, so the
common (at-bottom) case stays cheap.

Tests: pins by the growth delta while scrolled up; resets/follows at the bottom.
Gates: gofmt/vet/build(host+linux+windows)/tui -race/staticcheck clean.

* agent: search the web proactively instead of answering "I don't know"

When a web search/fetch tool is available and the user asks about something
external the model is unsure of or that may be current (a product, library,
company, price, version, recent event), the system prompt now tells it to search
and read results before answering, rather than replying that it doesn't know
without checking. Addresses the model saying "I don't know about that" until the
user explicitly asked it to search.

Tests: prompt includes the new guidance ("search the web and read"). Existing
prompt assertions use Contains, so the addition is non-breaking.

* tui: show clean labels for MCP tool cards (mcp_exa_web_search_exa → web search)

* agent: sharpen web-search guidance (disambiguate, refine, scale)

Strengthen the search rule so the model searches before answering about an
external entity/product/library/model/version/release it doesn't recognize,
doesn't assume the most common meaning of an ambiguous name (disambiguates by the
conversation's domain), and refines + re-searches when results look off-topic —
rather than confidently answering the wrong interpretation (e.g. reading "Fable 5"
as a video game instead of an AI model). Also: don't web-search timeless facts or
workspace-answerable questions (use the file tools), keep queries short, and scale
the number of searches to the question.

Tests: prompt includes the new guidance ("search the web before answering",
"do not recognize"). Existing assertions use Contains, so non-breaking.

* test: gofmt setup_fallback_test.go (comment alignment)

* Address review feedback on web_search + chat UX

Resolve CodeRabbit review comments on this PR:

- firstUsableProvider: skip a profile whose CatalogID no longer resolves
  *and* that has no explicit BaseURL (no endpoint → unusable). A stale
  CatalogID with a BaseURL still works as a custom endpoint and is kept.
- providerProfileIsLocal: classify by parsed URL hostname (exact
  localhost / 127.0.0.1 / ::1) instead of substring matching, so hosts
  like "notlocalhost.com" are no longer misread as local.
- lenientScore: reject non-finite scores (ParseFloat accepts "NaN"/"Inf")
  so the documented score filter actually holds and the renderer never
  emits a non-finite value.
- syncChatScroll: shift the pinned from-bottom offset by the signed line
  delta so the view also holds when the body shrinks (card collapse,
  transcript clear), clamped at zero; previously only growth was handled.
- copyTranscriptSelectionCmd: report "Copy failed" when both the native
  clipboard and the OSC52 fallback fail, instead of claiming success;
  keep the selection so the user can retry.
- Drop vestigial ZERO_WEBSEARCH_BACKEND test env: the native backend was
  removed (MCP path), so the var is read by no product code and the
  references misrepresented it as a config knob.

Adds tests for the local-host parsing, the catalog/BaseURL skip, and the
non-finite score rejection.
2026-06-16 13:47:08 +05:30