aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
AgeCommit message (Collapse)AuthorFilesLines
2026-08-09fix: reject non-finite provider prices, and read them as unpricedDanilo M.1-7/+181
A nan or inf in [providers.*] survived float() and reached the cost arithmetic, rendering $nan or, for -inf, a negative cost. Worse than a display wart: is_priced answered True, so the readout took the priced branch and showed a poisoned figure in the one state the ? is there to admit it cannot say. Guarded at both ends. providers._number mirrors models._get, which already covered the models.ini writer. is_priced states its own predicate completely rather than trusting its callers, since the dialog is a third writer and the file is documented for hand-editing. Records why float money stays, and that exact-equality assertions in the cost test hold for the chosen prices rather than in general. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09fix: reject non-finite prices and escape a model named DEFAULTDanilo M.1-22/+177
float() accepts "nan" and "inf", so a hand-edited models.ini could feed either into the cost arithmetic and render "$nan". Rejected once in _get rather than at each downstream call site. A model whose id is literally DEFAULT wrote a [DEFAULT] section, which a stock configparser reader treats as defaults inherited by every other model. Since the file is documented for hand-editing, that reintroduces the contamination the renamed default_section exists to prevent, so the id is escaped on write and mapped back on read. Also drops the marker branch in save(), leaving mark_skipped() as the sole writer of the "marker present iff no real keys" invariant, and prunes two test blocks that could not fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09fix: warn when [[providers.local]] discards its fieldsDanilo M.1-3/+51
A malformed local entry was the one skip that said nothing, on the reasoning that local still ends up working. It does, but the merge rebuilds it from the bare base_url, so an api_key, filter or ctx_size set on that entry is dropped without a word. The app then comes up looking healthy, which is the strongest possible signal that nothing is wrong, making this the case that most needs saying, not least. Local surviving at all depends on DEFAULTS supplying base_url, since that is what the rebuild reads. Noted in config.py so the key is not removed as redundant, and pinned by a test. The not-a-table wording now follows what was written: a list gets the single-bracket fix by name, while a scalar entry does not, since telling someone to fix a [[...]] they never typed points at the wrong line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176tzAW6H1i2Kz8vm2XXVGV
2026-08-09fix: survive malformed provider config at startupDanilo M.1-0/+182
Eight config shapes, all valid TOML, crashed config.load() before any window existed, so the user got a traceback on a terminal a desktop launch does not have. Skip malformed providers instead, keeping the local path working, and say on stderr which one was dropped and why. The guards are isinstance tests rather than a try/except because the shapes raise three different exception types, and the local entry is checked the same way: [[providers.local]] is a truthy list that raised during the bare base_url merge, before the loop could skip it. A config that cannot load at all now reports the file and exits rather than half-starting on defaults the user never configured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176tzAW6H1i2Kz8vm2XXVGV
2026-08-09docs: stop crediting from None with secret hygieneDanilo M.1-7/+7
A TimeoutExpired chained without from None already prints no secret, since its __str__ shows only the command and the duration. The suppressed display buys a tidier traceback, not a narrower leak, and the comments claimed otherwise. The from None calls stay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09fix: sharpen key resolution errors, rename KeyResolutionErrorDanilo M.1-29/+169
Raise KEY_TIMEOUT to 120s: a graphical pinentry plus a hardware token the user has to find and touch makes 30s a plausible successful unlock, and failing one converts a slow success into an error the user can only retry against the same clock. Point the timeout message at a pinentry that never appeared, which is the silent failure, rather than at one the user is already looking at. Name the binary when pass is missing instead of reporting a bare Errno 2. Raise from None so a partial secret on the failed process stdout stays out of printed tracebacks, and record on the cache that it is unlocked only because every caller resolves on the GUI thread today. Rename KeyError_ to KeyResolutionError before later tasks import it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09fix: skip unusable provider names, name the split ceilingDanilo M.1-1/+65
A provider name containing a colon builds ids that split back to a different provider, and an empty name builds ':model'; both routed to the local router under a nonsense name with no error, so parse() now skips them. split() stays non-injective by design, since bare local ids are the premise here, so the residual risk is recorded as a ponytail comment with its upgrade path rather than hidden. Also pin the trailing-colon form Task 9 relies on, and the empty-needle filter, which were correct but untested. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09fix: stop a partial [providers.local] from deleting the local providerDanilo M.1-5/+35
The synthesis guard tested key membership, so a [providers.local] that set only an api_key claimed the slot, blocked the bare base_url from filling it, then failed the URL check and vanished. Losing the local provider is the one outcome this feature cannot have. The guard now tests the URL and merges, so the table adds detail to the local provider rather than replacing it. A filter given as a bare string was iterated character-wise, turning filter = "qwen" into four needles that match almost every model id. Both failures were silent, which is what made them worth fixing now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09docs: implementation plan for external cloud providersDanilo M.1-0/+2521
Sixteen TDD tasks. Providers, model-id namespacing and key resolution go in a new providers.py; metadata and cost math in models.py; the settings dialog in modeldialog.py, keeping ui.py from growing further. The last task is manual: it needs a real key and spends real money, and it is where cloud tool-calling behaviour gets found out rather than guessed at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09docs: name the local provider "local", not "default"Danilo M.1-13/+13
The name is behaviour, not config: it decides which provider a bare base_url synthesizes, which one renders without a prefix, which one skips filtering, and which one is exempt from the model dialog. All four live in code, so the name had to be picked before implementation. "default" was doing double duty as "the fallback" and "the local one", and only the second is true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09docs: spec for external cloud providersDanilo M.1-0/+275
Design for talking to OpenAI-compatible cloud providers alongside the local router. Providers are config table entries, local becomes the "default" one, and a bare base_url still synthesizes it so existing configs keep working. Cloud models carry no presets.ini entry and cost money, so missing metadata (context size, vision, prices) is entered through a per-model dialog stored in models.ini, and an approximate per-conversation cost sits next to the context meter. Token counts and the producing model move onto the messages table so cost survives reopening a chat and prices each reply at whatever produced it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31docs: correct the follow-up tool call claimDanilo M.1-1/+11
The comments and README stated flatly that a second tool call always comes back as literal <tool_call> XML. On llama.cpp b10208 that is no longer true: forcing a follow-up by starving the first search gave 1/5 parsed as a structured call, 1/5 leaking XML, 3/5 answered without searching again. Earlier builds never parsed it. Still not reliable enough to raise max_searches, since the failure mode is a blank reply, but the wording should not read as permanent when it was measured once against one build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31fix: cap searches at one per turnDanilo M.1-0/+16
A second tool call in the same turn, issued after the model has seen the first set of results, does not arrive as a tool_calls delta. It comes back as literal <tool_call><function=web_search> text inside reasoning_content, with finish_reason stop, no tool_calls and empty content. There is no structured call for the loop to act on, so the turn ends and the user is shown a blank reply. Reproduced 3/3 with Qwen3.5-9B through the router; the first call of a turn parses correctly every time, so this is specific to a call that follows a tool result. A retry with the tool withdrawn was tried and rejected: it produced an answer only some of the time and could surface the raw XML in the reply, which is worse than the blank it replaces. Capping at one search avoids reaching the broken state at all. Empty replies drop from 3/3 to about 1/3 and the XML no longer leaks, but the failure is not eliminated. Raise max_searches once the chat template parses follow-up calls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31feat: web search via SearXNGDanilo M.1-1/+1
The model is offered a web_search tool and decides when a question needs current information. llamachat runs the query against SearXNG's JSON API, feeds the results back, and the model answers from them. Off unless both search_enabled and search_url are set, so an upgrade never starts talking to the network on its own. The loop lives in backend.stream_chat: a round ending in tool_calls is searched for, the result appended, and the request re-sent. After max_searches rounds the tool is withdrawn, which forces an answer rather than letting an uncertain model search forever. Searches show as a collapsible block above the thinking block, listing each query and its sources. The queries are visible on purpose: when an answer is wrong it is usually the query that was wrong, and without seeing it a bad search and a bad answer look identical. Two failures found while testing against the live stack shaped the design: The model has no clock, so it falls back on its training cutoff and writes that year into the query itself ("latest kernel ... 2025"), poisoning the results before they are fetched. The current date now goes into the system prompt whenever search is on, with an instruction not to date its own queries. A SearXNG whose engines are all rate-limited or CAPTCHA'd returns a valid response with zero results. Reporting that as "no results" tells the model the web is empty and invites a confident answer from stale training data, so a search where every engine failed is now an error naming the engines. Results are attacker-influenced text entering the model's context. Only title, url and the snippet survive, snippets are truncated, result text is escaped on display, result links are never fetched automatically, and a reply forging the search block's URL scheme has it defused as the reasoning scheme already was. None of that stops a poisoned snippet from influencing the answer, which is why the sources stay visible. Adds eight test groups, 23 to 31, all hermetic behind a fake HTTP layer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31docs: design for web search via SearXNGDanilo M.1-0/+243
Model-decided tool calling against a SearXNG instance, with the loop inside stream_chat, results shown as a collapsible block, and a cap of two searches per turn. Verified against the live stack before writing: Qwen3.5-9B through the router emits well-formed tool_calls and answers correctly from a role: "tool" result. No llama-server flags are involved; the shipped --tools option runs server-side tools and none of them search the web. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>