Repository navigation
feat(opencode): local LAN provider discovery + auto-discover models - #27554
androidand wants to merge 10000 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
a56a8fe
Compare
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
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. |
|
It would be very much appreciated. Thanks! |
|
This PR contains too much irrelevant data. You're adding local skills and theme content, along with debugging data from your local system. |
codec.ts:95-102 and lead.ts:195-201, each verified by reading the line at both ends of the range. An earlier check appeared to show lead.ts:195 was wrong; that was the check resolving src/cli/cmd/lead.ts instead of src/peer/lead.ts.
The user rejected the project server, so the premise of my recommendation is gone. Recording that rather than quietly rewriting it: the reasoning was sound against D5, and leaving it standing would point the decision at a destination that does not exist. Two corrections that came out of consulting the peer session that owns lead authority, after it re-checked the code path itself: 1. I cited the absence of SO_PEERCRED/getpeereid as a gap in this codebase. It is a platform constraint: probed on Bun 1.3.14 over a live unix socket, Socket exposes no getPeerName, getPeerCredentials or getPeerCert. An unforgeable connection identity is not reachable here at all, so any proposal resting on peer credentials is not implementable as written. 2. #111 is inert as merged. Nothing passes authorSessionID yet, so the driver check enforces nothing. Recorded as driver-side plumbing rather than a control in force. What survives is the conclusion, not the route: callback confirmation, where the receiver asks the claimed socket whether it sent a given msg_id and the real session answers from its own sent-log. Roughly one frame type plus a sent-log, and it hardens verifyLead as well. The residual belongs in the document rather than in a chat log: against a same-user shell-capable agent, nothing short of OS separation makes sender identity unforgeable. That is why the controls are bounded claims, and why 1b.1 should have the record written by a tool from a session id the tool holds, never parsed from message text.
44 sidecars accumulated on one machine and had to be killed by explicit PID. Two independent mechanisms miss them, and neither is accidental. sweepStaleSidecars removes registration FILES whose process has already exited. It never signals a running process, so it cannot reclaim an orphan by construction. The self-termination guard tests process.ppid !== originalPpid every 2s, which catches a parent that dies after startup. If the parent dies BEFORE the child reads process.ppid, the child is already reparented to 1, originalPpid captures that, and the comparison is false forever. The sidecar then has no parent to signal it and no sweep that can see it. Worse in combination: sidecar-manager.ts:121 spawns before writeSidecarRegistration, so a parent exiting in that window leaves a process that is both unregistered and unreclaimable. All 44 were in exactly that state — none held a session database, and none were routable, since resolveOpencodeSender resolves only through the registry. They could not receive anything and could be addressed by nothing. The existing test at sidecar-e2e.test.ts:222 waits 1500ms for registration before killing the parent, so it covers the case the guard handles. Neither startup race is tested. Phase 0 is confirmation, not implementation: reproduce the race, establish how a sidecar loses its registration, census for other leak shapes, and decide whether the fix must also reclaim sidecars already leaked elsewhere. A fix that only prevents future leaks leaves existing ones running. The spec keeps the boot sweep's safety property explicit: files for dead processes only, never a signal to a running one. Reclaiming a live sidecar is an explicit, confirming operator action that must refuse any candidate whose session is live. No code change.
…tarted The ppid guard compares process.ppid against a value captured at startup, so it can only see a parent that dies afterwards. If the parent is already gone by the time the sidecar reads its own ppid, that value is the reparented one, the comparison is false forever, and the sidecar survives indefinitely — which is how 44 of them accumulated here and had to be killed by explicit PID. Mechanism reproduced before fixing (Bun 1.3.14/macOS): SIGKILL the spawner before the child's first ppid read and the child records ppid 1 and never notices. The parent's stdin pipe closes when the parent dies, at any point in the sidecar's life, so EOF cannot be missed by a startup race. Exit on it. The discriminator is isSocket(), not isFIFO(). Measured: a child spawned with stdio ["pipe"] reports isSocket()=true, isFIFO()=false (mode 140000), while stdio "ignore" reports isCharacterDevice()=true (/dev/null). My first attempt checked isFIFO(), which silently disabled this guard in production and matched nothing — the failing test caught it, not review. The check exists because every test spawns with stdio "ignore", where stdin is /dev/null and reads EOF immediately; an unguarded listener would kill a healthy sidecar on startup. Also bound the stop. shutdown() set shuttingDown before awaiting sidecar.stop(), so a stop() that never settled left the process alive and every later SIGTERM swallowed — killable only by SIGKILL. The exit now happens on a timer rather than on stop() settling. The new test retries rather than asserting once: hitting the race depends on bun's startup time, so an attempt where the sidecar never registered proves nothing and is discarded. It only passes having observed a registration appear and then disappear, so it cannot pass vacuously. Verified by disabling the guard — the test fails, and passes with it restored. test/peer 211 pass / 0 fail. Not addressed here: reclaiming sidecars already leaked elsewhere, and the operator command. Those are separate tasks in the change.
Two peer sessions reproduced the race independently. The second narrowed the window in a way that corrects this document: it is the duration of startSidecar, not an abstract read of process.ppid. Registration is written from inside startSidecar (sidecar-server.ts:156) before originalPpid is captured (sidecar-entry.ts:93), which splits the window in two — alive-and-unregistered before the write, alive-and-registered after it. Both are real; the sweep skips both. All 44 observed were the first. Also records that the discriminator is isSocket() and not isFIFO(), because an isFIFO check reads as correct, matches nothing in production, and would have left the guard dead. That was caught by the failing test rather than by review, which is the only reason it is written down here. Phase 1 is done in 7efff55; Phase 0's reproduction and registration questions are answered. Reclamation of already-leaked sidecars is untouched.
- 5 tests covering disk fallback, synced repo exclusion, empty dir, missing openspec dir, and sibling discovery - Fix querySpecsync to handle specsync returning null (not []) for empty results
- Add fs.existsSync check in runAuto loop; items with missing repos are recorded as 'skipped' with cause 'repo not found' - Exclude skipped outcomes from ChangeOutcome[] (type only allows completed|quarantined) - Add test verifying a resolvable item starts a run and completes
- Add capacity() helper that probes local providers via parentCapacity, returning max(1, free) to bound concurrent item processing - Replace sequential for loop with worker pool bounded by capacity - Add runningRepos per-repo exclusivity guard (one run per repo at a time) - Add tests: 'concurrency follows the fleet' and 'one working tree, one run'
- Track haltsThisPass per pass in processItem - Accumulate consecutiveHalts across passes when no gate passes - Reset consecutiveHalts when a gate passes or a pass has no halts - Stop run with stalled status when consecutiveHalts >= 3 - Report suspected environmental cause in the run report - Add tests: one bad item among five, three identically broken items All 257 loop tests pass, typecheck clean.
- Add claimedItems set to track which items have been claimed - Claim items via claimChange before running gates - Release claims in finally block via releaseChange - Gate loop checks status at each iteration, so cancelled runs break out and release claims - Add test: cancel stops the run and releases claims All 258 loop tests pass, typecheck clean.
- Add mode === 'auto' to fenced condition - Previously auto runs were only fenced when policy was 'scoped' - Add test: authority ceiling is inherited and not widened All 259 loop tests pass, typecheck clean.
- Report generation in runAuto builds a multi-line report from outcomes - Each item listed with repo, change, and outcome (completed/HALTED/SKIPPED) - Environmental guard report also includes aggregated outcomes - Add test: aggregated report covers all items All 260 loop tests pass, typecheck clean.
- Added everFoundItems flag to track whether any items were ever found - If no items found and no outcomes exist, run ends with 'nothing found' report instead of 'drained' - Add test: distinguishes drained from found nothing All 261 loop tests pass, typecheck clean.
- Added --auto flag to opencode loop with documentation - Documents that auto is fenced with QueueDenyRules - Documents where work comes from (work source: specsync or local openspec) - runAuto already uses getWorkItems which uses the work source All 261 loop tests pass, typecheck clean.
- Added test: two-repository fixture drains to completion - Creates two fixture repos with changes, runs auto mode - Verifies both complete and report mentions both changes All 262 loop tests pass, typecheck clean.
- Added test: halt path end-to-end - Creates two fixture repos: one halts, other completes - Verifies report names both changes All 263 loop tests pass, typecheck clean.
ci(release): refuse a skein release version below the provider floor
… exits Reviewer was right, and the criticism was of the same kind I have been making of other checks: the test proved the sidecar terminates, not that the EOF guard terminates it. If the parent dies after the child reads process.ppid, the pre-existing ppid guard kills the sidecar anyway, so removing the EOF guard left the test green. It was a real mutation check on paper and not one in fact. Replaced with a deterministic test. The parent is this test process and stays alive for the whole test, so process.ppid never changes and the ppid guard cannot fire. Closing only the stdin pipe gives the child EOF and nothing else, so if the sidecar exits and unregisters, the EOF guard is the only possible cause. Verified: with the guard disabled this test fails, and only it fails. Removed the timing-based test. With the fix in place the sidecar now dies so fast after its parent exits that the test could no longer reliably observe the registration it was asserting on, and the 4-attempt retry was passing for the wrong reason or failing for a timing reason. Hitting that race depends on bun's startup time and cannot be made deterministic; the new test isolates the mechanism without depending on it at all. Suite went from 15s and flaky to 1.7s and stable. test/peer 211 pass / 0 fail. Typecheck clean, openspec validate passes, fork:verify 251/251 owned, 117/117 patched.
docs(review-on-done): peer sender identity is a claim, not an authentication
fix(sidecar): a sidecar cannot outlive a parent that died before it started
feat(loop): cross-repo auto mode with work-source and drain-complete
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