Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0

This commit is contained in:
mingholy.lmh
2025-09-10 21:01:40 +08:00
583 changed files with 30160 additions and 10770 deletions

View File

@@ -4,33 +4,35 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { promises as fs } from 'fs';
import path from 'path';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import toml from '@iarna/toml';
import { glob } from 'glob';
import { z } from 'zod';
import {
Config,
getProjectCommandsDir,
getUserCommandsDir,
} from '@qwen-code/qwen-code-core';
import { ICommandLoader } from './types.js';
import {
import type { Config } from '@qwen-code/qwen-code-core';
import { Storage } from '@qwen-code/qwen-code-core';
import type { ICommandLoader } from './types.js';
import type {
CommandContext,
CommandKind,
SlashCommand,
SlashCommandActionReturn,
} from '../ui/commands/types.js';
import { CommandKind } from '../ui/commands/types.js';
import { DefaultArgumentProcessor } from './prompt-processors/argumentProcessor.js';
import {
import type {
IPromptProcessor,
PromptPipelineContent,
} from './prompt-processors/types.js';
import {
SHORTHAND_ARGS_PLACEHOLDER,
SHELL_INJECTION_TRIGGER,
AT_FILE_INJECTION_TRIGGER,
} from './prompt-processors/types.js';
import {
ConfirmationRequiredError,
ShellProcessor,
} from './prompt-processors/shellProcessor.js';
import { AtFileProcessor } from './prompt-processors/atFileProcessor.js';
interface CommandDirectory {
path: string;
@@ -130,11 +132,13 @@ export class FileCommandLoader implements ICommandLoader {
private getCommandDirectories(): CommandDirectory[] {
const dirs: CommandDirectory[] = [];
const storage = this.config?.storage ?? new Storage(this.projectRoot);
// 1. User commands
dirs.push({ path: getUserCommandsDir() });
dirs.push({ path: Storage.getUserCommandsDir() });
// 2. Project commands (override user commands)
dirs.push({ path: getProjectCommandsDir(this.projectRoot) });
dirs.push({ path: storage.getProjectCommandsDir() });
// 3. Extension commands (processed last to detect all conflicts)
if (this.config) {
@@ -225,16 +229,25 @@ export class FileCommandLoader implements ICommandLoader {
const usesShellInjection = validDef.prompt.includes(
SHELL_INJECTION_TRIGGER,
);
const usesAtFileInjection = validDef.prompt.includes(
AT_FILE_INJECTION_TRIGGER,
);
// Interpolation (Shell Execution and Argument Injection)
// If the prompt uses either shell injection OR argument placeholders,
// we must use the ShellProcessor.
// 1. @-File Injection (Security First).
// This runs first to ensure we're not executing shell commands that
// could dynamically generate malicious @-paths.
if (usesAtFileInjection) {
processors.push(new AtFileProcessor(baseCommandName));
}
// 2. Argument and Shell Injection.
// This runs after file content has been safely injected.
if (usesShellInjection || usesArgs) {
processors.push(new ShellProcessor(baseCommandName));
}
// Default Argument Handling
// If NO explicit argument injection ({{args}}) was used, we append the raw invocation.
// 3. Default Argument Handling.
// Appends the raw invocation if no explicit {{args}} are used.
if (!usesArgs) {
processors.push(new DefaultArgumentProcessor());
}
@@ -254,19 +267,24 @@ export class FileCommandLoader implements ICommandLoader {
);
return {
type: 'submit_prompt',
content: validDef.prompt, // Fallback to unprocessed prompt
content: [{ text: validDef.prompt }], // Fallback to unprocessed prompt
};
}
try {
let processedPrompt = validDef.prompt;
let processedContent: PromptPipelineContent = [
{ text: validDef.prompt },
];
for (const processor of processors) {
processedPrompt = await processor.process(processedPrompt, context);
processedContent = await processor.process(
processedContent,
context,
);
}
return {
type: 'submit_prompt',
content: processedPrompt,
content: processedContent,
};
} catch (e) {
// Check if it's our specific error type