Download src/state/onChangeAppState.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 6.2 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/state/onChangeAppState.ts
- Command line
-
hf download hf://chenbhao/codev/src/state/onChangeAppState.ts
-
curl -L -o onChangeAppState.ts https://huggingface.co/chenbhao/codev/resolve/main/src/state/onChangeAppState.ts
6.2 kB
| import { setMainLoopModelOverride } from '../bootstrap/state.js' | |
| import { | |
| clearApiKeyHelperCache, | |
| clearAwsCredentialsCache, | |
| clearGcpCredentialsCache, | |
| } from '../utils/auth.js' | |
| import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js' | |
| import { toError } from '../utils/errors.js' | |
| import { logError } from '../utils/log.js' | |
| import { applyConfigEnvironmentVariables } from '../utils/managedEnv.js' | |
| import { | |
| permissionModeFromString, | |
| toExternalPermissionMode, | |
| } from '../utils/permissions/PermissionMode.js' | |
| import { | |
| notifyPermissionModeChanged, | |
| notifySessionMetadataChanged, | |
| type SessionExternalMetadata, | |
| } from '../utils/sessionState.js' | |
| import { updateSettingsForSource } from '../utils/settings/settings.js' | |
| import type { AppState } from './AppStateStore.js' | |
| // Inverse of the push below — restore on worker restart. | |
| export function externalMetadataToAppState( | |
| metadata: SessionExternalMetadata, | |
| ): (prev: AppState) => AppState { | |
| return prev => ({ | |
| ...prev, | |
| ...(typeof metadata.permission_mode === 'string' | |
| ? { | |
| toolPermissionContext: { | |
| ...prev.toolPermissionContext, | |
| mode: permissionModeFromString(metadata.permission_mode), | |
| }, | |
| } | |
| : {}), | |
| ...(typeof metadata.is_ultraplan_mode === 'boolean' | |
| ? { isUltraplanMode: metadata.is_ultraplan_mode } | |
| : {}), | |
| }) | |
| } | |
| export function onChangeAppState({ | |
| newState, | |
| oldState, | |
| }: { | |
| newState: AppState | |
| oldState: AppState | |
| }) { | |
| // toolPermissionContext.mode — single choke point for CCR/SDK mode sync. | |
| // | |
| // Prior to this block, mode changes were relayed to CCR by only 2 of 8+ | |
| // mutation paths: a bespoke setAppState wrapper in print.ts (headless/SDK | |
| // mode only) and a manual notify in the set_permission_mode handler. | |
| // Every other path — Shift+Tab cycling, ExitPlanModePermissionRequest | |
| // dialog options, the /plan slash command, rewind, the REPL bridge's | |
| // onSetPermissionMode — mutated AppState without telling | |
| // CCR, leaving external_metadata.permission_mode stale and the web UI out | |
| // of sync with the CLI's actual mode. | |
| // | |
| // Hooking the diff here means ANY setAppState call that changes the mode | |
| // notifies CCR (via notifySessionMetadataChanged → ccrClient.reportMetadata) | |
| // and the SDK status stream (via notifyPermissionModeChanged → registered | |
| // in print.ts). The scattered callsites above need zero changes. | |
| const prevMode = oldState.toolPermissionContext.mode | |
| const newMode = newState.toolPermissionContext.mode | |
| if (prevMode !== newMode) { | |
| // CCR external_metadata must not receive internal-only mode names | |
| // (bubble, ungated auto). Externalize first — and skip | |
| // the CCR notify if the EXTERNAL mode didn't change (e.g., | |
| // default→bubble→default is noise from CCR's POV since both | |
| // externalize to 'default'). The SDK channel (notifyPermissionModeChanged) | |
| // passes raw mode; its listener in print.ts applies its own filter. | |
| const prevExternal = toExternalPermissionMode(prevMode) | |
| const newExternal = toExternalPermissionMode(newMode) | |
| if (prevExternal !== newExternal) { | |
| // Ultraplan = first plan cycle only. The initial control_request | |
| // sets mode and isUltraplanMode atomically, so the flag's | |
| // transition gates it. null per RFC 7396 (removes the key). | |
| const isUltraplan = | |
| newExternal === 'plan' && | |
| newState.isUltraplanMode && | |
| !oldState.isUltraplanMode | |
| ? true | |
| : null | |
| notifySessionMetadataChanged({ | |
| permission_mode: newExternal, | |
| is_ultraplan_mode: isUltraplan, | |
| }) | |
| } | |
| notifyPermissionModeChanged(newMode) | |
| } | |
| // mainLoopModel: remove it from settings? | |
| if ( | |
| newState.mainLoopModel !== oldState.mainLoopModel && | |
| newState.mainLoopModel === null | |
| ) { | |
| // Remove from settings | |
| updateSettingsForSource('userSettings', { model: undefined }) | |
| setMainLoopModelOverride(null) | |
| } | |
| // mainLoopModel: add it to settings? | |
| if ( | |
| newState.mainLoopModel !== oldState.mainLoopModel && | |
| newState.mainLoopModel !== null | |
| ) { | |
| // Save to settings | |
| updateSettingsForSource('userSettings', { model: newState.mainLoopModel }) | |
| setMainLoopModelOverride(newState.mainLoopModel) | |
| } | |
| // expandedView → persist as showExpandedTodos + showSpinnerTree for backwards compat | |
| if (newState.expandedView !== oldState.expandedView) { | |
| const showExpandedTodos = newState.expandedView === 'tasks' | |
| const showSpinnerTree = newState.expandedView === 'teammates' | |
| if ( | |
| getGlobalConfig().showExpandedTodos !== showExpandedTodos || | |
| getGlobalConfig().showSpinnerTree !== showSpinnerTree | |
| ) { | |
| saveGlobalConfig(current => ({ | |
| ...current, | |
| showExpandedTodos, | |
| showSpinnerTree, | |
| })) | |
| } | |
| } | |
| // verbose | |
| if ( | |
| newState.verbose !== oldState.verbose && | |
| getGlobalConfig().verbose !== newState.verbose | |
| ) { | |
| const verbose = newState.verbose | |
| saveGlobalConfig(current => ({ | |
| ...current, | |
| verbose, | |
| })) | |
| } | |
| // tungstenPanelVisible (ant-only tmux panel sticky toggle) | |
| if (process.env.USER_TYPE === 'ant') { | |
| if ( | |
| newState.tungstenPanelVisible !== oldState.tungstenPanelVisible && | |
| newState.tungstenPanelVisible !== undefined && | |
| getGlobalConfig().tungstenPanelVisible !== newState.tungstenPanelVisible | |
| ) { | |
| const tungstenPanelVisible = newState.tungstenPanelVisible | |
| saveGlobalConfig(current => ({ ...current, tungstenPanelVisible })) | |
| } | |
| } | |
| // settings: clear auth-related caches when settings change | |
| // This ensures apiKeyHelper and AWS/GCP credential changes take effect immediately | |
| if (newState.settings !== oldState.settings) { | |
| try { | |
| clearApiKeyHelperCache() | |
| clearAwsCredentialsCache() | |
| clearGcpCredentialsCache() | |
| // Re-apply environment variables when settings.env changes | |
| // This is additive-only: new vars are added, existing may be overwritten, nothing is deleted | |
| if (newState.settings.env !== oldState.settings.env) { | |
| applyConfigEnvironmentVariables() | |
| } | |
| } catch (error) { | |
| logError(toError(error)) | |
| } | |
| } | |
| } | |