import React from 'react' import { Box, Text } from 'ink' import { theme, PANEL_WIDTH } from '../../../../ui/theme.js' import { fitHint } from '../../../../ui/Select.js' import { FieldRow } from './FieldRow.js' import type { EthagentConfig, EthagentIdentity } from '../../../../storage/config.js' import { displayCustodyMode, identityOwnerAddress, readCustodyMode, readIdentityStateString, } from '../../custody/state.js' import { hasPendingPublish } from '../../continuity/state.js' import { ensValidationReasonText, selectEnsStatus } from '../../ens/state.js' import { shortAddress } from '../model/format.js' import { identitySummaryRows, lastBackupLabel } from '../../profile/identity.js' import { transferSnapshotView, type TransferSnapshotView } from '../../transfer/state.js' import type { ContinuityWorkingTreeStatus } from '../../../continuity/storage.js' interface IdentitySummaryProps { identity?: EthagentIdentity config?: EthagentConfig workingStatus?: ContinuityWorkingTreeStatus | null hideHeader?: boolean tokenLinked?: boolean onchainOwner?: string compact?: boolean } export const IdentitySummary: React.FC = ({ identity, config, hideHeader = false, tokenLinked = true, onchainOwner, compact = false }) => { if (!identity) { return ( No agent yet. Create or load one. ) } const rows = identitySummaryRows(identity, config) const lastBackup = lastBackupLabel(identity) const stateName = readIdentityStateString(identity.state, 'name') const row = (label: string) => rows.find(item => item.label === label) const ensStatus = selectEnsStatus(identity) const custodyMode = readCustodyMode(identity.state) const activeOperator = readIdentityStateString(identity.state, 'activeOperatorAddress') const approvedOperatorCount = Array.isArray((identity.state as Record | undefined)?.approvedOperatorWallets) ? ((identity.state as Record).approvedOperatorWallets as unknown[]).length : 0 const ownerAddress = identityOwnerAddress(identity, onchainOwner) const transferSnapshot = transferSnapshotView(identity) const tokenValue = row('token')?.value ?? 'Not Created' const networkValue = row('network')?.value ?? 'Unknown' const tokenLine = identity.agentId ? `${tokenValue} · ${displayValue(networkValue)}` : displayValue(tokenValue) if (compact) { const rawName = stateName || 'Active Agent' const tokenSegment = identity.agentId ? `#${identity.agentId}` : null const networkSegment = identity.agentId ? networkValue : null const segmentsWidth = (tokenSegment ? tokenSegment.length + 3 : 0) + (networkSegment ? networkSegment.length + 3 : 0) const name = fitHint(rawName, Math.max(8, Math.min(16, PANEL_WIDTH - 4 - segmentsWidth))) const ensSegment = ensStatus.kind === 'linked' ? ensStatus.name === rawName ? null : ensStatus.name : ensStatus.kind === 'issue' ? ensStatus.name : null const usedWidth = name.length + segmentsWidth const ensText = ensSegment ? fitHint(ensSegment, PANEL_WIDTH - 4 - usedWidth - 3) : '' return ( {name} {tokenSegment ? <> · {tokenSegment} : null} {networkSegment ? <> · {networkSegment} : null} {ensText ? <> · {ensText} : null} ) } return ( {hideHeader ? null : ( <> {stateName || 'Active Agent'} {tokenLine} )} {ensStatus.name} : ensStatus.kind === 'issue' ? {ensStatus.name} ({ensValidationReasonText(ensStatus.reason)}) : Not Linked, }} right={tokenLinked ? { label: 'Custody', value: {displayCustodyMode(custodyMode)}, } : undefined} /> {(() => { const vaultAddress = custodyMode === 'advanced' ? readIdentityStateString(identity.state, 'operatorVaultAddress') : undefined const pairedOperatorsValue = custodyMode === 'advanced' && tokenLinked ? approvedOperatorCount > 1 ? {`${approvedOperatorCount} authorized`} : activeOperator ? {shortAddress(activeOperator)} : None Authorized : null const lastSavedCell = { label: 'Last Saved', value: {displayValue(lastBackup)}, } const pendingCell = { label: 'Pending', value: local ahead of onchain, } return ( <> {ownerAddress ? ( {shortAddress(ownerAddress)}, }} {...(pairedOperatorsValue ? { right: { label: 'Operators', value: pairedOperatorsValue } } : {})} /> ) : null} {vaultAddress ? ( {shortAddress(vaultAddress)} }} right={hasPendingPublish(identity) ? pendingCell : lastSavedCell} /> ) : ( hasPendingPublish(identity) ? : )} ) })()} {transferSnapshot ? ( ) : null} ) } type SummaryCell = { label: string; value: React.ReactNode } const LEFT_LABEL_WIDTH = 12 const SummaryCellLine: React.FC<{ cell: SummaryCell }> = ({ cell }) => ( ) const SummaryRow: React.FC<{ left: SummaryCell; right?: SummaryCell }> = ({ left, right }) => { if (!right) { return } return ( ) } const TransferSnapshotStatus: React.FC<{ status: NonNullable }> = ({ status }) => { const receiverLabel = status.receiverHandle && status.receiverHandle !== status.receiver ? `${shortAddress(status.receiver)} (${status.receiverHandle})` : shortAddress(status.receiver) const title = status.kind === 'ready-to-transfer' ? 'Transfer snapshot ready' : 'Transfer snapshot received' const detail = status.kind === 'ready-to-transfer' ? 'sender can transfer externally' : 'receiver can restore from this snapshot' return ( {title} {shortAddress(status.sender)}} /> {receiverLabel}} /> {status.slotCount} decrypt slots {detail} ) } function displayValue(value: string): string { const mapped = DISPLAY_VALUES[value] return mapped ?? value } const DISPLAY_VALUES: Record = { 'not attached': 'Not Attached', 'not connected': 'Not Connected', 'not created': 'Not Created', 'not saved': 'Not Saved', 'not saved yet': 'Not Saved Yet', 'never': 'Never', 'unknown': 'Unknown', 'ethereum mainnet': 'Ethereum Mainnet', 'base': 'Base', }