import figures from 'figures' import * as React from 'react' import { Suspense, use } from 'react' import { getSessionId } from '../../bootstrap/state.js' import type { LocalJSXCommandContext } from '../../commands.js' import { useIsInsideModal } from '../../context/modalContext.js' import { Box, Text, useTheme } from '@anthropic/ink' import { type AppState, useAppState } from '../../state/AppState.js' import { getCwd } from '../../utils/cwd.js' import { getCurrentSessionTitle } from '../../utils/sessionStorage.js' import { buildAccountProperties, buildAPIProviderProperties, buildIDEProperties, buildInstallationDiagnostics, buildInstallationHealthDiagnostics, buildMcpProperties, buildMemoryDiagnostics, buildSandboxProperties, buildSettingSourcesProperties, type Diagnostic, getModelDisplayLabel, type Property, } from '../../utils/status.js' import type { ThemeName } from '../../utils/theme.js' import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' type Props = { context: LocalJSXCommandContext diagnosticsPromise: Promise } function buildPrimarySection(): Property[] { const sessionId = getSessionId() const customTitle = getCurrentSessionTitle(sessionId) const nameValue = customTitle ?? /rename to add a name return [ { label: 'Version', value: MACRO.VERSION }, { label: 'Session name', value: nameValue }, { label: 'Session ID', value: sessionId }, { label: 'cwd', value: getCwd() }, ...buildAccountProperties(), ...buildAPIProviderProperties(), ] } function buildSecondarySection({ mainLoopModel, mcp, theme, context, }: { mainLoopModel: AppState['mainLoopModel'] mcp: AppState['mcp'] theme: ThemeName context: LocalJSXCommandContext }): Property[] { const modelLabel = getModelDisplayLabel(mainLoopModel) return [ { label: 'Model', value: modelLabel }, ...buildIDEProperties( mcp.clients, context.options.ideInstallationStatus, theme, ), ...buildMcpProperties(mcp.clients, theme), ...buildSandboxProperties(), ...buildSettingSourcesProperties(), ] } export async function buildDiagnostics(): Promise { return [ ...(await buildInstallationDiagnostics()), ...(await buildInstallationHealthDiagnostics()), ...(await buildMemoryDiagnostics()), ] } function PropertyValue({ value, }: { value: Property['value'] }): React.ReactNode { if (Array.isArray(value)) { return ( {value.map((item, i) => { return ( {item} {i < value.length - 1 ? ',' : ''} ) })} ) } if (typeof value === 'string') { return {value} } return value } export function Status({ context, diagnosticsPromise, }: Props): React.ReactNode { const mainLoopModel = useAppState(s => s.mainLoopModel) const mcp = useAppState(s => s.mcp) const [theme] = useTheme() // Sections are synchronous — compute in render so they're never empty. // diagnosticsPromise is created once in Settings.tsx so it resolves once // per pane invocation instead of re-fetching on every tab switch (Tab // unmounts children when not selected, which was causing the flash). const sections = React.useMemo( () => [ buildPrimarySection(), buildSecondarySection({ mainLoopModel, mcp, theme, context }), ], [mainLoopModel, mcp, theme, context], ) // flexGrow so the "Esc to cancel" footer pins to the bottom of the // Modal's inner ScrollBox when content is short. The ScrollBox content // wrapper has flexGrow:1 (fills at least the viewport), so this stretches // to match. Without it, short Status content floats at the top and the // footer sits mid-modal with 2-3 trailing blank rows below. Outside a // Modal (non-fullscreen), leave layout alone — no ScrollBox to fill. const grow = useIsInsideModal() ? 1 : undefined return ( {sections.map( (properties, i) => properties.length > 0 && ( {properties.map(({ label, value }, j) => ( {label !== undefined && {label}:} ))} ), )} ) } function Diagnostics({ promise, }: { promise: Promise }): React.ReactNode { const diagnostics = use(promise) if (diagnostics.length === 0) return null return ( System Diagnostics {diagnostics.map((diagnostic, i) => ( {figures.warning} {typeof diagnostic === 'string' ? ( {diagnostic} ) : ( diagnostic )} ))} ) }