qwen-code/packages/vscode-ide-companion/src/webview/context/VSCodePlatformProvider.tsx
易良 e49867a762
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
feat(vscode): replace OAuth with Coding Plan / API Key provider setup (#3398)
* refactor(core): move codingPlan constants from cli to core package

Extract Coding Plan region configs, model templates, and utility
functions into packages/core/src/constants/ so both CLI and VSCode
extension can import from a shared source of truth.

* refactor(cli): import codingPlan constants from core instead of local path

Update all CLI files to import CodingPlanRegion, CODING_PLAN_ENV_KEY,
and related utilities from @qwen-code/qwen-code-core, replacing the
local ../../constants/codingPlan.js imports.

* feat(vscode-ide-companion): replace login flow with provider setup via VSCode Settings

Replace the OAuth-based login command with a settings-driven provider
configuration flow. Users now configure Coding Plan or API Key providers
through VSCode Settings (qwen-code.*), which auto-syncs to
~/.qwen/settings.json.

- Rename login command to auth, opening VSCode Settings panel
- Add /auth2 interactive flow (QuickPick + InputBox)
- Add ProviderSetupForm onboarding component with inline config
- Add bidirectional sync between VSCode settings and ~/.qwen/settings.json
- Add settingsWriter service for direct settings.json read/write
- Add VSCode configuration schema (provider, apiKey, region, model, etc.)
- Update all login/session messages to use auth terminology

* refactor(vscode-ide-companion): rename auth2→auth, remove dead code, fix sync guard

- Rename auth2 to auth for all message types, handlers, and slash command
- Remove unused InfoBanner.tsx (128 lines, no references)
- Remove dead openProviderSettings handler (no callers)
- Remove redundant qwen-code.baseUrl VSCode setting (already in modelProviders)
- Replace unreliable setTimeout(500) sync guard with await Promise.all + finally
- Clean up old authHandler/setAuthHandler in favor of authInteractiveHandler

* refactor(vscode-ide-companion): remove dead VSCode Settings plumbing, simplify sync

- Remove qwen-code.modelProviders and qwen-code.model from package.json
  (model switching handled by chat UI's /model command, not VSCode Settings)
- Remove connectWithSettings message handler and plumbing
  (no webview component sends this message type)
- Remove handleConnectWithSettings method from WebViewProvider
- Simplify syncVSCodeSettingsToQwenConfig: only sync provider/apiKey/region
- Simplify syncQwenConfigToVSCodeSettings: only populate provider/apiKey/region
- Simplify QwenSettingsForVSCode interface: remove modelProviders and model
- Improve Onboarding UI: logo above card, better hierarchy, arrow icon on button

* fix(vscode-ide-companion): add missing vscode.workspace mock in test

Add onDidChangeConfiguration and getConfiguration to the vscode.workspace
mock in WebViewProvider.test.ts to fix CI test failures.

* fix(vscode-ide-companion): clean up stale coding plan state, add auth cancel handling, add tests

- Clear CODING_PLAN_ENV_KEY and codingPlan metadata when switching to api-key mode
- Add authCancelled notification when QuickPick/InputBox is dismissed
- ProviderSetupForm resets button state on authCancelled
- syncVSCodeSettingsToQwenConfig returns false for api-key mode (no-op)
- Fix Onboarding vertical centering (flex-1 min-h-0)
- Import from @qwen-code/qwen-code-core top-level instead of deep paths
- Add tests: settingsWriter, ProviderSetupForm cancel, AuthMessageHandler cancel, WebViewProvider sync
- Fix redundant ternary in pick() helper

* fix(vscode-ide-companion): force center Onboarding against parent override

Parent container uses [&>*]:items-start and [&>*]:text-left which overrides
Tailwind classes. Use inline style for alignItems/justifyContent/textAlign
to ensure Onboarding is always centered both horizontally and vertically.

* fix(vscode-ide-companion): bundle onboarding logo

* test(vscode-ide-companion): add png loader to bundle test

* fix(vscode-ide-companion/webview): avoid redundant auth sync reconnects

* fix(vscode-ide-companion/webview): fix auth sync typecheck

* docs(vscode-ide-companion): clarify auth restoration flow

* fix(webui): use bracket access for permission drawer plan content

* fix(vscode-ide-companion): guard authSuccess emission on actual auth state

After reconnecting in handleAuthInteractive, doInitializeAgentConnection
may return without throwing even when credentials are rejected (it sends
authState:false internally and returns early). Previously we unconditionally
emitted authSuccess, which contradicted the failed auth state and could
briefly show a success toast before re-opening the auth flow.

Now we check this.authState after reconnection: only emit authSuccess when
authentication actually succeeded, otherwise emit authError with a clear
credentials message.

Addresses review feedback from PR #3398.

* fix(vscode): address auth setup review feedback

* fix(vscode-ide-companion): guard concurrent auth flows, merge model providers

- Add authFlowActive mutex and autoAuthTimer to WebViewProvider so
  startInteractiveAuth() cancels the deferred auto-auth timeout,
  preventing two overlapping QuickPick flows from a single command.
- Change writeModelProvidersConfig() to merge new entries with existing
  non-target models (different envKey) instead of replacing the entire
  array, preserving unrelated providers like Coding Plan.

* fix(vscode-ide-companion): handle apiKey clearing as de-auth signal, fix auto-auth race, clean imports

- Add clearPersistedAuth() to settingsWriter.ts: removes selectedType,
  API keys, and coding plan metadata from ~/.qwen/settings.json
- Config change handler now detects empty apiKey with active agent and
  triggers de-auth: clear credentials, disconnect, update authState
- Auto-auth timer callback now properly sets authFlowActive mutex to
  prevent concurrent auth flows with startInteractiveAuth()
- Add test covering the de-auth path (clearPersistedAuth + disconnect)
- Fix import formatting in 7 CLI files (spacing, trailing commas)
- Remove duplicate comment in attemptAuthStateRestoration()

* fix(vscode-ide-companion): scope de-auth to apiKey changes only

The previous de-auth logic triggered on any auth-related setting change
where syncVSCodeSettingsToQwenConfig() returned false. For api-key
providers this is the normal path (interactive auth owns config), so
changing codingPlanRegion or provider would incorrectly wipe OPENAI_API_KEY.

Now the de-auth branch only fires when e.affectsConfiguration('qwen-code.apiKey')
is true AND the value is empty, preventing false-positive credential clearing.

Add regression test: non-apiKey setting changes on an api-key provider
must not trigger clearPersistedAuth or disconnect.

* fix(vscode-ide-companion): add disconnect to mock type to fix CI typecheck

The hoisted mockQwenAgentManagerInstances type was missing the
disconnect property, causing TS2339 in the de-auth test assertions.
2026-04-21 22:20:58 +08:00

171 lines
4 KiB
TypeScript

/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*
* VSCode Platform Provider - Adapts VSCode API to PlatformContext
* This allows webui components to work with VSCode's messaging system
*/
import { useMemo, useCallback, useEffect, useRef } from 'react';
import type { FC, ReactNode } from 'react';
import { PlatformProvider } from '@qwen-code/webui';
import type { PlatformContextValue } from '@qwen-code/webui';
import { useVSCode } from '../hooks/useVSCode.js';
import { generateIconUrl } from '../utils/resourceUrl.js';
/**
* Props for VSCodePlatformProvider
*/
interface VSCodePlatformProviderProps {
children: ReactNode;
}
/**
* VSCodePlatformProvider - Provides platform context for VSCode extension
*
* This component bridges the VSCode API with the platform-agnostic webui components.
* It wraps children with PlatformProvider and provides VSCode-specific implementations.
*/
export const VSCodePlatformProvider: FC<VSCodePlatformProviderProps> = ({
children,
}) => {
const vscode = useVSCode();
const messageHandlersRef = useRef<Set<(message: unknown) => void>>(new Set());
// Set up message listener
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
messageHandlersRef.current.forEach((handler) => {
handler(event.data);
});
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
// Open file handler
const openFile = useCallback(
(path: string) => {
vscode.postMessage({
type: 'openFile',
data: { path },
});
},
[vscode],
);
// Open diff handler
const openDiff = useCallback(
(
path: string,
oldText: string | null | undefined,
newText: string | undefined,
) => {
vscode.postMessage({
type: 'openDiff',
data: {
path,
oldText: oldText ?? '',
newText: newText ?? '',
},
});
},
[vscode],
);
// Open temp file handler
const openTempFile = useCallback(
(content: string, fileName: string = 'temp') => {
vscode.postMessage({
type: 'createAndOpenTempFile',
data: {
content,
fileName,
},
});
},
[vscode],
);
// Attach file handler
const attachFile = useCallback(() => {
vscode.postMessage({
type: 'attachFile',
data: {},
});
}, [vscode]);
// Auth handler
const login = useCallback(() => {
vscode.postMessage({
type: 'auth',
data: {},
});
}, [vscode]);
// Copy to clipboard handler
const copyToClipboard = useCallback(async (text: string) => {
try {
await navigator.clipboard.writeText(text);
} catch (err) {
console.error('Failed to copy to clipboard:', err);
}
}, []);
// Get resource URL handler (for icons and other assets)
const getResourceUrl = useCallback(
(resourceName: string) => generateIconUrl(resourceName) || undefined,
[],
);
// Subscribe to messages
const onMessage = useCallback((handler: (message: unknown) => void) => {
messageHandlersRef.current.add(handler);
return () => {
messageHandlersRef.current.delete(handler);
};
}, []);
// Build platform context value
const platformValue = useMemo<PlatformContextValue>(
() => ({
platform: 'vscode',
postMessage: vscode.postMessage,
onMessage,
openFile,
openDiff,
openTempFile,
attachFile,
login,
copyToClipboard,
getResourceUrl,
features: {
canOpenFile: true,
canOpenDiff: true,
canOpenTempFile: true,
canAttachFile: true,
canLogin: true,
canCopy: true,
},
}),
[
vscode.postMessage,
onMessage,
openFile,
openDiff,
openTempFile,
attachFile,
login,
copyToClipboard,
getResourceUrl,
],
);
return (
<PlatformProvider value={platformValue}>
{children as React.ReactNode}
</PlatformProvider>
);
};