Merge pull request #1988 from QwenLM/feat/hook-stop-implementation
Some checks are pending
Qwen Code CI / CodeQL (push) Waiting to run
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
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(hooks): Implement hooks system infrastructure with CLI and UI management
This commit is contained in:
DennisYu07 2026-03-06 12:59:16 +08:00 committed by GitHub
commit 79939e8ebe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 8418 additions and 9 deletions

View file

@ -33,6 +33,7 @@ import {
NativeLspService,
} from '@qwen-code/qwen-code-core';
import { extensionsCommand } from '../commands/extensions.js';
import { hooksCommand } from '../commands/hooks.js';
import type { Settings } from './settings.js';
import {
resolveCliGenerationConfig,
@ -124,6 +125,7 @@ export interface CliArgs {
acp: boolean | undefined;
experimentalAcp: boolean | undefined;
experimentalLsp: boolean | undefined;
experimentalHooks: boolean | undefined;
extensions: string[] | undefined;
listExtensions: boolean | undefined;
openaiLogging: boolean | undefined;
@ -337,6 +339,12 @@ export async function parseArguments(): Promise<CliArgs> {
'Enable experimental LSP (Language Server Protocol) feature for code intelligence',
default: false,
})
.option('experimental-hooks', {
type: 'boolean',
description:
'Enable experimental hooks feature for lifecycle event customization',
default: false,
})
.option('channel', {
type: 'string',
choices: ['VSCode', 'ACP', 'SDK', 'CI'],
@ -561,7 +569,9 @@ export async function parseArguments(): Promise<CliArgs> {
// Register MCP subcommands
.command(mcpCommand)
// Register Extension subcommands
.command(extensionsCommand);
.command(extensionsCommand)
// Register Hooks subcommands
.command(hooksCommand);
yargsInstance
.version(await getCliVersion()) // This will enable the --version flag based on package.json
@ -580,9 +590,11 @@ export async function parseArguments(): Promise<CliArgs> {
// and not return to main CLI logic
if (
result._.length > 0 &&
(result._[0] === 'mcp' || result._[0] === 'extensions')
(result._[0] === 'mcp' ||
result._[0] === 'extensions' ||
result._[0] === 'hooks')
) {
// MCP commands handle their own execution and process exit
// MCP/Extensions/Hooks commands handle their own execution and process exit
process.exit(0);
}
@ -1021,6 +1033,10 @@ export async function loadCliConfig(
output: {
format: outputSettingsFormat,
},
hooks: settings.hooks,
hooksConfig: settings.hooksConfig,
enableHooks:
argv.experimentalHooks === true || settings.hooksConfig?.enabled === true,
channel: argv.channel,
// Precedence: explicit CLI flag > settings file > default(true).
// NOTE: do NOT set a yargs default for `chat-recording`, otherwise argv will

View file

@ -1176,6 +1176,75 @@ const SETTINGS_SCHEMA = {
description: 'Configuration for web search providers.',
showInDialog: false,
},
hooksConfig: {
type: 'object',
label: 'Hooks Config',
category: 'Advanced',
requiresRestart: false,
default: {},
description:
'Hook configurations for intercepting and customizing agent behavior.',
showInDialog: false,
properties: {
enabled: {
type: 'boolean',
label: 'Enable Hooks',
category: 'Advanced',
requiresRestart: true,
default: true,
description:
'Canonical toggle for the hooks system. When disabled, no hooks will be executed.',
showInDialog: false,
},
disabled: {
type: 'array',
label: 'Disabled Hooks',
category: 'Advanced',
requiresRestart: false,
default: [] as string[],
description:
'List of hook names (commands) that should be disabled. Hooks in this list will not execute even if configured.',
showInDialog: false,
mergeStrategy: MergeStrategy.UNION,
},
},
},
hooks: {
type: 'object',
label: 'Hooks',
category: 'Advanced',
requiresRestart: false,
default: {},
description:
'Hook event configurations for extending CLI behavior at various lifecycle points.',
showInDialog: false,
properties: {
UserPromptSubmit: {
type: 'array',
label: 'Before Agent Hooks',
category: 'Advanced',
requiresRestart: false,
default: [],
description:
'Hooks that execute before agent processing. Can modify prompts or inject context.',
showInDialog: false,
mergeStrategy: MergeStrategy.CONCAT,
},
Stop: {
type: 'array',
label: 'After Agent Hooks',
category: 'Advanced',
requiresRestart: false,
default: [],
description:
'Hooks that execute after agent processing. Can post-process responses or log interactions.',
showInDialog: false,
mergeStrategy: MergeStrategy.CONCAT,
},
},
},
} as const satisfies SettingsSchema;
export type SettingsSchemaType = typeof SETTINGS_SCHEMA;