/** Spec for one flag in the keyed registry. The id is the object key, not a field. */ export interface FeatureFlagSpec { /** Shipped to clients alongside the values so a UI can render it. */ description: string; default: boolean; /** Full env var name. */ env: string; } /** Wire shape: the keyed spec plus its id, for GET /api/config/feature-flags. */ export interface FeatureFlagDefinition extends FeatureFlagSpec { /** Stable config/wire key. Used in server.yaml, on the CLI, and over HTTP. */ id: FeatureFlagId; } /** * Values arriving from a config source. PARTIAL on purpose: server.yaml, the * CLI and ServerConfig each speak about the flags they mention and stay silent * on the rest, which is what lets the precedence chain fall through. */ export type FeatureFlagValues = Partial>; /** Every flag, resolved. Total — see resolveFeatureFlags(). */ export type ResolvedFeatureFlags = Record; /** * Which rung of the precedence chain decided a flag. * * Kept because the resolved boolean alone cannot answer "why is this on?", and * that is the first question asked of a flag that is on when nobody expected it. * `override` is the legacy ServerConfig field (see resolveFeatureFlags). */ export type FeatureFlagSource = "override" | "env" | "cli" | "yaml" | "default"; export interface FeatureFlagResolution { values: ResolvedFeatureFlags; sources: Record; } /** * The registry, keyed by flag name. * * Prefer `FEATURE_FLAGS.ptyHost` over a string lookup. A typo is a compile * error; `findFeatureFlag("…")` is only for untrusted yaml/CLI tokens. * * `as const` + `keyof` is what makes `flags.ptyHsot` a compile error instead of * `undefined`. A TS enum would add a runtime object without a stronger type. */ export declare const FEATURE_FLAGS: { readonly subagentSessions: { readonly description: "Include provider-created Claude Code and Codex child sessions in history and session APIs."; readonly default: false; readonly env: "THREADBASE_FEATURE_SUBAGENT_SESSIONS"; }; readonly codexSystemPrompt: { readonly description: string; readonly default: false; readonly env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT"; }; readonly sessionRehydration: { readonly description: string; readonly default: true; readonly env: "THREADBASE_FEATURE_SESSION_REHYDRATION"; }; readonly liveActivityPush: { readonly description: string; readonly default: false; readonly env: "THREADBASE_FEATURE_LIVE_ACTIVITY_PUSH"; }; readonly e2ee: { readonly description: string; readonly default: true; readonly env: "THREADBASE_FEATURE_E2EE"; }; readonly accessProbe: { readonly description: string; readonly default: true; readonly env: "THREADBASE_FEATURE_ACCESS_PROBE"; }; readonly ptyHost: { readonly description: string; readonly default: false; readonly env: "THREADBASE_FEATURE_PTY_HOST"; }; }; /** * The registry's ids as a union, derived rather than declared. * * This is what makes `flags.ptyHsot` a compile error instead of `undefined`. * Under the old `Record` an index signature accepted any key, * so a typo in a consumer read as falsy and silently disabled the feature it * was meant to gate — the exact failure the "total map" contract exists to * prevent, reachable through the one door that contract left open. */ export type FeatureFlagId = keyof typeof FEATURE_FLAGS; /** * The yaml / `--feature` ids, in registry order. * * These are the `FEATURE_FLAGS` object keys — not the `THREADBASE_FEATURE_*` * env names. `feature_flags: {"ptyHost":true}` is valid; * `{"THREADBASE_FEATURE_PTY_HOST":true}` is dropped as unknown. */ export declare const FEATURE_FLAG_IDS: FeatureFlagId[]; /** * Ordered list for iteration and the HTTP registry. * * The wire shape stays an array of `{ id, description, default, env }` so a * keyed in-process registry is not a breaking GET /api/config/feature-flags * change. Each `id` is the matching `FEATURE_FLAGS` key. */ export declare const FEATURE_FLAG_LIST: readonly FeatureFlagDefinition[]; export declare function isFeatureFlagId(id: string): id is FeatureFlagId; /** Typed lookup. The flag is always present; unknown names do not type-check. */ export declare function getFeatureFlag(id: K): { id: K; } & { readonly subagentSessions: { readonly description: "Include provider-created Claude Code and Codex child sessions in history and session APIs."; readonly default: false; readonly env: "THREADBASE_FEATURE_SUBAGENT_SESSIONS"; }; readonly codexSystemPrompt: { readonly description: string; readonly default: false; readonly env: "THREADBASE_FEATURE_CODEX_SYSTEM_PROMPT"; }; readonly sessionRehydration: { readonly description: string; readonly default: true; readonly env: "THREADBASE_FEATURE_SESSION_REHYDRATION"; }; readonly liveActivityPush: { readonly description: string; readonly default: false; readonly env: "THREADBASE_FEATURE_LIVE_ACTIVITY_PUSH"; }; readonly e2ee: { readonly description: string; readonly default: true; readonly env: "THREADBASE_FEATURE_E2EE"; }; readonly accessProbe: { readonly description: string; readonly default: true; readonly env: "THREADBASE_FEATURE_ACCESS_PROBE"; }; readonly ptyHost: { readonly description: string; readonly default: false; readonly env: "THREADBASE_FEATURE_PTY_HOST"; }; }[K]; /** * Look a flag up by an unvalidated string (yaml keys, `--feature` tokens). * * In-process code should use `FEATURE_FLAGS.ptyHost` or `getFeatureFlag("ptyHost")`. */ export declare function findFeatureFlag(id: string): FeatureFlagDefinition | undefined; /** * Parse a boolean env var, tri-state. * * `undefined` means "this variable did not speak" — distinct from `false` — so * an unset var lets the next precedence rung (CLI, then yaml) decide instead of * silently forcing the flag off. * * That tri-state return is why this does not reuse one of the existing env * parsers (`parseIncludeAgentsEnv` in server.ts, `isTruthy` in * agent/agent-config.ts, the inline sets in api/middleware/cors.middleware.ts): * all three collapse "absent" into a boolean, which is exactly the distinction * the precedence chain needs. Consolidating those three is a separate change. */ export declare function parseBooleanEnv(raw: string | undefined): boolean | undefined; /** * Drop everything that isn't a known id carrying a boolean. * * A TRUST BOUNDARY, mirroring validateFlagValues() in claude-flags.ts: values * arrive from a user-editable server.yaml. Unknown ids and ill-typed values are * dropped with a warning rather than throwing, so one stale or fat-fingered key * can never stop the server from booting. * * Note the deliberate non-coercion: `"true"` (a string) is dropped, not read as * true. A value that isn't already a boolean means the writer misunderstood the * format, and guessing at intent is how a flag silently ends up on. */ export declare function validateFeatureFlagValues(raw: unknown): FeatureFlagValues; /** * Parse repeatable `--feature ` CLI tokens. * * Errors are returned rather than thrown so the caller owns the exit: a typo on * the command line should print one legible message and stop, not surface a * stack trace. Unlike the yaml path this is strict — a CLI typo is a mistake the * operator is standing right there to fix, whereas a stale yaml key must not * block an unattended boot. */ export declare function parseFeatureFlagArgs(entries: string[]): { values: FeatureFlagValues; errors: string[]; }; /** * Resolve every flag once, at boot. Precedence, highest first: * * override → env → CLI → server.yaml → registry default * * Env beats the CLI so an operator can flip a flag on a supervised instance * (launchd/systemd/Task Scheduler) whose argv is fixed — the same reason * THREADBASE_ALLOW_BROWSER_CORS overrides browser_cors: in server.yaml. * * `override` is the legacy explicit ServerConfig field (codexSystemPromptEnabled), * kept so embedders and tests that set it directly keep working. It is a real * rung here rather than a mutation applied to the finished map afterwards: the * old shape meant the resolver was not actually the single source of truth, and * an override was invisible to anything reporting where a value came from. * * The returned `values` map is TOTAL: every registry id is present, defaults * filled in. Callers therefore index it without a `?? default`, and a flag added * later cannot reach a boolean branch as `undefined` just because an older * server.yaml predates it. `sources` is total for the same reason. */ export declare function resolveFeatureFlags(opts?: { override?: FeatureFlagValues; cli?: FeatureFlagValues; yaml?: FeatureFlagValues; env?: NodeJS.ProcessEnv; }): FeatureFlagResolution; /** Registry ids whose resolved value differs from the registry default. */ export declare function nonDefaultFeatureFlags(values: ResolvedFeatureFlags): FeatureFlagId[]; /** * One line describing the whole resolution: `id=value(source)`, every flag. * * Replaces a boot log that printed only the ids differing from their defaults * under the heading "Feature flags active". That heading was wrong for any * flag whose default is ON — disabling `sessionRehydration` made it appear in * a list of active flags, stating the opposite of what had happened. Printing * the value removes the ambiguity, and printing every flag means the log * answers "what was this process running with" instead of only ever hinting. */ export declare function describeFeatureFlags(resolution: FeatureFlagResolution): string; //# sourceMappingURL=feature-flags.d.ts.map