* refactor(go): adopt modern stdlib idioms across kernel and desktop
Mechanical sweep with golangci-lint's modernize/intrange/copyloopvar/
usestdlibvars fixers plus hand-fixes for what they could not rewrite:
slices.Contains, min/max, slices.Backward, WaitGroup.Go, reflect.TypeFor,
maps.Copy, strings.Cut/CutPrefix/SplitSeq, strings.Builder, range-over-int.
Net -549 lines with no behavior change.
Three loops in session.go stay index-based: slices.Backward yields element
copies, so the fixer's rewrite silently dropped writes to DecisionReceipts
and ToolCalls (caught by TestDelivery* and TestDependentSameBatch*). All 49
slices.Backward sites were audited for that shape; only these three write
through the index, and they carry a nolint with the reason.
CaptureAfter's scan loop had an empty body and never ran any logic, so the
fixer could not express it. Drop it and the accessor it was the only caller
of. omitzero is deliberately untouched: turning omitempty into omitzero
changes the JSON written for nested structs, which is a wire-format change
to session files and the extension protocol.
* build(lint): gate the modern-idiom linters
Nothing stopped the codebase from drifting back to pre-1.22 idioms. Both
modules are now clean for modernize, intrange, copyloopvar, and
usestdlibvars, so enable them rather than leaving the sweep as a one-off.
omitzero stays disabled with the reason inline; usetesting, perfsprint, and
errorlint still have residue and land separately.
312 banner separators across 63 files, the thing REASONIX.md bans outright and
nothing has ever checked. 64 were pure rules with no content and are gone; the
other 248 carried a label inside the dashes, so the decoration is stripped and
the label kept -- `// --- session/resume ---` becomes `// session/resume`, which
still orients a reader in a 1000-line test file without drawing a line across it.
The repolint ceiling for this rule drops 312 -> 0, so it is now zero-tolerance
rather than merely frozen.
* feat(desktop): add workspace status bar items
* Fix DeepSeek billing source breakdown
* Address billing source review feedback
* fix(desktop): reset telemetry and context panel after /clear
Fixes#4186
Sub-bug (a): /clear sometimes fails to clear context (stale events
re-populate state after reset). Fix: guard usage handler with
turnActive check so stale events from the previous session are dropped
after the reset action zeros the state.
Sub-bug (b): token info and workspace context not refreshed after
/clear. Three causes:
1. Tab telemetry not reset — ClearSession() resets the executor session
but tab.usageTelemetry/readTelemetry accumulate forever. Fix: add
resetTelemetry() and call it from App.ClearSession().
2. ContextPanel not refreshed — dockRefreshKey only bumps on turn_done,
so the panel keeps showing stale data. Fix: bump dockRefreshKey in
confirmClearContext.
3. Stale info fallback — ContextPanel line 212 falls back to
info.usedTokens when context.used is 0, which is stale. Fix: clear
info state when context.used and sessionTokens are both 0.
* Fix clear-session context refresh races
Discard stale ContextPanel responses after clear-session refreshes and let ClearSession failures surface to the caller before showing success.
Co-authored-by: ashishexee <[email protected]>
Co-authored-by: lizhengwu <[email protected]>
* Address clear-context telemetry review
* fix(config): localize DeepSeek official pricing defaults
* fix: default built-in pricing to CNY
* Reset official pricing defaults once on desktop upgrade
* Update CLI migration test for config version 3
---------
Co-authored-by: lizhengwu <[email protected]>
Co-authored-by: ashishexee <[email protected]>
Co-authored-by: Codex <[email protected]>
Hashes the system/tools/combined prompt prefix each turn and attributes a cache miss to system drift, tools drift, log-rewrite (compaction), or an inherent tail miss; surfaces the reason on the usage line and through the serve + desktop wire. Rebased onto main-v2: kept the mutex-safe Session.Replace and atomic session cache counters, threaded RewriteVersion/IncrementRewrite alongside them, and adapted the rewrite-diagnostics test to the current 3-arg compact signature.
Co-authored-by: lab1 <[email protected]>
* fix(provider): pair tool results by position when ids are absent or duplicated
Some OpenAI-compatible gateways (vLLM, llama.cpp) stream tool calls by index
with no id, so a multi-tool turn stores every result under the same empty
tool_call_id. SanitizeToolPairing keyed those into a map, collapsing them to
one and silently dropping the rest from the model's context on the next send.
Pair by position when ids aren't distinct (the loop appends results in call
order), and backfill a synthetic call_<index> id in the openai stream so the
wire pairing stays valid. Well-formed histories still pair by id, so reordered
results re-sort and the output is byte-identical.
* fix(agent): extend the loop guard to repeated multi-call batches
The storm breaker only counted single-call fixations, so a model stuck
re-emitting the same all-failing multi-call batch never got nudged and could
burn the whole maxSteps budget bouncing off it. Key the signature on every
call's (tool, error) in order and fire when the whole batch repeats; any
success or blocked call still resets the counter, so genuine varied work is
untouched.
* fix(frontend): skip the partial tool dispatch in headless and ACP sinks
The agent emits an early Partial ToolDispatch (id+name, no args) the moment a
call starts streaming, then the full dispatch once args land. The chat TUI
already skips the partial, but the headless TextSink and the ACP adapter
rendered both — so piped/`reasonix run` output showed every call twice (the
first line with empty args) and ACP sent two pending tool_calls per call (the
first with empty rawInput, breaking its documented one-call-with-input
contract). Skip the partial in both, matching the TUI.
* fix(agent): stop auto-compaction re-firing every turn
A turn whose prompt crossed compactRatio triggered compaction, but the
recent tail was kept by message count (recentKeep), not token budget. A
few large tool results in that tail could exceed the trigger on their
own, so every following turn re-compacted and the prompt never dropped
below the threshold — the agent looped "summarize, continue, summarize".
Bound the kept tail by a fixed token budget, calibrated to the provider's
real usage rather than a local tokenizer, so compaction reclaims genuine
headroom and fires rarely; recentKeep becomes a message floor. When two
consecutive turns still compact, one turn cannot fit under the window:
latch and pause auto-compaction with an actionable notice instead of
looping. Source-side tool-output truncation already bounds single
messages, so this only touches the compaction side.
---------
Co-authored-by: reasonix <[email protected]>
The status line showed only the single-turn rate, which reads low on short
chats (a 2-turn session is ~73%) and confused users comparing it to the old
dashboard's 95%+. Those are different metrics: the old number was the session
AGGREGATE (Σhit/Σ(hit+miss)), the v2 number was the latest turn.
Add aggregate alongside single-turn: agent accumulates per-call hit/miss
(unaffected by compaction, since it never touches session.Messages), the
controller exposes SessionCache(), and it rides the Usage event through both
wire transports to the frontends. Status line now reads 'cache 88% · avg 78%'.
Measured truth: on DeepSeek's 1M window (compaction almost never fires) the
single-turn rate is the HIGHER one — the aggregate trails it, dragged by the
cold-start turns. The legacy 95%+ came from long, frequently-compacted sessions
where single turns crater and carryover keeps the aggregate high.
cachehit_e2e_test.go: a mock DeepSeek endpoint derives cache-hit tokens
from the byte-identical message prefix it shares with the prior request,
so the reported hit rate directly measures request-prefix stability.
Covers: prefix stays byte-stable across turns, the status-line hit% math,
the hit-rate climb on a long session, and the compaction-induced collapse.
realcache_test.go: env-gated (DEEPSEEK_API_KEY) live probe — repeated-prefix
hit rate, whether v4-flash emits reasoning_content, and the reasoning
round-trip cost. Skips when no key is set.