Remove unused code confirmed dead via vulture scan, grep verification,
and coverage analysis:
- _get_bridge_dir (cli/commands.py): 82-line function with zero callers
- add_assistant_message (agent/context.py): method body never executed,
also removed now-unused build_assistant_message import
- _tool_parameters_schema (agent/tools/base.py): redundant copy of schema
already exposed via the `parameters` property
- MSTEAMS_REF_TTL_S (channels/msteams.py): unused constant (production
uses config.ref_ttl_days directly); inlined in test
- MESSAGE_TYPE_USER (channels/weixin.py): unused constant
- Add `ModelPresetConfig` schema for named model presets
- Add `model_presets` dict to `Config` and `model_preset` field to `AgentDefaults`
- Add `resolve_preset()` to return effective model params from preset or defaults
- Add `@model_validator` to reject unknown preset names
- Update `_match_provider()` to use resolved preset model/provider
- Update `make_provider()` and `provider_signature()` to use `resolve_preset()`
- Add `model_preset` property to `AgentLoop` for atomic runtime switching
- Update `AgentLoop.from_config()` to inject a runtime `default` preset
- Wire self-tool to inspect/clear preset state
- Update CLI display strings to show active preset
This commit implements a progressive refactoring of the tool system to support
plugin discovery, scoped loading, and protocol-driven runtime context injection.
Key changes:
- Add Tool ABC metadata (tool_name, _scopes) and ToolContext dataclass for
dependency injection.
- Introduce ToolLoader with pkgutil-based builtin discovery and
entry_points-based third-party plugin loading.
- Add scope filtering (core/subagent/memory) so different contexts load
appropriate tool sets.
- Introduce ContextAware protocol and RequestContext dataclass to replace
hardcoded per-tool context injection in AgentLoop.
- Add RuntimeState / MutableRuntimeState protocols to decouple MyTool from
AgentLoop.
- Migrate all built-in tools to declare scopes and implement create()/enabled()
hooks.
- Migrate MessageTool, SpawnTool, CronTool, and MyTool to ContextAware.
- Refactor AgentLoop to use ToolLoader and protocol-driven context injection.
- Refactor SubagentManager to use ToolLoader(scope="subagent") with per-run
FileStates isolation.
- Register all built-in tools via pyproject.toml entry_points.
- Add comprehensive tests for loader scopes, entry_points, ContextAware,
subagent tools, and runtime state sync.
The hosted Xiaomi MiMo API accepts {"thinking": {"type": "enabled"|"disabled"}}
to toggle reasoning, which is exactly the shape produced by the existing
thinking_type style. The xiaomi_mimo ProviderSpec just needed to opt in.
Before this fix, setting reasoning_effort="none" had no effect on MiMo
because no thinking_style was configured, so the disable signal never
reached the server. Default-on models (mimo-v2.5-pro and friends) kept
reasoning regardless of user configuration.
Source: https://platform.xiaomimimo.com/docs/en-US/api/chat/openai-api
Co-authored with Claude Opus 4.7. Strategy and review via Claude Desktop,
implementation via Claude Code.
Six tests covering:
- AgentDefaults preserves 'nanobot' and the cat icon by default
- camelCase config keys (botName/botIcon) bind to the new fields
- Empty bot_icon is accepted (opt-out of the leading icon)
- ThinkingSpinner uses bot_name in its status text
- StreamRenderer header combines icon and name when icon is set
- StreamRenderer header is just the name when icon is empty
- Append [Archived Context Summary] to system prompt instead of injecting
it into the user message runtime context, improving KV cache reuse across
turns and avoiding consecutive same-role messages.
- _last_summary persists in metadata (no pop) for restart survival;
summary is re-injected every turn via the stable system prompt.
- Remove dynamic "Inactive for X minutes" from _format_summary — use
static last_active timestamp instead to preserve KV cache stability.
- Pass session_summary through build_messages() so both normal and
ask_user paths receive the archived summary in the system prompt.
- estimate_session_prompt_tokens now reads _last_summary from metadata
to include the summary in token budget estimation.
- Remove obsolete session_summary parameter from
maybe_consolidate_by_tokens and estimate_session_prompt_tokens
call sites in loop.py (summary flows through build_messages instead).
- Ensure /new (session.clear()) clears _last_summary from metadata.
Add show_reasoning config (default: False) to display model
thinking/reasoning content in the TUI during streaming. Reasoning
is emitted via a new emit_reasoning hook on AgentHook, gated by the
channels config. Display uses ✻ prefix with dim italic styling.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Route progress output through the Live's render hook to fix cursor
misalignment that caused content duplication. The root cause was that
progress/reasoning output used a separate Console instance, bypassing
Rich Live's process_renderables hook. Also fixes pre-existing issue
where multiple headers printed per agent turn.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extract duplicated bus/provider/loop initialization from CLI commands
(serve, _run_gateway, agent) and Nanobot facade into a single
AgentLoop.from_config() classmethod.
- Remove _make_provider() from cli/commands.py and nanobot.py
- Remove inline provider creation in all three CLI entry points
- AgentLoop.from_config() creates MessageBus, calls make_provider(),
and assembles AgentLoop with all standard config-derived parameters
- Supports **extra overrides for callers that need custom args
(e.g. cron_service, session_manager, provider_snapshot_loader)
- Update tests to mock make_provider at nanobot.providers.factory
and add from_config classmethod to _FakeAgentLoop fixtures
This is PR 1/4 of the model-preset feature decomposition.
The previous implementation popped _last_summary from session.metadata
after injecting it into the prompt, then saved the session. This caused
the summary to be permanently lost after a process restart, making the
AI forget archived context and appear to ignore memory or reference
non-existent previous messages.
Replace the destructive pop with a _last_summary_used sentinel:
- _last_summary stays in metadata for restart survival
- _last_summary_used prevents duplicate injection within the same turn
- Clear the sentinel whenever a new summary is generated
Updates tests to match the new persistence behavior.
Let WebUI users configure the single web search provider credential from BYOK while keeping saved secrets masked and hot-reloaded for new searches.
Co-authored-by: Cursor <cursoragent@cursor.com>
On Windows, prompt_toolkit produces lone surrogate code points (e.g.
🐈) for emoji input. These propagate through the message bus
and crash at json.dumps() / file write time because surrogates cannot
be encoded as UTF-8.
Extract _sanitize_surrogates() that round-trips through UTF-16 to
reconstruct paired surrogates into real characters (e.g. 🐈
→ 🐈), replacing unpaired surrogates with U+FFFD. Apply it at the CLI
input path and reuse in SafeFileHistory.
Fixes two related input-handling bugs in the onboard wizard:
1. _input_text treated "" as None, preventing users from clearing
optional string fields or entering empty strings intentionally.
2. _input_model_with_autocomplete used `if value else None`, which
discarded falsy values such as empty strings or 0.
To support clearing optional string fields, add _is_str_or_none() and
normalize empty strings to None inside _configure_pydantic_model only
when the field annotation is `str | None`. Required str fields keep
"" as a valid value.
Also included:
- Remember last selected item in provider/channel/model menus for
better UX when configuring multiple items.
- Rename _SIMPLE_TYPES and _MENU_DISPATCH to lowercase to follow
Python naming conventions (they are local variables, not constants).
- Remove unused imports in test file.
Extracted from PR #3358.
Track the Dream cursor in memory versioning so restores do not skip history after rolling back Dream commits.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a session-scoped slash command palette sourced from backend command metadata, and keep welcome-page quick actions localized across all WebUI languages.
Co-authored-by: Cursor <cursoragent@cursor.com>
_send_text() swallowed API errors (non-zero errcode) with just a
warning log, and send() had three silent return paths (no client,
session paused, no context_token). Neither triggered ChannelManager's
retry logic, causing persistent message loss until a new inbound
message refreshed the context_token.
Now all failure paths raise RuntimeError, matching BaseChannel's
contract and enabling proper retry behavior.
When host is set to 0.0.0.0, the gateway now enforces that either token
or token_issue_secret must be configured — it refuses to start otherwise.
Bootstrap endpoint behavior:
- token_issue_secret configured: always validate regardless of source IP
(handles reverse-proxy scenarios where all connections appear as localhost)
- No secret: only localhost can bootstrap (local dev mode)
The frontend shows an authentication form when bootstrap returns 401/403,
persists the secret in localStorage, and retries automatically on reload.
The previous LAN-access fix (PR #3656) relaxed the bootstrap localhost
check when host was 0.0.0.0, but did not require any authentication —
any device on the network could obtain a token without credentials.
New behavior:
- token_issue_secret configured: always validate, regardless of source
IP (handles reverse-proxy scenarios where all connections appear as
localhost).
- No secret configured: only localhost can bootstrap (local dev mode).
This supersedes the host-based check from PR #3656.
The webui bootstrap endpoint (/webui/bootstrap) rejected all non-localhost
connections with HTTP 403, preventing the embedded webui from working when
accessed from another device on the LAN — even when host was set to 0.0.0.0.
Skip the localhost check when the server is explicitly bound to 0.0.0.0 or ::,
since that signals intent to accept external connections.
Align the WebUI sidebar and chat chrome with the updated design, and generate WebUI session titles asynchronously without blocking turns.
Co-authored-by: Cursor <cursoragent@cursor.com>
The is_path branch in _fmt_known was not passing max_length to
abbreviate_path, so read_file, write_file, edit, list_dir, and
web_fetch always truncated paths at 40 chars regardless of config.
Now all three branches (is_path, is_command, fallback) honor the
configured toolHintMaxLength.