claude-code/spec/06_services_context_state.md
2026-04-01 01:20:27 +05:30

2631 lines
93 KiB
Markdown

# Claude Code — Services, Context, State & Screens
This document exhaustively covers all files in `src/services/`, `src/context/`, `src/bootstrap/state.ts`, `src/coordinator/`, `src/server/`, and `src/screens/`. Every exported symbol is listed with its full signature, key logic, configuration, and dependencies.
---
## Table of Contents
1. [bootstrap/state.ts](#bootstrapstatets)
2. [coordinator/coordinatorMode.ts](#coordinatorcoordinatormodetse)
3. [server/types.ts](#servertypests)
4. [server/createDirectConnectSession.ts](#servercreatedirectconnectsessionts)
5. [server/directConnectManager.ts](#serverdirectconnectmanagerts)
6. [services/analytics/config.ts](#servicesanalyticsconfigts)
7. [services/analytics/growthbook.ts](#servicesanalyticsgrowthbookts)
8. [services/analytics/metadata.ts](#servicesanalyticsmetadatats)
9. [services/analytics/index.ts](#servicesanalyticsindexts)
10. [services/analytics/sink.ts](#servicesanalyticssinkt)
11. [services/analytics/sinkKillswitch.ts](#servicesanalyticssinkKillswitchts)
12. [services/analytics/datadog.ts](#servicesanalyticsdatadogts)
13. [services/analytics/firstPartyEventLogger.ts](#servicesanalyticsfirstpartyeventloggerts)
14. [services/analytics/firstPartyEventLoggingExporter.ts](#servicesanalyticsfirstpartyeventloggingexporterts)
15. [services/api/bootstrap.ts](#servicesapibootstrapts)
16. [services/api/client.ts](#servicesapiclientts)
17. [services/api/claude.ts](#servicesapicludets)
18. [services/api/dumpPrompts.ts](#servicesapidumppromptsts)
19. [services/api/emptyUsage.ts](#servicesapiemptyusagets)
20. [services/api/errorUtils.ts](#servicesapierrorutilsts)
21. [services/api/errors.ts](#servicesapierrorsts)
22. [services/api/filesApi.ts](#servicesapifilesapits)
23. [services/api/firstTokenDate.ts](#servicesapifirsttokendatets)
24. [services/api/grove.ts](#servicesapigrovet)
25. [services/api/logging.ts](#servicesapiloggingts)
26. [services/api/metricsOptOut.ts](#servicesapimetricsoptoutts)
27. [services/api/overageCreditGrant.ts](#servicesapiovaragecreditgrantts)
28. [services/api/promptCacheBreakDetection.ts](#servicesapipromptcachebreakdetectionts)
29. [services/api/referral.ts](#servicesapireferralts)
30. [services/api/sessionIngress.ts](#servicesapisessioningressts)
31. [services/api/ultrareviewQuota.ts](#servicesapiultrareviewquotats)
32. [services/api/usage.ts](#servicesapiusagets)
33. [services/api/withRetry.ts](#servicesapiwithretryts)
34. [services/AgentSummary/agentSummary.ts](#servicesagentsummaryagentsummaryts)
35. [services/autoDream/autoDream.ts](#servicesautodreamautodreamts)
36. [services/autoDream/config.ts](#servicesautodreamconfigts)
37. [services/autoDream/consolidationLock.ts](#servicesautodreamconsolidationlockts)
38. [services/autoDream/consolidationPrompt.ts](#servicesautodreamconsolidationpromptt)
39. [services/awaySummary.ts](#servicesawaysummaryts)
40. [services/claudeAiLimits.ts](#servicesclaudeailimitsts)
41. [services/claudeAiLimitsHook.ts](#servicesclaudeailimitshookts)
42. [services/compact/apiMicrocompact.ts](#servicescompactapimicrocompactts)
43. [services/compact/autoCompact.ts](#servicescompactautocompactts)
44. [services/compact/compact.ts](#servicescompactcompactts)
45. [services/compact/compactWarningHook.ts](#servicescompactcompactwarninghookts)
46. [services/compact/compactWarningState.ts](#servicescompactcompactwarningstatets)
47. [services/compact/grouping.ts](#servicescompactgroupingts)
48. [services/compact/microCompact.ts](#servicescompactmicrocompactts)
49. [services/compact/postCompactCleanup.ts](#servicescompactpostcompactcleanuptes)
50. [services/compact/prompt.ts](#servicescompactpromptts)
51. [services/compact/sessionMemoryCompact.ts](#servicescompactsessionmemorycompactts)
52. [services/compact/timeBasedMCConfig.ts](#servicescompacttimebasedmcconfigts)
53. [services/diagnosticTracking.ts](#servicesdiagnostictrackingtss)
54. [services/internalLogging.ts](#servicesinternalloggingts)
55. [services/MagicDocs/magicDocs.ts](#servicesmagicdocsmagicdocsts)
56. [services/MagicDocs/prompts.ts](#servicesmagicdocspromptsts)
57. [services/mcpServerApproval.tsx](#servicesmcpserverapprovala)
58. [services/mockRateLimits.ts](#servicesmockratelimitsts)
59. [services/MCP (mcp/)](#services-mcp)
60. [services/notifier.ts](#servicesnotifierts)
61. [services/preventSleep.ts](#servicespreventsleepts)
62. [services/PromptSuggestion/promptSuggestion.ts](#servicespromptsuggestionpromptsuggestsionts)
63. [services/PromptSuggestion/speculation.ts](#servicespromptsuggestionspeculationts)
64. [services/rateLimitMocking.ts](#servicesratelimitmockingts)
65. [services/rateLimitMessages.ts](#servicesratelimitmessagests)
66. [services/SessionMemory/prompts.ts](#servicessessionmemorypromptsts)
67. [services/SessionMemory/sessionMemory.ts](#servicessessionmemorysessionmemoryts)
68. [services/SessionMemory/sessionMemoryUtils.ts](#servicessessionmemorysessionmemoryutilsts)
69. [services/tokenEstimation.ts](#servicestokenestimationts)
70. [services/vcr.ts](#servicesvcrts)
71. [services/voice.ts](#servicesvoicets)
72. [services/voiceKeyterms.ts](#servicesvoicekeytermsss)
73. [services/voiceStreamSTT.ts](#servicesvoicestreamsttts)
74. [context/QueuedMessageContext.tsx](#contextqueuedmessagecontexttsx)
75. [context/fpsMetrics.tsx](#contextfpsmetricstsx)
76. [context/mailbox.tsx](#contextmailboxtsx)
77. [context/modalContext.tsx](#contextmodalcontexttsx)
78. [context/notifications.tsx](#contextnotificationstsx)
79. [context/overlayContext.tsx](#contextoverlaycontexttsx)
80. [context/promptOverlayContext.tsx](#contextpromptoverlaycontexttsx)
81. [context/stats.tsx](#contextstatstsx)
82. [context/voice.tsx](#contextvoicetsx)
83. [screens/Doctor.tsx](#screensdoctortsx)
84. [screens/REPL.tsx](#screensrepltsx)
85. [screens/ResumeConversation.tsx](#screensresumeconversationtsx)
---
## bootstrap/state.ts
**Path:** `src/bootstrap/state.ts`
**Purpose:** The single global session state singleton for the entire Claude Code process. Acts as the authoritative source of truth for all per-session metrics, model configuration, telemetry handles, and feature flags. Designed as a strict leaf in the import DAG — imports nothing from `src/utils/` except via explicit safe indirection.
**Key Types Exported:**
```typescript
export type ChannelEntry =
| { kind: 'plugin'; name: string; marketplace: string; dev?: boolean }
| { kind: 'server'; name: string; dev?: boolean }
export type AttributedCounter = {
add(value: number, additionalAttributes?: Attributes): void
}
```
**`State` Type (internal, not exported directly):** Contains ~80 fields including:
- `originalCwd: string` — resolved cwd at process start (NFC-normalized, symlinks resolved)
- `projectRoot: string` — stable identity root (set at startup, never changed by mid-session EnterWorktreeTool)
- `totalCostUSD: number`, `totalAPIDuration: number`, `totalAPIDurationWithoutRetries: number`
- `totalToolDuration: number`, `turnHookDurationMs: number`, `turnToolDurationMs: number`
- `totalLinesAdded: number`, `totalLinesRemoved: number`
- `cwd: string` — current working directory (mutable, changes with shell.ts setCwd)
- `modelUsage: { [modelName: string]: ModelUsage }` — per-model usage tracking
- `mainLoopModelOverride: ModelSetting | undefined`, `initialMainLoopModel: ModelSetting`
- `sessionId: SessionId` — UUID regenerated on `clearConversation`
- `parentSessionId: SessionId | undefined` — previous session (for lineage tracking)
- `isInteractive: boolean`, `kairosActive: boolean`, `strictToolResultPairing: boolean`
- `sdkAgentProgressSummariesEnabled: boolean`, `userMsgOptIn: boolean`
- `clientType: string` (default `'cli'`), `sessionSource: string | undefined`
- `meter: Meter | null`, `sessionCounter`, `locCounter`, `prCounter`, `commitCounter`, `costCounter`, `tokenCounter`, `codeEditToolDecisionCounter`, `activeTimeCounter` — OTel metrics
- `sessionId: SessionId` (randomUUID at init), `parentSessionId: SessionId | undefined`
- `loggerProvider: LoggerProvider | null`, `eventLogger: ReturnType<typeof logs.getLogger> | null`
- `meterProvider: MeterProvider | null`, `tracerProvider: BasicTracerProvider | null`
- `agentColorMap: Map<string, AgentColorName>`, `agentColorIndex: number`
- `lastAPIRequest`, `lastAPIRequestMessages`, `lastClassifierRequests`, `cachedClaudeMdContent`
- `inMemoryErrorLog: Array<{ error: string; timestamp: string }>`
- `inlinePlugins: string[]`, `chromeFlagOverride: boolean | undefined`
- `sessionBypassPermissionsMode: boolean`, `scheduledTasksEnabled: boolean`
- `sessionCronTasks: SessionCronTask[]`, `sessionCreatedTeams: Set<string>`
- `sessionTrustAccepted: boolean`, `sessionPersistenceDisabled: boolean`
- `hasExitedPlanMode: boolean`, `needsPlanModeExitAttachment: boolean`, `needsAutoModeExitAttachment: boolean`
- `initJsonSchema: Record<string, unknown> | null`, `registeredHooks: Partial<Record<HookEvent, RegisteredHookMatcher[]>> | null`
- `planSlugCache: Map<string, string>` — sessionId → wordSlug
- `teleportedSessionInfo: { isTeleported, hasLoggedFirstMessage, sessionId } | null`
- `invokedSkills: Map<string, { skillName, skillPath, content, invokedAt, agentId }>` — keyed by `"${agentId ?? ''}:${skillName}"`
- `slowOperations: Array<{ operation, durationMs, timestamp }>` — ant-only dev bar
- `sdkBetas: string[] | undefined`, `mainThreadAgentType: string | undefined`
- `isRemoteMode: boolean`, `directConnectServerUrl: string | undefined`
- `systemPromptSectionCache: Map<string, string | null>`, `lastEmittedDate: string | null`
- `additionalDirectoriesForClaudeMd: string[]`, `allowedChannels: ChannelEntry[]`, `hasDevChannels: boolean`
- `sessionProjectDir: string | null` — transcript directory override
- `promptCache1hAllowlist: string[] | null`, `promptCache1hEligible: boolean | null`
- `afkModeHeaderLatched: boolean | null`, `fastModeHeaderLatched: boolean | null`
- `cacheEditingHeaderLatched: boolean | null`, `thinkingClearLatched: boolean | null`
- `promptId: string | null`, `lastMainRequestId: string | undefined`
- `lastApiCompletionTimestamp: number | null`, `pendingPostCompaction: boolean`
**Exported Functions (getters/setters/mutators):**
```typescript
export function getSessionId(): SessionId
export function regenerateSessionId(options?: { setCurrentAsParent?: boolean }): SessionId
export function getParentSessionId(): SessionId | undefined
export function switchSession(sessionId: SessionId, projectDir?: string | null): void
export const onSessionSwitch: Signal<[id: SessionId]>['subscribe']
export function getSessionProjectDir(): string | null
export function getOriginalCwd(): string
export function getProjectRoot(): string
export function setOriginalCwd(cwd: string): void
export function setProjectRoot(cwd: string): void // --worktree startup only
export function getCwdState(): string
export function setCwdState(cwd: string): void
export function getDirectConnectServerUrl(): string | undefined
export function setDirectConnectServerUrl(url: string): void
export function addToTotalDurationState(duration: number, durationWithoutRetries: number): void
export function resetTotalDurationStateAndCost_FOR_TESTS_ONLY(): void
export function addToTotalCostState(cost: number, modelUsage: ModelUsage, model: string): void
export function getTotalCostUSD(): number
export function getTotalAPIDuration(): number
export function getTotalDuration(): number
export function getTotalAPIDurationWithoutRetries(): number
export function getTotalToolDuration(): number
export function addToToolDuration(duration: number): void
export function getTurnHookDurationMs(): number
export function addToTurnHookDuration(duration: number): void
export function resetTurnHookDuration(): void
export function getTurnHookCount(): number
export function getTurnToolDurationMs(): number
export function resetTurnToolDuration(): void
export function getTurnToolCount(): number
export function getTurnClassifierDurationMs(): number
export function addToTurnClassifierDuration(duration: number): void
export function resetTurnClassifierDuration(): void
export function getTurnClassifierCount(): number
export function getStatsStore(): { observe(name: string, value: number): void } | null
export function setStatsStore(store: { observe(name: string, value: number): void } | null): void
export function updateLastInteractionTime(immediate?: boolean): void
export function flushInteractionTime(): void
export function addToTotalLinesChanged(added: number, removed: number): void
export function getTotalLinesAdded(): number
export function getTotalLinesRemoved(): number
export function getTotalInputTokens(): number
export function getTotalOutputTokens(): number
export function getTotalCacheReadInputTokens(): number
export function getTotalCacheCreationInputTokens(): number
export function getTotalWebSearchRequests(): number
export function getTurnOutputTokens(): number
export function getCurrentTurnTokenBudget(): number | null
export function snapshotOutputTokensForTurn(budget: number | null): void
export function getBudgetContinuationCount(): number
export function incrementBudgetContinuationCount(): void
export function setHasUnknownModelCost(): void
export function hasUnknownModelCost(): boolean
export function getLastMainRequestId(): string | undefined
export function setLastMainRequestId(requestId: string): void
export function getLastApiCompletionTimestamp(): number | null
export function setLastApiCompletionTimestamp(timestamp: number): void
export function markPostCompaction(): void
export function consumePostCompaction(): boolean
export function getLastInteractionTime(): number
export function markScrollActivity(): void
export function getIsScrollDraining(): boolean
export function waitForScrollIdle(): Promise<void>
export function getModelUsage(): { [modelName: string]: ModelUsage }
export function getUsageForModel(model: string): ModelUsage | undefined
export function getMainLoopModelOverride(): ModelSetting | undefined
export function getInitialMainLoopModel(): ModelSetting
export function setMainLoopModelOverride(model: ModelSetting | undefined): void
// ... and many more setters for isInteractive, clientType, sessionSource, telemetry counters, etc.
```
**Key Logic:**
- `STATE` is a module-level singleton initialized via `getInitialState()` on import
- `updateLastInteractionTime(immediate?)`: deferred by default (batches keypresses into single Date.now() per Ink render); pass `immediate=true` for post-render useEffect callbacks
- `flushInteractionTime()`: called by Ink before each render cycle
- Scroll drain: `markScrollActivity()` sets a debounce flag (`scrollDraining`) for 150ms; background intervals call `getIsScrollDraining()` to yield; `waitForScrollIdle()` polls with 150ms intervals
- `switchSession()` atomically updates `sessionId + sessionProjectDir`; emits `sessionSwitched` signal
- `regenerateSessionId()` can optionally set current as parent (used for plan mode → implementation lineage)
- `markPostCompaction()` / `consumePostCompaction()`: one-shot latch, auto-resets after first consumption
**Configuration:**
- `SCROLL_DRAIN_IDLE_MS = 150`
- `RESERVOIR_SIZE` (histogram sampling) = 1024 (in stats.tsx)
**Dependencies:** `@anthropic-ai/sdk`, `@opentelemetry/api`, `@opentelemetry/sdk-*`, `src/utils/crypto.js`, `src/utils/signal.js`, `src/utils/settings/settingsCache.js`, `src/types/ids.js`
---
## coordinator/coordinatorMode.ts
**Path:** `src/coordinator/coordinatorMode.ts`
**Purpose:** Implements multi-worker "coordinator mode" where Claude Code orchestrates multiple parallel subagents. Provides the system prompt, user context injection, mode detection, and session-resume alignment logic.
**Exports:**
```typescript
export function isCoordinatorMode(): boolean
export function matchSessionMode(
sessionMode: 'coordinator' | 'normal' | undefined
): string | undefined
export function getCoordinatorUserContext(
mcpClients: ReadonlyArray<{ name: string }>,
scratchpadDir?: string
): { [k: string]: string }
export function getCoordinatorSystemPrompt(): string
```
**Key Logic:**
- `isCoordinatorMode()`: reads `CLAUDE_CODE_COORDINATOR_MODE` env var; only active when `feature('COORDINATOR_MODE')` bundle flag is set
- `matchSessionMode()`: when resuming a session, aligns the current coordinator mode with the stored session mode. Flips `process.env.CLAUDE_CODE_COORDINATOR_MODE` in-place (since `isCoordinatorMode()` reads it live). Returns a user-visible warning message if mode was switched, `undefined` if no change needed. Logs `tengu_coordinator_mode_switched` analytics event
- `getCoordinatorUserContext()`: returns `{ workerToolsContext: string }` with worker tool list, MCP server names, and scratchpad directory (if gate `tengu_scratch` enabled). In `CLAUDE_CODE_SIMPLE` mode, limits worker tools to Bash/Read/Edit
- `getCoordinatorSystemPrompt()`: returns a multi-section system prompt (1500+ chars) describing coordinator role, available tools (Agent, SendMessage, TaskStop), task workflow phases (Research → Synthesis → Implementation → Verification), concurrency strategy, worker prompt writing guidelines, and full example session
**Internal Constants:**
```typescript
const INTERNAL_WORKER_TOOLS = new Set([
TEAM_CREATE_TOOL_NAME,
TEAM_DELETE_TOOL_NAME,
SEND_MESSAGE_TOOL_NAME,
SYNTHETIC_OUTPUT_TOOL_NAME,
])
```
**Configuration:**
- `COORDINATOR_MODE` bundle feature flag
- `CLAUDE_CODE_COORDINATOR_MODE` env var
- `CLAUDE_CODE_SIMPLE` env var — restricts worker tool set to Bash/Read/Edit
- GrowthBook gate `tengu_scratch` — enables scratchpad directory context
**Dependencies:** `bun:bundle`, `constants/tools.js`, `services/analytics/growthbook.js`, `services/analytics/index.js`, various tool name constants, `utils/envUtils.js`
---
## server/types.ts
**Path:** `src/server/types.ts`
**Purpose:** Shared type definitions for the Claude Code server (direct-connect mode). Provides the Zod validation schema for session creation responses.
**Exports:**
```typescript
export const connectResponseSchema: () => ZodObject<{
session_id: ZodString
ws_url: ZodString
work_dir: ZodString.optional()
}>
export type ServerConfig = {
port: number
host?: string
authToken?: string
}
export type SessionState = 'starting' | 'running' | 'detached' | 'stopping' | 'stopped'
export type SessionInfo = {
sessionId: string
state: SessionState
wsUrl: string
workDir?: string
createdAt: number
lastActivity: number
}
export type SessionIndexEntry = {
sessionId: string
createdAt: number
workDir?: string
}
export type SessionIndex = Record<string, SessionIndexEntry>
```
**Key Logic:** `connectResponseSchema()` is a factory function (not a cached value) to allow Zod to be lazy-loaded. Used by `createDirectConnectSession.ts` to validate the `POST /sessions` response body.
**Dependencies:** `zod`
---
## server/createDirectConnectSession.ts
**Path:** `src/server/createDirectConnectSession.ts`
**Purpose:** Creates a session on a remote direct-connect Claude Code server. Posts to `/sessions`, validates response, returns a `DirectConnectConfig` ready for use by the REPL or headless runner.
**Exports:**
```typescript
export class DirectConnectError extends Error {
constructor(message: string)
name: 'DirectConnectError'
}
export async function createDirectConnectSession(opts: {
serverUrl: string
authToken?: string
cwd: string
dangerouslySkipPermissions?: boolean
}): Promise<{
config: DirectConnectConfig
workDir?: string
}>
```
**Key Logic:**
- POSTs `{ cwd, dangerously_skip_permissions? }` as JSON to `${serverUrl}/sessions`
- Sends `Authorization: Bearer ${authToken}` if provided
- Validates response JSON via `connectResponseSchema().safeParse()`
- Returns `{ config: { serverUrl, sessionId, wsUrl, authToken }, workDir }`
- Throws `DirectConnectError` on fetch failure, non-OK HTTP status, or response parse failure
**Dependencies:** `server/types.js`, `server/directConnectManager.js`, `utils/errors.js`, `utils/slowOperations.js`
---
## server/directConnectManager.ts
**Path:** `src/server/directConnectManager.ts`
**Purpose:** WebSocket client for communicating with a remote direct-connect Claude Code server. Handles message routing, permission request/response, interrupt signals, and connection lifecycle.
**Exports:**
```typescript
export type DirectConnectConfig = {
serverUrl: string
sessionId: string
wsUrl: string
authToken?: string
}
export type DirectConnectCallbacks = {
onMessage: (message: SDKMessage) => void
onPermissionRequest: (request: SDKControlPermissionRequest, requestId: string) => void
onConnected?: () => void
onDisconnected?: () => void
onError?: (error: Error) => void
}
export class DirectConnectSessionManager {
constructor(config: DirectConnectConfig, callbacks: DirectConnectCallbacks)
connect(): void
sendMessage(content: RemoteMessageContent): boolean
respondToPermissionRequest(requestId: string, result: RemotePermissionResponse): void
sendInterrupt(): void
disconnect(): void
isConnected(): boolean
}
```
**Key Logic:**
- `connect()`: opens WebSocket with `Authorization: Bearer` header (Bun WebSocket headers override); sets up `open`, `message`, `close`, `error` listeners
- Message parsing: splits NDJSON lines, parses each line, dispatches:
- `control_request` with subtype `can_use_tool``onPermissionRequest()`
- unrecognized control subtypes → auto-sends error response (prevents server hang)
- Filtered out: `control_response`, `keep_alive`, `control_cancel_request`, `streamlined_text`, `streamlined_tool_use_summary`, system messages with subtype `post_turn_summary`
- All others → `onMessage()`
- `sendMessage()`: formats as `SDKUserMessage` (`{ type: 'user', message: { role: 'user', content }, parent_tool_use_id: null, session_id: '' }`)
- `respondToPermissionRequest()`: formats as `SDKControlResponse` with `behavior` and either `updatedInput` (allow) or `message` (deny)
- `sendInterrupt()`: sends `{ type: 'control_request', request_id: crypto.randomUUID(), request: { subtype: 'interrupt' } }`
**Dependencies:** `entrypoints/agentSdkTypes.js`, `entrypoints/sdk/controlTypes.js`, `remote/RemoteSessionManager.js`, `utils/debug.js`, `utils/slowOperations.js`, `utils/teleport/api.js`
---
## services/analytics/config.ts
**Path:** `src/services/analytics/config.ts`
**Purpose:** Shared analytics configuration — common logic for disabling analytics across all backends.
**Exports:**
```typescript
export function isAnalyticsDisabled(): boolean
export function isFeedbackSurveyDisabled(): boolean
```
**Key Logic:**
- `isAnalyticsDisabled()`: returns `true` when `NODE_ENV === 'test'`, `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY` truthy, or `isTelemetryDisabled()` is true
- `isFeedbackSurveyDisabled()`: returns `true` when `NODE_ENV === 'test'` or `isTelemetryDisabled()` — does NOT gate on 3P providers (Bedrock/Vertex/Foundry) since the survey is local UI with no transcript data; enterprise captures via OTEL
**Dependencies:** `utils/envUtils.js`, `utils/privacyLevel.js`
---
## services/analytics/growthbook.ts
**Path:** `src/services/analytics/growthbook.ts`
**Purpose:** GrowthBook feature flag and dynamic config client. Provides cached and blocking access to feature gates, handles remote eval with disk persistence, manages refresh lifecycle, and exposes override APIs for development/testing.
**Key Types:**
```typescript
export type GrowthBookUserAttributes = {
user_id?: string
org_id?: string
user_type?: string
// ... other Statsig-compatible attributes
}
```
**Exports:**
```typescript
export function onGrowthBookRefresh(listener: () => void): () => void
export function hasGrowthBookEnvOverride(feature: string): boolean
export function getAllGrowthBookFeatures(): Record<string, unknown>
export function getGrowthBookConfigOverrides(): Record<string, unknown>
export function setGrowthBookConfigOverride(feature: string, value: unknown): void
export function clearGrowthBookConfigOverrides(): void
export function getApiBaseUrlHost(): string | undefined
export function initializeGrowthBook(): Promise<GrowthBook | null>
export function getFeatureValue_DEPRECATED<T>(feature: string, defaultValue: T): Promise<T>
export function getFeatureValue_CACHED_MAY_BE_STALE<T>(feature: string, defaultValue: T): T
export function getFeatureValue_CACHED_WITH_REFRESH<T>(feature: string, defaultValue: T): T // deprecated
export function checkStatsigFeatureGate_CACHED_MAY_BE_STALE(gate: string): boolean
export function checkSecurityRestrictionGate(gate: string): Promise<boolean>
export function checkGate_CACHED_OR_BLOCKING(gate: string): Promise<boolean>
export function getDynamicConfig_CACHED_MAY_BE_STALE<T>(config: string, defaultValue: T): T
export function refreshGrowthBookAfterAuthChange(): void
```
**Key Logic:**
- **Initialization (`initializeGrowthBook()`):** Memoized singleton. Loads disk-cached features from `~/.claude/cachedGrowthBookFeatures`. Applies `CLAUDE_INTERNAL_FC_OVERRIDES` env var overrides (JSON). Connects to GrowthBook remote with 5000ms timeout, then sets up periodic refresh. Returns `null` when analytics disabled or in API key mode without user_id
- **Remote eval workaround:** GrowthBook's remote eval returns `{ value }` but client expects `{ defaultValue }`. The code transforms `{ value: V }``{ defaultValue: V }` before storing in `remoteEvalFeatureValues` Map. Synced to disk via `syncRemoteEvalToDisk()`
- **Caching tiers:**
- `_DEPRECATED` functions: block on `initializeGrowthBook()` Promise
- `_CACHED_MAY_BE_STALE`: returns synchronously from in-memory cache (may be stale after refresh)
- `_CACHED_OR_BLOCKING`: awaits init, then returns cached value; used for security gates only
- **Security gates (`checkSecurityRestrictionGate()`):** awaits init, checks gate value, blocks if not initialized. Used for enterprise policy enforcement
- **Refresh listeners:** `onGrowthBookRefresh()` registers a listener called after each GrowthBook refresh cycle; returns unsubscribe function
- **Overrides:** `setGrowthBookConfigOverride()` / `clearGrowthBookConfigOverrides()` in-process override map; `CLAUDE_INTERNAL_FC_OVERRIDES` JSON env var for process-level overrides
**Configuration:**
- Disk cache: `~/.claude/cachedGrowthBookFeatures`
- Init timeout: 5000ms
- `CLAUDE_INTERNAL_FC_OVERRIDES` env var: JSON override map
**Dependencies:** `growthbook` SDK, `services/analytics/config.js`, `utils/auth.js`, `utils/config.js`
---
## services/analytics/metadata.ts
**Path:** `src/services/analytics/metadata.ts`
**Purpose:** Event metadata enrichment for analytics. Provides types and utilities for building structured `EventMetadata` objects with environment context, process metrics, and safe telemetry extraction from tool inputs.
**Constants:**
- `TOOL_INPUT_STRING_TRUNCATE_AT = 512` — strings longer than this get truncated
- `TOOL_INPUT_STRING_TRUNCATE_TO = 128` — truncated target length
- `TOOL_INPUT_MAX_JSON_CHARS = 4096` — JSON input cap before discarding
- `MAX_FILE_EXTENSION_LENGTH = 10` — max chars for file extensions
**Exports:**
```typescript
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = never // marker type
export function sanitizeToolNameForAnalytics(toolName: string): never // returns marker type
export function isToolDetailsLoggingEnabled(): boolean // gated on OTEL_LOG_TOOL_DETAILS env
export function isAnalyticsToolDetailsLoggingEnabled(
mcpServerType: string | undefined,
mcpServerBaseUrl: string | undefined
): boolean
export function mcpToolDetailsForAnalytics(
toolName: string,
mcpServerType: string | undefined,
mcpServerBaseUrl: string | undefined
): { mcpServerName?: never; mcpToolName?: never }
export function extractMcpToolDetails(
toolName: string
): { serverName: string; mcpToolName: string } | undefined
export function extractSkillName(
toolName: string,
input: unknown
): never | undefined // returns marker type or undefined
export function extractToolInputForTelemetry(input: unknown): string | undefined
export function getFileExtensionForAnalytics(filePath: string): never | undefined
export function getFileExtensionsFromBashCommand(
command: string,
simulatedSedEditFilePath?: string
): never | undefined
export type EnvContext = {
userType: string
isCI: boolean
platform: string
// ... other context fields
}
export type ProcessMetrics = {
heapUsedMB: number
heapTotalMB: number
rssMB: number
externalMB: number
}
export type EventMetadata = {
// enriched event payload type
}
export type EnrichMetadataOptions = {
includeProcessMetrics?: boolean
// ...
}
export async function getEventMetadata(options?: EnrichMetadataOptions): Promise<EventMetadata>
export async function buildEnvContext(): Promise<EnvContext> // memoized
```
**Key Logic:**
- `BUILTIN_MCP_SERVER_NAMES` set is gated behind `CHICAGO_MCP` feature flag — determines which MCP servers are considered "builtin"
- `extractToolInputForTelemetry()`: JSON-serializes input, truncates strings over `TOOL_INPUT_STRING_TRUNCATE_AT` to `TOOL_INPUT_STRING_TRUNCATE_TO`, caps total at `TOOL_INPUT_MAX_JSON_CHARS`
- `getFileExtensionsFromBashCommand()`: parses bash command to extract file extensions using regex patterns; handles `sed -i` specially via `simulatedSedEditFilePath`
- `buildEnvContext()` is memoized — called once per process and cached
- Agent identification classifies turns as: teammate (subagent of another), subagent (spawned by coordinator), standalone
**Dependencies:** `services/analytics/growthbook.js`, `utils/envUtils.js`, `utils/platform.js`
---
## services/analytics/index.ts
**Path:** `src/services/analytics/index.ts`
**Purpose:** The main analytics entry point — a no-dependency module that provides a queuing facade for all event logging. Events are queued until a sink is attached, preventing startup ordering issues.
**Design:** Explicitly has NO dependencies to avoid import cycles. Events are queued in `eventQueue` until `attachAnalyticsSink()` drains them via `queueMicrotask`.
**Exports:**
```typescript
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = never // marker type
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED = never // PII-tagged marker type
export function stripProtoFields<V>(
metadata: Record<string, V>
): Record<string, V>
export type AnalyticsSink = {
logEvent: (eventName: string, metadata: LogEventMetadata) => void
logEventAsync: (eventName: string, metadata: LogEventMetadata) => Promise<void>
}
export function attachAnalyticsSink(newSink: AnalyticsSink): void
export function logEvent(eventName: string, metadata: LogEventMetadata): void
export async function logEventAsync(eventName: string, metadata: LogEventMetadata): Promise<void>
export function _resetForTesting(): void
```
**Types (internal):**
```typescript
type LogEventMetadata = { [key: string]: boolean | number | undefined }
type QueuedEvent = { eventName: string; metadata: LogEventMetadata; async: boolean }
```
**Key Logic:**
- `attachAnalyticsSink()`: idempotent (no-op if sink already set). Drains queue via `queueMicrotask` to avoid blocking startup. For ant users (`USER_TYPE === 'ant'`), logs `analytics_sink_attached` with `queued_event_count`
- `stripProtoFields()`: removes keys starting with `_PROTO_` from event metadata (for non-1P destinations). Returns same reference if no `_PROTO_` keys present
- `_PROTO_*` keys route to PII-tagged BigQuery columns — stripped before Datadog but preserved for firstPartyEventLoggingExporter
- Metadata type is intentionally restricted to `boolean | number | undefined` — no strings unless explicitly cast with the marker type
---
## services/analytics/sink.ts
**Path:** `src/services/analytics/sink.ts`
**Purpose:** The analytics sink implementation that routes events to Datadog and first-party event logging backends. Handles sampling, metadata enrichment, and per-sink kill switches.
**Exports:**
```typescript
export function createAnalyticsSink(options?: {
isInteractive?: boolean
}): AnalyticsSink
```
**Key Logic:**
- Routes events to two sinks: Datadog (via `logDatadogEvent`) and first-party event logging (via `log1PEvent`)
- Checks `isSinkKilled('datadog')` and `isSinkKilled('firstParty')` before dispatching to each sink
- Applies sampling based on `tengu_event_sampling_config` dynamic config — adds `sample_rate` to metadata when sampled
- Strips `_PROTO_*` keys before Datadog fanout (`stripProtoFields`)
- Metadata is enriched with `getEventMetadata()` before dispatch
---
## services/analytics/sinkKillswitch.ts
**Path:** `src/services/analytics/sinkKillswitch.ts`
**Purpose:** Per-sink analytics kill switch, controlled by a GrowthBook JSON config.
**Exports:**
```typescript
export type SinkName = 'datadog' | 'firstParty'
export function isSinkKilled(sink: SinkName): boolean
```
**Key Logic:**
- Config name: `tengu_frond_boric` (mangled/obfuscated name)
- Shape: `{ datadog?: boolean, firstParty?: boolean }``true` stops dispatch to that sink
- Default `{}` (nothing killed). Fail-open: missing/malformed config = sink stays on
- Must NOT be called from `isGrowthBookEnabled()` — would cause recursion; call at per-event dispatch sites instead
**Dependencies:** `services/analytics/growthbook.js`
---
## services/analytics/datadog.ts
**Path:** `src/services/analytics/datadog.ts`
**Purpose:** Datadog metrics and event logging integration for Claude Code analytics.
**Key Logic:**
- Sends events via `@datadog/datadog-ci` or direct HTTP to Datadog API
- Disabled when `isAnalyticsDisabled()` returns true
- Event namespace: `tengu_*` prefix for all Claude Code events
- Tags include version, platform, user type, session metadata
---
## services/analytics/firstPartyEventLogger.ts
**Path:** `src/services/analytics/firstPartyEventLogger.ts`
**Purpose:** First-party event logging integration — routes events to the internal event logging system.
**Key Logic:**
- Gated by `is1PEventLoggingEnabled()` which checks GrowthBook feature `tengu_fpel` and `isAnalyticsDisabled()`
- Enriches events with `EventMetadata` from `getEventMetadata()`
- Handles proto field hoisting from `_PROTO_*` keys
---
## services/analytics/firstPartyEventLoggingExporter.ts
**Path:** `src/services/analytics/firstPartyEventLoggingExporter.ts`
**Purpose:** OpenTelemetry log exporter that routes logs to the first-party event logging pipeline.
**Key Logic:**
- Implements OTel `LogRecordExporter` interface
- Hoists `_PROTO_*` keys to top-level proto fields in the BQ destination
- Calls `stripProtoFields()` after hoisting as defensive cleanup
- Only sends to first-party pipeline (not Datadog)
---
## services/api/bootstrap.ts
**Path:** `src/services/api/bootstrap.ts`
**Purpose:** Bootstraps the API client with session-specific configuration at startup. Wires together auth, proxy, and telemetry settings.
**Key Logic:**
- Configures `ANTHROPIC_API_URL` base URL, auth headers, proxy settings
- Calls `initializeGrowthBook()` early in startup
- Sets up OTel span management
- Handles `CLAUDE_CODE_SKIP_BEDROCK_TLS` for Bedrock TLS verification skip
---
## services/api/client.ts
**Path:** `src/services/api/client.ts`
**Purpose:** Provides the configured SDK client instance and helper utilities for making API calls.
**Exports:**
```typescript
export function getClient(): Anthropic
export function getClientForModel(model: string): Anthropic
```
**Key Logic:**
- Client is created lazily and cached
- Applies base URL overrides from `ANTHROPIC_BASE_URL` env
- Configures mTLS via `getMtlsConfig()`
- Routes through proxy if `HTTPS_PROXY` / `HTTP_PROXY` set
---
## services/api/claude.ts
**Path:** `src/services/api/claude.ts`
**Purpose:** Main API call layer for Claude completions. Handles prompt caching, extra body parameters, task budget configuration, metadata, and user message formatting.
**Exports:**
```typescript
export function getExtraBodyParams(betaHeaders?: string[]): JsonObject
export function getPromptCachingEnabled(model: string): boolean
export function getCacheControl(opts: {
scope?: string
querySource?: QuerySource
}): { type: 'ephemeral' | 'persistent'; ttl?: number; scope?: string }
export function configureTaskBudgetParams(
taskBudget: number,
outputConfig: OutputConfig,
betas: string[]
): void
export function getAPIMetadata(): { user_id: string }
export async function verifyApiKey(
apiKey: string,
isNonInteractiveSession: boolean
): Promise<boolean>
export function userMessageToMessageParam(
message: UserMessage,
addCache: boolean,
enablePromptCaching: boolean,
querySource?: QuerySource
): MessageParam
```
**Internal functions (not exported):**
- `configureEffortParams()`: sets thinking budget based on effort level
- `should1hCacheTTL()`: checks if 1h TTL is applicable for current user/model
**Key Logic:**
- **Prompt caching:** `getPromptCachingEnabled()` checks model allowlist. `getCacheControl()` returns `{ type: 'ephemeral' }` normally, `{ type: 'ephemeral', ttl: 3600 }` when 1h TTL gate passes
- **1h TTL gate:** `should1hCacheTTL()` checks `tengu_prompt_cache_1h_config` GrowthBook allowlist (session-stable, latched in `STATE.promptCache1hAllowlist`) and `STATE.promptCache1hEligible` (also latched to prevent mid-session overage flips)
- **Anti-distillation:** `tengu_anti_distill_fake_tool_injection` GrowthBook gate — injects fake tools into API calls as a training data quality signal
- **Extra body params:** `getExtraBodyParams()` assembles beta headers, model-specific params, and context management config
**Dependencies:** `@anthropic-ai/sdk`, `bootstrap/state.js`, `services/analytics/growthbook.js`, `services/compact/apiMicrocompact.js`
---
## services/api/dumpPrompts.ts
**Path:** `src/services/api/dumpPrompts.ts`
**Purpose:** Debug utility for ant users — dumps API request/response JSONL logs to disk for inspection. Used for prompt debugging and sharing bug reports.
**Exports:**
```typescript
export function getLastApiRequests(): Array<{ timestamp: string; request: unknown }>
export function clearApiRequestCache(): void
export function clearDumpState(agentIdOrSessionId: string): void
export function clearAllDumpState(): void
export function addApiRequestToCache(requestData: unknown): void
export function getDumpPromptsPath(agentIdOrSessionId?: string): string
export function createDumpPromptsFetch(
agentIdOrSessionId: string
): ClientOptions['fetch']
```
**Key Logic:**
- Ant-only (no-op for non-ant users)
- `MAX_CACHED_REQUESTS = 5` — in-memory ring buffer of recent requests
- Deferred writes via `setImmediate` to avoid blocking the request path
- JSONL format with records of types: `init`, `system_update`, `message`, `response`
- Per-session state tracking with fingerprint-based change detection (avoids re-writing unchanged system prompts)
- Path: `~/.claude/dump-prompts/<agentIdOrSessionId>.jsonl`
---
## services/api/emptyUsage.ts
**Path:** `src/services/api/emptyUsage.ts`
**Purpose:** Provides a zero-value `Usage` object for cases where usage data is unavailable.
**Exports:**
```typescript
export const EMPTY_USAGE: Usage
export type NonNullableUsage = {
input_tokens: number
output_tokens: number
cache_read_input_tokens: number
cache_creation_input_tokens: number
}
```
---
## services/api/errorUtils.ts
**Path:** `src/services/api/errorUtils.ts`
**Purpose:** Utilities for classifying and handling API errors.
**Exports:**
```typescript
export function isRateLimitError(error: unknown): boolean
export function isOverloadedError(error: unknown): boolean
export function isAuthError(error: unknown): boolean
export function isConnectionError(error: unknown): boolean
export function getRetryAfterMs(error: unknown): number | undefined
```
---
## services/api/errors.ts
**Path:** `src/services/api/errors.ts`
**Purpose:** Defines all API error message constants and classification functions for user-facing error handling.
**Exports:**
```typescript
export const API_ERROR_MESSAGE_PREFIX = 'API Error'
export function startsWithApiErrorPrefix(text: string): boolean
export const PROMPT_TOO_LONG_ERROR_MESSAGE: string
export function isPromptTooLongMessage(msg: string): boolean
export function parsePromptTooLongTokenCounts(
rawMessage: string
): { actualTokens: number; limitTokens: number } | null
export function getPromptTooLongTokenGap(msg: string): number | undefined
export function isMediaSizeError(raw: unknown): boolean
export function isMediaSizeErrorMessage(msg: string): boolean
export const CREDIT_BALANCE_TOO_LOW_ERROR_MESSAGE: string
export const INVALID_API_KEY_ERROR_MESSAGE: string
export const INVALID_API_KEY_ERROR_MESSAGE_EXTERNAL: string
export const TOKEN_REVOKED_ERROR_MESSAGE: string
export const CCR_AUTH_ERROR_MESSAGE: string
export const REPEATED_529_ERROR_MESSAGE: string
export const CUSTOM_OFF_SWITCH_MESSAGE: string
export const API_TIMEOUT_ERROR_MESSAGE: string
export const OAUTH_ORG_NOT_ALLOWED_ERROR_MESSAGE: string
export function getPdfTooLargeErrorMessage(): string
export function getPdfPasswordProtectedErrorMessage(): string
export function getPdfInvalidErrorMessage(): string
export function getImageTooLargeErrorMessage(): string
export function getRequestTooLargeErrorMessage(): string
export function getTokenRevokedErrorMessage(): string
export function getOauthOrgNotAllowedErrorMessage(): string
```
---
## services/api/filesApi.ts
**Path:** `src/services/api/filesApi.ts`
**Purpose:** Files API client — downloads, uploads, lists, and manages files in Files API (beta).
**Constants:**
- `FILES_API_BETA_HEADER = 'files-api-2025-04-14,oauth-2025-04-20'`
- `MAX_FILE_SIZE_BYTES = 500 * 1024 * 1024` (500MB)
- `DEFAULT_CONCURRENCY = 5`
- `MAX_RETRIES = 3`
- `BASE_DELAY_MS = 500`
**Exports:**
```typescript
export type File = {
fileId: string
relativePath: string
mimeType?: string
}
export type FilesApiConfig = {
apiKey?: string
baseUrl?: string
sessionId?: string
}
export type DownloadResult = {
fileId: string
relativePath: string
success: boolean
error?: string
savedPath?: string
}
export type UploadResult = {
fileId: string
relativePath: string
success: boolean
error?: string
remoteFileId?: string
}
export type FileMetadata = {
id: string
filename: string
created_at: number
purpose: string
size: number
}
export class UploadNonRetriableError extends Error {}
export function parseFileSpecs(fileSpecs: string[]): File[]
export async function downloadFile(fileId: string, config: FilesApiConfig): Promise<Buffer>
export function buildDownloadPath(
basePath: string,
sessionId: string,
relativePath: string
): string | null
export async function downloadAndSaveFile(
attachment: File,
config: FilesApiConfig
): Promise<DownloadResult>
export async function downloadSessionFiles(
files: File[],
config: FilesApiConfig,
concurrency?: number
): Promise<DownloadResult[]>
export async function uploadFile(
filePath: string,
relativePath: string,
config: FilesApiConfig,
opts?: { retries?: number }
): Promise<UploadResult>
export async function uploadSessionFiles(
files: File[],
config: FilesApiConfig,
concurrency?: number
): Promise<UploadResult[]>
export async function listFilesCreatedAfter(
afterCreatedAt: number,
config: FilesApiConfig
): Promise<FileMetadata[]>
```
**Key Logic:**
- `buildDownloadPath()`: path traversal guard — if `relativePath` contains `..` components or resolves outside `basePath/sessionId`, returns `null`
- Download/upload use exponential backoff: `BASE_DELAY_MS * 2^attempt` with jitter
- `uploadSessionFiles()` / `downloadSessionFiles()`: parallel with configurable concurrency (default 5)
- `listFilesCreatedAfter()`: paginated using cursor, collects all pages
---
## services/api/firstTokenDate.ts
**Path:** `src/services/api/firstTokenDate.ts`
**Purpose:** Fetches and stores the date when the user first made a Claude Code API call.
**Exports:**
```typescript
export async function fetchAndStoreClaudeCodeFirstTokenDate(): Promise<void>
```
**Key Logic:**
- Fetches `/api/organization/claude_code_first_token_date`
- Stores in `claudeCodeFirstTokenDate` config field
- Idempotent — no-ops if already stored
---
## services/api/grove.ts
**Path:** `src/services/api/grove.ts`
**Purpose:** Grove is a consumer Terms/Privacy Policy notification feature. Manages fetching, caching, and determining whether to show the Grove notice to users.
**Constants:**
- `GROVE_CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1000` (24 hours)
**Exports:**
```typescript
export type AccountSettings = {
groveEnabled: boolean
groveNoticeViewed: boolean
// ...
}
export type GroveConfig = {
enabled: boolean
forceShow: boolean
// ...
}
export type ApiResult<T> = { success: true; data: T } | { success: false; error: string }
export async function getGroveSettings(): Promise<ApiResult<AccountSettings>> // memoized 24h
export async function markGroveNoticeViewed(): Promise<void>
export async function updateGroveSettings(groveEnabled: boolean): Promise<void>
export async function isQualifiedForGrove(): Promise<boolean>
export async function getGroveNoticeConfig(): Promise<ApiResult<GroveConfig>> // memoized 24h
export function calculateShouldShowGrove(
settingsResult: ApiResult<AccountSettings>,
configResult: ApiResult<GroveConfig>,
showIfAlreadyViewed: boolean
): boolean
export async function checkGroveForNonInteractive(): Promise<void>
```
**Key Logic:**
- Cache-first with background refresh: returns cached data immediately, refreshes in background after expiry
- `calculateShouldShowGrove()`: checks config enabled, settings not viewed, and user qualification
- `checkGroveForNonInteractive()`: called in non-interactive mode to log grove status without showing UI
---
## services/api/logging.ts
**Path:** `src/services/api/logging.ts`
**Purpose:** API query/success/error logging with gateway detection and OTel span management.
**Exports:**
```typescript
export type GlobalCacheStrategy = 'tool_based' | 'system_prompt' | 'none'
export function logAPIQuery(opts: {
model: string
querySource: QuerySource
// ... other fields
}): void
export function logAPIError(opts: {
error: unknown
model: string
querySource: QuerySource
// ... other fields
}): void
export function logAPISuccessAndDuration(opts: {
model: string
usage: Usage
querySource: QuerySource
durationMs: number
// ... other fields
}): void
```
**Key Logic:**
- Gateway detection: identifies litellm, helicone, portkey, cloudflare-ai-gateway, kong, braintrust, databricks from `ANTHROPIC_BASE_URL`
- Events: `tengu_api_query`, `tengu_api_error`, `tengu_api_success`
- OTel spans created/ended around API calls
- Teleport session tracking: logs extra fields when session is teleported
- Re-exports `EMPTY_USAGE` and `NonNullableUsage`
---
## services/api/metricsOptOut.ts
**Path:** `src/services/api/metricsOptOut.ts`
**Purpose:** Checks whether metrics collection is enabled for the current organization. Implements two-tier caching.
**Constants:**
- `CACHE_TTL_MS = 60 * 60 * 1000` (1 hour in-memory)
- `DISK_CACHE_TTL_MS = 24 * 60 * 60 * 1000` (24 hours on disk)
- Endpoint: `api/claude_code/organizations/metrics_enabled`
**Exports:**
```typescript
export async function checkMetricsEnabled(): Promise<MetricsStatus>
export function _clearMetricsEnabledCacheForTesting(): void
```
**Key Logic:**
- Two-tier cache: in-memory (1h TTL) → disk (24h TTL) → network
- Requires `profile` OAuth scope; returns `enabled: true` if unauthenticated or scope missing
- `MetricsStatus`: `{ enabled: boolean; source: 'cache' | 'network' | 'default' }`
---
## services/api/overageCreditGrant.ts
**Path:** `src/services/api/overageCreditGrant.ts`
**Purpose:** Manages overage credit grant information for subscribed users who exceed their plan limits.
**Constants:**
- `CACHE_TTL_MS = 60 * 60 * 1000` (1 hour)
**Exports:**
```typescript
export type OverageCreditGrantInfo = {
hasGrant: boolean
amount?: number
currency?: string
expiresAt?: string
}
export type OverageCreditGrantCacheEntry = {
data: OverageCreditGrantInfo
fetchedAt: number
orgId: string
}
export function getCachedOverageCreditGrant(): OverageCreditGrantInfo | null
export function invalidateOverageCreditGrantCache(): void
export async function refreshOverageCreditGrantCache(): Promise<void>
export function formatGrantAmount(info: OverageCreditGrantInfo): string | null
```
**Key Logic:**
- Per-org cache in `overageCreditGrantCache` Map (keyed by org ID)
- `formatGrantAmount()`: formats amount as currency string (e.g., "$5.00") or `null` if no grant
---
## services/api/promptCacheBreakDetection.ts
**Path:** `src/services/api/promptCacheBreakDetection.ts`
**Purpose:** Detects unexpected prompt cache breaks that indicate server-side cache eviction. Writes diff files to disk for debugging.
**Constants:**
- `CACHE_TTL_1HOUR_MS = 3_600_000` (1 hour)
- `MIN_CACHE_MISS_TOKENS = 2_000` — minimum to consider a break significant
- 95% threshold — cache reads must drop to ≤5% of expected to count as a break
- `MAX_TRACKED_SOURCES = 10`
**Exports:**
```typescript
export type PromptStateSnapshot = {
messages: MessageParam[]
systemPrompt: string
tools: unknown[]
timestamp: number
querySource: QuerySource
}
export const CACHE_TTL_1HOUR_MS: number
export function recordPromptState(snapshot: PromptStateSnapshot): void
export async function checkResponseForCacheBreak(
querySource: QuerySource,
cacheReadTokens: number,
cacheCreationTokens: number,
messages: MessageParam[],
agentId?: string,
requestId?: string
): Promise<void>
export function notifyCacheDeletion(querySource: QuerySource, agentId?: string): void
export function notifyCompaction(querySource: QuerySource, agentId?: string): void
export function cleanupAgentTracking(agentId: string): void
export function resetPromptCacheBreakDetection(): void
```
**Key Logic:**
- 2-phase detection: `recordPromptState()` before call, `checkResponseForCacheBreak()` after
- Per-source tracking Map: keyed by `querySource` (or `agent:${agentId}`)
- Tracked source prefixes: `repl_main_thread`, `sdk`, `agent:custom`, `agent:default`, `agent:builtin`
- Writes diff files to `~/.claude/tmp/cache-break-*.diff` when a break is detected
- Events: `tengu_prompt_cache_break`
- `notifyCacheDeletion()` / `notifyCompaction()`: suppress false positives after intentional cache clearing
---
## services/api/referral.ts
**Path:** `src/services/api/referral.ts`
**Purpose:** Manages referral program eligibility, redemptions, and guest passes for Claude Code subscribers.
**Constants:**
- `CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1000` (24 hours)
**Exports:**
```typescript
export async function fetchReferralEligibility(
campaign?: string
): Promise<ReferralEligibilityResponse>
export async function fetchReferralRedemptions(
campaign?: string
): Promise<ReferralRedemptionsResponse>
export function checkCachedPassesEligibility(): {
eligible: boolean
needsRefresh: boolean
hasCache: boolean
}
export function formatCreditAmount(reward: ReferrerReward): string
export function getCachedReferrerReward(): ReferrerRewardInfo | null
export function getCachedRemainingPasses(): number | null
export async function fetchAndStorePassesEligibility(): Promise<ReferralEligibilityResponse | null>
export async function getCachedOrFetchPassesEligibility(): Promise<ReferralEligibilityResponse | null>
export async function prefetchPassesEligibility(): Promise<void>
```
**Key Logic:**
- Max-subscription only — returns `null` / ineligible for non-max subscribers
- In-flight deduplication: multiple calls to `getCachedOrFetchPassesEligibility()` share one pending Promise
- 24h cache TTL
---
## services/api/sessionIngress.ts
**Path:** `src/services/api/sessionIngress.ts`
**Purpose:** Manages session log ingress — append-log with optimistic concurrency for multi-writer scenarios (e.g., continued sessions from different machines).
**Constants:**
- `MAX_RETRIES = 10`
- `BASE_DELAY_MS = 500` — exponential backoff base
**Exports:**
```typescript
export async function appendSessionLog(
sessionId: string,
entry: SessionLogEntry,
url: string
): Promise<boolean>
export async function getSessionLogs(
sessionId: string,
url: string
): Promise<Entry[] | null>
export async function getSessionLogsViaOAuth(
sessionId: string,
accessToken: string,
orgUUID: string
): Promise<Entry[] | null>
export async function getTeleportEvents(
sessionId: string,
accessToken: string,
orgUUID: string
): Promise<Entry[] | null>
export function clearSession(sessionId: string): void
export function clearAllSessions(): void
```
**Key Logic:**
- Optimistic concurrency: `Last-Uuid` header on append; 409 response adopts server's last UUID
- Sequential wrappers per session prevent out-of-order appends
- `getTeleportEvents()`: paginated (max 100 pages, 1000 events/page)
- `clearSession()` / `clearAllSessions()`: clears in-memory sequential wrapper state
---
## services/api/ultrareviewQuota.ts
**Path:** `src/services/api/ultrareviewQuota.ts`
**Purpose:** Fetches ultrareview (deep code review) quota information for subscribed users.
**Exports:**
```typescript
export type UltrareviewQuotaResponse = {
used: number
limit: number
resetsAt: string
}
export async function fetchUltrareviewQuota(): Promise<UltrareviewQuotaResponse | null>
```
**Key Logic:**
- Endpoint: `/v1/ultrareview/quota`
- Subscriber-only; returns `null` for non-subscribers or on error
- 5 second timeout
---
## services/api/usage.ts
**Path:** `src/services/api/usage.ts`
**Purpose:** Fetches usage statistics for the current user/organization from the Claude Code API.
**Exports:**
```typescript
export async function fetchUsage(): Promise<UsageStats | null>
export type UsageStats = {
// usage breakdown fields
}
```
---
## services/api/withRetry.ts
**Path:** `src/services/api/withRetry.ts`
**Purpose:** Generic retry wrapper for API calls with exponential backoff.
**Exports:**
```typescript
export async function withRetry<T>(
fn: () => Promise<T>,
opts?: {
maxRetries?: number
baseDelayMs?: number
shouldRetry?: (error: unknown) => boolean
}
): Promise<T>
```
---
## services/AgentSummary/agentSummary.ts
**Path:** `src/services/AgentSummary/agentSummary.ts`
**Purpose:** Periodic background summarization of agent conversations to compress context while preserving key information.
**Key Logic:**
- Runs on a 30-second background timer
- Generates summaries using the main Claude model
- Compressed summaries are injected back as system messages
- Used by subagents and teammates to manage long-running conversations
---
## services/autoDream/autoDream.ts
**Path:** `src/services/autoDream/autoDream.ts`
**Purpose:** Background memory consolidation system. Periodically scans session transcripts and uses a forked agent to consolidate learnings into persistent memory files.
**Constants:**
- `SESSION_SCAN_INTERVAL_MS = 10 * 60 * 1000` (10 minutes)
- `DEFAULTS = { minHours: 24, minSessions: 5 }` — minimum time and sessions before consolidation
**Exports:**
```typescript
export function initAutoDream(): () => void // returns stop/cleanup function
```
**Key Logic:**
- Gate order: time check (minHours) → session count check (minSessions) → consolidation lock
- GrowthBook config `tengu_onyx_plover` controls `{ minHours, minSessions, enabled }`
- `initAutoDream()` registers as a post-sampling hook; returns cleanup function
- Uses `SESSION_SCAN_INTERVAL_MS` for polling
- Spawns a forked agent using `buildConsolidationPrompt()`
---
## services/autoDream/config.ts
**Path:** `src/services/autoDream/config.ts`
**Purpose:** Configuration gate for the autoDream memory consolidation feature.
**Exports:**
```typescript
export function isAutoDreamEnabled(): boolean
```
**Key Logic:** User setting takes precedence over GrowthBook gate `tengu_onyx_plover`. Checks `userSettings.autoDream` first, then GrowthBook.
---
## services/autoDream/consolidationLock.ts
**Path:** `src/services/autoDream/consolidationLock.ts`
**Purpose:** File-based mutex lock for the memory consolidation process to prevent concurrent consolidations across sessions/processes.
**Constants:**
- `LOCK_FILE = '.consolidate-lock'` — in memory directory
- `HOLDER_STALE_MS = 60 * 60 * 1000` (1 hour) — stale lock threshold
**Exports:**
```typescript
export async function readLastConsolidatedAt(): Promise<number>
export async function tryAcquireConsolidationLock(): Promise<number | null>
export async function rollbackConsolidationLock(priorMtime: number): Promise<void>
export async function listSessionsTouchedSince(sinceMs: number): Promise<string[]>
export async function recordConsolidation(): Promise<void>
```
**Key Logic:**
- Lock file mtime = `lastConsolidatedAt` timestamp (dual-purpose: both locking and timestamp)
- PID-based ownership — stale locks (PID dead or >1h old) are overwritten
- `tryAcquireConsolidationLock()`: returns prior mtime on success, `null` if already held
- `rollbackConsolidationLock()`: restores mtime to `priorMtime` on consolidation failure
---
## services/autoDream/consolidationPrompt.ts
**Path:** `src/services/autoDream/consolidationPrompt.ts`
**Purpose:** Builds the system prompt for the memory consolidation agent.
**Exports:**
```typescript
export function buildConsolidationPrompt(
memoryRoot: string,
transcriptDir: string,
extra: string
): string
```
**Key Logic:** Returns a 4-phase prompt:
1. Orient — read existing memory files to understand current state
2. Gather recent signal — read session transcripts since last consolidation
3. Consolidate — merge new learnings into memory files
4. Prune and index — remove stale entries, update index file
---
## services/awaySummary.ts
**Path:** `src/services/awaySummary.ts`
**Purpose:** Generates "away summaries" — brief catch-up summaries shown when the user returns to a long-running session after being away.
**Key Logic:**
- Triggered when `lastInteractionTime` gap exceeds threshold
- Uses Claude to generate a brief (1-3 sentence) summary of what happened while away
- Displayed as a system message above the prompt
---
## services/claudeAiLimits.ts
**Path:** `src/services/claudeAiLimits.ts`
**Purpose:** Fetches and manages rate limit information for Claude.ai-authenticated users.
**Exports:**
```typescript
export async function fetchClaudeAiLimits(): Promise<ClaudeAiLimits | null>
export type ClaudeAiLimits = {
// rate limit fields
}
```
---
## services/claudeAiLimitsHook.ts
**Path:** `src/services/claudeAiLimitsHook.ts`
**Purpose:** React hook for accessing Claude.ai rate limit data with automatic refresh.
**Exports:**
```typescript
export function useClaudeAiLimits(): ClaudeAiLimits | null
```
---
## services/compact/apiMicrocompact.ts
**Path:** `src/services/compact/apiMicrocompact.ts`
**Purpose:** API-native context management strategies using server-side `cache_edits` feature. Configures context window editing without full client-side rewriting.
**Constants:**
- `DEFAULT_MAX_INPUT_TOKENS = 180_000`
- `DEFAULT_TARGET_INPUT_TOKENS = 40_000`
**Type: `ContextEditStrategy`:**
```typescript
export type ContextEditStrategy =
| {
type: 'clear_tool_uses_20250919'
trigger?: { type: 'input_tokens'; value: number }
keep?: { type: 'tool_uses'; value: number }
clear_tool_inputs?: boolean | string[]
exclude_tools?: string[]
clear_at_least?: { type: 'input_tokens'; value: number }
}
| {
type: 'clear_thinking_20251015'
keep: { type: 'thinking_turns'; value: number } | 'all'
}
export type ContextManagementConfig = {
edits: ContextEditStrategy[]
}
```
**Exports:**
```typescript
export function getAPIContextManagement(options?: {
hasThinking?: boolean
isRedactThinkingActive?: boolean
clearAllThinking?: boolean
}): ContextManagementConfig | undefined
```
**Key Logic:**
- Tool clearing strategies are ant-only, gated by `USE_API_CLEAR_TOOL_RESULTS` and `USE_API_CLEAR_TOOL_USES` env vars
- `TOOLS_CLEARABLE_RESULTS`: shell tools, Glob, Grep, FileRead, WebFetch, WebSearch
- `TOOLS_CLEARABLE_USES`: FileEdit, FileWrite, NotebookEdit
- Thinking clearing: when `hasThinking && !isRedactThinkingActive`, adds `clear_thinking_20251015`
- When `clearAllThinking` (>1h idle = confirmed cache miss): keeps only last 1 thinking turn
---
## services/compact/autoCompact.ts
**Path:** `src/services/compact/autoCompact.ts`
**Purpose:** Automatic context compaction — triggers full conversation summarization when context window usage exceeds threshold.
**Key Logic:**
- Monitors token usage against configurable threshold (default 90% of context window)
- When triggered, calls `compact()` to summarize the conversation
- Posts a `CompactBoundaryMessage` in the conversation to mark compaction point
- Resets token tracking after compaction
---
## services/compact/compact.ts
**Path:** `src/services/compact/compact.ts`
**Purpose:** Full conversation compaction — replaces conversation history with an LLM-generated summary.
**Key Logic:**
- Uses the detailed analysis instruction prompts from `prompt.ts`
- Optionally performs partial compaction (keeps recent messages)
- The `NO_TOOLS_PREAMBLE` constant is a critical instruction preventing the compaction model from calling tools during summarization
- Writes compact summary as a synthetic `<compact_summary>` tagged message
---
## services/compact/compactWarningHook.ts
**Path:** `src/services/compact/compactWarningHook.ts`
**Purpose:** React hook for accessing the compact warning suppression state.
**Exports:**
```typescript
export function useCompactWarningSuppression(): boolean
```
---
## services/compact/compactWarningState.ts
**Path:** `src/services/compact/compactWarningState.ts`
**Purpose:** Store and actions for suppressing the "compact recommended" warning after microcompact runs.
**Exports:**
```typescript
export const compactWarningStore: Store<boolean>
export function suppressCompactWarning(): void
export function clearCompactWarningSuppression(): void
```
---
## services/compact/grouping.ts
**Path:** `src/services/compact/grouping.ts`
**Purpose:** Groups conversation messages by API round (each assistant message with its preceding user message).
**Exports:**
```typescript
export function groupMessagesByApiRound(messages: Message[]): Message[][]
```
**Key Logic:** Groups by assistant `message.id` boundary — each group contains one API round (user + assistant + tool results).
---
## services/compact/microCompact.ts
**Path:** `src/services/compact/microCompact.ts`
**Purpose:** Microcompaction — lightweight context reduction by clearing tool result content without full conversation summarization. Two paths: cached microcompact (via API `cache_edits`) and time-based microcompact (direct content mutation when cache is cold).
**Constants (exported):**
```typescript
export const TIME_BASED_MC_CLEARED_MESSAGE = '[Old tool result content cleared]'
```
**Compactable tool sets:**
```typescript
const COMPACTABLE_TOOLS = new Set([
FILE_READ_TOOL_NAME, SHELL_TOOL_NAMES..., GREP_TOOL_NAME, GLOB_TOOL_NAME,
WEB_SEARCH_TOOL_NAME, WEB_FETCH_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME
])
```
**Exports:**
```typescript
export function consumePendingCacheEdits():
import('./cachedMicrocompact.js').CacheEditsBlock | null
export function getPinnedCacheEdits():
import('./cachedMicrocompact.js').PinnedCacheEdits[]
export function pinCacheEdits(
userMessageIndex: number,
block: import('./cachedMicrocompact.js').CacheEditsBlock
): void
export function markToolsSentToAPIState(): void
export function resetMicrocompactState(): void
export function estimateMessageTokens(messages: Message[]): number
export type PendingCacheEdits = {
trigger: 'auto'
deletedToolIds: string[]
baselineCacheDeletedTokens: number
}
export type MicrocompactResult = {
messages: Message[]
compactionInfo?: {
pendingCacheEdits?: PendingCacheEdits
}
}
export function evaluateTimeBasedTrigger(
messages: Message[],
querySource: QuerySource | undefined
): { gapMinutes: number; config: TimeBasedMCConfig } | null
export async function microcompactMessages(
messages: Message[],
toolUseContext?: ToolUseContext,
querySource?: QuerySource
): Promise<MicrocompactResult>
```
**Key Logic:**
- `microcompactMessages()` dispatch order:
1. Time-based trigger check: if gap since last assistant > threshold → `maybeTimeBasedMicrocompact()` (short-circuits)
2. Cached MC path: if `CACHED_MICROCOMPACT` feature enabled, model supported, and main thread source → `cachedMicrocompactPath()`
3. Otherwise: return messages unchanged (legacy path removed)
- **Cached MC path:** registers tool results grouped by user message; calls `getToolResultsToDelete()`; queues `CacheEditsBlock` as `pendingCacheEdits`; does NOT mutate message content
- **Time-based MC path:** directly mutates tool result `content` to `TIME_BASED_MC_CLEARED_MESSAGE`; resets cached MC state; notifies cache break detection
- `estimateMessageTokens()`: rough estimation with 4/3 padding factor; images/documents = 2000 tokens
- `isMainThreadSource()`: prefix-matches `repl_main_thread` (handles output style variants like `repl_main_thread:outputStyle:custom`)
- Events: `tengu_cached_microcompact`, `tengu_time_based_microcompact`
---
## services/compact/postCompactCleanup.ts
**Path:** `src/services/compact/postCompactCleanup.ts`
**Purpose:** Runs cleanup tasks after any compaction (auto or manual `/compact`).
**Exports:**
```typescript
export function runPostCompactCleanup(querySource?: QuerySource): void
```
**Key Logic:** Clears: microcompact state, context collapse state, system prompt sections, classifier approvals, speculative checks, beta tracing state, session messages cache.
---
## services/compact/prompt.ts
**Path:** `src/services/compact/prompt.ts`
**Purpose:** Prompt constants used for compact summarization.
**Key Exports:**
- `NO_TOOLS_PREAMBLE`: Critical instruction string prepended to compact calls — prevents the model from invoking any tools during summarization
- `DETAILED_ANALYSIS_INSTRUCTION_BASE`: Instruction for full compaction
- `DETAILED_ANALYSIS_INSTRUCTION_PARTIAL`: Instruction for partial compaction (keeps recent messages)
---
## services/compact/sessionMemoryCompact.ts
**Path:** `src/services/compact/sessionMemoryCompact.ts`
**Purpose:** Session memory compaction — a lighter alternative to full compact that summarizes older session memory segments while preserving recent tool context.
**Constants:**
```typescript
export const DEFAULT_SM_COMPACT_CONFIG: SessionMemoryCompactConfig = {
minTokens: 10000,
minTextBlockMessages: 5,
maxTokens: 40000
}
```
**Exports:**
```typescript
export type SessionMemoryCompactConfig = {
minTokens: number
minTextBlockMessages: number
maxTokens: number
}
export function setSessionMemoryCompactConfig(config: SessionMemoryCompactConfig): void
export function getSessionMemoryCompactConfig(): SessionMemoryCompactConfig
export function resetSessionMemoryCompactConfig(): void
export function hasTextBlocks(message: Message): boolean
export function adjustIndexToPreserveAPIInvariants(
messages: Message[],
startIndex: number
): number
export function calculateMessagesToKeepIndex(
messages: Message[],
lastSummarizedIndex: number
): number
export function shouldUseSessionMemoryCompaction(): boolean
export async function trySessionMemoryCompaction(
messages: Message[],
agentId?: string,
autoCompactThreshold?: number
): Promise<CompactionResult | null>
```
**Key Logic:**
- GrowthBook config `tengu_sm_compact_config` overrides defaults
- Guarded by `ENABLE_CLAUDE_CODE_SM_COMPACT` / `DISABLE_CLAUDE_CODE_SM_COMPACT` env vars
- AND requires both GrowthBook gates `tengu_session_memory` AND `tengu_sm_compact` to be enabled
- `adjustIndexToPreserveAPIInvariants()`: ensures cut point doesn't leave orphaned tool_use without tool_result
- `calculateMessagesToKeepIndex()`: binary search-based index calculation respecting min/max token bounds
---
## services/compact/timeBasedMCConfig.ts
**Path:** `src/services/compact/timeBasedMCConfig.ts`
**Purpose:** Configuration for time-based microcompact (triggers on idle gap).
**Exports:**
```typescript
export type TimeBasedMCConfig = {
enabled: boolean
gapThresholdMinutes: number
keepRecent: number
}
export function getTimeBasedMCConfig(): TimeBasedMCConfig
```
**Key Logic:**
- GrowthBook config: `tengu_slate_heron`
- Defaults: `{ enabled: false, gapThresholdMinutes: 60, keepRecent: 5 }`
---
## services/diagnosticTracking.ts
**Path:** `src/services/diagnosticTracking.ts`
**Purpose:** Tracks file diagnostics (lint errors, type errors) before and after file edits to detect regressions introduced by Claude Code.
**Constants:**
- `MAX_DIAGNOSTICS_SUMMARY_CHARS = 4000`
**Exports:**
```typescript
export interface Diagnostic {
severity: 'error' | 'warning' | 'information' | 'hint'
message: string
source?: string
range: { start: { line: number; character: number }; end: { line: number; character: number } }
}
export interface DiagnosticFile {
uri: string
diagnostics: Diagnostic[]
}
export class DiagnosticTrackingService {
static getInstance(): DiagnosticTrackingService
initialize(mcpClient: unknown): void
shutdown(): Promise<void>
reset(): void
ensureFileOpened(fileUri: string): Promise<void>
beforeFileEdited(filePath: string): Promise<void>
}
```
**Key Logic:**
- Singleton via `getInstance()`
- `beforeFileEdited()`: captures current diagnostics for a file before any edits, so post-edit diagnostics can be compared
- `initialize()`: connects to LSP MCP client for diagnostic data
- `shutdown()`: flushes any pending diagnostic comparisons
---
## services/internalLogging.ts
**Path:** `src/services/internalLogging.ts`
**Purpose:** Internal logging utilities for internal (ant) users — logs K8s namespace, container ID, and tool permission context for debugging.
**Exports:**
```typescript
export async function getContainerId(): Promise<string | null> // memoized
export async function logPermissionContextForAnts(
toolPermissionContext: unknown,
moment: string
): Promise<void>
```
**Key Logic:**
- K8s namespace: reads from `/var/run/secrets/kubernetes.io/serviceaccount/namespace`
- Container ID: extracted from `/proc/self/mountinfo` (first overlay/device entry)
- Both are memoized — container identity doesn't change mid-session
- Only logs when `USER_TYPE === 'ant'`
---
## services/MagicDocs/magicDocs.ts
**Path:** `src/services/MagicDocs/magicDocs.ts`
**Purpose:** Auto-maintained markdown documentation files that stay in sync with code changes made by Claude Code.
**Key Logic:**
- Monitors file edits and regenerates associated markdown docs
- Uses Claude to understand the semantic meaning of changes
- Prompts defined in `prompts.ts`
---
## services/MagicDocs/prompts.ts
**Path:** `src/services/MagicDocs/prompts.ts`
**Purpose:** System prompt and instruction templates for magic docs generation.
---
## services/mcpServerApproval.tsx
**Path:** `src/services/mcpServerApproval.tsx`
**Purpose:** Shows MCP server approval dialogs for pending project servers at startup, reusing the existing Ink root instance.
**Exports:**
```typescript
export async function handleMcpjsonServerApprovals(root: Root): Promise<void>
```
**Key Logic:**
- Queries `getMcpConfigsByScope('project')` for project-scoped MCP servers
- Filters to servers with status `'pending'` via `getProjectMcpServerStatus()`
- Single pending server: renders `MCPServerApprovalDialog`
- Multiple pending servers: renders `MCPServerMultiselectDialog`
- Awaits user decision via Promise/resolve pattern before returning
---
## services/mockRateLimits.ts
**Path:** `src/services/mockRateLimits.ts`
**Purpose:** Development/testing utility for simulating various rate limit scenarios without hitting actual API limits. Ant-only.
**Exports:**
```typescript
export type MockHeaderKey =
| 'x-ratelimit-requests-remaining'
| 'x-ratelimit-tokens-remaining'
// ... other rate limit header names
export type MockScenario =
| 'primary_hard_limit'
| 'secondary_hard_limit'
| 'approaching_limit'
| 'fast_mode_rate_limit'
| 'burst_limit'
// ... 20+ scenarios total
export function setMockHeader(key: MockHeaderKey, value?: string): void
export function addExceededLimit(type: string, hoursFromNow: number): void
export function setMockEarlyWarning(
claimAbbrev: string,
utilization: number,
hoursFromNow?: number
): void
export function clearMockEarlyWarning(): void
export function setMockRateLimitScenario(scenario: MockScenario): void
export function getMockHeaderless429Message(): string | null
export function getMockHeaders(): MockHeaders | null
export function getMockStatus(): string
export function clearMockHeaders(): void
export function applyMockHeaders(headers: Headers): Headers
export function shouldProcessMockLimits(): boolean
export function getCurrentMockScenario(): MockScenario | null
export function getScenarioDescription(scenario: MockScenario): string
export function setMockSubscriptionType(type: SubscriptionType | null): void
export function getMockSubscriptionType(): SubscriptionType | null
export function shouldUseMockSubscription(): boolean
export function setMockBillingAccess(hasAccess: boolean | null): void
export function isMockFastModeRateLimitScenario(): boolean
export function checkMockFastModeRateLimit(isFastModeActive?: boolean): MockHeaders | null
```
---
## services/notifier.ts
**Path:** `src/services/notifier.ts`
**Purpose:** System-level desktop notifications for long-running operations.
**Key Logic:**
- Sends macOS/Linux notifications when task completes (user is away)
- Uses `node-notifier` or native OS notification APIs
- Gated on user preference and focus state
---
## services/preventSleep.ts
**Path:** `src/services/preventSleep.ts`
**Purpose:** Prevents system sleep while Claude Code tasks are running.
**Key Logic:**
- Uses platform-specific APIs (caffeinate on macOS, systemd-inhibit on Linux)
- Returns a cleanup function to re-enable sleep
- Only active during tool execution phases
---
## services/PromptSuggestion/promptSuggestion.ts
**Path:** `src/services/PromptSuggestion/promptSuggestion.ts`
**Purpose:** Generates prompt suggestions based on current codebase context for the prompt input autocomplete.
**Key Logic:**
- Analyzes recent git changes, open files, and task patterns
- Returns ranked suggestion list
- Caches suggestions per-context hash to avoid redundant generation
---
## services/PromptSuggestion/speculation.ts
**Path:** `src/services/PromptSuggestion/speculation.ts`
**Purpose:** Speculative pre-execution — starts running likely next commands before user confirms, then either applies or discards the result.
**Key Logic:**
- Monitors user typing patterns to predict next action
- Pre-warms common tool executions (file reads, searches)
- Cancels speculative execution if prediction was wrong
---
## services/rateLimitMocking.ts
**Path:** `src/services/rateLimitMocking.ts`
**Purpose:** Facade layer for rate limit mock application and error checking.
**Exports:**
```typescript
export function processRateLimitHeaders(headers: Headers): Headers
export function shouldProcessRateLimits(isSubscriber: boolean): boolean
export function checkMockRateLimitError(
currentModel: string,
isFastModeActive?: boolean
): APIError | null
export function isMockRateLimitError(error: unknown): boolean
export { shouldProcessMockLimits } // re-exported from mockRateLimits.ts
```
---
## services/rateLimitMessages.ts
**Path:** `src/services/rateLimitMessages.ts`
**Purpose:** Human-readable rate limit message formatting and display logic.
**Key Logic:**
- Formats rate limit headers into user-friendly messages
- Handles early warning, hard limit, and reset time display
- Localizes timestamps to user's timezone
---
## services/SessionMemory/prompts.ts
**Path:** `src/services/SessionMemory/prompts.ts`
**Purpose:** Prompt templates for session memory operations (summarization, retrieval, consolidation).
---
## services/SessionMemory/sessionMemory.ts
**Path:** `src/services/SessionMemory/sessionMemory.ts`
**Purpose:** Manages persistent session memory — stores and retrieves relevant context snippets across sessions.
**Key Logic:**
- GrowthBook gate: `tengu_session_memory`
- Stores memory in `~/.claude/sessions/<sessionId>/memory.jsonl`
- Retrieves relevant memories using semantic similarity
- Integrates with conversation context as system prompt additions
---
## services/SessionMemory/sessionMemoryUtils.ts
**Path:** `src/services/SessionMemory/sessionMemoryUtils.ts`
**Purpose:** Utility functions for session memory operations (formatting, filtering, path resolution).
---
## services/tokenEstimation.ts
**Path:** `src/services/tokenEstimation.ts`
**Purpose:** Rough token count estimation without calling the API tokenizer.
**Exports:**
```typescript
export function roughTokenCountEstimation(text: string): number
```
**Key Logic:** Approximates token count as `text.length / 4` (roughly 4 chars per token for English/code). Used for pre-flight estimates in compaction decisions.
---
## services/vcr.ts
**Path:** `src/services/vcr.ts`
**Purpose:** VCR (Video Cassette Recorder) test fixture system — records and replays API interactions for deterministic testing.
**Exports:**
```typescript
export async function withVCR<T>(
messages: unknown[],
f: () => Promise<T>
): Promise<T>
export async function withFixture<T>(
input: unknown,
fixtureName: string,
f: () => Promise<T>
): Promise<T>
export async function withStreamingVCR<T>(
messages: unknown[],
f: () => Promise<T>
): Promise<T>
```
**Key Logic:**
- SHA1-hashes input to create fixture filenames (deterministic, content-addressed)
- `FORCE_VCR` env var: ants can force VCR mode outside of test environment
- CI guard: fails if fixture is missing and `VCR_RECORD` is not set (prevents silent misses)
- Fixture storage: `src/test-fixtures/vcr/` directory
---
## services/voice.ts
**Path:** `src/services/voice.ts`
**Purpose:** Audio recording service for push-to-talk voice input. Supports native audio (cpal via NAPI) on macOS/Linux/Windows with SoX and arecord fallbacks on Linux.
**Constants:**
- `RECORDING_SAMPLE_RATE = 16000`
- `RECORDING_CHANNELS = 1`
- `SILENCE_DURATION_SECS = '2.0'` — SoX silence detection
- `SILENCE_THRESHOLD = '3%'`
**Exports:**
```typescript
export type RecordingAvailability = {
available: boolean
reason: string | null
}
export async function checkVoiceDependencies(): Promise<{
available: boolean
missing: string[]
installCommand: string | null
}>
export async function requestMicrophonePermission(): Promise<boolean>
export async function checkRecordingAvailability(): Promise<RecordingAvailability>
export async function startRecording(
onData: (chunk: Buffer) => void,
onEnd: () => void,
options?: { silenceDetection?: boolean }
): Promise<boolean>
export function stopRecording(): void
export function _resetArecordProbeForTesting(): void
export function _resetAlsaCardsForTesting(): void
```
**Key Logic:**
- **Backend selection priority:** native (cpal via NAPI) → arecord (ALSA, Linux only) → SoX rec
- **Native module:** `audio-capture-napi` is lazy-loaded on first voice keypress (dlopen blocks event loop ~1s warm, ~8s cold)
- **arecord probe:** memoized async probe that verifies device open succeeds (not just binary existence); 150ms race timer
- **Linux ALSA guard:** checks `/proc/asound/cards` before using native cpal to avoid spurious stderr
- **WSL handling:** distinguishes WSL1 (no audio), Win10 WSL2 (no audio), Win11 WSLg (PulseAudio works)
- SoX arguments: raw PCM 16kHz/16-bit/mono with `--buffer 1024` for small chunk flushing and silence detection
- Push-to-talk mode: `silenceDetection: false` ignores native module's silence-triggered `onEnd`
---
## services/voiceKeyterms.ts
**Path:** `src/services/voiceKeyterms.ts`
**Purpose:** Generates domain-specific vocabulary hints (Deepgram "keywords") for improved STT accuracy in the voice_stream endpoint.
**Constants:**
- `MAX_KEYTERMS = 50`
- `GLOBAL_KEYTERMS`: hardcoded list including `'MCP'`, `'symlink'`, `'grep'`, `'regex'`, `'localhost'`, `'codebase'`, `'TypeScript'`, `'JSON'`, `'OAuth'`, `'webhook'`, `'gRPC'`, `'dotfiles'`, `'subagent'`, `'worktree'`
**Exports:**
```typescript
export function splitIdentifier(name: string): string[]
export async function getVoiceKeyterms(
recentFiles?: ReadonlySet<string>
): Promise<string[]>
```
**Key Logic:**
- `splitIdentifier()`: splits camelCase/PascalCase/kebab-case/snake_case/path identifiers into words; discards fragments ≤2 chars
- `getVoiceKeyterms()`: combines global terms + project root basename + git branch words + recent file name words
- Project root basename kept whole (not split) to match full project name phrases
- Git branch words split via `splitIdentifier()`; recent files split by filename stem
- Capped at `MAX_KEYTERMS = 50`
---
## services/voiceStreamSTT.ts
**Path:** `src/services/voiceStreamSTT.ts`
**Purpose:** voice_stream WebSocket STT client. Connects to `wss://api.anthropic.com/api/ws/speech_to_text/voice_stream` using OAuth credentials for push-to-talk transcription.
**Constants:**
- `VOICE_STREAM_PATH = '/api/ws/speech_to_text/voice_stream'`
- `KEEPALIVE_INTERVAL_MS = 8_000`
- `FINALIZE_TIMEOUTS_MS = { safety: 5_000, noData: 1_500 }` (exported for tests)
- Wire messages: `KEEPALIVE_MSG = '{"type":"KeepAlive"}'`, `CLOSE_STREAM_MSG = '{"type":"CloseStream"}'`
**Exports:**
```typescript
export const FINALIZE_TIMEOUTS_MS: { safety: number; noData: number }
export type VoiceStreamCallbacks = {
onTranscript: (text: string, isFinal: boolean) => void
onError: (error: string, opts?: { fatal?: boolean }) => void
onClose: () => void
onReady: (connection: VoiceStreamConnection) => void
}
export type FinalizeSource =
| 'post_closestream_endpoint'
| 'no_data_timeout'
| 'safety_timeout'
| 'ws_close'
| 'ws_already_closed'
export type VoiceStreamConnection = {
send: (audioChunk: Buffer) => void
finalize: () => Promise<FinalizeSource>
close: () => void
isConnected: () => boolean
}
export function isVoiceStreamAvailable(): boolean
export async function connectVoiceStream(
callbacks: VoiceStreamCallbacks,
options?: { language?: string; keyterms?: string[] }
): Promise<VoiceStreamConnection | null>
```
**Key Logic:**
- Only available for OAuth-authenticated users (OAuth tokens); gates on `isOAuthAuthEnabled()` and valid access token
- Routes to `api.anthropic.com` (not `claude.ai`) to avoid Cloudflare TLS fingerprinting challenges
- `VOICE_STREAM_BASE_URL` env var allows override for testing
- URL params: `encoding=linear16`, `sample_rate=16000`, `channels=1`, `endpointing_ms=300`, `utterance_end_ms=1000`
- GrowthBook gate `tengu_cobalt_frost`: enables Nova 3 STT provider via `use_conversation_engine=true&stt_provider=deepgram-nova3`
- Keyterms appended as repeated `keyterms=` query params
- keepalive interval: 8s
- `finalize()` sends `CloseStream`, races `noData` (1.5s) vs `safety` (5s) timers vs `TranscriptEndpoint` message
- Wire message types: `TranscriptText`, `TranscriptEndpoint`, `TranscriptError`, `error`
- Ant-only build (behind `feature('VOICE_MODE')` gate)
---
## context/QueuedMessageContext.tsx
**Path:** `src/context/QueuedMessageContext.tsx`
**Purpose:** React context for the queued message system — tracks messages waiting to be sent to Claude when the current turn completes.
**Exports:**
```typescript
export const QueuedMessageContext: React.Context<QueuedMessage[]>
export function useQueuedMessages(): QueuedMessage[]
export function QueuedMessageProvider(props: { children: React.ReactNode }): JSX.Element
```
---
## context/fpsMetrics.tsx
**Path:** `src/context/fpsMetrics.tsx`
**Purpose:** FPS (frames per second) measurement context for Ink terminal rendering performance monitoring.
**Exports:**
```typescript
export function FpsMetricsProvider(props: { children: React.ReactNode }): JSX.Element
export function useFpsMetrics(): { fps: number; frameCount: number }
```
---
## context/mailbox.tsx
**Path:** `src/context/mailbox.tsx`
**Purpose:** Provides inter-agent messaging context — the "mailbox" for receiving messages from other agents/workers.
**Exports:**
```typescript
export type MailboxMessage = { from: string; content: string; timestamp: number }
export const MailboxContext: React.Context<MailboxMessage[]>
export function MailboxProvider(props: { children: React.ReactNode }): JSX.Element
export function useMailbox(): MailboxMessage[]
```
---
## context/modalContext.tsx
**Path:** `src/context/modalContext.tsx`
**Purpose:** Context for managing modal dialog state — tracks which modal is currently open and provides open/close actions.
**Exports:**
```typescript
export type ModalState = { isOpen: boolean; content: React.ReactNode | null }
export const ModalContext: React.Context<ModalState>
export function ModalProvider(props: { children: React.ReactNode }): JSX.Element
export function useModal(): { open: (content: React.ReactNode) => void; close: () => void }
```
---
## context/notifications.tsx
**Path:** `src/context/notifications.tsx`
**Purpose:** Notification queue management — displays toast-style notifications in the status line with priority ordering, deduplication, fold/merge, and timeout handling.
**Types:**
```typescript
type Priority = 'low' | 'medium' | 'high' | 'immediate'
type BaseNotification = {
key: string
invalidates?: string[]
priority: Priority
timeoutMs?: number
fold?: (accumulator: Notification, incoming: Notification) => Notification
}
type TextNotification = BaseNotification & { text: string; color?: keyof Theme }
type JSXNotification = BaseNotification & { jsx: React.ReactNode }
export type Notification = TextNotification | JSXNotification
```
**Exports:**
```typescript
const DEFAULT_TIMEOUT_MS = 8000
export function useNotifications(): {
addNotification: (content: Notification) => void
removeNotification: (key: string) => void
}
```
**Key Logic:**
- Notification state lives in `AppState` (`notifications.current` + `notifications.queue`)
- `immediate` priority: bypasses queue, shows immediately, re-queues current (non-immediate) notification
- `fold` function: merges notifications with the same key (accumulator pattern)
- Deduplication: only one notification per key in queue + current
- `invalidates[]`: removes named notifications from queue and clears current if matching
- `DEFAULT_TIMEOUT_MS = 8000` (8 seconds); auto-advance to next queued after timeout
- Module-level `currentTimeoutId` tracks the active auto-dismiss timer
---
## context/overlayContext.tsx
**Path:** `src/context/overlayContext.tsx`
**Purpose:** Overlay tracking for Escape key coordination. Tracks which overlays (dialogs, selects) are currently open so the cancel handler doesn't misinterpret Escape presses.
**Constants:**
```typescript
const NON_MODAL_OVERLAYS = new Set(['autocomplete'])
```
**Exports:**
```typescript
export function useRegisterOverlay(id: string, enabled?: boolean): void
export function useIsOverlayActive(): boolean
export function useIsModalOverlayActive(): boolean
```
**Key Logic:**
- State stored in `AppState.activeOverlays: Set<string>`
- `useRegisterOverlay()`: registers on mount (useEffect), unregisters on unmount via cleanup
- On overlay close: triggers `instances.get(process.stdout)?.invalidatePrevFrame()` (via useLayoutEffect) to force full-damage diff — prevents ghost cells from tall overlays (e.g. FuzzyPicker)
- `useIsOverlayActive()`: `activeOverlays.size > 0`
- `useIsModalOverlayActive()`: any overlay in set that is NOT in `NON_MODAL_OVERLAYS`
---
## context/promptOverlayContext.tsx
**Path:** `src/context/promptOverlayContext.tsx`
**Purpose:** Context for prompt-level overlay management — tracks overlays that affect prompt input focus and behavior.
**Exports:**
```typescript
export function useRegisterPromptOverlay(id: string, enabled?: boolean): void
export function useIsPromptOverlayActive(): boolean
```
---
## context/stats.tsx
**Path:** `src/context/stats.tsx`
**Purpose:** In-process performance metrics store with reservoir sampling for histograms. Persists metrics to project config on process exit.
**Constants:**
- `RESERVOIR_SIZE = 1024` — reservoir sampling capacity for histograms
**Types:**
```typescript
export type StatsStore = {
increment(name: string, value?: number): void
set(name: string, value: number): void
observe(name: string, value: number): void
add(name: string, value: string): void
getAll(): Record<string, number>
}
```
**Exports:**
```typescript
export function createStatsStore(): StatsStore
export const StatsContext: React.Context<StatsStore | null>
export function StatsProvider(props: { store?: StatsStore; children: React.ReactNode }): JSX.Element
export function useStats(): StatsStore
export function useCounter(name: string): (value?: number) => void
export function useGauge(name: string): (value: number) => void
export function useTimer(name: string): (value: number) => void
// ... additional hook exports
```
**Key Logic:**
- `createStatsStore()`: creates a stats store with three internal data structures:
- `metrics: Map<string, number>` — counters and gauges
- `histograms: Map<string, Histogram>` — reservoir-sampled distributions
- `sets: Map<string, Set<string>>` — unique value sets (reported as `.size`)
- `observe()`: histogram using reservoir sampling (Algorithm R) with `RESERVOIR_SIZE = 1024`
- `getAll()`: returns flat `Record<string, number>` with histogram percentiles (`_p50`, `_p95`, `_p99`), min, max, avg, count
- `StatsProvider`: flushes metrics to `lastSessionMetrics` in project config on process `'exit'` event
- `useCounter()` / `useGauge()` / `useTimer()`: memoized hooks returning bound store methods
---
## context/voice.tsx
**Path:** `src/context/voice.tsx`
**Purpose:** Voice mode context — provides voice recording state, transcript, and connection status to the UI.
**Exports:**
```typescript
export type VoiceContextValue = {
isRecording: boolean
transcript: string
isConnecting: boolean
error: string | null
startRecording: () => void
stopRecording: () => void
}
export const VoiceContext: React.Context<VoiceContextValue>
export function VoiceProvider(props: { children: React.ReactNode }): JSX.Element
export function useVoice(): VoiceContextValue
```
---
## screens/Doctor.tsx
**Path:** `src/screens/Doctor.tsx`
**Purpose:** The `/doctor` command UI screen — displays system health diagnostics including version info, environment checks, MCP server status, sandbox status, keybinding warnings, and available updates.
**Props:**
```typescript
type Props = {
onDone: () => void
}
```
**Internal Types:**
```typescript
type AgentInfo = {
name: string
version: string
// ...
}
type VersionLockInfo = {
locked: boolean
version?: string
// ...
}
```
**Key Logic:**
- Uses `getDoctorDiagnostic()` for environment checks
- Calls `checkContextWarnings()` for context-related issues
- Loads dist tags with `getNpmDistTags()` and `getGcsDistTags()` (wrapped in Suspense)
- Renders sub-sections: `SandboxDoctorSection`, `ValidationErrorsList`, `KeybindingWarnings`, `McpParsingWarnings`
- Displays version comparison: current vs latest npm/GCS versions
- Shows agent info, version lock status, and update channels
---
## screens/REPL.tsx
**Path:** `src/screens/REPL.tsx`
**Purpose:** The main interactive REPL screen — the primary conversational UI that orchestrates the entire Claude Code interactive session.
**Key Logic:**
- Manages the full conversation lifecycle: user input → API query → tool execution → response display
- Handles all interactive features: conversation history, compaction, clear, file editing, permissions
- Routes slash commands to command handlers
- Manages modal dialogs (permissions, MCP approvals, etc.)
- Integrates with all context providers: notifications, overlays, voice, stats
- The largest component in the codebase, responsible for the overall user interaction loop
---
## screens/ResumeConversation.tsx
**Path:** `src/screens/ResumeConversation.tsx`
**Purpose:** UI screen for the session resume flow — shows a list of recent sessions with previews and allows the user to select one to resume.
**Key Logic:**
- Loads session index from disk
- Renders `SessionPreview` components for each session
- Handles keyboard navigation (arrow keys, Enter to select)
- Filters sessions by project root or shows all
- Passes selected session ID back to caller via callback
---
## Cross-Cutting Architecture Notes
### State Management Architecture
The codebase uses three distinct state layers:
1. **`bootstrap/state.ts`** — Global mutable singleton for session-scoped state (costs, model, telemetry). Intentionally a DAG leaf — imports nothing from services.
2. **React `AppState`** — UI state via Zustand-like store (`state/AppState.ts`). Accessed via `useAppState()` selectors and `useSetAppState()`.
3. **React Contexts** — Domain-specific contexts (notifications, overlays, stats, voice) for scoped subtree state.
### Analytics Architecture
Events flow through a multi-layer pipeline:
```
logEvent() → index.ts queue → sink.ts dispatch
→ [sampling check] → [isSinkKilled check]
→ datadog.ts (strips _PROTO_ keys)
→ firstPartyEventLogger.ts (hoists _PROTO_ keys to proto fields)
```
All analytics is disabled when: `NODE_ENV === test`, 3P providers (Bedrock/Vertex/Foundry), or `isTelemetryDisabled()`.
### Compact/Microcompact Architecture
Context compression has multiple layers:
1. **`apiMicrocompact.ts`** — API-native server-side editing (cache_edits) — no client-side mutation
2. **`microCompact.ts`** — Client-side tool result clearing (cached path + time-based path)
3. **`autoCompact.ts`** — Full conversation summarization trigger (threshold-based)
4. **`compact.ts`** — Full summarization implementation (LLM call)
5. **`sessionMemoryCompact.ts`** — Lighter session memory segment summarization
### GrowthBook Feature Flag Naming Convention
All feature flags use obfuscated/mangled names with `tengu_` prefix to prevent scraping:
- Feature flags: `tengu_prompt_cache_1h_config`, `tengu_session_memory`, `tengu_sm_compact`, etc.
- Kill switches: `tengu_frond_boric` (analytics sink killswitch)
- Voice: `tengu_cobalt_frost` (Nova 3 STT)
- AutoDream: `tengu_onyx_plover`
- Coordinator mode: checked via `feature('COORDINATOR_MODE')` bundle flag