mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 19:52:02 +00:00
feat(cli): add bare startup mode (#3448)
* feat(cli): add bare startup mode Skip implicit startup discovery in bare mode while keeping explicit inputs such as include directories and extension overrides. Add a repository plan document and targeted tests for config, startup, skills, extensions, and memory discovery. * fix(bare): enforce explicit-only startup behavior * fix(cli): preserve bare tools in non-interactive mode * chore(docs): remove bare mode planning note
This commit is contained in:
parent
cfe142e9a3
commit
41f71ab7e7
19 changed files with 750 additions and 72 deletions
|
|
@ -28,6 +28,7 @@ import {
|
|||
NativeLspClient,
|
||||
createDebugLogger,
|
||||
NativeLspService,
|
||||
isBareMode,
|
||||
isToolEnabled,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { extensionsCommand } from '../commands/extensions.js';
|
||||
|
|
@ -115,6 +116,7 @@ export interface CliArgs {
|
|||
systemPrompt: string | undefined;
|
||||
appendSystemPrompt: string | undefined;
|
||||
yolo: boolean | undefined;
|
||||
bare: boolean | undefined;
|
||||
approvalMode: string | undefined;
|
||||
telemetry: boolean | undefined;
|
||||
checkpointing: boolean | undefined;
|
||||
|
|
@ -260,6 +262,12 @@ export async function parseArguments(): Promise<CliArgs> {
|
|||
description: 'Run in debug mode?',
|
||||
default: false,
|
||||
})
|
||||
.option('bare', {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Minimal mode: skip implicit startup auto-discovery and only honor explicitly provided CLI inputs.',
|
||||
default: false,
|
||||
})
|
||||
.option('proxy', {
|
||||
type: 'string',
|
||||
description: 'Proxy for Qwen Code, like schema://user:password@host:port',
|
||||
|
|
@ -737,6 +745,7 @@ export async function loadCliConfig(
|
|||
},
|
||||
): Promise<Config> {
|
||||
const debugMode = isDebugMode(argv);
|
||||
const bareMode = isBareMode(argv.bare);
|
||||
|
||||
// Set runtime output directory from settings (env var QWEN_RUNTIME_DIR
|
||||
// is auto-detected inside getRuntimeBaseDir() at each call site).
|
||||
|
|
@ -771,20 +780,24 @@ export async function loadCliConfig(
|
|||
);
|
||||
|
||||
let outputLanguageFilePath: string | undefined;
|
||||
if (fs.existsSync(projectOutputLanguagePath)) {
|
||||
outputLanguageFilePath = projectOutputLanguagePath;
|
||||
} else if (fs.existsSync(globalOutputLanguagePath)) {
|
||||
outputLanguageFilePath = globalOutputLanguagePath;
|
||||
if (!bareMode) {
|
||||
if (fs.existsSync(projectOutputLanguagePath)) {
|
||||
outputLanguageFilePath = projectOutputLanguagePath;
|
||||
} else if (fs.existsSync(globalOutputLanguagePath)) {
|
||||
outputLanguageFilePath = globalOutputLanguagePath;
|
||||
}
|
||||
}
|
||||
|
||||
const fileService = new FileDiscoveryService(cwd);
|
||||
|
||||
const includeDirectories = (settings.context?.includeDirectories || [])
|
||||
const includeDirectories = (
|
||||
bareMode ? [] : (settings.context?.includeDirectories ?? [])
|
||||
)
|
||||
.map(resolvePath)
|
||||
.concat((argv.includeDirectories || []).map(resolvePath));
|
||||
|
||||
// LSP configuration: enabled only via --experimental-lsp flag
|
||||
const lspEnabled = argv.experimentalLsp === true;
|
||||
const lspEnabled = !bareMode && argv.experimentalLsp === true;
|
||||
let lspClient: LspClient | undefined;
|
||||
const question = argv.promptInteractive || argv.prompt || '';
|
||||
const inputFormat: InputFormat =
|
||||
|
|
@ -810,7 +823,7 @@ export async function loadCliConfig(
|
|||
approvalMode = parseApprovalModeValue(argv.approvalMode);
|
||||
} else if (argv.yolo) {
|
||||
approvalMode = ApprovalMode.YOLO;
|
||||
} else if (settings.tools?.approvalMode) {
|
||||
} else if (!bareMode && settings.tools?.approvalMode) {
|
||||
approvalMode = parseApprovalModeValue(settings.tools.approvalMode);
|
||||
} else {
|
||||
approvalMode = ApprovalMode.DEFAULT;
|
||||
|
|
@ -888,17 +901,19 @@ export async function loadCliConfig(
|
|||
// not auto-approve semantics. They are passed via the `coreTools` Config param
|
||||
// and handled by PermissionManager.coreToolsAllowList.
|
||||
const resolvedCoreTools: string[] = [
|
||||
...(argv.coreTools ?? []),
|
||||
...(settings.tools?.core ?? []),
|
||||
...(bareMode ? [] : (argv.coreTools ?? [])),
|
||||
...(bareMode ? [] : (settings.tools?.core ?? [])),
|
||||
];
|
||||
const mergedAllow: string[] = [
|
||||
...(settings.permissions?.allow ?? []),
|
||||
...(settings.tools?.allowed ?? []),
|
||||
...(bareMode ? [] : (settings.permissions?.allow ?? [])),
|
||||
...(bareMode ? [] : (settings.tools?.allowed ?? [])),
|
||||
];
|
||||
const mergedAsk: string[] = [
|
||||
...(bareMode ? [] : (settings.permissions?.ask ?? [])),
|
||||
];
|
||||
const mergedAsk: string[] = [...(settings.permissions?.ask ?? [])];
|
||||
const mergedDeny: string[] = [
|
||||
...(settings.permissions?.deny ?? []),
|
||||
...(settings.tools?.exclude ?? []),
|
||||
...(bareMode ? [] : (settings.permissions?.deny ?? [])),
|
||||
...(bareMode ? [] : (settings.tools?.exclude ?? [])),
|
||||
];
|
||||
|
||||
// argv.allowedTools adds allow rules (auto-approve).
|
||||
|
|
@ -941,7 +956,12 @@ export async function loadCliConfig(
|
|||
// the caller has explicitly allowed them. Stream-JSON input is excluded from
|
||||
// this logic because approval can be sent programmatically via JSON messages.
|
||||
const isAcpMode = argv.acp || argv.experimentalAcp;
|
||||
if (!interactive && !isAcpMode && inputFormat !== InputFormat.STREAM_JSON) {
|
||||
if (
|
||||
!bareMode &&
|
||||
!interactive &&
|
||||
!isAcpMode &&
|
||||
inputFormat !== InputFormat.STREAM_JSON
|
||||
) {
|
||||
const denyUnlessAllowed = (toolName: ToolName): void => {
|
||||
if (!isExplicitlyAllowed(toolName)) {
|
||||
const name = toolName as string;
|
||||
|
|
@ -974,7 +994,7 @@ export async function loadCliConfig(
|
|||
if (argv.allowedMcpServerNames) {
|
||||
allowedMcpServers = new Set(argv.allowedMcpServerNames.filter(Boolean));
|
||||
excludedMcpServers = undefined;
|
||||
} else {
|
||||
} else if (!bareMode) {
|
||||
allowedMcpServers = settings.mcp?.allowed
|
||||
? new Set(settings.mcp.allowed.filter(Boolean))
|
||||
: undefined;
|
||||
|
|
@ -985,7 +1005,7 @@ export async function loadCliConfig(
|
|||
|
||||
const selectedAuthType =
|
||||
(argv.authType as AuthType | undefined) ||
|
||||
settings.security?.auth?.selectedType ||
|
||||
(bareMode ? undefined : settings.security?.auth?.selectedType) ||
|
||||
/* getAuthTypeFromEnv means no authType was explicitly provided, we infer the authType from env vars */
|
||||
getAuthTypeFromEnv();
|
||||
|
||||
|
|
@ -1005,7 +1025,10 @@ export async function loadCliConfig(
|
|||
|
||||
const { model: resolvedModel } = resolvedCliConfig;
|
||||
|
||||
const sandboxConfig = await loadSandboxConfig(settings, argv);
|
||||
const sandboxConfig = await loadSandboxConfig(
|
||||
bareMode ? ({} as Settings) : settings,
|
||||
argv,
|
||||
);
|
||||
const screenReader =
|
||||
argv.screenReader !== undefined
|
||||
? argv.screenReader
|
||||
|
|
@ -1054,16 +1077,21 @@ export async function loadCliConfig(
|
|||
sandbox: sandboxConfig,
|
||||
targetDir: cwd,
|
||||
includeDirectories,
|
||||
loadMemoryFromIncludeDirectories:
|
||||
settings.context?.loadFromIncludeDirectories || false,
|
||||
loadMemoryFromIncludeDirectories: bareMode
|
||||
? includeDirectories.length > 0
|
||||
: (settings.context?.loadFromIncludeDirectories ?? false),
|
||||
importFormat: settings.context?.importFormat || 'tree',
|
||||
debugMode,
|
||||
question,
|
||||
systemPrompt: argv.systemPrompt,
|
||||
appendSystemPrompt: argv.appendSystemPrompt,
|
||||
// Legacy fields – kept for backward compatibility with getCoreTools() etc.
|
||||
coreTools: argv.coreTools || settings.tools?.core || undefined,
|
||||
allowedTools: argv.allowedTools || settings.tools?.allowed || undefined,
|
||||
coreTools: bareMode
|
||||
? undefined
|
||||
: argv.coreTools || settings.tools?.core || undefined,
|
||||
allowedTools: bareMode
|
||||
? argv.allowedTools || undefined
|
||||
: argv.allowedTools || settings.tools?.allowed || undefined,
|
||||
excludeTools: mergedDeny,
|
||||
// New unified permissions (PermissionManager source of truth).
|
||||
permissions: {
|
||||
|
|
@ -1085,10 +1113,12 @@ export async function loadCliConfig(
|
|||
currentSettings.setValue(settingScope, key, [...currentRules, rule]);
|
||||
}
|
||||
},
|
||||
toolDiscoveryCommand: settings.tools?.discoveryCommand,
|
||||
toolCallCommand: settings.tools?.callCommand,
|
||||
mcpServerCommand: settings.mcp?.serverCommand,
|
||||
mcpServers: settings.mcpServers || {},
|
||||
toolDiscoveryCommand: bareMode
|
||||
? undefined
|
||||
: settings.tools?.discoveryCommand,
|
||||
toolCallCommand: bareMode ? undefined : settings.tools?.callCommand,
|
||||
mcpServerCommand: bareMode ? undefined : settings.mcp?.serverCommand,
|
||||
mcpServers: bareMode ? {} : settings.mcpServers || {},
|
||||
allowedMcpServers: allowedMcpServers
|
||||
? Array.from(allowedMcpServers)
|
||||
: undefined,
|
||||
|
|
@ -1133,9 +1163,14 @@ export async function loadCliConfig(
|
|||
generationConfigSources: resolvedCliConfig.sources,
|
||||
generationConfig: resolvedCliConfig.generationConfig,
|
||||
warnings: resolvedCliConfig.warnings,
|
||||
allowedHttpHookUrls: settings.security?.allowedHttpHookUrls ?? [],
|
||||
bareMode,
|
||||
allowedHttpHookUrls: bareMode
|
||||
? []
|
||||
: (settings.security?.allowedHttpHookUrls ?? []),
|
||||
cliVersion: await getCliVersion(),
|
||||
webSearch: buildWebSearchConfig(argv, settings, selectedAuthType),
|
||||
webSearch: bareMode
|
||||
? undefined
|
||||
: buildWebSearchConfig(argv, settings, selectedAuthType),
|
||||
ideMode,
|
||||
chatCompression: settings.model?.chatCompression,
|
||||
folderTrust,
|
||||
|
|
@ -1154,14 +1189,18 @@ export async function loadCliConfig(
|
|||
output: {
|
||||
format: outputSettingsFormat,
|
||||
},
|
||||
enableManagedAutoMemory: settings.memory?.enableManagedAutoMemory ?? true,
|
||||
enableManagedAutoMemory: bareMode
|
||||
? false
|
||||
: (settings.memory?.enableManagedAutoMemory ?? true),
|
||||
enableManagedAutoDream: settings.memory?.enableManagedAutoDream ?? false,
|
||||
fastModel: settings.fastModel || undefined,
|
||||
// Use separated hooks if provided, otherwise fall back to merged hooks
|
||||
userHooks: hooksConfig?.userHooks ?? settings.hooks,
|
||||
projectHooks: hooksConfig?.projectHooks,
|
||||
hooks: settings.hooks, // Keep for backward compatibility
|
||||
disableAllHooks: settings.disableAllHooks ?? false,
|
||||
userHooks: bareMode
|
||||
? undefined
|
||||
: (hooksConfig?.userHooks ?? settings.hooks),
|
||||
projectHooks: bareMode ? undefined : hooksConfig?.projectHooks,
|
||||
hooks: bareMode ? undefined : settings.hooks, // Keep for backward compatibility
|
||||
disableAllHooks: bareMode ? true : (settings.disableAllHooks ?? false),
|
||||
channel: argv.channel,
|
||||
// CLI flag wins over settings.json. `--json-fd` is fd-only (no settings
|
||||
// equivalent — fd passing is a spawn-time concern). `--json-file` and
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue