@inspectr/mcplab 1.30.0 → 1.31.1
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/README.md +2 -2
- package/dist/app/assets/index-Co-US91U.js +267 -0
- package/dist/app/assets/index-D5OYchKq.css +2 -0
- package/dist/app/index.html +2 -2
- package/dist/app-server/app-context.d.ts +6 -22
- package/dist/app-server/app-context.d.ts.map +1 -1
- package/dist/app-server/assistant-agent.d.ts +10 -0
- package/dist/app-server/assistant-agent.d.ts.map +1 -0
- package/dist/app-server/assistant-agent.js +29 -0
- package/dist/app-server/assistant-agent.js.map +1 -0
- package/dist/app-server/assistant-common.d.ts +7 -34
- package/dist/app-server/assistant-common.d.ts.map +1 -1
- package/dist/app-server/assistant-common.js +33 -194
- package/dist/app-server/assistant-common.js.map +1 -1
- package/dist/app-server/assistant-events.d.ts +10 -6
- package/dist/app-server/assistant-events.d.ts.map +1 -1
- package/dist/app-server/assistant-events.js +23 -11
- package/dist/app-server/assistant-events.js.map +1 -1
- package/dist/app-server/assistant-graph-nodes.d.ts +226 -0
- package/dist/app-server/assistant-graph-nodes.d.ts.map +1 -0
- package/dist/app-server/assistant-graph-nodes.js +365 -0
- package/dist/app-server/assistant-graph-nodes.js.map +1 -0
- package/dist/app-server/assistant-graph-state.d.ts +64 -0
- package/dist/app-server/assistant-graph-state.d.ts.map +1 -0
- package/dist/app-server/assistant-graph-state.js +137 -0
- package/dist/app-server/assistant-graph-state.js.map +1 -0
- package/dist/app-server/assistant-mcp-context-tools.d.ts +17 -0
- package/dist/app-server/assistant-mcp-context-tools.d.ts.map +1 -0
- package/dist/app-server/assistant-mcp-context-tools.js +149 -0
- package/dist/app-server/assistant-mcp-context-tools.js.map +1 -0
- package/dist/app-server/assistant-model.d.ts +24 -0
- package/dist/app-server/assistant-model.d.ts.map +1 -0
- package/dist/app-server/assistant-model.js +240 -0
- package/dist/app-server/assistant-model.js.map +1 -0
- package/dist/app-server/assistant-runtime-contract.d.ts +46 -0
- package/dist/app-server/assistant-runtime-contract.d.ts.map +1 -0
- package/dist/app-server/assistant-runtime-contract.js +2 -0
- package/dist/app-server/assistant-runtime-contract.js.map +1 -0
- package/dist/app-server/assistant-runtime-policy.d.ts +22 -0
- package/dist/app-server/assistant-runtime-policy.d.ts.map +1 -0
- package/dist/app-server/assistant-runtime-policy.js +71 -0
- package/dist/app-server/assistant-runtime-policy.js.map +1 -0
- package/dist/app-server/assistant-service.d.ts +23 -0
- package/dist/app-server/assistant-service.d.ts.map +1 -0
- package/dist/app-server/assistant-service.js +185 -0
- package/dist/app-server/assistant-service.js.map +1 -0
- package/dist/app-server/assistant-session-routes.d.ts +25 -0
- package/dist/app-server/assistant-session-routes.d.ts.map +1 -0
- package/dist/app-server/assistant-session-routes.js +210 -0
- package/dist/app-server/assistant-session-routes.js.map +1 -0
- package/dist/app-server/assistant-tracing.d.ts +5 -0
- package/dist/app-server/assistant-tracing.d.ts.map +1 -0
- package/dist/app-server/assistant-tracing.js +30 -0
- package/dist/app-server/assistant-tracing.js.map +1 -0
- package/dist/app-server/browser-provider-learning.js +1 -1
- package/dist/app-server/browser-provider-learning.js.map +1 -1
- package/dist/app-server/inspectr-session-manager.d.ts +28 -34
- package/dist/app-server/inspectr-session-manager.d.ts.map +1 -1
- package/dist/app-server/inspectr-session-manager.js +13 -4
- package/dist/app-server/inspectr-session-manager.js.map +1 -1
- package/dist/app-server/langchain-assistant-runtime.d.ts +11 -0
- package/dist/app-server/langchain-assistant-runtime.d.ts.map +1 -0
- package/dist/app-server/langchain-assistant-runtime.js +852 -0
- package/dist/app-server/langchain-assistant-runtime.js.map +1 -0
- package/dist/app-server/one-shot-json.d.ts +26 -0
- package/dist/app-server/one-shot-json.d.ts.map +1 -0
- package/dist/app-server/one-shot-json.js +168 -0
- package/dist/app-server/one-shot-json.js.map +1 -0
- package/dist/app-server/result-assistant-domain.d.ts +46 -48
- package/dist/app-server/result-assistant-domain.d.ts.map +1 -1
- package/dist/app-server/result-assistant-domain.js +148 -133
- package/dist/app-server/result-assistant-domain.js.map +1 -1
- package/dist/app-server/result-assistant.d.ts +2 -2
- package/dist/app-server/result-assistant.d.ts.map +1 -1
- package/dist/app-server/result-assistant.js +43 -311
- package/dist/app-server/result-assistant.js.map +1 -1
- package/dist/app-server/router.d.ts.map +1 -1
- package/dist/app-server/router.js +14 -16
- package/dist/app-server/router.js.map +1 -1
- package/dist/app-server/runs-routes.js +1 -1
- package/dist/app-server/runs-routes.js.map +1 -1
- package/dist/app-server/scenario-assistant-domain.d.ts +43 -136
- package/dist/app-server/scenario-assistant-domain.d.ts.map +1 -1
- package/dist/app-server/scenario-assistant-domain.js +377 -215
- package/dist/app-server/scenario-assistant-domain.js.map +1 -1
- package/dist/app-server/scenario-assistant.d.ts +3 -3
- package/dist/app-server/scenario-assistant.d.ts.map +1 -1
- package/dist/app-server/scenario-assistant.js +52 -406
- package/dist/app-server/scenario-assistant.js.map +1 -1
- package/dist/app-server/settings-store.d.ts.map +1 -1
- package/dist/app-server/settings-store.js +2 -5
- package/dist/app-server/settings-store.js.map +1 -1
- package/dist/app-server/tool-analysis-domain.d.ts +1 -1
- package/dist/app-server/tool-analysis-domain.d.ts.map +1 -1
- package/dist/app-server/tool-analysis-domain.js +5 -4
- package/dist/app-server/tool-analysis-domain.js.map +1 -1
- package/dist/app-server/types.d.ts +1 -1
- package/dist/app-server/types.d.ts.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/package.json +10 -4
- package/dist/app/assets/index-8I9ZyPIK.js +0 -243
- package/dist/app/assets/index-jX7LoIQP.css +0 -2
|
@@ -1,37 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { McpClientManager } from '@inspectr/mcplab-core';
|
|
2
|
+
import { ASSISTANT_TOOL_TIMEOUT_MESSAGE, assistantToolDisplay, ASSISTANT_TOOL_TIMEOUT_MS, cleanupSessionsByTtl, makeAssistantToolPublicName, touchSession, truncateJson, withTimeout } from './assistant-common.js';
|
|
3
3
|
import { endAssistantSseClients } from './assistant-events.js';
|
|
4
|
-
|
|
4
|
+
import { setupAssistantRuntimeSession } from './assistant-service.js';
|
|
5
|
+
import { createMcpContextTools, MCP_CONTEXT_TOOL_NAMES, mcpContextToolGuidance } from './assistant-mcp-context-tools.js';
|
|
6
|
+
import { APPROVAL_GUIDANCE } from './assistant-runtime-policy.js';
|
|
7
|
+
function createScenarioAssistantResources(agentConfig, context) {
|
|
8
|
+
return {
|
|
9
|
+
agentConfig,
|
|
10
|
+
mcp: new McpClientManager(),
|
|
11
|
+
tools: [],
|
|
12
|
+
toolPublicMap: new Map(),
|
|
13
|
+
contextServers: { resources: [], prompts: [] },
|
|
14
|
+
context
|
|
15
|
+
};
|
|
16
|
+
}
|
|
5
17
|
const SCENARIO_ASSISTANT_SESSION_TTL_MS = 30 * 60 * 1000;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
// The hard TTL also aborts work in abandoned sessions; active operations refresh lastTouchedAt.
|
|
19
|
+
export function cleanupScenarioAssistantSessions(sessions, now = Date.now()) {
|
|
20
|
+
cleanupSessionsByTtl(sessions, SCENARIO_ASSISTANT_SESSION_TTL_MS, now, (session) => {
|
|
21
|
+
void Promise.resolve(session.runtime.dispose()).catch(() => { });
|
|
22
|
+
endAssistantSseClients(session);
|
|
23
|
+
});
|
|
10
24
|
}
|
|
11
|
-
export function
|
|
25
|
+
export function touchScenarioAssistantSession(session) {
|
|
12
26
|
touchSession(session);
|
|
13
27
|
}
|
|
14
|
-
export function
|
|
28
|
+
export function scenarioAssistantSessionView(session) {
|
|
29
|
+
const runtimeSnapshot = session.runtime.getSnapshot();
|
|
15
30
|
return {
|
|
16
31
|
id: session.id,
|
|
17
32
|
createdAt: new Date(session.createdAt).toISOString(),
|
|
18
33
|
updatedAt: new Date(session.lastTouchedAt).toISOString(),
|
|
19
34
|
selectedAssistantAgentName: session.selectedAssistantAgentName,
|
|
20
|
-
model: session.
|
|
21
|
-
provider: session.
|
|
35
|
+
model: session.model,
|
|
36
|
+
provider: session.provider,
|
|
22
37
|
warnings: session.warnings,
|
|
23
|
-
toolsLoaded: session.
|
|
24
|
-
toolServers:
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
toolsLoaded: session.toolsLoaded,
|
|
39
|
+
toolServers: session.toolServers,
|
|
40
|
+
revision: runtimeSnapshot.revision,
|
|
41
|
+
messages: runtimeSnapshot.messages,
|
|
42
|
+
pendingToolCalls: runtimeSnapshot.pendingToolCalls,
|
|
43
|
+
activeTurnId: runtimeSnapshot.activeTurnId,
|
|
44
|
+
streamingMessage: runtimeSnapshot.streamingMessage
|
|
27
45
|
};
|
|
28
46
|
}
|
|
29
|
-
function assistantSystemPrompt(session) {
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
32
|
-
const { scenario } =
|
|
33
|
-
const toolLines =
|
|
34
|
-
const mapping =
|
|
47
|
+
function assistantSystemPrompt(session, state) {
|
|
48
|
+
if (state.systemPromptCache)
|
|
49
|
+
return state.systemPromptCache;
|
|
50
|
+
const { scenario } = state.context;
|
|
51
|
+
const toolLines = state.tools.map((tool) => {
|
|
52
|
+
const mapping = state.toolPublicMap.get(tool.name);
|
|
35
53
|
const schemaText = tool.inputSchema ? truncateJson(tool.inputSchema, 500) : '{}';
|
|
36
54
|
return `- ${tool.name} (server=${mapping?.server ?? 'unknown'}, tool=${mapping?.tool ?? tool.name}) schema=${schemaText}`;
|
|
37
55
|
});
|
|
@@ -39,18 +57,17 @@ function assistantSystemPrompt(session) {
|
|
|
39
57
|
'You are a Scenario Authoring Assistant for MCP evaluation scenarios.',
|
|
40
58
|
'Goal: help the user author deterministic scenario prompt, Checks (pass/fail), and Value Capture Rules.',
|
|
41
59
|
'Use the available MCP tools and schemas to ground suggestions.',
|
|
42
|
-
|
|
60
|
+
APPROVAL_GUIDANCE,
|
|
61
|
+
...mcpContextToolGuidance(state.contextServers),
|
|
43
62
|
'Tool selection policy: prefer search_* tools first for retrieval; fall back to list_* tools when the query is unknown, broad, or full coverage is required.',
|
|
44
63
|
'Use user-facing terminology in your text responses: "Checks" and "Value Capture Rules" (not "eval rules" / "extract rules").',
|
|
45
|
-
'Respond
|
|
46
|
-
|
|
47
|
-
`{"type":"tool_call_request","text":"...","toolCall":{"name":"PUBLIC_TOOL_NAME","arguments":{}},"suggestions":{...optional...}}`,
|
|
48
|
-
'For suggestions, use keys: prompt, evalRules, extractRules, notes.',
|
|
64
|
+
'Respond naturally in ordinary prose. Use the local propose_scenario_changes tool whenever you propose scenario edits.',
|
|
65
|
+
'The local proposal tool accepts these keys: prompt, evalRules, extractRules, notes.',
|
|
49
66
|
'prompt: { replacement: string, rationale?: string }',
|
|
50
67
|
'evalRules: { replacement: [{ type, tool?, value?, sequence?, path?, equals?, label?, prompt? }...], rationale?: string }',
|
|
51
68
|
'extractRules: { replacement: [{ name, pattern }...], rationale?: string }',
|
|
52
|
-
'
|
|
53
|
-
'
|
|
69
|
+
'Use the proposal tool with a complete replacement for every field you propose to change, plus a rationale.',
|
|
70
|
+
'Include only the fields you are changing; omit the others entirely.',
|
|
54
71
|
'Keep rule types limited to: required_tool, forbidden_tool, tool_sequence, tool_input_contains, tool_input_regex, tool_input_jsonpath, response_contains, response_not_contains, response_starts_with, response_ends_with, response_equals, response_regex, response_jsonpath, response_jsonpath_exists, response_jsonpath_not_exists, agent_check.',
|
|
55
72
|
'Optional checks you may suggest when useful: tool_sequence for ordered tool-call sequences and agent_check for semantic, fuzzy, or intent-based validation.',
|
|
56
73
|
'Use tool_sequence when the order of tool calls matters and you want to validate that a sequence appears in the run in order, even if other tools happen between the listed tools.',
|
|
@@ -77,7 +94,7 @@ function assistantSystemPrompt(session) {
|
|
|
77
94
|
? `Available MCP tools:\n${toolLines.join('\n')}`
|
|
78
95
|
: 'No MCP tools available.'
|
|
79
96
|
].join('\n');
|
|
80
|
-
|
|
97
|
+
state.systemPromptCache = prompt;
|
|
81
98
|
return prompt;
|
|
82
99
|
}
|
|
83
100
|
function formatAssistantMcpPreloadError(serverName, error) {
|
|
@@ -93,33 +110,128 @@ function formatAssistantMcpPreloadError(serverName, error) {
|
|
|
93
110
|
}
|
|
94
111
|
return `Scenario Assistant MCP preload failed for server '${serverName}': ${raw}`;
|
|
95
112
|
}
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
function createScenarioAssistantSetup(session, agentConfig, context) {
|
|
114
|
+
const state = createScenarioAssistantResources(agentConfig, context);
|
|
115
|
+
let disposePromise;
|
|
116
|
+
const dispose = () => {
|
|
117
|
+
disposePromise ??= Promise.resolve().then(() => state.mcp.disconnectAll());
|
|
118
|
+
return disposePromise;
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
dispose,
|
|
122
|
+
async preload(serversByName, selectedServerNames, options) {
|
|
123
|
+
const usedNames = new Set();
|
|
124
|
+
for (const serverName of selectedServerNames) {
|
|
125
|
+
const server = serversByName[serverName];
|
|
126
|
+
if (!server) {
|
|
127
|
+
session.warnings.push(`Scenario Assistant: server '${serverName}' not found in config.`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
await state.mcp.connectAll({ [serverName]: server }, undefined, {
|
|
132
|
+
serverAuthHeaders: options?.serverAuthHeaders
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
session.warnings.push(formatAssistantMcpPreloadError(serverName, error));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// A server may offer only resources or prompts, so detect them independently of tools.
|
|
140
|
+
let features = { tools: true, resources: false, prompts: false };
|
|
141
|
+
try {
|
|
142
|
+
features = state.mcp.getServerFeatures(serverName);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Treat the server as offering tools only.
|
|
146
|
+
}
|
|
147
|
+
if (features.resources)
|
|
148
|
+
state.contextServers.resources.push(serverName);
|
|
149
|
+
if (features.prompts)
|
|
150
|
+
state.contextServers.prompts.push(serverName);
|
|
151
|
+
if (!features.tools)
|
|
152
|
+
continue;
|
|
153
|
+
try {
|
|
154
|
+
const tools = await state.mcp.listTools(serverName);
|
|
155
|
+
for (const tool of tools) {
|
|
156
|
+
const publicName = makeAssistantToolPublicName(serverName, tool.name, usedNames);
|
|
157
|
+
state.toolPublicMap.set(publicName, {
|
|
158
|
+
server: serverName,
|
|
159
|
+
tool: tool.name,
|
|
160
|
+
display: assistantToolDisplay(tool)
|
|
161
|
+
});
|
|
162
|
+
state.tools.push({
|
|
163
|
+
...tool,
|
|
164
|
+
name: publicName,
|
|
165
|
+
description: `${tool.description ?? ''}\n[server=${serverName} tool=${tool.name}]`.trim()
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
session.warnings.push(formatAssistantMcpPreloadError(serverName, error));
|
|
171
|
+
}
|
|
117
172
|
}
|
|
173
|
+
session.toolsLoaded = state.tools.length;
|
|
174
|
+
session.toolServers = Array.from(new Set(state.tools.map((tool) => tool.name.split('__')[0])));
|
|
175
|
+
},
|
|
176
|
+
createRuntimeDefinition() {
|
|
177
|
+
return {
|
|
178
|
+
assistant: 'scenario',
|
|
179
|
+
agent: state.agentConfig,
|
|
180
|
+
context: state.context,
|
|
181
|
+
initialMessages: session.initialMessages,
|
|
182
|
+
dispose,
|
|
183
|
+
getSystemPrompt: () => assistantSystemPrompt(session, state),
|
|
184
|
+
tools: [
|
|
185
|
+
...state.tools.map((tool) => {
|
|
186
|
+
const mapping = state.toolPublicMap.get(tool.name);
|
|
187
|
+
return {
|
|
188
|
+
name: tool.name,
|
|
189
|
+
server: mapping.server,
|
|
190
|
+
tool: mapping.tool,
|
|
191
|
+
description: tool.description,
|
|
192
|
+
inputSchema: tool.inputSchema,
|
|
193
|
+
display: mapping.display,
|
|
194
|
+
execute: async (arguments_, signal) => withTimeout(() => state.mcp.callTool(mapping.server, mapping.tool, arguments_, { signal }), ASSISTANT_TOOL_TIMEOUT_MS, ASSISTANT_TOOL_TIMEOUT_MESSAGE, signal)
|
|
195
|
+
};
|
|
196
|
+
}),
|
|
197
|
+
{
|
|
198
|
+
name: PROPOSE_SCENARIO_CHANGES,
|
|
199
|
+
server: 'local',
|
|
200
|
+
tool: PROPOSE_SCENARIO_CHANGES,
|
|
201
|
+
description: 'Submit structured scenario changes for the user to review and apply.',
|
|
202
|
+
display: { title: 'Propose scenario changes' },
|
|
203
|
+
inputSchema: proposalSchema,
|
|
204
|
+
execute: async (args, _signal) => validateScenarioAssistantProposal(args, state.toolPublicMap),
|
|
205
|
+
extractProposal: (result) => result
|
|
206
|
+
},
|
|
207
|
+
...createMcpContextTools(state.mcp, state.contextServers)
|
|
208
|
+
],
|
|
209
|
+
approvalPolicy: (name) => name === PROPOSE_SCENARIO_CHANGES
|
|
210
|
+
? 'local'
|
|
211
|
+
: MCP_CONTEXT_TOOL_NAMES.includes(name) ||
|
|
212
|
+
state.tools.find((tool) => tool.name === name)?.annotations?.readOnlyHint === true
|
|
213
|
+
? 'automatic'
|
|
214
|
+
: 'approval_required'
|
|
215
|
+
};
|
|
118
216
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
export async function setupScenarioAssistantSession(params) {
|
|
220
|
+
const setup = createScenarioAssistantSetup(params.session, params.agentConfig, params.context);
|
|
221
|
+
return setupAssistantRuntimeSession({
|
|
222
|
+
session: params.session,
|
|
223
|
+
factory: params.assistantRuntimeFactory,
|
|
224
|
+
disposeSetup: setup.dispose,
|
|
225
|
+
prepare: async () => {
|
|
226
|
+
await setup.preload(params.serversByName, params.selectedServerNames, {
|
|
227
|
+
serverAuthHeaders: params.serverAuthHeaders
|
|
228
|
+
});
|
|
229
|
+
return setup.createRuntimeDefinition();
|
|
230
|
+
},
|
|
231
|
+
initialEvents: params.initialEvents,
|
|
232
|
+
onEvent: params.onEvent,
|
|
233
|
+
onActivity: params.onActivity
|
|
234
|
+
});
|
|
123
235
|
}
|
|
124
236
|
function normalizeEvalRuleToolNames(suggestions, toolPublicMap) {
|
|
125
237
|
if (!suggestions?.evalRules?.replacement)
|
|
@@ -414,176 +526,226 @@ export function normalizeScenarioAssistantEvalRules(replacement) {
|
|
|
414
526
|
}
|
|
415
527
|
return lintContradictoryEvalRules(intentDeduplicateEvalRules(collapseOffByOneCountGuards(deduped)));
|
|
416
528
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
529
|
+
const PROPOSE_SCENARIO_CHANGES = 'propose_scenario_changes';
|
|
530
|
+
const ruleTypes = new Set([
|
|
531
|
+
'required_tool',
|
|
532
|
+
'forbidden_tool',
|
|
533
|
+
'tool_sequence',
|
|
534
|
+
'tool_input_contains',
|
|
535
|
+
'tool_input_regex',
|
|
536
|
+
'tool_input_jsonpath',
|
|
537
|
+
'response_contains',
|
|
538
|
+
'response_not_contains',
|
|
539
|
+
'response_starts_with',
|
|
540
|
+
'response_ends_with',
|
|
541
|
+
'response_equals',
|
|
542
|
+
'response_regex',
|
|
543
|
+
'response_jsonpath',
|
|
544
|
+
'response_jsonpath_exists',
|
|
545
|
+
'response_jsonpath_not_exists',
|
|
546
|
+
'agent_check'
|
|
547
|
+
]);
|
|
548
|
+
const proposalSchema = {
|
|
549
|
+
type: 'object',
|
|
550
|
+
additionalProperties: false,
|
|
551
|
+
minProperties: 1,
|
|
552
|
+
properties: {
|
|
553
|
+
prompt: {
|
|
554
|
+
type: 'object',
|
|
555
|
+
description: 'Omit this field if you are not changing it; do not send null or empty values.',
|
|
556
|
+
additionalProperties: false,
|
|
557
|
+
required: ['replacement'],
|
|
558
|
+
properties: { replacement: { type: 'string' }, rationale: { type: 'string' } }
|
|
559
|
+
},
|
|
560
|
+
evalRules: {
|
|
561
|
+
type: 'object',
|
|
562
|
+
description: 'Omit this field if you are not changing it; do not send null or empty values. Use replacement: [] to remove all checks.',
|
|
563
|
+
additionalProperties: false,
|
|
564
|
+
required: ['replacement'],
|
|
565
|
+
properties: {
|
|
566
|
+
replacement: {
|
|
567
|
+
type: 'array',
|
|
568
|
+
items: {
|
|
569
|
+
type: 'object',
|
|
570
|
+
additionalProperties: false,
|
|
571
|
+
required: ['type'],
|
|
572
|
+
properties: {
|
|
573
|
+
type: { type: 'string', enum: [...ruleTypes] },
|
|
574
|
+
tool: { type: 'string' },
|
|
575
|
+
value: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }] },
|
|
576
|
+
sequence: { type: 'array', items: { type: 'string' } },
|
|
577
|
+
path: { type: 'string' },
|
|
578
|
+
equals: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }] },
|
|
579
|
+
label: { type: 'string' },
|
|
580
|
+
prompt: { type: 'string' }
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
rationale: { type: 'string' }
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
extractRules: {
|
|
588
|
+
type: 'object',
|
|
589
|
+
description: 'Omit this field if you are not changing it; do not send null or empty values. Use replacement: [] to remove all capture rules.',
|
|
590
|
+
additionalProperties: false,
|
|
591
|
+
required: ['replacement'],
|
|
592
|
+
properties: {
|
|
593
|
+
replacement: {
|
|
594
|
+
type: 'array',
|
|
595
|
+
items: {
|
|
596
|
+
type: 'object',
|
|
597
|
+
additionalProperties: false,
|
|
598
|
+
required: ['name', 'pattern'],
|
|
599
|
+
properties: { name: { type: 'string' }, pattern: { type: 'string' } }
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
rationale: { type: 'string' }
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
notes: {
|
|
606
|
+
type: 'array',
|
|
607
|
+
description: 'Optional notes accompanying changes. Omit if unused; notes alone are not a change.',
|
|
608
|
+
items: { type: 'string' }
|
|
441
609
|
}
|
|
442
610
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
611
|
+
};
|
|
612
|
+
export function validateScenarioAssistantProposal(input, toolPublicMap) {
|
|
613
|
+
const invalid = (path, issue) => {
|
|
614
|
+
throw new Error(`Invalid scenario proposal at ${path}: ${issue}`);
|
|
615
|
+
};
|
|
616
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
617
|
+
return invalid('proposal', 'expected an object');
|
|
618
|
+
const isUnchangedPlaceholder = (field) => field === null ||
|
|
619
|
+
field === '' ||
|
|
620
|
+
(typeof field === 'object' && !Array.isArray(field) && Object.keys(field).length === 0);
|
|
621
|
+
const value = Object.fromEntries(Object.entries(input).filter(([, field]) => !isUnchangedPlaceholder(field)));
|
|
622
|
+
const checkKeys = (object, allowed, path) => {
|
|
623
|
+
const unknown = Object.keys(object).find((key) => !allowed.includes(key));
|
|
624
|
+
if (unknown !== undefined)
|
|
625
|
+
invalid(`${path}.${unknown}`, 'unknown key');
|
|
626
|
+
};
|
|
627
|
+
checkKeys(value, ['prompt', 'evalRules', 'extractRules', 'notes'], 'proposal');
|
|
628
|
+
const result = {};
|
|
629
|
+
const validRationale = (obj) => obj.rationale === undefined || typeof obj.rationale === 'string';
|
|
630
|
+
if (value.prompt !== undefined) {
|
|
631
|
+
if (!value.prompt || typeof value.prompt !== 'object' || Array.isArray(value.prompt))
|
|
632
|
+
return invalid('prompt', 'expected an object');
|
|
633
|
+
const p = value.prompt;
|
|
634
|
+
checkKeys(p, ['replacement', 'rationale'], 'prompt');
|
|
635
|
+
if (typeof p.replacement !== 'string' || !p.replacement.trim())
|
|
636
|
+
return invalid('prompt.replacement', 'expected a non-empty string');
|
|
637
|
+
if (!validRationale(p))
|
|
638
|
+
return invalid('prompt.rationale', 'expected a string');
|
|
639
|
+
result.prompt = {
|
|
640
|
+
replacement: p.replacement,
|
|
641
|
+
...(p.rationale === undefined ? {} : { rationale: p.rationale })
|
|
642
|
+
};
|
|
468
643
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
644
|
+
if (value.evalRules !== undefined) {
|
|
645
|
+
if (!value.evalRules || typeof value.evalRules !== 'object' || Array.isArray(value.evalRules))
|
|
646
|
+
return invalid('evalRules', 'expected an object');
|
|
647
|
+
const e = value.evalRules;
|
|
648
|
+
checkKeys(e, ['replacement', 'rationale'], 'evalRules');
|
|
649
|
+
if (!Array.isArray(e.replacement))
|
|
650
|
+
return invalid('evalRules.replacement', 'expected an array');
|
|
651
|
+
if (!validRationale(e))
|
|
652
|
+
return invalid('evalRules.rationale', 'expected a string');
|
|
653
|
+
for (const [index, rule] of e.replacement.entries()) {
|
|
654
|
+
const path = `evalRules.replacement[${index}]`;
|
|
655
|
+
if (!rule || typeof rule !== 'object' || Array.isArray(rule))
|
|
656
|
+
return invalid(path, 'expected an object');
|
|
657
|
+
const r = rule;
|
|
658
|
+
checkKeys(r, ['type', 'tool', 'value', 'sequence', 'path', 'equals', 'label', 'prompt'], path);
|
|
659
|
+
if (typeof r.type !== 'string')
|
|
660
|
+
return invalid(`${path}.type`, 'expected a rule type');
|
|
661
|
+
if (!ruleTypes.has(r.type))
|
|
662
|
+
return invalid(`${path}.type`, `unsupported rule type '${r.type}'`);
|
|
663
|
+
for (const key of ['tool', 'path', 'label', 'prompt'])
|
|
664
|
+
if (r[key] !== undefined && typeof r[key] !== 'string')
|
|
665
|
+
return invalid(`${path}.${key}`, 'expected a string');
|
|
666
|
+
if (r.value !== undefined && !['string', 'number', 'boolean'].includes(typeof r.value))
|
|
667
|
+
return invalid(`${path}.value`, 'expected a string, number, or boolean');
|
|
668
|
+
if (r.equals !== undefined && !['string', 'number', 'boolean'].includes(typeof r.equals))
|
|
669
|
+
return invalid(`${path}.equals`, 'expected a string, number, or boolean');
|
|
670
|
+
if (r.sequence !== undefined &&
|
|
671
|
+
(!Array.isArray(r.sequence) || !r.sequence.every((x) => typeof x === 'string')))
|
|
672
|
+
return invalid(`${path}.sequence`, 'expected an array of strings');
|
|
673
|
+
const type = r.type;
|
|
674
|
+
if (type === 'required_tool' ||
|
|
675
|
+
type === 'forbidden_tool' ||
|
|
676
|
+
(type.startsWith('response_') && !type.includes('jsonpath'))) {
|
|
677
|
+
if (typeof r.value !== 'string')
|
|
678
|
+
return invalid(`${path}.value`, 'required and must be a string');
|
|
483
679
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
const firstPending = pendingCalls[0];
|
|
503
|
-
session.chatMessages.push({
|
|
504
|
-
id: newAssistantEntityId('msg'),
|
|
505
|
-
role: 'assistant',
|
|
506
|
-
text: modelOutput.text,
|
|
507
|
-
createdAt: new Date().toISOString(),
|
|
508
|
-
suggestions: modelOutput.suggestions,
|
|
509
|
-
pendingToolCallId: firstPending.id,
|
|
510
|
-
pendingToolCallIds: pendingCalls.map((p) => p.id),
|
|
511
|
-
toolRequestServer: firstPending.server,
|
|
512
|
-
toolRequestName: firstPending.tool,
|
|
513
|
-
toolRequestPublicName: firstPending.publicToolName
|
|
514
|
-
});
|
|
515
|
-
session.llmMessages.push({
|
|
516
|
-
role: 'assistant',
|
|
517
|
-
content: modelOutput.text,
|
|
518
|
-
tool_calls: llmToolCalls
|
|
519
|
-
});
|
|
520
|
-
touchAssistantSession(session);
|
|
521
|
-
return {
|
|
522
|
-
session: assistantSessionView(session),
|
|
523
|
-
response: {
|
|
524
|
-
type: 'tool_call_request',
|
|
525
|
-
text: modelOutput.text,
|
|
526
|
-
suggestions: modelOutput.suggestions,
|
|
527
|
-
pendingToolCall: firstPending,
|
|
528
|
-
pendingToolCalls: pendingCalls
|
|
680
|
+
if (type === 'tool_sequence' && (!Array.isArray(r.sequence) || r.sequence.length === 0))
|
|
681
|
+
return invalid(`${path}.sequence`, 'required and must be a non-empty array');
|
|
682
|
+
// Blank names are dropped when the rule is normalized, which could empty the sequence.
|
|
683
|
+
if (type === 'tool_sequence' && r.sequence.some((toolName) => !toolName.trim()))
|
|
684
|
+
return invalid(`${path}.sequence`, 'tool names must not be blank');
|
|
685
|
+
if (type.startsWith('tool_input_') && typeof r.tool !== 'string')
|
|
686
|
+
return invalid(`${path}.tool`, 'required and must be a string');
|
|
687
|
+
if ((type === 'tool_input_contains' || type === 'tool_input_regex') &&
|
|
688
|
+
typeof r.value !== 'string')
|
|
689
|
+
return invalid(`${path}.value`, 'required and must be a string');
|
|
690
|
+
if ((type === 'tool_input_regex' || type === 'response_regex') && typeof r.value !== 'string')
|
|
691
|
+
return invalid(`${path}.value`, 'required and must be a string');
|
|
692
|
+
if (type.endsWith('jsonpath') ||
|
|
693
|
+
type.endsWith('jsonpath_exists') ||
|
|
694
|
+
type.endsWith('jsonpath_not_exists')) {
|
|
695
|
+
if (typeof r.path !== 'string')
|
|
696
|
+
return invalid(`${path}.path`, 'required and must be a string');
|
|
529
697
|
}
|
|
698
|
+
if (type === 'agent_check' && typeof r.label !== 'string')
|
|
699
|
+
return invalid(`${path}.label`, 'required and must be a string');
|
|
700
|
+
if (type === 'agent_check' && typeof r.prompt !== 'string')
|
|
701
|
+
return invalid(`${path}.prompt`, 'required and must be a string');
|
|
702
|
+
}
|
|
703
|
+
const rules = structuredClone(e.replacement);
|
|
704
|
+
const bundle = { evalRules: { replacement: rules } };
|
|
705
|
+
normalizeEvalRuleToolNames(bundle, toolPublicMap);
|
|
706
|
+
bundle.evalRules.replacement = normalizeScenarioAssistantEvalRules(bundle.evalRules.replacement);
|
|
707
|
+
result.evalRules = {
|
|
708
|
+
...bundle.evalRules,
|
|
709
|
+
...(e.rationale === undefined ? {} : { rationale: e.rationale })
|
|
530
710
|
};
|
|
531
711
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
712
|
+
if (value.extractRules !== undefined) {
|
|
713
|
+
if (!value.extractRules ||
|
|
714
|
+
typeof value.extractRules !== 'object' ||
|
|
715
|
+
Array.isArray(value.extractRules))
|
|
716
|
+
return invalid('extractRules', 'expected an object');
|
|
717
|
+
const e = value.extractRules;
|
|
718
|
+
checkKeys(e, ['replacement', 'rationale'], 'extractRules');
|
|
719
|
+
if (!Array.isArray(e.replacement))
|
|
720
|
+
return invalid('extractRules.replacement', 'expected an array');
|
|
721
|
+
if (!validRationale(e))
|
|
722
|
+
return invalid('extractRules.rationale', 'expected a string');
|
|
723
|
+
for (const [index, item] of e.replacement.entries()) {
|
|
724
|
+
const path = `extractRules.replacement[${index}]`;
|
|
725
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
726
|
+
return invalid(path, 'expected an object');
|
|
727
|
+
const rule = item;
|
|
728
|
+
checkKeys(rule, ['name', 'pattern'], path);
|
|
729
|
+
if (typeof rule.name !== 'string')
|
|
730
|
+
return invalid(`${path}.name`, 'required and must be a string');
|
|
731
|
+
if (typeof rule.pattern !== 'string')
|
|
732
|
+
return invalid(`${path}.pattern`, 'required and must be a string');
|
|
550
733
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
return withTimeout(() => session.mcp.callTool(pending.server, pending.tool, pending.arguments, {
|
|
556
|
-
signal
|
|
557
|
-
}), timeoutMs, `Tool call timed out after ${timeoutMs}ms`, signal);
|
|
558
|
-
}
|
|
559
|
-
export function summarizeToolResultForAssistant(result) {
|
|
560
|
-
return truncateJson(result, SCENARIO_ASSISTANT_TOOL_RESULT_PREVIEW_CHARS);
|
|
561
|
-
}
|
|
562
|
-
export function resolveAssistantAgentFromConfig(config, selectedAssistantAgentName) {
|
|
563
|
-
const agent = config.agents[selectedAssistantAgentName];
|
|
564
|
-
if (!agent) {
|
|
565
|
-
throw new Error(`Scenario Assistant agent '${selectedAssistantAgentName}' not found in resolved config agents.`);
|
|
734
|
+
result.extractRules = {
|
|
735
|
+
replacement: structuredClone(e.replacement),
|
|
736
|
+
...(e.rationale === undefined ? {} : { rationale: e.rationale })
|
|
737
|
+
};
|
|
566
738
|
}
|
|
567
|
-
if (
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
throw new Error(`Scenario Assistant agent '${selectedAssistantAgentName}' not found in library agents. Configure the central Scenario Assistant Agent in Libraries > Scenarios.`);
|
|
739
|
+
if (value.notes !== undefined) {
|
|
740
|
+
if (!Array.isArray(value.notes))
|
|
741
|
+
return invalid('notes', 'expected an array');
|
|
742
|
+
for (const [index, note] of value.notes.entries())
|
|
743
|
+
if (typeof note !== 'string')
|
|
744
|
+
return invalid(`notes[${index}]`, 'expected a string');
|
|
745
|
+
result.notes = [...value.notes];
|
|
575
746
|
}
|
|
576
|
-
if (!
|
|
577
|
-
|
|
578
|
-
return
|
|
579
|
-
}
|
|
580
|
-
export function pickDefaultAssistantAgentName(params) {
|
|
581
|
-
const requested = params.requested?.trim();
|
|
582
|
-
if (requested)
|
|
583
|
-
return requested;
|
|
584
|
-
const settingsDefault = params.settingsDefault?.trim();
|
|
585
|
-
if (settingsDefault)
|
|
586
|
-
return settingsDefault;
|
|
587
|
-
return params.agentNames[0] ?? '';
|
|
747
|
+
if (!result.prompt && !result.evalRules && !result.extractRules)
|
|
748
|
+
return invalid('proposal', 'must include at least one change');
|
|
749
|
+
return result;
|
|
588
750
|
}
|
|
589
751
|
//# sourceMappingURL=scenario-assistant-domain.js.map
|