Releases: cortexkit/opencode-magic-context
v0.15.7
What's Fixed
-
Tagger no longer cascades into repeated cache busts when its internal counter drifts behind the tags table's actual max tag number. Previously, once divergence occurred (from earlier outer-transaction rollback, multi-process race, or non-monotonic counter upsert), every transform pass would propose an already-claimed tag number, hit a UNIQUE collision, abort tagging, and skip the persisted-drop / reasoning-clear / caveman-compression replay — resurrecting tens of thousands of stripped tokens and forcing a full provider-cache miss on every affected turn. The tagger now reads the live DB max on every allocation and walks past collisions automatically, so a brand-new pass on a divergent session self-heals in milliseconds without any user intervention.
-
Counter upsert is now monotonic. Concurrent writers (multiple OpenCode instances, or sessions in different processes) can no longer move a session's counter backward, so a stale low writer cannot undo state from a higher writer that already advanced past it.
-
Existing divergent sessions heal automatically on first startup after upgrading. A one-shot migration brings every session whose
MAX(tag_number) > counterback into sync. Cheap, idempotent, and runs once — fresh DBs and already-healthy sessions are unaffected. -
One tagging failure no longer rolls back the whole transform pass. Per-call atomicity is preserved through SAVEPOINTs while the outer wrapper is removed, so a single isolated collision can no longer leave the pass with empty tag targets and skipped drop/reasoning replay.
Upgrade
bunx --bun @cortexkit/opencode-magic-context@latest doctor --forceAfter upgrading to v0.15.7, divergent sessions self-heal on next plugin load. If you were seeing repeated cache busts after long idle gaps or multi-process activity on the same project, this release closes that path.
Full Changelog: v0.15.6...v0.15.7
v0.15.6
What's Fixed
- Note reminders re-surface after work boundaries when the agent's prior
ctx_note(read)result has dropped out of context (was reduced, aged out, or compartmentalized). Previously, once an agent read its notes, future reminders were suppressed until note content changed — even if the read tool call was no longer visible to the agent. Now, work-boundary triggers (commit detection, historian completion, todo completion) re-surface the same notes when the agent has lost visibility into them. - Installer always picks the latest plugin version. The
curl | bashand PowerShell installers now explicitly pin@cortexkit/opencode-magic-context@latestso cached older versions are bypassed cleanly.
What's New
- Auto-update: Magic Context now detects newer npm releases on session start and self-updates the cached install, since OpenCode no longer auto-resolves
@latestplugin entries on each launch. Updates apply on the next OpenCode restart. Theauto_updateflag (defaulttrue) is USER-only — project configs cannot disable plugin self-updates. Pinned versions (@0.15.5) are detected and skip auto-update with a notification instead. doctor --issueredacts secrets before bundling logs into bug reports. The sanitizer recognizes 12 secret-token shapes including Anthropic, OpenAI, GitHub PATs, HuggingFace, AWS access keys, Slack, Google API keys, JWTs, bearer tokens, and generic env-var assignments where the variable name suggests a credential.
Upgrade
bunx --bun @cortexkit/opencode-magic-context@latest doctor --forceAfter upgrading to v0.15.6, future plugin updates install automatically — no more manual doctor --force runs to clear stale caches.
Full Changelog: v0.15.5...v0.15.6
v0.15.5
🚨 Critical Fix
v0.15.4 broke the curl-piped installer and any plain-bunx CLI invocation. This patch restores both.
What was broken in v0.15.4
The CLI bundle imported bun:sqlite at module top (used by the historian-failure section of the diagnostics report). Node's ESM loader rejects bun: specifiers during module resolution — before any user code runs — so the entire CLI crashed the moment it was launched under Node:
Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in:
file, data, and node are supported by the default ESM loader.
Received protocol 'bun:'
This affected:
curl ... install.sh | bash— the piped installer routes throughbunx <pkg> setup(delegates to Node by design, because@clack/promptsinteractive selects don't work reliably under Bun on/dev/tty).- Plain
bunx <pkg> setup/bunx <pkg> doctor/bunx <pkg> doctor --force/bunx <pkg> doctor --issue— same Node delegation path.
The runtime plugin loaded by OpenCode itself was unaffected (it's built --target bun and OpenCode loads it under Bun).
Fix
bun:sqlite is now loaded lazily via a runtime-gated dynamic import inside collectHistorianFailures():
- Under Bun: the import succeeds and historian failures are read normally.
- Under Node: the import is skipped entirely. The historian-failure section of
doctor --issuereports as empty; every other diagnostic (config, plugin cache, conflicts, log tail, dump metadata) builds normally.
Upgrading
If you're on v0.15.4 and bunx ... setup or bunx ... doctor is failing for you, force the Bun runtime once to clear the broken cache:
bunx --bun @cortexkit/opencode-magic-context@latest doctor --forceAfter v0.15.5, both bunx <pkg> (Node) and bunx --bun <pkg> (Bun) work for all CLI commands.
Full Changelog: v0.15.4...v0.15.5
v0.15.4
v0.15.4
Three correctness fixes plus a sidebar/status alignment that resolves a long-standing UX mismatch.
🎯 Sidebar token breakdown now matches /ctx-status
The TUI sidebar's token bar and /ctx-status's "History block" line have been showing different numbers for the same content — sometimes off by 5K+ tokens. Two structural fixes land in this release:
Per-model calibration for tokenizer drift. ai-tokenizer's claude / o200k_base / cl100k_base / p50k_base encodings approximate provider tokenizers but drift by model-specific amounts — Anthropic Opus 4.7 most aggressively (1.51× system, 1.57× tools), GitHub Copilot and OpenRouter routing the same models with the same drift. The sidebar now scales System and Tool Definitions through empirically measured ratios from scripts/calibrate-tokenizer/, so those two buckets match billed tokens within ~5%.
Verbatim history buckets. Compartments, Facts, and Memories now display local raw counts unscaled — the same numbers /ctx-status reports as "History block". The sidebar and status dialog now match exactly. Conversation and Tool Calls absorb the remaining provider-reported drift proportionally, which is the most honest mapping for buckets containing mixed user/assistant text and tool I/O.
The sum of all categories equals provider-reported inputTokens exactly — verified across pathological inputs including clamp-path edge cases.
🧠 Three single-purpose signal sets replace flushedSessions
The old flushedSessions set was overloaded: producers used it to mean "history needs refresh", "system prompt needs refresh", and "pending ops need to materialize" all at once, and consumers couldn't tell which intent applied. This caused two real bugs:
- After a historian publication busted cache once, the flag stayed set, so the next defer pass would gratuitously rebuild
<session-history>again with no DB change in between — one wasted cache-busting cycle per historian run. - A
/ctx-flushissued during an active historian run could lose its pending materialization signal because the same flag was being drained for unrelated reasons.
Split into three independent session-scoped sets in live-session-state.ts:
historyRefreshSessions— drained immediately whenprepareCompartmentInjection()consumes itsystemPromptRefreshSessions— drained at handler entry only when the handler actually consumed a refresh signalpendingMaterializationSessions— survives blocked passes; only drains after heuristics actually run
isCacheBustingPass was redefined to mean "this pass mutated state" rather than "this pass had queued intent" — closing the cascade and making the cache-busting discipline explicit at every consumer.
💾 memory.enabled and memory.auto_promote now honored
Two memory config flags existed but had no effect on automatic promotion. Historian and recomp would still promote session facts to project memories regardless of either setting. Reported in #44.
memoryEnabled and autoPromote are now threaded through both runners; promotion is gated on memoryEnabled !== false && autoPromote !== false. Manual ctx_memory write is unaffected — it still works when memory.enabled: false, intentionally, because tools that the agent invokes explicitly should respect the agent's intent. When memory.enabled: false, the ctx_memory tool itself is hidden from the agent's tool list as before.
🔍 Embedding endpoint failures now actionable
embedding-openai.ts was reporting Unexpected end of JSON input SyntaxErrors when openai-compatible endpoints returned empty bodies, HTML error pages, or non-JSON responses. The root cause was almost always endpoint config (wrong path, auth failure, wrong model name) — not actually a JSON parse problem.
The reader now loads the response as text first, then attempts to parse. Empty bodies produce a typed EmptyBody failure, non-JSON bodies produce a typed NonJsonBody failure with a snippet of the actual response, and both are recorded as circuit failures with informative log messages. Single transient blips don't trip the breaker — it still requires 3 failures within a 60s window.
🪟 Dashboard cache page: per-session windowing
Reported separately. The cache events query was using a single global LIMIT, so when many sessions were active concurrently, each one's visible bar count would shrink. Replaced with ROW_NUMBER() OVER (PARTITION BY session_id) so each session can show up to 200 cache events independently. Frontend windowing matches.
The dashboard's Dreamer config card was also rendering as a malformed nested-grid; restructured into two clean stacked 2-col rows. (Both ship in the next dashboard release.)
🧪 Verification
- 973 plugin tests, 23 e2e tests, lint, typecheck, build — all green
- Plus regression tests for the three-set signal model, calibration sum invariants (including pathological tiny inputs), longest-prefix matching across OpenRouter / GitHub Copilot routes, and
memory.enabled=falsepromotion gating
🔧 How to upgrade
```bash
bunx --bun @cortexkit/opencode-magic-context@latest doctor --force
```
Restart OpenCode afterward.
Dashboard dashboard-v0.3.4
🐛 Fix
Model dropdowns no longer empty for users with stock OpenCode installs
The "Add fallback model" dropdown for historian, dreamer, and sidekick was rendering "No models found" for users whose opencode binary lives at ~/.opencode/bin/opencode (the path the official OpenCode installer writes to).
Root cause: GUI processes on macOS don't inherit shell $PATH, so the dashboard tries an explicit list of candidate binary locations when running opencode models. The previous candidate list missed the most important location — ~/.opencode/bin/opencode — so the lookup failed silently and models() returned an empty array, which made every model dropdown render its empty-state.
This bug masqueraded as the same fallback_models filter problem fixed in v0.3.3. The v0.3.3 fix (string-vs-array normalization) addressed one of two distinct empty-dropdown causes; this release fixes the deeper one.
~/.opencode/bin/opencode is now the first candidate on macOS and Linux, with %USERPROFILE%\.opencode\bin\opencode.exe as the first Windows candidate. The shell-PATH opencode fallback is preserved for users who launch OpenCode from a terminal.
After upgrade, the existing availableModels localStorage cache (empty [] from a prior failed lookup) is overwritten by the next refresh on app startup — no manual cache clear required.
📦 Pairs with plugin v0.15.4
This dashboard release is tested against plugin v0.15.4. Upgrade both for the full experience:
bunx --bun @cortexkit/opencode-magic-context@latest doctor --forceUpgrade
Auto-update is enabled — existing installations will prompt within 24 hours. You can also trigger it immediately via Check for Updates... in the app menu (macOS) or the tray icon.
Manual install: download from the release assets below, or fetch from https://cortexkit.github.io/opencode-magic-context/latest.json.
Full Changelog: dashboard-v0.3.3...dashboard-v0.3.4
Dashboard dashboard-v0.3.3
🐛 Fixes
Fallback model dropdown said "No models found" when fallback_models was a string (Config editor)
The plugin's AgentOverrideConfigSchema accepts fallback_models as either a single string or an array of strings — both shapes are valid plugin config:
// All three are valid
{ "historian": { "fallback_models": "openai/gpt-5" } }
{ "historian": { "fallback_models": ["openai/gpt-5", "anthropic/claude-opus-4-7"] } }
{ "historian": {} } // omitted — uses built-in chainThe dashboard cast every read as string[], which silently ran String.prototype.includes(m) against every available model when filtering the "Add fallback" dropdown. Substring matching is aggressive — a stored value of "openai/gpt-5" would match (and exclude from the dropdown) any model containing that prefix, including openai/gpt-5-codex, openai/gpt-5-mini, etc. Result: empty dropdown with the user-reported "No models found" message and no way to add a fallback.
The chip list also iterated the string per-character, rendering single letters as separate "fallback" entries.
Both bugs are now fixed across all three sections (historian, dreamer, sidekick) via a shared readFallbackModels() helper that normalizes both shapes to a real array.
Dreamer card padding repaired (Config editor)
The Dreamer card stacks two .config-card-two-col rows — Enabled/Schedule/Inject Docs above User Memories/Key File Pinning. Both rows had column gaps but no margin between the rows themselves, so Inject Docs flowed straight into User Memories with no breathing room. Added .config-card-two-col + .config-card-two-col { margin-top: 24px; } so stacked rows space out cleanly without affecting any other card.
Tags & Cleanup paired with Sidekick (Config editor)
The previous layout left Sidekick alone on a row with empty space to its right, then Experimental full-width, then Tags & Cleanup also alone with empty space to its right. Reordered so Sidekick + Tags & Cleanup pair on one row (both compact cards) and Experimental sits full-width as the very last card on the page.
📦 Pairs with plugin v0.15.4
This dashboard release is tested against plugin v0.15.4. Upgrade both for the full experience:
bunx --bun @cortexkit/opencode-magic-context@latest doctor --forceUpgrade
Auto-update is enabled — existing installations will prompt within 24 hours. You can also trigger it immediately via Check for Updates... in the app menu (macOS) or the tray icon.
Manual install: download from the release assets below, or fetch from https://cortexkit.github.io/opencode-magic-context/latest.json.
Full Changelog: dashboard-v0.3.2...dashboard-v0.3.3
Dashboard dashboard-v0.3.2
✨ Highlights
Experimental section in the Config editor
The dashboard had no surface for experimental.* settings, forcing users into the raw JSONC editor to enable any of them. Added a dedicated Experimental card with master toggles for all four feature flags:
- Temporal Awareness — inject elapsed-time markers between user messages and date ranges on compartments.
- Git Commit Indexing — index
HEADnon-merge commits as a 4thctx_searchsource. Sub-controls for history window (days) and max commits fold out when enabled. - Auto Search Hint — append a compact
<ctx-search-hint>block to new user messages whenctx_searchfinds highly-related context. Sub-controls for score threshold and minimum prompt length fold out when enabled. - Caveman Text Compression — age-tiered compression for long text parts. Includes an inline warning when both this and
ctx_reduce_enabledare on, since the runtime gates this behindctx_reduce_enabled: falseand would otherwise silently no-op.
Numeric inputs clamp to schema bounds so invalid values can't be saved through the form path. Raw JSONC editing remains available via the existing escape hatch.
🐛 Fixes
Cache page bars stay full with multiple active sessions. The cache events query was using a single global LIMIT, so when many sessions were running concurrently, each one's visible bar count would shrink as another session pushed older events out of the window. Replaced with ROW_NUMBER() OVER (PARTITION BY session_id) so each session can show up to 200 cache events independently. Also reduced the underlying N+1 query that ran per session on page open down to a single GROUP BY aggregate.
Dreamer config card layout repaired. The Dreamer card was rendering as a malformed nested-grid (a config-card-two-col inside another two-column grid). Restructured into two clean stacked 2-col rows: one for Enabled/Schedule/Inject Docs versus Model/Fallbacks, and one for User Memories versus Key File Pinning.
📦 Pairs with plugin v0.15.4
This dashboard release is tested against plugin v0.15.4. Upgrade both for the full experience:
```bash
bunx --bun @cortexkit/opencode-magic-context@latest doctor --force
```
Upgrade
Auto-update is enabled — existing installations will prompt within 24 hours. You can also trigger it immediately via Check for Updates... in the app menu (macOS) or the tray icon.
Manual install: download from the release assets below, or fetch from `https://cortexkit.github.io/opencode-magic-context/latest.json\`.
Full Changelog: dashboard-v0.3.1...dashboard-v0.3.2
v0.15.3
v0.15.3
Six bug fixes since v0.15.2 — five subagent-correctness repairs from a focused Oracle review, plus a plugin-conflict false-positive fix from issue #43.
🧵 Subagent classification race
Magic Context routes work very differently for primary sessions and subagents. Primary sessions get the full transform stack — historian, compartments, memories, key files, project docs, nudges. Subagents get only the reduced path — heuristic cleanup at the execute threshold.
But OpenCode's Session.create() returns the new session ID before the session.created event fires through to plugins:
// session/session.ts:456
yield* Effect.sync(() => SyncEvent.run(Event.Created, { sessionID, info }))
// sync/index.ts:160
void publish(result) // ← fire-and-forget, returns to caller firstThe API caller can immediately prompt() against the new session. On that prompt's first transform pass, getOrCreateSessionMeta() creates a fresh row with default is_subagent=0 — misclassifying child sessions as primary for one pass, only flipping to the correct value when session.created is finally processed.
The race wasn't catastrophic — the next pass fixed it — but it had real downstream costs:
- Cache invalidation. Stripping behavior, prompt adjuncts, and nudge gates that flip
subagent-state-aware rules between pass 1 and pass 2 force a fresh provider cache key on the very next turn. - Stale state writes. Sticky reminders, note nudges, and overflow-recovery flags written under primary semantics during pass 1 then replay forever after the session is reclassified as a subagent.
Fix
When getOrCreateSessionMeta() creates a new session_meta row, it now peeks OpenCode's session table (already populated synchronously by Session.create()) and reads parent_id. Non-empty parent_id → set is_subagent=1 immediately. Subsequent reads hit the cached value with no extra DB cost.
Existing rows are never re-checked, so the fallback is one extra read-only query per fresh session at most.
🚪 Primary-only paths now properly gated
Even after the race fix, several paths still ran for subagents that shouldn't have:
- System prompt adjuncts (
<project-docs>,<user-profile>,<key-files>) were being injected for subagent sessions. Subagents have bounded lifetimes, run focused tasks, and don't need persistent project context — these adjuncts now correctly skip subagent runs. - Note-nudge triggers (
commit_detected,todos_complete) fired regardless of session type, accumulating orphan trigger state in subagent sessions that have no path to display nudges. - Sticky reminder replay ran for all sessions in
transform-postprocess-phase.ts. The producer correctly gated on!isSubagent, but the replay didn't — so any sticky reminder written before correct classification (the race window above) would replay forever in a subagent.
All three paths now consistently check fullFeatureMode / !isSubagentSession.
🧮 Detected context limits no longer thrown away for subagents
When a provider returns a context-overflow error, Magic Context parses the reported real limit and persists it to session_meta.detected_context_limit. Future pressure math then uses that limit instead of stale models.dev data — critical for self-correcting against models with bad upstream metadata.
v0.15.1 introduced overflow handling for subagents but skipped recordOverflowDetected() entirely, throwing away the detected limit alongside the (correctly-skipped) emergency-recovery flag. Subagents that overflow could re-overflow on the next try because pressure math kept using the wrong baseline.
recordOverflowDetected() now splits into two operations:
recordDetectedContextLimit()— useful for both primary and subagent sessionsrecordEmergencyRecovery()— primary-only
Subagents now get accurate per-session limit tracking without the unused recovery path.
🔌 Plugin conflict detection: substring → canonical match
Reported in #43. The previous matchers were substring-based:
plugins.some((p) => p.includes("oh-my-opencode"))
plugins.some((p) => p.includes("opencode-dcp"))This misclassified forks like oh-my-opencode-slim and any local file:// path containing those substrings. Once a fork was "detected" as OMO, checkOmoHooks defaulted all three conflicting hooks to active (forks have no OMO config), so Magic Context disabled itself with a false-positive warning telling the user to remove a plugin that doesn't actually conflict.
Confirmed by cloning oh-my-opencode-slim: it ships none of preemptive-compaction, context-window-monitor, or anthropic-context-window-limit-recovery — its hooks are unrelated task-tracking helpers.
Fix
Replaced substring matchers with matchesPackageName():
- Strips version suffixes (
@latest,@^3.1.0,@3.17.5) - Skips
file://,http(s)://, and//.//../paths entirely — they're never canonical npm specifiers - Compares against explicit canonical-name sets (
DCP_PACKAGE_NAMES,OMO_PACKAGE_NAMES)
Also dropped the legacy @code-yeongyu/ scope — no longer published; both oh-my-opencode and oh-my-openagent are unscoped on npm.
16 regression tests cover the exact failure modes: slim with and without version suffix, file:// paths, mixed canonical+fork installs, and DCP forks.
📊 Verification
- 938 plugin tests, 23 e2e tests, lint, typecheck — all green
- Plus 9 new regression tests for the subagent-correctness work and 16 for the conflict-detector fix
🔧 How to upgrade
bunx --bun @cortexkit/opencode-magic-context@latest doctor --force--force ensures OpenCode picks up the latest cached package.
v0.15.2
v0.15.2
Five focused fixes since v0.15.1 — a malformed-tag-prefix repair, memory hardening, and audit-driven polish.
🧹 Malformed §N">§ tag prefixes now self-heal
After experimental.temporal_awareness shipped in v0.13, some models started emitting garbled tag prefixes at the start of their own assistant text:
§15298">§15298§ Confirmed — every OpenCode restart…
§15434">§ Type mismatch: m.file.as_path() returns ...
Root cause was token-level confusion: adding start-date and end-date attributes roughly doubled the density of quoted-number patterns ("N", "N">) near the model's own §N§ tag emissions, and greedy decoding occasionally drifted from the tag-close path to the compartment-attribute-close path.
Without repair the pattern compounded — stripTagPrefix required § immediately after digits and couldn't match the malformed shape, so every future transform pass stacked a fresh §newN§ in front, reinforcing the bad pattern in-context.
Three-layer fix:
- Repair:
stripTagPrefixnow recognizes and strips both the§N">§N§repeat and the§N">§stub variants before prepending a fresh tag. Messages self-heal on the next transform pass. - Root cause: the compartment-attribute example in prompt text changed from
<compartment start=N end=M>to<compartment start="N" end="M">— matches rendered data and removes quoted-vs-unquoted ambiguity. - Dead guidance removal: when
ctx_reduce_enabled: false, the plugin already skips§N§prefix injection entirely. The stale "Messages and tool outputs are tagged with §N§" sentence is now dropped from the no-reduce prompt, so we stop describing a system the agent can't observe (and stop priming it to emit malformed variants for no reason).
23 regression tests lock in both well-formed and malformed strip paths.
🧠 Module-level session caches now bounded
injectionCache (compartment injection block per session) and messageTokensBySession (per-message token counts) were module-scope Maps cleared only on session.deleted. Sessions that never get explicitly deleted — crashed OpenCode processes, force-quit, archived sessions — leaked entries indefinitely. Each PreparedCompartmentInjection holds tens of KB of rendered <session-history> XML, so long-running OpenCode instances accumulated non-trivial memory.
Both now use a shared BoundedSessionMap LRU capped at 100 sessions. Evicted entries recompute lazily on the next cache-busting pass from SQLite state — no correctness impact.
🛡️ Compaction marker now validates OpenCode schema before writing
compaction-marker.ts writes rows directly into OpenCode's opencode.db to place the filterCompacted boundary. OpenCode uses Drizzle migrations and has shipped several breaking schema updates in recent months — any future column rename or drop would make our hardcoded INSERTs fail at runtime, potentially leaving half-written marker state.
Added a startup column probe that runs once per plugin lifetime and caches its result. On schema mismatch, marker injection is disabled for the rest of the process with a clear log line pointing at a plugin update. Zero hot-path cost after the first call.
Also pushed the summary-message filter in findBoundaryUserMessage into SQL via json_extract — avoids an O(N) JSON.parse loop on historian publication for large sessions.
⚡ NULL-column healing moved to one-shot migration
Previous releases ran healNullTextColumns / healNullIntegerColumns / healMissingMemoryBlockIds on every plugin startup — ~25 no-op UPDATE statements per launch, each acquiring a write lock for zero rows on DBs that had already been healed.
Now gated behind migration v5: runs once per DB during the v4 → v5 upgrade and never again. Startup path stops touching session_meta at all on healthy DBs.
Also removed confirmed dead code: CREATE TABLE session_notes / smart_notes in initializeDatabase() (both tables are merged into unified notes by migration v1 on first boot), and the stale idx_session_notes_session index.
🔍 Minor audit cleanups
- Dreamer success gate:
hasSuccessfulTasknow excludesuser memories,key files, andsmart-notespost-task phases from the success check. A project whose configured tasks all failed but whose post-task phases succeeded would havelast_dream_atadvanced, silently suppressing re-scheduling of the failed tasks. - Reused session meta read:
system-prompt-hash.tsno longer does twogetOrCreateSessionMetacalls per system-prompt transform when nothing between them mutates the row. - Single project identity resolution:
resolveProjectIdentityis now computed once per transform pass instead of twice.
Upgrade
bunx --bun @cortexkit/opencode-magic-context@latest doctor --forceRestart OpenCode afterward. Sessions with accumulated malformed tag prefixes will self-heal on the next transform pass.
v0.15.1
v0.15.1
Two focused patches — a Windows upgrade blocker and a sidebar/status UX refinement.
🪟 Windows doctor --force now actually clears the cache
doctor --force was targeting %LOCALAPPDATA%\opencode on Windows, but OpenCode actually stores its plugin cache at <homedir>/.cache/opencode on every platform (via xdg-basedir). Windows users ran doctor --force, saw "cache not found", and the real cache at C:\Users\<user>\.cache\opencode\packages\@cortexkit\opencode-magic-context@latest\ stayed untouched — forcing them to manually clear it to pick up new plugin versions.
Now all cache paths route through shared helpers that match OpenCode's resolution exactly. Regression test locks Windows behavior so this can't silently regress.
bunx --bun @cortexkit/opencode-magic-context@latest doctor --force🎨 Sidebar: measured tool definitions + warm palette
The sidebar's "Tool Defs + Overhead" bucket was blind residual math — one grey bar combining two unrelated things. It's now split:
- Tool Definitions (pink): real measurement of tool schema cost via the
tool.definitionplugin hook. Tokenizes each tool's description + JSON-schema parameters with the same Claude tokenizer used elsewhere. Per-{provider, model, agent}store with idempotent replacement per flight. - Overhead (grey): the residual — whatever's left after system, conversation, tool calls, and measured tool definitions. This is where tool-definition measurement would show up before the first turn's measurement lands.
The three previously-grey user-facing buckets now have distinct warm colors:
| Bucket | Color |
|---|---|
| Conversation | red |
| Tool Calls | orange |
| Tool Definitions | pink |
| Overhead | grey |
Structured injected buckets (System, Compartments, Facts, Memories) keep their cool palette, so the sidebar now reads clearly: cool = plugin-managed injection, warm = user/tool traffic, grey = residual.
Upgrade
bunx --bun @cortexkit/opencode-magic-context@latest doctor --forceRestart OpenCode afterward.