mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 02:20: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
|
|
@ -193,6 +193,15 @@ const hermesLocalAdapter: ServerAdapterModule = {
|
|||
|
||||
const adaptersByType = new Map<string, ServerAdapterModule>();
|
||||
|
||||
// For builtin types that are overridden by an external adapter, we keep the
|
||||
// original builtin so it can be restored when the override is deactivated.
|
||||
const builtinFallbacks = new Map<string, ServerAdapterModule>();
|
||||
|
||||
// Tracks which override types are currently deactivated (paused). When
|
||||
// paused, `getServerAdapter()` returns the builtin fallback instead of the
|
||||
// external. Persisted across reloads via the same disabled-adapters store.
|
||||
const pausedOverrides = new Set<string>();
|
||||
|
||||
function registerBuiltInAdapters() {
|
||||
for (const adapter of [
|
||||
claudeLocalAdapter,
|
||||
|
|
@ -237,8 +246,13 @@ const externalAdaptersReady: Promise<void> = (async () => {
|
|||
const overriding = BUILTIN_ADAPTER_TYPES.has(externalAdapter.type);
|
||||
if (overriding) {
|
||||
console.log(
|
||||
`[paperclip] External adapter \"${externalAdapter.type}\" overrides built-in adapter`,
|
||||
`[paperclip] External adapter "${externalAdapter.type}" overrides built-in adapter`,
|
||||
);
|
||||
// Save the original builtin for later restoration.
|
||||
const existing = adaptersByType.get(externalAdapter.type);
|
||||
if (existing && !builtinFallbacks.has(externalAdapter.type)) {
|
||||
builtinFallbacks.set(externalAdapter.type, existing);
|
||||
}
|
||||
}
|
||||
adaptersByType.set(
|
||||
externalAdapter.type,
|
||||
|
|
@ -281,6 +295,10 @@ export function requireServerAdapter(type: string): ServerAdapterModule {
|
|||
}
|
||||
|
||||
export function getServerAdapter(type: string): ServerAdapterModule {
|
||||
if (pausedOverrides.has(type)) {
|
||||
const fallback = builtinFallbacks.get(type);
|
||||
if (fallback) return fallback;
|
||||
}
|
||||
return adaptersByType.get(type) ?? processAdapter;
|
||||
}
|
||||
|
||||
|
|
@ -326,6 +344,49 @@ export async function detectAdapterModel(
|
|||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Override pause / resume
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Pause or resume an external override for a builtin adapter type.
|
||||
*
|
||||
* - `paused = true` → subsequent calls to `getServerAdapter(type)` return
|
||||
* the builtin fallback instead of the external adapter. Already-running
|
||||
* agent sessions are unaffected (they hold a reference to the module they
|
||||
* started with).
|
||||
*
|
||||
* - `paused = false` → the external adapter is active again.
|
||||
*
|
||||
* Returns `true` if the state actually changed, `false` if the type is not
|
||||
* an override or was already in the requested state.
|
||||
*/
|
||||
export function setOverridePaused(type: string, paused: boolean): boolean {
|
||||
if (!builtinFallbacks.has(type)) return false;
|
||||
const wasPaused = pausedOverrides.has(type);
|
||||
if (paused && !wasPaused) {
|
||||
pausedOverrides.add(type);
|
||||
console.log(`[paperclip] Override paused for "${type}" — builtin adapter restored`);
|
||||
return true;
|
||||
}
|
||||
if (!paused && wasPaused) {
|
||||
pausedOverrides.delete(type);
|
||||
console.log(`[paperclip] Override resumed for "${type}" — external adapter active`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Check whether the external override for a builtin type is currently paused. */
|
||||
export function isOverridePaused(type: string): boolean {
|
||||
return pausedOverrides.has(type);
|
||||
}
|
||||
|
||||
/** Get the set of types whose overrides are currently paused. */
|
||||
export function getPausedOverrides(): Set<string> {
|
||||
return pausedOverrides;
|
||||
}
|
||||
|
||||
export function findServerAdapter(type: string): ServerAdapterModule | null {
|
||||
return adaptersByType.get(type) ?? null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import {
|
|||
listEnabledServerAdapters,
|
||||
registerServerAdapter,
|
||||
unregisterServerAdapter,
|
||||
isOverridePaused,
|
||||
setOverridePaused,
|
||||
} from "../adapters/registry.js";
|
||||
import { getAdapterSessionManagement } from "@paperclipai/adapter-utils";
|
||||
import {
|
||||
|
|
@ -65,6 +67,8 @@ interface AdapterInfo {
|
|||
disabled: boolean;
|
||||
/** True when an external plugin has replaced a built-in adapter of the same type. */
|
||||
overriddenBuiltin?: boolean;
|
||||
/** True when the external override for a builtin type is currently paused. */
|
||||
overridePaused?: boolean;
|
||||
version?: string;
|
||||
packageName?: string;
|
||||
isLocalPath?: boolean;
|
||||
|
|
@ -108,6 +112,7 @@ function buildAdapterInfo(adapter: ServerAdapterModule, externalRecord: AdapterP
|
|||
loaded: true, // If it's in the registry, it's loaded
|
||||
disabled: disabledSet.has(adapter.type),
|
||||
overriddenBuiltin: externalRecord ? BUILTIN_ADAPTER_TYPES.has(adapter.type) : undefined,
|
||||
overridePaused: BUILTIN_ADAPTER_TYPES.has(adapter.type) ? isOverridePaused(adapter.type) : undefined,
|
||||
// Prefer on-disk package.json so the UI reflects bumps without relying on store-only fields.
|
||||
version: fromDisk ?? externalRecord?.version,
|
||||
packageName: externalRecord?.packageName,
|
||||
|
|
@ -352,6 +357,37 @@ export function adapterRoutes() {
|
|||
res.json({ type: adapterType, disabled, changed });
|
||||
});
|
||||
|
||||
/**
|
||||
* PATCH /api/adapters/:type/override
|
||||
*
|
||||
* Pause or resume an external adapter's override of a builtin type.
|
||||
* When paused, the server returns the builtin adapter for all new requests
|
||||
* (execute, listModels, config schema, etc.). Already-running sessions
|
||||
* keep the adapter they started with.
|
||||
*/
|
||||
router.patch("/adapters/:type/override", async (req, res) => {
|
||||
assertBoard(req);
|
||||
|
||||
const adapterType = req.params.type;
|
||||
const { paused } = req.body as { paused?: boolean };
|
||||
|
||||
if (typeof paused !== "boolean") {
|
||||
res.status(400).json({ error: "\"paused\" (boolean) is required in request body." });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BUILTIN_ADAPTER_TYPES.has(adapterType)) {
|
||||
res.status(400).json({ error: `Type "${adapterType}" is not a builtin adapter.` });
|
||||
return;
|
||||
}
|
||||
|
||||
const changed = setOverridePaused(adapterType, paused);
|
||||
|
||||
logger.info({ type: adapterType, paused, changed }, "Adapter override toggle");
|
||||
|
||||
res.json({ type: adapterType, paused, changed });
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/adapters/:type
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue