@wix/pathgrade 1.0.37 → 1.0.39
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 +3 -1
- package/dist/adapter-kit/index.d.ts +2 -2
- package/dist/adapter-kit/index.js +1 -1
- package/dist/adapters/jest/lifecycle.js +4 -2
- package/dist/adapters/node-test/index.js +4 -2
- package/dist/agents/claude/sdk-message-projector.js +4 -5
- package/dist/agents/claude/tool-permission-bridge.js +2 -1
- package/dist/agents/codex-app-server/item-projection.js +2 -2
- package/dist/agents/codex-app-server/mcp-approval-correlator.js +3 -2
- package/dist/agents/opencode.js +4 -5
- package/dist/commands/report.js +5 -25
- package/dist/internal/direct-mcp-v2/acp-author-projector.js +20 -7
- package/dist/reporters/cli.js +20 -1
- package/dist/reporters/github-comment.js +18 -7
- package/dist/reporters/loader.d.ts +4 -0
- package/dist/reporters/loader.js +26 -9
- package/dist/reporting/comparison-contract.js +20 -6
- package/dist/reporting/core.js +18 -8
- package/dist/reporting/report-parser.js +57 -2
- package/dist/reporting/types.d.ts +2 -1
- package/dist/runners/adapter.d.ts +4 -2
- package/dist/runners/lifecycle-hooks.js +6 -1
- package/dist/runners/orchestrator.js +3 -4
- package/dist/runners/repeated-invocation.js +2 -5
- package/dist/runners/report-projection.js +1 -0
- package/dist/runners/vitest-lifecycle.d.ts +1 -0
- package/dist/runners/vitest-lifecycle.js +21 -4
- package/dist/sdk/agent-flow.d.ts +51 -0
- package/dist/sdk/agent-flow.js +23 -0
- package/dist/sdk/agent.js +10 -1
- package/dist/sdk/evaluate.d.ts +2 -1
- package/dist/sdk/evaluate.js +114 -4
- package/dist/sdk/index.d.ts +4 -2
- package/dist/sdk/index.js +2 -2
- package/dist/sdk/judge-prompt-builder.js +11 -7
- package/dist/sdk/lifecycle.d.ts +3 -1
- package/dist/sdk/lifecycle.js +68 -5
- package/dist/sdk/mcp-event-input.d.ts +1 -0
- package/dist/sdk/mcp-event-input.js +3 -0
- package/dist/sdk/mcp-evidence.js +16 -3
- package/dist/sdk/mcp-safety.js +2 -2
- package/dist/sdk/result-capture.d.ts +14 -1
- package/dist/sdk/result-capture.js +49 -0
- package/dist/sdk/scripted-mcp-events.js +3 -2
- package/dist/sdk/tool-event-log.js +18 -3
- package/dist/sdk/tool-event-secrets.d.ts +4 -0
- package/dist/sdk/tool-event-secrets.js +33 -2
- package/dist/sdk/types.d.ts +6 -0
- package/dist/tool-event-results.d.ts +3 -0
- package/dist/tool-event-results.js +153 -23
- package/dist/types.d.ts +13 -7
- package/docs/agent-flow-evaluation.md +31 -0
- package/package.json +3 -2
package/dist/sdk/lifecycle.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Agent, PathgradeTestMeta, RecordedEvalResult } from './types.js';
|
|
1
|
+
import type { Agent, AgentFlowTrace, PathgradeTestMeta, RecordedEvalResult } from './types.js';
|
|
2
2
|
export type LifecycleAgentOwner = {
|
|
3
3
|
type: 'runner-case';
|
|
4
4
|
caseId: string;
|
|
@@ -24,6 +24,7 @@ declare function registerAgent(agent: Agent, owner?: LifecycleAgentOwner | null)
|
|
|
24
24
|
declare function untrackAgent(agent: Agent): void;
|
|
25
25
|
declare function releaseAgent(agent: Agent): void;
|
|
26
26
|
declare function recordResult(result: RecordedEvalResult, agent: Agent, attribution?: ResultAttribution): void;
|
|
27
|
+
declare function recordFlowResult(result: RecordedEvalResult, flow: AgentFlowTrace, caseId?: string): void;
|
|
27
28
|
declare function flushCase(input: FlushCaseInput): Promise<PathgradeTestMeta[]>;
|
|
28
29
|
declare function cleanupAll(): Promise<void>;
|
|
29
30
|
declare function reset(): void;
|
|
@@ -33,6 +34,7 @@ export declare const lifecycleCore: {
|
|
|
33
34
|
releaseAgent: typeof releaseAgent;
|
|
34
35
|
getAgentOwner: typeof getAgentOwner;
|
|
35
36
|
recordResult: typeof recordResult;
|
|
37
|
+
recordFlowResult: typeof recordFlowResult;
|
|
36
38
|
flushCase: typeof flushCase;
|
|
37
39
|
cleanupAll: typeof cleanupAll;
|
|
38
40
|
reset: typeof reset;
|
package/dist/sdk/lifecycle.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { getCurrentCaseContext } from './case-context.js';
|
|
2
2
|
import { buildDiagnosticsReport } from './diagnostics.js';
|
|
3
3
|
import { countShellCommandsFromLog } from '../tool-events.js';
|
|
4
|
+
import { summarizeFlow } from './agent-flow.js';
|
|
4
5
|
const lifecycleStateKey = Symbol.for('@wix/pathgrade/lifecycle-state');
|
|
5
6
|
const sharedLifecycle = globalThis;
|
|
6
7
|
const lifecycleState = sharedLifecycle[lifecycleStateKey] ?? (sharedLifecycle[lifecycleStateKey] = {
|
|
7
8
|
pendingAgents: new Set(),
|
|
8
9
|
agentOwners: new WeakMap(),
|
|
9
10
|
agentResults: new WeakMap(),
|
|
11
|
+
flowResults: new Map(),
|
|
12
|
+
nextResultSequence: 0,
|
|
10
13
|
});
|
|
11
|
-
|
|
14
|
+
lifecycleState.nextResultSequence ??= 0;
|
|
15
|
+
lifecycleState.flowResults ??= new Map();
|
|
16
|
+
const { pendingAgents, agentOwners, agentResults, flowResults } = lifecycleState;
|
|
12
17
|
function currentAgentOwner() {
|
|
13
18
|
const current = getCurrentCaseContext();
|
|
14
19
|
if (current.status !== 'active')
|
|
@@ -74,6 +79,7 @@ function recordResult(result, agent, attribution) {
|
|
|
74
79
|
const conversationEnd = [...agent.log].reverse().find((entry) => entry.type === 'conversation_end');
|
|
75
80
|
const completionReason = conversationEnd?.completion_reason ?? (agent.log.some((entry) => entry.type === 'agent_result') ? 'completed' : undefined);
|
|
76
81
|
agentResults.get(agent).push({
|
|
82
|
+
sequence: lifecycleState.nextResultSequence++,
|
|
77
83
|
attribution: attribution ?? currentResultAttribution(owner),
|
|
78
84
|
meta: {
|
|
79
85
|
score: result.score,
|
|
@@ -101,8 +107,60 @@ function recordResult(result, agent, attribution) {
|
|
|
101
107
|
},
|
|
102
108
|
});
|
|
103
109
|
}
|
|
110
|
+
function recordFlowResult(result, flow, caseId) {
|
|
111
|
+
const current = getCurrentCaseContext();
|
|
112
|
+
const resolvedCaseId = caseId ?? (current.status === 'active' && current.context.scope === 'runner-case'
|
|
113
|
+
? current.context.caseId
|
|
114
|
+
: undefined);
|
|
115
|
+
if (!resolvedCaseId)
|
|
116
|
+
return;
|
|
117
|
+
const entries = flowResults.get(resolvedCaseId) ?? [];
|
|
118
|
+
entries.push({
|
|
119
|
+
sequence: lifecycleState.nextResultSequence++,
|
|
120
|
+
meta: {
|
|
121
|
+
score: result.score,
|
|
122
|
+
scorers: result.scorers,
|
|
123
|
+
...(result.evaluationDefinitionKey ? { evaluationDefinitionKey: result.evaluationDefinitionKey } : {}),
|
|
124
|
+
...(result.scorerRevision ? { scorerRevision: result.scorerRevision } : {}),
|
|
125
|
+
trial: {
|
|
126
|
+
...(result.trial ?? {
|
|
127
|
+
trial_id: 0,
|
|
128
|
+
...(result.score !== undefined ? { reward: result.score } : {}),
|
|
129
|
+
scorer_results: result.scorers.map(scorer => ({
|
|
130
|
+
scorer_type: scorer.type === 'check' || scorer.type === 'score' ? 'deterministic' : scorer.type === 'judge' ? 'llm_rubric' : 'tool_usage',
|
|
131
|
+
score: scorer.score,
|
|
132
|
+
weight: scorer.weight,
|
|
133
|
+
details: scorer.details ?? '',
|
|
134
|
+
status: scorer.status,
|
|
135
|
+
})),
|
|
136
|
+
duration_ms: 0,
|
|
137
|
+
n_commands: 0,
|
|
138
|
+
input_tokens: result.tokenUsage?.inputTokens ?? 0,
|
|
139
|
+
output_tokens: result.tokenUsage?.outputTokens ?? 0,
|
|
140
|
+
session_log: [],
|
|
141
|
+
}),
|
|
142
|
+
flow_summary: summarizeFlow(flow),
|
|
143
|
+
flow_trace: structuredClone(flow),
|
|
144
|
+
},
|
|
145
|
+
resultKind: result.resultKind ?? 'evaluated',
|
|
146
|
+
...(result.scoringDurationMs !== undefined ? { scoringDurationMs: result.scoringDurationMs } : {}),
|
|
147
|
+
...(result.recordedAt ? { recordedAt: result.recordedAt } : {}),
|
|
148
|
+
diagnostics: buildDiagnosticsReport({
|
|
149
|
+
completionReason: ['completed', 'failed', 'rejected', 'canceled', 'auth-required'].includes(flow.outcome.state)
|
|
150
|
+
? flow.outcome.state
|
|
151
|
+
: undefined,
|
|
152
|
+
completionDetail: flow.outcome.error,
|
|
153
|
+
score: result.score,
|
|
154
|
+
scorers: result.scorers,
|
|
155
|
+
log: [],
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
flowResults.set(resolvedCaseId, entries);
|
|
160
|
+
}
|
|
104
161
|
async function flushCase(input) {
|
|
105
|
-
const results = [];
|
|
162
|
+
const results = flowResults.get(input.caseId) ?? [];
|
|
163
|
+
flowResults.delete(input.caseId);
|
|
106
164
|
const toDispose = [];
|
|
107
165
|
for (const agent of pendingAgents) {
|
|
108
166
|
const owner = agentOwners.get(agent);
|
|
@@ -110,7 +168,7 @@ async function flushCase(input) {
|
|
|
110
168
|
const meta = agentResults.get(agent);
|
|
111
169
|
const matchingMeta = meta?.filter((entry) => resultMatchesCase(entry, input.caseId));
|
|
112
170
|
if (matchingMeta && matchingMeta.length > 0 && canFlushResultsToCase(owner, input.caseId)) {
|
|
113
|
-
results.push(...matchingMeta
|
|
171
|
+
results.push(...matchingMeta);
|
|
114
172
|
const remainingMeta = meta?.filter((entry) => !resultMatchesCase(entry, input.caseId)) ?? [];
|
|
115
173
|
if (remainingMeta.length > 0) {
|
|
116
174
|
agentResults.set(agent, remainingMeta);
|
|
@@ -124,7 +182,7 @@ async function flushCase(input) {
|
|
|
124
182
|
if (!hasResultsForCase) {
|
|
125
183
|
const synthTrial = synthesizeTrialFromAgent(agent);
|
|
126
184
|
if (synthTrial) {
|
|
127
|
-
results.push(synthTrial);
|
|
185
|
+
results.push({ sequence: lifecycleState.nextResultSequence++, meta: synthTrial });
|
|
128
186
|
}
|
|
129
187
|
}
|
|
130
188
|
pendingAgents.delete(agent);
|
|
@@ -133,7 +191,7 @@ async function flushCase(input) {
|
|
|
133
191
|
}
|
|
134
192
|
}
|
|
135
193
|
await Promise.all(toDispose.map((agent) => agent.dispose().catch(() => { })));
|
|
136
|
-
return results;
|
|
194
|
+
return results.toSorted((left, right) => left.sequence - right.sequence).map(entry => entry.meta);
|
|
137
195
|
}
|
|
138
196
|
function synthesizeTrialFromAgent(agent) {
|
|
139
197
|
if (agent.log.length === 0)
|
|
@@ -181,10 +239,14 @@ function synthesizeTrialFromAgent(agent) {
|
|
|
181
239
|
async function cleanupAll() {
|
|
182
240
|
const toDispose = [...pendingAgents];
|
|
183
241
|
pendingAgents.clear();
|
|
242
|
+
flowResults.clear();
|
|
243
|
+
lifecycleState.nextResultSequence = 0;
|
|
184
244
|
await Promise.all(toDispose.map((agent) => agent.dispose().catch(() => { })));
|
|
185
245
|
}
|
|
186
246
|
function reset() {
|
|
187
247
|
pendingAgents.clear();
|
|
248
|
+
flowResults.clear();
|
|
249
|
+
lifecycleState.nextResultSequence = 0;
|
|
188
250
|
}
|
|
189
251
|
export const lifecycleCore = {
|
|
190
252
|
registerAgent,
|
|
@@ -192,6 +254,7 @@ export const lifecycleCore = {
|
|
|
192
254
|
releaseAgent,
|
|
193
255
|
getAgentOwner,
|
|
194
256
|
recordResult,
|
|
257
|
+
recordFlowResult,
|
|
195
258
|
flushCase,
|
|
196
259
|
cleanupAll,
|
|
197
260
|
reset,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ToolEvent } from '../tool-events.js';
|
|
2
2
|
export declare function attachOriginalMcpInput(event: ToolEvent, input: Record<string, unknown>): ToolEvent;
|
|
3
3
|
export declare function getOriginalMcpInput(event: ToolEvent): Record<string, unknown> | undefined;
|
|
4
|
+
export declare function clearOriginalMcpInput(event: ToolEvent): void;
|
package/dist/sdk/mcp-evidence.js
CHANGED
|
@@ -2,9 +2,12 @@ export function getMcpToolCall(event) {
|
|
|
2
2
|
if (event.action !== 'mcp_tool_call')
|
|
3
3
|
return undefined;
|
|
4
4
|
const args = event.arguments ?? {};
|
|
5
|
-
const serverName =
|
|
6
|
-
|
|
7
|
-
const
|
|
5
|
+
const serverName = event.mcp?.serverName
|
|
6
|
+
?? (typeof args.server === 'string' ? args.server : undefined);
|
|
7
|
+
const toolName = event.mcp?.toolName
|
|
8
|
+
?? (typeof args.tool === 'string' ? args.tool : undefined);
|
|
9
|
+
const argumentStatus = typeof args.status === 'string' ? args.status : undefined;
|
|
10
|
+
const status = mcpClassificationStatus(event.mcp, argumentStatus) ?? argumentStatus;
|
|
8
11
|
if (!serverName || !toolName || !status)
|
|
9
12
|
return undefined;
|
|
10
13
|
return {
|
|
@@ -15,6 +18,16 @@ export function getMcpToolCall(event) {
|
|
|
15
18
|
event,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
21
|
+
function mcpClassificationStatus(mcp, argumentStatus) {
|
|
22
|
+
if (!mcp)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (mcp.invocation === 'confirmed') {
|
|
25
|
+
if (mcp.outcome === 'completed')
|
|
26
|
+
return 'completed';
|
|
27
|
+
return argumentStatus === 'failed' || argumentStatus === 'error' ? argumentStatus : 'error';
|
|
28
|
+
}
|
|
29
|
+
return mcp.outcome;
|
|
30
|
+
}
|
|
18
31
|
export function isMcpToolCall(event, expected = {}) {
|
|
19
32
|
const call = getMcpToolCall(event);
|
|
20
33
|
return call !== undefined && matchesMcpToolCall(call, expected);
|
package/dist/sdk/mcp-safety.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sanitizeUntrustedPersistenceValue } from '../tool-event-results.js';
|
|
2
2
|
export function decideMcpToolCall(options, request) {
|
|
3
3
|
const runMode = options?.runMode ?? 'mock';
|
|
4
4
|
if (runMode === 'mock')
|
|
@@ -31,7 +31,7 @@ export function decideMcpToolCall(options, request) {
|
|
|
31
31
|
return { action: 'allow' };
|
|
32
32
|
}
|
|
33
33
|
export function redactMcpSecrets(value) {
|
|
34
|
-
return
|
|
34
|
+
return sanitizeUntrustedPersistenceValue(value);
|
|
35
35
|
}
|
|
36
36
|
function ruleMatches(rule, request) {
|
|
37
37
|
if (rule.serverName !== undefined && rule.serverName !== request.serverName)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Agent, RecordedEvalResult } from './types.js';
|
|
1
|
+
import type { Agent, AgentFlowTrace, RecordedEvalResult } from './types.js';
|
|
2
2
|
import { type CaseContext } from './case-context.js';
|
|
3
3
|
export interface EvalResultEvent {
|
|
4
4
|
readonly result: RecordedEvalResult;
|
|
@@ -6,6 +6,15 @@ export interface EvalResultEvent {
|
|
|
6
6
|
readonly case?: CaseContext;
|
|
7
7
|
}
|
|
8
8
|
export type EvalResultObserver = (event: EvalResultEvent) => void;
|
|
9
|
+
export type EvaluationResultEvent = (EvalResultEvent & {
|
|
10
|
+
readonly sourceKind: 'agent';
|
|
11
|
+
}) | {
|
|
12
|
+
readonly sourceKind: 'flow';
|
|
13
|
+
readonly result: RecordedEvalResult;
|
|
14
|
+
readonly flow: AgentFlowTrace;
|
|
15
|
+
readonly case?: CaseContext;
|
|
16
|
+
};
|
|
17
|
+
export type EvaluationResultObserver = (event: EvaluationResultEvent) => void;
|
|
9
18
|
export type ResultObserverOwner = 'user' | 'adapter' | 'test';
|
|
10
19
|
export interface ResultObserverOptions {
|
|
11
20
|
readonly owner?: ResultObserverOwner;
|
|
@@ -15,6 +24,10 @@ export interface ResultObserverHandle {
|
|
|
15
24
|
unsubscribe(): void;
|
|
16
25
|
}
|
|
17
26
|
export declare function subscribeToEvalResults(observer: EvalResultObserver, options?: ResultObserverOptions): ResultObserverHandle;
|
|
27
|
+
export declare function subscribeToEvaluationResults(observer: EvaluationResultObserver, options?: ResultObserverOptions): ResultObserverHandle;
|
|
18
28
|
export declare function emitEvalResult(event: EvalResultEvent): void;
|
|
29
|
+
export declare function emitFlowEvalResult(event: Omit<Extract<EvaluationResultEvent, {
|
|
30
|
+
sourceKind: 'flow';
|
|
31
|
+
}>, 'sourceKind'>): void;
|
|
19
32
|
export declare function resetUserResultObservers(): void;
|
|
20
33
|
export declare function resetAllResultObserversForTests(): void;
|
|
@@ -3,6 +3,10 @@ const observerRegistryKey = Symbol.for('@wix/pathgrade/eval-result-observers');
|
|
|
3
3
|
const globalRegistry = globalThis;
|
|
4
4
|
const observers = globalRegistry[observerRegistryKey]
|
|
5
5
|
?? (globalRegistry[observerRegistryKey] = new Set());
|
|
6
|
+
const evaluationObserverRegistryKey = Symbol.for('@wix/pathgrade/evaluation-result-observers');
|
|
7
|
+
const evaluationGlobalRegistry = globalThis;
|
|
8
|
+
const evaluationObservers = evaluationGlobalRegistry[evaluationObserverRegistryKey]
|
|
9
|
+
?? (evaluationGlobalRegistry[evaluationObserverRegistryKey] = new Set());
|
|
6
10
|
export function subscribeToEvalResults(observer, options = {}) {
|
|
7
11
|
if (options.owner === 'adapter' && options.key) {
|
|
8
12
|
removeObserverByOwnerAndKey(options.owner, options.key);
|
|
@@ -23,6 +27,22 @@ export function subscribeToEvalResults(observer, options = {}) {
|
|
|
23
27
|
},
|
|
24
28
|
};
|
|
25
29
|
}
|
|
30
|
+
export function subscribeToEvaluationResults(observer, options = {}) {
|
|
31
|
+
if (options.owner === 'adapter' && options.key) {
|
|
32
|
+
removeEvaluationObserverByOwnerAndKey(options.owner, options.key);
|
|
33
|
+
}
|
|
34
|
+
const subscription = { observer, owner: options.owner ?? 'user', key: options.key };
|
|
35
|
+
evaluationObservers.add(subscription);
|
|
36
|
+
let active = true;
|
|
37
|
+
return {
|
|
38
|
+
unsubscribe() {
|
|
39
|
+
if (!active)
|
|
40
|
+
return;
|
|
41
|
+
active = false;
|
|
42
|
+
evaluationObservers.delete(subscription);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
26
46
|
export function emitEvalResult(event) {
|
|
27
47
|
const currentCase = getCurrentCaseContext();
|
|
28
48
|
const deliveredEvent = event.case || currentCase.status !== 'active'
|
|
@@ -40,6 +60,14 @@ export function emitEvalResult(event) {
|
|
|
40
60
|
// broken hook must not block adapter-owned reporting.
|
|
41
61
|
}
|
|
42
62
|
}
|
|
63
|
+
deliverEvaluationEvent({ ...deliveredEvent, sourceKind: 'agent' });
|
|
64
|
+
}
|
|
65
|
+
export function emitFlowEvalResult(event) {
|
|
66
|
+
const currentCase = getCurrentCaseContext();
|
|
67
|
+
const deliveredEvent = event.case || currentCase.status !== 'active'
|
|
68
|
+
? event
|
|
69
|
+
: { ...event, case: currentCase.context };
|
|
70
|
+
deliverEvaluationEvent({ ...deliveredEvent, sourceKind: 'flow' });
|
|
43
71
|
}
|
|
44
72
|
export function resetUserResultObservers() {
|
|
45
73
|
for (const subscription of observers) {
|
|
@@ -47,9 +75,14 @@ export function resetUserResultObservers() {
|
|
|
47
75
|
observers.delete(subscription);
|
|
48
76
|
}
|
|
49
77
|
}
|
|
78
|
+
for (const subscription of evaluationObservers) {
|
|
79
|
+
if (subscription.owner === 'user')
|
|
80
|
+
evaluationObservers.delete(subscription);
|
|
81
|
+
}
|
|
50
82
|
}
|
|
51
83
|
export function resetAllResultObserversForTests() {
|
|
52
84
|
observers.clear();
|
|
85
|
+
evaluationObservers.clear();
|
|
53
86
|
}
|
|
54
87
|
function removeObserverByOwnerAndKey(owner, key) {
|
|
55
88
|
for (const subscription of observers) {
|
|
@@ -58,3 +91,19 @@ function removeObserverByOwnerAndKey(owner, key) {
|
|
|
58
91
|
}
|
|
59
92
|
}
|
|
60
93
|
}
|
|
94
|
+
function removeEvaluationObserverByOwnerAndKey(owner, key) {
|
|
95
|
+
for (const subscription of evaluationObservers) {
|
|
96
|
+
if (subscription.owner === owner && subscription.key === key)
|
|
97
|
+
evaluationObservers.delete(subscription);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function deliverEvaluationEvent(event) {
|
|
101
|
+
for (const subscription of [...evaluationObservers]) {
|
|
102
|
+
try {
|
|
103
|
+
subscription.observer(event);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Generic result capture has the same best-effort isolation as the legacy stream.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { redactMcpSecrets } from './mcp-safety.js';
|
|
2
|
+
import { attachLiveMcpInput } from './tool-event-secrets.js';
|
|
2
3
|
export function buildScriptedMcpApprovalEvent(opts) {
|
|
3
4
|
const providerToolName = `${opts.serverName}.${opts.toolName}`;
|
|
4
5
|
return {
|
|
@@ -18,7 +19,7 @@ export function buildScriptedMcpApprovalEvent(opts) {
|
|
|
18
19
|
export function buildScriptedMcpDeniedCallEvent(opts) {
|
|
19
20
|
const providerToolName = `${opts.serverName}.${opts.toolName}`;
|
|
20
21
|
const args = redactMcpSecrets(opts.args);
|
|
21
|
-
return {
|
|
22
|
+
return attachLiveMcpInput({
|
|
22
23
|
action: 'mcp_tool_call', provider: opts.provider, providerToolName,
|
|
23
24
|
toolUseId: opts.toolUseId, turnNumber: opts.turnNumber, status: 'error',
|
|
24
25
|
mcp: { serverName: opts.serverName, toolName: opts.toolName, invocation: 'not_invoked', outcome: opts.outcome },
|
|
@@ -26,5 +27,5 @@ export function buildScriptedMcpDeniedCallEvent(opts) {
|
|
|
26
27
|
summary: `MCP tool ${providerToolName} ${opts.outcome}`,
|
|
27
28
|
confidence: 'high',
|
|
28
29
|
rawSnippet: JSON.stringify({ status: opts.outcome }),
|
|
29
|
-
};
|
|
30
|
+
}, opts.args);
|
|
30
31
|
}
|
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
2
|
-
import { getToolEventSensitiveValues } from './tool-event-secrets.js';
|
|
1
|
+
import { collectStructuredSensitiveValues, isSensitiveValueScanLimitError, sanitizePersistenceValue, } from '../tool-event-results.js';
|
|
2
|
+
import { attachToolEventSensitiveValues, cloneToolEventWithRuntimeMetadata, getToolEventSensitiveValues, redactToolEventPayload, } from './tool-event-secrets.js';
|
|
3
3
|
export function buildToolEventLogEntry(toolEvent, fallbackTimestamp) {
|
|
4
|
-
|
|
4
|
+
let event = toolEvent;
|
|
5
|
+
let discoveredValues;
|
|
6
|
+
try {
|
|
7
|
+
discoveredValues = collectStructuredSensitiveValues(event);
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (!isSensitiveValueScanLimitError(error))
|
|
11
|
+
throw error;
|
|
12
|
+
event = redactToolEventPayload(event);
|
|
13
|
+
discoveredValues = [];
|
|
14
|
+
}
|
|
15
|
+
const sensitiveValues = [...new Set([
|
|
16
|
+
...getToolEventSensitiveValues(toolEvent),
|
|
17
|
+
...discoveredValues,
|
|
18
|
+
])];
|
|
19
|
+
const persistedEvent = attachToolEventSensitiveValues(cloneToolEventWithRuntimeMetadata(event, sanitizePersistenceValue(event, sensitiveValues)), sensitiveValues);
|
|
5
20
|
return {
|
|
6
21
|
type: 'tool_event',
|
|
7
22
|
timestamp: persistedEvent.startedAt ?? fallbackTimestamp,
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ToolEvent } from '../tool-events.js';
|
|
2
2
|
export declare function attachToolEventSensitiveValues(event: ToolEvent, sensitiveValues: readonly string[]): ToolEvent;
|
|
3
3
|
export declare function getToolEventSensitiveValues(event: ToolEvent): readonly string[];
|
|
4
|
+
export declare function attachLiveMcpInput(event: ToolEvent, input: Record<string, unknown>): ToolEvent;
|
|
5
|
+
export declare function redactToolEventPayload(event: ToolEvent): ToolEvent;
|
|
6
|
+
export declare function collectToolEventSensitiveValues(events: readonly ToolEvent[]): string[];
|
|
7
|
+
export declare function clearToolEventRuntimeMetadata(event: ToolEvent): void;
|
|
4
8
|
export declare function cloneToolEventWithRuntimeMetadata(source: ToolEvent, overrides: Partial<ToolEvent>): ToolEvent;
|
|
@@ -1,12 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collectStructuredSensitiveValues, isSensitiveValueScanLimitError } from '../tool-event-results.js';
|
|
2
|
+
import { attachOriginalMcpInput, clearOriginalMcpInput, getOriginalMcpInput, } from './mcp-event-input.js';
|
|
2
3
|
const sensitiveValuesByEvent = new WeakMap();
|
|
3
4
|
export function attachToolEventSensitiveValues(event, sensitiveValues) {
|
|
4
|
-
sensitiveValuesByEvent.set(event, [...
|
|
5
|
+
sensitiveValuesByEvent.set(event, [...new Set([
|
|
6
|
+
...getToolEventSensitiveValues(event),
|
|
7
|
+
...sensitiveValues,
|
|
8
|
+
])]);
|
|
5
9
|
return event;
|
|
6
10
|
}
|
|
7
11
|
export function getToolEventSensitiveValues(event) {
|
|
8
12
|
return sensitiveValuesByEvent.get(event) ?? [];
|
|
9
13
|
}
|
|
14
|
+
export function attachLiveMcpInput(event, input) {
|
|
15
|
+
let sensitiveValues;
|
|
16
|
+
try {
|
|
17
|
+
sensitiveValues = collectStructuredSensitiveValues(input);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (!isSensitiveValueScanLimitError(error))
|
|
21
|
+
throw error;
|
|
22
|
+
return redactToolEventPayload(event);
|
|
23
|
+
}
|
|
24
|
+
return attachToolEventSensitiveValues(attachOriginalMcpInput(event, input), sensitiveValues);
|
|
25
|
+
}
|
|
26
|
+
export function redactToolEventPayload(event) {
|
|
27
|
+
return {
|
|
28
|
+
...event,
|
|
29
|
+
arguments: { redacted: true },
|
|
30
|
+
summary: `${event.action} via ${event.providerToolName}`,
|
|
31
|
+
rawSnippet: '<redacted>',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function collectToolEventSensitiveValues(events) {
|
|
35
|
+
return [...new Set(events.flatMap((event) => getToolEventSensitiveValues(event)))];
|
|
36
|
+
}
|
|
37
|
+
export function clearToolEventRuntimeMetadata(event) {
|
|
38
|
+
sensitiveValuesByEvent.delete(event);
|
|
39
|
+
clearOriginalMcpInput(event);
|
|
40
|
+
}
|
|
10
41
|
export function cloneToolEventWithRuntimeMetadata(source, overrides) {
|
|
11
42
|
const clone = attachToolEventSensitiveValues({ ...source, ...overrides }, getToolEventSensitiveValues(source));
|
|
12
43
|
const originalInput = getOriginalMcpInput(source);
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type { McpSafetyOptions } from './mcp-safety.js';
|
|
|
8
8
|
import type { McpMockApprovalRule } from './mcp-mock-approvals.js';
|
|
9
9
|
import type { EvidenceEnvelope, JsonValue } from '../internal/direct-mcp-v2/types.js';
|
|
10
10
|
import type { ScenarioMachineV2 } from './scenario-machine-v2.js';
|
|
11
|
+
import type { AgentFlowTrace } from './agent-flow.js';
|
|
12
|
+
export type { AgentFlowTrace, AgentFlowParticipant, AgentFlowInteraction, AgentFlowEvidenceAvailability, AgentFlowEvidenceCompleteness, AgentFlowSummary } from './agent-flow.js';
|
|
11
13
|
export type AgentName = 'claude' | 'codex' | 'cursor' | 'opencode';
|
|
12
14
|
export type AgentInteractionMode = 'prompt' | 'start_chat' | 'conversation';
|
|
13
15
|
/** Runtime channel that actually executed the agent. */
|
|
@@ -351,11 +353,15 @@ export interface ScorerContext {
|
|
|
351
353
|
artifacts: SessionArtifacts;
|
|
352
354
|
/** Canonical host evidence for ScenarioMachineV2 scorers. */
|
|
353
355
|
scenarioEvidence?: readonly EvidenceEnvelope[];
|
|
356
|
+
/** Protocol-neutral multi-agent execution evidence, when evaluating a flow. */
|
|
357
|
+
flow?: AgentFlowTrace;
|
|
354
358
|
}
|
|
355
359
|
export interface EvaluateOptions {
|
|
356
360
|
failFast?: boolean;
|
|
357
361
|
llm?: LLMPort;
|
|
358
362
|
onScorerError?: 'skip' | 'zero' | 'fail';
|
|
363
|
+
/** Evidence visible to deterministic scorers. Live mode is unavailable for snapshot replay. */
|
|
364
|
+
deterministicToolEvidence?: 'persisted' | 'live';
|
|
359
365
|
/** Stable identity for this evaluation definition across separate runs. */
|
|
360
366
|
evaluationDefinitionKey?: string;
|
|
361
367
|
}
|
|
@@ -7,4 +7,7 @@ export declare function collectSensitiveEnvValues(env?: Readonly<Record<string,
|
|
|
7
7
|
* is important for summaries, snippets, traces, and provider error text.
|
|
8
8
|
*/
|
|
9
9
|
export declare function sanitizePersistenceValue<T>(source: T, explicitSensitiveValues?: readonly string[]): T;
|
|
10
|
+
export declare function sanitizeUntrustedPersistenceValue<T>(source: T, explicitSensitiveValues?: readonly string[]): T;
|
|
11
|
+
export declare function isSensitiveValueScanLimitError(error: unknown): boolean;
|
|
10
12
|
export declare function sanitizeToolEventResult(source: Readonly<ToolEventResult>, sensitiveValues: readonly string[]): ToolEventResult;
|
|
13
|
+
export declare function collectStructuredSensitiveValues(source: unknown): string[];
|