@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.
Files changed (53) hide show
  1. package/README.md +3 -1
  2. package/dist/adapter-kit/index.d.ts +2 -2
  3. package/dist/adapter-kit/index.js +1 -1
  4. package/dist/adapters/jest/lifecycle.js +4 -2
  5. package/dist/adapters/node-test/index.js +4 -2
  6. package/dist/agents/claude/sdk-message-projector.js +4 -5
  7. package/dist/agents/claude/tool-permission-bridge.js +2 -1
  8. package/dist/agents/codex-app-server/item-projection.js +2 -2
  9. package/dist/agents/codex-app-server/mcp-approval-correlator.js +3 -2
  10. package/dist/agents/opencode.js +4 -5
  11. package/dist/commands/report.js +5 -25
  12. package/dist/internal/direct-mcp-v2/acp-author-projector.js +20 -7
  13. package/dist/reporters/cli.js +20 -1
  14. package/dist/reporters/github-comment.js +18 -7
  15. package/dist/reporters/loader.d.ts +4 -0
  16. package/dist/reporters/loader.js +26 -9
  17. package/dist/reporting/comparison-contract.js +20 -6
  18. package/dist/reporting/core.js +18 -8
  19. package/dist/reporting/report-parser.js +57 -2
  20. package/dist/reporting/types.d.ts +2 -1
  21. package/dist/runners/adapter.d.ts +4 -2
  22. package/dist/runners/lifecycle-hooks.js +6 -1
  23. package/dist/runners/orchestrator.js +3 -4
  24. package/dist/runners/repeated-invocation.js +2 -5
  25. package/dist/runners/report-projection.js +1 -0
  26. package/dist/runners/vitest-lifecycle.d.ts +1 -0
  27. package/dist/runners/vitest-lifecycle.js +21 -4
  28. package/dist/sdk/agent-flow.d.ts +51 -0
  29. package/dist/sdk/agent-flow.js +23 -0
  30. package/dist/sdk/agent.js +10 -1
  31. package/dist/sdk/evaluate.d.ts +2 -1
  32. package/dist/sdk/evaluate.js +114 -4
  33. package/dist/sdk/index.d.ts +4 -2
  34. package/dist/sdk/index.js +2 -2
  35. package/dist/sdk/judge-prompt-builder.js +11 -7
  36. package/dist/sdk/lifecycle.d.ts +3 -1
  37. package/dist/sdk/lifecycle.js +68 -5
  38. package/dist/sdk/mcp-event-input.d.ts +1 -0
  39. package/dist/sdk/mcp-event-input.js +3 -0
  40. package/dist/sdk/mcp-evidence.js +16 -3
  41. package/dist/sdk/mcp-safety.js +2 -2
  42. package/dist/sdk/result-capture.d.ts +14 -1
  43. package/dist/sdk/result-capture.js +49 -0
  44. package/dist/sdk/scripted-mcp-events.js +3 -2
  45. package/dist/sdk/tool-event-log.js +18 -3
  46. package/dist/sdk/tool-event-secrets.d.ts +4 -0
  47. package/dist/sdk/tool-event-secrets.js +33 -2
  48. package/dist/sdk/types.d.ts +6 -0
  49. package/dist/tool-event-results.d.ts +3 -0
  50. package/dist/tool-event-results.js +153 -23
  51. package/dist/types.d.ts +13 -7
  52. package/docs/agent-flow-evaluation.md +31 -0
  53. package/package.json +3 -2
@@ -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;
@@ -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
- const { pendingAgents, agentOwners, agentResults } = lifecycleState;
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.map((entry) => entry.meta));
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;
@@ -6,3 +6,6 @@ export function attachOriginalMcpInput(event, input) {
6
6
  export function getOriginalMcpInput(event) {
7
7
  return originalInputs.get(event);
8
8
  }
9
+ export function clearOriginalMcpInput(event) {
10
+ originalInputs.delete(event);
11
+ }
@@ -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 = typeof args.server === 'string' ? args.server : undefined;
6
- const toolName = typeof args.tool === 'string' ? args.tool : undefined;
7
- const status = typeof args.status === 'string' ? args.status : undefined;
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);
@@ -1,4 +1,4 @@
1
- import { sanitizePersistenceValue } from '../tool-event-results.js';
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 sanitizePersistenceValue(value);
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
- const persistedEvent = sanitizePersistenceValue(toolEvent, getToolEventSensitiveValues(toolEvent));
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 { attachOriginalMcpInput, getOriginalMcpInput } from './mcp-event-input.js';
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, [...sensitiveValues]);
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);
@@ -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[];