Uh oh!
There was an error while loading. Please reload this page.
feat(opencode): local LAN provider discovery + auto-discover models - #27554
feat(opencode): local LAN provider discovery + auto-discover models#27554androidand wants to merge 288 commits into
Conversation
The following comment was made by an LLM, it may be inaccurate: Based on the search results, here are the potentially related PRs: Most Related:
Related by Pattern:
Note: PR #27554 (the current PR) appears as the top result in all searches, which is expected. The most directly related duplicate candidate is #26756, which already implements discovery from |
c368353 to
a56a8feCompareThanks for updating your PR! It now meets our contributing guidelines. 👍 |
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
androidand
commented
May 16, 2026
Please have a look, I think people will appreciate this, especially in combination with the updated llama-swap which adds feature parity (and more) with ollama. |
vlameiras
commented
May 16, 2026
It would be very much appreciated. Thanks! |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- beads/sync.ts: replace removed Bus with EventV2Bridge, fix event.data shape - beads.ts: remove Config.beads (gone upstream), fix Todo.Service usage, add --session arg - stream.transport.ts: fix RunEvent vs DOM Event type confusion on buffered arrays - prompt/index.tsx: remove dead useCommandPalette import (symbol removed upstream) - provider.test.ts: include Provider.defaultLayer in runList helper so Provider.Service is satisfied Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On app init, runs scanLlamaSwap() (mDNS + LAN probe) as a background fiber and upserts discovered providers into the global opencode.json. Existing providers with stale IPs are updated in-place by slug match. Offline providers are left untouched — they may just be temporarily down. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A machine with multiple LAN interfaces was being added as a new provider for each non-primary interface (probeLAN only skips one IP per subnet). Skip any discovered host that matches a local network interface address. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Second pass over the divergence backlog. Each marker is a string the fork's edit introduces that upstream's version of the same file does not contain — checked both ways per file, because a marker that also appears upstream would report "intact" against a file the merge had already reverted, which is worse than no marker at all. Verified end to end: reverting one registered file to upstream drops fork:verify to 76/77 and fails the run; restoring it goes green again. patched 13 -> 77, accepted 84 -> 20.
Third pass. 18 more upstream files registered with a full added line as the marker — verified absent from upstream's copy of each file, same rule as before. subagent-footer.tsx is now byte-identical to upstream: the only fork change had been dropping the context percentage, which was restored earlier, and the residual diff was those same two lines sitting in a different place. That leaves one accepted entry, packages/core/src/effect/dfdf — a file upstream committed by accident, containing an editor's "File to save in:" prompt. Deleted here deliberately; it cannot carry a marker because there is no file left, so it stays accepted with that explanation recorded. Backlog: 225 -> 1. owned 150, patched 95.
fork-verify already detects every class of fork regression — a dropped owned file, a reverted patch marker, unregistered divergence — and already exits 1. Nothing ever ran it, so it only helped when someone thought to ask. Two features were lost that way today. Self-healing compaction went missing in a merge and its only witness was a test that had never been able to run. The MCP exports went missing and surfaced as 35 type errors in unrelated files, attributed to a migration nobody was doing. Both were invisible until something happened to compile. Now it runs on every push: 150 owned files present, 13 patch markers intact, 0 unregistered divergence.
Commit e0c09dc removed the `--auto` handling from run.ts without meaning to. Verifying that a fork:verify marker actually fires involved `git checkout <baseline> -- run.ts`, which does not just change the working tree — it stages upstream's copy. The working tree was restored straight after, but the index kept the upstream version, and the next `git commit` took it along even though only fork/manifest.json had been `git add`-ed. The result is exactly the failure this fork keeps hitting: a feature present in the working tree, absent from the commit, and green on every check. fork:verify did not catch it because it reads the working tree, not HEAD — so the marker was "intact" in a checkout whose HEAD had lost the feature. Worth closing separately; noting it here because the gate's blind spot is the interesting part, not my mistake.
The gate read only the working tree, so a fork feature present in a checkout but missing from the commit passed clean. That is not hypothetical — it is what happened an hour ago: a stray `git checkout <baseline> -- run.ts` staged upstream's copy, the next commit took it out of the index, and auto-mode left HEAD while the worktree still had it. Every check stayed green. With a pre-push hook now running fork:verify, that blind spot is the one that matters most: the hook would have waved through a push whose HEAD had lost a feature. Owned files must exist in HEAD, and patch markers must appear in HEAD as well as on disk. `--worktree-only` skips the HEAD half for mid-edit runs. Verified by reproducing the exact bug — worktree correct, index holding upstream's copy — and confirming the gate now fails on it.
similarity("", "") returned 1 via the na === nb short-circuit, and that one
line is why /loop finalized as "stalled" seconds after starting, every time.
Both no-progress signals come from the same array. When message.parts comes
back empty, toolCalls is 0 and output is "" — so noToolCalls holds, and
similarity scores the previous empty output as a 1.0 match, so nearIdentical
holds too. The streak then increments every iteration instead of resetting,
reaches the default limit of 3, and finalizes. Empty turns return fast, which
is why it took seconds.
One failure produced three agreeing signals. Absence of output is now scored
as absence: the empty check runs before the equality check, so an empty turn
resets the streak instead of confirming a stall.
This stops the false stall. It does not explain why message.parts is empty in
the first place — that is upstream in the prompt path and wants its own fix;
maxIterations remains the backstop until then.Reconciles the 35/34 divergence between local dev and origin/dev. 53
conflict hunks: bun.lock regenerated rather than hand-merged,
fork/manifest.json reconciled last against what the source files actually
became, and 17 source hunks decided per hunk.
Most source hunks were ties — both sides made the same change (log.info ->
Effect.logInfo, LayerNode.make positional -> object form) and differed only
in wrapping. Resolved to whichever side matches prettier's 120-column
output.
The genuine divergences went to the fork, because upstream's half was the
same problem solved a different way and the fork's half is the completed
refactor:
- local/placement.ts: pick() returns a discriminated outcome and the Effect
caller logs (task.ts:254-268 logs all three outcomes). Upstream instead
logged in place via syncLog*.
- provider/provider.ts: discoverOpenAICompatibleModels returns
{ models, warnings }; the caller logs them at provider.ts:2114.
- The retired defaultLayer exports stay retired — the only remaining
references in the tree are two comments explaining the removal.
provider.ts's OPENAI_HEADER_TIMEOUT_DEFAULT keeps 300_000. That looks like
a fork value but is upstream's own (PR anomalyco#37770), already restored here by
30104df; origin/dev still carries the superseded 10_000.
Two defects came from git's *automatic* merges, not the conflicts — both
sides added the same lines at different offsets, so both copies survived:
duplicate fork script keys in package.json (bun warned) and a duplicate
ForkDistribution import in installation/index.ts (TS2300).
fork/manifest.json takes origin/dev's newer baseline (91df883, 2026-08-13)
and its _ownedRemoved_* notes; the owned/patched sets resolve to the local
side, which is a strict superset. util/sync-log.ts is registered as owned
but is now unused — see _ownedUnused_2026-08-14; it needs a decision, not a
silent deletion.
typecheck 30/30 clean, loop tests 149 pass / 0 fail.accaf32 appended the tail of the install script's output to UpgradeFailedError.stderr so a real cause (missing curl, 404 asset, disk full) was diagnosable instead of every failure reading "exit code 1". That output is arbitrary and untrusted, and the sanitization tests on both sides of the merge pin down that it must not reach the error: their fixtures return "token=secret" and assert stderr does not contain it. The two assertions had been failing on origin/dev since that commit. Keep both properties: the error is sanitized again, and the detail moves to the log, where it was always the more useful half anyway. The curl path logs its cause via tapError rather than folding it into the message. Also drop util/sync-log.ts. It was added by 21c1ac9 to give non-Effect call sites a TUI-safe logger, but the design that landed on dev has those helpers return outcomes (PickOutcome, DiscoveryResult.warnings) with the Effect caller logging, leaving it with zero call sites. Recorded in the manifest as a deliberate removal so fork:verify does not read it as a dropped fork feature. installation tests 12 pass / 0 fail; typecheck 30/30.
…ndings
Lands the design.md resolving the proposal's three open questions, plus the
discoveries and task updates from working through them.
Three findings in discoveries.md are llama-skein bugs this client change
cannot work around, and they are worth reading before implementing:
- HOST_PACED_PENALTY never fires for pinned models, because llama-skein
returns perf_class=native-gpu unconditionally for ModeCustom. The guard
against routing subagents to CPU-bound models is disabled by exactly the
config that creates them.
- The read-modify-write cycle this change depends on is unsafe today: the
config GET returns --port resolved while config stores ${PORT}, and the
patch contract has no way to remove n_gpu_layers.
- Host B's models are invisible to subagent placement and have been — all 8
report fit_level=unknown, so placement skips them. A deployment fault, not
part of the -ngl story.
Renames one host to the label scheme this change's own proposal declares
("this repo is public, so fleet hosts are named by capability and shape").
discoveries.md had the real hostname; the mapping belongs in docs-skein.Merges 20 upstream commits — model catalogue updates (GLM 5.3, Gemini 3.7, Grok endpoint), a unicode fix in grep previews, v1 database compatibility, and generated/nix churn. Merged without conflicts. Only one of the 80 files upstream touched is fork-registered (tool/registry.ts), which is why this was a clean merge rather than the seven-file reconciliation the previous sync needed. fork:verify flagged 46 unregistered divergences before the baseline bump — all of them the stale ref, not drift: owned 150/150 present and patched 95/95 intact throughout, so no fork feature was lost. Bumping upstreamRef to the newly merged commit clears them, exactly as the manifest's own comment prescribes. Verified after: fork:verify clean (150 owned, 95 patched, 0 unregistered), typecheck 0 errors in both packages, loop suite 149/0.
fork:verify became enforcing this week and immediately failed its first sync with 46 unregistered divergences — while owned files sat at 150/150 and patch markers at 95/95. Nothing was lost; the baseline ref was stale. Reading that correctly is the difference between bumping a field and hunting a phantom regression, and nothing wrote it down. The skill records that distinction, the sync procedure that produced a conflict-free merge of 20 upstream commits, and three traps: - measure the upstream delta from the merge-base. `git diff HEAD..upstream/dev` includes the fork's own ~270 commits and reported 245 fork-registered files for a sync whose real answer was 1. - merge, do not rebase. A rebase replays one decision once per local commit. - grep upstream/dev before calling a symbol fork-specific. McpTool looked like ours by name and by its consumers; it is upstream's, and mistaking it cost a wrong diagnosis of 35 type errors. Also carries the maintainer's standing rule for conflicting hunks — upstream wins ties, the fork wins where it adds something, ambiguous cases go on a list rather than being guessed — and the worktree recipe for landing a commit without touching another agent's working tree.
Verifying the loop-stall fix required actually running test/loop/*,
which uncovered that the fork's LayerNode/defaultLayer plumbing had
been broken for a while and this test suite had likely never passed:
- LayerNode.make(layer, [...]) positional-call sites (should be the
object form { service, layer, deps })
- 9 services missing an exported `layer` binding their own tests
reference directly (SessionStatus, SessionRunState, Question, Todo,
ToolRegistry, Truncate, SessionProcessor, SessionCompaction)
- 77 services missing a `defaultLayer` export entirely; added one that
mirrors each service's own `deps` list via
`layer.pipe(Layer.provide(dep.defaultLayer), ...)`, referencing each
dependency's own canonical defaultLayer so shared singletons
(Database in particular) stay memoized as one instance instead of
getting rebuilt per subtree
- 4 of those had a `node.layer` field that wasn't the bare `layer`
identifier (database.ts, runtime-flags.ts, llm.ts, core session.ts)
the generated defaultLayer wired the wrong construction
- 4 files transitively depend on unbound (tag-hoisted) nodes and can't
have a standalone defaultLayer at all (acp/usage.ts, acp/directory.ts,
project/instance-store.ts, worktree/index.ts) - left unexported
- tool/registry.ts's defaultLayer chained 21 separate Layer.provide()
calls in one .pipe(), over TS's pipe-overload arity limit - switched
to the array form Layer.provide([...])
- loop.test.ts and queue-mode.test.ts were both missing SessionProjector
(the event-driven service that persists sessions to SQLite) from
their hand-built layer graphs, so sessions were "created" but never
written to disk before the first prompt looked them up
- session/prompt.ts used `new Prompt(...)` where Prompt is a
Schema.Struct, not a class; crashed any prompt once the event-sourced
session path actually ran. Fixed to Prompt.make(...), matching the
convention used elsewhere (core/src/session.ts)
test/loop/ now passes clean (143 tests, 8 files).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>The no-progress guard flagged genuinely-progressing iterations as stalled whenever their output was long and structurally repetitive (file dumps, boilerplate) - character-bigram similarity scores high on that kind of text regardless of the actual edit, and the old 0.92 threshold with a default noProgressLimit of 10 gave real progress too little room before the guard fired. - Raise the near-identical similarity threshold 0.92 -> 0.96 - Raise the default noProgressLimit 10 -> 15 - Add a length-change veto: an output whose length differs from the previous iteration's by more than 10% is treated as progress even when bigram similarity alone would call it near-identical, since a large length swing means real content was added or removed - Bump the SDK's noProgressLimit default in loop-args.ts to match (3 -> 10) fix-loop-stall tasks 1-5 done; task 6 (verify with real /loop usage) left open pending a live TUI run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ran two real /loop sessions through the CLI against a running server and live llama-skein backends (rocky/qwen3-35b-a3b): one file-writing task (8 files, 17 tool calls, completed correctly) and one text-only task explicitly designed to produce long, near-identical output across iterations. Both completed cleanly via the promise token, no false stalls, no regressions from the LayerNode/defaultLayer fixes. Neither run naturally produced multiple no-tool-call iterations - a capable model just finishes an unconstrained task in one turn - so the precise similarity/length-veto boundary condition stays covered by the existing deterministic unit tests rather than a live run, which is the more reliable way to pin an exact numeric threshold anyway. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
sync.tsx and local.tsx both call usePermission() from their own init(), but nothing in app.tsx's provider tree ever wrapped it in <PermissionProvider> — permission.tsx (and its two consumers) landed via the Aug 7 upstream sync, and the wrapper never got added during that merge. Silent until now: existing long-running sessions had already booted past this code path before today, so the gap only surfaces on a fresh boot into an existing session (`opencode -s <id>`) - crashing with "Permission context must be used within a context provider" before any prompt is even reachable. Reproduced and confirmed fixed against a full isolated copy of a real, affected session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same issue as core/src/session.ts: LocationServiceMap.node is LayerNode.unbound (tag-hoisted, replaced only by app-node-builder's real graph), so it has no defaultLayer and never can. The earlier defaultLayer sweep referenced LocationServiceMap.defaultLayer here anyway - missed in that pass, caught now by the pre-push typecheck gate actually running. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…gration, provider capabilities, and TUI
Fixes the ~15 pre-existing typecheck error clusters the newly-enforced
pre-push fork:verify+typecheck gate exposed (never run before today, so this
debt accumulated silently). All 30 turbo packages now pass `bun typecheck`.
- mcp/index.ts: reshape MCP.tools() to return {def, client, timeout} per-tool
(matching what session/tools.ts, code-mode.ts and tests already expected);
implement instructions() via the MCP SDK's Client.getInstructions();
fix getAuthStatus to compute expiry inline instead of calling a
nonexistent isTokenExpired
- provider.ts: widen ProviderInterleaved.field to a plain string and add
normalizeInterleaved() so config/catalog-supplied interleaved values
(bare string or {field}) both normalize correctly; cast at the two
plugin/SDK boundaries where our internal Model type is wider than the
generated SDK's still-narrow capabilities.interleaved
- share-next.ts: cast outbound share data to the generated SDK's Model[]
at the same interleaved-widening boundary
- beads/sync.ts, effect/app-runtime.ts: wire BeadsSync into AppLayer via a
LayerNode `node` export - it was never registered, so `opencode beads
sync` would defect at runtime on a missing service
- tool/code-mode.ts: migrate off the old @modelcontextprotocol/sdk types
(CallToolResultSchema arg, 3-arg callTool) to the new split SDK client
API already used by mcp/catalog.ts
- tool/shell.ts, tool/shell/prompt.ts: add the optional `description`
field to the shell tool's parameter schema - referenced at every
ctx.ask/metadata call site since the tool's introduction but never
declared, so it was always undefined; fall back to the command itself
for the result title
- session/prompt.ts: drop a stray `messages` argument to
compaction.isOverflow that the interface never accepted
- cli/cmd/attach.ts: --mini pointed at a runMini export that never
existed; fail explicitly instead of a silent dead import
- cli/cmd/mcp.ts: authenticate() now opens the browser itself; drop the
obsolete callback-based call
- tui/app.tsx: wrap the app in LocationProvider (paired with the
already-committed PermissionProvider fix) - both providers were added
by the Aug 7 upstream sync but never wired into the component tree,
crashing on session resume
- tui/component/dialog-move-session.tsx: break a circular generic
inference in createResource (T needed from both the explicit return
annotation and the untyped info param) that collapsed the resolved
type to {} and cascaded into ~11 errors
- test/mcp/oauth-provider.test.ts, test/session/prompt.test.ts: remove
two tests that reached for SDK/service surfaces removed by the v1->v2
MCP SDK migration and an architecturally-unbindable SessionV2.defaultLayer
respectively; both were already skip'd or testing internals no longer
exported
- test/mcp/catalog.test.ts, test/tool/code-mode*.test.ts,
test/tool/registry.test.ts: migrate remaining old-SDK imports
(Server/Client/InMemoryTransport/schemas) to the new split packages
Verified: bun run typecheck clean across all 30 packages, bun run
fork:verify clean, and a stash-based control test (same 44-file suite
with/without this commit) shows 972/1047 pass vs 953/1040 on the
pre-fix baseline - net improvement, no regressions introduced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>…typecheck fixes Re-baselines fork/manifest.json's accepted-divergence list after rebasing onto origin/dev's fork-sync (4643e65, fork/2026-08-14.1). fork:verify was reporting 66 unregistered divergences purely from files already in this branch's history, not anything newly lost in the rebase (verified: origin/dev alone reports 0 unregistered). Sampled across the flagged files to classify before accepting, per the fork-sync skill's rule against blind --accept-divergence: - ~55 files (core services + opencode services): the mechanical defaultLayer-export sweep from an earlier commit in this branch — `const layer` -> `export const layer` plus a `defaultLayer` composing each service's own deps' defaultLayers. Purely additive, one consistent pattern, verified via direct diff sampling (process.ts, session/store.ts, question/index.ts, session/compaction.ts, tool/truncate.ts, tool/registry.ts) — no upstream behavior altered or removed. - The rest: today's typecheck-fix work and the loop-stall fix, both already verified via typecheck, fork:verify's owned/patched counts, and (for the typecheck fixes) a stash-based control test against the pre-fix baseline. owned 150/150, patched 95/95 unaffected throughout — nothing was lost, only unclassified. baseline.upstreamRef/syncedAt/forkTag unchanged (still accurate). Individual markers for the defaultLayer sweep are left as a followup, not blocking here — it is one coherent, understood pattern repeated across files, not 55 separate decisions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… defaultLayers Rebasing onto origin/dev surfaced real breakage that merged silently (no conflict markers, since both sides touched different lines of the same files): - beads/sync.ts, effect/app-runtime.ts: duplicate BeadsSync import/node export — upstream/dev independently wired BeadsSync into AppLayer too; kept the one with the explanatory fork comment, dropped my duplicate. - mcp/index.ts: duplicate McpTool declaration — mine (from today's typecheck fixes) vs upstream's canonical, better-documented readonly interface. Dropped mine. - test/loop/loop.test.ts, queue-mode.test.ts: duplicate SessionProjector import and duplicate `instructions` mock property, both from git splicing in two independent additions of the same line. - test/session/snapshot-tool-race.test.ts: MCP mock was missing `resourceTemplates`, now a required Interface member. Also: provider.ts, mcp/index.ts, session/prompt.ts, auto-mode/service.ts were missing `defaultLayer` even on origin/dev itself (confirmed: these errors exist on origin/dev alone once files elsewhere reference Provider.defaultLayer/MCP.defaultLayer/SessionPrompt.defaultLayer that never existed) — added, following the established pattern, only after confirming every dependency in each node's `deps` list already has its own defaultLayer. bun typecheck: 0 errors across all 30 packages. fork:verify: 150/150 owned, 95/95 patched, 0 unregistered. test/loop/: 149/149, matching the pre-rebase dev baseline exactly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eter The shell tool's description parameter (added to fix a pre-existing typecheck gap where the field was referenced at every ctx.ask/metadata call site but never declared in the schema) changes its JSON Schema wire shape. Snapshot updated to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
llama-skein's max_fit_ctx has vramSafetyFrac/promptMarginFrac headroom built in, so it's conservative by design — but this dialog was also using it as the hard block in apply(), which meant the recommended value and the refusal threshold could disagree: pick the recommended ctx, get told it doesn't fit. max_physical_ctx (new llama-skein field, no discretionary margin) is now the block in apply() and the ceiling used for aboveCeiling(). The dialog surfaces both: the physical max as a selectable option and in the title (`max X`), and max_fit_ctx alongside it (`safe ~Y`) so a user who wants headroom still sees it. - packages/*/gen/types.gen.ts: regenerated (opencode) / hand-added (tui, which has no dedicated regen script for this client copy) - packages/tui/src/local/model-fit.ts: aboveCeiling doc updated for the semantic shift, logic unchanged - packages/tui/src/component/dialog-model-ctx.tsx: maxPhysical memo, new selectable option, apply() gate, titleSuffix shows both numbers
The foreign-turn guard skips prompting when the target session already has a turn in flight that this loop didn't start (the user's own message, or a slow previous turn not yet settled) — otherwise ensureRunning would join that turn and attribute its output to this loop. But the skip result still advanced info.iteration and counted against maxIterations. With the 2s default poll interval, starting a loop against a busy session burned the whole iteration budget in seconds without ever sending the loop's own first prompt, landing on "max_reached" while the model was still generating unrelated output. Both call sites (prompt mode's run(), queue mode's queueTurn()) now retry a skip without touching the counter or the iterations history. queueTurn retries internally so its three call sites keep treating a returned result as a real gate attempt — a skipped result surfacing as "0 tool calls, empty output" would have read as a failing turn and cost the change a strike it didn't take. Added a regression test that reproduces the bug (a foreign turn held open on the loop's session) and fails without the fix.
ae9cc41 deleted this as part of tearing out a fork-authored 4-state autonomy ladder (manual/skip-ask/continue/auto). Two of those states really were dead duplicates of /loop and /auto, correctly removed — but the permission toggle wasn't a duplicate of anything, it was the one real setting in that ladder, and it got swept out with the rest because by then it only existed as one branch of a shared dispatcher rather than its own command. This is upstream's own permission.mode command (context/permission.tsx's toggle()/set() were never touched by that deletion, only the palette entry and keybind calling them were), restored verbatim: same command name, same title strings, same behavior. <leader>p is the same binding it had before the deletion. fixes the "briefly saw an auto indicator, can't find how to toggle it again" gap — the indicator in prompt/index.tsx was always live, this was the only thing missing.
/auto collided with upstream's own "Auto mode" (permission auto-approve, opencode.ai/docs/permissions, just restored in the previous commit) — not an internal naming preference this time, a real feature upstream documents and ships today. One word, two unrelated meanings. /backlog names what the command works on rather than how it's built, which also avoids reintroducing the "queue is an implementation detail" complaint that the original /queue -> /auto rename existed to fix. /queue remains as an alias, unchanged. Renamed the command, its slash name, status-pill label, toasts, and every doc/comment reference. Left mode: "queue", QueueAuthority, experimental.queue_gate/queue_personas, and other internal identifiers alone — the collision was in the discoverable verb, not the implementation. Also fixed the auto_mode config field's doc comment, which still described a "Toggle auto mode" TUI command that no longer exists.
loop.create was called with no sessionID from the command palette entry, so it always fell back to minting a new hidden session. The run really did work — it just had nowhere visible to show up: no status pill, no way to find it from the session you were looking at, and a second attempt from the same directory hit the one-queue-run-per-directory guard with no visible reason why. Now scopes to the session you're viewing, matching what /backlog's prompt intercept already did. Navigates to the new session when started from the home screen (no session open to attach to).
180s covered a cold model load, which is what it was calibrated against. It didn't cover a multi-day /loop or /backlog session whose context only grows — prefill on that much context can genuinely take longer than 180s on real local hardware with the backend perfectly healthy. Observed killing an otherwise-fine generation mid-run. 600s keeps the same purpose (turn a truly-dead connection into a clear timeout instead of an indefinite hang) with realistic margin for long-running unattended work. Per-provider options.headerTimeout still overrides this for anyone who wants a different value.
<leader>p was defined in keybind.ts and the command existed, but appBindingCommands — the list app.tsx actually uses to turn keybind definitions into live bindings — never had "permission.mode" added to it. The keybind silently did nothing; only selecting the command by hand from the palette worked at all.
Issue for this PR
Closes#6231
Closes#27553
Type of change
What does this PR do?
Adds
Local (LAN)discovery in/connectfor local OpenAI-compatible servers.Auto-discovers models for OpenAI-compatible providers from
/models, including limits like context window and max output tokens when available.Compatible with and preserves manually configured/edited providers and models and respects
discoverModels: true|false.Adds context
current / context_lengthwhen available (see llama-swap PR below) to Context in sidebar and footer.Fixes unrelated bugs introduced by rebase, --agent parameter would crash opencode.
Related
Server-side companion: mostlygeek/llama-swap#755 adds
context_lengthandmax_output_tokensto the/v1/modelsresponse. When those fields are present, this PR reads them to configure each model's context window and output token limits automatically — no manual config needed.How did you verify your code works?
bun run typecheckbun test test/provider/provider.test.ts --timeout 30000bun turbo typecheck/connect -> Local (LAN)against multiple local OpenAI-compatible servers on localhost and LAN.Screenshots / recordings
Provider config example
Checklist