feat: 类型修复

This commit is contained in:
claude-code-best 2026-03-31 21:46:46 +08:00
parent c4d92178b7
commit 2c759fe6fa
6 changed files with 38 additions and 19 deletions

View File

@ -27,7 +27,7 @@ import { Dialog } from '../design-system/Dialog.js';
import { Select } from '../CustomSelect/index.js'; import { Select } from '../CustomSelect/index.js';
import { OutputStylePicker } from '../OutputStylePicker.js'; import { OutputStylePicker } from '../OutputStylePicker.js';
import { LanguagePicker } from '../LanguagePicker.js'; import { LanguagePicker } from '../LanguagePicker.js';
import { getExternalClaudeMdIncludes, getMemoryFiles, hasExternalClaudeMdIncludes } from 'src/utils/claudemd.js'; import { getExternalClaudeMdIncludes, getMemoryFiles, hasExternalClaudeMdIncludes, type MemoryFileInfo } from 'src/utils/claudemd.js';
import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js'; import { KeyboardShortcutHint } from '../design-system/KeyboardShortcutHint.js';
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js'; import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js';
import { Byline } from '../design-system/Byline.js'; import { Byline } from '../design-system/Byline.js';
@ -197,7 +197,7 @@ export function Config({
}, [ownsEsc, onIsSearchModeChange]); }, [ownsEsc, onIsSearchModeChange]);
const isConnectedToIde = hasAccessToIDEExtensionDiffFeature(context.options.mcpClients); const isConnectedToIde = hasAccessToIDEExtensionDiffFeature(context.options.mcpClients);
const isFileCheckpointingAvailable = !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING); const isFileCheckpointingAvailable = !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING);
const memoryFiles = React.use(getMemoryFiles(true)); const memoryFiles = React.use(getMemoryFiles(true)) as MemoryFileInfo[];
const shouldShowExternalIncludesToggle = hasExternalClaudeMdIncludes(memoryFiles); const shouldShowExternalIncludesToggle = hasExternalClaudeMdIncludes(memoryFiles);
const autoUpdaterDisabledReason = getAutoUpdaterDisabledReason(); const autoUpdaterDisabledReason = getAutoUpdaterDisabledReason();
function onChangeMainModelConfig(value: string | null): void { function onChangeMainModelConfig(value: string | null): void {
@ -392,7 +392,7 @@ export function Config({
} }
}] : []), }] : []),
// Speculation toggle (ant-only) // Speculation toggle (ant-only)
...("external" === 'ant' ? [{ ...(("external" as string) === 'ant' ? [{
id: 'speculationEnabled', id: 'speculationEnabled',
label: 'Speculative execution', label: 'Speculative execution',
value: globalConfig.speculationEnabled ?? true, value: globalConfig.speculationEnabled ?? true,

View File

@ -46,6 +46,7 @@ type Props = {
pauseStartTimeRef: React.RefObject<number | null>; pauseStartTimeRef: React.RefObject<number | null>;
spinnerTip?: string; spinnerTip?: string;
responseLengthRef: React.RefObject<number>; responseLengthRef: React.RefObject<number>;
apiMetricsRef?: React.RefObject<Array<{ ttftMs: number; firstTokenTime: number; lastTokenTime: number; responseLengthBaseline: number; endResponseLength: number }>>;
overrideColor?: keyof Theme | null; overrideColor?: keyof Theme | null;
overrideShimmerColor?: keyof Theme | null; overrideShimmerColor?: keyof Theme | null;
overrideMessage?: string | null; overrideMessage?: string | null;

View File

@ -123,6 +123,7 @@ import type { ToolPermissionContext, Tool } from '../Tool.js';
import { applyPermissionUpdate, applyPermissionUpdates, persistPermissionUpdate } from '../utils/permissions/PermissionUpdate.js'; import { applyPermissionUpdate, applyPermissionUpdates, persistPermissionUpdate } from '../utils/permissions/PermissionUpdate.js';
import { buildPermissionUpdates } from '../components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.js'; import { buildPermissionUpdates } from '../components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.js';
import { stripDangerousPermissionsForAutoMode } from '../utils/permissions/permissionSetup.js'; import { stripDangerousPermissionsForAutoMode } from '../utils/permissions/permissionSetup.js';
import type { PermissionMode } from '../types/permissions.js';
import { getScratchpadDir, isScratchpadEnabled } from '../utils/permissions/filesystem.js'; import { getScratchpadDir, isScratchpadEnabled } from '../utils/permissions/filesystem.js';
import { WEB_FETCH_TOOL_NAME } from '../tools/WebFetchTool/prompt.js'; import { WEB_FETCH_TOOL_NAME } from '../tools/WebFetchTool/prompt.js';
import { SLEEP_TOOL_NAME } from '../tools/SleepTool/prompt.js'; import { SLEEP_TOOL_NAME } from '../tools/SleepTool/prompt.js';
@ -1655,7 +1656,10 @@ export function REPL({
const onlySleepToolActive = useMemo(() => { const onlySleepToolActive = useMemo(() => {
const lastAssistant = messages.findLast(m => m.type === 'assistant'); const lastAssistant = messages.findLast(m => m.type === 'assistant');
if (lastAssistant?.type !== 'assistant') return false; if (lastAssistant?.type !== 'assistant') return false;
const inProgressToolUses = lastAssistant.message.content.filter(b => b.type === 'tool_use' && inProgressToolUseIDs.has(b.id)); const content = lastAssistant.message.content;
if (typeof content === 'string') return false;
const contentArr = content as Array<{ type: string; id?: string; name?: string; [key: string]: unknown }>;
const inProgressToolUses = contentArr.filter(b => b.type === 'tool_use' && b.id && inProgressToolUseIDs.has(b.id));
return inProgressToolUses.length > 0 && inProgressToolUses.every(b => b.type === 'tool_use' && b.name === SLEEP_TOOL_NAME); return inProgressToolUses.length > 0 && inProgressToolUses.every(b => b.type === 'tool_use' && b.name === SLEEP_TOOL_NAME);
}, [messages, inProgressToolUseIDs]); }, [messages, inProgressToolUseIDs]);
const { const {
@ -2605,7 +2609,7 @@ export function REPL({
if (feature('PROACTIVE') || feature('KAIROS')) { if (feature('PROACTIVE') || feature('KAIROS')) {
proactiveModule?.setContextBlocked(false); proactiveModule?.setContextBlocked(false);
} }
} else if ((newMessage as MessageType).type === 'progress' && isEphemeralToolProgress((newMessage as ProgressMessage<unknown>).data.type)) { } else if ((newMessage as MessageType).type === 'progress' && isEphemeralToolProgress(((newMessage as MessageType).data as { type: string }).type)) {
// Replace the previous ephemeral progress tick for the same tool // Replace the previous ephemeral progress tick for the same tool
// call instead of appending. Sleep/Bash emit a tick per second and // call instead of appending. Sleep/Bash emit a tick per second and
// only the last one is rendered; appending blows up the messages // only the last one is rendered; appending blows up the messages
@ -2618,7 +2622,7 @@ export function REPL({
// "Initializing…" because it renders the full progress trail. // "Initializing…" because it renders the full progress trail.
setMessages(oldMessages => { setMessages(oldMessages => {
const last = oldMessages.at(-1); const last = oldMessages.at(-1);
if (last?.type === 'progress' && last.parentToolUseID === (newMessage as MessageType).parentToolUseID && last.data.type === (newMessage as ProgressMessage<unknown>).data.type) { if (last?.type === 'progress' && (last as MessageType).parentToolUseID === (newMessage as MessageType).parentToolUseID && ((last as MessageType).data as { type: string }).type === ((newMessage as MessageType).data as { type: string }).type) {
const copy = oldMessages.slice(); const copy = oldMessages.slice();
copy[copy.length - 1] = newMessage; copy[copy.length - 1] = newMessage;
return copy; return copy;
@ -2683,7 +2687,7 @@ export function REPL({
// title silently fell through to the "Claude Code" default. // title silently fell through to the "Claude Code" default.
if (!titleDisabled && !sessionTitle && !agentTitle && !haikuTitleAttemptedRef.current) { if (!titleDisabled && !sessionTitle && !agentTitle && !haikuTitleAttemptedRef.current) {
const firstUserMessage = newMessages.find(m => m.type === 'user' && !m.isMeta); const firstUserMessage = newMessages.find(m => m.type === 'user' && !m.isMeta);
const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null; const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content as string | ContentBlockParam[]) : null;
// Skip synthetic breadcrumbs — slash-command output, prompt-skill // Skip synthetic breadcrumbs — slash-command output, prompt-skill
// expansions (/commit → <command-message>), local-command headers // expansions (/commit → <command-message>), local-command headers
// (/help → <command-name>), and bash-mode (!cmd → <bash-input>). // (/help → <command-name>), and bash-mode (!cmd → <bash-input>).
@ -2873,7 +2877,7 @@ export function REPL({
// Extract and enqueue user message text, skipping meta messages // Extract and enqueue user message text, skipping meta messages
// (e.g. expanded skill content, tick prompts) that should not be // (e.g. expanded skill content, tick prompts) that should not be
// replayed as user-visible text. // replayed as user-visible text.
newMessages.filter((m): m is UserMessage => m.type === 'user' && !m.isMeta).map(_ => getContentText(_.message.content)).filter(_ => _ !== null).forEach((msg, i) => { newMessages.filter((m): m is UserMessage => m.type === 'user' && !m.isMeta).map(_ => getContentText(_.message.content as string | ContentBlockParam[])).filter(_ => _ !== null).forEach((msg, i) => {
enqueue({ enqueue({
value: msg, value: msg,
mode: 'prompt' mode: 'prompt'
@ -3081,7 +3085,7 @@ export function REPL({
toolPermissionContext: updatedToolPermissionContext, toolPermissionContext: updatedToolPermissionContext,
...(shouldStorePlanForVerification && { ...(shouldStorePlanForVerification && {
pendingPlanVerification: { pendingPlanVerification: {
plan: initialMsg.message.planContent!, plan: initialMsg.message.planContent as string,
verificationStarted: false, verificationStarted: false,
verificationCompleted: false verificationCompleted: false
} }
@ -3693,7 +3697,7 @@ export function REPL({
// Restore permission mode from the message // Restore permission mode from the message
toolPermissionContext: message.permissionMode && prev.toolPermissionContext.mode !== message.permissionMode ? { toolPermissionContext: message.permissionMode && prev.toolPermissionContext.mode !== message.permissionMode ? {
...prev.toolPermissionContext, ...prev.toolPermissionContext,
mode: message.permissionMode mode: message.permissionMode as PermissionMode
} : prev.toolPermissionContext, } : prev.toolPermissionContext,
// Clear stale prompt suggestion from previous conversation state // Clear stale prompt suggestion from previous conversation state
promptSuggestion: { promptSuggestion: {
@ -3719,7 +3723,7 @@ export function REPL({
// Restore pasted images // Restore pasted images
if (Array.isArray(message.message.content) && message.message.content.some(block => block.type === 'image')) { if (Array.isArray(message.message.content) && message.message.content.some(block => block.type === 'image')) {
const imageBlocks: Array<ImageBlockParam> = message.message.content.filter(block => block.type === 'image'); const imageBlocks = message.message.content.filter(block => block.type === 'image') as Array<ImageBlockParam>;
if (imageBlocks.length > 0) { if (imageBlocks.length > 0) {
const newPastedContents: Record<number, PastedContent> = {}; const newPastedContents: Record<number, PastedContent> = {};
imageBlocks.forEach((block, index) => { imageBlocks.forEach((block, index) => {

View File

@ -49,9 +49,9 @@ declare function ExperimentEnrollmentNotice(): JSX.Element | null
declare const HOOK_TIMING_DISPLAY_THRESHOLD_MS: number declare const HOOK_TIMING_DISPLAY_THRESHOLD_MS: number
// Ultraplan (internal) // Ultraplan (internal)
declare function UltraplanChoiceDialog(): JSX.Element | null declare function UltraplanChoiceDialog(props: Record<string, unknown>): JSX.Element | null
declare function UltraplanLaunchDialog(): JSX.Element | null declare function UltraplanLaunchDialog(props: Record<string, unknown>): JSX.Element | null
declare function launchUltraplan(...args: unknown[]): void declare function launchUltraplan(...args: unknown[]): Promise<string>
// T — Generic type parameter leaked from React compiler output // T — Generic type parameter leaked from React compiler output
// (react/compiler-runtime emits compiled JSX that loses generic type params) // (react/compiler-runtime emits compiled JSX that loses generic type params)

View File

@ -14,24 +14,37 @@ export type Message = {
isCompactSummary?: boolean isCompactSummary?: boolean
toolUseResult?: unknown toolUseResult?: unknown
isVisibleInTranscriptOnly?: boolean isVisibleInTranscriptOnly?: boolean
attachment?: { type: string; toolUseID?: string; [key: string]: unknown }
message?: { message?: {
role?: string role?: string
content?: string | Array<{ type: string; text?: string; [key: string]: unknown }> id?: string
content?: string | Array<{ type: string; text?: string; id?: string; name?: string; tool_use_id?: string; [key: string]: unknown }>
usage?: Record<string, unknown> usage?: Record<string, unknown>
[key: string]: unknown [key: string]: unknown
} }
[key: string]: unknown [key: string]: unknown
} }
export type AssistantMessage = Message & { type: 'assistant' }; export type AssistantMessage = Message & { type: 'assistant' };
export type AttachmentMessage<T = unknown> = Message & { type: 'attachment' }; export type AttachmentMessage<T = unknown> = Message & { type: 'attachment'; attachment: { type: string; [key: string]: unknown } };
export type ProgressMessage<T = unknown> = Message & { type: 'progress' }; export type ProgressMessage<T = unknown> = Message & { type: 'progress'; data: T };
export type SystemLocalCommandMessage = Message & { type: 'system' }; export type SystemLocalCommandMessage = Message & { type: 'system' };
export type SystemMessage = Message & { type: 'system' }; export type SystemMessage = Message & { type: 'system' };
export type UserMessage = Message & { type: 'user' }; export type UserMessage = Message & { type: 'user' };
export type NormalizedUserMessage = UserMessage; export type NormalizedUserMessage = UserMessage;
export type RequestStartEvent = { type: string; [key: string]: unknown }; export type RequestStartEvent = { type: string; [key: string]: unknown };
export type StreamEvent = { type: string; [key: string]: unknown }; export type StreamEvent = { type: string; [key: string]: unknown };
export type SystemCompactBoundaryMessage = Message & { type: 'system' }; export type SystemCompactBoundaryMessage = Message & {
type: 'system'
compactMetadata: {
preservedSegment?: {
headUuid: UUID
tailUuid: UUID
anchorUuid: UUID
[key: string]: unknown
}
[key: string]: unknown
}
};
export type TombstoneMessage = Message; export type TombstoneMessage = Message;
export type ToolUseSummaryMessage = Message; export type ToolUseSummaryMessage = Message;
export type MessageOrigin = string; export type MessageOrigin = string;

View File

@ -1058,6 +1058,7 @@ class Project {
entrypoint: getEntrypoint(), entrypoint: getEntrypoint(),
cwd: getCwd(), cwd: getCwd(),
sessionId, sessionId,
timestamp: new Date().toISOString(),
version: VERSION, version: VERSION,
gitBranch, gitBranch,
slug, slug,
@ -2225,7 +2226,7 @@ export function checkResumeConsistency(chain: Message[]): void {
for (let i = chain.length - 1; i >= 0; i--) { for (let i = chain.length - 1; i >= 0; i--) {
const m = chain[i]! const m = chain[i]!
if (m.type !== 'system' || m.subtype !== 'turn_duration') continue if (m.type !== 'system' || m.subtype !== 'turn_duration') continue
const expected = m.messageCount const expected = m.messageCount as number | undefined
if (expected === undefined) return if (expected === undefined) return
// `i` is the 0-based index of the checkpoint in the reconstructed chain. // `i` is the 0-based index of the checkpoint in the reconstructed chain.
// The checkpoint was appended AFTER messageCount messages, so its own // The checkpoint was appended AFTER messageCount messages, so its own