feat(brain): route real-time search by live broadcast state

STREAM_BROWSER becomes the broadcast *capability* master flag; the live
screen-share state (new ToolContext.broadcasting, passed per turn by the bot)
decides the backend:
  - master off            -> broadcast disabled, always Gemini
  - master on + live on    -> on-screen Chrome (visible on the stream)
  - master on + live off   -> Gemini
context.broadcasting is None outside the voice path (evals, text entry, older
bot) and falls back to the master flag, so current behaviour is unchanged.
This is the brain-side foundation; bot-side wiring (bridge passes broadcast
state, auto-broadcast on voice join, voice on/off toggle) follows.

Specs + docs/llm_contexts.md updated. Covered by
tests/test_web_search_broadcast_routing.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
javis-bot
2026-06-11 01:17:50 +09:00
parent f54e2a46ae
commit 5d45d1d3bd
6 changed files with 144 additions and 22 deletions

View File

@@ -594,15 +594,27 @@ class WebSearchTool(Tool):
context.user_print(f"🌐 Searching the web for '{search_query}'")
debug_log(f" 🌐 searching for '{search_query}'", "web")
# Real-time info routing by STREAM_BROWSER (docs/stream_browser_modes.md):
# true -> drive the on-screen Chrome (visible on the broadcast),
# false -> Gemini grounded search. Either falls through to the
# DDG/Brave/Wikipedia cascade below if it yields nothing (fail-open).
# Real-time info routing (docs/stream_browser_modes.md):
# master flag cfg.stream_browser (env STREAM_BROWSER) is the
# broadcast *capability*; context.broadcasting is the *live*
# screen-share state for this turn (set by the bot).
# - master off -> broadcast disabled, always Gemini
# - master on + live on -> on-screen Chrome (visible on stream)
# - master on + live off -> Gemini
# context.broadcasting is None outside the voice path (evals, text
# entry, older bot) -> fall back to the master flag so behaviour is
# unchanged. Either backend falls through to the DDG/Brave/Wikipedia
# cascade below if it yields nothing (fail-open).
from .realtime_search import browser_search, gemini_search, gemini_cli_search
if getattr(cfg, "stream_browser", True):
master_browser = getattr(cfg, "stream_browser", True)
live = getattr(context, "broadcasting", None)
if live is None:
live = master_browser
use_browser = master_browser and live
if use_browser:
routed = browser_search(search_query)
if routed:
debug_log(" 🌐 routed via browser (STREAM_BROWSER=true)", "web")
debug_log(" 🌐 routed via browser (broadcast live)", "web")
return ToolExecutionResult(success=True, reply_text=routed)
elif getattr(cfg, "gemini_auth", "oauth") == "oauth":
routed = gemini_cli_search(search_query)

View File

@@ -7,14 +7,23 @@ memory.
### Real-time info routing (`STREAM_BROWSER`)
Before the DuckDuckGo cascade, `run()` routes by the env flag `STREAM_BROWSER`
(mirrored into `cfg.stream_browser`; see `docs/stream_browser_modes.md` and
`realtime_search.py`):
Before the DuckDuckGo cascade, `run()` routes by the broadcast capability flag
`cfg.stream_browser` (env `STREAM_BROWSER`) combined with the live broadcast
state `context.broadcasting` for the turn (see `docs/stream_browser_modes.md`
and `realtime_search.py`). `context.broadcasting` is `None` outside the voice
path (evals, text entry) and falls back to the master flag:
- **true** (default): `browser_search()` drives the on-screen Chrome (Node CDP
helper `bot/scripts/stream-test/browse-search.mjs`) to Google-search the
query, so the action is visible on the Go-Live broadcast.
- **false**: Gemini answers, with the sub-mode chosen by `cfg.gemini_auth`
| `cfg.stream_browser` | `context.broadcasting` | backend |
|---|---|---|
| True | True | on-screen Chrome (`browser_search()`) |
| True | False | Gemini |
| True | None | Chrome (no signal -> master flag) |
| False | any | Gemini (broadcast disabled, never Chrome) |
- **on-screen Chrome**: `browser_search()` drives Chrome (Node CDP helper
`bot/scripts/stream-test/browse-search.mjs`) to Google-search the query, so
the action is visible on the Go-Live broadcast.
- **Gemini**: answers, with the sub-mode chosen by `cfg.gemini_auth`
(env `GEMINI_AUTH`, default `oauth`):
- `oauth` (default): `gemini_cli_search()` shells out to the Gemini CLI
(`gemini -p <query> -o json --skip-trust`, default approval mode) using the