Merge branch 'main' into feat/support-permission

This commit is contained in:
LaZzyMan 2026-03-19 19:08:55 +08:00
commit b59864f554
57 changed files with 14025 additions and 619 deletions

View file

@ -36,6 +36,8 @@ import { RipGrepTool } from '../tools/ripGrep.js';
import { logRipgrepFallback } from '../telemetry/loggers.js';
import { RipgrepFallbackEvent } from '../telemetry/types.js';
import { ToolRegistry } from '../tools/tool-registry.js';
import { fireNotificationHook } from '../core/toolHookTriggers.js';
import type { MessageBus } from '../confirmation-bus/message-bus.js';
function createToolMock(toolName: string) {
const ToolMock = vi.fn();
@ -195,6 +197,10 @@ vi.mock('../ide/ide-client.js', () => ({
import { BaseLlmClient } from '../core/baseLlmClient.js';
vi.mock('../core/baseLlmClient.js');
// Mock fireNotificationHook from toolHookTriggers
vi.mock('../core/toolHookTriggers.js', () => ({
fireNotificationHook: vi.fn().mockResolvedValue({}),
}));
describe('Server Config (config.ts)', () => {
const MODEL = 'qwen3-coder-plus';
@ -337,6 +343,64 @@ describe('Server Config (config.ts)', () => {
expect(GeminiClient).toHaveBeenCalledWith(config);
});
it('should fire auth_success notification hook when hooks are enabled', async () => {
const mockMessageBus = { request: vi.fn() };
const config = new Config({
...baseParams,
enableHooks: true,
});
// Set messageBus using the setter
config.setMessageBus(mockMessageBus as unknown as MessageBus);
const authType = AuthType.USE_GEMINI;
const mockContentConfig = {
apiKey: 'test-key',
model: 'qwen3-coder-plus',
authType,
};
vi.mocked(resolveContentGeneratorConfigWithSources).mockReturnValue({
config: mockContentConfig as ContentGeneratorConfig,
sources: {},
});
await config.refreshAuth(authType);
// Verify that fireNotificationHook was called with correct parameters
expect(fireNotificationHook).toHaveBeenCalledWith(
mockMessageBus,
`Successfully authenticated with ${authType}`,
'auth_success',
'Authentication successful',
);
});
it('should not fire notification hook when hooks are disabled', async () => {
const config = new Config({
...baseParams,
enableHooks: false,
});
const authType = AuthType.USE_GEMINI;
const mockContentConfig = {
apiKey: 'test-key',
model: 'qwen3-coder-plus',
authType,
};
vi.mocked(resolveContentGeneratorConfigWithSources).mockReturnValue({
config: mockContentConfig as ContentGeneratorConfig,
sources: {},
});
// Clear any previous calls
vi.mocked(fireNotificationHook).mockClear();
await config.refreshAuth(authType);
// Verify that fireNotificationHook was not called
expect(fireNotificationHook).not.toHaveBeenCalled();
});
it('should not strip thoughts when switching from Vertex to GenAI', async () => {
const config = new Config(baseParams);

View file

@ -94,6 +94,12 @@ import {
type HookExecutionRequest,
type HookExecutionResponse,
} from '../confirmation-bus/types.js';
import {
PermissionMode,
NotificationType,
type PermissionSuggestion,
} from '../hooks/types.js';
import { fireNotificationHook } from '../core/toolHookTriggers.js';
// Utils
import { shouldAttemptBrowserLaunch } from '../utils/browser.js';
@ -819,6 +825,73 @@ export class Config {
(input['last_assistant_message'] as string) || '',
);
break;
case 'PreToolUse': {
result = await hookSystem.firePreToolUseEvent(
(input['tool_name'] as string) || '',
(input['tool_input'] as Record<string, unknown>) || {},
(input['tool_use_id'] as string) || '',
(input['permission_mode'] as PermissionMode | undefined) ??
PermissionMode.Default,
);
break;
}
case 'PostToolUse':
result = await hookSystem.firePostToolUseEvent(
(input['tool_name'] as string) || '',
(input['tool_input'] as Record<string, unknown>) || {},
(input['tool_response'] as Record<string, unknown>) || {},
(input['tool_use_id'] as string) || '',
(input['permission_mode'] as PermissionMode) || 'default',
);
break;
case 'PostToolUseFailure':
result = await hookSystem.firePostToolUseFailureEvent(
(input['tool_use_id'] as string) || '',
(input['tool_name'] as string) || '',
(input['tool_input'] as Record<string, unknown>) || {},
(input['error'] as string) || '',
input['is_interrupt'] as boolean | undefined,
(input['permission_mode'] as PermissionMode) || 'default',
);
break;
case 'Notification':
result = await hookSystem.fireNotificationEvent(
(input['message'] as string) || '',
(input['notification_type'] as NotificationType) ||
'permission_prompt',
(input['title'] as string) || undefined,
);
break;
case 'PermissionRequest':
result = await hookSystem.firePermissionRequestEvent(
(input['tool_name'] as string) || '',
(input['tool_input'] as Record<string, unknown>) || {},
(input['permission_mode'] as PermissionMode) ||
PermissionMode.Default,
(input['permission_suggestions'] as
| PermissionSuggestion[]
| undefined) || undefined,
);
break;
case 'SubagentStart':
result = await hookSystem.fireSubagentStartEvent(
(input['agent_id'] as string) || '',
(input['agent_type'] as string) || '',
(input['permission_mode'] as PermissionMode) ||
PermissionMode.Default,
);
break;
case 'SubagentStop':
result = await hookSystem.fireSubagentStopEvent(
(input['agent_id'] as string) || '',
(input['agent_type'] as string) || '',
(input['agent_transcript_path'] as string) || '',
(input['last_assistant_message'] as string) || '',
(input['stop_hook_active'] as boolean) || false,
(input['permission_mode'] as PermissionMode) ||
PermissionMode.Default,
);
break;
default:
this.debugLogger.warn(
`Unknown hook event: ${request.eventName}`,
@ -846,6 +919,8 @@ export class Config {
);
this.debugLogger.debug('MessageBus initialized with hook subscription');
} else {
this.debugLogger.debug('Hook system disabled, skipping initialization');
}
this.subagentManager = new SubagentManager(this);
@ -973,6 +1048,21 @@ export class Config {
// Initialize BaseLlmClient now that the ContentGenerator is available
this.baseLlmClient = new BaseLlmClient(this.contentGenerator, this);
// Fire auth_success notification hook (supports both interactive & non-interactive)
const messageBus = this.getMessageBus();
const hooksEnabled = this.getEnableHooks();
if (hooksEnabled && messageBus) {
fireNotificationHook(
messageBus,
`Successfully authenticated with ${authMethod}`,
NotificationType.AuthSuccess,
'Authentication successful',
).catch(() => {
// Silently ignore errors - fireNotificationHook has internal error handling
// and notification hooks should not block the auth flow
});
}
}
/**