mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-16 10:50:38 +09:00
feat: server-side override pause/resume for builtin adapter types
Replace the client-side-only override store with a real server-side toggle. When a developer pauses the external override, the server swaps ALL adapter behavior back to the builtin — execute handler, model listing, config schema, detection — not just the UI parser. Server changes: - registry.ts: builtinFallbacks map + pausedOverrides set + setOverridePaused() - routes/adapters.ts: PATCH /api/adapters/:type/override endpoint + overridePaused in list UI changes: - adapters.ts: setOverridePaused API method + overridePaused on AdapterInfo - AdapterManager: overrideMutation calls server, instant feedback via invalidate() - use-disabled-adapters.ts: reads adapter.overridePaused from server response Removed: - disabled-overrides-store.ts: no longer needed (server is the source of truth) Note: already-running agent sessions keep the adapter they started with. Only new sessions use the swapped adapter.
This commit is contained in:
parent
4efe018a8f
commit
b81d765d2e
6 changed files with 127 additions and 126 deletions
|
|
@ -1,90 +0,0 @@
|
|||
/**
|
||||
* Client-side store for disabled external adapter overrides.
|
||||
*
|
||||
* When an external adapter overrides a builtin type, the user may want to
|
||||
* pause the override (use the builtin parser) without hiding the type from
|
||||
* menus entirely. This is separate from the server's per-type `disabled`
|
||||
* flag which controls menu visibility.
|
||||
*
|
||||
* Persisted to localStorage so it survives page reloads.
|
||||
*
|
||||
* Implements the React external store pattern (subscribe/getSnapshot)
|
||||
* so that components using useSyncExternalStore re-render on changes.
|
||||
*/
|
||||
|
||||
const STORAGE_KEY = "paperclip:disabled-overrides";
|
||||
|
||||
let disabledOverrides = new Set<string>();
|
||||
|
||||
// ── React external store plumbing ────────────────────────────────────
|
||||
|
||||
/** Monotonically increasing version — changes on every mutation. */
|
||||
let snapshotVersion = 0;
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
/** Subscribe to store changes (for useSyncExternalStore). */
|
||||
export function subscribeToOverrides(callback: () => void): () => void {
|
||||
listeners.add(callback);
|
||||
return () => listeners.delete(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value that changes whenever the store changes.
|
||||
* React compares this with Object.is to decide whether to re-render.
|
||||
*/
|
||||
export function getOverridesSnapshot(): number {
|
||||
return snapshotVersion;
|
||||
}
|
||||
|
||||
function emitChange(): void {
|
||||
snapshotVersion++;
|
||||
for (const fn of listeners) fn();
|
||||
}
|
||||
|
||||
// ── Public API ───────────────────────────────────────────────────────
|
||||
|
||||
/** Check if the external override for a builtin type is paused. */
|
||||
export function isOverrideDisabled(type: string): boolean {
|
||||
return disabledOverrides.has(type);
|
||||
}
|
||||
|
||||
/** Pause or resume an external override. */
|
||||
export function setOverrideDisabled(type: string, disabled: boolean): void {
|
||||
if (disabled) {
|
||||
disabledOverrides.add(type);
|
||||
} else {
|
||||
disabledOverrides.delete(type);
|
||||
}
|
||||
persist();
|
||||
emitChange();
|
||||
}
|
||||
|
||||
/** Get all types with paused overrides (sync read). */
|
||||
export function getDisabledOverrides(): Set<string> {
|
||||
return disabledOverrides;
|
||||
}
|
||||
|
||||
// ── Persistence ──────────────────────────────────────────────────────
|
||||
|
||||
function persist(): void {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify([...disabledOverrides]));
|
||||
} catch {
|
||||
// localStorage unavailable — no-op
|
||||
}
|
||||
}
|
||||
|
||||
function hydrate(): void {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (raw) {
|
||||
disabledOverrides = new Set(JSON.parse(raw));
|
||||
}
|
||||
} catch {
|
||||
// corrupt or unavailable — start empty
|
||||
}
|
||||
}
|
||||
|
||||
// Hydrate on module load
|
||||
hydrate();
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { useEffect, useMemo, useSyncExternalStore } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { adaptersApi } from "@/api/adapters";
|
||||
import { setDisabledAdapterTypes } from "@/adapters/disabled-store";
|
||||
import { isOverrideDisabled, subscribeToOverrides, getOverridesSnapshot } from "@/adapters/disabled-overrides-store";
|
||||
import { syncExternalAdapters } from "@/adapters/registry";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
|
||||
|
|
@ -24,10 +23,6 @@ export function useDisabledAdaptersSync(): Set<string> {
|
|||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
// Subscribe to the client-side override store so that
|
||||
// syncExternalAdapters() re-runs when overrides are toggled.
|
||||
useSyncExternalStore(subscribeToOverrides, getOverridesSnapshot);
|
||||
|
||||
// Eagerly register external adapter types in the UI registry so that
|
||||
// consumers calling listUIAdapters() in the same render cycle see them.
|
||||
// This is idempotent — already-registered types are skipped.
|
||||
|
|
@ -39,7 +34,7 @@ export function useDisabledAdaptersSync(): Set<string> {
|
|||
type: a.type,
|
||||
label: a.label,
|
||||
disabled: a.disabled,
|
||||
overrideDisabled: a.overriddenBuiltin ? isOverrideDisabled(a.type) : undefined,
|
||||
overrideDisabled: a.overridePaused,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue