mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-14 08:01:02 -07:00
feat(hooks): Hook Input/Output Contracts (#9080)
This commit is contained in:
@@ -6,18 +6,11 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
import type {
|
||||
ConfigParameters,
|
||||
SandboxConfig,
|
||||
HookDefinition,
|
||||
} from './config.js';
|
||||
import {
|
||||
Config,
|
||||
DEFAULT_FILE_FILTERING_OPTIONS,
|
||||
HookType,
|
||||
HookEventName,
|
||||
} from './config.js';
|
||||
import type { ConfigParameters, SandboxConfig } from './config.js';
|
||||
import { Config, DEFAULT_FILE_FILTERING_OPTIONS } from './config.js';
|
||||
import { ApprovalMode } from '../policy/types.js';
|
||||
import type { HookDefinition } from '../hooks/types.js';
|
||||
import { HookType, HookEventName } from '../hooks/types.js';
|
||||
import * as path from 'node:path';
|
||||
import { setGeminiMdFilename as mockSetGeminiMdFilename } from '../tools/memoryTool.js';
|
||||
import {
|
||||
|
||||
@@ -32,6 +32,7 @@ import { MemoryTool, setGeminiMdFilename } from '../tools/memoryTool.js';
|
||||
import { WebSearchTool } from '../tools/web-search.js';
|
||||
import { GeminiClient } from '../core/client.js';
|
||||
import { BaseLlmClient } from '../core/baseLlmClient.js';
|
||||
import type { HookDefinition, HookEventName } from '../hooks/types.js';
|
||||
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||
import { GitService } from '../services/gitService.js';
|
||||
import type { TelemetryTarget } from '../telemetry/index.js';
|
||||
@@ -207,50 +208,6 @@ export interface SandboxConfig {
|
||||
image: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event names for the hook system
|
||||
*/
|
||||
export enum HookEventName {
|
||||
BeforeTool = 'BeforeTool',
|
||||
AfterTool = 'AfterTool',
|
||||
BeforeAgent = 'BeforeAgent',
|
||||
Notification = 'Notification',
|
||||
AfterAgent = 'AfterAgent',
|
||||
SessionStart = 'SessionStart',
|
||||
SessionEnd = 'SessionEnd',
|
||||
PreCompress = 'PreCompress',
|
||||
BeforeModel = 'BeforeModel',
|
||||
AfterModel = 'AfterModel',
|
||||
BeforeToolSelection = 'BeforeToolSelection',
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook configuration entry
|
||||
*/
|
||||
export interface CommandHookConfig {
|
||||
type: HookType.Command;
|
||||
command: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export type HookConfig = CommandHookConfig;
|
||||
|
||||
/**
|
||||
* Hook definition with matcher
|
||||
*/
|
||||
export interface HookDefinition {
|
||||
matcher?: string;
|
||||
sequential?: boolean;
|
||||
hooks: HookConfig[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook implementation types
|
||||
*/
|
||||
export enum HookType {
|
||||
Command = 'command',
|
||||
}
|
||||
|
||||
export interface ConfigParameters {
|
||||
sessionId: string;
|
||||
embeddingModel?: string;
|
||||
|
||||
38
packages/core/src/hooks/types.test.ts
Normal file
38
packages/core/src/hooks/types.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { HookEventName, HookType } from './types.js';
|
||||
|
||||
describe('Hook Types', () => {
|
||||
describe('HookEventName', () => {
|
||||
it('should contain all required event names', () => {
|
||||
const expectedEvents = [
|
||||
'BeforeTool',
|
||||
'AfterTool',
|
||||
'BeforeAgent',
|
||||
'Notification',
|
||||
'AfterAgent',
|
||||
'SessionStart',
|
||||
'SessionEnd',
|
||||
'PreCompress',
|
||||
'BeforeModel',
|
||||
'AfterModel',
|
||||
'BeforeToolSelection',
|
||||
];
|
||||
|
||||
for (const event of expectedEvents) {
|
||||
expect(Object.values(HookEventName)).toContain(event);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('HookType', () => {
|
||||
it('should contain command type', () => {
|
||||
expect(HookType.Command).toBe('command');
|
||||
});
|
||||
});
|
||||
});
|
||||
602
packages/core/src/hooks/types.ts
Normal file
602
packages/core/src/hooks/types.ts
Normal file
@@ -0,0 +1,602 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {
|
||||
GenerateContentResponse,
|
||||
GenerateContentParameters,
|
||||
ToolConfig as GenAIToolConfig,
|
||||
ToolListUnion,
|
||||
} from '@google/genai';
|
||||
import type {
|
||||
LLMRequest,
|
||||
LLMResponse,
|
||||
HookToolConfig,
|
||||
} from './hookTranslator.js';
|
||||
import { defaultHookTranslator } from './hookTranslator.js';
|
||||
|
||||
/**
|
||||
* Event names for the hook system
|
||||
*/
|
||||
export enum HookEventName {
|
||||
BeforeTool = 'BeforeTool',
|
||||
AfterTool = 'AfterTool',
|
||||
BeforeAgent = 'BeforeAgent',
|
||||
Notification = 'Notification',
|
||||
AfterAgent = 'AfterAgent',
|
||||
SessionStart = 'SessionStart',
|
||||
SessionEnd = 'SessionEnd',
|
||||
PreCompress = 'PreCompress',
|
||||
BeforeModel = 'BeforeModel',
|
||||
AfterModel = 'AfterModel',
|
||||
BeforeToolSelection = 'BeforeToolSelection',
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook configuration entry
|
||||
*/
|
||||
export interface CommandHookConfig {
|
||||
type: HookType.Command;
|
||||
command: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export type HookConfig = CommandHookConfig;
|
||||
|
||||
/**
|
||||
* Hook definition with matcher
|
||||
*/
|
||||
export interface HookDefinition {
|
||||
matcher?: string;
|
||||
sequential?: boolean;
|
||||
hooks: HookConfig[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook implementation types
|
||||
*/
|
||||
export enum HookType {
|
||||
Command = 'command',
|
||||
}
|
||||
|
||||
/**
|
||||
* Decision types for hook outputs
|
||||
*/
|
||||
export type HookDecision =
|
||||
| 'ask'
|
||||
| 'block'
|
||||
| 'deny'
|
||||
| 'approve'
|
||||
| 'allow'
|
||||
| undefined;
|
||||
|
||||
/**
|
||||
* Base hook input - common fields for all events
|
||||
*/
|
||||
export interface HookInput {
|
||||
session_id: string;
|
||||
transcript_path: string;
|
||||
cwd: string;
|
||||
hook_event_name: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base hook output - common fields for all events
|
||||
*/
|
||||
export interface HookOutput {
|
||||
continue?: boolean;
|
||||
stopReason?: string;
|
||||
suppressOutput?: boolean;
|
||||
systemMessage?: string;
|
||||
decision?: HookDecision;
|
||||
reason?: string;
|
||||
hookSpecificOutput?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to create the appropriate hook output class based on event name
|
||||
* Returns DefaultHookOutput for all events since it contains all necessary methods
|
||||
*/
|
||||
export function createHookOutput(
|
||||
eventName: string,
|
||||
data: Partial<HookOutput>,
|
||||
): DefaultHookOutput {
|
||||
switch (eventName) {
|
||||
case 'BeforeModel':
|
||||
return new BeforeModelHookOutput(data);
|
||||
case 'AfterModel':
|
||||
return new AfterModelHookOutput(data);
|
||||
case 'BeforeToolSelection':
|
||||
return new BeforeToolSelectionHookOutput(data);
|
||||
default:
|
||||
return new DefaultHookOutput(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of HookOutput with utility methods
|
||||
*/
|
||||
export class DefaultHookOutput implements HookOutput {
|
||||
continue?: boolean;
|
||||
stopReason?: string;
|
||||
suppressOutput?: boolean;
|
||||
systemMessage?: string;
|
||||
decision?: HookDecision;
|
||||
reason?: string;
|
||||
hookSpecificOutput?: Record<string, unknown>;
|
||||
|
||||
constructor(data: Partial<HookOutput> = {}) {
|
||||
this.continue = data.continue;
|
||||
this.stopReason = data.stopReason;
|
||||
this.suppressOutput = data.suppressOutput;
|
||||
this.systemMessage = data.systemMessage;
|
||||
this.decision = data.decision;
|
||||
this.reason = data.reason;
|
||||
this.hookSpecificOutput = data.hookSpecificOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this output represents a blocking decision
|
||||
*/
|
||||
isBlockingDecision(): boolean {
|
||||
return this.decision === 'block' || this.decision === 'deny';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this output requests to stop execution
|
||||
*/
|
||||
shouldStopExecution(): boolean {
|
||||
return this.continue === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective reason for blocking or stopping
|
||||
*/
|
||||
getEffectiveReason(): string {
|
||||
return this.reason || this.stopReason || 'No reason provided';
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply LLM request modifications (specific method for BeforeModel hooks)
|
||||
*/
|
||||
applyLLMRequestModifications(
|
||||
target: GenerateContentParameters,
|
||||
): GenerateContentParameters {
|
||||
// Base implementation - overridden by BeforeModelHookOutput
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply tool config modifications (specific method for BeforeToolSelection hooks)
|
||||
*/
|
||||
applyToolConfigModifications(target: {
|
||||
toolConfig?: GenAIToolConfig;
|
||||
tools?: ToolListUnion;
|
||||
}): {
|
||||
toolConfig?: GenAIToolConfig;
|
||||
tools?: ToolListUnion;
|
||||
} {
|
||||
// Base implementation - overridden by BeforeToolSelectionHookOutput
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional context for adding to responses
|
||||
*/
|
||||
getAdditionalContext(): string | undefined {
|
||||
if (
|
||||
this.hookSpecificOutput &&
|
||||
'additionalContext' in this.hookSpecificOutput
|
||||
) {
|
||||
const context = this.hookSpecificOutput['additionalContext'];
|
||||
return typeof context === 'string' ? context : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if execution should be blocked and return error info
|
||||
*/
|
||||
getBlockingError(): { blocked: boolean; reason: string } {
|
||||
if (this.isBlockingDecision()) {
|
||||
return {
|
||||
blocked: true,
|
||||
reason: this.getEffectiveReason(),
|
||||
};
|
||||
}
|
||||
return { blocked: false, reason: '' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific hook output class for BeforeTool events with compatibility support
|
||||
*/
|
||||
export class BeforeToolHookOutput extends DefaultHookOutput {
|
||||
/**
|
||||
* Get the effective blocking reason, considering compatibility fields
|
||||
*/
|
||||
override getEffectiveReason(): string {
|
||||
// Check for compatibility fields first
|
||||
if (this.hookSpecificOutput) {
|
||||
if ('permissionDecisionReason' in this.hookSpecificOutput) {
|
||||
const compatReason =
|
||||
this.hookSpecificOutput['permissionDecisionReason'];
|
||||
if (typeof compatReason === 'string') {
|
||||
return compatReason;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.getEffectiveReason();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this output represents a blocking decision, considering compatibility fields
|
||||
*/
|
||||
override isBlockingDecision(): boolean {
|
||||
// Check compatibility field first
|
||||
if (
|
||||
this.hookSpecificOutput &&
|
||||
'permissionDecision' in this.hookSpecificOutput
|
||||
) {
|
||||
const compatDecision = this.hookSpecificOutput['permissionDecision'];
|
||||
if (compatDecision === 'block' || compatDecision === 'deny') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.isBlockingDecision();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific hook output class for BeforeModel events
|
||||
*/
|
||||
export class BeforeModelHookOutput extends DefaultHookOutput {
|
||||
/**
|
||||
* Get synthetic LLM response if provided by hook
|
||||
*/
|
||||
getSyntheticResponse(): GenerateContentResponse | undefined {
|
||||
if (this.hookSpecificOutput && 'llm_response' in this.hookSpecificOutput) {
|
||||
const hookResponse = this.hookSpecificOutput[
|
||||
'llm_response'
|
||||
] as LLMResponse;
|
||||
if (hookResponse) {
|
||||
// Convert hook format to SDK format
|
||||
return defaultHookTranslator.fromHookLLMResponse(hookResponse);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply modifications to LLM request
|
||||
*/
|
||||
override applyLLMRequestModifications(
|
||||
target: GenerateContentParameters,
|
||||
): GenerateContentParameters {
|
||||
if (this.hookSpecificOutput && 'llm_request' in this.hookSpecificOutput) {
|
||||
const hookRequest = this.hookSpecificOutput[
|
||||
'llm_request'
|
||||
] as Partial<LLMRequest>;
|
||||
if (hookRequest) {
|
||||
// Convert hook format to SDK format
|
||||
const sdkRequest = defaultHookTranslator.fromHookLLMRequest(
|
||||
hookRequest as LLMRequest,
|
||||
target,
|
||||
);
|
||||
return {
|
||||
...target,
|
||||
...sdkRequest,
|
||||
};
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific hook output class for BeforeToolSelection events
|
||||
*/
|
||||
export class BeforeToolSelectionHookOutput extends DefaultHookOutput {
|
||||
/**
|
||||
* Apply tool configuration modifications
|
||||
*/
|
||||
override applyToolConfigModifications(target: {
|
||||
toolConfig?: GenAIToolConfig;
|
||||
tools?: ToolListUnion;
|
||||
}): { toolConfig?: GenAIToolConfig; tools?: ToolListUnion } {
|
||||
if (this.hookSpecificOutput && 'toolConfig' in this.hookSpecificOutput) {
|
||||
const hookToolConfig = this.hookSpecificOutput[
|
||||
'toolConfig'
|
||||
] as HookToolConfig;
|
||||
if (hookToolConfig) {
|
||||
// Convert hook format to SDK format
|
||||
const sdkToolConfig =
|
||||
defaultHookTranslator.fromHookToolConfig(hookToolConfig);
|
||||
return {
|
||||
...target,
|
||||
tools: target.tools || [],
|
||||
toolConfig: sdkToolConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific hook output class for AfterModel events
|
||||
*/
|
||||
export class AfterModelHookOutput extends DefaultHookOutput {
|
||||
/**
|
||||
* Get modified LLM response if provided by hook
|
||||
*/
|
||||
getModifiedResponse(): GenerateContentResponse | undefined {
|
||||
if (this.hookSpecificOutput && 'llm_response' in this.hookSpecificOutput) {
|
||||
const hookResponse = this.hookSpecificOutput[
|
||||
'llm_response'
|
||||
] as Partial<LLMResponse>;
|
||||
if (hookResponse?.candidates?.[0]?.content) {
|
||||
// Convert hook format to SDK format
|
||||
return defaultHookTranslator.fromHookLLMResponse(
|
||||
hookResponse as LLMResponse,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If hook wants to stop execution, create a synthetic stop response
|
||||
if (this.shouldStopExecution()) {
|
||||
const stopResponse: LLMResponse = {
|
||||
candidates: [
|
||||
{
|
||||
content: {
|
||||
role: 'model',
|
||||
parts: [this.getEffectiveReason() || 'Execution stopped by hook'],
|
||||
},
|
||||
finishReason: 'STOP',
|
||||
},
|
||||
],
|
||||
};
|
||||
return defaultHookTranslator.fromHookLLMResponse(stopResponse);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BeforeTool hook input
|
||||
*/
|
||||
export interface BeforeToolInput extends HookInput {
|
||||
tool_name: string;
|
||||
tool_input: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* BeforeTool hook output
|
||||
*/
|
||||
export interface BeforeToolOutput extends HookOutput {
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'BeforeTool';
|
||||
permissionDecision?: HookDecision;
|
||||
permissionDecisionReason?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* AfterTool hook input
|
||||
*/
|
||||
export interface AfterToolInput extends HookInput {
|
||||
tool_name: string;
|
||||
tool_input: Record<string, unknown>;
|
||||
tool_response: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* AfterTool hook output
|
||||
*/
|
||||
export interface AfterToolOutput extends HookOutput {
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'AfterTool';
|
||||
additionalContext?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* BeforeAgent hook input
|
||||
*/
|
||||
export interface BeforeAgentInput extends HookInput {
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* BeforeAgent hook output
|
||||
*/
|
||||
export interface BeforeAgentOutput extends HookOutput {
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'BeforeAgent';
|
||||
additionalContext?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification types
|
||||
*/
|
||||
export enum NotificationType {
|
||||
ToolPermission = 'ToolPermission',
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification hook input
|
||||
*/
|
||||
export interface NotificationInput extends HookInput {
|
||||
notification_type: NotificationType;
|
||||
message: string;
|
||||
details: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification hook output
|
||||
*/
|
||||
export interface NotificationOutput {
|
||||
suppressOutput?: boolean;
|
||||
systemMessage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AfterAgent hook input
|
||||
*/
|
||||
export interface AfterAgentInput extends HookInput {
|
||||
prompt: string;
|
||||
prompt_response: string;
|
||||
stop_hook_active: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SessionStart source types
|
||||
*/
|
||||
export enum SessionStartSource {
|
||||
Startup = 'startup',
|
||||
Resume = 'resume',
|
||||
Clear = 'clear',
|
||||
Compress = 'compress',
|
||||
}
|
||||
|
||||
/**
|
||||
* SessionStart hook input
|
||||
*/
|
||||
export interface SessionStartInput extends HookInput {
|
||||
source: SessionStartSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* SessionStart hook output
|
||||
*/
|
||||
export interface SessionStartOutput extends HookOutput {
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'SessionStart';
|
||||
additionalContext?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* SessionEnd reason types
|
||||
*/
|
||||
export enum SessionEndReason {
|
||||
Exit = 'exit',
|
||||
Clear = 'clear',
|
||||
Logout = 'logout',
|
||||
PromptInputExit = 'prompt_input_exit',
|
||||
Other = 'other',
|
||||
}
|
||||
|
||||
/**
|
||||
* SessionEnd hook input
|
||||
*/
|
||||
export interface SessionEndInput extends HookInput {
|
||||
reason: SessionEndReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* PreCompress trigger types
|
||||
*/
|
||||
export enum PreCompressTrigger {
|
||||
Manual = 'manual',
|
||||
Auto = 'auto',
|
||||
}
|
||||
|
||||
/**
|
||||
* PreCompress hook input
|
||||
*/
|
||||
export interface PreCompressInput extends HookInput {
|
||||
trigger: PreCompressTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* PreCompress hook output
|
||||
*/
|
||||
export interface PreCompressOutput {
|
||||
suppressOutput?: boolean;
|
||||
systemMessage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* BeforeModel hook input - uses decoupled types
|
||||
*/
|
||||
export interface BeforeModelInput extends HookInput {
|
||||
llm_request: LLMRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* BeforeModel hook output
|
||||
*/
|
||||
export interface BeforeModelOutput extends HookOutput {
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'BeforeModel';
|
||||
llm_request?: Partial<LLMRequest>;
|
||||
llm_response?: LLMResponse;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* AfterModel hook input - uses decoupled types
|
||||
*/
|
||||
export interface AfterModelInput extends HookInput {
|
||||
llm_request: LLMRequest;
|
||||
llm_response: LLMResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* AfterModel hook output
|
||||
*/
|
||||
export interface AfterModelOutput extends HookOutput {
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'AfterModel';
|
||||
llm_response?: Partial<LLMResponse>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* BeforeToolSelection hook input - uses decoupled types
|
||||
*/
|
||||
export interface BeforeToolSelectionInput extends HookInput {
|
||||
llm_request: LLMRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* BeforeToolSelection hook output
|
||||
*/
|
||||
export interface BeforeToolSelectionOutput extends HookOutput {
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'BeforeToolSelection';
|
||||
toolConfig?: HookToolConfig;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook execution result
|
||||
*/
|
||||
export interface HookExecutionResult {
|
||||
hookConfig: HookConfig;
|
||||
eventName: HookEventName;
|
||||
success: boolean;
|
||||
output?: HookOutput;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
exitCode?: number;
|
||||
duration: number;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook execution plan for an event
|
||||
*/
|
||||
export interface HookExecutionPlan {
|
||||
eventName: HookEventName;
|
||||
hookConfigs: HookConfig[];
|
||||
sequential: boolean;
|
||||
}
|
||||
@@ -138,3 +138,6 @@ export { Storage } from './config/storage.js';
|
||||
|
||||
// Export test utils
|
||||
export * from './test-utils/index.js';
|
||||
|
||||
// Export hook types
|
||||
export * from './hooks/types.js';
|
||||
|
||||
Reference in New Issue
Block a user