style: 完成所有文件的lint

This commit is contained in:
claude-code-best
2026-05-01 21:39:30 +08:00
parent d136872cc9
commit 6182015005
1333 changed files with 68255 additions and 77882 deletions

View File

@@ -1,9 +1,9 @@
import { feature } from 'bun:bundle'
import * as React from 'react'
import { memo, useCallback, useEffect, useRef } from 'react'
import { logEvent } from 'src/services/analytics/index.js'
import { useAppState, useSetAppState } from 'src/state/AppState.js'
import type { PermissionMode } from 'src/utils/permissions/PermissionMode.js'
import { feature } from 'bun:bundle';
import * as React from 'react';
import { memo, useCallback, useEffect, useRef } from 'react';
import { logEvent } from 'src/services/analytics/index.js';
import { useAppState, useSetAppState } from 'src/state/AppState.js';
import type { PermissionMode } from 'src/utils/permissions/PermissionMode.js';
import {
getIsRemoteMode,
getKairosActive,
@@ -11,9 +11,9 @@ import {
getOriginalCwd,
getSdkBetas,
getSessionId,
} from '../bootstrap/state.js'
import { DEFAULT_OUTPUT_STYLE_NAME } from '../constants/outputStyles.js'
import { useNotifications } from '../context/notifications.js'
} from '../bootstrap/state.js';
import { DEFAULT_OUTPUT_STYLE_NAME } from '../constants/outputStyles.js';
import { useNotifications } from '../context/notifications.js';
import {
getTotalAPIDuration,
getTotalCost,
@@ -22,45 +22,32 @@ import {
getTotalLinesAdded,
getTotalLinesRemoved,
getTotalOutputTokens,
} from '../cost-tracker.js'
import { useMainLoopModel } from '../hooks/useMainLoopModel.js'
import { type ReadonlySettings, useSettings } from '../hooks/useSettings.js'
import { Ansi, Box, Text } from '@anthropic/ink'
import { getRawUtilization } from '../services/claudeAiLimits.js'
import type { Message } from '../types/message.js'
import type { StatusLineCommandInput } from '../types/statusLine.js'
import type { VimMode } from '../types/textInputTypes.js'
import { checkHasTrustDialogAccepted } from '../utils/config.js'
import {
calculateContextPercentages,
getContextWindowForModel,
} from '../utils/context.js'
import { getCwd } from '../utils/cwd.js'
import { logForDebugging } from '../utils/debug.js'
import { isFullscreenEnvEnabled } from '../utils/fullscreen.js'
import {
createBaseHookInput,
executeStatusLineCommand,
} from '../utils/hooks.js'
import { getLastAssistantMessage } from '../utils/messages.js'
import {
getRuntimeMainLoopModel,
type ModelName,
renderModelName,
} from '../utils/model/model.js'
import { getCurrentSessionTitle } from '../utils/sessionStorage.js'
import {
doesMostRecentAssistantMessageExceed200k,
getCurrentUsage,
} from '../utils/tokens.js'
import { getCurrentWorktreeSession } from '../utils/worktree.js'
import { isVimModeEnabled } from './PromptInput/utils.js'
} from '../cost-tracker.js';
import { useMainLoopModel } from '../hooks/useMainLoopModel.js';
import { type ReadonlySettings, useSettings } from '../hooks/useSettings.js';
import { Ansi, Box, Text } from '@anthropic/ink';
import { getRawUtilization } from '../services/claudeAiLimits.js';
import type { Message } from '../types/message.js';
import type { StatusLineCommandInput } from '../types/statusLine.js';
import type { VimMode } from '../types/textInputTypes.js';
import { checkHasTrustDialogAccepted } from '../utils/config.js';
import { calculateContextPercentages, getContextWindowForModel } from '../utils/context.js';
import { getCwd } from '../utils/cwd.js';
import { logForDebugging } from '../utils/debug.js';
import { isFullscreenEnvEnabled } from '../utils/fullscreen.js';
import { createBaseHookInput, executeStatusLineCommand } from '../utils/hooks.js';
import { getLastAssistantMessage } from '../utils/messages.js';
import { getRuntimeMainLoopModel, type ModelName, renderModelName } from '../utils/model/model.js';
import { getCurrentSessionTitle } from '../utils/sessionStorage.js';
import { doesMostRecentAssistantMessageExceed200k, getCurrentUsage } from '../utils/tokens.js';
import { getCurrentWorktreeSession } from '../utils/worktree.js';
import { isVimModeEnabled } from './PromptInput/utils.js';
export function statusLineShouldDisplay(settings: ReadonlySettings): boolean {
// Assistant mode: statusline fields (model, permission mode, cwd) reflect the
// REPL/daemon process, not what the agent child is actually running. Hide it.
if (feature('KAIROS') && getKairosActive()) return false
return settings?.statusLine !== undefined
if (feature('KAIROS') && getKairosActive()) return false;
return settings?.statusLine !== undefined;
}
function buildStatusLineCommandInput(
@@ -72,28 +59,22 @@ function buildStatusLineCommandInput(
mainLoopModel: ModelName,
vimMode?: VimMode,
): StatusLineCommandInput {
const agentType = getMainThreadAgentType()
const worktreeSession = getCurrentWorktreeSession()
const agentType = getMainThreadAgentType();
const worktreeSession = getCurrentWorktreeSession();
const runtimeModel = getRuntimeMainLoopModel({
permissionMode,
mainLoopModel,
exceeds200kTokens,
})
const outputStyleName = settings?.outputStyle || DEFAULT_OUTPUT_STYLE_NAME
});
const outputStyleName = settings?.outputStyle || DEFAULT_OUTPUT_STYLE_NAME;
const currentUsage = getCurrentUsage(messages)
const contextWindowSize = getContextWindowForModel(
runtimeModel,
getSdkBetas(),
)
const contextPercentages = calculateContextPercentages(
currentUsage,
contextWindowSize,
)
const currentUsage = getCurrentUsage(messages);
const contextWindowSize = getContextWindowForModel(runtimeModel, getSdkBetas());
const contextPercentages = calculateContextPercentages(currentUsage, contextWindowSize);
const sessionId = getSessionId()
const sessionName = getCurrentSessionTitle(sessionId)
const rawUtil = getRawUtilization()
const sessionId = getSessionId();
const sessionName = getCurrentSessionTitle(sessionId);
const rawUtil = getRawUtilization();
const rateLimits: StatusLineCommandInput['rate_limits'] = {
...(rawUtil.five_hour && {
five_hour: {
@@ -107,7 +88,7 @@ function buildStatusLineCommandInput(
resets_at: rawUtil.seven_day.resets_at,
},
}),
}
};
return {
...createBaseHookInput(),
...(sessionName && { session_name: sessionName }),
@@ -167,97 +148,89 @@ function buildStatusLineCommandInput(
original_branch: worktreeSession.originalBranch,
},
}),
}
};
}
type Props = {
// messages stays behind a ref (read only in the debounced callback);
// lastAssistantMessageId is the actual re-render trigger.
messagesRef: React.RefObject<Message[]>
lastAssistantMessageId: string | null
vimMode?: VimMode
}
messagesRef: React.RefObject<Message[]>;
lastAssistantMessageId: string | null;
vimMode?: VimMode;
};
export function getLastAssistantMessageId(messages: Message[]): string | null {
return getLastAssistantMessage(messages)?.uuid ?? null
return getLastAssistantMessage(messages)?.uuid ?? null;
}
function StatusLineInner({
messagesRef,
lastAssistantMessageId,
vimMode,
}: Props): React.ReactNode {
const abortControllerRef = useRef<AbortController | undefined>(undefined)
const permissionMode = useAppState(s => s.toolPermissionContext.mode)
const additionalWorkingDirectories = useAppState(
s => s.toolPermissionContext.additionalWorkingDirectories,
)
const statusLineText = useAppState(s => s.statusLineText)
const setAppState = useSetAppState()
const settings = useSettings()
const { addNotification } = useNotifications()
function StatusLineInner({ messagesRef, lastAssistantMessageId, vimMode }: Props): React.ReactNode {
const abortControllerRef = useRef<AbortController | undefined>(undefined);
const permissionMode = useAppState(s => s.toolPermissionContext.mode);
const additionalWorkingDirectories = useAppState(s => s.toolPermissionContext.additionalWorkingDirectories);
const statusLineText = useAppState(s => s.statusLineText);
const setAppState = useSetAppState();
const settings = useSettings();
const { addNotification } = useNotifications();
// AppState-sourced model — same source as API requests. getMainLoopModel()
// re-reads settings.json on every call, so another session's /model write
// would leak into this session's statusline (anthropics/claude-code#37596).
const mainLoopModel = useMainLoopModel()
const mainLoopModel = useMainLoopModel();
// Keep latest values in refs for stable callback access
const settingsRef = useRef(settings)
settingsRef.current = settings
const vimModeRef = useRef(vimMode)
vimModeRef.current = vimMode
const permissionModeRef = useRef(permissionMode)
permissionModeRef.current = permissionMode
const addedDirsRef = useRef(additionalWorkingDirectories)
addedDirsRef.current = additionalWorkingDirectories
const mainLoopModelRef = useRef(mainLoopModel)
mainLoopModelRef.current = mainLoopModel
const settingsRef = useRef(settings);
settingsRef.current = settings;
const vimModeRef = useRef(vimMode);
vimModeRef.current = vimMode;
const permissionModeRef = useRef(permissionMode);
permissionModeRef.current = permissionMode;
const addedDirsRef = useRef(additionalWorkingDirectories);
addedDirsRef.current = additionalWorkingDirectories;
const mainLoopModelRef = useRef(mainLoopModel);
mainLoopModelRef.current = mainLoopModel;
// Track previous state to detect changes and cache expensive calculations
const previousStateRef = useRef<{
messageId: string | null
exceeds200kTokens: boolean
permissionMode: PermissionMode
vimMode: VimMode | undefined
mainLoopModel: ModelName
messageId: string | null;
exceeds200kTokens: boolean;
permissionMode: PermissionMode;
vimMode: VimMode | undefined;
mainLoopModel: ModelName;
}>({
messageId: null,
exceeds200kTokens: false,
permissionMode,
vimMode,
mainLoopModel,
})
});
// Debounce timer ref
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
undefined,
)
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
// True when the next invocation should log its result (first run or after settings reload)
const logNextResultRef = useRef(true)
const logNextResultRef = useRef(true);
// Stable update function — reads latest values from refs
const doUpdate = useCallback(async () => {
// Cancel any in-flight requests
abortControllerRef.current?.abort()
abortControllerRef.current?.abort();
const controller = new AbortController()
abortControllerRef.current = controller
const controller = new AbortController();
abortControllerRef.current = controller;
const msgs = messagesRef.current
const msgs = messagesRef.current;
const logResult = logNextResultRef.current
logNextResultRef.current = false
const logResult = logNextResultRef.current;
logNextResultRef.current = false;
try {
let exceeds200kTokens = previousStateRef.current.exceeds200kTokens
let exceeds200kTokens = previousStateRef.current.exceeds200kTokens;
// Only recalculate 200k check if messages changed
const currentMessageId = getLastAssistantMessageId(msgs)
const currentMessageId = getLastAssistantMessageId(msgs);
if (currentMessageId !== previousStateRef.current.messageId) {
exceeds200kTokens = doesMostRecentAssistantMessageExceed200k(msgs)
previousStateRef.current.messageId = currentMessageId
previousStateRef.current.exceeds200kTokens = exceeds200kTokens
exceeds200kTokens = doesMostRecentAssistantMessageExceed200k(msgs);
previousStateRef.current.messageId = currentMessageId;
previousStateRef.current.exceeds200kTokens = exceeds200kTokens;
}
const statusInput = buildStatusLineCommandInput(
@@ -268,40 +241,35 @@ function StatusLineInner({
Array.from(addedDirsRef.current.keys()),
mainLoopModelRef.current,
vimModeRef.current,
)
);
const text = await executeStatusLineCommand(
statusInput,
controller.signal,
undefined,
logResult,
)
const text = await executeStatusLineCommand(statusInput, controller.signal, undefined, logResult);
if (!controller.signal.aborted) {
setAppState(prev => {
if (prev.statusLineText === text) return prev
return { ...prev, statusLineText: text }
})
if (prev.statusLineText === text) return prev;
return { ...prev, statusLineText: text };
});
}
} catch {
// Silently ignore errors in status line updates
}
}, [messagesRef, setAppState])
}, [messagesRef, setAppState]);
// Stable debounced schedule function — no deps, uses refs
const scheduleUpdate = useCallback(() => {
if (debounceTimerRef.current !== undefined) {
clearTimeout(debounceTimerRef.current)
clearTimeout(debounceTimerRef.current);
}
debounceTimerRef.current = setTimeout(
(ref, doUpdate) => {
ref.current = undefined
void doUpdate()
ref.current = undefined;
void doUpdate();
},
300,
debounceTimerRef,
doUpdate,
)
}, [doUpdate])
);
}, [doUpdate]);
// Only trigger update when assistant message, permission mode, vim mode, or model actually changes
useEffect(() => {
@@ -313,45 +281,36 @@ function StatusLineInner({
) {
// Don't update messageId here — let doUpdate handle it so
// exceeds200kTokens is recalculated with the latest messages
previousStateRef.current.permissionMode = permissionMode
previousStateRef.current.vimMode = vimMode
previousStateRef.current.mainLoopModel = mainLoopModel
scheduleUpdate()
previousStateRef.current.permissionMode = permissionMode;
previousStateRef.current.vimMode = vimMode;
previousStateRef.current.mainLoopModel = mainLoopModel;
scheduleUpdate();
}
}, [
lastAssistantMessageId,
permissionMode,
vimMode,
mainLoopModel,
scheduleUpdate,
])
}, [lastAssistantMessageId, permissionMode, vimMode, mainLoopModel, scheduleUpdate]);
// When the statusLine command changes (hot reload), log the next result
const statusLineCommand = settings?.statusLine?.command
const isFirstSettingsRender = useRef(true)
const statusLineCommand = settings?.statusLine?.command;
const isFirstSettingsRender = useRef(true);
useEffect(() => {
if (isFirstSettingsRender.current) {
isFirstSettingsRender.current = false
return
isFirstSettingsRender.current = false;
return;
}
logNextResultRef.current = true
void doUpdate()
}, [statusLineCommand, doUpdate])
logNextResultRef.current = true;
void doUpdate();
}, [statusLineCommand, doUpdate]);
// Separate effect for logging on mount
useEffect(() => {
const statusLine = settings?.statusLine
const statusLine = settings?.statusLine;
if (statusLine) {
logEvent('tengu_status_line_mount', {
command_length: statusLine.command.length,
padding: statusLine.padding,
})
});
// Log if status line is configured but disabled by disableAllHooks
if (settings.disableAllHooks === true) {
logForDebugging(
'Status line is configured but disableAllHooks is true',
{ level: 'warn' },
)
logForDebugging('Status line is configured but disableAllHooks is true', { level: 'warn' });
}
// executeStatusLineCommand (hooks.ts) returns undefined when trust is
// blocked — statusLineText stays undefined forever, user sees nothing,
@@ -362,31 +321,28 @@ function StatusLineInner({
text: 'statusline skipped · restart to fix',
color: 'warning',
priority: 'low',
})
logForDebugging(
'Status line command skipped: workspace trust not accepted',
{ level: 'warn' },
)
});
logForDebugging('Status line command skipped: workspace trust not accepted', { level: 'warn' });
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) // Only run once on mount - settings stable for initial logging
}, []); // Only run once on mount - settings stable for initial logging
// Initial update on mount + cleanup on unmount
useEffect(() => {
void doUpdate()
void doUpdate();
return () => {
abortControllerRef.current?.abort()
abortControllerRef.current?.abort();
if (debounceTimerRef.current !== undefined) {
clearTimeout(debounceTimerRef.current)
clearTimeout(debounceTimerRef.current);
}
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) // Only run once on mount, not when doUpdate changes
}, []); // Only run once on mount, not when doUpdate changes
// Get padding from settings or default to 0
const paddingX = settings?.statusLine?.padding ?? 0
const paddingX = settings?.statusLine?.padding ?? 0;
// StatusLine must have stable height in fullscreen — the footer is
// flexShrink:0 so a 0→1 row change when the command finishes steals
@@ -402,10 +358,10 @@ function StatusLineInner({
<Text> </Text>
) : null}
</Box>
)
);
}
// Parent (PromptInputFooter) re-renders on every setMessages, but StatusLine's
// own props now only change when lastAssistantMessageId flips — memo keeps it
// from being dragged along (previously ~18 no-prop-change renders per session).
export const StatusLine = memo(StatusLineInner)
export const StatusLine = memo(StatusLineInner);