mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,134 +1,152 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { logEvent } from 'src/services/analytics/index.js';
|
||||
import { logForDebugging } from 'src/utils/debug.js';
|
||||
import { logError } from 'src/utils/log.js';
|
||||
import { useInterval } from 'usehooks-ts';
|
||||
import { useUpdateNotification } from '../hooks/useUpdateNotification.js';
|
||||
import { Box, Text } from '../ink.js';
|
||||
import type { AutoUpdaterResult } from '../utils/autoUpdater.js';
|
||||
import { getMaxVersion, getMaxVersionMessage } from '../utils/autoUpdater.js';
|
||||
import { isAutoUpdaterDisabled } from '../utils/config.js';
|
||||
import { installLatest } from '../utils/nativeInstaller/index.js';
|
||||
import { gt } from '../utils/semver.js';
|
||||
import { getInitialSettings } from '../utils/settings/settings.js';
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { logEvent } from 'src/services/analytics/index.js'
|
||||
import { logForDebugging } from 'src/utils/debug.js'
|
||||
import { logError } from 'src/utils/log.js'
|
||||
import { useInterval } from 'usehooks-ts'
|
||||
import { useUpdateNotification } from '../hooks/useUpdateNotification.js'
|
||||
import { Box, Text } from '../ink.js'
|
||||
import type { AutoUpdaterResult } from '../utils/autoUpdater.js'
|
||||
import { getMaxVersion, getMaxVersionMessage } from '../utils/autoUpdater.js'
|
||||
import { isAutoUpdaterDisabled } from '../utils/config.js'
|
||||
import { installLatest } from '../utils/nativeInstaller/index.js'
|
||||
import { gt } from '../utils/semver.js'
|
||||
import { getInitialSettings } from '../utils/settings/settings.js'
|
||||
|
||||
/**
|
||||
* Categorize error messages for analytics
|
||||
*/
|
||||
function getErrorType(errorMessage: string): string {
|
||||
if (errorMessage.includes('timeout')) {
|
||||
return 'timeout';
|
||||
return 'timeout'
|
||||
}
|
||||
if (errorMessage.includes('Checksum mismatch')) {
|
||||
return 'checksum_mismatch';
|
||||
return 'checksum_mismatch'
|
||||
}
|
||||
if (errorMessage.includes('ENOENT') || errorMessage.includes('not found')) {
|
||||
return 'not_found';
|
||||
return 'not_found'
|
||||
}
|
||||
if (errorMessage.includes('EACCES') || errorMessage.includes('permission')) {
|
||||
return 'permission_denied';
|
||||
return 'permission_denied'
|
||||
}
|
||||
if (errorMessage.includes('ENOSPC')) {
|
||||
return 'disk_full';
|
||||
return 'disk_full'
|
||||
}
|
||||
if (errorMessage.includes('npm')) {
|
||||
return 'npm_error';
|
||||
return 'npm_error'
|
||||
}
|
||||
if (errorMessage.includes('network') || errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ENOTFOUND')) {
|
||||
return 'network_error';
|
||||
if (
|
||||
errorMessage.includes('network') ||
|
||||
errorMessage.includes('ECONNREFUSED') ||
|
||||
errorMessage.includes('ENOTFOUND')
|
||||
) {
|
||||
return 'network_error'
|
||||
}
|
||||
return 'unknown';
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
type Props = {
|
||||
isUpdating: boolean;
|
||||
onChangeIsUpdating: (isUpdating: boolean) => void;
|
||||
onAutoUpdaterResult: (autoUpdaterResult: AutoUpdaterResult) => void;
|
||||
autoUpdaterResult: AutoUpdaterResult | null;
|
||||
showSuccessMessage: boolean;
|
||||
verbose: boolean;
|
||||
};
|
||||
isUpdating: boolean
|
||||
onChangeIsUpdating: (isUpdating: boolean) => void
|
||||
onAutoUpdaterResult: (autoUpdaterResult: AutoUpdaterResult) => void
|
||||
autoUpdaterResult: AutoUpdaterResult | null
|
||||
showSuccessMessage: boolean
|
||||
verbose: boolean
|
||||
}
|
||||
|
||||
export function NativeAutoUpdater({
|
||||
isUpdating,
|
||||
onChangeIsUpdating,
|
||||
onAutoUpdaterResult,
|
||||
autoUpdaterResult,
|
||||
showSuccessMessage,
|
||||
verbose
|
||||
verbose,
|
||||
}: Props): React.ReactNode {
|
||||
const [versions, setVersions] = useState<{
|
||||
current?: string | null;
|
||||
latest?: string | null;
|
||||
}>({});
|
||||
const [maxVersionIssue, setMaxVersionIssue] = useState<string | null>(null);
|
||||
const updateSemver = useUpdateNotification(autoUpdaterResult?.version);
|
||||
const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest';
|
||||
current?: string | null
|
||||
latest?: string | null
|
||||
}>({})
|
||||
const [maxVersionIssue, setMaxVersionIssue] = useState<string | null>(null)
|
||||
const updateSemver = useUpdateNotification(autoUpdaterResult?.version)
|
||||
const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest'
|
||||
|
||||
// Track latest isUpdating value in a ref so the memoized checkForUpdates
|
||||
// callback always sees the current value without changing callback identity
|
||||
// (which would re-trigger the initial-check useEffect below and cause
|
||||
// repeated downloads on remount — the upstream trigger for #22413).
|
||||
const isUpdatingRef = useRef(isUpdating);
|
||||
isUpdatingRef.current = isUpdating;
|
||||
const isUpdatingRef = useRef(isUpdating)
|
||||
isUpdatingRef.current = isUpdating
|
||||
|
||||
const checkForUpdates = React.useCallback(async () => {
|
||||
if (isUpdatingRef.current) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (("production" as string) === 'test' || ("production" as string) === 'development') {
|
||||
logForDebugging('NativeAutoUpdater: Skipping update check in test/dev environment');
|
||||
return;
|
||||
|
||||
if (
|
||||
"production" === 'test' ||
|
||||
"production" === 'development'
|
||||
) {
|
||||
logForDebugging(
|
||||
'NativeAutoUpdater: Skipping update check in test/dev environment',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (isAutoUpdaterDisabled()) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
onChangeIsUpdating(true);
|
||||
const startTime = Date.now();
|
||||
|
||||
onChangeIsUpdating(true)
|
||||
const startTime = Date.now()
|
||||
|
||||
// Log the start of an auto-update check for funnel analysis
|
||||
logEvent('tengu_native_auto_updater_start', {});
|
||||
logEvent('tengu_native_auto_updater_start', {})
|
||||
|
||||
try {
|
||||
// Check if current version is above the max allowed version
|
||||
const maxVersion = await getMaxVersion();
|
||||
const maxVersion = await getMaxVersion()
|
||||
if (maxVersion && gt(MACRO.VERSION, maxVersion)) {
|
||||
const msg = await getMaxVersionMessage();
|
||||
setMaxVersionIssue(msg ?? 'affects your version');
|
||||
const msg = await getMaxVersionMessage()
|
||||
setMaxVersionIssue(msg ?? 'affects your version')
|
||||
}
|
||||
const result = await installLatest(channel);
|
||||
const currentVersion = MACRO.VERSION;
|
||||
const latencyMs = Date.now() - startTime;
|
||||
|
||||
const result = await installLatest(channel)
|
||||
const currentVersion = MACRO.VERSION
|
||||
const latencyMs = Date.now() - startTime
|
||||
|
||||
// Handle lock contention gracefully - just return without treating as error
|
||||
if (result.lockFailed) {
|
||||
logEvent('tengu_native_auto_updater_lock_contention', {
|
||||
latency_ms: latencyMs
|
||||
});
|
||||
return; // Silently skip this update check, will try again later
|
||||
latency_ms: latencyMs,
|
||||
})
|
||||
return // Silently skip this update check, will try again later
|
||||
}
|
||||
|
||||
// Update versions for display
|
||||
setVersions({
|
||||
current: currentVersion,
|
||||
latest: result.latestVersion
|
||||
});
|
||||
setVersions({ current: currentVersion, latest: result.latestVersion })
|
||||
|
||||
if (result.wasUpdated) {
|
||||
logEvent('tengu_native_auto_updater_success', {
|
||||
latency_ms: latencyMs
|
||||
});
|
||||
latency_ms: latencyMs,
|
||||
})
|
||||
|
||||
onAutoUpdaterResult({
|
||||
version: result.latestVersion,
|
||||
status: 'success'
|
||||
});
|
||||
status: 'success',
|
||||
})
|
||||
} else {
|
||||
// Already up to date
|
||||
logEvent('tengu_native_auto_updater_up_to_date', {
|
||||
latency_ms: latencyMs
|
||||
});
|
||||
latency_ms: latencyMs,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
const latencyMs = Date.now() - startTime;
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logError(error);
|
||||
const errorType = getErrorType(errorMessage);
|
||||
const latencyMs = Date.now() - startTime
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error)
|
||||
logError(error)
|
||||
|
||||
const errorType = getErrorType(errorMessage)
|
||||
logEvent('tengu_native_auto_updater_fail', {
|
||||
latency_ms: latencyMs,
|
||||
error_timeout: errorType === 'timeout',
|
||||
@@ -137,56 +155,77 @@ export function NativeAutoUpdater({
|
||||
error_permission: errorType === 'permission_denied',
|
||||
error_disk_full: errorType === 'disk_full',
|
||||
error_npm: errorType === 'npm_error',
|
||||
error_network: errorType === 'network_error'
|
||||
});
|
||||
error_network: errorType === 'network_error',
|
||||
})
|
||||
|
||||
onAutoUpdaterResult({
|
||||
version: null,
|
||||
status: 'install_failed'
|
||||
});
|
||||
status: 'install_failed',
|
||||
})
|
||||
} finally {
|
||||
onChangeIsUpdating(false);
|
||||
onChangeIsUpdating(false)
|
||||
}
|
||||
// isUpdating intentionally omitted from deps; we read isUpdatingRef
|
||||
// instead so the guard is always current without changing callback
|
||||
// identity (which would re-trigger the initial-check useEffect below).
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: isUpdating read via ref
|
||||
}, [onAutoUpdaterResult, channel]);
|
||||
}, [onAutoUpdaterResult, channel])
|
||||
|
||||
// Initial check
|
||||
useEffect(() => {
|
||||
void checkForUpdates();
|
||||
}, [checkForUpdates]);
|
||||
void checkForUpdates()
|
||||
}, [checkForUpdates])
|
||||
|
||||
// Check every 30 minutes
|
||||
useInterval(checkForUpdates, 30 * 60 * 1000);
|
||||
const hasUpdateResult = !!autoUpdaterResult?.version;
|
||||
const hasVersionInfo = !!versions.current && !!versions.latest;
|
||||
useInterval(checkForUpdates, 30 * 60 * 1000)
|
||||
|
||||
const hasUpdateResult = !!autoUpdaterResult?.version
|
||||
const hasVersionInfo = !!versions.current && !!versions.latest
|
||||
// Show the component when:
|
||||
// - warning banner needed (above max version), or
|
||||
// - there's an update result to display (success/error), or
|
||||
// - actively checking and we have version info to show
|
||||
const shouldRender = !!maxVersionIssue || hasUpdateResult || isUpdating && hasVersionInfo;
|
||||
const shouldRender =
|
||||
!!maxVersionIssue || hasUpdateResult || (isUpdating && hasVersionInfo)
|
||||
|
||||
if (!shouldRender) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
return <Box flexDirection="row" gap={1}>
|
||||
{verbose && <Text dimColor wrap="truncate">
|
||||
|
||||
return (
|
||||
<Box flexDirection="row" gap={1}>
|
||||
{verbose && (
|
||||
<Text dimColor wrap="truncate">
|
||||
current: {versions.current} · {channel}: {versions.latest}
|
||||
</Text>}
|
||||
{isUpdating ? <Box>
|
||||
</Text>
|
||||
)}
|
||||
{isUpdating ? (
|
||||
<Box>
|
||||
<Text dimColor wrap="truncate">
|
||||
Checking for updates
|
||||
</Text>
|
||||
</Box> : autoUpdaterResult?.status === 'success' && showSuccessMessage && updateSemver && <Text color="success" wrap="truncate">
|
||||
</Box>
|
||||
) : (
|
||||
autoUpdaterResult?.status === 'success' &&
|
||||
showSuccessMessage &&
|
||||
updateSemver && (
|
||||
<Text color="success" wrap="truncate">
|
||||
✓ Update installed · Restart to update
|
||||
</Text>}
|
||||
{autoUpdaterResult?.status === 'install_failed' && <Text color="error" wrap="truncate">
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
{autoUpdaterResult?.status === 'install_failed' && (
|
||||
<Text color="error" wrap="truncate">
|
||||
✗ Auto-update failed · Try <Text bold>/status</Text>
|
||||
</Text>}
|
||||
{maxVersionIssue && (process.env.USER_TYPE) === 'ant' && <Text color="warning">
|
||||
</Text>
|
||||
)}
|
||||
{maxVersionIssue && process.env.USER_TYPE === 'ant' && (
|
||||
<Text color="warning">
|
||||
⚠ Known issue: {maxVersionIssue} · Run{' '}
|
||||
<Text bold>claude rollback --safe</Text> to downgrade
|
||||
</Text>}
|
||||
</Box>;
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user