feat(shell): enable PTY by default and various enhancements

### Shell & Interactive Terminal Improvements
- PTY shell is now enabled by default instead of disabled
- Improved shell output rendering, process termination, and added fallback warning
- Background commands now properly capture subprocess PIDs on non-Windows

### Coding Plan Improvements
- Simplified auth message, added /model tip, improved system info display
- Reordered model list to prioritize glm-5, kimi-k2.5, MiniMax-M2.5
- Model selection is now preserved when updating if the model still exists

### Other Changes
- Added shared symlink utility; debug logs now have latest alias
- Unknown settings warnings go to debug log instead of user-facing warnings
- Fixed subagent confirmation state detection
- Removed debug UI from AgentCreationWizard

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-03-05 11:28:13 +08:00
parent 991ae9febc
commit b48e3caa75
31 changed files with 729 additions and 314 deletions

View file

@ -448,7 +448,7 @@ describe('Settings Loading and Merging', () => {
);
});
it('should warn about unknown top-level keys in a v2 settings file', () => {
it('should silently ignore unknown top-level keys in a v2 settings file', () => {
(mockFsExistsSync as Mock).mockImplementation(
(p: fs.PathLike) => p === USER_SETTINGS_PATH,
);
@ -466,13 +466,7 @@ describe('Settings Loading and Merging', () => {
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(getSettingsWarnings(settings)).toEqual(
expect.arrayContaining([
expect.stringContaining(
"Unknown setting 'someUnknownKey' will be ignored",
),
]),
);
expect(getSettingsWarnings(settings)).toEqual([]);
});
it('should not warn for valid v2 container keys', () => {

View file

@ -14,6 +14,7 @@ import {
QWEN_DIR,
getErrorMessage,
Storage,
createDebugLogger,
} from '@qwen-code/qwen-code-core';
import stripJsonComments from 'strip-json-comments';
import { DefaultLight } from '../ui/themes/default-light.js';
@ -32,6 +33,8 @@ import { customDeepMerge, type MergeableObject } from '../utils/deepMerge.js';
import { updateSettingsFilePreservingFormat } from '../utils/commentJson.js';
import { writeStderrLine } from '../utils/stdioHelpers.js';
const debugLogger = createDebugLogger('SETTINGS');
function getMergeStrategyForPath(path: string[]): MergeStrategy | undefined {
let current: SettingDefinition | undefined = undefined;
let currentSchema: SettingsSchema | undefined = getSettingsSchema();
@ -564,7 +567,7 @@ function getSettingsFileKeyWarnings(
);
}
// Unknown top-level keys.
// Unknown top-level keys — log silently to debug output.
const schemaKeys = new Set(Object.keys(getSettingsSchema()));
for (const key of Object.keys(settings)) {
if (key === SETTINGS_VERSION_KEY) {
@ -577,8 +580,8 @@ function getSettingsFileKeyWarnings(
continue;
}
warnings.push(
`Warning: Unknown setting '${key}' will be ignored in ${settingsFilePath}.`,
debugLogger.warn(
`Unknown setting '${key}' will be ignored in ${settingsFilePath}.`,
);
}

View file

@ -822,9 +822,9 @@ const SETTINGS_SCHEMA = {
label: 'Interactive Shell (PTY)',
category: 'Tools',
requiresRestart: true,
default: false,
default: true,
description:
'Use node-pty for an interactive shell experience. Fallback to child_process still applies.',
'Use node-pty for an interactive shell experience. Falls back to child_process if PTY is unavailable.',
showInDialog: true,
},
pager: {

View file

@ -64,6 +64,42 @@ export function generateCodingPlanTemplate(
contextWindowSize: 1000000,
},
},
{
id: 'glm-5',
name: '[Bailian Coding Plan] glm-5',
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
envKey: CODING_PLAN_ENV_KEY,
generationConfig: {
extra_body: {
enable_thinking: true,
},
contextWindowSize: 202752,
},
},
{
id: 'kimi-k2.5',
name: '[Bailian Coding Plan] kimi-k2.5',
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
envKey: CODING_PLAN_ENV_KEY,
generationConfig: {
extra_body: {
enable_thinking: true,
},
contextWindowSize: 262144,
},
},
{
id: 'MiniMax-M2.5',
name: '[Bailian Coding Plan] MiniMax-M2.5',
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
envKey: CODING_PLAN_ENV_KEY,
generationConfig: {
extra_body: {
enable_thinking: true,
},
contextWindowSize: 1000000,
},
},
{
id: 'qwen3-coder-plus',
name: '[Bailian Coding Plan] qwen3-coder-plus',
@ -106,42 +142,6 @@ export function generateCodingPlanTemplate(
contextWindowSize: 202752,
},
},
{
id: 'glm-5',
name: '[Bailian Coding Plan] glm-5',
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
envKey: CODING_PLAN_ENV_KEY,
generationConfig: {
extra_body: {
enable_thinking: true,
},
contextWindowSize: 202752,
},
},
{
id: 'MiniMax-M2.5',
name: '[Bailian Coding Plan] MiniMax-M2.5',
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
envKey: CODING_PLAN_ENV_KEY,
generationConfig: {
extra_body: {
enable_thinking: true,
},
contextWindowSize: 1000000,
},
},
{
id: 'kimi-k2.5',
name: '[Bailian Coding Plan] kimi-k2.5',
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
envKey: CODING_PLAN_ENV_KEY,
generationConfig: {
extra_body: {
enable_thinking: true,
},
contextWindowSize: 262144,
},
},
];
}

View file

@ -1457,6 +1457,10 @@ export default {
'Neue Modellkonfigurationen sind für {{region}} verfügbar. Jetzt aktualisieren?',
'{{region}} configuration updated successfully. Model switched to "{{model}}".':
'{{region}}-Konfiguration erfolgreich aktualisiert. Modell auf "{{model}}" umgeschaltet.',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'Erfolgreich mit {{region}} authentifiziert. API-Schlüssel und Modellkonfigurationen wurden in settings.json gespeichert (gesichert).',
'{{region}} configuration updated successfully.':
'{{region}}-Konfiguration erfolgreich aktualisiert.',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json.':
'Erfolgreich mit {{region}} authentifiziert. API-Schlüssel und Modellkonfigurationen wurden in settings.json gespeichert.',
'Tip: Use /model to switch between available Coding Plan models.':
'Tipp: Verwenden Sie /model, um zwischen verfügbaren Coding Plan-Modellen zu wechseln.',
};

View file

@ -1446,6 +1446,10 @@ export default {
'New model configurations are available for {{region}}. Update now?',
'{{region}} configuration updated successfully. Model switched to "{{model}}".':
'{{region}} configuration updated successfully. Model switched to "{{model}}".',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).',
'{{region}} configuration updated successfully.':
'{{region}} configuration updated successfully.',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json.':
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json.',
'Tip: Use /model to switch between available Coding Plan models.':
'Tip: Use /model to switch between available Coding Plan models.',
};

View file

@ -964,6 +964,10 @@ export default {
'{{region}} の新しいモデル設定が利用可能です。今すぐ更新しますか?',
'{{region}} configuration updated successfully. Model switched to "{{model}}".':
'{{region}} の設定が正常に更新されました。モデルが "{{model}}" に切り替わりました。',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'{{region}} での認証に成功しました。APIキーとモデル設定が settings.json に保存されました(バックアップ済み)。',
'{{region}} configuration updated successfully.':
'{{region}} の設定が正常に更新されました。',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json.':
'{{region}} での認証に成功しました。APIキーとモデル設定が settings.json に保存されました。',
'Tip: Use /model to switch between available Coding Plan models.':
'ヒント: /model で利用可能な Coding Plan モデルを切り替えられます。',
};

View file

@ -1451,6 +1451,10 @@ export default {
'Novas configurações de modelo estão disponíveis para o {{region}}. Atualizar agora?',
'{{region}} configuration updated successfully. Model switched to "{{model}}".':
'Configuração do {{region}} atualizada com sucesso. Modelo alterado para "{{model}}".',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'Autenticado com sucesso com {{region}}. Chave de API e configurações de modelo salvas em settings.json (com backup).',
'{{region}} configuration updated successfully.':
'Configuração do {{region}} atualizada com sucesso.',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json.':
'Autenticado com sucesso com {{region}}. Chave de API e configurações de modelo salvas em settings.json.',
'Tip: Use /model to switch between available Coding Plan models.':
'Dica: Use /model para alternar entre os modelos disponíveis do Coding Plan.',
};

View file

@ -1461,6 +1461,10 @@ export default {
'Доступны новые конфигурации моделей для {{region}}. Обновить сейчас?',
'{{region}} configuration updated successfully. Model switched to "{{model}}".':
'Конфигурация {{region}} успешно обновлена. Модель переключена на "{{model}}".',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'Успешная аутентификация с {{region}}. API-ключ и конфигурации моделей сохранены в settings.json (резервная копия создана).',
'{{region}} configuration updated successfully.':
'Конфигурация {{region}} успешно обновлена.',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json.':
'Успешная аутентификация с {{region}}. API-ключ и конфигурации моделей сохранены в settings.json.',
'Tip: Use /model to switch between available Coding Plan models.':
'Совет: Используйте /model для переключения между доступными моделями Coding Plan.',
};

View file

@ -1279,6 +1279,9 @@ export default {
'{{region}} 有新的模型配置可用。是否立即更新?',
'{{region}} configuration updated successfully. Model switched to "{{model}}".':
'{{region}} 配置更新成功。模型已切换至 "{{model}}"。',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).':
'成功通过 {{region}} 认证。API Key 和模型配置已保存至 settings.json已备份。',
'{{region}} configuration updated successfully.': '{{region}} 配置更新成功。',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json.':
'成功通过 {{region}} 认证。API Key 和模型配置已保存至 settings.json。',
'Tip: Use /model to switch between available Coding Plan models.':
'提示:使用 /model 切换可用的 Coding Plan 模型。',
};

View file

@ -14,9 +14,7 @@ import type {
InsightProgressCallback,
} from '../types/StaticInsightTypes.js';
import { createDebugLogger, type Config } from '@qwen-code/qwen-code-core';
const logger = createDebugLogger('StaticInsightGenerator');
import { updateSymlink, type Config } from '@qwen-code/qwen-code-core';
export class StaticInsightGenerator {
private dataProcessor: DataProcessor;
@ -54,40 +52,12 @@ export class StaticInsightGenerator {
return outputPath;
}
// Create or update the "latest" alias (symlink preferred, copy as fallback)
private async updateLatestAlias(
private async updateInsightSymlink(
outputDir: string,
targetPath: string,
): Promise<void> {
const latestPath = path.join(outputDir, 'insight.html');
const relativeTarget = path.relative(outputDir, targetPath);
// Remove existing file/symlink if it exists
try {
await fs.unlink(latestPath);
} catch {
// File doesn't exist, ignore
}
// Try symlink first (preferred - lightweight, always points to latest)
try {
await fs.symlink(relativeTarget, latestPath);
logger.debug('Created insight symlink:', relativeTarget);
return;
} catch (error) {
logger.debug(
'Failed to create insight symlink, falling back to copy:',
error,
);
}
// Fallback: copy file (works everywhere, uses more disk space)
try {
await fs.copyFile(targetPath, latestPath);
logger.debug('Created insight copy:', targetPath);
} catch (error) {
logger.debug('Failed to create insight latest alias:', error);
}
await updateSymlink(latestPath, targetPath);
}
// Generate the static insight HTML file
@ -116,8 +86,7 @@ export class StaticInsightGenerator {
// Write the HTML file
await fs.writeFile(outputPath, html, 'utf-8');
// Update latest alias (symlink preferred, copy as fallback)
await this.updateLatestAlias(outputDir, outputPath);
await this.updateInsightSymlink(outputDir, outputPath);
return outputPath;
}

View file

@ -389,13 +389,24 @@ export const useAuthCommand = (
{
type: MessageType.INFO,
text: t(
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json (backed up).',
'Authenticated successfully with {{region}}. API key and model configs saved to settings.json.',
{ region: t('Alibaba Cloud Coding Plan') },
),
},
Date.now(),
);
// Hint about /model command
addItem(
{
type: MessageType.INFO,
text: t(
'Tip: Use /model to switch between available Coding Plan models.',
),
},
Date.now(),
);
// Log success
const authEvent = new AuthEvent(
AuthType.USE_OPENAI,

View file

@ -11,7 +11,7 @@ exports[`SettingsDialog > Snapshot Tests > should render default state correctly
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode false │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false │
│ ▼ │
@ -32,7 +32,7 @@ exports[`SettingsDialog > Snapshot Tests > should render focused on scope select
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode false │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false │
│ ▼ │
@ -53,7 +53,7 @@ exports[`SettingsDialog > Snapshot Tests > should render with accessibility sett
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode true* │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false │
│ ▼ │
@ -74,7 +74,7 @@ exports[`SettingsDialog > Snapshot Tests > should render with all boolean settin
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode false* │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false* │
│ ▼ │
@ -95,7 +95,7 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode (Modified in System) false │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false │
│ ▼ │
@ -116,7 +116,7 @@ exports[`SettingsDialog > Snapshot Tests > should render with different scope se
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode (Modified in Workspace) false │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false │
│ ▼ │
@ -137,7 +137,7 @@ exports[`SettingsDialog > Snapshot Tests > should render with file filtering set
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode false │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false │
│ ▼ │
@ -158,7 +158,7 @@ exports[`SettingsDialog > Snapshot Tests > should render with mixed boolean and
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode false* │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false │
│ ▼ │
@ -179,7 +179,7 @@ exports[`SettingsDialog > Snapshot Tests > should render with tools and security
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode false │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE false │
│ ▼ │
@ -200,7 +200,7 @@ exports[`SettingsDialog > Snapshot Tests > should render with various boolean se
│ Language: Model auto │
│ Theme Qwen Dark │
│ Vim Mode true* │
│ Interactive Shell (PTY) false │
│ Interactive Shell (PTY) true │
│ Preferred Editor │
│ Auto-connect to IDE true* │
│ ▼ │

View file

@ -120,45 +120,6 @@ export function AgentCreationWizard({
);
}, [state.currentStep, state.generationMethod]);
const renderDebugContent = useCallback(() => {
if (process.env['NODE_ENV'] !== 'development') {
return null;
}
return (
<Box borderStyle="single" borderColor={theme.status.warning} padding={1}>
<Box flexDirection="column">
<Text color={theme.status.warning} bold>
Debug Info:
</Text>
<Text color={theme.text.secondary}>Step: {state.currentStep}</Text>
<Text color={theme.text.secondary}>
Can Proceed: {state.canProceed ? 'Yes' : 'No'}
</Text>
<Text color={theme.text.secondary}>
Generating: {state.isGenerating ? 'Yes' : 'No'}
</Text>
<Text color={theme.text.secondary}>Location: {state.location}</Text>
<Text color={theme.text.secondary}>
Method: {state.generationMethod}
</Text>
{state.validationErrors.length > 0 && (
<Text color={theme.status.error}>
Errors: {state.validationErrors.join(', ')}
</Text>
)}
</Box>
</Box>
);
}, [
state.currentStep,
state.canProceed,
state.isGenerating,
state.location,
state.generationMethod,
state.validationErrors,
]);
const renderStepFooter = useCallback(() => {
const getNavigationInstructions = () => {
// Special case: During generation in description input step, only show cancel option
@ -331,7 +292,6 @@ export function AgentCreationWizard({
>
{renderStepHeader()}
{renderStepContent()}
{renderDebugContent()}
{renderStepFooter()}
</Box>
</Box>

View file

@ -481,6 +481,111 @@ describe('useCodingPlanUpdates', () => {
).toBe(true);
});
it('should show "model preserved" message when current model exists in new template', async () => {
mockSettings.merged.codingPlan = {
region: CodingPlanRegion.CHINA,
version: 'old-version-hash',
};
mockSettings.merged.modelProviders = {
[AuthType.USE_OPENAI]: [
{
id: 'qwen3.5-plus',
baseUrl: chinaConfig.baseUrl,
envKey: CODING_PLAN_ENV_KEY,
},
],
};
// Simulate the user's current model being one that exists in the new template
mockConfig.getModel.mockReturnValue('qwen3.5-plus');
mockConfig.refreshAuth.mockResolvedValue(undefined);
const { result } = renderHook(() =>
useCodingPlanUpdates(
mockSettings as never,
mockConfig as never,
mockAddItem,
),
);
await waitFor(() => {
expect(result.current.codingPlanUpdateRequest).toBeDefined();
});
await result.current.codingPlanUpdateRequest!.onConfirm(true);
await waitFor(() => {
expect(mockSettings.setValue).toHaveBeenCalled();
});
// Should show plain success message without "switched"
expect(mockAddItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'info',
text: expect.stringContaining('updated successfully'),
}),
expect.any(Number),
);
expect(mockAddItem).not.toHaveBeenCalledWith(
expect.objectContaining({
type: 'info',
text: expect.stringContaining('switched'),
}),
expect.any(Number),
);
// Reset mock
mockConfig.getModel.mockReturnValue('qwen-max');
});
it('should show "model switched" message when current model is not in new template', async () => {
mockSettings.merged.codingPlan = {
region: CodingPlanRegion.CHINA,
version: 'old-version-hash',
};
mockSettings.merged.modelProviders = {
[AuthType.USE_OPENAI]: [
{
id: 'removed-model',
baseUrl: chinaConfig.baseUrl,
envKey: CODING_PLAN_ENV_KEY,
},
],
};
// The user's current model no longer exists in the new template
mockConfig.getModel.mockReturnValue('removed-model');
mockConfig.refreshAuth.mockResolvedValue(undefined);
const { result } = renderHook(() =>
useCodingPlanUpdates(
mockSettings as never,
mockConfig as never,
mockAddItem,
),
);
await waitFor(() => {
expect(result.current.codingPlanUpdateRequest).toBeDefined();
});
await result.current.codingPlanUpdateRequest!.onConfirm(true);
await waitFor(() => {
expect(mockSettings.setValue).toHaveBeenCalled();
});
// Should show "model switched" message
expect(mockAddItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'info',
text: expect.stringContaining('switched'),
}),
expect.any(Number),
);
// Reset mock
mockConfig.getModel.mockReturnValue('qwen-max');
});
it('should handle update errors gracefully', async () => {
mockSettings.merged.codingPlan = {
region: CodingPlanRegion.CHINA,

View file

@ -42,6 +42,7 @@ export function useCodingPlanUpdates(
/**
* Execute the Coding Plan configuration update.
* Removes old Coding Plan configs and replaces them with new ones from the template.
* Preserves the user's current model selection if it still exists in the new template.
* Uses the region from settings.codingPlan.region (defaults to CHINA).
*/
const executeUpdate = useCallback(
@ -82,6 +83,12 @@ export function useCodingPlanUpdates(
...(nonCodingPlanConfigs as Array<Record<string, unknown>>),
] as Array<Record<string, unknown>>;
// Record the user's current model before the update
const previousModel = config.getModel();
const previousModelStillAvailable = newConfigs.some(
(cfg) => cfg.id === previousModel,
);
// Hot-reload model providers configuration first (in-memory only)
const updatedModelProviders = {
...(settings.merged.modelProviders as
@ -112,12 +119,34 @@ export function useCodingPlanUpdates(
const activeModel = config.getModel();
if (previousModelStillAvailable && activeModel === previousModel) {
addItem(
{
type: 'info',
text: t('{{region}} configuration updated successfully.', {
region: t('Alibaba Cloud Coding Plan'),
}),
},
Date.now(),
);
} else {
addItem(
{
type: 'info',
text: t(
'{{region}} configuration updated successfully. Model switched to "{{model}}".',
{ region: t('Alibaba Cloud Coding Plan'), model: activeModel },
),
},
Date.now(),
);
}
addItem(
{
type: 'info',
text: t(
'{{region}} configuration updated successfully. Model switched to "{{model}}".',
{ region: t('Alibaba Cloud Coding Plan'), model: activeModel },
'Tip: Use /model to switch between available Coding Plan models.',
),
},
Date.now(),

View file

@ -59,6 +59,7 @@ import {
type TrackedToolCall,
type TrackedCompletedToolCall,
type TrackedCancelledToolCall,
type TrackedExecutingToolCall,
type TrackedWaitingToolCall,
} from './useReactToolScheduler.js';
import { promises as fs } from 'node:fs';
@ -358,6 +359,23 @@ export const useGeminiStream = (
if (toolCalls.some((tc) => tc.status === 'awaiting_approval')) {
return StreamingState.WaitingForConfirmation;
}
// Check if any executing subagent task has a pending confirmation
if (
toolCalls.some((tc) => {
if (tc.status !== 'executing') return false;
const liveOutput = (tc as TrackedExecutingToolCall).liveOutput;
return (
typeof liveOutput === 'object' &&
liveOutput !== null &&
'type' in liveOutput &&
liveOutput.type === 'task_execution' &&
'pendingConfirmation' in liveOutput &&
liveOutput.pendingConfirmation != null
);
})
) {
return StreamingState.WaitingForConfirmation;
}
if (
isResponding ||
toolCalls.some(

View file

@ -38,6 +38,7 @@ export interface SystemInfo {
export interface ExtendedSystemInfo extends SystemInfo {
memoryUsage: string;
baseUrl?: string;
apiKeyEnvKey?: string;
gitCommit?: string;
proxy?: string;
}
@ -154,12 +155,14 @@ export async function getExtendedSystemInfo(
// For bug reports, use sandbox name without prefix
const sandboxEnv = getSandboxEnv(true);
// Get base URL if using OpenAI auth
const baseUrl =
// Get base URL and apiKeyEnvKey if using OpenAI or Anthropic auth
const contentGeneratorConfig =
baseInfo.selectedAuthType === AuthType.USE_OPENAI ||
baseInfo.selectedAuthType === AuthType.USE_ANTHROPIC
? context.services.config?.getContentGeneratorConfig()?.baseUrl
? context.services.config?.getContentGeneratorConfig()
: undefined;
const baseUrl = contentGeneratorConfig?.baseUrl;
const apiKeyEnvKey = contentGeneratorConfig?.apiKeyEnvKey;
// Get git commit info
const gitCommit =
@ -172,6 +175,7 @@ export async function getExtendedSystemInfo(
sandboxEnv,
memoryUsage,
baseUrl,
apiKeyEnvKey,
gitCommit,
};
}

View file

@ -6,6 +6,7 @@
import type { ExtendedSystemInfo } from './systemInfo.js';
import { t } from '../i18n/index.js';
import { isCodingPlanConfig } from '../constants/codingPlan.js';
/**
* Field configuration for system information display
@ -30,6 +31,7 @@ export function getSystemInfoFields(
addField(fields, t('IDE Client'), info.ideClient);
addField(fields, t('OS'), formatOs(info));
addField(fields, t('Auth'), formatAuth(info));
addField(fields, t('Base URL'), formatBaseUrl(info));
addField(fields, t('Model'), info.modelVersion);
addField(fields, t('Session ID'), info.sessionId);
addField(fields, t('Sandbox'), info.sandboxEnv);
@ -86,15 +88,34 @@ function formatAuth(info: ExtendedSystemInfo): string {
if (!info.selectedAuthType) {
return '';
}
const authType = formatAuthType(info.selectedAuthType);
if (!info.baseUrl) {
return authType;
if (isCodingPlanConfig(info.baseUrl, info.apiKeyEnvKey)) {
return t('Alibaba Cloud Coding Plan');
}
return `${authType} (${info.baseUrl})`;
if (
info.selectedAuthType.startsWith('oauth') ||
info.selectedAuthType === 'qwen-oauth'
) {
return 'Qwen OAuth';
}
return `API Key - ${info.selectedAuthType}`;
}
function formatAuthType(authType: string): string {
return authType.startsWith('oauth') ? 'OAuth' : authType;
function formatBaseUrl(info: ExtendedSystemInfo): string {
if (!info.selectedAuthType || !info.baseUrl) {
return '';
}
if (
info.selectedAuthType.startsWith('oauth') ||
info.selectedAuthType === 'qwen-oauth'
) {
return '';
}
return info.baseUrl;
}
function formatProxy(proxy?: string): string {