feat: enable GrowthBook local gate defaults for P0/P1 features

Add LOCAL_GATE_DEFAULTS mapping in growthbook.ts with 27 feature gate
defaults (25 boolean + 2 object config). Insert local defaults into the
fallback chain of all getter functions so they work regardless of whether
GrowthBook is enabled or disabled:

  env overrides → config overrides → in-memory cache → disk cache
  → LOCAL_GATE_DEFAULTS → caller defaultValue

P0 (local): keybindings, streaming tool exec, cron, JSON tools,
ultrathink, explore/plan agents, deep link, immediate model switch
P1 (API): session memory, auto memory, prompt suggestions, brief mode,
verification agent, away summary, auto dream, idle return prompt
Kill switches: 10 gates kept true to prevent remote disable

New compile flags: AGENT_TRIGGERS, ULTRATHINK, BUILTIN_EXPLORE_PLAN_AGENTS,
LODESTONE, EXTRACT_MEMORIES, VERIFICATION_AGENT, KAIROS_BRIEF, AWAY_SUMMARY

Bypass all local defaults: CLAUDE_CODE_DISABLE_LOCAL_GATES=1
This commit is contained in:
unraid
2026-04-06 17:00:30 +08:00
parent ca630488c6
commit 1b47333d72
7 changed files with 675 additions and 18 deletions

View File

@@ -416,6 +416,72 @@ function syncRemoteEvalToDisk(): void {
}))
}
/**
* Local default overrides for GrowthBook feature gates.
*
* When GrowthBook is not connected (e.g. no 1P event logging, no adapter),
* these values are used instead of the hard-coded defaults (usually false).
* This allows enabling features that have real implementations without
* requiring a GrowthBook server connection.
*
* Set CLAUDE_CODE_DISABLE_LOCAL_GATES=1 to bypass these defaults.
*
* Categories:
* P0 — Pure local features (no external dependencies)
* P1 — Requires Claude API (works with any valid API key)
* KS — Kill switches (default true, keep them true)
*/
const LOCAL_GATE_DEFAULTS: Record<string, unknown> = {
// ── P0: Pure local features ──────────────────────────────────────
tengu_keybinding_customization_release: true, // Custom keybindings
tengu_streaming_tool_execution2: true, // Streaming tool execution
tengu_kairos_cron: true, // Cron/scheduled tasks
tengu_amber_json_tools: true, // Token-efficient JSON tools (~4.5% savings)
tengu_immediate_model_command: true, // Instant /model, /fast, /effort during query
tengu_basalt_3kr: true, // MCP instructions delta (send only changes)
tengu_pebble_leaf_prune: true, // Session storage leaf pruning
tengu_chair_sermon: true, // Message smooshing (merge adjacent blocks)
tengu_lodestone_enabled: true, // Deep link protocol (claude://)
tengu_auto_background_agents: true, // Auto-background agents after 120s
tengu_fgts: true, // Fine-grained tool state in system prompt
// ── P1: API-dependent features ───────────────────────────────────
tengu_session_memory: true, // Session memory (cross-session persistence)
tengu_passport_quail: true, // Auto memory extraction
tengu_moth_copse: true, // Skip memory index, use prefetched memories
tengu_coral_fern: true, // "Searching past context" section
tengu_chomp_inflection: true, // Prompt suggestions
tengu_hive_evidence: true, // Verification agent
tengu_kairos_brief: true, // Brief mode
tengu_kairos_brief_config: { enable_slash_command: true }, // Brief /slash command visibility
tengu_sedge_lantern: true, // Away summary
tengu_onyx_plover: { enabled: true }, // Auto dream (memory consolidation)
tengu_willow_mode: 'dialog', // Idle return prompt
// ── Kill switches (keep true to prevent remote disable) ──────────
tengu_turtle_carbon: true, // Ultrathink extended thinking
tengu_amber_stoat: true, // Built-in Explore/Plan agents
tengu_amber_flint: true, // Agent teams/swarms
tengu_slim_subagent_claudemd: true, // Slim CLAUDE.md for subagents
tengu_birch_trellis: true, // Tree-sitter bash security analysis
tengu_collage_kaleidoscope: true, // macOS clipboard image reading
tengu_compact_cache_prefix: true, // Reuse prompt cache during compaction
tengu_kairos_cron_durable: true, // Persistent cron tasks
tengu_attribution_header: true, // API request attribution header
tengu_slate_prism: true, // Agent progress summaries
}
/**
* Look up a local gate default. Returns undefined if not configured,
* allowing the caller to fall through to the original defaultValue.
*/
function getLocalGateDefault(feature: string): unknown | undefined {
if (process.env.CLAUDE_CODE_DISABLE_LOCAL_GATES) {
return undefined
}
return LOCAL_GATE_DEFAULTS[feature]
}
/**
* Check if GrowthBook operations should be enabled
*/
@@ -500,11 +566,13 @@ const getGrowthBookClient = memoize(
const attributes = getUserAttributes()
const clientKey = getGrowthBookClientKey()
const baseUrl =
process.env.CLAUDE_GB_ADAPTER_URL
|| (process.env.USER_TYPE === 'ant'
? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/'
: 'https://api.anthropic.com/')
const isAdapterMode = !!(process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY)
process.env.CLAUDE_GB_ADAPTER_URL ||
(process.env.USER_TYPE === 'ant'
? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/'
: 'https://api.anthropic.com/')
const isAdapterMode = !!(
process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY
)
if (process.env.USER_TYPE === 'ant') {
logForDebugging(
`GrowthBook: Creating client with clientKey=${clientKey}, attributes: ${jsonStringify(attributes)}`,
@@ -537,7 +605,9 @@ const getGrowthBookClient = memoize(
// remoteEval only works with Anthropic internal API, GrowthBook Cloud doesn't support it
remoteEval: !isAdapterMode,
// cacheKeyAttributes only valid with remoteEval
...(!isAdapterMode ? { cacheKeyAttributes: ['id', 'organizationUUID'] } : {}),
...(!isAdapterMode
? { cacheKeyAttributes: ['id', 'organizationUUID'] }
: {}),
// Add auth headers if available
...(authHeaders.error
? {}
@@ -691,12 +761,14 @@ async function getFeatureValueInternal<T>(
}
if (!isGrowthBookEnabled()) {
return defaultValue
const localDefault = getLocalGateDefault(feature)
return localDefault !== undefined ? (localDefault as T) : defaultValue
}
const growthBookClient = await initializeGrowthBook()
if (!growthBookClient) {
return defaultValue
const localDefault = getLocalGateDefault(feature)
return localDefault !== undefined ? (localDefault as T) : defaultValue
}
// Use cached remote eval values if available (workaround for SDK bug)
@@ -754,7 +826,8 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
}
if (!isGrowthBookEnabled()) {
return defaultValue
const localDefault = getLocalGateDefault(feature)
return localDefault !== undefined ? (localDefault as T) : defaultValue
}
// Log experiment exposure if data is available, otherwise defer until after init
@@ -776,7 +849,16 @@ export function getFeatureValue_CACHED_MAY_BE_STALE<T>(
// Fall back to disk cache (survives across process restarts)
try {
const cached = getGlobalConfig().cachedGrowthBookFeatures?.[feature]
return cached !== undefined ? (cached as T) : defaultValue
if (cached !== undefined) {
return cached as T
}
// Disk cache miss — use local gate defaults before falling back to
// the caller's defaultValue. This covers the common case where
// GrowthBook is "enabled" (user has an API key and analytics are on)
// but has never connected to the remote server, so both in-memory
// and disk caches are empty.
const localDefault = getLocalGateDefault(feature)
return localDefault !== undefined ? (localDefault as T) : defaultValue
} catch {
return defaultValue
}
@@ -823,7 +905,8 @@ export function checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
}
if (!isGrowthBookEnabled()) {
return false
const localDefault = getLocalGateDefault(gate)
return localDefault !== undefined ? Boolean(localDefault) : false
}
// Log experiment exposure if data is available, otherwise defer until after init
@@ -841,7 +924,13 @@ export function checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
return Boolean(gbCached)
}
// Fallback to Statsig cache for migration period
return config.cachedStatsigGates?.[gate] ?? false
const statsigCached = config.cachedStatsigGates?.[gate]
if (statsigCached !== undefined) {
return statsigCached
}
// Neither cache has a value — use local gate defaults
const localDefault = getLocalGateDefault(gate)
return localDefault !== undefined ? Boolean(localDefault) : false
}
/**
@@ -923,7 +1012,8 @@ export async function checkGate_CACHED_OR_BLOCKING(
}
if (!isGrowthBookEnabled()) {
return false
const localDefault = getLocalGateDefault(gate)
return localDefault !== undefined ? Boolean(localDefault) : false
}
// Fast path: disk cache already says true — trust it