mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 09:17:53 +00:00
Merge branch 'main' into fix/subagent-update
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user