Skip to content

fix(core): keep Code Mode catalog updates small and stable - #62176

Merged
rekram1-node merged 7 commits into
devfrom
stable-catalog
Nov 8, 2026
Merged

rekram1-node merged 7 commits into
devfrom
stable-catalog

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Noisy updates. The catalog shared one 2k budget across all servers, filled cheapest-first. A server connecting or leaving reshuffled which tools the others showed, so most updates restated the whole catalog (~2.2k tokens each), and every restatement stays in history.
  • Wasted work. Picking the cheapest tools meant rendering every tool's signature on every rebuild.

Change

Same format and 2k cap as today, as small edits to the existing code.

  • Which tools show (catalog.ts): the namespaces still take turns, one tool per round, but in path order instead of cheapest first. Only the signatures it selects are rendered, plus the next one it tries; tool.ts makes signatures lazy for that. The stored catalog also keeps every tool's path, so an update can tell a new tool from one that just wasn't shown.
  • The prompt is unchanged: the complete version when every tool is listed, the partial one with search otherwise.
  • Updates (instructions.ts), capped at ~1k tokens while the catalog is partial, in the existing wording:
    • a new namespace, or one whose description changed: "New tools are available:" with its first tools that fit, plus a count;
    • new tools in a known namespace: "New tools are available in addition to those previously listed:" with their signatures, then "N more; search for them";
    • a signature the model was shown that changed, even if it's no longer shown: "Changed tool listings supersede the previously listed ones:";
    • removed tools and removed namespaces: the existing one-line notices;
    • when the catalog stops listing every tool, a one-time note that introduces search, instead of restating the catalog.
  • No update when a namespace shows more or fewer tools because another came or went, or when a tool is pinned or unpinned.
  • The full catalog is restated only around an empty catalog, or when that is shorter than the update.
The Code Mode tool catalog has changed.

New tools are available in addition to those previously listed:
  - tools.github.actions_cancel({ owner: string, repo: string, run_id: number }): Promise<unknown> // Cancel a workflow run

Changed tool listings supersede the previously listed ones:
  - tools.github.actions_get({ … }): Promise<unknown> // Get details about GitHub Actions resources

The following tools are no longer available and must not be called: tools.github.actions_list.

Results

Updates over a replayed session (GitHub, Playwright, Context7, Filesystem; a late server, the browser attaching and detaching twice, GitHub gaining a tool):

dev This PR
Starting catalog 2,218 tokens 2,322
Late server connects 2,235 (full restatement) 24
Browser attaches (×2) 2,254 (full restatement) 802
Browser detaches (×2) 2,235 (full restatement) 27
GitHub gains a tool 42 236
All updates 11,255 1,918 (−83%)

Rebuild time, only when tools change: 2–12 ms typical, ~70 ms with Cloudflare's 3,767 tools.

Evals, dev vs this PR (* beyond noise):

dev This PR
Unseen tools (codemode-novel, 480 trials): pass rate 0.97 0.98
Unseen tools: steps 4.43 3.34 *
Unseen tools: input tokens 34.7k 23.3k *
Unseen tools: seconds 11.4 6.3 *
Known tools (codemode-catalog, 256 trials): pass rate 0.86 0.84
Known tools: seconds 13.8 10.1 *
Known tools: input tokens 53.3k 57.1k

Real servers (codemode-real: GitHub's MCP server read-only, and Playwright on real pages, 11 tasks), plus the fully fake tracker suite. 9 models, 2 repetitions:

dev This PR
Real servers: pass rate, 8 models without gpt-5-nano 0.98 0.97
Real servers: steps 4.1 3.9
Real servers: input tokens 42.3k 41.3k
Fake tracker: pass rate 0.88 0.89
Fake tracker: steps 5.2 4.6

The one regression is gpt-5-nano on real servers (7 of 22 passed vs 2 of 22). It tried a plain web request instead of the tools, then gave up, once saying the GitHub tools had no issue lookup because the catalog showed only the first GitHub tools alphabetically. Stronger models searched.

Evals: anomalyco/evals#21. The older codemode-catalog suite mixed real tool lists with fake responses and was replaced by codemode-real and codemode-tracker.

Servers connecting mid-session (codemode-arrival: one small server, a first question, then a small and a ~100-tool server connect and the catalog stops listing every tool; 10 models, 480 trials each):

dev This PR
Pass rate 0.99 1.00
Steps 3.8 3.1
Input tokens 36.2k 24.5k
Seconds 6.8 5.2

On tasks whose tool was not listed, models searched 97% of the time after the one-time note and passed every time.

Known limits

  • In a group too big to show in full, the shown tools are the alphabetically first ones, not the most useful. search covers the rest.
  • Sessions from before the upgrade restate the catalog once.
  • Weak models may conclude a tool doesn't exist instead of searching for it (see gpt-5-nano above).

Verification

  • packages/core: bun typecheck, bun run typecheck:tsc, and the full suite. On this machine 29 shell, plugin and image tests time out on dev too, plus the known isolates global home and XDG roots failure.
  • Root bun run check.
  • Existing catalog and instruction tests edited in place, with new cases for path order, rendering only what is selected, each update shape, the 1k cap, no update when only the shown tools move, and a changed signature on a tool that is no longer shown.
  • One end-to-end scenario: a new namespace, a removed one, a new tool, compaction and a fork.

Visual

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 72" width="240" height="72" fill="none" stroke="currentColor">
  <style>
    .new{animation:grow 3s ease-in-out infinite}
    @keyframes grow{0%,20%{opacity:0;transform:translateY(4px)}40%,80%{opacity:1;transform:none}100%{opacity:0}}
    @media (prefers-reduced-motion:reduce){.new{animation:none;opacity:1}}
  </style>
  <rect x="20" y="8" width="200" height="56" rx="6" stroke-width="1.5" opacity=".5"/>
  <g stroke-width="2" opacity=".7">
    <path d="M32 20 H70"/><path d="M40 28 H150"/>
    <path d="M32 38 H64"/><path d="M40 46 H130"/>
  </g>
  <g class="new" stroke-width="2.5">
    <path d="M32 56 H72"/><path d="M80 56 H160"/>
  </g>
</svg>

Added by the opencode bot (AI-generated).

Fixes #55529

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The arrival and departure deltas work, but the catalog is not stable yet: it changes again on the turn after a namespace arrives.

How I checked: I ran this PR's catalog.test.ts against the base. The stickiness tests and the complete↔partial delta tests fail there, so the old reshuffle and full restatement reproduce; they pass here. In packages/core, bun test test/codemode test/session-runner.test.ts passes (277), and bun run typecheck:tsc passes. I also chained summarize calls with the same setup as the new tests (opencode 13 + slack 27 tools, budget 300, then a browser namespace with a description arrives):

  • Arrival turn: the update is just New tool namespaces are available: - browser …, and all 16 listings are kept. 👍
  • Next turn, same tools: the catalog drops to 15 listings and sends another update: The following tools are no longer listed: tools.opencode.tool0 ….

So each arrival into a full partial catalog still produces two updates, and the second one evicts tools the model was relying on. Details are inline.

Public API: unchanged. InstructionState.value, the previous option and the exported CodeModeInstructions.key are internal to packages/core, and the stored catalog format is the same.

Fixes #55529

Comment thread packages/core/src/codemode/catalog.ts Outdated
.reduce((total, listing) => total + cost(listing.line), 0)
// A namespace that arrived since the previous catalog is always shown, but its header does not cost the kept
// listings their place; only pinned tools added since can, and they drop the costliest first.
const known = new Set(options.previous?.namespaces.map((namespace) => namespace.name))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This skips the new namespace's header cost only on the turn it arrives. On the next turn browser is in known, so arrived is 0 and remaining is still negative. The loop below then evicts kept listings and sends a second update ("no longer listed: tools.opencode.tool0") even though no tools changed. I reproduced it by calling summarize again with the same inventory and previous: joined (opencode 13 / slack 27 / browser 45 with a description, budget 300): 16 listings drop to 15. A compaction or fork that rebuilds from the stored catalog would hit the same eviction.

One fix: allow overage up to what the previous catalog already cost, or carry the excused header cost forward, so kept listings are evicted only when newly pinned tools or grown signatures need the room. A regression test could run the same inventory twice after an arrival and expect update to have nothing to send the second time.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commit, thanks. Kept listings are no longer evicted at all while their tools exist, so the over-budget case can't drop them on a later step. A fixed-point test now builds the catalog again with the same tools after an arrival, a departure and new pins, and expects no change. It fails on the previous commit with exactly this eviction. The catalog can run over budget, bounded by the headers of namespaces that arrived since and any newly pinned tools; pinned tools already exceed the budget in a fresh catalog.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The eviction problem from my last review is fixed. Kept listings are no longer dropped, so building the catalog again from the stored one with the same tools reproduces it exactly. I checked this with my earlier repro (opencode 13 and slack 27 tools at a 300-token budget, with a 45-tool browser namespace attached and detached four times): a second build with the same tools changed nothing after each attach and each detach, and each detach notice stayed small. test/codemode and session-runner.test.ts pass, and so does typecheck:tsc.

Public API is unchanged. Nothing else to raise.

@rekram1-node

Copy link
Copy Markdown
Collaborator Author

A review pass found no case where the stored catalog and what the model saw can diverge (compaction, nested forks, reverts, moves, agent switches, Code Mode off and on, undecodable values, preview). It also confirmed that rebuilding with unchanged tools is a fixed point. Fixed from that pass:

  • Removed tools now always say "no longer available". The "may have been removed; search" wording dated from the first commit's eviction.
  • Namespace lines whose tool count or shown count changes are restated, so counts don't go stale.
  • The complete → partial notice says search only works inside execute.
  • Tests: the undecodable-value test now compares the reads, the delta tests build on the previous catalog, the revert scenario compares the baseline to a fresh render, and there is a new complete → partial → compaction scenario.

The arrival-order trade-off is now described in the PR body.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My earlier eviction thread stays fixed: rebuilding the catalog with the same tools still reproduces it exactly. I checked three attach/detach cycles of a 45-tool browser namespace against opencode and slack at a 300-token budget. Each attach sent only the new-namespace block, each detach sent only a one-line removal, and building again with the same tools never produced an update. The new header restatement and the "no longer available" wording are consistent with tools now leaving only when they no longer exist. test/codemode and session-runner.test.ts pass (282), and so does typecheck:tsc.

Public API is unchanged. Nothing new to raise.

@rekram1-node rekram1-node changed the title fix(core): keep the Code Mode catalog stable as tools come and go fix(core): give each Code Mode namespace its own catalog section Nov 5, 2026
@rekram1-node

Copy link
Copy Markdown
Collaborator Author

The approach has changed. The earlier sticky selection (and the review findings about it) is gone. Each namespace now gets its own catalog section, built only from that namespace's tools: names with required parameters, full signatures only for pinned tools, and a size cap per namespace. Updates name only the namespaces and tools that changed, and there's no new session state. The PR description has the details, a session-replay token comparison (−92% on catalog updates) and an A/B eval against dev.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The per-namespace sections work as described. I ran the changed tests (test/codemode, codemode.test.ts, session-runner, session-generate, test/instructions; 291 pass) and typecheck:tsc (passes). In a scratch check with opencode and slack, attaching a 45-tool browser namespace sends only its section (~420 tokens), detaching sends one line (~32), and summarizing the same tools again gives an identical catalog. My earlier eviction thread no longer applies.

One concern: the catalog now has no overall size limit (inline). Older sessions whose stored catalog uses the previous format fall back to one fresh render through the instructions decode path, so nothing breaks.

Public API is unchanged; the stored catalog value is internal.

Comment thread packages/core/src/codemode/catalog.ts Outdated
const CHARACTERS_PER_TOKEN = 4
const INLINE_BUDGET = 2_000
// Enough for every tool of a typical MCP server; a larger one lists what fits and the rest is found with search.
const SECTION_BUDGET = 1_500

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a per-namespace cap and no overall one, the catalog grows with every connected server. Eight servers with 60 tools each render to about 4,600 tokens, against the base's ~2,000-token total, and users with many large MCP servers could pay 10k+ tokens on every request. Could the catalog keep an overall ceiling, for example by shrinking every section's cap evenly once the total is exceeded? That would keep each section independent of its neighbours' contents, so updates stay small, while still bounding the total.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commits look sound. Marked partial-update lines, not reporting a tool that only moved between pinned and listed by name as gone, bare names for inputs that aren't plain objects, and listing a tool at its namespace's own path all match their tests. The changed tests pass (test/codemode including the new parameters.test.ts, codemode.test.ts, session-runner, session-generate, test/instructions; 298 pass), and so does typecheck:tsc.

The point about the catalog having no overall size limit isn't changed in code; it's now documented as a known limit, so that thread stays open. Nothing new to raise.

Public API is unchanged.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detail levels for each namespace work as described. Updates still restate only the namespace that changed: in a scratch check, adding one tool sent ~95 tokens, a 45-tool browser namespace arriving sent only its own section, and leaving sent one line. Signature rendering runs only on namespaces of 75 tools or fewer, and the catalog inventory is cached across steps, so the extra rendering is paid once per tool set. Tests pass in packages/core (test/codemode, codemode.test.ts, session-runner, session-generate, test/instructions; 300) and packages/codemode (1,501), and typecheck:tsc passes in both.

The open size thread still applies: by the description's own figures, the starting catalog roughly doubled. Nothing new to raise.

Public API: unchanged for users. ToolDescription.compactSignature is an additive field inside @opencode/codemode.

@rekram1-node rekram1-node changed the title fix(core): give each Code Mode namespace its own catalog section fix(core): keep Code Mode catalog updates small and stable Nov 7, 2026

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The global 2k cap is back and the catalog depends only on the current tools, which resolves both earlier threads. In a scratch check, a group arriving sends only its section, leaving sends one line, and other groups showing fewer tools sends nothing. test/codemode, codemode.test.ts, session-runner, session-generate and test/instructions pass (295), and so does typecheck:tsc.

One new gap: a signature the model was shown can change without the model being told when the tool also drops out of the shown set (inline).

Public API is unchanged.

  • packages/core/src/codemode/instructions.ts:97: This only reports a changed signature when the tool is still shown in current. If a tool the model saw changes signature in the same step it drops out of the shown set (because another group arrived), nothing is said. Later, when it's shown again, previous.listings no longer has it, so the change is never reported. I reproduced it with opencode 60 / slack 80 tools at the default budget: slack.tool58 gains force: boolean as a 3-tool browser group arrives. The update only announces browser, and when browser leaves the update only says it's gone. The model keeps the old signature until the next compaction. Comparing each previous.listings entry against the current inventory through lookup(path), not against current.listings, would catch the case where a change and a hide happen in the same step.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stale-signature gap is fixed. Comparing every listing the model was shown against the current inventory catches a changed signature even when the tool is no longer shown. My repro (a shown slack tool changing signature in the same step a new group pushes it out of view) now reports it under Changed:, and rebuilding with the same tools sends nothing. The changed tests pass (296), and so does typecheck:tsc. The earlier eviction and size threads are addressed by the current design.

Nothing new to raise. Public API is unchanged.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The in-place version behaves as described. The shared 2k cap stays, tools are chosen in path order with only the shown signatures rendered, and updates name only what the model needs. In a scratch check (opencode 60 + slack 80 tools), a 45-tool group arriving sent ~860 tokens with no restatement and still reported a shown signature that changed while being pushed out of view. Rebuilding with the same tools sent nothing, and the group leaving sent one line. The changed tests pass (296), and so does typecheck:tsc. Both earlier threads are addressed by this design.

Nothing new to raise. Public API is unchanged.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one-time search note works as described. In a scratch check, a catalog going from complete to partial gets the note inside a small update with no restatement. Rebuilding with the same tools sends nothing, and later arrivals while partial don't repeat the note. The complete-catalog prompt is unchanged from dev. The changed tests pass (297), and so does typecheck:tsc. The earlier eviction and size threads are addressed by this design.

Nothing new to raise. Public API is unchanged.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit is right. While the catalog still lists every tool, the model hasn't been introduced to search, so cutting an update short and pointing it there would be wrong. Showing every new tool is bounded, because such a catalog already fits the 2k cap. In a scratch check, a 70-tool group arriving into a still-complete catalog was listed in full with no 'search' line. The changed tests pass (298), and so does typecheck:tsc. The earlier eviction and size threads are addressed by this design.

Nothing new to raise. Public API is unchanged.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going back to dev's wording keeps the behaviour I checked before. In a scratch check (opencode 60 + slack 80 tools), a 45-tool group arriving sent ~870 tokens with no restatement and still reported a shown signature that changed while being pushed out of view. Rebuilding with the same tools sent nothing, and the group leaving sent one line. New and removed tools in known namespaces each got their single block, and the complete-to-partial note is still sent once. The changed tests pass (298), and so does typecheck:tsc. The earlier eviction and size threads are addressed by this design.

Nothing new to raise. Public API is unchanged.

@rekram1-node
rekram1-node merged commit 61045f1 into dev Nov 8, 2026
17 checks passed
@rekram1-node
rekram1-node deleted the stable-catalog branch November 8, 2026 16:56
@amrkmn amrkmn mentioned this pull request Nov 10, 2026
1 task
SirWellington added a commit to WellingtonHQ/opencode that referenced this pull request Nov 13, 2026
* chore(site): remove the unused arcade sound library from the media manifest (anomalyco#62611)

* fix(site): bring the latest site fixes from the new-website workshop (anomalyco#62613)

Co-authored-by: Adam <[email protected]>

* feat(data): switch the mono font to OpenTUI Mono (anomalyco#62623)

* feat(site): add the shared product and logo menus to every nav (anomalyco#62654)

Co-authored-by: Adam <[email protected]>

* fix(data): keep native light-dark() so the shared nav menus get their colours (anomalyco#62660)

* feat(core): wake sessions on Console event watches (anomalyco#62662)

* fix(site): match the product chevron to the docs chevron (anomalyco#62665)

Co-authored-by: Adam <[email protected]>

* fix(site): stop the page dimming when a product card opens (anomalyco#62622)

* perf(core): fetch Console instructions and skills concurrently (anomalyco#62672)

* docs: antivirus HTTPS scanning and W-9 tax form (anomalyco#62661)

* fix(site): stop server functions failing on the dev server behind a proxy (anomalyco#62677)

* fix(site): pin the Like letters? bar so Safari's toolbar turns blue with it (anomalyco#62674)

* fix(site): tidy the docs bars and search on phones and desktop (anomalyco#62669)

* fix(site): use one dark link blue across the sites (anomalyco#62676)

* feat(data): per-page Open Graph cards for model and lab pages (anomalyco#62692)

* fix(core): clearer shell exit and timeout status, pass unmatched zsh globs through (anomalyco#62141)

Co-authored-by: Aiden Cline <[email protected]>

* docs(legal): cover integrations with third-party services (anomalyco#62700)

* fix(site): follow up anomalyco#62700 with dev's commit in the legal history and new legal bundle budgets (anomalyco#62701)

* fix(core): keep Console policies when its instructions or skills fail (anomalyco#62702)

* fix(core): don't fail MCP tool calls whose results break their output schema (anomalyco#61254)

* fix(site): tidy the phone nav, docs menus and icons (anomalyco#62707)

* chore(perf): bench with strobe 0.20.0 from github.com/anomalyco/strobe (anomalyco#62705)

* feat(core): watch workspace MCP servers' events through the Console plugin (anomalyco#62734)

* chore(perf): bench with strobe 0.21.0 (accuracy audit) (anomalyco#62747)

* feat(site): keep the page and current section stuck on phone docs (anomalyco#62751)

* fix(site): return Safari's toolbar to the page colour when the letters go back (anomalyco#62754)

* fix(site): make the phone sticky rows full width so their tap fill reaches both edges (anomalyco#62759)

* fix(core): keep Code Mode catalog updates small and stable (anomalyco#62176)

Co-authored-by: exe.dev user <[email protected]>

* fix(core): make build metadata a required service so flags can't silently fall back to release defaults (anomalyco#62686)

* chore(perf): bench with strobe 0.22.0; tabs' warm switches end on the frame after the answer (anomalyco#62764)

* fix(core): keep a v1 provider's baseURL when api is also set (anomalyco#62570)

* fix(desktop): show an error when opening a path in the default app fails (anomalyco#62572)

* Add Frantic Ferret Free (anomalyco#62770)

Co-authored-by: Daniel <[email protected]>

* fix(app): slide the side panel shut when its last tab closes (anomalyco#62758)

* feat(core): frame Console channel follow-up wakes as maybe unrelated, and let watch take minutes (anomalyco#62774)

* Remove Frantic Ferret Free from docs (anomalyco#62780)

* fix(desktop): restore the review shortcut (anomalyco#62567)

* docs(legal): cover hosted agent sessions and their retention (anomalyco#62789)

* fix(site): point the legal history's hosted agents entry at dev's commit (anomalyco#62790)

* fix(core): tail reads from the end of a file and suggest close file names (anomalyco#62147)

Co-authored-by: Aiden Cline <[email protected]>

* docs: strobe moved to github.com/anomalyco/strobe (anomalyco#62706)

* feat(core): say which credential changed in credential.updated (anomalyco#62762)

* chore(perf): bench with strobe 0.23.0 (anomalyco#62799)

* chore: let bun resolve strobe's Windows on Arm package (anomalyco#62804)

* feat(core): tag shell, tool and model-stream spans with session, agent and command (anomalyco#62806)

* fix(server): stop waiting for plugins in request handlers (anomalyco#62796)

* fix(core): refresh the Copilot API endpoint with the model list (anomalyco#62171)

Co-authored-by: exe.dev user <[email protected]>

* feat(ai): support predicate criteria in decisions (anomalyco#62819)

* fix(core): stop retrying ChatGPT workspace usage limits (anomalyco#62823)

* feat(acp): support _session/steering (anomalyco#54394)

Co-authored-by: Dan Smolsky <[email protected]>

* docs: document cache.ttl and per-model cache.warming (anomalyco#62829)

* fix(core): stop applying well-known remote config after logout (anomalyco#62760)

Co-authored-by: 2SinceJuly <[email protected]>

* fix(core): refresh MCP OAuth tokens under the shared credential lock (anomalyco#62827)

* refactor(core): run GitHub Copilot on native ai protocols (anomalyco#62034)

Co-authored-by: exe.dev user <[email protected]>

* fix(app): drop the dead plugin-boot retry on catalog syncs (anomalyco#62830)

* docs(go): add LongCat 2.5 Free to Free and Go (anomalyco#62878)

Co-authored-by: Jack <[email protected]>

* refactor(tui): remove the import cycle in plugin (anomalyco#62884)

* refactor(core): remove the import cycle in v1 migration (anomalyco#62885)

* refactor(codemode): remove the import cycle in interpreter (anomalyco#62886)

* docs(console): lower Claude Sonnet 5.5 cached read price to $0.10 (anomalyco#62887)

* feat(app): mark sessions read or unread from their menus (anomalyco#62889)

* fix(session-ui): show the picked question answer as a raised card (anomalyco#62896)

* chore(perf): bench with strobe 0.24.0 (anomalyco#62895)

* feat(core): return usage from session generate (anomalyco#62835)

* fix(tui): use keyboard-navigable buttons in update and confirm dialogs (anomalyco#62906)

* fix(ui): remove focus form borderless inline input while editing (anomalyco#62919)

* fix(ui): keep selects, menus, tooltips and toasts reachable from a dialog (anomalyco#62913)

* fix(ui): list types before import in packed exports and export select.css (anomalyco#62914)

* chore(infra): drop stripe and aws from deploy, move share to its own worker (anomalyco#62908)

* fix(site): align Copy as Markdown with the end of the docs page footer (anomalyco#62933)

* chore(infra): drop SiteDomain and retain zen buckets and trust dns before untracking (anomalyco#62938)

* docs(console): add Muse Ember 1.0 (anomalyco#62935)

Co-authored-by: Daniel <[email protected]>

* fix(core): hold Console watch wakes after 5 in 10 minutes or 20 since the prompt (anomalyco#62924)

* fix(tui): use the base text color for the dialog search cursor (anomalyco#62939)

* chore(infra): untrack zen buckets and trust dns now that they are retained (anomalyco#62940)

* fix(tui): drop usage and the model detail line from the model picker (anomalyco#62927)

* fix(tui): remove dark gap between execute detail and its footer (anomalyco#62928)

* fix(tui): add a blank row under the OAuth dialog title (anomalyco#62929)

* fix(tui): give the shell output dialog new-design footer actions (anomalyco#62932)

* docs(console): add Claude Fable 5.2 (anomalyco#62949)

Co-authored-by: Daniel <[email protected]>

* fix(core): time warming from the model's cache.ttl by default (anomalyco#62831)

Co-authored-by: Ryan Skidmore <[email protected]>

* chore(perf): bench with strobe 0.26.0 (anomalyco#62931)

* feat(app): close side panel tabs to the right and make ⌘W/⌘⇧T side-panel aware (anomalyco#62922)

* ci: read deploy and release secrets from aws secrets manager (anomalyco#62954)

* fix(perf): type navigation.bench.ts's setup key on its target (anomalyco#62952)

* fix(core): stop warming a session after a ping misses the cache (anomalyco#62926)

Co-authored-by: Ryan Skidmore <[email protected]>

* fix(core): count the warming interval from when the request was sent (anomalyco#62941)

* fix(site): keep open menus under the pinned header (anomalyco#62968)

* docs: match Gemini Flash intro pricing on Console models (anomalyco#62937)

Co-authored-by: Frank <[email protected]>

* docs(console): deprecate Crouton Alpha Free (anomalyco#62975)

Co-authored-by: Daniel <[email protected]>

* fix(site): fetch the docs' status bars from a 79-byte endpoint when the footer comes near (anomalyco#62962)

* fix(site): stop idle polling and scroll layout thrashing (anomalyco#62966)

* fix(files): cache the site's media for a year (anomalyco#62980)

* fix(site): flatten masked logo SVGs so they render sharp (anomalyco#62946)

* refactor(site): keep dev-only code out of production pages and group small shared chunks (anomalyco#62964)

* fix(site): load the nav menus, console API and Hex pop-up on intent (anomalyco#62965)

* fix(site): serve AV1/HEVC card videos and load card media after the page (anomalyco#62971)

* refactor(core): give each warmed session one owned loop (anomalyco#62972)

* chore(perf): bench with strobe 0.27.0 (anomalyco#62985)

* refactor(site): parameterize src/docs for multiple docs sets (anomalyco#62950)

* feat(site): serve V1 docs in the new design at /v1/docs (anomalyco#62951)

* feat(site): translate the V1 docs' chrome and missing pages (anomalyco#62956)

* feat(app): keep assistant copy visible on dev builds (anomalyco#62992)

* chore(core): update MCP SDK to 2.4.0 (anomalyco#62982)

* refactor(site): prune component CSS variants and split shell from route stylesheets (anomalyco#62990)

* fix(stats): map crouton alpha to muse ember (anomalyco#62994)

* chore(infra): point docs.opencode.ai at the site worker (anomalyco#62999)

* docs(console): add MiniMax M3.1 Flash Free (anomalyco#63005)

Co-authored-by: MrMushrooooom <[email protected]>

* fix(core): count session generate requests in the session's cost (anomalyco#63006)

* feat(ai): add embedding models with the OpenAI embeddings protocol (anomalyco#62832)

* refactor(ai): remove the ImageResponse.image shortcut (anomalyco#63011)

* fix(core): keep MCP OAuth credentials when a refresh fails temporarily (anomalyco#63012)

* feat(ai): add Google embedContent embeddings (anomalyco#62843)

* feat(core): add GitHub Copilot Auto and a model resolve hook (anomalyco#62997)

* refactor(plugin): edit the model in place in experimental.model.resolve (anomalyco#63015)

* feat(ai): add Vertex predict embeddings (anomalyco#62844)

* feat(ai): add Google and Vertex embedding entry points (anomalyco#63017)

* refactor(core): share OAuth credential refresh between provider logins and MCP (anomalyco#63022)

* fix(core): keep background compaction from stopping a running Claude Agent session (anomalyco#63025)

* fix(core): keep catalog capabilities when a v1 model override sets only some (anomalyco#62573)

* fix(site): build the Worker as one file so its chunks can't import each other (anomalyco#63031)

* feat(site): serve the reading pages as static HTML (anomalyco#63032)

* fix(core): send gatewayApiKey to Cloudflare AI Gateway (anomalyco#62574)

* fix(core): put Console event watches behind the console_watches flag (anomalyco#63036)

* fix(core): stop splitting an emoji when read truncates a long line (anomalyco#62575)

* fix(core): list Console's watch tool and say pull requests wake with their checks (anomalyco#63039)

* fix(core): stop interrupting Claude Agent tool calls when instructions or tools change (anomalyco#63033)

* ignore: wait for plugins again where managed policy applies  (anomalyco#63040)

* refactor(site): share the static pages' behaviour with their components (anomalyco#63041)

* chore(perf): bench with strobe 0.28.0 (anomalyco#63043)

* release: v2.2.6 (anomalyco#62450)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(site): serve the home and product pages as static HTML (anomalyco#63060)

* refactor(site): make every page static and stop shipping the app's JavaScript (anomalyco#63067)

* feat(app): make session IDs easier to find and copy (anomalyco#63083)

* release: v2.2.7 (anomalyco#63085)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(app): show tab shortcut numbers while holding the mod key (anomalyco#63089)

* fix(site): answer other methods on pages with a 405 (anomalyco#63102)

* fix(site): send every unhandled method a 405 listing what the address takes (anomalyco#63104)

* fix(app): tighten gap between titlebar search and first tab to 10px (anomalyco#63108)

* chore(release): mint short-lived cloudflare token for r2 uploads (anomalyco#63115)

* docs(console): list Muse Ember 1.0's over-200K price (anomalyco#63116)

* feat(app): queue a follow-up first with Alt+Enter (anomalyco#63109)

* feat(app): steer the next queued prompt with Mod+Enter (anomalyco#63111)

* fix(site): load the changelog's collapsed lists when opened (anomalyco#63122)

* chore(web): move the V1 docs into services/site and remove packages/web (anomalyco#63117)

* fix(release): publish platform packages before the cli package (anomalyco#63124)

* feat(app): link Console MCPs and skills in Extensions settings (anomalyco#63125)

* fix(site): serve 1344-wide hero videos to 1x screens and phones (anomalyco#63135)

* refactor(site): keep Solid off the static pages until an overlay opens (anomalyco#63140)

* refactor(site): drop code only the hydrated production pages used (anomalyco#63147)

* fix(core): retry failed streams cleanly and keep finished work on interrupt (anomalyco#60674)

* fix(core): send Copilot requests to the login's own endpoint until models load (anomalyco#63146)

* feat(core): background shell commands when their timeout expires (anomalyco#63024)

* docs(console): add Gemini 4 Carbon to Zen (anomalyco#63153)

Co-authored-by: Daniel <[email protected]>

* refactor(core): remove the import cycle between provider and aisdk-native (anomalyco#62883)

Co-authored-by: Aiden Cline <[email protected]>

* refactor(ai): remove the import cycles between media modules and their clients (anomalyco#62882)

Co-authored-by: Aiden Cline <[email protected]>

* refactor(site): share the docs' and static pages' shell code (anomalyco#63161)

* fix(site): keep the Like letters? bar's look when the announcement banner is off (anomalyco#63157)

* docs(console): document paying for Go with credit (anomalyco#63162)

* fix(site): give the not-found page one stylesheet of what it renders (anomalyco#63169)

* feat(app): add an experimental customizable sessions sidebar (anomalyco#63143)

* feat(core): speak opencode-pty protocol v8 (anomalyco#62976)

* fix(app): slide the file tree sidebar open and shut again (anomalyco#62773)

* fix(core): keep a shell command's output when the user interrupts its call (anomalyco#62977)

* fix(app): name the main checkout by its branch instead of Local (anomalyco#63187)

* feat(core): run shell commands in the process daemon across handoffs (anomalyco#62978)

* feat(plugin): let promise tools stop cooperatively through their signal (anomalyco#63127)

* feat(core): use freeform tool input on models that support it (anomalyco#61703)

* fix(core): accept decision-type models from LM Studio (anomalyco#56214)

* fix(tui): draw paste chip text dark in dark mode and light in light mode (anomalyco#62930)

* fix(tui): open the right activity tab and make tabs clickable (anomalyco#60168)

* feat(app): add Recent sessions sidebar experiment and iOS-style recency (anomalyco#63177)

* feat(tui): default transcript verbosity to low (anomalyco#63207)

* fix(app): show Recent sidebar rows exactly like side tabs (anomalyco#63211)

* fix(desktop): stop freezing the main process during the login shell env probe (anomalyco#63208)

* v0.8.4 (anomalyco#63055)

* fix(core): accept null context length from LM Studio models (anomalyco#63198)

* fix(core): report a refused Console auto-watch (anomalyco#63217)

* fix(cli): update npm installs through their own prefix (anomalyco#63152)

* feat(codemode): rank tool search with name-weighted BM25 (anomalyco#62984)

* feat(core): run LM Studio decision models (anomalyco#63200)

* fix(core): describe the execute program as plain JavaScript for every tool shape (anomalyco#63226)

* chore(ci): keep the heavy test tier off pull requests

pull requests now run the affected unit tests on linux only. the windows unit
tests, the compiled builds and the four playwright suites still run on every
push to dev and beta, on nightly runs, and as a release.yml gate before a
stable release publishes.

* chore(skills): describe the test tiers as they now run

* chore(ci): gate a stable release on the linux browser suites

the windows playwright legs still fail on dev pushes often enough that a
release gate over them would stall shipping. unit tests and builds still cover
both OSes, and dev pushes and nightly runs still cover the windows browser
legs.

* feat(tui): open sessions from session IDs in messages (anomalyco#63224)

* chore: move the api worker into the legacy service

* fix(tui): only re-highlight blocks whose session link changed (anomalyco#63247)

* chore: move the data site off sst

the stats site, its og worker and the sync workflow deploy from services/data with cf as
opencode-data-<stage>, opencode-data-og-<stage> and opencode-data-sync-<stage>. the beta
web app and the whole sst setup (sst.config.ts, infra/, sst-env.d.ts, the sst dependency)
are gone, so nothing deploys through sst anymore (security finding anomalyco#920).

* chore: drop the sst-env.d.ts files sst regenerated

* fix(core): refresh MCP OAuth tokens before they expire (anomalyco#63028)

* fix(site): keep the bare host as a custom domain

* fix(ai): keep exhausted request rate limits retryable (anomalyco#59816)

* fix(codemode): return compact JSON results (anomalyco#63258)

* feat(core): support MCP events in the MCP client (anomalyco#63254)

Co-authored-by: Adam <[email protected]>

* ci(deploy): mint short-lived cloudflare tokens from the token issuer

* feat(core): wake a session on MCP events with a watch tool (anomalyco#63269)

Co-authored-by: Adam <[email protected]>

* fix(core): send saved keys during local model discovery (anomalyco#63266)

* refactor(data): strict dev/production stages and data naming

- services/data/stage.ts: stage(mode) throws on anything but dev or production, used by all three configs, build.ts and deploy.ts
- packages/stats -> packages/data (@opencode/data-app, -core, -server)
- StatsSyncWorkflow -> DataSyncWorkflow, instance ids data-hourly-/data-history-, Data* resource keys, data sync log lines
- worker, route, workflow and cron names unchanged

* feat(core): tell agents when they are in a fork (anomalyco#63235)

Co-authored-by: OpenCode Agent <opencode-agent[bot]@users.noreply.github.com>

* fix(deploy): run Cloudflare deploys from verified artifacts behind pinned targets

Repository code outside .github/ needs no review, but every deploy job held a
Cloudflare token strong enough to write any Worker on the account (the data
sync Workflow PUT has no per-Worker permission). A dev merge could therefore
overwrite production Workers.

Split each service into a build job with no environment, secrets, or OIDC token
that uploads a SHA256SUMS artifact, and a deploy job in dev|production that
checks out only .github/release/, verifies the artifact against the build job's
digest, installs the cf/wrangler CLI pinned here, and runs only deploy-*.sh.

The deploy scripts treat the artifact as data and refuse it unless its resolved
config equals the pinned target in .github/release/targets/ exactly: Worker
names, routes, custom domains, crons, Workflow names, and bindings. For cf
services the pinned config is what cf reads. That is what makes an account-wide
token safe, and it keeps production out of reach of a dev merge.

Adds the cf 1.0.0-beta.12 and wrangler 4.110.0 lockfiles the deploy jobs
install with npm ci --ignore-scripts.

* fix(core): fetch Console instructions and skills only when the config says the workspace has them (anomalyco#63259)

* fix(ci): treat require-digests=false as off in verify-artifacts

The download action always sets OPENCODE_REQUIRE_DIGESTS (false by default),
so testing for a non-empty value required digests for release artifacts that
legitimately have none and broke sign-macos, sign-windows and
publish-attestations. Compare the value to true instead.

* fix(site): show download and install menus again after closing them (anomalyco#63311)

* feat(app): add the New timeline experiment (anomalyco#63295)

* fix(app): snap vertical tab preview to its tab (anomalyco#63074)

* feat(app): limit Recent sidebar to 7 days and 30 rows, align its hover preview (anomalyco#63313)

* fix(tui): give plugins a live theme view (anomalyco#63306)

* fix(app): accept a pasted redirect URL for provider sign-in (anomalyco#63315)

Co-authored-by: Aiden Cline <[email protected]>

* fix(session): measure tps from the first streamed token (anomalyco#63321)

* chore(protocol): refresh generated openapi for the first-token event (anomalyco#63324)

* fix(app): show only the branch in the new session row (anomalyco#63182)

* refactor(codemode): remove the legacy interpreter; the VM is the only engine (anomalyco#62642)

* fix(core): keep the truncated preview when tool output can't be saved (anomalyco#63334)

* fix(app): show the agent switcher by default (anomalyco#63337)

* feat(process-daemon): pipe mode on Windows (anomalyco#63196)

* fix(update): verify access identity on admin routes (anomalyco#63340)

* fix(app): remeasure the desktop drag area when direction changes (anomalyco#63347)

* chore: remove beta what's new and visual svgs (anomalyco#63325)

* feat(core): report subagent results to the parent every time a child goes idle (anomalyco#63209)

* chore(process-daemon): release 0.5.1 (anomalyco#63366)

* refactor(process-daemon): simplify Windows pipe mode (anomalyco#63346)

* chore(process-daemon): allow 60 minutes for Windows release builds (anomalyco#63378)

* feat(core): always run subagents in the background (anomalyco#63382)

* refactor(core): remove the unused foreground path from SubagentJob (anomalyco#63223)

* fix(core): defer restart wakes until parent attempts are counted (anomalyco#63367)

* chore(process-daemon): release 0.5.2 (anomalyco#63381)

* feat(ai): add Google Decisions (anomalyco#63369)

* fix(acp): stream the parent's reaction to background subagent reports (anomalyco#63395)

* chore(core): bump gitlab-ai-provider to 6.24.0 (anomalyco#63342)

* ci: deploy dev workers in anomaly-dev (anomalyco#63379)

* read dev data sync from the dev lake

* fix(core): show the start of tool output that exceeds the limit (anomalyco#63272)

* feat(site): simplify the docs header menu and move the V1 notice into the header (anomalyco#63399)

* chore(site): update OpenCode link cards (anomalyco#63401)

* refactor(core): remove leftover foreground subagent and reattach code (anomalyco#63392)

* refactor(app): remove leftover foreground subagent UI (anomalyco#63391)

* fix(tui): show the subagent card instead of launch instructions in mini scrollback (anomalyco#63389)

* chore(site): remove the /ide page (anomalyco#63386)

Co-authored-by: Adam <[email protected]>

* ci: run macos build jobs on blacksmith (anomalyco#63397)

* fix(storybook): provide settings context to stories

* fix(app): avoid transforms on scrolling composer suggestions

* fix(app): keep mobile tab menu open when closing tabs

* fix(desktop): update build-windows.bat to post-merge workflow

The merge adopted the upstream desktop workflow, which folded prebuild into build and removed package:win. Drop the stale prebuild step and package the Windows NSIS installer directly with electron-builder.

* chore: ignore Astro and Cloudflare build artifacts

Ignore the generated .astro content modules, .wrangler miniflare state, and the generated services/www/public/cli.json schema so local builds of the site packages no longer show up as untracked files.

* fix(core): keep ephemeral server credentials from wiping shared auth sessions

A stdio/ACP auxiliary server mints its own random password and shares the data directory (and database) with the canonical server. On startup, AuthSession.configure treated the verifier mismatch as a credential change: it deleted the whole auth_session table, wiping the canonical server's remembered logins, and adopted a random verifier. Thread an ephemeral flag from the CLI (set when the password was minted, not from env/config) down to configure, which now skips the reconcile for such a process. The canonical server's behavior, including invalidation on a real credential change, is unchanged.

* fix(desktop): update build-mac.sh to the post-merge build workflow

The script called bun run prebuild and bun run package:mac, neither of which exists in packages/desktop/package.json, so it aborted before producing a build. Mirror build-windows.bat: run bun run build, then bunx electron-builder --mac --publish never --config electron-builder.config.ts.

* feat(app): add sortable schedule list with persisted preference

The schedules panel gains a sort selector with three modes: name (alphabetical, the default), lastRun (most recent first, never-run tasks last), and status (enabled first, name order within each group). The choice is stored with the app's local preference storage (home.schedules.sort) and restored when the panel reopens.

A neutral review pass pinned the never-run ordering (second undefined fixture plus a numeric-aware name pair) and replaced the -Infinity comparator sentinel with -1, since lastRunAtMs is a NonNegativeInt, so the comparator always returns a finite number.

* feat(session-ui): show full input in expanded generic tool calls

---------

Co-authored-by: David Hill <[email protected]>
Co-authored-by: Adam <[email protected]>
Co-authored-by: Kit Langton <[email protected]>
Co-authored-by: vprdev <[email protected]>
Co-authored-by: Aiden Cline <[email protected]>
Co-authored-by: Aiden Cline <[email protected]>
Co-authored-by: Luke Parker <[email protected]>
Co-authored-by: exe.dev user <[email protected]>
Co-authored-by: James Long <[email protected]>
Co-authored-by: Dan Smolsky <[email protected]>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Daniel <[email protected]>
Co-authored-by: smagnuso <[email protected]>
Co-authored-by: Dan Smolsky <[email protected]>
Co-authored-by: 2SinceJuly <[email protected]>
Co-authored-by: Jack <[email protected]>
Co-authored-by: Stefan Avram <[email protected]>
Co-authored-by: usrnk1 <[email protected]>
Co-authored-by: Ludvig Rask <[email protected]>
Co-authored-by: Dax <[email protected]>
Co-authored-by: Ryan Skidmore <[email protected]>
Co-authored-by: Frank <[email protected]>
Co-authored-by: Jay <[email protected]>
Co-authored-by: MrMushrooooom <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Liyang Zhu <[email protected]>
Co-authored-by: OpeOginni <[email protected]>
Co-authored-by: Simon Klee <[email protected]>
Co-authored-by: Dax Raad <[email protected]>
Co-authored-by: Kyle Bush <[email protected]>
Co-authored-by: Sebastian <[email protected]>
Co-authored-by: OpenCode Agent <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Aarav Sareen <[email protected]>
Co-authored-by: Swapnil Devesh <[email protected]>
Co-authored-by: Filip <[email protected]>
Co-authored-by: Vladimir Glafirov <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codemode: "Instructions updated" re-injects the full tool catalog on every tool-tree change (browser attach/detach, MCP reconnect), polluting context

1 participant