Refs #1722
Preserves auto_compact as opt-in, adds the saved threshold setting, keeps the 500K hard floor, and wires Ctrl+L as a manual compaction shortcut for context-pressure recovery.
Harvested from PR #1723 by @aboimpinto
Co-authored-by: Paulo Aboim Pinto <aboimpinto@gmail.com>
Moonshot/Kimi is a shipped provider with full support (model registry entry,
provider ID, env var plumbing, CLI provider picker), but config.example.toml
was missing its [providers.moonshot] table block while all other 15 providers
had one. This made Moonshot the only shipped provider absent from the example.
Added the section between siliconflow and sglang, matching the shipped-provider
ordering in docs/PROVIDERS.md. Includes the Kimi Code alternate path and
kimi_oauth auth_mode comment.
v0.8.48 removed the Qwen 3.7 Max preset (it's a hosted model, not open-weight).
Two references remained in config.example.toml:
- Default Models comment (line 41)
- OpenRouter provider section comment (line 280)
These stale references could confuse users into trying a removed model.
Add Volcengine (火山引擎) as a new SearchProvider in web_search tool.
Uses Volcengine's Responses API with `tools: [{type: "web_search"}]`
and strict JSON prompt constraints to extract structured search results.
- Free tier: 20K queries/month per API key
- API key resolution: [search] api_key → VOLCENGINE_API_KEY →
VOLCENGINE_ARK_API_KEY → ARK_API_KEY env vars
- Select via `DEEPSEEK_SEARCH_PROVIDER=volcengine` or
`[search] provider = "volcengine"` in config.toml
Co-authored-by: Cursor <cursoragent@cursor.com>
Add SiliconFlow as an additive OpenAI-compatible hosted provider across config, secrets, CLI, agent registry, TUI runtime, picker, and docs.
Credit: based in part on the SiliconFlow provider direction from #1864 by @qychen2001, extended here with broader registry, documentation, and test coverage on current main.
Adds native xiaomi-mimo provider configuration, auth/env aliases, model registry entries, TUI request handling, tests, and docs. Keeps credentials in existing provider-scoped config/env/keyring paths and uses placeholders only in docs.
Adds Metaso AI Search as a new SearchProvider option alongside Bing,
DuckDuckGo, Tavily, and Bocha.
Co-authored-by: Zhao Xiaohong <zhaoxiaohong@metasota.ai>
* docs: v0.8.46 CHANGELOG — platform archives, palette, sub-agents, sandbox, web install, search fixes
Closes#2188
* feat(v0.8.46): quick fixes — palette, model picker Esc, sub-agent sidebar, shell chip, model name casing, CVE bump (#2212)
* fix: bump qs to >=6.15.2 for CVE-2026-8723
Add qs override in feishu-bridge package.json to force transitive
dependency resolution to >=6.15.2, addressing CVE-2026-8723.
Refs: #2198
* fix: Esc in model picker applies last-highlighted choice
Previously Esc reverted to the initial model when the user hadn't
moved the selection. Now Esc always applies the currently highlighted
model and thinking-effort tier, making Esc consistent with Enter.
Also updates the picker footer hint from 'Esc cancel' to 'Esc apply'.
Refs: #2196
* feat: show '⏳ shell running' chip in TUI footer
Adds a footer_shell_chip function that displays a '⏳ shell running'
status chip in the footer's right cluster whenever a foreground shell
command is active via exec_shell. The chip is always visible regardless
of user-configured status items.
Refs: #2194
* feat: auto-collapse finished sub-agents in sidebar
When a sub-agent completes (status = 'done'), its detail lines
(id, steps, duration, progress) are now hidden in the sidebar agents
panel. Only the summary label line is shown, keeping the sidebar
compact. Running agents still show full detail.
Refs: #2195
* feat: refresh Whale dark palette for better contrast
Improve contrast and layer separation in the Whale dark theme:
- Deepen base background for more depth (10,17,32)
- Lighten panel (22,34,56) for clearer distinction from bg
- Lighten elevated surface (36,52,78) for better elevation
- Lighten selection (48,68,100) for clearer selected state
- Boost text hint (138,150,174) and dim (118,130,156) readability
- Brighter border (52,88,145) for better edge definition
- Update tool surface colors for consistency
Refs: #2197
* fix: preserve model name casing in normalize_model_name_for_provider
When the user enters a model name like 'DeepSeek-V4-Flash', the
normalizer was lowercasing it to 'deepseek-v4-flash' via the
canonical_official_deepseek_model_id function. Now the normalizer
preserves the caller's casing when the input already matches a known
model id case-insensitively. Compact aliases like 'deepseek-v4pro'
are still rewritten to 'deepseek-v4-pro'.
Refs: #2109
* feat(web): install download tile with arch detection, SHA256, China mirrors + companion binary fix (#2213)
* fix(web): download both codewhale and codewhale-tui binaries in install snippets
The SNIPPETS map only fetched one binary per platform, causing the
dispatcher to fail with MISSING_COMPANION_BINARY. Every arch now
downloads both codewhale AND codewhale-tui side-by-side.
- macOS/Linux: added second curl + combined chmod/xattr/mv for tui
- Windows: added second Invoke-WebRequest for codewhale-tui.exe
- VERIFY: PowerShell now hashes both binaries; Unix --ignore-missing
covers all present binaries in a single sha256sum pass
* feat(web): add install download tile with arch detection, SHA256, and China mirrors (#2192)
* feat(sandbox/linux): process hardening — PR_SET_DUMPABLE, NO_NEW_PRIVS, RLIMIT_CORE (#2214)
* feat(sandbox/linux): add process hardening module — PR_SET_DUMPABLE, NO_NEW_PRIVS, RLIMIT_CORE (#2183)
* feat(sandbox/linux): seccomp filter + bwrap passthrough
- seccomp: BPF filter whitelisting safe syscalls, denying ptrace/mount/kexec
and other dangerous syscalls. Uses raw BPF instructions via libc prctl to
avoid external dependencies (#2182).
- bwrap: optional bubblewrap passthrough when /usr/bin/bwrap is present
and [sandbox] prefer_bwrap=true in config. Creates read-only rootfs with
write access limited to the working directory (#2184).
- landlock detect_denial extended to recognize seccomp SIGSYS/"Bad system
call" patterns alongside existing Landlock EACCES/EPERM detection.
- SandboxManager gains prefer_bwrap field; set_prefer_bwrap on ShellManager.
- EngineConfig gains prefer_bwrap field, wired through main/ui/runtime_threads.
- Diagnostics now reports bwrap_available and cgroup_version.
- config.example.toml documents the prefer_bwrap key.
Pre-existing clippy fixes picked up in the same build:
- collapsible_if in ui.rs version-check
- cmp_owned in goal.rs test
- consecutive str::replace in normalize_auth_mode
Closes#2182, closes#2184
* docs: add cross-links to issue and PR templates in CONTRIBUTING.md (#2215)
- Link .github/ISSUE_TEMPLATE/bug_report.md and feature_request.md from
the Reporting Issues section
- Link .github/PULL_REQUEST_TEMPLATE.md from the Pull Request Guidelines
section
* feat(release): bundle platform archives with install scripts (#2216)
- Add bundle job to release workflow that creates per-platform archives
(tar.gz for Linux/macOS, .zip for Windows) containing both codewhale
and codewhale-tui binaries plus install scripts
- Create install.bat (Windows) — copies binaries to %USERPROFILE%\bin
- Create install.sh (Unix) — copies binaries to ~/.local/bin
- Windows gets a portable .zip variant without install script
- Release notes updated to promote archives as primary download method
- Individual binaries retained for npm wrapper and scripting
Closes#2193
* fix(web_search): fall back to DuckDuckGo when Bing returns zero results (#2130)
When the configured search provider is Bing and the query returns zero
results (common for technical/compound queries), fall through to the
DuckDuckGo path instead of reporting empty. A provenance message is
surfaced: "Bing returned no results; used DuckDuckGo fallback".
Also adds Security and Code of Conduct cross-links to CONTRIBUTING.md
per the sub-agent renovation (#2203).
* docs: SANDBOX.md threat model + RFCs for persistence and MCP + SandboxExecutor trait
- docs/SANDBOX.md: complete threat model describing each platform's sandbox
(Seatbelt, Landlock, seccomp, process hardening, bwrap, Windows v1).
Covers defense-in-depth layering, config keys, denial detection, limitations.
- docs/rfcs/2189-persistence-sqlite.md: RFC for SQLite migration (drafted by sub-agent)
- docs/rfcs/2190-mcp-modularization.md: RFC for MCP crate split into
protocol/client/server with OAuth support
- crates/tui/src/sandbox/policy.rs: SandboxExecutor trait definition and
SafetyLevel→SandboxPolicyBehavior mapping function with tests
Closes#2180, closes#2186, closes#2189, closes#2190
* feat: sandbox parity tests + remove sub-agent 100-turn cap
- Add sandbox parity tests covering platform detection, denial patterns,
bwrap preference, and policy consistency across modes (#2187)
- Remove arbitrary 100-turn sub-agent cap: DEFAULT_MAX_STEPS changed
from 100 to u32::MAX. Sub-agents now run until they produce a final
text response, are cancelled by the parent, or hit a configured
explicit budget (#2034)
Closes#2187, closes#2034
Users reported running `deepseek-tui` inside project directories
with hundreds of GB of content — ML datasets, model weights
(`.safetensors`, `.gguf`, `.pt`, `.onnx`), Docker image dumps,
parquet / arrow caches, anything that falls outside the snapshot
built-in excludes. The pre/post-turn snapshot path called
`SnapshotRepo::open_or_init` which initialized the side git repo
and then ran `git add -A` — which walked the entire workspace
indexing every file. On a 100-300 GB directory this hung the TUI
for minutes-to-hours while git churned through the index.
The pre-existing v0.8.27 fixes (#1112: retention cap, mid-session
prune, expanded built-in excludes) addressed the orthogonal
"snapshots grow unbounded over many turns" angle but did nothing
to prevent the first snapshot from being impossible to take.
This change adds `estimate_workspace_size_bounded()` — a bounded
`ignore::WalkBuilder` walk that respects `.gitignore` and the
snapshot module's existing skip list (`node_modules/`, `target/`,
`.next/`, `.venv/`, `__pycache__/`, etc.). The walk early-exits
at either the byte cap or 200,000 file entries, returning `None`
to signal "too big to snapshot."
`SnapshotRepo::open_or_init_with_cap(workspace, cap_bytes)` calls
the estimator *before* the side `git init`, and returns
`Err(InvalidInput)` with a "workspace too large" reason — which
`turn::snapshot_with_label` already logs at WARN and continues
past, so a too-large workspace silently disables snapshots
without blocking any turn. The check is paid only on first init;
subsequent snapshots through the existing side repo skip it.
Plumbing:
- `SnapshotsConfig.max_workspace_gb` (default 2, `0` disables)
- `EngineConfig.snapshots_max_workspace_bytes` resolved at engine
construction from `config.snapshots_config().max_workspace_gb`
- `pre_turn_snapshot` / `post_turn_snapshot` / `pre_tool_snapshot`
take a `cap_bytes: u64` argument threaded from the engine
- `SnapshotRepo::open_or_init` retains its v0.8.31 signature as a
thin wrapper over `open_or_init_with_cap` using the default cap
- `config.example.toml` documents the new `max_workspace_gb` knob
with the "set to 0 to disable" escape hatch for users with
legitimate large monorepos
Six new tests pin both the estimator (under-cap returns Some,
over-cap returns None, builtin-excluded dirs skipped, cap=0
disables the bound) and the `open_or_init_with_cap` integration
(oversized workspace fails with the right error and references
the config knob; cap=0 succeeds even on oversized content).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`image_analyze` sends an image file to an OpenAI-compatible vision
endpoint and returns the model's natural-language description.
Complements `image_ocr` (which uses local tesseract for "what text
is on this image"); `image_analyze` is for "what is this image
about" — visual reasoning the local OCR engine can't do.
Trust-boundary scope: **two-step opt-in only**.
1. The feature is gated by `[features] vision_model = true` —
default `false`.
2. The tool needs a `[vision_model]` config block specifying
`model` (with optional `api_key` / `base_url` — falls back to
the main config api_key + the OpenAI base URL).
Without both, the tool isn't registered, so no install fires a
vision API call without explicit user setup. Workspace boundary:
the tool rejects absolute paths and any `..` parent-dir
traversal before any base64 encoding or HTTP call. Stateless —
each call sends only the requested image + optional prompt; no
session, no conversation history attached. Supports PNG, JPEG,
GIF, WebP, and BMP inputs.
**Billing**: each call hits the configured vision endpoint
(OpenAI by default — `gpt-4o-mini` / `gpt-4o` family commonly
configured). Users with their own deployments (Gemini, Claude
Vision via OpenAI shim, local llama.cpp) can point `base_url` /
`api_key` at the alternative.
Tests cover the tool metadata (read-only capability, correct
name), MIME-type detection across the supported formats and the
unsupported-format rejection path, and the workspace-boundary
checks (absolute paths and `..` traversal both reject before
any API call). Skipped from the upstream PR: the
`.github/workflows/sync-cnb.yml` rewrite, which v0.8.31 already
addressed with the concurrency/scoped-push refactor; landing the
older form would regress that commit.
Resolved a clippy::collapsible_if in tool_setup.rs (the
`if feature && let Some(cfg) = ...` form) to satisfy the
workspace -D warnings gate.
Harvested from PR #1467 by @MMMarcinho
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AtlasCloud (https://atlascloud.ai) hosts the V4 family on its own
DeepSeek-compatible endpoint at `https://api.atlascloud.ai/v1`, and
several contributors had been running it through the
OpenAI-compatible passthrough with manual `base_url` / model
overrides. Selecting `provider = "atlascloud"` in
`~/.deepseek/config.toml` (or via `DEEPSEEK_PROVIDER=atlascloud`)
now wires up:
- documented `DEFAULT_ATLASCLOUD_BASE_URL` /
`DEFAULT_ATLASCLOUD_MODEL` defaults so a fresh install needs
only the api_key
- a `[providers.atlascloud]` config block with the same fields
every other named provider exposes (api_key / base_url / model
/ http_headers)
- `ATLASCLOUD_API_KEY` env var path, including the secrets test
cleanup loop so per-test env hygiene continues to work
- the provider-picker / `/provider` slash command entries so the
provider is reachable from the runtime UI, not just config
- the env-driven `*_BASE_URL` override branch so users who pin a
proxy can still flip it without editing config.toml
Trust-boundary pins held: AtlasCloud is opt-in (default remains
DeepSeek), no API keys are hardcoded, the api_key resolution flows
through the same `secrets` crate path every other provider uses,
and the provider-config base_url stays settable per environment.
Resolved 3-way merge conflicts in `crates/secrets/src/lib.rs` (env
cleanup loop) and `crates/tui/src/config.rs` (per-provider
base_url match arm + `provider_passes_model_through` predicate)
so the contributor's AtlasCloud branch coexists with the v0.8.x
provider expansion already on `main`. Added the missing match arm
in `validate_provider_base_url` so the non-exhaustive-pattern
check passes after the new variant lands.
Harvested from PR #1436 by @lucaszhu-hue
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DuckDuckGo HTML scraping with Bing fallback remains the default
`web_search` backend — no API key required, no behaviour change for
installs that don't opt in. Users in regions where those scrapers
are rate-limited or unreliable can now set `[search]
provider = "tavily" | "bocha"` plus `api_key = "..."` in
`~/.deepseek/config.toml` (or via the `DEEPSEEK_SEARCH_PROVIDER` /
`DEEPSEEK_SEARCH_API_KEY` env vars) to route every `web_search`
call through the chosen API.
Tavily targets general AI-agent search; Bocha (博查) is the
mainland-China-friendly equivalent. Both providers are gated by
the existing `[network]` policy on their respective hosts
(`api.tavily.com`, `api.bochaai.com`) and surface a clear
`ToolError` (rather than a silent fallback to DuckDuckGo) when
the user has opted in but forgotten to set `api_key`. Test pins
the missing-key behaviour for both providers.
Resolved 3-way merge conflicts in `web_search.rs` (description
text and test module) so the contributor's helpers
(`truncate_error_body`, `sanitize_error_body`) coexist with the
v0.8.30 spam-detection and query-parsing tests already on `main`.
Folded the `SearchProvider::default()` impl into a `#[default]`
derive to satisfy the workspace `-D warnings` clippy gate, and
threaded `search: Option<SearchConfig>` through `merge_config` so
the multi-config layering doesn't break the field initialiser.
Harvested from PR #1294 by @sandofree
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets the `deepseek-cn` provider preset's default `base_url` to the official host (`https://api.deepseek.com`) per [api-docs.deepseek.com](https://api-docs.deepseek.com/). Keeps recognizing `api.deepseeki.com` in URL heuristics and chat-client normalization so existing configs continue to work, and updates the `doctor` strict-tool-mode endpoint hint, docs, and examples accordingly.
Closes#1079. Thanks to @Jefsky for the fix.
Summary:
- Keep default auto alternate-screen mode inside the TUI so transcript scrolling stays app-owned unless users explicitly opt out.
- Queue terminal resume events when the engine channel is full, avoiding stranded paused terminal state after interactive tool cancellation or bursts.
- Scope crash-checkpoint recovery to the resolved launch workspace instead of the shell cwd.
- Add runtime deepseek_version to the prompt environment block so agents can distinguish installed runtime identity from a stale checkout.
Test plan:
- cargo test -p deepseek-tui --locked on a simulated merge with current main
- cargo fmt --all -- --check
- git diff --check
- Existing PR CI was green for lint, version drift, Linux/macOS/Windows tests, npm wrapper smoke, and GitGuardian.
* feat(tui): add `notification_condition` override + assistant text in body (#820)
`[notifications]` already controls method (auto/osc9/bel/off), the
`threshold_secs` gate, and the `include_summary` body. Some users
want a simpler high-level switch — "always notify on every turn" or
"never notify" — without having to know the lower-level fields.
This adds a single optional `[tui].notification_condition` field:
- `"always"` — notify on every successful turn (no duration
threshold). The configured `[notifications].method` and
`include_summary` flag are still respected.
- `"never"` — suppress all turn-completion notifications.
- omitted — fall back to the existing `[notifications]` defaults
(the v0.8.15 behavior is unchanged).
The OSC 9 / BEL body now also carries the assistant's reply text,
sanitized and truncated to 360 characters, with a fallback to the
latest assistant message in `api_messages` when the streaming buffer
was empty (e.g. a tool-only turn). When `include_summary = true`,
the elapsed/cost line is appended on a new line.
Drive-by: drop the unused `Method::from_str` helper (the new code
match-arms over the typed `NotificationMethod` enum, so the parser
helper had no remaining callers).
Differences from upstream #820:
- Keeps the `[notifications]` section in `config.example.toml`
(with `notification_condition` documented as an opt-in override)
rather than deleting the existing block. This avoids breaking
configs that already set `[notifications].method` etc.
- Drops the unrelated `#[allow(dead_code)]` on
`schema_migration::registry`.
- Threads `Option<CostEstimate>` through the helper (the cost
surface changed from `Option<f64>` since #820 was authored).
Tests:
- `notification_settings_*` (3) — `always` keeps the configured
method, `never` returns `None`, missing override falls back.
- `completed_turn_notification_*` (4) — streaming text wins, falls
back to latest assistant message, default placeholder, and 360-char
truncation with `...`.
Integrates #820.
Co-authored-by: zero <1603852@qq.com>
Co-authored-by: zerx-lab <161401688+zerx-lab@users.noreply.github.com>
* style: fmt — collapse short test message helper calls
---------
Co-authored-by: zero <1603852@qq.com>
Co-authored-by: zerx-lab <161401688+zerx-lab@users.noreply.github.com>
Integrates the useful custom HTTP header support from #881 onto current main.
- support root, provider-specific, and DEEPSEEK_HTTP_HEADERS overrides
- apply validated extra headers to model API requests while preserving protected Authorization and Content-Type defaults
- document the config shape in README, config.example.toml, and docs/CONFIGURATION.md
Co-authored-by: Desheng <8596814+dst1213@users.noreply.github.com>
Neither field had any code path that read it. Shipping config knobs
that do nothing trains users to mistrust config. Remove until the
implementation exists.
Tool outputs (read_file, grep_files, exec_shell, fetch_url, web_search) that
exceed a configurable token threshold are now intercepted before they reach
the parent context. A structured synthesis header replaces the raw blob; the
full content is stored in the workshop variable `last_tool_result` for later
`promote_to_context` retrieval.
Key changes:
- New `crates/tui/src/tools/large_output_router.rs`: `LargeOutputRouter`,
`WorkshopConfig`, `WorkshopVariables`, `RouteDecision`, token estimator,
synthesis-prompt builder, and wrap_synthesis helper. Full unit-test suite.
- `ToolContext` gains `large_output_router` and `workshop_vars` fields plus
the `with_large_output_router` builder; constructor defaults are `None` so
sub-agents and test contexts are unaffected.
- `ToolRegistry::execute_full_with_context` applies routing after every tool
call; `raw=true` in the tool input bypasses routing for that invocation.
- `EngineConfig` gains a `workshop` field; `Engine::new` creates the shared
`WorkshopVariables` Arc when the field is present and wires it into every
`build_tool_context` call.
- `Config` gains `[workshop]` table deserialization; `merge_config` propagates
it like other optional tables.
- `config.example.toml` documents `[workshop]`, `large_output_threshold_tokens`
(default 4096), and per-tool threshold overrides.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>