mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
refactor(vscode-ide-companion/types): move ApprovalModeValue type to dedicated file
feat(vscode-ide-companion/file-context): improve file context handling and search Enhance file context hook to better handle search queries and reduce redundant requests. Track last query to optimize when to refetch full file list. Improve logging for debugging purposes.
This commit is contained in:
@@ -10,8 +10,8 @@ import type {
|
||||
AcpPermissionRequest,
|
||||
AcpResponse,
|
||||
AcpSessionUpdate,
|
||||
ApprovalModeValue,
|
||||
} from '../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../types/approvalModeValueTypes.js';
|
||||
import type { ChildProcess, SpawnOptions } from 'child_process';
|
||||
import { spawn } from 'child_process';
|
||||
import type {
|
||||
|
||||
@@ -14,8 +14,8 @@ import type {
|
||||
AcpRequest,
|
||||
AcpNotification,
|
||||
AcpResponse,
|
||||
ApprovalModeValue,
|
||||
} from '../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../types/approvalModeValueTypes.js';
|
||||
import { AGENT_METHODS } from '../constants/acpSchema.js';
|
||||
import type { PendingRequest } from '../types/connectionTypes.js';
|
||||
import type { ChildProcess } from 'child_process';
|
||||
|
||||
@@ -7,8 +7,8 @@ import { AcpConnection } from './acpConnection.js';
|
||||
import type {
|
||||
AcpSessionUpdate,
|
||||
AcpPermissionRequest,
|
||||
ApprovalModeValue,
|
||||
} from '../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../types/approvalModeValueTypes.js';
|
||||
import { QwenSessionReader, type QwenSession } from './qwenSessionReader.js';
|
||||
import { QwenSessionManager } from './qwenSessionManager.js';
|
||||
import type {
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
* Handles session updates from ACP and dispatches them to appropriate callbacks
|
||||
*/
|
||||
|
||||
import type { AcpSessionUpdate, ApprovalModeValue } from '../types/acpTypes.js';
|
||||
import type { AcpSessionUpdate } from '../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../types/approvalModeValueTypes.js';
|
||||
import type { QwenAgentCallbacks } from '../types/chatTypes.js';
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import type { ApprovalModeValue } from './approvalModeValueTypes.js';
|
||||
|
||||
export const JSONRPC_VERSION = '2.0' as const;
|
||||
export const authMethod = 'qwen-oauth';
|
||||
@@ -138,8 +139,6 @@ export interface PlanUpdate extends BaseSessionUpdate {
|
||||
};
|
||||
}
|
||||
|
||||
export type ApprovalModeValue = 'plan' | 'default' | 'auto-edit' | 'yolo';
|
||||
|
||||
export {
|
||||
ApprovalMode,
|
||||
APPROVAL_MODE_MAP,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Type for approval mode values
|
||||
* Used in ACP protocol for controlling agent behavior
|
||||
*/
|
||||
export type ApprovalModeValue = 'plan' | 'default' | 'auto-edit' | 'yolo';
|
||||
@@ -3,7 +3,8 @@
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import type { AcpPermissionRequest, ApprovalModeValue } from './acpTypes.js';
|
||||
import type { AcpPermissionRequest } from './acpTypes.js';
|
||||
import type { ApprovalModeValue } from './approvalModeValueTypes.js';
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'user' | 'assistant';
|
||||
|
||||
@@ -43,7 +43,7 @@ import { InputForm } from './components/layout/InputForm.js';
|
||||
import { SessionSelector } from './components/layout/SessionSelector.js';
|
||||
import { FileIcon, UserIcon } from './components/icons/index.js';
|
||||
import { ApprovalMode, NEXT_APPROVAL_MODE } from '../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../types/approvalModeValueTypes.js';
|
||||
import type { PlanEntry } from '../types/chatTypes.js';
|
||||
|
||||
export const App: React.FC = () => {
|
||||
@@ -90,9 +90,13 @@ export const App: React.FC = () => {
|
||||
const getCompletionItems = React.useCallback(
|
||||
async (trigger: '@' | '/', query: string): Promise<CompletionItem[]> => {
|
||||
if (trigger === '@') {
|
||||
if (!fileContext.hasRequestedFiles) {
|
||||
fileContext.requestWorkspaceFiles();
|
||||
}
|
||||
console.log('[App] getCompletionItems @ called', {
|
||||
query,
|
||||
requested: fileContext.hasRequestedFiles,
|
||||
workspaceFiles: fileContext.workspaceFiles.length,
|
||||
});
|
||||
// 始终根据当前 query 触发请求,让 hook 判断是否需要真正请求
|
||||
fileContext.requestWorkspaceFiles(query);
|
||||
|
||||
const fileIcon = <FileIcon />;
|
||||
const allItems: CompletionItem[] = fileContext.workspaceFiles.map(
|
||||
@@ -109,7 +113,6 @@ export const App: React.FC = () => {
|
||||
);
|
||||
|
||||
if (query && query.length >= 1) {
|
||||
fileContext.requestWorkspaceFiles(query);
|
||||
const lowerQuery = query.toLowerCase();
|
||||
return allItems.filter(
|
||||
(item) =>
|
||||
@@ -154,17 +157,39 @@ export const App: React.FC = () => {
|
||||
|
||||
const completion = useCompletionTrigger(inputFieldRef, getCompletionItems);
|
||||
|
||||
// Track a lightweight signature of workspace files to detect content changes even when length is unchanged
|
||||
const workspaceFilesSignature = useMemo(
|
||||
() =>
|
||||
fileContext.workspaceFiles
|
||||
.map(
|
||||
(file) =>
|
||||
`${file.id}|${file.label}|${file.description ?? ''}|${file.path}`,
|
||||
)
|
||||
.join('||'),
|
||||
[fileContext.workspaceFiles],
|
||||
);
|
||||
|
||||
// When workspace files update while menu open for @, refresh items so the first @ shows the list
|
||||
// Note: Avoid depending on the entire `completion` object here, since its identity
|
||||
// changes on every render which would retrigger this effect and can cause a refresh loop.
|
||||
useEffect(() => {
|
||||
if (completion.isOpen && completion.triggerChar === '@') {
|
||||
// Only auto-refresh when there's no query (first @ popup) to avoid repeated refreshes during search
|
||||
if (
|
||||
completion.isOpen &&
|
||||
completion.triggerChar === '@' &&
|
||||
!completion.query
|
||||
) {
|
||||
// Only refresh items; do not change other completion state to avoid re-renders loops
|
||||
completion.refreshCompletion();
|
||||
}
|
||||
// Only re-run when the actual data source changes, not on every render
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fileContext.workspaceFiles, completion.isOpen, completion.triggerChar]);
|
||||
}, [
|
||||
workspaceFilesSignature,
|
||||
completion.isOpen,
|
||||
completion.triggerChar,
|
||||
completion.query,
|
||||
]);
|
||||
|
||||
// Message submission
|
||||
const { handleSubmit: submitMessage } = useMessageSubmit({
|
||||
|
||||
@@ -14,7 +14,7 @@ import { MessageHandler } from '../webview/MessageHandler.js';
|
||||
import { WebViewContent } from '../webview/WebViewContent.js';
|
||||
import { CliInstaller } from '../cli/cliInstaller.js';
|
||||
import { getFileName } from './utils/webviewUtils.js';
|
||||
import { type ApprovalModeValue } from '../types/acpTypes.js';
|
||||
import { type ApprovalModeValue } from '../types/approvalModeValueTypes.js';
|
||||
|
||||
export class WebViewProvider {
|
||||
private panelManager: PanelManager;
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import { CompletionMenu } from '../layout/CompletionMenu.js';
|
||||
import type { CompletionItem } from '../../../types/completionItemTypes.js';
|
||||
import { getApprovalModeInfoFromString } from '../../../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../../../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../../../types/approvalModeValueTypes.js';
|
||||
|
||||
interface InputFormProps {
|
||||
inputText: string;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { BaseMessageHandler } from './BaseMessageHandler.js';
|
||||
import type { ChatMessage } from '../../services/qwenAgentManager.js';
|
||||
import type { ApprovalModeValue } from '../../types/approvalModeValueTypes.js';
|
||||
|
||||
/**
|
||||
* Session message handler
|
||||
@@ -29,6 +30,8 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
'cancelStreaming',
|
||||
// UI action: open a new chat tab (new WebviewPanel)
|
||||
'openNewChatTab',
|
||||
// Settings-related messages
|
||||
'setApprovalMode',
|
||||
].includes(messageType);
|
||||
}
|
||||
|
||||
@@ -112,6 +115,14 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
await this.handleCancelStreaming();
|
||||
break;
|
||||
|
||||
case 'setApprovalMode':
|
||||
await this.handleSetApprovalMode(
|
||||
message.data as {
|
||||
modeId?: ApprovalModeValue;
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(
|
||||
'[SessionMessageHandler] Unknown message type:',
|
||||
@@ -1073,4 +1084,23 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set approval mode via agent (ACP session/set_mode)
|
||||
*/
|
||||
private async handleSetApprovalMode(data?: {
|
||||
modeId?: ApprovalModeValue;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const modeId = data?.modeId || 'default';
|
||||
await this.agentManager.setApprovalModeFromUi(modeId);
|
||||
// No explicit response needed; WebView listens for modeChanged
|
||||
} catch (error) {
|
||||
console.error('[SessionMessageHandler] Failed to set mode:', error);
|
||||
this.sendToWebView({
|
||||
type: 'error',
|
||||
data: { message: `Failed to set mode: ${error}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ export const useFileContext = (vscode: VSCodeAPI) => {
|
||||
// Whether workspace files have been requested
|
||||
const hasRequestedFilesRef = useRef(false);
|
||||
|
||||
// Last non-empty query to decide when to refetch full list
|
||||
const lastQueryRef = useRef<string | undefined>(undefined);
|
||||
|
||||
// Search debounce timer
|
||||
const searchTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
@@ -42,12 +45,10 @@ export const useFileContext = (vscode: VSCodeAPI) => {
|
||||
*/
|
||||
const requestWorkspaceFiles = useCallback(
|
||||
(query?: string) => {
|
||||
if (!hasRequestedFilesRef.current && !query) {
|
||||
hasRequestedFilesRef.current = true;
|
||||
}
|
||||
const normalizedQuery = query?.trim();
|
||||
|
||||
// If there's a query, clear previous timer and set up debounce
|
||||
if (query && query.length >= 1) {
|
||||
if (normalizedQuery && normalizedQuery.length >= 1) {
|
||||
if (searchTimerRef.current) {
|
||||
clearTimeout(searchTimerRef.current);
|
||||
}
|
||||
@@ -55,14 +56,23 @@ export const useFileContext = (vscode: VSCodeAPI) => {
|
||||
searchTimerRef.current = setTimeout(() => {
|
||||
vscode.postMessage({
|
||||
type: 'getWorkspaceFiles',
|
||||
data: { query },
|
||||
data: { query: normalizedQuery },
|
||||
});
|
||||
}, 300);
|
||||
lastQueryRef.current = normalizedQuery;
|
||||
} else {
|
||||
vscode.postMessage({
|
||||
type: 'getWorkspaceFiles',
|
||||
data: query ? { query } : {},
|
||||
});
|
||||
// For empty query, request once initially and whenever we are returning from a search
|
||||
const shouldRequestFullList =
|
||||
!hasRequestedFilesRef.current || lastQueryRef.current !== undefined;
|
||||
|
||||
if (shouldRequestFullList) {
|
||||
lastQueryRef.current = undefined;
|
||||
hasRequestedFilesRef.current = true;
|
||||
vscode.postMessage({
|
||||
type: 'getWorkspaceFiles',
|
||||
data: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[vscode],
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
ToolCall as PermissionToolCall,
|
||||
} from '../components/PermissionDrawer/PermissionRequest.js';
|
||||
import type { ToolCallUpdate } from '../../types/chatTypes.js';
|
||||
import type { ApprovalModeValue } from '../../types/acpTypes.js';
|
||||
import type { ApprovalModeValue } from '../../types/approvalModeValueTypes.js';
|
||||
import type { PlanEntry } from '../../types/chatTypes.js';
|
||||
|
||||
interface UseWebViewMessagesProps {
|
||||
|
||||
Reference in New Issue
Block a user