Both checks match a bare "tool" or "auto" anywhere in the error text,
which catches a lot that has nothing to do with tool support: a tool
timing out, an MCP server dropping, any message containing "automatic".
At the first site the consequences are not cosmetic. The real error is
replaced with "This model doesn't support agent tools", so the actual
cause is lost. The UI leaves agent mode. And the choice is written to
localStorage, so every later message in that conversation also runs
without tools, with nothing on screen explaining why.
It presents as "agent mode works in a new chat but not in this one",
which sends you looking at the model, the session, or the endpoint
rather than at a substring match in the error path. Diagnosing it took
a while precisely because the message you are shown is the one thing
that is not true.
The second site is milder -- it appends a hint rather than switching --
but appending "this model may not support tools" to an unrelated
failure still points the user at the wrong fix.
Match the provider's actual wording instead. Ollama returns
"<model> does not support tools"; the pattern also covers "doesn't
support tools" and "tools are not supported". The original error text
is kept in both cases so a genuine failure stays diagnosable.
Checked against the strings that used to misfire -- "tool execution
timed out", "MCP server not connected", "automatic retry failed",
"tool_choice auto is invalid", "peer closed connection" -- none of
which now demote, while the three real phrasings still do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(models): define capability schema and readers
* fix(models): harden Google catalog probing
Restrict native catalog probing to the Gemini host, keep provider keys out of request URLs, filter non-chat model resources, and preserve the manual refresh default in the built-in Google add flow.
* docs: update static/js/MODULE_SUMMARY.md to reflect current ES6 frontend
Rewrite the stale module summary to match the current no-build,
ES6-module frontend architecture. Adds coverage of app.js orchestration,
the chat/SSE pipeline (chat.js, chatStream.js, chatRenderer.js,
streamingRenderer.js), new subsystems (research/, compare/, document
streaming, cookbook*, skills.js), and removes the obsolete <script> load
order assumptions.
* cleanup: remove dead MEMORY_DOC / memory_doc paths (closes#4411)
Removes the unused MEMORY_DOC constant and the matching DataConfig
memory_doc field / set_data_paths entry. No runtime code imports or
references these paths, so this is a no-behavior-change dead-code
cleanup under the storage-architecture tracker #4377.
* fix(chat): Expand user chat bubble edit textbox width
- Update user chat bubble width from `fit-content` to `85%` to ensure consistency with the AI chat bubble edit textbox width.
* style(chat): Refine user message bubble width logic
- Change general bubble width to `fit-content`
- Set width to 85% specifically for user messages containing a `textarea`
Align regression tests with the current Odysseus behavior after merging origin/dev into local main.
- keep phone/name-only contacts valid and cover null email without crashes
- pin explicit web-search false form submission in chat.js
- update Cookbook dependency/download completion tests for combined live + persisted output
- expose SGLang OS package repair hints from backend diagnosis
- treat MLX and MLX-community repos as servable on Apple Metal while keeping CUDA behavior unchanged
- keep desktop new-chat coverage on the shared preferred-model helper
- remove a hardcoded crop overlay portal z-index literal
- include the local agent-loop cleanup that removes the old manage_notes reminder repair shim
Verified with: docker run --rm -v /home/pewds/odysseus-cookbook-fresh:/app -w /app odysseus-cookbook-fresh-odysseus python3 -m pytest -q (4515 passed, 4 skipped).
The comparison grid hard-codes 2-4 equal columns with no phone breakpoint, so at
390px two models get ~178px columns and four get ~88px columns. Each column is a
full scrolling chat, so content is unreadably over-wrapped and clipped. On
phones (<=768px), stack the panes into a single scrollable column. Desktop is
unaffected.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Setting epSel.value triggered an async change event whose handler
called refreshModels('') — wiping the correct model selection that
refreshModels(settings.default_model) had just applied moments earlier.
The dropdown silently fell back to the alphabetically-first model
(deepseek-v4-flash instead of qwen-3.6-35B-A3B).
Moved the change listener registration to after the settings block
so the async change event fires before any listener exists. The
utility and teacher sections already followed this pattern.