@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.
Files changed (144) hide show
  1. package/dist/google-gemini-cli-core-0.16.0-nightly.20251112.c961f274.tgz +0 -0
  2. package/dist/src/agents/codebase-investigator.test.d.ts +6 -0
  3. package/dist/src/agents/codebase-investigator.test.js +35 -0
  4. package/dist/src/agents/codebase-investigator.test.js.map +1 -0
  5. package/dist/src/agents/executor.test.js +181 -1
  6. package/dist/src/agents/executor.test.js.map +1 -1
  7. package/dist/src/code_assist/codeAssist.test.d.ts +6 -0
  8. package/dist/src/code_assist/codeAssist.test.js +99 -0
  9. package/dist/src/code_assist/codeAssist.test.js.map +1 -0
  10. package/dist/src/code_assist/experiments/client_metadata.js +2 -1
  11. package/dist/src/code_assist/experiments/client_metadata.js.map +1 -1
  12. package/dist/src/code_assist/experiments/client_metadata.test.d.ts +6 -0
  13. package/dist/src/code_assist/experiments/client_metadata.test.js +99 -0
  14. package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -0
  15. package/dist/src/code_assist/experiments/experiments.test.d.ts +6 -0
  16. package/dist/src/code_assist/experiments/experiments.test.js +92 -0
  17. package/dist/src/code_assist/experiments/experiments.test.js.map +1 -0
  18. package/dist/src/code_assist/oauth-credential-storage.test.js +49 -0
  19. package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
  20. package/dist/src/code_assist/server.js +5 -8
  21. package/dist/src/code_assist/server.js.map +1 -1
  22. package/dist/src/code_assist/server.test.js +109 -28
  23. package/dist/src/code_assist/server.test.js.map +1 -1
  24. package/dist/src/config/defaultModelConfigs.js +6 -0
  25. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  26. package/dist/src/confirmation-bus/message-bus.d.ts +1 -1
  27. package/dist/src/confirmation-bus/message-bus.js +2 -2
  28. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  29. package/dist/src/confirmation-bus/message-bus.test.js +30 -24
  30. package/dist/src/confirmation-bus/message-bus.test.js.map +1 -1
  31. package/dist/src/core/loggingContentGenerator.test.d.ts +6 -0
  32. package/dist/src/core/loggingContentGenerator.test.js +180 -0
  33. package/dist/src/core/loggingContentGenerator.test.js.map +1 -0
  34. package/dist/src/core/tokenLimits.test.d.ts +6 -0
  35. package/dist/src/core/tokenLimits.test.js +26 -0
  36. package/dist/src/core/tokenLimits.test.js.map +1 -0
  37. package/dist/src/generated/git-commit.d.ts +2 -2
  38. package/dist/src/generated/git-commit.js +2 -2
  39. package/dist/src/generated/git-commit.js.map +1 -1
  40. package/dist/src/hooks/hookAggregator.d.ts +68 -0
  41. package/dist/src/hooks/hookAggregator.js +262 -0
  42. package/dist/src/hooks/hookAggregator.js.map +1 -0
  43. package/dist/src/hooks/hookAggregator.test.d.ts +6 -0
  44. package/dist/src/hooks/hookAggregator.test.js +387 -0
  45. package/dist/src/hooks/hookAggregator.test.js.map +1 -0
  46. package/dist/src/hooks/types.js +1 -1
  47. package/dist/src/hooks/types.js.map +1 -1
  48. package/dist/src/hooks/types.test.js +280 -2
  49. package/dist/src/hooks/types.test.js.map +1 -1
  50. package/dist/src/ide/ide-client.test.js +159 -0
  51. package/dist/src/ide/ide-client.test.js.map +1 -1
  52. package/dist/src/mcp/oauth-provider.test.js +177 -0
  53. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  54. package/dist/src/policy/config.js +3 -1
  55. package/dist/src/policy/config.js.map +1 -1
  56. package/dist/src/policy/config.test.js +118 -1
  57. package/dist/src/policy/config.test.js.map +1 -1
  58. package/dist/src/policy/policies/write.toml +10 -0
  59. package/dist/src/policy/policy-engine.d.ts +12 -3
  60. package/dist/src/policy/policy-engine.js +61 -7
  61. package/dist/src/policy/policy-engine.js.map +1 -1
  62. package/dist/src/policy/policy-engine.test.js +422 -86
  63. package/dist/src/policy/policy-engine.test.js.map +1 -1
  64. package/dist/src/policy/toml-loader.d.ts +2 -1
  65. package/dist/src/policy/toml-loader.js +103 -6
  66. package/dist/src/policy/toml-loader.js.map +1 -1
  67. package/dist/src/policy/toml-loader.test.js +32 -88
  68. package/dist/src/policy/toml-loader.test.js.map +1 -1
  69. package/dist/src/policy/types.d.ts +65 -0
  70. package/dist/src/policy/types.js +4 -0
  71. package/dist/src/policy/types.js.map +1 -1
  72. package/dist/src/prompts/mcp-prompts.test.d.ts +6 -0
  73. package/dist/src/prompts/mcp-prompts.test.js +40 -0
  74. package/dist/src/prompts/mcp-prompts.test.js.map +1 -0
  75. package/dist/src/prompts/prompt-registry.test.d.ts +6 -0
  76. package/dist/src/prompts/prompt-registry.test.js +111 -0
  77. package/dist/src/prompts/prompt-registry.test.js.map +1 -0
  78. package/dist/src/safety/built-in.d.ts +21 -0
  79. package/dist/src/safety/built-in.js +106 -0
  80. package/dist/src/safety/built-in.js.map +1 -0
  81. package/dist/src/safety/built-in.test.d.ts +6 -0
  82. package/dist/src/safety/built-in.test.js +199 -0
  83. package/dist/src/safety/built-in.test.js.map +1 -0
  84. package/dist/src/safety/checker-runner.d.ts +48 -0
  85. package/dist/src/safety/checker-runner.js +208 -0
  86. package/dist/src/safety/checker-runner.js.map +1 -0
  87. package/dist/src/safety/checker-runner.test.d.ts +6 -0
  88. package/dist/src/safety/checker-runner.test.js +238 -0
  89. package/dist/src/safety/checker-runner.test.js.map +1 -0
  90. package/dist/src/safety/context-builder.d.ts +23 -0
  91. package/dist/src/safety/context-builder.js +47 -0
  92. package/dist/src/safety/context-builder.js.map +1 -0
  93. package/dist/src/safety/context-builder.test.d.ts +6 -0
  94. package/dist/src/safety/context-builder.test.js +49 -0
  95. package/dist/src/safety/context-builder.test.js.map +1 -0
  96. package/dist/src/safety/protocol.d.ts +88 -0
  97. package/dist/src/safety/protocol.js +15 -0
  98. package/dist/src/safety/protocol.js.map +1 -0
  99. package/dist/src/safety/registry.d.ts +26 -0
  100. package/dist/src/safety/registry.js +65 -0
  101. package/dist/src/safety/registry.js.map +1 -0
  102. package/dist/src/safety/registry.test.d.ts +6 -0
  103. package/dist/src/safety/registry.test.js +31 -0
  104. package/dist/src/safety/registry.test.js.map +1 -0
  105. package/dist/src/services/loopDetectionService.d.ts +3 -0
  106. package/dist/src/services/loopDetectionService.js +81 -41
  107. package/dist/src/services/loopDetectionService.js.map +1 -1
  108. package/dist/src/services/loopDetectionService.test.js +96 -4
  109. package/dist/src/services/loopDetectionService.test.js.map +1 -1
  110. package/dist/src/services/test-data/resolved-aliases.golden.json +7 -0
  111. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +4 -2
  112. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +29 -0
  113. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  114. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +31 -0
  115. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  116. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +5 -1
  117. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +12 -1
  118. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  119. package/dist/src/telemetry/loggers.d.ts +2 -1
  120. package/dist/src/telemetry/loggers.js +11 -0
  121. package/dist/src/telemetry/loggers.js.map +1 -1
  122. package/dist/src/telemetry/types.d.ts +15 -2
  123. package/dist/src/telemetry/types.js +42 -3
  124. package/dist/src/telemetry/types.js.map +1 -1
  125. package/dist/src/tools/base-tool-invocation.test.js +2 -2
  126. package/dist/src/tools/base-tool-invocation.test.js.map +1 -1
  127. package/dist/src/tools/memoryTool.js +1 -1
  128. package/dist/src/tools/memoryTool.js.map +1 -1
  129. package/dist/src/tools/memoryTool.test.js +1 -1
  130. package/dist/src/tools/memoryTool.test.js.map +1 -1
  131. package/dist/src/tools/ripGrep.d.ts +24 -7
  132. package/dist/src/tools/ripGrep.js +125 -145
  133. package/dist/src/tools/ripGrep.js.map +1 -1
  134. package/dist/src/tools/ripGrep.test.js +144 -20
  135. package/dist/src/tools/ripGrep.test.js.map +1 -1
  136. package/dist/src/tools/tools.js +1 -1
  137. package/dist/src/tools/tools.js.map +1 -1
  138. package/dist/src/tools/write-todos.js +1 -1
  139. package/dist/src/tools/write-todos.js.map +1 -1
  140. package/dist/src/utils/llm-edit-fixer.test.js +8 -1
  141. package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
  142. package/dist/tsconfig.tsbuildinfo +1 -1
  143. package/package.json +1 -1
  144. 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,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export {};
@@ -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