From 7f580a1e024d20b6aff6486e93e4f485f0d3d01f Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Mon, 17 Aug 2026 08:52:45 +0100 Subject: [PATCH] fix(daemon): providers enable overrides review-disabled providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual disable/enable now write to the SDK's manual lists instead of the review-owned auto list, and display paths use the effective set (review-disabled ∪ manual-disable − manual-enable): - disable: add to manuallyDisabledProviders and clear any manual-enable override for that provider - enable: clear the manual disable and record a manual-enable override so the Nostr review sync does not re-disable it - /providers, cached models and per-model provider views: effective set - refreshProvidersAndModels: mirror the review-disabled list exactly (null from the sync means "unchanged", so no clobbering with []) --- src/daemon/http/index.ts | 61 +++++++++++++++++++++++++++++++--------- src/daemon/models.ts | 39 +++++++++++++++++-------- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/daemon/http/index.ts b/src/daemon/http/index.ts index e49bedc..186f538 100644 --- a/src/daemon/http/index.ts +++ b/src/daemon/http/index.ts @@ -1048,8 +1048,16 @@ export function createDaemonRequestHandler(deps: { const state = deps.store.getState(); const baseUrlsList: string[] = state.baseUrlsList || []; - const disabledProviders: string[] = [ - ...(state.disabledProviders || []), + // User-driven disables belong in the *manual* disabled list. The + // review-based list (setDisabledProviders) is owned by the Nostr + // kind-38425 review sync, which overwrites it and would otherwise + // silently re-enable manually-disabled providers. Disabling also + // clears any manual-enable override for that provider. + const manuallyDisabledProviders: string[] = [ + ...(state.manuallyDisabledProviders || []), + ]; + const manuallyEnabledProviders: string[] = [ + ...(state.manuallyEnabledProviders || []), ]; const toDisable: string[] = []; @@ -1060,15 +1068,21 @@ export function createDaemonRequestHandler(deps: { idx < baseUrlsList.length ) { const baseUrl = baseUrlsList[idx]!; - if (!disabledProviders.includes(baseUrl)) { - disabledProviders.push(baseUrl); + if (!manuallyDisabledProviders.includes(baseUrl)) { + manuallyDisabledProviders.push(baseUrl); toDisable.push(baseUrl); } + const enabledPos = manuallyEnabledProviders.indexOf(baseUrl); + if (enabledPos !== -1) { + manuallyEnabledProviders.splice(enabledPos, 1); + } } } - deps.store.getState().setDisabledProviders(disabledProviders); - deps.discoveryAdapter.setDisabledProviders(disabledProviders); + deps.store.getState().setManuallyDisabledProviders(manuallyDisabledProviders); + deps.discoveryAdapter.setManuallyDisabledProviders(manuallyDisabledProviders); + deps.store.getState().setManuallyEnabledProviders(manuallyEnabledProviders); + deps.discoveryAdapter.setManuallyEnabledProviders(manuallyEnabledProviders); res.writeHead(200, { "Content-Type": "application/json" }); res.end( @@ -1104,8 +1118,14 @@ export function createDaemonRequestHandler(deps: { const state = deps.store.getState(); const baseUrlsList: string[] = state.baseUrlsList || []; - const disabledProviders: string[] = [ - ...(state.disabledProviders || []), + // Re-enabling clears the manual disable and records a manual-enable + // override so the review sync (which disables providers without an + // lgtm review) does not silently re-disable it on the next pass. + const manuallyDisabledProviders: string[] = [ + ...(state.manuallyDisabledProviders || []), + ]; + const manuallyEnabledProviders: string[] = [ + ...(state.manuallyEnabledProviders || []), ]; const toEnable: string[] = []; @@ -1116,16 +1136,21 @@ export function createDaemonRequestHandler(deps: { idx < baseUrlsList.length ) { const baseUrl = baseUrlsList[idx]!; - const pos = disabledProviders.indexOf(baseUrl); + const pos = manuallyDisabledProviders.indexOf(baseUrl); if (pos !== -1) { - disabledProviders.splice(pos, 1); - toEnable.push(baseUrl); + manuallyDisabledProviders.splice(pos, 1); } + if (!manuallyEnabledProviders.includes(baseUrl)) { + manuallyEnabledProviders.push(baseUrl); + } + toEnable.push(baseUrl); } } - deps.store.getState().setDisabledProviders(disabledProviders); - deps.discoveryAdapter.setDisabledProviders(disabledProviders); + deps.store.getState().setManuallyDisabledProviders(manuallyDisabledProviders); + deps.discoveryAdapter.setManuallyDisabledProviders(manuallyDisabledProviders); + deps.store.getState().setManuallyEnabledProviders(manuallyEnabledProviders); + deps.discoveryAdapter.setManuallyEnabledProviders(manuallyEnabledProviders); res.writeHead(200, { "Content-Type": "application/json" }); res.end( @@ -1344,7 +1369,15 @@ export function createDaemonRequestHandler(deps: { const state = deps.store.getState(); const baseUrlsList: string[] = state.baseUrlsList || []; - const disabledProviders: string[] = state.disabledProviders || []; + const manuallyEnabled = new Set( + state.manuallyEnabledProviders || [], + ); + const disabledProviders: string[] = [ + ...new Set([ + ...(state.disabledProviders || []), + ...(state.manuallyDisabledProviders || []), + ]), + ].filter((url) => !manuallyEnabled.has(url)); const providers = baseUrlsList.map((baseUrl, index) => ({ index, diff --git a/src/daemon/models.ts b/src/daemon/models.ts index c0f55b7..985077f 100644 --- a/src/daemon/models.ts +++ b/src/daemon/models.ts @@ -41,8 +41,17 @@ export function createModelService( const currentProviders = new Set( modelManager.getBaseUrls().map(normalizeBaseUrl), ); + const s = store.getState(); + const manuallyEnabled = new Set( + (s.manuallyEnabledProviders || []).map(normalizeBaseUrl), + ); const disabledProviders = new Set( - (store.getState().disabledProviders || []).map(normalizeBaseUrl), + [ + ...(s.disabledProviders || []), + ...(s.manuallyDisabledProviders || []), + ] + .map(normalizeBaseUrl) + .filter((url) => !manuallyEnabled.has(url)), ); const bestById = new Map(); @@ -140,7 +149,16 @@ export function createModelService( ): Promise => { await ensureProvidersBootstrapped(); - const disabledSet = new Set(store.getState().disabledProviders || []); + const s = store.getState(); + const manuallyEnabled = new Set( + (s.manuallyEnabledProviders || []).map(normalizeBaseUrl), + ); + const disabledSet = new Set( + [ + ...(s.disabledProviders || []), + ...(s.manuallyDisabledProviders || []), + ].filter((url) => !manuallyEnabled.has(normalizeBaseUrl(url))), + ); // Use the SDK ranking (sorted by prompt+completion per million tokens) // so the display order matches real routing. includeDisabled keeps @@ -212,28 +230,27 @@ export function createModelService( undefined, true, ); - if (reviewedDisabled.length > 0) { + if (reviewedDisabled && reviewedDisabled.length > 0) { console.log( `Review sync disabled ${reviewedDisabled.length} provider(s): ${reviewedDisabled.join(", ")}`, ); } // Sync discovered providers into the store - const { baseUrlsList, setBaseUrlsList, disabledProviders, setDisabledProviders } = - store.getState() as any; + const { setBaseUrlsList, setDisabledProviders } = store.getState() as any; // Replace baseUrlsList with the fresh provider list setBaseUrlsList(providers); - // Merge review-disabled providers into the store's disabled list - const existingDisabled = new Set(disabledProviders || []); - for (const url of reviewedDisabled) { - existingDisabled.add(url); + // Mirror the review-disabled set into the store's auto-disabled list. + // `null` means the review sync left the adapter unchanged (e.g. no lgtm + // reviews found), so we must not clobber the store list with an empty array. + if (reviewedDisabled !== null) { + setDisabledProviders(reviewedDisabled); } - setDisabledProviders([...existingDisabled]); console.log( - `Provider refresh complete: ${providers.length} total, ${existingDisabled.size} disabled`, + `Provider refresh complete: ${providers.length} total, ${reviewedDisabled?.length ?? store.getState().disabledProviders?.length ?? 0} review-disabled`, ); };