Merge branch 'main' into fix/subagent-update

This commit is contained in:
tanzhenxin
2025-09-17 19:12:22 +08:00
607 changed files with 27283 additions and 9387 deletions

View File

@@ -7,14 +7,20 @@
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import {
Box,
DOMElement,
type DOMElement,
measureElement,
Static,
Text,
useStdin,
useStdout,
} from 'ink';
import { StreamingState, type HistoryItem, MessageType } from './types.js';
import {
StreamingState,
type HistoryItem,
MessageType,
ToolCallStatus,
type HistoryItemWithoutId,
} from './types.js';
import { useTerminalSize } from './hooks/useTerminalSize.js';
import { useGeminiStream } from './hooks/useGeminiStream.js';
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
@@ -53,7 +59,8 @@ import {
} from './components/subagents/index.js';
import { Colors } from './colors.js';
import { loadHierarchicalGeminiMemory } from '../config/config.js';
import { LoadedSettings, SettingScope } from '../config/settings.js';
import type { LoadedSettings } from '../config/settings.js';
import { SettingScope } from '../config/settings.js';
import { Tips } from './components/Tips.js';
import { ConsolePatcher } from './utils/ConsolePatcher.js';
import { registerCleanup } from '../utils/cleanup.js';
@@ -62,23 +69,22 @@ import { HistoryItemDisplay } from './components/HistoryItemDisplay.js';
import { ContextSummaryDisplay } from './components/ContextSummaryDisplay.js';
import { useHistory } from './hooks/useHistoryManager.js';
import process from 'node:process';
import type { EditorType, Config, IdeContext } from '@qwen-code/qwen-code-core';
import {
getErrorMessage,
type Config,
getAllGeminiMdFilenames,
ApprovalMode,
getAllGeminiMdFilenames,
isEditorAvailable,
EditorType,
FlashFallbackEvent,
logFlashFallback,
getErrorMessage,
AuthType,
type IdeContext,
logFlashFallback,
FlashFallbackEvent,
ideContext,
isProQuotaExceededError,
isGenericQuotaExceededError,
UserTierId,
} from '@qwen-code/qwen-code-core';
import {
IdeIntegrationNudge,
IdeIntegrationNudgeResult,
} from './IdeIntegrationNudge.js';
import type { IdeIntegrationNudgeResult } from './IdeIntegrationNudge.js';
import { IdeIntegrationNudge } from './IdeIntegrationNudge.js';
import { validateAuthMethod } from '../config/auth.js';
import { useLogger } from './hooks/useLogger.js';
import { StreamingContext } from './contexts/StreamingContext.js';
@@ -92,18 +98,14 @@ import { useBracketedPaste } from './hooks/useBracketedPaste.js';
import { useTextBuffer } from './components/shared/text-buffer.js';
import { useVimMode, VimModeProvider } from './contexts/VimModeContext.js';
import { useVim } from './hooks/vim.js';
import { useKeypress, Key } from './hooks/useKeypress.js';
import type { Key } from './hooks/useKeypress.js';
import { useKeypress } from './hooks/useKeypress.js';
import { KeypressProvider } from './contexts/KeypressContext.js';
import { useKittyKeyboardProtocol } from './hooks/useKittyKeyboardProtocol.js';
import { keyMatchers, Command } from './keyMatchers.js';
import * as fs from 'fs';
import * as fs from 'node:fs';
import { UpdateNotification } from './components/UpdateNotification.js';
import {
isProQuotaExceededError,
isGenericQuotaExceededError,
UserTierId,
} from '@qwen-code/qwen-code-core';
import { UpdateObject } from './utils/updateCheck.js';
import type { UpdateObject } from './utils/updateCheck.js';
import ansiEscapes from 'ansi-escapes';
import { OverflowProvider } from './contexts/OverflowContext.js';
import { ShowMoreLines } from './components/ShowMoreLines.js';
@@ -113,6 +115,8 @@ import { SettingsDialog } from './components/SettingsDialog.js';
import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
import { appEvents, AppEvent } from '../utils/events.js';
import { isNarrowWidth } from './utils/isNarrowWidth.js';
import { useWorkspaceMigration } from './hooks/useWorkspaceMigration.js';
import { WorkspaceMigrationDialog } from './components/WorkspaceMigrationDialog.js';
import { WelcomeBackDialog } from './components/WelcomeBackDialog.js';
// Maximum number of queued messages to display in UI to prevent performance issues
@@ -125,12 +129,28 @@ interface AppProps {
version: string;
}
function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
return pendingHistoryItems.some((item) => {
if (item && item.type === 'tool_group') {
return item.tools.some(
(tool) => ToolCallStatus.Executing === tool.status,
);
}
return false;
});
}
export const AppWrapper = (props: AppProps) => {
const kittyProtocolStatus = useKittyKeyboardProtocol();
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
return (
<KeypressProvider
kittyProtocolEnabled={kittyProtocolStatus.enabled}
pasteWorkaround={process.platform === 'win32' || nodeMajorVersion < 20}
config={props.config}
debugKeystrokeLogging={
props.settings.merged.general?.debugKeystrokeLogging
}
>
<SessionStatsProvider>
<VimModeProvider settings={props.settings}>
@@ -157,7 +177,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
const shouldShowIdePrompt =
currentIDE &&
!config.getIdeMode() &&
!settings.merged.hasSeenIdeIntegrationNudge &&
!settings.merged.ide?.hasSeenNudge &&
!idePromptAnswered;
useEffect(() => {
@@ -221,6 +241,12 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
>();
const [showEscapePrompt, setShowEscapePrompt] = useState(false);
const [isProcessing, setIsProcessing] = useState<boolean>(false);
const {
showWorkspaceMigrationDialog,
workspaceExtensions,
onWorkspaceMigrationDialogOpen,
onWorkspaceMigrationDialogClose,
} = useWorkspaceMigration(settings);
useEffect(() => {
const unsubscribe = ideContext.subscribeToIdeContext(setIdeContextState);
@@ -291,10 +317,8 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
closeAgentsManagerDialog,
} = useAgentsManagerDialog();
const { isFolderTrustDialogOpen, handleFolderTrustSelect } = useFolderTrust(
settings,
setIsTrustedFolder,
);
const { isFolderTrustDialogOpen, handleFolderTrustSelect, isRestarting } =
useFolderTrust(settings, setIsTrustedFolder);
const { showQuitConfirmation, handleQuitConfirmationSelect } =
useQuitConfirmation();
@@ -317,16 +341,21 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
} = useQwenAuth(settings, isAuthenticating);
useEffect(() => {
if (settings.merged.selectedAuthType && !settings.merged.useExternalAuth) {
const error = validateAuthMethod(settings.merged.selectedAuthType);
if (
settings.merged.security?.auth?.selectedType &&
!settings.merged.security?.auth?.useExternal
) {
const error = validateAuthMethod(
settings.merged.security.auth.selectedType,
);
if (error) {
setAuthError(error);
openAuthDialog();
}
}
}, [
settings.merged.selectedAuthType,
settings.merged.useExternalAuth,
settings.merged.security?.auth?.selectedType,
settings.merged.security?.auth?.useExternal,
openAuthDialog,
setAuthError,
]);
@@ -382,14 +411,14 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
try {
const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory(
process.cwd(),
settings.merged.loadMemoryFromIncludeDirectories
settings.merged.context?.loadMemoryFromIncludeDirectories
? config.getWorkspaceContext().getDirectories()
: [],
config.getDebugMode(),
config.getFileService(),
settings.merged,
config.getExtensionContextFilePaths(),
settings.merged.memoryImportFormat || 'tree', // Use setting or default to 'tree'
settings.merged.context?.importFormat || 'tree', // Use setting or default to 'tree'
config.getFileFilteringOptions(),
);
@@ -547,7 +576,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
}, []);
const getPreferredEditor = useCallback(() => {
const editorType = settings.merged.preferredEditor;
const editorType = settings.merged.general?.preferredEditor;
const isValidEditor = isEditorAvailable(editorType);
if (!isValidEditor) {
openEditorDialog();
@@ -637,6 +666,17 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
() => cancelHandlerRef.current(),
);
const pendingHistoryItems = useMemo(
() =>
[...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems].map(
(item, index) => ({
...item,
id: index,
}),
),
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
);
// Welcome back functionality
const {
welcomeBackInfo,
@@ -652,7 +692,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
handleThemeSelect,
isAuthDialogOpen,
handleAuthSelect,
selectedAuthType: settings.merged.selectedAuthType,
selectedAuthType: settings.merged.security?.auth?.selectedType,
isEditorDialogOpen,
exitEditorDialog,
isSettingsDialogOpen,
@@ -674,6 +714,11 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
// Update the cancel handler with message queue support
cancelHandlerRef.current = useCallback(() => {
if (isToolExecuting(pendingHistoryItems)) {
buffer.setText(''); // Just clear the prompt
return;
}
const lastUserMessage = userMessages.at(-1);
let textToSet = lastUserMessage || '';
@@ -687,7 +732,13 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
if (textToSet) {
buffer.setText(textToSet);
}
}, [buffer, userMessages, getQueuedMessagesText, clearQueue]);
}, [
buffer,
userMessages,
getQueuedMessagesText,
clearQueue,
pendingHistoryItems,
]);
// Input handling - queue messages for processing
const handleFinalSubmit = useCallback(
@@ -723,15 +774,10 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
);
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
const pendingHistoryItems = useMemo(() => {
const items = [...pendingSlashCommandHistoryItems];
items.push(...pendingGeminiHistoryItems);
return items.map((item, i) => ({ ...item, id: i }));
}, [pendingSlashCommandHistoryItems, pendingGeminiHistoryItems]);
const { elapsedTime, currentLoadingPhrase } =
useLoadingIndicator(streamingState);
const showAutoAcceptIndicator = useAutoAcceptIndicator({ config });
const showAutoAcceptIndicator = useAutoAcceptIndicator({ config, addItem });
const handleExit = useCallback(
(
@@ -789,6 +835,11 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
const handleGlobalKeypress = useCallback(
(key: Key) => {
// Debug log keystrokes if enabled
if (settings.merged.general?.debugKeystrokeLogging) {
console.log('[DEBUG] Keystroke:', JSON.stringify(key));
}
let enteringConstrainHeightMode = false;
if (!constrainHeight) {
enteringConstrainHeightMode = true;
@@ -848,6 +899,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
ctrlDTimerRef,
handleSlashCommand,
isAuthenticating,
settings.merged.general?.debugKeystrokeLogging,
],
);
@@ -861,7 +913,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
}
}, [config, config.getGeminiMdFileCount]);
const logger = useLogger();
const logger = useLogger(config.storage);
useEffect(() => {
const fetchUserMessages = async () => {
@@ -964,12 +1016,12 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
const branchName = useGitBranchName(config.getTargetDir());
const contextFileNames = useMemo(() => {
const fromSettings = settings.merged.contextFileName;
const fromSettings = settings.merged.context?.fileName;
if (fromSettings) {
return Array.isArray(fromSettings) ? fromSettings : [fromSettings];
}
return getAllGeminiMdFilenames();
}, [settings.merged.contextFileName]);
}, [settings.merged.context?.fileName]);
const initialPrompt = useMemo(() => config.getQuestion(), [config]);
const geminiClient = config.getGeminiClient();
@@ -1051,10 +1103,12 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
key={staticKey}
items={[
<Box flexDirection="column" key="header">
{!settings.merged.hideBanner && (
<Header version={version} nightly={nightly} />
{!(
settings.merged.ui?.hideBanner || config.getScreenReader()
) && <Header version={version} nightly={nightly} />}
{!(settings.merged.ui?.hideTips || config.getScreenReader()) && (
<Tips config={config} />
)}
{!settings.merged.hideTips && <Tips config={config} />}
</Box>,
...history.map((h) => (
<HistoryItemDisplay
@@ -1115,14 +1169,22 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
onClose={handleWelcomeBackClose}
/>
)}
{shouldShowIdePrompt && currentIDE ? (
{showWorkspaceMigrationDialog ? (
<WorkspaceMigrationDialog
workspaceExtensions={workspaceExtensions}
onOpen={onWorkspaceMigrationDialogOpen}
onClose={onWorkspaceMigrationDialogClose}
/>
) : shouldShowIdePrompt && currentIDE ? (
<IdeIntegrationNudge
ide={currentIDE}
onComplete={handleIdePromptComplete}
/>
) : isFolderTrustDialogOpen ? (
<FolderTrustDialog onSelect={handleFolderTrustSelect} />
<FolderTrustDialog
onSelect={handleFolderTrustSelect}
isRestarting={isRestarting}
/>
) : quitConfirmationRequest ? (
<QuitConfirmationDialog
onSelect={(choice) => {
@@ -1270,12 +1332,14 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
<LoadingIndicator
thought={
streamingState === StreamingState.WaitingForConfirmation ||
config.getAccessibility()?.disableLoadingPhrases
config.getAccessibility()?.disableLoadingPhrases ||
config.getScreenReader()
? undefined
: thought
}
currentLoadingPhrase={
config.getAccessibility()?.disableLoadingPhrases
config.getAccessibility()?.disableLoadingPhrases ||
config.getScreenReader()
? undefined
: currentLoadingPhrase
}
@@ -1306,8 +1370,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
<Box paddingLeft={2}>
<Text dimColor>
... (+
{messageQueue.length -
MAX_DISPLAYED_QUEUED_MESSAGES}{' '}
{messageQueue.length - MAX_DISPLAYED_QUEUED_MESSAGES}
more)
</Text>
</Box>
@@ -1427,7 +1490,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
)}
</Box>
)}
{!settings.merged.hideFooter && (
{!settings.merged.ui?.hideFooter && (
<Footer
model={currentModel}
targetDir={config.getTargetDir()}
@@ -1439,7 +1502,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
showErrorDetails={showErrorDetails}
showMemoryUsage={
config.getDebugMode() ||
settings.merged.showMemoryUsage ||
settings.merged.ui?.showMemoryUsage ||
false
}
promptTokenCount={sessionStats.lastPromptTokenCount}