mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 04:30:48 +00:00
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:
parent
991ae9febc
commit
b48e3caa75
31 changed files with 729 additions and 314 deletions
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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}.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 モデルを切り替えられます。',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 模型。',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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* │
|
||||
│ ▼ │
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue