import type { ZodType, ZodTypeDef } from 'zod'; import { type ConfigOverrideStore } from '../store/config/index.js'; import { type Logger } from '../utils/logger.js'; /** * Configuration a plugin declares and an operator retunes while a turn is * live. * * `config/runtime.ts` is one Zod schema parsed once into `RUNTIME_DEFAULTS` * and threaded around as a frozen object. Every section in it is one the SDK * author anticipated — taskRouter, compaction, agentBus, plugins, sandbox — * and nothing in `config/` watches, subscribes or changes. So a plugin has * no way to expose an operator-editable section of its own, and retuning one * knob means rebuilding the config object and restarting whatever consumed * it. * * **Live is the whole point.** A registry that resolved a value once and * handed it out would be the frozen object again with more ceremony. The * driver in this same change is the MCP reconnect supervisor, which reads * `get()` on every attempt — so raising `maxAttempts` during an outage takes * effect on the next retry rather than on the next process. * * Programmatic surface only: no JSON-schema export for a UI, and no * redaction. A secret in a config value is the vault's problem, and a * redactor here would be a second, weaker one. */ export interface ConfigScope { /** * The resolved value, now. * * Synchronous and cheap: consumers read it inside loops and timers, and * an async read would make every one of them async for a value already in * memory. */ get(): T; /** * Merge a patch over the override layer and re-validate. * * Throws on a schema-invalid result, leaving the previous value in place * and firing no watcher — validate then assign, never the reverse. * `refuse-do-not-degrade`: a config that accepted a bad value and clamped * it would leave an operator believing they had set something they had * not. */ update(patch: Partial | Record): T; /** Called after a successful update. Returns an unsubscribe. */ watch(listener: (next: T, previous: T) => void): () => void; } export declare class ConfigNamespaceCollisionError extends Error { readonly namespace: string; constructor(namespace: string); } export interface ConfigRegistryOptions { /** * Where overrides persist. Defaults to memory, which is honest for a * process that has not been told where to keep them. */ readonly store?: ConfigOverrideStore; readonly log?: Logger; /** * Prefix for this registry's store keys. Set by {@link ConfigRegistry.scope}. * * Two concurrent turns share a process and a store; without a prefix the * second would read the first's overrides and retune it. */ readonly scopeId?: string; } /** * Output first, input pinned to `unknown` — the shape every other schema * field in this tree uses (`ConnectorDefinition.configSchema`, * `ToolDefinition.inputSchema`). * * Not decoration. `ZodType` alone leaves the input parameter free, so `T` * infers from the schema's INPUT as well as its output, and every `.default()` * field comes back optional: `get().attempts` types as `number | undefined` * for a field that always has a value. What is parsed here really is * `unknown` — a persisted override is whatever was on disk. */ type ConfigSchema = ZodType; export declare class ConfigRegistry { private readonly entries; private readonly store; private readonly persisted; private readonly log; private readonly scopeId; constructor(options?: ConfigRegistryOptions); /** * A registry for one turn, sharing this one's store. * * Namespaces and watchers are per scope — two concurrent turns cannot see * or retune each other — while the store is shared so an operator's * override written under one scope is not lost when it ends. The same * arrangement `ScopedConnectorRegistry` uses, keyed the same way. */ scope(scopeId: string): ConfigRegistry; register(namespace: string, schema: ConfigSchema, options?: { readonly base?: Record; }): ConfigScope; /** Namespaces registered on THIS scope. */ namespaces(): readonly string[]; private applyUpdate; private storeKey; } export {}; //# sourceMappingURL=registry.d.ts.map