DeepTutor v1.5.10 Release Notes
Release Date: 2026.08.07
v1.5.9 was about providers; this one is about the paths underneath them holding up — a batch upload that stalled every other request, a knowledge-base list that rescanned every index, a tool call dispatched with no arguments and retried until the budget ran out. Two things that were quietly missing also land: an account can sign in to its own Codex, and the model's reply language is finally its own setting. Drop-in — no migrations, nothing to re-index.
What's New
Every account can sign in to its own Codex
A Codex token authorizes one person's ChatGPT plan, so the profile was correctly never grantable — but the OAuth endpoints were admin-only too, which left ordinary users with no route to Codex at all. They can now sign in for themselves: the card sits under Models → LLM, and the profile it creates is written to that user's own catalog (data/users/<uid>/settings/model_catalog.json), never the shared admin one. Grants resolve personal rows alongside assigned ones in a single place, so the option list, the capability gate, and selection validation cannot disagree — and an administrator inspecting someone's grants still sees only what they assigned. Partners are refused: a partner is a synthetic user, and admitting one would mean acting on its owner's login.
Model output language is its own setting
Settings → Appearance now carries two independent toggles. Interface language covers navigation, settings, and status text; Model output language sets the default language of chat and capability replies, which until now you could only steer from the prompt. An interface.json written before the split inherits its single language into both fields, so nothing changes until you pick. The pre-session bootstrap that reads these is also public now — /login and /register stayed English before, because the anonymous read hit the gated router and 401'd; it returns theme and language only, and a choice made in this browser still wins.
A tool call with no arguments is rejected, not run
Under a large tool surface and long history, providers intermittently emit a native function call with empty arguments even where the schema marks fields required. The call was dispatched anyway, so the tool did the rejecting — exec raised "requires a non-empty command", write_note answered "Unknown mode ''" — and neither reads as "an argument is missing", so the model re-emitted the same malformed call until the loop budget ran out. A guard in front of dispatch now names the missing arguments and their accepted values (enums included), turning that into one self-correcting round. The rules stay narrow: a parameter with a default is not required, empty collections are left alone, server-injected kwargs count as supplied.
Uploads and knowledge-base listing stop holding the loop
Batch-uploading files stalled every other request — chat WebSockets included — for the whole duration of the batch: the chunked write, zip extraction, and per-file PocketBase upload all ran inline in an async context, and the follow-up staging pass sat in an async def background task, which Starlette awaits on the loop rather than in its threadpool. Both now run in a worker thread. Separately, building one KB list response reloaded configuration and rescanned every index per item; it now reuses one reconciled snapshot — a 58-KB read-only listing finishes in ~2s instead of timing out the proxy.
Deep Research reads a connected Obsidian vault directly
When the selected knowledge base is a linked Obsidian vault, the research loop no longer auto-mounts rag against an index that does not exist. It mounts the three read-only vault tools instead — obsidian_search / obsidian_read / obsidian_list — injects the vault path server-side (overwriting any model-supplied value), treats their results as citable so evidence reaches the citation manager and the final report, and swaps in an Obsidian-specific system note. Indexed KBs and no-KB runs are untouched.
The server-side backend address is IPv4 loopback everywhere
localhost resolves to ::1 first on a dual-stack host while uvicorn binds 0.0.0.0 — IPv4 only — so every rewritten /api/* request failed to connect. The launcher was fixed first; the Docker entrypoint's render_environment and web/proxy.ts's last-resort default kept the hostname and have now been brought back in sync.
Assorted
- Escape stops a streaming Partner reply, and only that: the listener bails while any dialog-role overlay is mounted, so dismissing a modal or picker no longer kills the answer behind it.
- Vision capability overrides now match
claude-by vendor prefix (nothing ever began withclaude-4, so that key matched nothing), and recognize Kimi K3 and Qwen3.8-Max. - Codex context-window metadata survives the catalog round-trip instead of being dropped.
- Editing a message no longer races the refresh that follows it, so the optimistic bubble stops flickering back.
- The
deeptutor-cliwheel ships the agents' prompt YAML again — it had none, so prompt loading failed and the empty result was cached for the life of the process. Misses are no longer cached either, so a resource that appears after startup is picked up on retry.
Upgrade Notes
pip install -U deeptutor; Docker users pull ghcr.io/hkuds/deeptutor:latest. No schema changes, no re-index, no migration.
- Model output language defaults to your existing interface language. The split is inherited, not reset — set it explicitly under Settings → Appearance if you want replies in a different language than the UI.
- Non-admin Codex sign-in is on by default. Each account's profile stays in its own catalog and is invisible to the administrator and to every other user; nothing an ordinary user signs into can appear in the shared model list or the grant editor.
Full Changelog: v1.5.9...v1.5.10