@google/gemini-cli-core 0.15.0-preview.3 → 0.16.0-nightly.20251113.ad1f0d99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/google-gemini-cli-core-0.16.0-nightly.20251112.c961f274.tgz +0 -0
- package/dist/src/agents/codebase-investigator.test.d.ts +6 -0
- package/dist/src/agents/codebase-investigator.test.js +35 -0
- package/dist/src/agents/codebase-investigator.test.js.map +1 -0
- package/dist/src/agents/executor.test.js +181 -1
- package/dist/src/agents/executor.test.js.map +1 -1
- package/dist/src/code_assist/codeAssist.test.d.ts +6 -0
- package/dist/src/code_assist/codeAssist.test.js +99 -0
- package/dist/src/code_assist/codeAssist.test.js.map +1 -0
- package/dist/src/code_assist/experiments/client_metadata.js +2 -1
- package/dist/src/code_assist/experiments/client_metadata.js.map +1 -1
- package/dist/src/code_assist/experiments/client_metadata.test.d.ts +6 -0
- package/dist/src/code_assist/experiments/client_metadata.test.js +99 -0
- package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -0
- package/dist/src/code_assist/experiments/experiments.test.d.ts +6 -0
- package/dist/src/code_assist/experiments/experiments.test.js +92 -0
- package/dist/src/code_assist/experiments/experiments.test.js.map +1 -0
- package/dist/src/code_assist/oauth-credential-storage.test.js +49 -0
- package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
- package/dist/src/code_assist/server.js +5 -8
- package/dist/src/code_assist/server.js.map +1 -1
- package/dist/src/code_assist/server.test.js +109 -28
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/config/defaultModelConfigs.js +6 -0
- package/dist/src/config/defaultModelConfigs.js.map +1 -1
- package/dist/src/confirmation-bus/message-bus.d.ts +1 -1
- package/dist/src/confirmation-bus/message-bus.js +2 -2
- package/dist/src/confirmation-bus/message-bus.js.map +1 -1
- package/dist/src/confirmation-bus/message-bus.test.js +30 -24
- package/dist/src/confirmation-bus/message-bus.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.test.d.ts +6 -0
- package/dist/src/core/loggingContentGenerator.test.js +180 -0
- package/dist/src/core/loggingContentGenerator.test.js.map +1 -0
- package/dist/src/core/tokenLimits.test.d.ts +6 -0
- package/dist/src/core/tokenLimits.test.js +26 -0
- package/dist/src/core/tokenLimits.test.js.map +1 -0
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/hooks/hookAggregator.d.ts +68 -0
- package/dist/src/hooks/hookAggregator.js +262 -0
- package/dist/src/hooks/hookAggregator.js.map +1 -0
- package/dist/src/hooks/hookAggregator.test.d.ts +6 -0
- package/dist/src/hooks/hookAggregator.test.js +387 -0
- package/dist/src/hooks/hookAggregator.test.js.map +1 -0
- package/dist/src/hooks/types.js +1 -1
- package/dist/src/hooks/types.js.map +1 -1
- package/dist/src/hooks/types.test.js +280 -2
- package/dist/src/hooks/types.test.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +159 -0
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +177 -0
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/policy/config.js +3 -1
- package/dist/src/policy/config.js.map +1 -1
- package/dist/src/policy/config.test.js +118 -1
- package/dist/src/policy/config.test.js.map +1 -1
- package/dist/src/policy/policies/write.toml +10 -0
- package/dist/src/policy/policy-engine.d.ts +12 -3
- package/dist/src/policy/policy-engine.js +61 -7
- package/dist/src/policy/policy-engine.js.map +1 -1
- package/dist/src/policy/policy-engine.test.js +422 -86
- package/dist/src/policy/policy-engine.test.js.map +1 -1
- package/dist/src/policy/toml-loader.d.ts +2 -1
- package/dist/src/policy/toml-loader.js +103 -6
- package/dist/src/policy/toml-loader.js.map +1 -1
- package/dist/src/policy/toml-loader.test.js +32 -88
- package/dist/src/policy/toml-loader.test.js.map +1 -1
- package/dist/src/policy/types.d.ts +65 -0
- package/dist/src/policy/types.js +4 -0
- package/dist/src/policy/types.js.map +1 -1
- package/dist/src/prompts/mcp-prompts.test.d.ts +6 -0
- package/dist/src/prompts/mcp-prompts.test.js +40 -0
- package/dist/src/prompts/mcp-prompts.test.js.map +1 -0
- package/dist/src/prompts/prompt-registry.test.d.ts +6 -0
- package/dist/src/prompts/prompt-registry.test.js +111 -0
- package/dist/src/prompts/prompt-registry.test.js.map +1 -0
- package/dist/src/safety/built-in.d.ts +21 -0
- package/dist/src/safety/built-in.js +106 -0
- package/dist/src/safety/built-in.js.map +1 -0
- package/dist/src/safety/built-in.test.d.ts +6 -0
- package/dist/src/safety/built-in.test.js +199 -0
- package/dist/src/safety/built-in.test.js.map +1 -0
- package/dist/src/safety/checker-runner.d.ts +48 -0
- package/dist/src/safety/checker-runner.js +208 -0
- package/dist/src/safety/checker-runner.js.map +1 -0
- package/dist/src/safety/checker-runner.test.d.ts +6 -0
- package/dist/src/safety/checker-runner.test.js +238 -0
- package/dist/src/safety/checker-runner.test.js.map +1 -0
- package/dist/src/safety/context-builder.d.ts +23 -0
- package/dist/src/safety/context-builder.js +47 -0
- package/dist/src/safety/context-builder.js.map +1 -0
- package/dist/src/safety/context-builder.test.d.ts +6 -0
- package/dist/src/safety/context-builder.test.js +49 -0
- package/dist/src/safety/context-builder.test.js.map +1 -0
- package/dist/src/safety/protocol.d.ts +88 -0
- package/dist/src/safety/protocol.js +15 -0
- package/dist/src/safety/protocol.js.map +1 -0
- package/dist/src/safety/registry.d.ts +26 -0
- package/dist/src/safety/registry.js +65 -0
- package/dist/src/safety/registry.js.map +1 -0
- package/dist/src/safety/registry.test.d.ts +6 -0
- package/dist/src/safety/registry.test.js +31 -0
- package/dist/src/safety/registry.test.js.map +1 -0
- package/dist/src/services/loopDetectionService.d.ts +3 -0
- package/dist/src/services/loopDetectionService.js +81 -41
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +96 -4
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/test-data/resolved-aliases.golden.json +7 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +4 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +29 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +31 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +5 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +12 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +2 -1
- package/dist/src/telemetry/loggers.js +11 -0
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +15 -2
- package/dist/src/telemetry/types.js +42 -3
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/tools/base-tool-invocation.test.js +2 -2
- package/dist/src/tools/base-tool-invocation.test.js.map +1 -1
- package/dist/src/tools/memoryTool.js +1 -1
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/memoryTool.test.js +1 -1
- package/dist/src/tools/memoryTool.test.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +24 -7
- package/dist/src/tools/ripGrep.js +125 -145
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/ripGrep.test.js +144 -20
- package/dist/src/tools/ripGrep.test.js.map +1 -1
- package/dist/src/tools/tools.js +1 -1
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/write-todos.js +1 -1
- package/dist/src/tools/write-todos.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.test.js +8 -1
- package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/google-gemini-cli-core-0.15.0-preview.2.tgz +0 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { FunctionCallingConfigMode } from '@google/genai';
|
|
7
|
+
import { DefaultHookOutput, BeforeToolHookOutput, BeforeModelHookOutput, BeforeToolSelectionHookOutput, AfterModelHookOutput, } from './types.js';
|
|
8
|
+
import { HookEventName } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Hook aggregator that merges results from multiple hooks using event-specific strategies
|
|
11
|
+
*/
|
|
12
|
+
export class HookAggregator {
|
|
13
|
+
/**
|
|
14
|
+
* Aggregate results from multiple hook executions
|
|
15
|
+
*/
|
|
16
|
+
aggregateResults(results, eventName) {
|
|
17
|
+
const allOutputs = [];
|
|
18
|
+
const errors = [];
|
|
19
|
+
let totalDuration = 0;
|
|
20
|
+
// Collect all outputs and errors
|
|
21
|
+
for (const result of results) {
|
|
22
|
+
totalDuration += result.duration;
|
|
23
|
+
if (result.error) {
|
|
24
|
+
errors.push(result.error);
|
|
25
|
+
}
|
|
26
|
+
if (result.output) {
|
|
27
|
+
allOutputs.push(result.output);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Merge outputs using event-specific strategy
|
|
31
|
+
const mergedOutput = this.mergeOutputs(allOutputs, eventName);
|
|
32
|
+
const finalOutput = mergedOutput
|
|
33
|
+
? this.createSpecificHookOutput(mergedOutput, eventName)
|
|
34
|
+
: undefined;
|
|
35
|
+
return {
|
|
36
|
+
success: errors.length === 0,
|
|
37
|
+
finalOutput,
|
|
38
|
+
allOutputs,
|
|
39
|
+
errors,
|
|
40
|
+
totalDuration,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Merge hook outputs using event-specific strategies
|
|
45
|
+
*
|
|
46
|
+
* Note: We always use the merge logic even for single hooks to ensure
|
|
47
|
+
* consistent default behaviors (e.g., default decision='allow' for OR logic)
|
|
48
|
+
*/
|
|
49
|
+
mergeOutputs(outputs, eventName) {
|
|
50
|
+
if (outputs.length === 0) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
switch (eventName) {
|
|
54
|
+
case HookEventName.BeforeTool:
|
|
55
|
+
case HookEventName.AfterTool:
|
|
56
|
+
case HookEventName.BeforeAgent:
|
|
57
|
+
case HookEventName.AfterAgent:
|
|
58
|
+
case HookEventName.SessionStart:
|
|
59
|
+
return this.mergeWithOrDecision(outputs);
|
|
60
|
+
case HookEventName.BeforeModel:
|
|
61
|
+
case HookEventName.AfterModel:
|
|
62
|
+
return this.mergeWithFieldReplacement(outputs);
|
|
63
|
+
case HookEventName.BeforeToolSelection:
|
|
64
|
+
return this.mergeToolSelectionOutputs(outputs);
|
|
65
|
+
default:
|
|
66
|
+
// For other events, use simple merge
|
|
67
|
+
return this.mergeSimple(outputs);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Merge outputs with OR decision logic and message concatenation
|
|
72
|
+
*/
|
|
73
|
+
mergeWithOrDecision(outputs) {
|
|
74
|
+
const merged = {
|
|
75
|
+
continue: true,
|
|
76
|
+
suppressOutput: false,
|
|
77
|
+
};
|
|
78
|
+
const messages = [];
|
|
79
|
+
const reasons = [];
|
|
80
|
+
const systemMessages = [];
|
|
81
|
+
const additionalContexts = [];
|
|
82
|
+
let hasBlockDecision = false;
|
|
83
|
+
let hasContinueFalse = false;
|
|
84
|
+
for (const output of outputs) {
|
|
85
|
+
// Handle continue flag
|
|
86
|
+
if (output.continue === false) {
|
|
87
|
+
hasContinueFalse = true;
|
|
88
|
+
merged.continue = false;
|
|
89
|
+
if (output.stopReason) {
|
|
90
|
+
messages.push(output.stopReason);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Handle decision (OR logic for blocking)
|
|
94
|
+
const tempOutput = new DefaultHookOutput(output);
|
|
95
|
+
if (tempOutput.isBlockingDecision()) {
|
|
96
|
+
hasBlockDecision = true;
|
|
97
|
+
merged.decision = output.decision;
|
|
98
|
+
}
|
|
99
|
+
// Collect messages
|
|
100
|
+
if (output.reason) {
|
|
101
|
+
reasons.push(output.reason);
|
|
102
|
+
}
|
|
103
|
+
if (output.systemMessage) {
|
|
104
|
+
systemMessages.push(output.systemMessage);
|
|
105
|
+
}
|
|
106
|
+
// Handle suppress output (any true wins)
|
|
107
|
+
if (output.suppressOutput) {
|
|
108
|
+
merged.suppressOutput = true;
|
|
109
|
+
}
|
|
110
|
+
// Collect additional context from hook-specific outputs
|
|
111
|
+
this.extractAdditionalContext(output, additionalContexts);
|
|
112
|
+
}
|
|
113
|
+
// Set final decision if no blocking decision was found
|
|
114
|
+
if (!hasBlockDecision && !hasContinueFalse) {
|
|
115
|
+
merged.decision = 'allow';
|
|
116
|
+
}
|
|
117
|
+
// Merge messages
|
|
118
|
+
if (messages.length > 0) {
|
|
119
|
+
merged.stopReason = messages.join('\n');
|
|
120
|
+
}
|
|
121
|
+
if (reasons.length > 0) {
|
|
122
|
+
merged.reason = reasons.join('\n');
|
|
123
|
+
}
|
|
124
|
+
if (systemMessages.length > 0) {
|
|
125
|
+
merged.systemMessage = systemMessages.join('\n');
|
|
126
|
+
}
|
|
127
|
+
// Add merged additional context
|
|
128
|
+
if (additionalContexts.length > 0) {
|
|
129
|
+
merged.hookSpecificOutput = {
|
|
130
|
+
...(merged.hookSpecificOutput || {}),
|
|
131
|
+
additionalContext: additionalContexts.join('\n'),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return merged;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Merge outputs with later fields replacing earlier fields
|
|
138
|
+
*/
|
|
139
|
+
mergeWithFieldReplacement(outputs) {
|
|
140
|
+
let merged = {};
|
|
141
|
+
for (const output of outputs) {
|
|
142
|
+
// Later outputs override earlier ones
|
|
143
|
+
merged = {
|
|
144
|
+
...merged,
|
|
145
|
+
...output,
|
|
146
|
+
hookSpecificOutput: {
|
|
147
|
+
...merged.hookSpecificOutput,
|
|
148
|
+
...output.hookSpecificOutput,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return merged;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Merge tool selection outputs with specific logic for tool config
|
|
156
|
+
*
|
|
157
|
+
* Tool Selection Strategy:
|
|
158
|
+
* - The intent is to provide a UNION of tools from all hooks
|
|
159
|
+
* - If any hook specifies NONE mode, no tools are available (most restrictive wins)
|
|
160
|
+
* - If any hook specifies ANY mode (and no NONE), ANY mode is used
|
|
161
|
+
* - Otherwise AUTO mode is used
|
|
162
|
+
* - Function names are collected from all hooks and sorted for deterministic caching
|
|
163
|
+
*
|
|
164
|
+
* This means hooks can only add/enable tools, not filter them out individually.
|
|
165
|
+
* If one hook restricts and another re-enables, the union takes the re-enabled tool.
|
|
166
|
+
*/
|
|
167
|
+
mergeToolSelectionOutputs(outputs) {
|
|
168
|
+
const merged = {};
|
|
169
|
+
const allFunctionNames = new Set();
|
|
170
|
+
let hasNoneMode = false;
|
|
171
|
+
let hasAnyMode = false;
|
|
172
|
+
for (const output of outputs) {
|
|
173
|
+
const toolConfig = output.hookSpecificOutput?.toolConfig;
|
|
174
|
+
if (!toolConfig) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
// Check mode (using simplified HookToolConfig format)
|
|
178
|
+
if (toolConfig.mode === 'NONE') {
|
|
179
|
+
hasNoneMode = true;
|
|
180
|
+
}
|
|
181
|
+
else if (toolConfig.mode === 'ANY') {
|
|
182
|
+
hasAnyMode = true;
|
|
183
|
+
}
|
|
184
|
+
// Collect function names (union of all hooks)
|
|
185
|
+
if (toolConfig.allowedFunctionNames) {
|
|
186
|
+
for (const name of toolConfig.allowedFunctionNames) {
|
|
187
|
+
allFunctionNames.add(name);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Determine final mode and function names
|
|
192
|
+
let finalMode;
|
|
193
|
+
let finalFunctionNames = [];
|
|
194
|
+
if (hasNoneMode) {
|
|
195
|
+
// NONE mode wins - most restrictive
|
|
196
|
+
finalMode = FunctionCallingConfigMode.NONE;
|
|
197
|
+
finalFunctionNames = [];
|
|
198
|
+
}
|
|
199
|
+
else if (hasAnyMode) {
|
|
200
|
+
// ANY mode if present (and no NONE)
|
|
201
|
+
finalMode = FunctionCallingConfigMode.ANY;
|
|
202
|
+
// Sort for deterministic output to ensure consistent caching
|
|
203
|
+
finalFunctionNames = Array.from(allFunctionNames).sort();
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// Default to AUTO mode
|
|
207
|
+
finalMode = FunctionCallingConfigMode.AUTO;
|
|
208
|
+
// Sort for deterministic output to ensure consistent caching
|
|
209
|
+
finalFunctionNames = Array.from(allFunctionNames).sort();
|
|
210
|
+
}
|
|
211
|
+
merged.hookSpecificOutput = {
|
|
212
|
+
hookEventName: 'BeforeToolSelection',
|
|
213
|
+
toolConfig: {
|
|
214
|
+
mode: finalMode,
|
|
215
|
+
allowedFunctionNames: finalFunctionNames,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
return merged;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Simple merge for events without special logic
|
|
222
|
+
*/
|
|
223
|
+
mergeSimple(outputs) {
|
|
224
|
+
let merged = {};
|
|
225
|
+
for (const output of outputs) {
|
|
226
|
+
merged = { ...merged, ...output };
|
|
227
|
+
}
|
|
228
|
+
return merged;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Create the appropriate specific hook output class based on event type
|
|
232
|
+
*/
|
|
233
|
+
createSpecificHookOutput(output, eventName) {
|
|
234
|
+
switch (eventName) {
|
|
235
|
+
case HookEventName.BeforeTool:
|
|
236
|
+
return new BeforeToolHookOutput(output);
|
|
237
|
+
case HookEventName.BeforeModel:
|
|
238
|
+
return new BeforeModelHookOutput(output);
|
|
239
|
+
case HookEventName.BeforeToolSelection:
|
|
240
|
+
return new BeforeToolSelectionHookOutput(output);
|
|
241
|
+
case HookEventName.AfterModel:
|
|
242
|
+
return new AfterModelHookOutput(output);
|
|
243
|
+
default:
|
|
244
|
+
return new DefaultHookOutput(output);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Extract additional context from hook-specific outputs
|
|
249
|
+
*/
|
|
250
|
+
extractAdditionalContext(output, contexts) {
|
|
251
|
+
const specific = output.hookSpecificOutput;
|
|
252
|
+
if (!specific) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Extract additionalContext from various hook types
|
|
256
|
+
if ('additionalContext' in specific &&
|
|
257
|
+
typeof specific['additionalContext'] === 'string') {
|
|
258
|
+
contexts.push(specific['additionalContext']);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=hookAggregator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hookAggregator.js","sourceRoot":"","sources":["../../../src/hooks/hookAggregator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
7
|
+
import { HookAggregator } from './hookAggregator.js';
|
|
8
|
+
import { HookType, HookEventName } from './types.js';
|
|
9
|
+
// Helper function to create proper HookExecutionResult objects
|
|
10
|
+
function createHookExecutionResult(output, success = true, duration = 100, error) {
|
|
11
|
+
return {
|
|
12
|
+
success,
|
|
13
|
+
output,
|
|
14
|
+
duration,
|
|
15
|
+
error,
|
|
16
|
+
hookConfig: {
|
|
17
|
+
type: HookType.Command,
|
|
18
|
+
command: 'test-command',
|
|
19
|
+
timeout: 30000,
|
|
20
|
+
},
|
|
21
|
+
eventName: HookEventName.BeforeTool,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
describe('HookAggregator', () => {
|
|
25
|
+
let aggregator;
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
aggregator = new HookAggregator();
|
|
28
|
+
});
|
|
29
|
+
describe('aggregateResults', () => {
|
|
30
|
+
it('should handle empty results', () => {
|
|
31
|
+
const results = [];
|
|
32
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
|
|
33
|
+
expect(aggregated.success).toBe(true);
|
|
34
|
+
expect(aggregated.allOutputs).toHaveLength(0);
|
|
35
|
+
expect(aggregated.errors).toHaveLength(0);
|
|
36
|
+
expect(aggregated.totalDuration).toBe(0);
|
|
37
|
+
expect(aggregated.finalOutput).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
it('should aggregate successful results', () => {
|
|
40
|
+
const results = [
|
|
41
|
+
createHookExecutionResult({ decision: 'allow', reason: 'Hook 1 approved' }, true, 100),
|
|
42
|
+
createHookExecutionResult({ decision: 'allow', reason: 'Hook 2 approved' }, true, 150),
|
|
43
|
+
];
|
|
44
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
|
|
45
|
+
expect(aggregated.success).toBe(true);
|
|
46
|
+
expect(aggregated.allOutputs).toHaveLength(2);
|
|
47
|
+
expect(aggregated.errors).toHaveLength(0);
|
|
48
|
+
expect(aggregated.totalDuration).toBe(250);
|
|
49
|
+
expect(aggregated.finalOutput?.decision).toBe('allow');
|
|
50
|
+
expect(aggregated.finalOutput?.reason).toBe('Hook 1 approved\nHook 2 approved');
|
|
51
|
+
});
|
|
52
|
+
it('should handle errors in results', () => {
|
|
53
|
+
const results = [
|
|
54
|
+
{
|
|
55
|
+
hookConfig: {
|
|
56
|
+
type: HookType.Command,
|
|
57
|
+
command: 'test-command',
|
|
58
|
+
timeout: 30000,
|
|
59
|
+
},
|
|
60
|
+
eventName: HookEventName.BeforeTool,
|
|
61
|
+
success: false,
|
|
62
|
+
error: new Error('Hook failed'),
|
|
63
|
+
duration: 50,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
hookConfig: {
|
|
67
|
+
type: HookType.Command,
|
|
68
|
+
command: 'test-command',
|
|
69
|
+
timeout: 30000,
|
|
70
|
+
},
|
|
71
|
+
eventName: HookEventName.BeforeTool,
|
|
72
|
+
success: true,
|
|
73
|
+
output: { decision: 'allow' },
|
|
74
|
+
duration: 100,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
|
|
78
|
+
expect(aggregated.success).toBe(false);
|
|
79
|
+
expect(aggregated.allOutputs).toHaveLength(1);
|
|
80
|
+
expect(aggregated.errors).toHaveLength(1);
|
|
81
|
+
expect(aggregated.errors[0].message).toBe('Hook failed');
|
|
82
|
+
expect(aggregated.totalDuration).toBe(150);
|
|
83
|
+
});
|
|
84
|
+
it('should handle blocking decisions with OR logic', () => {
|
|
85
|
+
const results = [
|
|
86
|
+
{
|
|
87
|
+
hookConfig: {
|
|
88
|
+
type: HookType.Command,
|
|
89
|
+
command: 'test-command',
|
|
90
|
+
timeout: 30000,
|
|
91
|
+
},
|
|
92
|
+
eventName: HookEventName.BeforeTool,
|
|
93
|
+
success: true,
|
|
94
|
+
output: { decision: 'allow', reason: 'Hook 1 allowed' },
|
|
95
|
+
duration: 100,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
hookConfig: {
|
|
99
|
+
type: HookType.Command,
|
|
100
|
+
command: 'test-command',
|
|
101
|
+
timeout: 30000,
|
|
102
|
+
},
|
|
103
|
+
eventName: HookEventName.BeforeTool,
|
|
104
|
+
success: true,
|
|
105
|
+
output: { decision: 'block', reason: 'Hook 2 blocked' },
|
|
106
|
+
duration: 150,
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
|
|
110
|
+
expect(aggregated.success).toBe(true);
|
|
111
|
+
expect(aggregated.finalOutput?.decision).toBe('block');
|
|
112
|
+
expect(aggregated.finalOutput?.reason).toBe('Hook 1 allowed\nHook 2 blocked');
|
|
113
|
+
});
|
|
114
|
+
it('should handle continue=false with precedence', () => {
|
|
115
|
+
const results = [
|
|
116
|
+
{
|
|
117
|
+
hookConfig: {
|
|
118
|
+
type: HookType.Command,
|
|
119
|
+
command: 'test-command',
|
|
120
|
+
timeout: 30000,
|
|
121
|
+
},
|
|
122
|
+
eventName: HookEventName.BeforeTool,
|
|
123
|
+
success: true,
|
|
124
|
+
output: { decision: 'allow', continue: true },
|
|
125
|
+
duration: 100,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
hookConfig: {
|
|
129
|
+
type: HookType.Command,
|
|
130
|
+
command: 'test-command',
|
|
131
|
+
timeout: 30000,
|
|
132
|
+
},
|
|
133
|
+
eventName: HookEventName.BeforeTool,
|
|
134
|
+
success: true,
|
|
135
|
+
output: {
|
|
136
|
+
decision: 'allow',
|
|
137
|
+
continue: false,
|
|
138
|
+
stopReason: 'Stop requested',
|
|
139
|
+
},
|
|
140
|
+
duration: 150,
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeTool);
|
|
144
|
+
expect(aggregated.success).toBe(true);
|
|
145
|
+
expect(aggregated.finalOutput?.continue).toBe(false);
|
|
146
|
+
expect(aggregated.finalOutput?.stopReason).toBe('Stop requested');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('BeforeToolSelection merge strategy', () => {
|
|
150
|
+
it('should merge tool configurations with NONE mode precedence', () => {
|
|
151
|
+
const results = [
|
|
152
|
+
{
|
|
153
|
+
hookConfig: {
|
|
154
|
+
type: HookType.Command,
|
|
155
|
+
command: 'test-command',
|
|
156
|
+
timeout: 30000,
|
|
157
|
+
},
|
|
158
|
+
eventName: HookEventName.BeforeToolSelection,
|
|
159
|
+
success: true,
|
|
160
|
+
output: {
|
|
161
|
+
hookSpecificOutput: {
|
|
162
|
+
hookEventName: 'BeforeToolSelection',
|
|
163
|
+
toolConfig: {
|
|
164
|
+
mode: 'ANY',
|
|
165
|
+
allowedFunctionNames: ['tool1', 'tool2'],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
duration: 100,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
hookConfig: {
|
|
173
|
+
type: HookType.Command,
|
|
174
|
+
command: 'test-command',
|
|
175
|
+
timeout: 30000,
|
|
176
|
+
},
|
|
177
|
+
eventName: HookEventName.BeforeToolSelection,
|
|
178
|
+
success: true,
|
|
179
|
+
output: {
|
|
180
|
+
hookSpecificOutput: {
|
|
181
|
+
hookEventName: 'BeforeToolSelection',
|
|
182
|
+
toolConfig: {
|
|
183
|
+
mode: 'NONE',
|
|
184
|
+
allowedFunctionNames: [],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
duration: 150,
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeToolSelection);
|
|
192
|
+
expect(aggregated.success).toBe(true);
|
|
193
|
+
const output = aggregated.finalOutput;
|
|
194
|
+
const toolConfig = output.hookSpecificOutput?.toolConfig;
|
|
195
|
+
expect(toolConfig?.mode).toBe('NONE');
|
|
196
|
+
expect(toolConfig?.allowedFunctionNames).toEqual([]);
|
|
197
|
+
});
|
|
198
|
+
it('should merge tool configurations with ANY mode', () => {
|
|
199
|
+
const results = [
|
|
200
|
+
{
|
|
201
|
+
hookConfig: {
|
|
202
|
+
type: HookType.Command,
|
|
203
|
+
command: 'test-command',
|
|
204
|
+
timeout: 30000,
|
|
205
|
+
},
|
|
206
|
+
eventName: HookEventName.BeforeToolSelection,
|
|
207
|
+
success: true,
|
|
208
|
+
output: {
|
|
209
|
+
hookSpecificOutput: {
|
|
210
|
+
hookEventName: 'BeforeToolSelection',
|
|
211
|
+
toolConfig: {
|
|
212
|
+
mode: 'AUTO',
|
|
213
|
+
allowedFunctionNames: ['tool1'],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
duration: 100,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
hookConfig: {
|
|
221
|
+
type: HookType.Command,
|
|
222
|
+
command: 'test-command',
|
|
223
|
+
timeout: 30000,
|
|
224
|
+
},
|
|
225
|
+
eventName: HookEventName.BeforeToolSelection,
|
|
226
|
+
success: true,
|
|
227
|
+
output: {
|
|
228
|
+
hookSpecificOutput: {
|
|
229
|
+
hookEventName: 'BeforeToolSelection',
|
|
230
|
+
toolConfig: {
|
|
231
|
+
mode: 'ANY',
|
|
232
|
+
allowedFunctionNames: ['tool2', 'tool3'],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
duration: 150,
|
|
237
|
+
},
|
|
238
|
+
];
|
|
239
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeToolSelection);
|
|
240
|
+
expect(aggregated.success).toBe(true);
|
|
241
|
+
const output = aggregated.finalOutput;
|
|
242
|
+
const toolConfig = output.hookSpecificOutput?.toolConfig;
|
|
243
|
+
expect(toolConfig?.mode).toBe('ANY');
|
|
244
|
+
expect(toolConfig?.allowedFunctionNames).toEqual([
|
|
245
|
+
'tool1',
|
|
246
|
+
'tool2',
|
|
247
|
+
'tool3',
|
|
248
|
+
]);
|
|
249
|
+
});
|
|
250
|
+
it('should merge tool configurations with AUTO mode when all are AUTO', () => {
|
|
251
|
+
const results = [
|
|
252
|
+
{
|
|
253
|
+
hookConfig: {
|
|
254
|
+
type: HookType.Command,
|
|
255
|
+
command: 'test-command',
|
|
256
|
+
timeout: 30000,
|
|
257
|
+
},
|
|
258
|
+
eventName: HookEventName.BeforeToolSelection,
|
|
259
|
+
success: true,
|
|
260
|
+
output: {
|
|
261
|
+
hookSpecificOutput: {
|
|
262
|
+
hookEventName: 'BeforeToolSelection',
|
|
263
|
+
toolConfig: {
|
|
264
|
+
mode: 'AUTO',
|
|
265
|
+
allowedFunctionNames: ['tool1'],
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
duration: 100,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
hookConfig: {
|
|
273
|
+
type: HookType.Command,
|
|
274
|
+
command: 'test-command',
|
|
275
|
+
timeout: 30000,
|
|
276
|
+
},
|
|
277
|
+
eventName: HookEventName.BeforeToolSelection,
|
|
278
|
+
success: true,
|
|
279
|
+
output: {
|
|
280
|
+
hookSpecificOutput: {
|
|
281
|
+
hookEventName: 'BeforeToolSelection',
|
|
282
|
+
toolConfig: {
|
|
283
|
+
mode: 'AUTO',
|
|
284
|
+
allowedFunctionNames: ['tool2'],
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
duration: 150,
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeToolSelection);
|
|
292
|
+
expect(aggregated.success).toBe(true);
|
|
293
|
+
const output = aggregated.finalOutput;
|
|
294
|
+
const toolConfig = output.hookSpecificOutput?.toolConfig;
|
|
295
|
+
expect(toolConfig?.mode).toBe('AUTO');
|
|
296
|
+
expect(toolConfig?.allowedFunctionNames).toEqual(['tool1', 'tool2']);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
describe('BeforeModel/AfterModel merge strategy', () => {
|
|
300
|
+
it('should use field replacement strategy', () => {
|
|
301
|
+
const results = [
|
|
302
|
+
{
|
|
303
|
+
hookConfig: {
|
|
304
|
+
type: HookType.Command,
|
|
305
|
+
command: 'test-command',
|
|
306
|
+
timeout: 30000,
|
|
307
|
+
},
|
|
308
|
+
eventName: HookEventName.BeforeModel,
|
|
309
|
+
success: true,
|
|
310
|
+
output: {
|
|
311
|
+
decision: 'allow',
|
|
312
|
+
hookSpecificOutput: {
|
|
313
|
+
hookEventName: 'BeforeModel',
|
|
314
|
+
llm_request: { model: 'model1', config: {}, contents: [] },
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
duration: 100,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
hookConfig: {
|
|
321
|
+
type: HookType.Command,
|
|
322
|
+
command: 'test-command',
|
|
323
|
+
timeout: 30000,
|
|
324
|
+
},
|
|
325
|
+
eventName: HookEventName.BeforeModel,
|
|
326
|
+
success: true,
|
|
327
|
+
output: {
|
|
328
|
+
decision: 'block',
|
|
329
|
+
hookSpecificOutput: {
|
|
330
|
+
hookEventName: 'BeforeModel',
|
|
331
|
+
llm_request: { model: 'model2', config: {}, contents: [] },
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
duration: 150,
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.BeforeModel);
|
|
338
|
+
expect(aggregated.success).toBe(true);
|
|
339
|
+
expect(aggregated.finalOutput?.decision).toBe('block'); // Later value wins
|
|
340
|
+
const output = aggregated.finalOutput;
|
|
341
|
+
const llmRequest = output.hookSpecificOutput?.llm_request;
|
|
342
|
+
expect(llmRequest?.['model']).toBe('model2'); // Later value wins
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
describe('extractAdditionalContext', () => {
|
|
346
|
+
it('should extract additional context from hook outputs', () => {
|
|
347
|
+
const results = [
|
|
348
|
+
{
|
|
349
|
+
hookConfig: {
|
|
350
|
+
type: HookType.Command,
|
|
351
|
+
command: 'test-command',
|
|
352
|
+
timeout: 30000,
|
|
353
|
+
},
|
|
354
|
+
eventName: HookEventName.AfterTool,
|
|
355
|
+
success: true,
|
|
356
|
+
output: {
|
|
357
|
+
hookSpecificOutput: {
|
|
358
|
+
hookEventName: 'AfterTool',
|
|
359
|
+
additionalContext: 'Context from hook 1',
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
duration: 100,
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
hookConfig: {
|
|
366
|
+
type: HookType.Command,
|
|
367
|
+
command: 'test-command',
|
|
368
|
+
timeout: 30000,
|
|
369
|
+
},
|
|
370
|
+
eventName: HookEventName.AfterTool,
|
|
371
|
+
success: true,
|
|
372
|
+
output: {
|
|
373
|
+
hookSpecificOutput: {
|
|
374
|
+
hookEventName: 'AfterTool',
|
|
375
|
+
additionalContext: 'Context from hook 2',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
duration: 150,
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
const aggregated = aggregator.aggregateResults(results, HookEventName.AfterTool);
|
|
382
|
+
expect(aggregated.success).toBe(true);
|
|
383
|
+
expect(aggregated.finalOutput?.hookSpecificOutput?.['additionalContext']).toBe('Context from hook 1\nContext from hook 2');
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
//# sourceMappingURL=hookAggregator.test.js.map
|