@wix/pathgrade 1.0.26 → 1.0.28
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 +14 -21
- package/dist/adapters/jest/invocation-adapter.js +5 -1
- package/dist/adapters/jest/reporter.js +4 -1
- package/dist/adapters/jest/results.js +8 -0
- package/dist/adapters/node-test/index.d.ts +5 -0
- package/dist/adapters/node-test/index.js +35 -7
- package/dist/adapters/node-test/invocation-adapter.js +3 -1
- package/dist/adapters/node-test/runner-adapter.js +22 -15
- package/dist/adapters/vitest/reporter.js +4 -1
- package/dist/agents/claude/sdk-message-projector.js +5 -0
- package/dist/agents/codex-app-server/agent.js +7 -57
- package/dist/agents/codex-app-server/turn-notifications.d.ts +6 -0
- package/dist/agents/codex-app-server/turn-notifications.js +51 -0
- package/dist/agents/codex-app-server/turn-state.d.ts +19 -0
- package/dist/agents/codex-app-server/turn-state.js +1 -0
- package/dist/agents/codex.js +1 -0
- package/dist/agents/cursor.js +1 -0
- package/dist/agents/opencode/protocol.d.ts +7 -0
- package/dist/agents/opencode/protocol.js +47 -0
- package/dist/agents/opencode.js +5 -49
- package/dist/analytics/engine.js +5 -2
- package/dist/commands/report.d.ts +10 -2
- package/dist/commands/report.js +41 -6
- package/dist/commands/run-args.d.ts +1 -0
- package/dist/commands/run-args.js +13 -0
- package/dist/commands/run-changed.js +5 -15
- package/dist/config/pathgrade.d.ts +3 -0
- package/dist/config/pathgrade.js +33 -1
- package/dist/pathgrade.js +32 -3
- package/dist/reporters/cli.js +13 -6
- package/dist/reporters/github-comment.d.ts +12 -3
- package/dist/reporters/github-comment.js +92 -18
- package/dist/reporters/loader.d.ts +1 -0
- package/dist/reporters/loader.js +27 -2
- package/dist/reporters/report-summary.js +13 -5
- package/dist/reporting/artifacts.js +5 -2
- package/dist/reporting/core.d.ts +1 -0
- package/dist/reporting/core.js +183 -105
- package/dist/reporting/types.d.ts +19 -3
- package/dist/runners/adapter-loader.js +17 -12
- package/dist/runners/direct-reporter-attempts.d.ts +1 -0
- package/dist/runners/direct-reporter-attempts.js +7 -0
- package/dist/runners/invocation.d.ts +2 -0
- package/dist/runners/model-builders.js +1 -0
- package/dist/runners/model-validation.js +31 -0
- package/dist/runners/model.d.ts +4 -0
- package/dist/runners/orchestrator.d.ts +2 -0
- package/dist/runners/orchestrator.js +11 -1
- package/dist/runners/repeated-attempts.d.ts +7 -0
- package/dist/runners/repeated-attempts.js +149 -0
- package/dist/runners/repeated-invocation.d.ts +7 -0
- package/dist/runners/repeated-invocation.js +129 -0
- package/dist/runners/report-projection.js +16 -6
- package/dist/runners/vitest-adapter.js +10 -0
- package/dist/runners/vitest-invocation.js +2 -0
- package/dist/sdk/agent-runtime-options.d.ts +12 -0
- package/dist/sdk/agent-runtime-options.js +67 -0
- package/dist/sdk/agent.js +11 -59
- package/dist/sdk/case-context.js +7 -2
- package/dist/sdk/evaluate.d.ts +2 -0
- package/dist/sdk/evaluate.js +14 -9
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/lifecycle.js +16 -6
- package/dist/sdk/result-capture.js +4 -1
- package/dist/sdk/types.d.ts +2 -0
- package/dist/tool-event-results.d.ts +1 -1
- package/dist/tool-event-results.js +2 -1
- package/dist/tool-events.d.ts +5 -0
- package/dist/tool-events.js +5 -0
- package/dist/types.d.ts +34 -5
- package/dist/viewer.html +19 -19
- package/package.json +2 -2
package/dist/sdk/evaluate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractSkillsFromLog, extractToolEventsFromLog } from '../tool-events.js';
|
|
1
|
+
import { countShellCommandsFromLog, extractSkillsFromLog, extractToolEventsFromLog } from '../tool-events.js';
|
|
2
2
|
import { getRuntime } from './eval-runtime.js';
|
|
3
3
|
import { emitEvalResult } from './result-capture.js';
|
|
4
4
|
import { runJudgePipeline } from './judge-pipeline.js';
|
|
@@ -33,7 +33,7 @@ export class EvalScorerError extends Error {
|
|
|
33
33
|
// Local per-call: no module-level state, no cleanup export needed.
|
|
34
34
|
function makeEvaluateAgent() {
|
|
35
35
|
const conversationAttributed = new WeakSet();
|
|
36
|
-
return async function evaluateAgent(agent, scorers, opts) {
|
|
36
|
+
return async function evaluateAgent(agent, scorers, opts, attributeConversation = true) {
|
|
37
37
|
const toolEvents = extractToolEventsFromLog(agent.log);
|
|
38
38
|
const ctx = {
|
|
39
39
|
workspace: agent.workspace,
|
|
@@ -46,12 +46,12 @@ function makeEvaluateAgent() {
|
|
|
46
46
|
};
|
|
47
47
|
const trackedLLM = opts?.llm ?? agent.llm;
|
|
48
48
|
// Snapshot conversation tokens BEFORE running scorers, for first-eval attribution.
|
|
49
|
-
const before =
|
|
49
|
+
const before = agent.llm.tokenUsage ?? { inputTokens: 0, outputTokens: 0 };
|
|
50
50
|
// Snapshot conversation cost too. AgentImpl's `sendTurn` accumulates
|
|
51
51
|
// per-turn `costUsd` onto `trackedLLM` via `addCost`, so by the time
|
|
52
52
|
// `evaluate()` runs the pre-evaluate cost is the conversation's
|
|
53
53
|
// accumulated agent-turn cost.
|
|
54
|
-
const beforeCostUsd =
|
|
54
|
+
const beforeCostUsd = agent.llm.costUsd ?? 0;
|
|
55
55
|
// measure() returns the delta consumed by this evaluate call.
|
|
56
56
|
const scoringStartedAt = performance.now();
|
|
57
57
|
const { result: evalResult, tokens: deltaTokenUsage } = trackedLLM.measure
|
|
@@ -60,12 +60,13 @@ function makeEvaluateAgent() {
|
|
|
60
60
|
const r = await evaluateWithContext(ctx, scorers, { ...opts, llm: trackedLLM });
|
|
61
61
|
return { result: r, tokens: r.tokenUsage ?? { inputTokens: 0, outputTokens: 0 } };
|
|
62
62
|
})();
|
|
63
|
-
// Attribute conversation tokens on the first evaluate() for this agent.
|
|
64
|
-
const isFirstEval = !conversationAttributed.has(agent);
|
|
63
|
+
// Attribute conversation tokens on the first public evaluate() for this agent.
|
|
64
|
+
const isFirstEval = attributeConversation && !conversationAttributed.has(agent);
|
|
65
65
|
const conversationTokens = isFirstEval && (before.inputTokens > 0 || before.outputTokens > 0)
|
|
66
66
|
? { conversation_input_tokens: before.inputTokens, conversation_output_tokens: before.outputTokens }
|
|
67
67
|
: undefined;
|
|
68
|
-
//
|
|
68
|
+
// Agent usage is independent of the optional scorer LLM. Same
|
|
69
|
+
// first-eval attribution rule for cost; omit it when unavailable.
|
|
69
70
|
// no conversation cost was captured (Codex / Cursor today).
|
|
70
71
|
const conversationCost = isFirstEval && beforeCostUsd > 0
|
|
71
72
|
? { conversation_cost_usd: beforeCostUsd }
|
|
@@ -89,6 +90,10 @@ function makeEvaluateAgent() {
|
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
92
|
const evaluateAgent = makeEvaluateAgent();
|
|
93
|
+
/** Internal runConversation hook: step scorers must not consume final-run attribution. */
|
|
94
|
+
export function evaluateStepScorers(agent, scorers, opts) {
|
|
95
|
+
return evaluateAgent(agent, scorers, opts, false);
|
|
96
|
+
}
|
|
92
97
|
async function fromSnapshot(snapshotPath, scorers, opts) {
|
|
93
98
|
const snapshot = await loadRunSnapshot(snapshotPath);
|
|
94
99
|
const trackedLLM = opts?.llm ?? createLLMClient({ adapters: [{
|
|
@@ -268,7 +273,7 @@ function matchesArtifactPattern(artifactPath, pattern) {
|
|
|
268
273
|
return pattern.test(artifactPath);
|
|
269
274
|
}
|
|
270
275
|
function buildTrialResult(log, result, scenarioEvidence, conversationTokens, conversationCost) {
|
|
271
|
-
const nCommands = log
|
|
276
|
+
const nCommands = countShellCommandsFromLog(log);
|
|
272
277
|
const skills = extractSkillsFromLog(log);
|
|
273
278
|
return {
|
|
274
279
|
trial_id: 0,
|
|
@@ -300,7 +305,7 @@ function getProcessEnv() {
|
|
|
300
305
|
}
|
|
301
306
|
return env;
|
|
302
307
|
}
|
|
303
|
-
export const evaluate = Object.assign(evaluateAgent, {
|
|
308
|
+
export const evaluate = Object.assign((agent, scorers, opts) => evaluateAgent(agent, scorers, opts), {
|
|
304
309
|
fromSnapshot,
|
|
305
310
|
});
|
|
306
311
|
function toTrialScorerResult(result) {
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export { previewReactions } from './reaction-preview.js';
|
|
|
19
19
|
export { setRuntime, resetRuntime } from './eval-runtime.js';
|
|
20
20
|
export { DEFAULT_COPY_IGNORE } from '../providers/copy-filter.js';
|
|
21
21
|
export { extractToolEventsFromLog } from '../tool-events.js';
|
|
22
|
+
export { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../tool-event-results.js';
|
|
23
|
+
export { parseEnvFile } from '../utils/env.js';
|
|
22
24
|
export { createAskBus, requireAskBusForLiveBatches, AskBusTimeoutError } from './ask-bus/bus.js';
|
|
23
25
|
export { toAskUserToolEvent } from './ask-bus/projection.js';
|
|
24
26
|
export type { AskUserToolEvent, AskUserToolEventArguments, AskUserToolEventQuestionArgument, } from './ask-bus/projection.js';
|
package/dist/sdk/index.js
CHANGED
|
@@ -17,6 +17,8 @@ export { previewReactions } from './reaction-preview.js';
|
|
|
17
17
|
export { setRuntime, resetRuntime } from './eval-runtime.js';
|
|
18
18
|
export { DEFAULT_COPY_IGNORE } from '../providers/copy-filter.js';
|
|
19
19
|
export { extractToolEventsFromLog } from '../tool-events.js';
|
|
20
|
+
export { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../tool-event-results.js';
|
|
21
|
+
export { parseEnvFile } from '../utils/env.js';
|
|
20
22
|
export { createAskBus, requireAskBusForLiveBatches, AskBusTimeoutError } from './ask-bus/bus.js';
|
|
21
23
|
export { toAskUserToolEvent } from './ask-bus/projection.js';
|
|
22
24
|
export { buildAskBatchLogEntries } from './agent-result-log.js';
|
package/dist/sdk/lifecycle.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { getCurrentCaseContext } from './case-context.js';
|
|
2
2
|
import { buildDiagnosticsReport } from './diagnostics.js';
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
import { countShellCommandsFromLog } from '../tool-events.js';
|
|
4
|
+
const lifecycleStateKey = Symbol.for('@wix/pathgrade/lifecycle-state');
|
|
5
|
+
const sharedLifecycle = globalThis;
|
|
6
|
+
const lifecycleState = sharedLifecycle[lifecycleStateKey] ?? (sharedLifecycle[lifecycleStateKey] = {
|
|
7
|
+
pendingAgents: new Set(),
|
|
8
|
+
agentOwners: new WeakMap(),
|
|
9
|
+
agentResults: new WeakMap(),
|
|
10
|
+
});
|
|
11
|
+
const { pendingAgents, agentOwners, agentResults } = lifecycleState;
|
|
6
12
|
function currentAgentOwner() {
|
|
7
13
|
const current = getCurrentCaseContext();
|
|
8
14
|
if (current.status !== 'active')
|
|
@@ -131,7 +137,7 @@ async function flushCase(input) {
|
|
|
131
137
|
function synthesizeTrialFromAgent(agent) {
|
|
132
138
|
if (agent.log.length === 0)
|
|
133
139
|
return null;
|
|
134
|
-
const nCommands = agent.log
|
|
140
|
+
const nCommands = countShellCommandsFromLog(agent.log);
|
|
135
141
|
const tokenUsage = agent.llm.tokenUsage;
|
|
136
142
|
const conversationEnd = [...agent.log].reverse().find((entry) => entry.type === 'conversation_end');
|
|
137
143
|
const completionReason = conversationEnd?.completion_reason ?? (agent.log.some((entry) => entry.type === 'agent_result') ? 'completed' : undefined);
|
|
@@ -147,8 +153,12 @@ function synthesizeTrialFromAgent(agent) {
|
|
|
147
153
|
n_commands: nCommands,
|
|
148
154
|
input_tokens: 0,
|
|
149
155
|
output_tokens: 0,
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
...(tokenUsage && (tokenUsage.inputTokens > 0 || tokenUsage.outputTokens > 0)
|
|
157
|
+
? {
|
|
158
|
+
conversation_input_tokens: tokenUsage.inputTokens,
|
|
159
|
+
conversation_output_tokens: tokenUsage.outputTokens,
|
|
160
|
+
}
|
|
161
|
+
: {}),
|
|
152
162
|
session_log: [...agent.log],
|
|
153
163
|
},
|
|
154
164
|
diagnostics: buildDiagnosticsReport({
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getCurrentCaseContext } from './case-context.js';
|
|
2
|
-
const
|
|
2
|
+
const observerRegistryKey = Symbol.for('@wix/pathgrade/eval-result-observers');
|
|
3
|
+
const globalRegistry = globalThis;
|
|
4
|
+
const observers = globalRegistry[observerRegistryKey]
|
|
5
|
+
?? (globalRegistry[observerRegistryKey] = new Set());
|
|
3
6
|
export function subscribeToEvalResults(observer, options = {}) {
|
|
4
7
|
if (options.owner === 'adapter' && options.key) {
|
|
5
8
|
removeObserverByOwnerAndKey(options.owner, options.key);
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface AgentExecutionMetadata {
|
|
|
17
17
|
name: AgentName;
|
|
18
18
|
/** Model override requested by the caller; not necessarily the provider-resolved model. */
|
|
19
19
|
requestedModel?: string;
|
|
20
|
+
/** Concrete model observed or selected for the executed turn. */
|
|
21
|
+
resolvedModel?: string;
|
|
20
22
|
transport?: AgentExecutionTransport;
|
|
21
23
|
interactionMode?: AgentInteractionMode;
|
|
22
24
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ToolEventResult } from './tool-events.js';
|
|
2
2
|
export declare const TOOL_RESULT_MAX_CHARS: number;
|
|
3
|
-
export declare function collectSensitiveEnvValues(env?: Readonly<Record<string, string>>): string[];
|
|
3
|
+
export declare function collectSensitiveEnvValues(env?: Readonly<Record<string, string | undefined>>): string[];
|
|
4
4
|
/**
|
|
5
5
|
* Clone a persistence payload while removing secrets from both structured
|
|
6
6
|
* containers and any other strings that repeat their values. The second pass
|
|
@@ -41,6 +41,7 @@ const NON_SECRET_ENVIRONMENT_KEY_NAMES = new Set(['tokencount', 'tokenizersparal
|
|
|
41
41
|
const BOUNDARY_ONLY_SENSITIVE_VALUE_MAX_LENGTH = 1;
|
|
42
42
|
export function collectSensitiveEnvValues(env) {
|
|
43
43
|
return [...new Set(Object.entries(env ?? {})
|
|
44
|
+
.filter((entry) => typeof entry[1] === 'string')
|
|
44
45
|
.filter(([key, value]) => isSecretEnvironmentKey(key) && value.length > 0)
|
|
45
46
|
.map(([, value]) => value))]
|
|
46
47
|
.sort((a, b) => b.length - a.length);
|
|
@@ -153,7 +154,7 @@ function redactCredentialShapes(value, sensitiveValues, explicitSensitiveValues)
|
|
|
153
154
|
redacted = redacted.replace(/([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s/@]+(@)/gi, '$1<redacted>$2');
|
|
154
155
|
}
|
|
155
156
|
if (redacted.includes('=') || redacted.includes(':')) {
|
|
156
|
-
redacted = redacted.replace(/(\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|passwd|session(?:id)?|cookie)\s*[=:]\s*)[^\s,;"'}]+/gi, '$1<redacted>');
|
|
157
|
+
redacted = redacted.replace(/(\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|token|secret|auth|password|passwd|session(?:id)?|cookie)\s*[=:]\s*)[^\s,;"'}]+/gi, '$1<redacted>');
|
|
157
158
|
}
|
|
158
159
|
return redacted;
|
|
159
160
|
}
|
package/dist/tool-events.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ export declare function extractToolEventsFromLog(log: ReadonlyArray<{
|
|
|
64
64
|
type: string;
|
|
65
65
|
tool_event?: ToolEvent;
|
|
66
66
|
}>): ToolEvent[];
|
|
67
|
+
/** Count shell commands across legacy command logs and provider tool events. */
|
|
68
|
+
export declare function countShellCommandsFromLog(log: ReadonlyArray<{
|
|
69
|
+
type: string;
|
|
70
|
+
tool_event?: ToolEvent;
|
|
71
|
+
}>): number;
|
|
67
72
|
/**
|
|
68
73
|
* Extract deduplicated skill names from tool events that have action 'use_skill'.
|
|
69
74
|
*/
|
package/dist/tool-events.js
CHANGED
|
@@ -101,6 +101,11 @@ export function extractToolEventsFromLog(log) {
|
|
|
101
101
|
.filter((e) => e.type === 'tool_event' && e.tool_event != null)
|
|
102
102
|
.map((e) => e.tool_event);
|
|
103
103
|
}
|
|
104
|
+
/** Count shell commands across legacy command logs and provider tool events. */
|
|
105
|
+
export function countShellCommandsFromLog(log) {
|
|
106
|
+
return log.filter((entry) => entry.type === 'command'
|
|
107
|
+
|| (entry.type === 'tool_event' && entry.tool_event?.action === 'run_shell')).length;
|
|
108
|
+
}
|
|
104
109
|
/**
|
|
105
110
|
* Extract deduplicated skill names from tool events that have action 'use_skill'.
|
|
106
111
|
*/
|
package/dist/types.d.ts
CHANGED
|
@@ -150,6 +150,14 @@ export interface LogEntry {
|
|
|
150
150
|
}
|
|
151
151
|
export interface TrialResult {
|
|
152
152
|
trial_id: number;
|
|
153
|
+
/** Stable semantic case identity in schema-v2 reports. */
|
|
154
|
+
case_id?: string;
|
|
155
|
+
/** Unique identity for one runner-owned execution of the case. */
|
|
156
|
+
attempt_id?: string;
|
|
157
|
+
/** One-based invocation index. */
|
|
158
|
+
attempt_index?: number;
|
|
159
|
+
/** Display-safe runner outcome for this specific attempt. */
|
|
160
|
+
runner_outcome?: import('./runners/model.js').AttemptOutcome['kind'];
|
|
153
161
|
name?: string;
|
|
154
162
|
reward?: number;
|
|
155
163
|
scorer_results: ScorerResult[];
|
|
@@ -192,9 +200,22 @@ export interface TrialResult {
|
|
|
192
200
|
}
|
|
193
201
|
export interface EvalReport {
|
|
194
202
|
task: string;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
/** Canonical status for this report or consolidated group when available. */
|
|
204
|
+
status?: 'pass' | 'fail';
|
|
205
|
+
/** Schema-v2 canonical average score. */
|
|
206
|
+
mean_reward?: number;
|
|
207
|
+
/** Binary success frequency, absent when attempts are ineligible. */
|
|
208
|
+
success_rate?: number;
|
|
209
|
+
/** Finite-sample pass@k values keyed by k in schema v2; number only for v1 reads. */
|
|
210
|
+
pass_at_k?: Record<string, number> | number;
|
|
211
|
+
pass_at_k_method?: 'finite_sample_unbiased';
|
|
212
|
+
pass_at_k_unavailable_reason?: PassAtKUnavailableReason;
|
|
213
|
+
eligible_case_count?: number;
|
|
214
|
+
attempts_per_case?: number;
|
|
215
|
+
/** @deprecated Version-1 artifact compatibility only. */
|
|
216
|
+
pass_rate?: number;
|
|
217
|
+
/** @deprecated Version-1 artifact compatibility only. New writers omit it. */
|
|
218
|
+
pass_pow_k?: number;
|
|
198
219
|
trials: TrialResult[];
|
|
199
220
|
skills_used: string[];
|
|
200
221
|
}
|
|
@@ -208,7 +229,8 @@ export type StrippedTrialResult = Omit<TrialResult, 'session_log' | 'conversatio
|
|
|
208
229
|
/**
|
|
209
230
|
* Per-group entry in the consolidated `.pathgrade/results.json` report.
|
|
210
231
|
*/
|
|
211
|
-
export type PathgradeGroupReport = Omit<EvalReport, 'trials'> & {
|
|
232
|
+
export type PathgradeGroupReport = Omit<EvalReport, 'trials' | 'status'> & {
|
|
233
|
+
status?: 'pass' | 'fail';
|
|
212
234
|
trials: StrippedTrialResult[];
|
|
213
235
|
/** Relative path from `.pathgrade/` to the trace file for this group. */
|
|
214
236
|
trace_file: string;
|
|
@@ -239,12 +261,16 @@ export interface PathgradeSelectionReport {
|
|
|
239
261
|
* external consumers can gate on schema revisions.
|
|
240
262
|
*/
|
|
241
263
|
export interface PathgradeReport {
|
|
242
|
-
version: 1;
|
|
264
|
+
version: 1 | 2;
|
|
243
265
|
timestamp: string;
|
|
244
266
|
/** `ci.threshold` from Pathgrade config or legacy plugin config, if configured. */
|
|
245
267
|
threshold?: number;
|
|
246
268
|
/** Weighted average of every individual trial score across all groups. */
|
|
247
269
|
overall_pass_rate: number;
|
|
270
|
+
/** Canonical schema-v2 average reward. */
|
|
271
|
+
overall_mean_reward?: number;
|
|
272
|
+
attempts_requested?: number;
|
|
273
|
+
attempts_completed?: number;
|
|
248
274
|
/**
|
|
249
275
|
* Threshold check result. When `threshold` is set: `'pass'` iff
|
|
250
276
|
* `overall_pass_rate >= threshold`. Otherwise: `'pass'` iff every trial
|
|
@@ -259,6 +285,7 @@ export interface PathgradeReport {
|
|
|
259
285
|
*/
|
|
260
286
|
selection?: PathgradeSelectionReport;
|
|
261
287
|
}
|
|
288
|
+
export type PassAtKUnavailableReason = 'non_binary_reward' | 'incomplete_attempts' | 'case_identity_mismatch';
|
|
262
289
|
export interface TrialPaths {
|
|
263
290
|
root: string;
|
|
264
291
|
workspace: string;
|
|
@@ -291,6 +318,8 @@ export interface AgentTurnResult {
|
|
|
291
318
|
timedOut?: boolean;
|
|
292
319
|
toolEvents: import('./tool-events.js').ToolEvent[];
|
|
293
320
|
runtimePoliciesApplied?: RuntimePolicyDescriptor[];
|
|
321
|
+
/** Concrete model that executed this turn, as resolved by the provider or runner. */
|
|
322
|
+
resolvedModel?: string;
|
|
294
323
|
inputTokens?: number;
|
|
295
324
|
outputTokens?: number;
|
|
296
325
|
/**
|
package/dist/viewer.html
CHANGED
|
@@ -838,12 +838,13 @@
|
|
|
838
838
|
applyTheme(getTheme());
|
|
839
839
|
|
|
840
840
|
// ─── State ─────────────────────────────────────────────────
|
|
841
|
-
let reports = [];
|
|
842
|
-
let sortKey = 'date';
|
|
843
|
-
let sortDir = 'desc';
|
|
841
|
+
let reports = [], sortKey = 'date', sortDir = 'desc';
|
|
844
842
|
const $ = id => document.getElementById(id);
|
|
845
843
|
const esc = s => s ? String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') : '';
|
|
846
|
-
|
|
844
|
+
const meanReward = r => r.mean_reward ?? r.pass_rate ?? 0;
|
|
845
|
+
const reportStatus = r => r.status === 'pass' || r.status === 'fail' ? r.status : (meanReward(r) >= 0.5 ? 'pass' : 'fail');
|
|
846
|
+
const passAtKSortValue = r => typeof r.pass_at_k === 'number' ? r.pass_at_k : (r.pass_at_k ? Math.max(...Object.values(r.pass_at_k)) : -1);
|
|
847
|
+
const passAtKDisplay = r => typeof r.pass_at_k === 'number' ? (r.pass_at_k * 100).toFixed(0) + '% v1' : r.pass_at_k ? Object.entries(r.pass_at_k).map(([k, value]) => '@' + k + ' ' + (value * 100).toFixed(0) + '%').join(' · ') : r.pass_at_k_unavailable_reason ? '— ' + r.pass_at_k_unavailable_reason.replaceAll('_', ' ') : '—';
|
|
847
848
|
function parseDate(file) {
|
|
848
849
|
// filename format: 2026-04-08T14-25-03-134Z → 2026-04-08T14:25:03.134
|
|
849
850
|
const m = file.match(/(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d+)/);
|
|
@@ -877,10 +878,10 @@
|
|
|
877
878
|
switch (sortKey) {
|
|
878
879
|
case 'task': va = (a.task || '').toLowerCase(); vb = (b.task || '').toLowerCase(); break;
|
|
879
880
|
case 'date': va = parseDate(a.file); vb = parseDate(b.file); break;
|
|
880
|
-
case '
|
|
881
|
-
case '
|
|
882
|
-
case '
|
|
883
|
-
case 'status': va = a
|
|
881
|
+
case 'mean_reward': va = meanReward(a); vb = meanReward(b); break;
|
|
882
|
+
case 'success_rate': va = a.success_rate ?? -1; vb = b.success_rate ?? -1; break;
|
|
883
|
+
case 'pass_at_k': va = passAtKSortValue(a); vb = passAtKSortValue(b); break;
|
|
884
|
+
case 'status': va = reportStatus(a) === 'pass' ? 1 : 0; vb = reportStatus(b) === 'pass' ? 1 : 0; break;
|
|
884
885
|
default: return 0;
|
|
885
886
|
}
|
|
886
887
|
if (va < vb) return sortDir === 'asc' ? -1 : 1;
|
|
@@ -936,24 +937,23 @@
|
|
|
936
937
|
const header = `<div class="reports-header">
|
|
937
938
|
${colHeader('task', 'Task')}
|
|
938
939
|
${colHeader('date', 'Date')}
|
|
939
|
-
${colHeader('
|
|
940
|
+
${colHeader('mean_reward', 'Mean Reward', 'center')}
|
|
941
|
+
${colHeader('success_rate', 'Success Rate', 'center')}
|
|
940
942
|
${colHeader('pass_at_k', 'pass@k', 'center')}
|
|
941
|
-
${colHeader('pass_pow_k', 'pass^k', 'center')}
|
|
942
943
|
${colHeader('status', 'Status', 'center')}
|
|
943
944
|
<div></div>
|
|
944
945
|
</div>`;
|
|
945
946
|
|
|
946
947
|
const rows = sorted.map(r => {
|
|
947
|
-
const
|
|
948
|
-
const isPass = r.pass_rate >= 0.5;
|
|
948
|
+
const reward = (meanReward(r) * 100).toFixed(0), isPass = reportStatus(r) === 'pass';
|
|
949
949
|
const ts = r.timestamp || parseDate(r.file);
|
|
950
950
|
|
|
951
951
|
return `<a class="report-row" onclick="navigate('detail','${esc(r.file)}')" href="javascript:void(0)">
|
|
952
952
|
<div class="report-task">${esc(r.task)}</div>
|
|
953
953
|
<div class="report-date">${formatDate(ts)}</div>
|
|
954
|
-
<div class="report-metric"><div class="val">${
|
|
955
|
-
<div class="report-metric"><div class="val">${r.
|
|
956
|
-
<div class="report-metric"><div class="val">${
|
|
954
|
+
<div class="report-metric"><div class="val">${reward}%</div></div>
|
|
955
|
+
<div class="report-metric"><div class="val">${r.success_rate != null ? (r.success_rate * 100).toFixed(0) + '%' : '—'}</div></div>
|
|
956
|
+
<div class="report-metric"><div class="val">${esc(passAtKDisplay(r))}</div></div>
|
|
957
957
|
<div style="text-align:center"><span class="badge ${isPass ? 'badge-pass' : 'badge-fail'}">${isPass ? 'PASS' : 'FAIL'}</span></div>
|
|
958
958
|
<button class="delete-btn" onclick="event.preventDefault();event.stopPropagation();deleteReport('${esc(r.file)}')" title="Delete report">✕</button>
|
|
959
959
|
</a>`;
|
|
@@ -975,9 +975,9 @@
|
|
|
975
975
|
const totalTokens = r.trials.reduce((s, t) => s + (t.input_tokens || 0) + (t.output_tokens || 0) + (t.conversation_input_tokens || 0) + (t.conversation_output_tokens || 0), 0);
|
|
976
976
|
|
|
977
977
|
const stats = `
|
|
978
|
-
<div class="stat-pill"><div class="val">${(r
|
|
979
|
-
<div class="stat-pill"><div class="val">${r.
|
|
980
|
-
<div class="stat-pill"><div class="val">${
|
|
978
|
+
<div class="stat-pill"><div class="val">${(meanReward(r) * 100).toFixed(1)}%</div><div class="lbl">Mean Reward</div></div>
|
|
979
|
+
<div class="stat-pill"><div class="val">${r.success_rate != null ? (r.success_rate * 100).toFixed(1) + '%' : '—'}</div><div class="lbl">Success Rate</div></div>
|
|
980
|
+
<div class="stat-pill"><div class="val">${esc(passAtKDisplay(r))}</div><div class="lbl">pass@k</div></div>
|
|
981
981
|
<div class="stat-pill"><div class="val">${(avgDur / 1000).toFixed(1)}s</div><div class="lbl">Avg Duration</div></div>
|
|
982
982
|
<div class="stat-pill"><div class="val">~${totalTokens}</div><div class="lbl">Total Tokens</div></div>
|
|
983
983
|
<div class="stat-pill"><div class="val">${r.skills_used?.join(', ') || 'none'}</div><div class="lbl">Skills</div></div>
|
|
@@ -999,7 +999,7 @@
|
|
|
999
999
|
}
|
|
1000
1000
|
function renderTrial(t, idx) {
|
|
1001
1001
|
const evaluated = t.reward !== undefined;
|
|
1002
|
-
const pass = evaluated && t.reward >= 0.5;
|
|
1002
|
+
const pass = evaluated && (t.runner_outcome === undefined ? t.reward >= 0.5 : t.runner_outcome === 'passed' && t.reward === 1);
|
|
1003
1003
|
const dur = ((t.duration_ms || 0) / 1000).toFixed(1);
|
|
1004
1004
|
const tokens = (t.input_tokens || 0) + (t.output_tokens || 0);
|
|
1005
1005
|
const convTokens = (t.conversation_input_tokens || 0) + (t.conversation_output_tokens || 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"packageManager": "yarn@4.12.0",
|
|
5
5
|
"description": "Evaluate whether AI agents discover and use your skills correctly",
|
|
6
6
|
"exports": {
|
|
@@ -140,5 +140,5 @@
|
|
|
140
140
|
"typescript": "^5.9.3",
|
|
141
141
|
"zod": "4.3.6"
|
|
142
142
|
},
|
|
143
|
-
"falconPackageHash": "
|
|
143
|
+
"falconPackageHash": "f8df1d8950a1ce263fb945ab13722fdb8328a2f761153de0df90d0e9"
|
|
144
144
|
}
|