@illuma-ai/agents 1.1.21 → 1.1.23
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/cjs/graphs/Graph.cjs +12 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +105 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/run.cjs +20 -9
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +12 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +105 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/run.mjs +20 -9
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/types/graphs/MultiAgentGraph.d.ts +17 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +13 -1
- package/src/graphs/MultiAgentGraph.ts +128 -1
- package/src/graphs/__tests__/multi-agent-delegate.test.ts +205 -0
- package/src/run.ts +20 -11
- package/src/scripts/test-bedrock-handoff-autonomous.ts +231 -0
- package/src/utils/llm.ts +1 -0
- package/src/agents/AgentContext.js +0 -782
- package/src/agents/AgentContext.test.js +0 -421
- package/src/agents/__tests__/AgentContext.test.js +0 -678
- package/src/agents/__tests__/resolveStructuredOutputMode.test.js +0 -117
- package/src/common/enum.js +0 -192
- package/src/common/index.js +0 -3
- package/src/events.js +0 -166
- package/src/graphs/Graph.js +0 -1857
- package/src/graphs/MultiAgentGraph.js +0 -1092
- package/src/graphs/__tests__/structured-output.integration.test.js +0 -624
- package/src/graphs/__tests__/structured-output.test.js +0 -144
- package/src/graphs/contextManagement.e2e.test.js +0 -718
- package/src/graphs/contextManagement.test.js +0 -485
- package/src/graphs/handoffValidation.test.js +0 -276
- package/src/graphs/index.js +0 -3
- package/src/index.js +0 -28
- package/src/instrumentation.js +0 -21
- package/src/llm/anthropic/index.js +0 -319
- package/src/llm/anthropic/types.js +0 -46
- package/src/llm/anthropic/utils/message_inputs.js +0 -627
- package/src/llm/anthropic/utils/message_outputs.js +0 -290
- package/src/llm/anthropic/utils/output_parsers.js +0 -89
- package/src/llm/anthropic/utils/tools.js +0 -25
- package/src/llm/bedrock/__tests__/bedrock-caching.test.js +0 -392
- package/src/llm/bedrock/index.js +0 -303
- package/src/llm/bedrock/types.js +0 -2
- package/src/llm/bedrock/utils/index.js +0 -6
- package/src/llm/bedrock/utils/message_inputs.js +0 -463
- package/src/llm/bedrock/utils/message_outputs.js +0 -269
- package/src/llm/fake.js +0 -92
- package/src/llm/google/index.js +0 -215
- package/src/llm/google/types.js +0 -12
- package/src/llm/google/utils/common.js +0 -670
- package/src/llm/google/utils/tools.js +0 -111
- package/src/llm/google/utils/zod_to_genai_parameters.js +0 -47
- package/src/llm/openai/index.js +0 -1033
- package/src/llm/openai/types.js +0 -2
- package/src/llm/openai/utils/index.js +0 -756
- package/src/llm/openai/utils/isReasoningModel.test.js +0 -79
- package/src/llm/openrouter/index.js +0 -261
- package/src/llm/openrouter/reasoning.test.js +0 -181
- package/src/llm/providers.js +0 -36
- package/src/llm/text.js +0 -65
- package/src/llm/vertexai/index.js +0 -402
- package/src/messages/__tests__/tools.test.js +0 -392
- package/src/messages/cache.js +0 -404
- package/src/messages/cache.test.js +0 -1167
- package/src/messages/content.js +0 -48
- package/src/messages/content.test.js +0 -314
- package/src/messages/core.js +0 -359
- package/src/messages/ensureThinkingBlock.test.js +0 -997
- package/src/messages/format.js +0 -973
- package/src/messages/formatAgentMessages.test.js +0 -2278
- package/src/messages/formatAgentMessages.tools.test.js +0 -362
- package/src/messages/formatMessage.test.js +0 -608
- package/src/messages/ids.js +0 -18
- package/src/messages/index.js +0 -9
- package/src/messages/labelContentByAgent.test.js +0 -725
- package/src/messages/prune.js +0 -438
- package/src/messages/reducer.js +0 -60
- package/src/messages/shiftIndexTokenCountMap.test.js +0 -63
- package/src/messages/summarize.js +0 -146
- package/src/messages/summarize.test.js +0 -332
- package/src/messages/tools.js +0 -90
- package/src/mockStream.js +0 -81
- package/src/prompts/collab.js +0 -7
- package/src/prompts/index.js +0 -3
- package/src/prompts/taskmanager.js +0 -58
- package/src/run.js +0 -427
- package/src/schemas/index.js +0 -3
- package/src/schemas/schema-preparation.test.js +0 -370
- package/src/schemas/validate.js +0 -314
- package/src/schemas/validate.test.js +0 -264
- package/src/scripts/abort.js +0 -127
- package/src/scripts/ant_web_search.js +0 -130
- package/src/scripts/ant_web_search_edge_case.js +0 -133
- package/src/scripts/ant_web_search_error_edge_case.js +0 -119
- package/src/scripts/args.js +0 -41
- package/src/scripts/bedrock-cache-debug.js +0 -186
- package/src/scripts/bedrock-content-aggregation-test.js +0 -195
- package/src/scripts/bedrock-merge-test.js +0 -80
- package/src/scripts/bedrock-parallel-tools-test.js +0 -150
- package/src/scripts/caching.js +0 -106
- package/src/scripts/cli.js +0 -152
- package/src/scripts/cli2.js +0 -119
- package/src/scripts/cli3.js +0 -163
- package/src/scripts/cli4.js +0 -165
- package/src/scripts/cli5.js +0 -165
- package/src/scripts/code_exec.js +0 -171
- package/src/scripts/code_exec_files.js +0 -180
- package/src/scripts/code_exec_multi_session.js +0 -185
- package/src/scripts/code_exec_ptc.js +0 -265
- package/src/scripts/code_exec_session.js +0 -217
- package/src/scripts/code_exec_simple.js +0 -120
- package/src/scripts/content.js +0 -111
- package/src/scripts/empty_input.js +0 -125
- package/src/scripts/handoff-test.js +0 -96
- package/src/scripts/image.js +0 -138
- package/src/scripts/memory.js +0 -83
- package/src/scripts/multi-agent-chain.js +0 -271
- package/src/scripts/multi-agent-conditional.js +0 -185
- package/src/scripts/multi-agent-document-review-chain.js +0 -171
- package/src/scripts/multi-agent-hybrid-flow.js +0 -264
- package/src/scripts/multi-agent-parallel-start.js +0 -214
- package/src/scripts/multi-agent-parallel.js +0 -346
- package/src/scripts/multi-agent-sequence.js +0 -184
- package/src/scripts/multi-agent-supervisor.js +0 -324
- package/src/scripts/multi-agent-test.js +0 -147
- package/src/scripts/parallel-asymmetric-tools-test.js +0 -202
- package/src/scripts/parallel-full-metadata-test.js +0 -176
- package/src/scripts/parallel-tools-test.js +0 -256
- package/src/scripts/programmatic_exec.js +0 -277
- package/src/scripts/programmatic_exec_agent.js +0 -168
- package/src/scripts/search.js +0 -118
- package/src/scripts/sequential-full-metadata-test.js +0 -143
- package/src/scripts/simple.js +0 -174
- package/src/scripts/single-agent-metadata-test.js +0 -152
- package/src/scripts/stream.js +0 -113
- package/src/scripts/test-custom-prompt-key.js +0 -132
- package/src/scripts/test-handoff-input.js +0 -143
- package/src/scripts/test-handoff-preamble.js +0 -227
- package/src/scripts/test-handoff-steering.js +0 -353
- package/src/scripts/test-multi-agent-list-handoff.js +0 -318
- package/src/scripts/test-parallel-agent-labeling.js +0 -253
- package/src/scripts/test-parallel-handoffs.js +0 -229
- package/src/scripts/test-thinking-handoff-bedrock.js +0 -132
- package/src/scripts/test-thinking-handoff.js +0 -132
- package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js +0 -140
- package/src/scripts/test-tool-before-handoff-role-order.js +0 -223
- package/src/scripts/test-tools-before-handoff.js +0 -187
- package/src/scripts/test_code_api.js +0 -263
- package/src/scripts/thinking-bedrock.js +0 -128
- package/src/scripts/thinking-vertexai.js +0 -130
- package/src/scripts/thinking.js +0 -134
- package/src/scripts/tool_search.js +0 -114
- package/src/scripts/tools.js +0 -125
- package/src/specs/agent-handoffs-bedrock.integration.test.js +0 -280
- package/src/specs/agent-handoffs.test.js +0 -924
- package/src/specs/anthropic.simple.test.js +0 -287
- package/src/specs/azure.simple.test.js +0 -381
- package/src/specs/cache.simple.test.js +0 -282
- package/src/specs/custom-event-await.test.js +0 -148
- package/src/specs/deepseek.simple.test.js +0 -189
- package/src/specs/emergency-prune.test.js +0 -308
- package/src/specs/moonshot.simple.test.js +0 -237
- package/src/specs/observability.integration.test.js +0 -1337
- package/src/specs/openai.simple.test.js +0 -233
- package/src/specs/openrouter.simple.test.js +0 -202
- package/src/specs/prune.test.js +0 -733
- package/src/specs/reasoning.test.js +0 -144
- package/src/specs/spec.utils.js +0 -4
- package/src/specs/thinking-handoff.test.js +0 -486
- package/src/specs/thinking-prune.test.js +0 -600
- package/src/specs/token-distribution-edge-case.test.js +0 -246
- package/src/specs/token-memoization.test.js +0 -32
- package/src/specs/tokens.test.js +0 -49
- package/src/specs/tool-error.test.js +0 -139
- package/src/splitStream.js +0 -204
- package/src/splitStream.test.js +0 -504
- package/src/stream.js +0 -650
- package/src/stream.test.js +0 -225
- package/src/test/mockTools.js +0 -340
- package/src/tools/BrowserTools.js +0 -245
- package/src/tools/Calculator.js +0 -38
- package/src/tools/Calculator.test.js +0 -225
- package/src/tools/CodeExecutor.js +0 -233
- package/src/tools/ProgrammaticToolCalling.js +0 -602
- package/src/tools/StreamingToolCallBuffer.js +0 -179
- package/src/tools/ToolNode.js +0 -930
- package/src/tools/ToolSearch.js +0 -904
- package/src/tools/__tests__/BrowserTools.test.js +0 -306
- package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.js +0 -276
- package/src/tools/__tests__/ProgrammaticToolCalling.test.js +0 -807
- package/src/tools/__tests__/StreamingToolCallBuffer.test.js +0 -175
- package/src/tools/__tests__/ToolApproval.test.js +0 -675
- package/src/tools/__tests__/ToolNode.recovery.test.js +0 -200
- package/src/tools/__tests__/ToolNode.session.test.js +0 -319
- package/src/tools/__tests__/ToolSearch.integration.test.js +0 -125
- package/src/tools/__tests__/ToolSearch.test.js +0 -812
- package/src/tools/__tests__/handlers.test.js +0 -799
- package/src/tools/__tests__/truncation-recovery.integration.test.js +0 -362
- package/src/tools/handlers.js +0 -306
- package/src/tools/schema.js +0 -25
- package/src/tools/search/anthropic.js +0 -34
- package/src/tools/search/content.js +0 -116
- package/src/tools/search/content.test.js +0 -133
- package/src/tools/search/firecrawl.js +0 -173
- package/src/tools/search/format.js +0 -198
- package/src/tools/search/highlights.js +0 -241
- package/src/tools/search/index.js +0 -3
- package/src/tools/search/jina-reranker.test.js +0 -106
- package/src/tools/search/rerankers.js +0 -165
- package/src/tools/search/schema.js +0 -102
- package/src/tools/search/search.js +0 -561
- package/src/tools/search/serper-scraper.js +0 -126
- package/src/tools/search/test.js +0 -129
- package/src/tools/search/tool.js +0 -453
- package/src/tools/search/types.js +0 -2
- package/src/tools/search/utils.js +0 -59
- package/src/types/graph.js +0 -24
- package/src/types/graph.test.js +0 -192
- package/src/types/index.js +0 -7
- package/src/types/llm.js +0 -2
- package/src/types/messages.js +0 -2
- package/src/types/run.js +0 -2
- package/src/types/stream.js +0 -2
- package/src/types/tools.js +0 -2
- package/src/utils/contextAnalytics.js +0 -79
- package/src/utils/contextAnalytics.test.js +0 -166
- package/src/utils/events.js +0 -26
- package/src/utils/graph.js +0 -11
- package/src/utils/handlers.js +0 -65
- package/src/utils/index.js +0 -10
- package/src/utils/llm.js +0 -21
- package/src/utils/llmConfig.js +0 -205
- package/src/utils/logging.js +0 -37
- package/src/utils/misc.js +0 -51
- package/src/utils/run.js +0 -69
- package/src/utils/schema.js +0 -21
- package/src/utils/title.js +0 -119
- package/src/utils/tokens.js +0 -92
- package/src/utils/toonFormat.js +0 -379
|
@@ -1,675 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Human-in-the-Loop (HITL) tool approval in ToolNode.
|
|
3
|
-
*
|
|
4
|
-
* Tests the approval policy evaluation and the event-driven approval flow.
|
|
5
|
-
* The HITL system dispatches ON_TOOL_APPROVAL_REQUIRED events that the host
|
|
6
|
-
* (Ranger) handles by showing UI and resolving/rejecting the promise.
|
|
7
|
-
*/
|
|
8
|
-
import { tool } from '@langchain/core/tools';
|
|
9
|
-
import { z } from 'zod';
|
|
10
|
-
import { HumanMessage, AIMessage, ToolMessage } from '@langchain/core/messages';
|
|
11
|
-
import { StateGraph, Annotation, messagesStateReducer, START, } from '@langchain/langgraph';
|
|
12
|
-
import { ToolNode, toolsCondition } from '@/tools/ToolNode';
|
|
13
|
-
import { GraphEvents } from '@/common';
|
|
14
|
-
// ============================================================================
|
|
15
|
-
// Test Fixtures
|
|
16
|
-
// ============================================================================
|
|
17
|
-
/** Simple echo tool that returns its input */
|
|
18
|
-
const echoTool = tool(async ({ message }) => {
|
|
19
|
-
return `Echo: ${message}`;
|
|
20
|
-
}, {
|
|
21
|
-
name: 'echo',
|
|
22
|
-
description: 'Echoes the input message',
|
|
23
|
-
schema: z.object({
|
|
24
|
-
message: z.string().describe('The message to echo'),
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
/** Simulated "send email" tool */
|
|
28
|
-
const sendEmailTool = tool(async ({ to, subject }) => {
|
|
29
|
-
return JSON.stringify({ status: 'sent', to, subject });
|
|
30
|
-
}, {
|
|
31
|
-
name: 'send_email',
|
|
32
|
-
description: 'Sends an email',
|
|
33
|
-
schema: z.object({
|
|
34
|
-
to: z.string().describe('Recipient email'),
|
|
35
|
-
subject: z.string().describe('Email subject'),
|
|
36
|
-
}),
|
|
37
|
-
});
|
|
38
|
-
/** Simulated "search" tool - typically safe, no approval needed */
|
|
39
|
-
const searchTool = tool(async ({ query }) => {
|
|
40
|
-
return JSON.stringify({ results: [`Result for: ${query}`] });
|
|
41
|
-
}, {
|
|
42
|
-
name: 'search',
|
|
43
|
-
description: 'Searches for information',
|
|
44
|
-
schema: z.object({
|
|
45
|
-
query: z.string().describe('Search query'),
|
|
46
|
-
}),
|
|
47
|
-
});
|
|
48
|
-
/**
|
|
49
|
-
* Helper to build a graph with HITL support for integration testing.
|
|
50
|
-
* Returns the compiled graph and a way to intercept approval events.
|
|
51
|
-
*/
|
|
52
|
-
function createTestGraph(tools, toolApprovalConfig, modelResponses, approvalHandler) {
|
|
53
|
-
const StateAnnotation = Annotation.Root({
|
|
54
|
-
messages: Annotation({
|
|
55
|
-
reducer: messagesStateReducer,
|
|
56
|
-
default: () => [],
|
|
57
|
-
}),
|
|
58
|
-
});
|
|
59
|
-
let responseIndex = 0;
|
|
60
|
-
/** Fake model node that returns pre-defined tool calls or text */
|
|
61
|
-
const callModel = async (_state) => {
|
|
62
|
-
const responses = modelResponses[responseIndex] ?? ['Done.'];
|
|
63
|
-
responseIndex++;
|
|
64
|
-
// Check if the response is a tool call instruction (JSON format)
|
|
65
|
-
if (responses.length === 1 && responses[0].startsWith('{')) {
|
|
66
|
-
const parsed = JSON.parse(responses[0]);
|
|
67
|
-
return {
|
|
68
|
-
messages: [
|
|
69
|
-
new AIMessage({
|
|
70
|
-
content: '',
|
|
71
|
-
tool_calls: [parsed],
|
|
72
|
-
}),
|
|
73
|
-
],
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
messages: [new AIMessage({ content: responses.join(' ') })],
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
const toolNode = new ToolNode({
|
|
81
|
-
tools,
|
|
82
|
-
toolApprovalConfig,
|
|
83
|
-
});
|
|
84
|
-
const routeMessage = (state) => {
|
|
85
|
-
return toolsCondition(state, 'tools');
|
|
86
|
-
};
|
|
87
|
-
const workflow = new StateGraph(StateAnnotation)
|
|
88
|
-
.addNode('agent', callModel)
|
|
89
|
-
.addNode('tools', toolNode)
|
|
90
|
-
.addEdge(START, 'agent')
|
|
91
|
-
.addConditionalEdges('agent', routeMessage)
|
|
92
|
-
.addEdge('tools', 'agent');
|
|
93
|
-
const compiled = workflow.compile();
|
|
94
|
-
// Create a config with custom event handler to intercept approval requests
|
|
95
|
-
const config = {
|
|
96
|
-
version: 'v2',
|
|
97
|
-
callbacks: [
|
|
98
|
-
{
|
|
99
|
-
handleCustomEvent: async (eventName, data) => {
|
|
100
|
-
if (eventName === GraphEvents.ON_TOOL_APPROVAL_REQUIRED) {
|
|
101
|
-
approvalHandler(data);
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
};
|
|
107
|
-
return { compiled, config };
|
|
108
|
-
}
|
|
109
|
-
// ============================================================================
|
|
110
|
-
// Unit Tests: ToolApprovalConfig policy evaluation
|
|
111
|
-
// ============================================================================
|
|
112
|
-
describe('HITL Tool Approval - Policy Evaluation', () => {
|
|
113
|
-
test('no approval config means no interruption', async () => {
|
|
114
|
-
// ToolNode without toolApprovalConfig should execute tools directly
|
|
115
|
-
const toolNode = new ToolNode({
|
|
116
|
-
tools: [echoTool],
|
|
117
|
-
// No toolApprovalConfig
|
|
118
|
-
});
|
|
119
|
-
const input = {
|
|
120
|
-
messages: [
|
|
121
|
-
new AIMessage({
|
|
122
|
-
content: '',
|
|
123
|
-
tool_calls: [
|
|
124
|
-
{
|
|
125
|
-
name: 'echo',
|
|
126
|
-
args: { message: 'hello' },
|
|
127
|
-
id: 'call_1',
|
|
128
|
-
type: 'tool_call',
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
}),
|
|
132
|
-
],
|
|
133
|
-
};
|
|
134
|
-
// Should execute without interruption
|
|
135
|
-
const result = await toolNode.invoke(input);
|
|
136
|
-
expect(result.messages).toHaveLength(1);
|
|
137
|
-
expect(result.messages[0]).toBeInstanceOf(ToolMessage);
|
|
138
|
-
expect(result.messages[0].content).toContain('Echo: hello');
|
|
139
|
-
});
|
|
140
|
-
test('scheduled execution context bypasses all approvals', async () => {
|
|
141
|
-
// Even with defaultPolicy: 'always', scheduled should not interrupt
|
|
142
|
-
const toolNode = new ToolNode({
|
|
143
|
-
tools: [echoTool],
|
|
144
|
-
toolApprovalConfig: {
|
|
145
|
-
defaultPolicy: 'always',
|
|
146
|
-
executionContext: 'scheduled',
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
const input = {
|
|
150
|
-
messages: [
|
|
151
|
-
new AIMessage({
|
|
152
|
-
content: '',
|
|
153
|
-
tool_calls: [
|
|
154
|
-
{
|
|
155
|
-
name: 'echo',
|
|
156
|
-
args: { message: 'scheduled hello' },
|
|
157
|
-
id: 'call_2',
|
|
158
|
-
type: 'tool_call',
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
}),
|
|
162
|
-
],
|
|
163
|
-
};
|
|
164
|
-
// Should execute without interruption because context is 'scheduled'
|
|
165
|
-
const result = await toolNode.invoke(input);
|
|
166
|
-
expect(result.messages).toHaveLength(1);
|
|
167
|
-
expect(result.messages[0].content).toContain('Echo: scheduled hello');
|
|
168
|
-
});
|
|
169
|
-
test('tool with "never" override skips approval even when default is "always"', async () => {
|
|
170
|
-
const toolNode = new ToolNode({
|
|
171
|
-
tools: [searchTool],
|
|
172
|
-
toolApprovalConfig: {
|
|
173
|
-
defaultPolicy: 'always',
|
|
174
|
-
executionContext: 'interactive',
|
|
175
|
-
overrides: {
|
|
176
|
-
search: 'never',
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
const input = {
|
|
181
|
-
messages: [
|
|
182
|
-
new AIMessage({
|
|
183
|
-
content: '',
|
|
184
|
-
tool_calls: [
|
|
185
|
-
{
|
|
186
|
-
name: 'search',
|
|
187
|
-
args: { query: 'test query' },
|
|
188
|
-
id: 'call_3',
|
|
189
|
-
type: 'tool_call',
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
}),
|
|
193
|
-
],
|
|
194
|
-
};
|
|
195
|
-
// Should execute without interruption because override is 'never'
|
|
196
|
-
const result = await toolNode.invoke(input);
|
|
197
|
-
expect(result.messages).toHaveLength(1);
|
|
198
|
-
expect(result.messages[0].content).toContain('Result for: test query');
|
|
199
|
-
});
|
|
200
|
-
test('browser execution context bypasses all approvals', async () => {
|
|
201
|
-
// Browser extension has no HITL UI — all approvals should be auto-granted
|
|
202
|
-
const toolNode = new ToolNode({
|
|
203
|
-
tools: [sendEmailTool],
|
|
204
|
-
toolApprovalConfig: {
|
|
205
|
-
defaultPolicy: 'always',
|
|
206
|
-
executionContext: 'browser',
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
const input = {
|
|
210
|
-
messages: [
|
|
211
|
-
new AIMessage({
|
|
212
|
-
content: '',
|
|
213
|
-
tool_calls: [
|
|
214
|
-
{
|
|
215
|
-
name: 'send_email',
|
|
216
|
-
args: { to: 'user@example.com', subject: 'Test' },
|
|
217
|
-
id: 'call_browser_1',
|
|
218
|
-
type: 'tool_call',
|
|
219
|
-
},
|
|
220
|
-
],
|
|
221
|
-
}),
|
|
222
|
-
],
|
|
223
|
-
};
|
|
224
|
-
// Should execute without interruption because context is 'browser'
|
|
225
|
-
const result = await toolNode.invoke(input);
|
|
226
|
-
expect(result.messages).toHaveLength(1);
|
|
227
|
-
expect(result.messages[0].content).toContain('sent');
|
|
228
|
-
});
|
|
229
|
-
test('browser context bypasses custom function policy', async () => {
|
|
230
|
-
// Even a custom function that always returns true should be skipped in browser context
|
|
231
|
-
const alwaysRequire = () => true;
|
|
232
|
-
const toolNode = new ToolNode({
|
|
233
|
-
tools: [echoTool],
|
|
234
|
-
toolApprovalConfig: {
|
|
235
|
-
defaultPolicy: alwaysRequire,
|
|
236
|
-
executionContext: 'browser',
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
const input = {
|
|
240
|
-
messages: [
|
|
241
|
-
new AIMessage({
|
|
242
|
-
content: '',
|
|
243
|
-
tool_calls: [
|
|
244
|
-
{
|
|
245
|
-
name: 'echo',
|
|
246
|
-
args: { message: 'browser test' },
|
|
247
|
-
id: 'call_browser_2',
|
|
248
|
-
type: 'tool_call',
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
}),
|
|
252
|
-
],
|
|
253
|
-
};
|
|
254
|
-
const result = await toolNode.invoke(input);
|
|
255
|
-
expect(result.messages).toHaveLength(1);
|
|
256
|
-
expect(result.messages[0].content).toContain('Echo: browser test');
|
|
257
|
-
});
|
|
258
|
-
test('browser context bypasses per-tool overrides', async () => {
|
|
259
|
-
// Even with an explicit 'always' override for the tool, browser context should skip it
|
|
260
|
-
const toolNode = new ToolNode({
|
|
261
|
-
tools: [searchTool],
|
|
262
|
-
toolApprovalConfig: {
|
|
263
|
-
defaultPolicy: 'never',
|
|
264
|
-
executionContext: 'browser',
|
|
265
|
-
overrides: {
|
|
266
|
-
search: 'always',
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
const input = {
|
|
271
|
-
messages: [
|
|
272
|
-
new AIMessage({
|
|
273
|
-
content: '',
|
|
274
|
-
tool_calls: [
|
|
275
|
-
{
|
|
276
|
-
name: 'search',
|
|
277
|
-
args: { query: 'browser override test' },
|
|
278
|
-
id: 'call_browser_3',
|
|
279
|
-
type: 'tool_call',
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
}),
|
|
283
|
-
],
|
|
284
|
-
};
|
|
285
|
-
const result = await toolNode.invoke(input);
|
|
286
|
-
expect(result.messages).toHaveLength(1);
|
|
287
|
-
expect(result.messages[0].content).toContain('Result for: browser override test');
|
|
288
|
-
});
|
|
289
|
-
test('interactive context still requires approval (browser does not)', async () => {
|
|
290
|
-
// Verify that 'interactive' context DOES require approval (contrasts with browser)
|
|
291
|
-
const approvalEvents = [];
|
|
292
|
-
const { compiled, config } = createTestGraph([sendEmailTool], {
|
|
293
|
-
defaultPolicy: 'always',
|
|
294
|
-
executionContext: 'interactive',
|
|
295
|
-
}, [
|
|
296
|
-
// First model call: request tool call
|
|
297
|
-
[
|
|
298
|
-
JSON.stringify({
|
|
299
|
-
name: 'send_email',
|
|
300
|
-
args: { to: 'user@example.com', subject: 'Interactive' },
|
|
301
|
-
id: 'call_int_1',
|
|
302
|
-
type: 'tool_call',
|
|
303
|
-
}),
|
|
304
|
-
],
|
|
305
|
-
// Second model call: finish after tool result
|
|
306
|
-
['Done.'],
|
|
307
|
-
], (event) => {
|
|
308
|
-
approvalEvents.push(event);
|
|
309
|
-
// Auto-approve for test to complete
|
|
310
|
-
event.resolve({ approved: true });
|
|
311
|
-
});
|
|
312
|
-
const result = await compiled.invoke({ messages: [new HumanMessage('send an email')] }, config);
|
|
313
|
-
// Approval WAS required for interactive context
|
|
314
|
-
expect(approvalEvents.length).toBe(1);
|
|
315
|
-
expect(approvalEvents[0].toolName).toBe('send_email');
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
// ============================================================================
|
|
319
|
-
// Integration Tests: Event-driven approval flow
|
|
320
|
-
// ============================================================================
|
|
321
|
-
describe('HITL Tool Approval - Event-Driven Flow', () => {
|
|
322
|
-
jest.setTimeout(15000);
|
|
323
|
-
test('interactive mode with "always" policy dispatches approval event and waits', async () => {
|
|
324
|
-
const approvalEvents = [];
|
|
325
|
-
const { compiled, config } = createTestGraph([echoTool], {
|
|
326
|
-
defaultPolicy: 'always',
|
|
327
|
-
executionContext: 'interactive',
|
|
328
|
-
}, [
|
|
329
|
-
// First model response: request tool call
|
|
330
|
-
[
|
|
331
|
-
JSON.stringify({
|
|
332
|
-
name: 'echo',
|
|
333
|
-
args: { message: 'needs approval' },
|
|
334
|
-
id: 'call_int_1',
|
|
335
|
-
type: 'tool_call',
|
|
336
|
-
}),
|
|
337
|
-
],
|
|
338
|
-
// Second model response (after tool executes): final text
|
|
339
|
-
['The echo returned the result.'],
|
|
340
|
-
], (event) => {
|
|
341
|
-
// Auto-approve after capturing the event
|
|
342
|
-
approvalEvents.push(event);
|
|
343
|
-
event.resolve({ approved: true });
|
|
344
|
-
});
|
|
345
|
-
const result = await compiled.invoke({ messages: [new HumanMessage('Say hello')] }, config);
|
|
346
|
-
// Verify the approval event was dispatched
|
|
347
|
-
expect(approvalEvents).toHaveLength(1);
|
|
348
|
-
expect(approvalEvents[0].type).toBe('tool_approval_required');
|
|
349
|
-
expect(approvalEvents[0].toolName).toBe('echo');
|
|
350
|
-
expect(approvalEvents[0].toolArgs).toEqual({ message: 'needs approval' });
|
|
351
|
-
// Verify the tool executed after approval
|
|
352
|
-
const toolMessages = result.messages.filter((m) => m._getType() === 'tool');
|
|
353
|
-
expect(toolMessages.length).toBeGreaterThan(0);
|
|
354
|
-
expect(toolMessages[0].content.toString()).toContain('Echo: needs approval');
|
|
355
|
-
// Verify final model response
|
|
356
|
-
const lastMessage = result.messages[result.messages.length - 1];
|
|
357
|
-
expect(lastMessage.content).toContain('The echo returned the result');
|
|
358
|
-
});
|
|
359
|
-
test('denial stops tool execution and returns denial message', async () => {
|
|
360
|
-
const { compiled, config } = createTestGraph([sendEmailTool], {
|
|
361
|
-
defaultPolicy: 'always',
|
|
362
|
-
executionContext: 'interactive',
|
|
363
|
-
}, [
|
|
364
|
-
// First model response: request send email
|
|
365
|
-
[
|
|
366
|
-
JSON.stringify({
|
|
367
|
-
name: 'send_email',
|
|
368
|
-
args: { to: 'user@example.com', subject: 'Test' },
|
|
369
|
-
id: 'call_deny_1',
|
|
370
|
-
type: 'tool_call',
|
|
371
|
-
}),
|
|
372
|
-
],
|
|
373
|
-
// Second model response (after denial): acknowledge
|
|
374
|
-
['I understand, the email was not sent.'],
|
|
375
|
-
], (event) => {
|
|
376
|
-
// Deny the tool call
|
|
377
|
-
event.resolve({ approved: false, feedback: 'Not now' });
|
|
378
|
-
});
|
|
379
|
-
const result = await compiled.invoke({ messages: [new HumanMessage('Send an email')] }, config);
|
|
380
|
-
// Find the tool message - it should be a denial
|
|
381
|
-
const toolMessages = result.messages.filter((m) => m._getType() === 'tool');
|
|
382
|
-
expect(toolMessages.length).toBeGreaterThan(0);
|
|
383
|
-
const denialMsg = toolMessages.find((m) => m.content.toString().includes('denied'));
|
|
384
|
-
expect(denialMsg).toBeDefined();
|
|
385
|
-
expect(denialMsg.content.toString()).toContain('Not now');
|
|
386
|
-
expect(denialMsg.content.toString()).toContain('BLOCKED');
|
|
387
|
-
expect(denialMsg.content.toString()).toContain('MUST NOT call');
|
|
388
|
-
});
|
|
389
|
-
test('custom function override evaluates args to determine approval', async () => {
|
|
390
|
-
// Only require approval for emails to external domains
|
|
391
|
-
const approvalEvents = [];
|
|
392
|
-
const { compiled, config } = createTestGraph([sendEmailTool], {
|
|
393
|
-
defaultPolicy: 'never',
|
|
394
|
-
executionContext: 'interactive',
|
|
395
|
-
overrides: {
|
|
396
|
-
send_email: (_toolName, args) => {
|
|
397
|
-
const to = args.to;
|
|
398
|
-
return !to.endsWith('@internal.com');
|
|
399
|
-
},
|
|
400
|
-
},
|
|
401
|
-
}, [
|
|
402
|
-
// Tool call to internal domain (should NOT require approval)
|
|
403
|
-
[
|
|
404
|
-
JSON.stringify({
|
|
405
|
-
name: 'send_email',
|
|
406
|
-
args: { to: 'colleague@internal.com', subject: 'Internal' },
|
|
407
|
-
id: 'call_func_1',
|
|
408
|
-
type: 'tool_call',
|
|
409
|
-
}),
|
|
410
|
-
],
|
|
411
|
-
// Final text
|
|
412
|
-
['Email sent to internal address.'],
|
|
413
|
-
], (event) => {
|
|
414
|
-
approvalEvents.push(event);
|
|
415
|
-
event.resolve({ approved: true });
|
|
416
|
-
});
|
|
417
|
-
const result = await compiled.invoke({ messages: [new HumanMessage('Send internal email')] }, config);
|
|
418
|
-
// No approval events should have been dispatched (internal domain)
|
|
419
|
-
expect(approvalEvents).toHaveLength(0);
|
|
420
|
-
// Email should have been sent directly
|
|
421
|
-
const toolMessages = result.messages.filter((m) => m._getType() === 'tool');
|
|
422
|
-
expect(toolMessages.length).toBeGreaterThan(0);
|
|
423
|
-
expect(toolMessages[0].content.toString()).toContain('colleague@internal.com');
|
|
424
|
-
});
|
|
425
|
-
test('custom function override triggers approval for external email', async () => {
|
|
426
|
-
const approvalEvents = [];
|
|
427
|
-
const { compiled, config } = createTestGraph([sendEmailTool], {
|
|
428
|
-
defaultPolicy: 'never',
|
|
429
|
-
executionContext: 'interactive',
|
|
430
|
-
overrides: {
|
|
431
|
-
send_email: (_toolName, args) => {
|
|
432
|
-
const to = args.to;
|
|
433
|
-
return !to.endsWith('@internal.com');
|
|
434
|
-
},
|
|
435
|
-
},
|
|
436
|
-
}, [
|
|
437
|
-
// Tool call to external domain (SHOULD require approval)
|
|
438
|
-
[
|
|
439
|
-
JSON.stringify({
|
|
440
|
-
name: 'send_email',
|
|
441
|
-
args: { to: 'external@gmail.com', subject: 'External' },
|
|
442
|
-
id: 'call_func_2',
|
|
443
|
-
type: 'tool_call',
|
|
444
|
-
}),
|
|
445
|
-
],
|
|
446
|
-
// After approval
|
|
447
|
-
['Email sent to external address.'],
|
|
448
|
-
], (event) => {
|
|
449
|
-
approvalEvents.push(event);
|
|
450
|
-
event.resolve({ approved: true });
|
|
451
|
-
});
|
|
452
|
-
const result = await compiled.invoke({ messages: [new HumanMessage('Send external email')] }, config);
|
|
453
|
-
// Approval event should have been dispatched (external domain)
|
|
454
|
-
expect(approvalEvents).toHaveLength(1);
|
|
455
|
-
expect(approvalEvents[0].toolName).toBe('send_email');
|
|
456
|
-
expect(approvalEvents[0].toolArgs.to).toBe('external@gmail.com');
|
|
457
|
-
// Email should have been sent after approval
|
|
458
|
-
const lastMessage = result.messages[result.messages.length - 1];
|
|
459
|
-
expect(lastMessage.content).toContain('Email sent to external address');
|
|
460
|
-
});
|
|
461
|
-
test('modified args are used when human edits the tool call', async () => {
|
|
462
|
-
const { compiled, config } = createTestGraph([sendEmailTool], {
|
|
463
|
-
defaultPolicy: 'always',
|
|
464
|
-
executionContext: 'interactive',
|
|
465
|
-
}, [
|
|
466
|
-
// Model requests send email with original args
|
|
467
|
-
[
|
|
468
|
-
JSON.stringify({
|
|
469
|
-
name: 'send_email',
|
|
470
|
-
args: { to: 'wrong@example.com', subject: 'Original Subject' },
|
|
471
|
-
id: 'call_edit_1',
|
|
472
|
-
type: 'tool_call',
|
|
473
|
-
}),
|
|
474
|
-
],
|
|
475
|
-
// After tool execution with modified args
|
|
476
|
-
['Email sent with corrected details.'],
|
|
477
|
-
], (event) => {
|
|
478
|
-
// Approve with modified args
|
|
479
|
-
event.resolve({
|
|
480
|
-
approved: true,
|
|
481
|
-
modifiedArgs: {
|
|
482
|
-
to: 'correct@example.com',
|
|
483
|
-
subject: 'Corrected Subject',
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
const result = await compiled.invoke({ messages: [new HumanMessage('Send email')] }, config);
|
|
488
|
-
// Find the tool message to verify modified args were used
|
|
489
|
-
const toolMessages = result.messages.filter((m) => m._getType() === 'tool');
|
|
490
|
-
const sentMsg = toolMessages.find((m) => m.name === 'send_email' &&
|
|
491
|
-
m.content.toString().includes('correct@example.com'));
|
|
492
|
-
expect(sentMsg).toBeDefined();
|
|
493
|
-
expect(sentMsg.content.toString()).toContain('Corrected Subject');
|
|
494
|
-
});
|
|
495
|
-
test('scheduled context auto-approves without dispatching events', async () => {
|
|
496
|
-
const approvalEvents = [];
|
|
497
|
-
const { compiled, config } = createTestGraph([sendEmailTool], {
|
|
498
|
-
defaultPolicy: 'always',
|
|
499
|
-
executionContext: 'scheduled', // Scheduled = auto-approve
|
|
500
|
-
}, [
|
|
501
|
-
[
|
|
502
|
-
JSON.stringify({
|
|
503
|
-
name: 'send_email',
|
|
504
|
-
args: { to: 'user@example.com', subject: 'Scheduled Email' },
|
|
505
|
-
id: 'call_sched_1',
|
|
506
|
-
type: 'tool_call',
|
|
507
|
-
}),
|
|
508
|
-
],
|
|
509
|
-
['Scheduled email sent.'],
|
|
510
|
-
], (event) => {
|
|
511
|
-
approvalEvents.push(event);
|
|
512
|
-
event.resolve({ approved: true });
|
|
513
|
-
});
|
|
514
|
-
const result = await compiled.invoke({ messages: [new HumanMessage('Send scheduled email')] }, config);
|
|
515
|
-
// No approval events should be dispatched for scheduled execution
|
|
516
|
-
expect(approvalEvents).toHaveLength(0);
|
|
517
|
-
// Email should have been sent directly
|
|
518
|
-
const toolMessages = result.messages.filter((m) => m._getType() === 'tool');
|
|
519
|
-
expect(toolMessages.length).toBeGreaterThan(0);
|
|
520
|
-
expect(toolMessages[0].content.toString()).toContain('user@example.com');
|
|
521
|
-
});
|
|
522
|
-
test('multiple tool calls each get individual approval', async () => {
|
|
523
|
-
const approvalEvents = [];
|
|
524
|
-
const toolNode = new ToolNode({
|
|
525
|
-
tools: [echoTool, sendEmailTool],
|
|
526
|
-
toolApprovalConfig: {
|
|
527
|
-
defaultPolicy: 'always',
|
|
528
|
-
executionContext: 'interactive',
|
|
529
|
-
},
|
|
530
|
-
});
|
|
531
|
-
const StateAnnotation = Annotation.Root({
|
|
532
|
-
messages: Annotation({
|
|
533
|
-
reducer: messagesStateReducer,
|
|
534
|
-
default: () => [],
|
|
535
|
-
}),
|
|
536
|
-
});
|
|
537
|
-
let callCount = 0;
|
|
538
|
-
const callModel = async (_state) => {
|
|
539
|
-
callCount++;
|
|
540
|
-
if (callCount === 1) {
|
|
541
|
-
return {
|
|
542
|
-
messages: [
|
|
543
|
-
new AIMessage({
|
|
544
|
-
content: '',
|
|
545
|
-
tool_calls: [
|
|
546
|
-
{
|
|
547
|
-
name: 'echo',
|
|
548
|
-
args: { message: 'test' },
|
|
549
|
-
id: 'c1',
|
|
550
|
-
type: 'tool_call',
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
name: 'send_email',
|
|
554
|
-
args: { to: 'a@b.com', subject: 'Hi' },
|
|
555
|
-
id: 'c2',
|
|
556
|
-
type: 'tool_call',
|
|
557
|
-
},
|
|
558
|
-
],
|
|
559
|
-
}),
|
|
560
|
-
],
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
return { messages: [new AIMessage({ content: 'Both done.' })] };
|
|
564
|
-
};
|
|
565
|
-
const routeMessage = (state) => toolsCondition(state, 'tools');
|
|
566
|
-
const workflow = new StateGraph(StateAnnotation)
|
|
567
|
-
.addNode('agent', callModel)
|
|
568
|
-
.addNode('tools', toolNode)
|
|
569
|
-
.addEdge(START, 'agent')
|
|
570
|
-
.addConditionalEdges('agent', routeMessage)
|
|
571
|
-
.addEdge('tools', 'agent');
|
|
572
|
-
const compiled = workflow.compile();
|
|
573
|
-
const config = {
|
|
574
|
-
version: 'v2',
|
|
575
|
-
callbacks: [
|
|
576
|
-
{
|
|
577
|
-
handleCustomEvent: async (eventName, data) => {
|
|
578
|
-
if (eventName === GraphEvents.ON_TOOL_APPROVAL_REQUIRED) {
|
|
579
|
-
const event = data;
|
|
580
|
-
approvalEvents.push(event);
|
|
581
|
-
event.resolve({ approved: true });
|
|
582
|
-
}
|
|
583
|
-
},
|
|
584
|
-
},
|
|
585
|
-
],
|
|
586
|
-
};
|
|
587
|
-
const result = await compiled.invoke({ messages: [new HumanMessage('Do both')] }, config);
|
|
588
|
-
// Both tools should have triggered approval events
|
|
589
|
-
expect(approvalEvents).toHaveLength(2);
|
|
590
|
-
expect(approvalEvents.map((e) => e.toolName).sort()).toEqual([
|
|
591
|
-
'echo',
|
|
592
|
-
'send_email',
|
|
593
|
-
]);
|
|
594
|
-
// Both tools should have executed
|
|
595
|
-
const toolMessages = result.messages.filter((m) => m._getType() === 'tool');
|
|
596
|
-
expect(toolMessages).toHaveLength(2);
|
|
597
|
-
});
|
|
598
|
-
});
|
|
599
|
-
// ============================================================================
|
|
600
|
-
// Type Tests: ToolApprovalConfig shape validation
|
|
601
|
-
// ============================================================================
|
|
602
|
-
describe('HITL Types', () => {
|
|
603
|
-
test('ToolApprovalConfig accepts valid configurations', () => {
|
|
604
|
-
const config1 = {
|
|
605
|
-
defaultPolicy: 'always',
|
|
606
|
-
executionContext: 'interactive',
|
|
607
|
-
};
|
|
608
|
-
expect(config1.defaultPolicy).toBe('always');
|
|
609
|
-
const config2 = {
|
|
610
|
-
defaultPolicy: 'never',
|
|
611
|
-
executionContext: 'scheduled',
|
|
612
|
-
};
|
|
613
|
-
expect(config2.executionContext).toBe('scheduled');
|
|
614
|
-
const config3 = {
|
|
615
|
-
defaultPolicy: 'always',
|
|
616
|
-
executionContext: 'interactive',
|
|
617
|
-
overrides: {
|
|
618
|
-
search: 'never',
|
|
619
|
-
send_email: 'always',
|
|
620
|
-
custom_tool: (_toolName, args) => args.dangerous === true,
|
|
621
|
-
},
|
|
622
|
-
};
|
|
623
|
-
expect(config3.overrides).toBeDefined();
|
|
624
|
-
expect(config3.overrides.search).toBe('never');
|
|
625
|
-
});
|
|
626
|
-
test('ToolApprovalRequest has correct shape', () => {
|
|
627
|
-
const request = {
|
|
628
|
-
type: 'tool_approval_required',
|
|
629
|
-
toolCallId: 'call_123',
|
|
630
|
-
toolName: 'send_email',
|
|
631
|
-
toolArgs: { to: 'user@example.com' },
|
|
632
|
-
agentId: 'agent-1',
|
|
633
|
-
description: 'Send email to user',
|
|
634
|
-
};
|
|
635
|
-
expect(request.type).toBe('tool_approval_required');
|
|
636
|
-
expect(request.toolName).toBe('send_email');
|
|
637
|
-
});
|
|
638
|
-
test('ToolApprovalResponse covers all response types', () => {
|
|
639
|
-
const approved = { approved: true };
|
|
640
|
-
expect(approved.approved).toBe(true);
|
|
641
|
-
const denied = {
|
|
642
|
-
approved: false,
|
|
643
|
-
feedback: 'Too risky',
|
|
644
|
-
};
|
|
645
|
-
expect(denied.feedback).toBe('Too risky');
|
|
646
|
-
const edited = {
|
|
647
|
-
approved: true,
|
|
648
|
-
modifiedArgs: { to: 'corrected@example.com' },
|
|
649
|
-
};
|
|
650
|
-
expect(edited.modifiedArgs).toBeDefined();
|
|
651
|
-
});
|
|
652
|
-
test('ToolApprovalRule function receives toolName as first argument', () => {
|
|
653
|
-
// Verify the function-based policy can use toolName for classification
|
|
654
|
-
const actionPrefixes = ['send', 'create', 'delete', 'post'];
|
|
655
|
-
const policy = (toolName, _args) => {
|
|
656
|
-
const lower = toolName.toLowerCase();
|
|
657
|
-
return actionPrefixes.some((prefix) => lower.includes(prefix));
|
|
658
|
-
};
|
|
659
|
-
// Action tools should require approval
|
|
660
|
-
expect(policy('send_email', { to: 'a@b.com' })).toBe(true);
|
|
661
|
-
expect(policy('create_document', {})).toBe(true);
|
|
662
|
-
expect(policy('delete_file', {})).toBe(true);
|
|
663
|
-
expect(policy('post_message', {})).toBe(true);
|
|
664
|
-
// Read-only tools should not
|
|
665
|
-
expect(policy('search', { query: 'test' })).toBe(false);
|
|
666
|
-
expect(policy('list_emails', {})).toBe(false);
|
|
667
|
-
expect(policy('get_calendar', {})).toBe(false);
|
|
668
|
-
const config = {
|
|
669
|
-
defaultPolicy: policy,
|
|
670
|
-
executionContext: 'interactive',
|
|
671
|
-
};
|
|
672
|
-
expect(typeof config.defaultPolicy).toBe('function');
|
|
673
|
-
});
|
|
674
|
-
});
|
|
675
|
-
//# sourceMappingURL=ToolApproval.test.js.map
|