@wix/pathgrade 1.0.26 → 1.0.27
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/agents/claude/sdk-message-projector.js +5 -0
- package/dist/agents/codex-app-server/agent.js +6 -6
- package/dist/agents/codex.js +1 -0
- package/dist/agents/cursor.js +1 -0
- package/dist/agents/opencode.js +4 -2
- package/dist/commands/report.d.ts +10 -2
- package/dist/commands/report.js +25 -4
- package/dist/pathgrade.js +22 -1
- package/dist/reporters/github-comment.d.ts +12 -3
- package/dist/reporters/github-comment.js +80 -9
- package/dist/reporting/artifacts.js +5 -2
- package/dist/sdk/agent.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 +8 -3
- 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 +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -428,14 +428,14 @@ pathgrade analyze [--skill=<name>] [--dir=<path>]
|
|
|
428
428
|
pathgrade affected [--since=<ref>] [--changed-files=<path>] [--explain] [--json]
|
|
429
429
|
pathgrade preview [browser] [--last=N] [--filter=text]
|
|
430
430
|
pathgrade preview-reactions --snapshot <run-snapshot.json> --reactions <file.ts>
|
|
431
|
-
pathgrade report [--results-path=<path>] [--no-comment] [--comment-id=<id>]
|
|
431
|
+
pathgrade report [--results-path=<path>] [--no-comment] [--comment-id=<id>] [--details-url=<url>] [--expected-head-sha=<sha>] [--strict]
|
|
432
432
|
```
|
|
433
433
|
|
|
434
434
|
Useful details:
|
|
435
435
|
|
|
436
436
|
- `pathgrade run --changed` computes affected evals first, writes selection metadata to `.pathgrade/selection.json`, and only then launches the selected runner adapter. Vitest is the default adapter; Jest is selected with `runner.adapter: 'jest'` or `--adapter=jest`.
|
|
437
437
|
- `pathgrade preview browser` starts a local viewer on `http://localhost:3847`.
|
|
438
|
-
- `pathgrade report` posts or updates a PR comment in GitHub Actions; locally it prints the markdown report and then the numeric pass rate.
|
|
438
|
+
- `pathgrade report` posts or updates a PR comment in GitHub Actions; locally it prints the markdown report and then the numeric pass rate. Provider orchestrators can add a `--details-url`, suppress stale updates with `--expected-head-sha`, and opt into surfaced API failures with `--strict`.
|
|
439
439
|
- `pathgrade validate --affected` is a strict mode for CI: every discovered eval must either live under a `SKILL.md` anchor or export valid `__pathgradeMeta`.
|
|
440
440
|
|
|
441
441
|
Run `pathgrade --help` for the full help text.
|
|
@@ -30,6 +30,7 @@ import { parseClaudeSdkMcpToolName } from './mcp-tool-name.js';
|
|
|
30
30
|
import { applyObservedToolResult, extractObservedToolResults, } from './tool-results.js';
|
|
31
31
|
export function projectSdkMessages(input) {
|
|
32
32
|
let sessionId;
|
|
33
|
+
let resolvedModel;
|
|
33
34
|
let initSkills;
|
|
34
35
|
let assistantText = '';
|
|
35
36
|
let resultText = '';
|
|
@@ -52,6 +53,9 @@ export function projectSdkMessages(input) {
|
|
|
52
53
|
sessionId = sid;
|
|
53
54
|
const sub = msg.subtype;
|
|
54
55
|
if (sub === 'init') {
|
|
56
|
+
const model = msg.model;
|
|
57
|
+
if (typeof model === 'string' && model)
|
|
58
|
+
resolvedModel = model;
|
|
55
59
|
const skills = msg.skills;
|
|
56
60
|
if (Array.isArray(skills))
|
|
57
61
|
initSkills = skills.filter((s) => typeof s === 'string');
|
|
@@ -153,6 +157,7 @@ export function projectSdkMessages(input) {
|
|
|
153
157
|
traceOutput,
|
|
154
158
|
toolEvents: finalToolEvents,
|
|
155
159
|
runtimePoliciesApplied: [],
|
|
160
|
+
...(resolvedModel ? { resolvedModel } : {}),
|
|
156
161
|
inputTokens,
|
|
157
162
|
outputTokens,
|
|
158
163
|
...(cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens } : {}),
|
|
@@ -397,7 +397,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
397
397
|
message: info.message ?? 'app-server exited',
|
|
398
398
|
pid: info.pid,
|
|
399
399
|
signal: info.signal,
|
|
400
|
-
sensitiveValues,
|
|
400
|
+
sensitiveValues, resolvedModel: model,
|
|
401
401
|
});
|
|
402
402
|
}
|
|
403
403
|
if (turn.turnFailed) {
|
|
@@ -406,14 +406,14 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
406
406
|
activeTurn: turn,
|
|
407
407
|
exitCode: 1,
|
|
408
408
|
message: turn.failureMessage ?? 'turn failed',
|
|
409
|
-
sensitiveValues,
|
|
409
|
+
sensitiveValues, resolvedModel: model,
|
|
410
410
|
});
|
|
411
411
|
}
|
|
412
412
|
await authoritativeTurnReady;
|
|
413
413
|
if (turn.turnFailed) {
|
|
414
414
|
return assembleTurnResult({
|
|
415
415
|
askBus, activeTurn: turn, exitCode: 1,
|
|
416
|
-
message: turn.failureMessage ?? 'turn failed', sensitiveValues,
|
|
416
|
+
message: turn.failureMessage ?? 'turn failed', sensitiveValues, resolvedModel: model,
|
|
417
417
|
});
|
|
418
418
|
}
|
|
419
419
|
return assembleTurnResult({
|
|
@@ -421,7 +421,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
421
421
|
activeTurn: turn,
|
|
422
422
|
exitCode: 0,
|
|
423
423
|
message: turn.assistantMessageParts.join('\n\n'),
|
|
424
|
-
sensitiveValues,
|
|
424
|
+
sensitiveValues, resolvedModel: model,
|
|
425
425
|
});
|
|
426
426
|
}
|
|
427
427
|
finally {
|
|
@@ -612,7 +612,7 @@ function shouldEnableMcpElicitations(options) {
|
|
|
612
612
|
return options?.mcpConfigOrigin === 'generated_mock';
|
|
613
613
|
}
|
|
614
614
|
function assembleTurnResult(args) {
|
|
615
|
-
const { askBus, activeTurn, exitCode, message, pid, signal, sensitiveValues = [] } = args;
|
|
615
|
+
const { askBus, activeTurn, exitCode, message, pid, signal, sensitiveValues = [], resolvedModel } = args;
|
|
616
616
|
for (const pending of activeTurn.pendingMcpDenials.splice(0)) {
|
|
617
617
|
activeTurn.nonAskToolEvents.push(buildPolicyDeniedMcpToolEvent(activeTurn.turnNumber, pending));
|
|
618
618
|
}
|
|
@@ -641,7 +641,7 @@ function assembleTurnResult(args) {
|
|
|
641
641
|
visibleAssistantMessageSource: 'assistant_message',
|
|
642
642
|
exitCode,
|
|
643
643
|
traceOutput: rawOutput,
|
|
644
|
-
toolEvents,
|
|
644
|
+
toolEvents, resolvedModel,
|
|
645
645
|
...(exitCode !== 0
|
|
646
646
|
? {
|
|
647
647
|
crashInfo: {
|
package/dist/agents/codex.js
CHANGED
package/dist/agents/cursor.js
CHANGED
|
@@ -241,6 +241,7 @@ export class CursorAgent extends BaseAgent {
|
|
|
241
241
|
timedOut: result.timedOut,
|
|
242
242
|
toolEvents,
|
|
243
243
|
runtimePoliciesApplied: appliedRuntimePolicies,
|
|
244
|
+
...(options?.model ? { resolvedModel: options.model } : {}),
|
|
244
245
|
...(parsed.tokenUsage
|
|
245
246
|
? { inputTokens: parsed.tokenUsage.inputTokens, outputTokens: parsed.tokenUsage.outputTokens }
|
|
246
247
|
: {}),
|
package/dist/agents/opencode.js
CHANGED
|
@@ -6,7 +6,7 @@ import { buildSummary, enrichSkillEvents } from '../tool-events.js';
|
|
|
6
6
|
import { collectSensitiveEnvValues, sanitizePersistenceValue, sanitizeToolEventResult, } from '../tool-event-results.js';
|
|
7
7
|
import { readStagedMcpServers } from '../providers/mcp-config.js';
|
|
8
8
|
import { removeSandboxRoot } from '../providers/sandbox-lifecycle.js';
|
|
9
|
-
import { attachTurnResultSensitiveValues } from '../sdk/turn-result-secrets.js';
|
|
9
|
+
import { attachTurnResultSensitiveValues, cloneTurnResultWithSensitiveValues } from '../sdk/turn-result-secrets.js';
|
|
10
10
|
import { attachOriginalMcpInput } from '../sdk/mcp-event-input.js';
|
|
11
11
|
import { attachToolEventSensitiveValues } from '../sdk/tool-event-secrets.js';
|
|
12
12
|
import { currentOpenCodePlatformKey, OPENCODE_RUNTIME_LOCK, } from './opencode/contract.js';
|
|
@@ -451,7 +451,9 @@ class OpenCodeSession {
|
|
|
451
451
|
throw new Error('OpenCode protocol error: resumed session ID changed');
|
|
452
452
|
}
|
|
453
453
|
this.sessionId = parsed.sessionId;
|
|
454
|
-
return parsed.result
|
|
454
|
+
return cloneTurnResultWithSensitiveValues(parsed.result, {
|
|
455
|
+
resolvedModel: this.runtimePolicy.model,
|
|
456
|
+
});
|
|
455
457
|
}
|
|
456
458
|
catch (error) {
|
|
457
459
|
this.failed = true;
|
|
@@ -24,6 +24,14 @@ export interface ReportOptions {
|
|
|
24
24
|
noComment?: boolean;
|
|
25
25
|
/** Override the comment-id used in the dedup marker. See `resolveCommentId`. */
|
|
26
26
|
commentId?: string;
|
|
27
|
+
/** Provider-neutral URL for a richer external report. */
|
|
28
|
+
detailsUrl?: string;
|
|
29
|
+
/** Provider-neutral status notice included in the report body. */
|
|
30
|
+
notice?: string;
|
|
31
|
+
/** Surface missing context, malformed results, and GitHub API failures. */
|
|
32
|
+
strict?: boolean;
|
|
33
|
+
/** Skip a comment update when the pull request has advanced. */
|
|
34
|
+
expectedHeadSha?: string;
|
|
27
35
|
}
|
|
28
36
|
/**
|
|
29
37
|
* Resolve the comment-id used for the dedup marker.
|
|
@@ -33,7 +41,7 @@ export interface ReportOptions {
|
|
|
33
41
|
*/
|
|
34
42
|
export declare function resolveCommentId(explicit?: string): string;
|
|
35
43
|
/**
|
|
36
|
-
* Entry point for `pathgrade report`.
|
|
37
|
-
*
|
|
44
|
+
* Entry point for `pathgrade report`. The default display-only mode never
|
|
45
|
+
* fails CI. Strict mode is intended for orchestrators that own publication.
|
|
38
46
|
*/
|
|
39
47
|
export declare function runReport(cwd: string, opts?: ReportOptions): Promise<void>;
|
package/dist/commands/report.js
CHANGED
|
@@ -66,8 +66,8 @@ function printMarkdownAndPassRate(markdown, passRate) {
|
|
|
66
66
|
console.log(String(passRate));
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* Entry point for `pathgrade report`.
|
|
70
|
-
*
|
|
69
|
+
* Entry point for `pathgrade report`. The default display-only mode never
|
|
70
|
+
* fails CI. Strict mode is intended for orchestrators that own publication.
|
|
71
71
|
*/
|
|
72
72
|
export async function runReport(cwd, opts = {}) {
|
|
73
73
|
const resolvedPath = path.resolve(cwd, opts.resultsPath ?? DEFAULT_RESULTS_PATH);
|
|
@@ -77,6 +77,9 @@ export async function runReport(cwd, opts = {}) {
|
|
|
77
77
|
// Branch point: when true, post the comment and print only the pass rate.
|
|
78
78
|
const shouldPost = inCI && !noComment;
|
|
79
79
|
const prContext = shouldPost ? resolvePrContext(process.env) : null;
|
|
80
|
+
if (opts.strict && shouldPost && !prContext) {
|
|
81
|
+
throw new Error('pathgrade report: strict PR commenting requires GITHUB_TOKEN, GITHUB_REPOSITORY, and pull-request context');
|
|
82
|
+
}
|
|
80
83
|
let report = null;
|
|
81
84
|
let loadError = null;
|
|
82
85
|
try {
|
|
@@ -95,6 +98,8 @@ export async function runReport(cwd, opts = {}) {
|
|
|
95
98
|
await postOrUpdateComment(prContext, {
|
|
96
99
|
commentId,
|
|
97
100
|
body: markdown,
|
|
101
|
+
strict: opts.strict,
|
|
102
|
+
expectedHeadSha: opts.expectedHeadSha,
|
|
98
103
|
});
|
|
99
104
|
console.log('0');
|
|
100
105
|
}
|
|
@@ -108,15 +113,31 @@ export async function runReport(cwd, opts = {}) {
|
|
|
108
113
|
await postOrUpdateComment(prContext, {
|
|
109
114
|
commentId,
|
|
110
115
|
body: MISSING_RESULTS_BODY,
|
|
116
|
+
strict: opts.strict,
|
|
117
|
+
expectedHeadSha: opts.expectedHeadSha,
|
|
111
118
|
});
|
|
112
119
|
}
|
|
113
120
|
// Always emit a pass rate line so downstream capture doesn't explode.
|
|
114
121
|
console.log('0');
|
|
122
|
+
if (opts.strict)
|
|
123
|
+
throw new Error(`pathgrade report: ${loadError}`);
|
|
115
124
|
return;
|
|
116
125
|
}
|
|
117
|
-
const markdown = formatReportMarkdown(report, {
|
|
126
|
+
const markdown = formatReportMarkdown(report, {
|
|
127
|
+
commentId,
|
|
128
|
+
detailsUrl: opts.detailsUrl,
|
|
129
|
+
notice: opts.notice,
|
|
130
|
+
});
|
|
118
131
|
if (prContext) {
|
|
119
|
-
await postOrUpdateComment(prContext, {
|
|
132
|
+
const postResult = await postOrUpdateComment(prContext, {
|
|
133
|
+
commentId,
|
|
134
|
+
body: markdown,
|
|
135
|
+
strict: opts.strict,
|
|
136
|
+
expectedHeadSha: opts.expectedHeadSha,
|
|
137
|
+
});
|
|
138
|
+
if (postResult === 'stale') {
|
|
139
|
+
console.error('pathgrade report: pull-request head advanced; skipped stale comment update');
|
|
140
|
+
}
|
|
120
141
|
// Only the pass rate goes to stdout — the markdown lives on the PR.
|
|
121
142
|
console.log(String(report.overall_pass_rate));
|
|
122
143
|
return;
|
package/dist/pathgrade.js
CHANGED
|
@@ -156,10 +156,27 @@ export async function runPathgradeCli(options = {}) {
|
|
|
156
156
|
const reportArgs = args.slice(1);
|
|
157
157
|
const resultsPathFlag = reportArgs.find(a => a.startsWith('--results-path='));
|
|
158
158
|
const commentIdFlag = reportArgs.find(a => a.startsWith('--comment-id='));
|
|
159
|
+
const detailsUrlFlag = reportArgs.find(a => a.startsWith('--details-url='));
|
|
160
|
+
const noticeFlag = reportArgs.find(a => a.startsWith('--notice='));
|
|
161
|
+
const expectedHeadShaFlag = reportArgs.find(a => a.startsWith('--expected-head-sha='));
|
|
159
162
|
const noComment = reportArgs.includes('--no-comment');
|
|
163
|
+
const strict = reportArgs.includes('--strict');
|
|
160
164
|
const resultsPath = resultsPathFlag ? resultsPathFlag.split('=').slice(1).join('=') : undefined;
|
|
161
165
|
const commentId = commentIdFlag ? commentIdFlag.split('=').slice(1).join('=') : undefined;
|
|
162
|
-
|
|
166
|
+
const detailsUrl = detailsUrlFlag ? detailsUrlFlag.split('=').slice(1).join('=') : undefined;
|
|
167
|
+
const notice = noticeFlag ? noticeFlag.split('=').slice(1).join('=') : undefined;
|
|
168
|
+
const expectedHeadSha = expectedHeadShaFlag
|
|
169
|
+
? expectedHeadShaFlag.split('=').slice(1).join('=')
|
|
170
|
+
: undefined;
|
|
171
|
+
await runReport(process.cwd(), {
|
|
172
|
+
resultsPath,
|
|
173
|
+
commentId,
|
|
174
|
+
detailsUrl,
|
|
175
|
+
notice,
|
|
176
|
+
expectedHeadSha,
|
|
177
|
+
noComment,
|
|
178
|
+
strict,
|
|
179
|
+
});
|
|
163
180
|
return;
|
|
164
181
|
}
|
|
165
182
|
if (command === 'run' || !command || command.startsWith('-')) {
|
|
@@ -234,6 +251,10 @@ function printHelp(cliName) {
|
|
|
234
251
|
[--results-path=<path>] Override results.json location
|
|
235
252
|
[--no-comment] Print markdown to stdout; do not post
|
|
236
253
|
[--comment-id=<id>] Override comment marker (default: $GITHUB_WORKFLOW:$GITHUB_JOB)
|
|
254
|
+
[--details-url=<url>] Add a provider-neutral full-report link
|
|
255
|
+
[--notice=<text>] Add a provider-neutral report status notice
|
|
256
|
+
[--expected-head-sha=<sha>] Skip stale PR comment updates
|
|
257
|
+
[--strict] Fail on missing context, results, or GitHub errors
|
|
237
258
|
${cliName} affected Print eval files affected by a change-set (one per line)
|
|
238
259
|
[--since=<ref>] Diff <ref>...HEAD (overrides git auto-detection)
|
|
239
260
|
[--changed-files=<path>] Newline-delimited repo-relative file list
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
import type { PathgradeReport, PathgradeSelectionReport } from '../types.js';
|
|
11
11
|
export interface FormatOptions {
|
|
12
12
|
commentId: string;
|
|
13
|
+
/** Provider-neutral URL for a richer external report. */
|
|
14
|
+
detailsUrl?: string;
|
|
15
|
+
/** Provider-neutral status notice rendered above report details. */
|
|
16
|
+
notice?: string;
|
|
13
17
|
}
|
|
14
18
|
/** Minimal body posted when `.pathgrade/results.json` is missing. */
|
|
15
19
|
export declare const MISSING_RESULTS_BODY = "Pathgrade evals did not produce results. Check the workflow logs.";
|
|
@@ -55,14 +59,19 @@ export interface PostOptions {
|
|
|
55
59
|
commentId: string;
|
|
56
60
|
/** Body WITHOUT the marker — the marker is prepended if not already present. */
|
|
57
61
|
body: string;
|
|
62
|
+
/** Throw instead of swallowing GitHub API failures. */
|
|
63
|
+
strict?: boolean;
|
|
64
|
+
/** Skip the update if the pull request has advanced to another head. */
|
|
65
|
+
expectedHeadSha?: string;
|
|
58
66
|
}
|
|
67
|
+
export type PostResult = 'created' | 'updated' | 'stale';
|
|
59
68
|
/**
|
|
60
69
|
* Find an existing PR comment carrying `<!-- pathgrade:${commentId} -->`
|
|
61
|
-
* and update it; otherwise create a new one.
|
|
62
|
-
*
|
|
70
|
+
* and update it; otherwise create a new one. Default mode logs and swallows
|
|
71
|
+
* transport errors; strict mode rethrows them for an owning orchestrator.
|
|
63
72
|
*
|
|
64
73
|
* Bodies longer than GitHub's 65,536-character limit are truncated with a
|
|
65
74
|
* sentinel pointing to workflow artifacts, preserving the leading dedup
|
|
66
75
|
* marker so subsequent runs still find and update this comment.
|
|
67
76
|
*/
|
|
68
|
-
export declare function postOrUpdateComment(ctx: PrContext, opts: PostOptions): Promise<
|
|
77
|
+
export declare function postOrUpdateComment(ctx: PrContext, opts: PostOptions): Promise<PostResult | undefined>;
|
|
@@ -26,6 +26,12 @@ export function formatNoAffectedEvalsMarkdown(selection) {
|
|
|
26
26
|
return lines.join('\n');
|
|
27
27
|
}
|
|
28
28
|
export function commentMarker(commentId) {
|
|
29
|
+
if (commentId.length === 0
|
|
30
|
+
|| commentId.length > 128
|
|
31
|
+
|| /[<>\r\n]/.test(commentId)
|
|
32
|
+
|| commentId.includes('--')) {
|
|
33
|
+
throw new Error('Pathgrade comment id must be 1-128 characters and cannot contain HTML comment delimiters');
|
|
34
|
+
}
|
|
29
35
|
return `<!-- pathgrade:${commentId} -->`;
|
|
30
36
|
}
|
|
31
37
|
function pct(n) {
|
|
@@ -59,9 +65,19 @@ export function formatReportMarkdown(report, opts) {
|
|
|
59
65
|
lines.push('');
|
|
60
66
|
lines.push(`### ${icon} Pathgrade report`);
|
|
61
67
|
lines.push('');
|
|
68
|
+
lines.push(`**Last run:** \`${report.timestamp}\``);
|
|
69
|
+
lines.push('');
|
|
62
70
|
lines.push(`**Pass rate:** ${pct(p)} | ` +
|
|
63
71
|
`**pass@${totalTrials}:** ${pct(overallPassAtK)} | ` +
|
|
64
72
|
`**pass^${totalTrials}:** ${pct(overallPassPowK)}`);
|
|
73
|
+
if (opts.detailsUrl) {
|
|
74
|
+
lines.push('');
|
|
75
|
+
lines.push(`**Details:** [Open the full report](${formatLinkDestination(opts.detailsUrl)})`);
|
|
76
|
+
}
|
|
77
|
+
if (opts.notice) {
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(`> ${opts.notice.replace(/[\r\n]+/g, ' ').trim()}`);
|
|
80
|
+
}
|
|
65
81
|
if (report.threshold != null) {
|
|
66
82
|
lines.push('');
|
|
67
83
|
lines.push(`Threshold: ${pct(report.threshold)} — ${report.status.toUpperCase()}`);
|
|
@@ -100,14 +116,14 @@ export function formatSelectionSection(selection) {
|
|
|
100
116
|
out.push('### Selection');
|
|
101
117
|
const total = selection.selected.length + selection.skipped.length;
|
|
102
118
|
if (selection.global_match) {
|
|
103
|
-
out.push(`
|
|
119
|
+
out.push(`Selected **all ${total}** eval files — global trigger \`${selection.global_match}\` matched.`);
|
|
104
120
|
return out.join('\n');
|
|
105
121
|
}
|
|
106
122
|
if (selection.skipped.length === 0 && selection.selected.length > 0) {
|
|
107
|
-
out.push(`
|
|
123
|
+
out.push(`Selected **all ${total}** eval files — every eval had a matching change.`);
|
|
108
124
|
return out.join('\n');
|
|
109
125
|
}
|
|
110
|
-
out.push(`
|
|
126
|
+
out.push(`Selected **${selection.selected.length} of ${total}** eval files based on changes vs \`${selection.base_ref}\`.`);
|
|
111
127
|
if (selection.skipped.length > 0) {
|
|
112
128
|
out.push('');
|
|
113
129
|
out.push(`<details><summary>${selection.skipped.length} skipped (unaffected)</summary>`);
|
|
@@ -171,13 +187,36 @@ function apiHeaders(token) {
|
|
|
171
187
|
};
|
|
172
188
|
}
|
|
173
189
|
async function listPrComments(ctx) {
|
|
174
|
-
const
|
|
190
|
+
const comments = [];
|
|
191
|
+
for (let page = 1; page <= 100; page += 1) {
|
|
192
|
+
const url = `https://api.github.com/repos/${ctx.owner}/${ctx.repo}/issues/${ctx.prNumber}/comments?per_page=100&page=${page}`;
|
|
193
|
+
const res = await fetch(url, { headers: apiHeaders(ctx.token) });
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
throw new Error(`GET ${url} returned ${res.status}`);
|
|
196
|
+
}
|
|
197
|
+
const data = await res.json();
|
|
198
|
+
if (!Array.isArray(data)) {
|
|
199
|
+
throw new Error(`GET ${url} returned a non-array response`);
|
|
200
|
+
}
|
|
201
|
+
for (const value of data) {
|
|
202
|
+
if (isGithubComment(value))
|
|
203
|
+
comments.push(value);
|
|
204
|
+
}
|
|
205
|
+
if (data.length < 100)
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
return comments;
|
|
209
|
+
}
|
|
210
|
+
async function currentPrHeadSha(ctx) {
|
|
211
|
+
const url = `https://api.github.com/repos/${ctx.owner}/${ctx.repo}/pulls/${ctx.prNumber}`;
|
|
175
212
|
const res = await fetch(url, { headers: apiHeaders(ctx.token) });
|
|
176
|
-
if (!res.ok)
|
|
213
|
+
if (!res.ok)
|
|
177
214
|
throw new Error(`GET ${url} returned ${res.status}`);
|
|
215
|
+
const data = await res.json();
|
|
216
|
+
if (!isRecord(data) || !isRecord(data.head) || typeof data.head.sha !== 'string') {
|
|
217
|
+
throw new Error(`GET ${url} returned no pull-request head SHA`);
|
|
178
218
|
}
|
|
179
|
-
|
|
180
|
-
return Array.isArray(data) ? data : [];
|
|
219
|
+
return data.head.sha;
|
|
181
220
|
}
|
|
182
221
|
async function createPrComment(ctx, body) {
|
|
183
222
|
const url = `https://api.github.com/repos/${ctx.owner}/${ctx.repo}/issues/${ctx.prNumber}/comments`;
|
|
@@ -206,8 +245,8 @@ const GITHUB_COMMENT_MAX = 65536;
|
|
|
206
245
|
const TRUNCATION_SENTINEL = '\n\n_…output truncated to fit GitHub\'s comment size limit — see the workflow artifacts for the full report._';
|
|
207
246
|
/**
|
|
208
247
|
* Find an existing PR comment carrying `<!-- pathgrade:${commentId} -->`
|
|
209
|
-
* and update it; otherwise create a new one.
|
|
210
|
-
*
|
|
248
|
+
* and update it; otherwise create a new one. Default mode logs and swallows
|
|
249
|
+
* transport errors; strict mode rethrows them for an owning orchestrator.
|
|
211
250
|
*
|
|
212
251
|
* Bodies longer than GitHub's 65,536-character limit are truncated with a
|
|
213
252
|
* sentinel pointing to workflow artifacts, preserving the leading dedup
|
|
@@ -221,19 +260,51 @@ export async function postOrUpdateComment(ctx, opts) {
|
|
|
221
260
|
: withMarker.slice(0, GITHUB_COMMENT_MAX - TRUNCATION_SENTINEL.length) +
|
|
222
261
|
TRUNCATION_SENTINEL;
|
|
223
262
|
try {
|
|
263
|
+
if (opts.expectedHeadSha) {
|
|
264
|
+
const currentHead = await currentPrHeadSha(ctx);
|
|
265
|
+
if (currentHead !== opts.expectedHeadSha)
|
|
266
|
+
return 'stale';
|
|
267
|
+
}
|
|
224
268
|
const existing = await listPrComments(ctx);
|
|
225
269
|
const match = existing.find((c) => typeof c.body === 'string' && c.body.includes(marker));
|
|
226
270
|
if (match) {
|
|
227
271
|
await updatePrComment(ctx, match.id, body);
|
|
272
|
+
return 'updated';
|
|
228
273
|
}
|
|
229
274
|
else {
|
|
230
275
|
await createPrComment(ctx, body);
|
|
276
|
+
return 'created';
|
|
231
277
|
}
|
|
232
278
|
}
|
|
233
279
|
catch (err) {
|
|
234
280
|
const message = err instanceof Error ? err.message : String(err);
|
|
235
281
|
console.error(`pathgrade report: failed to post PR comment — ${message}`);
|
|
282
|
+
if (opts.strict)
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function formatLinkDestination(value) {
|
|
287
|
+
let url;
|
|
288
|
+
try {
|
|
289
|
+
url = new URL(value);
|
|
236
290
|
}
|
|
291
|
+
catch {
|
|
292
|
+
throw new Error('Pathgrade details URL must be an absolute HTTP(S) URL');
|
|
293
|
+
}
|
|
294
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
295
|
+
throw new Error('Pathgrade details URL must be an absolute HTTP(S) URL');
|
|
296
|
+
}
|
|
297
|
+
return url.toString().replace(/\)/g, '%29');
|
|
298
|
+
}
|
|
299
|
+
function isRecord(value) {
|
|
300
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
301
|
+
}
|
|
302
|
+
function isGithubComment(value) {
|
|
303
|
+
return isRecord(value)
|
|
304
|
+
&& typeof value.id === 'number'
|
|
305
|
+
&& Number.isSafeInteger(value.id)
|
|
306
|
+
&& value.id > 0
|
|
307
|
+
&& typeof value.body === 'string';
|
|
237
308
|
}
|
|
238
309
|
function formatGroupDetails(group) {
|
|
239
310
|
const out = [];
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
|
+
import { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../tool-event-results.js';
|
|
3
4
|
export async function writePathgradeArtifacts(artifactRoot, built) {
|
|
4
5
|
await fs.ensureDir(path.join(artifactRoot, 'traces'));
|
|
6
|
+
const sensitiveValues = collectSensitiveEnvValues(process.env);
|
|
7
|
+
const report = sanitizePersistenceValue(built.report, sensitiveValues);
|
|
5
8
|
const gitignorePath = path.join(artifactRoot, '.gitignore');
|
|
6
9
|
if (!(await fs.pathExists(gitignorePath))) {
|
|
7
10
|
await fs.writeFile(gitignorePath, '*\n');
|
|
8
11
|
}
|
|
9
12
|
const traceFiles = [];
|
|
10
13
|
for (const trace of built.traces) {
|
|
11
|
-
await fs.writeJson(path.join(artifactRoot, trace.traceFile), trace.trials, { spaces: 2 });
|
|
14
|
+
await fs.writeJson(path.join(artifactRoot, trace.traceFile), sanitizePersistenceValue(trace.trials, sensitiveValues), { spaces: 2 });
|
|
12
15
|
traceFiles.push(trace.traceFile);
|
|
13
16
|
}
|
|
14
17
|
const resultsPath = path.join(artifactRoot, 'results.json');
|
|
15
|
-
await fs.writeJson(resultsPath,
|
|
18
|
+
await fs.writeJson(resultsPath, report, { spaces: 2 });
|
|
16
19
|
return {
|
|
17
20
|
resultsPath,
|
|
18
21
|
traceFiles,
|
package/dist/sdk/agent.js
CHANGED
|
@@ -4,7 +4,7 @@ import { lifecycleCore } from './lifecycle.js';
|
|
|
4
4
|
import { ChatSessionImpl } from './chat.js';
|
|
5
5
|
import { runConversation } from './converse.js';
|
|
6
6
|
import { createPersona } from './persona.js';
|
|
7
|
-
import {
|
|
7
|
+
import { evaluateStepScorers } from './evaluate.js';
|
|
8
8
|
import { createManagedSession } from './managed-session.js';
|
|
9
9
|
import { createAgentLLM } from '../utils/llm.js';
|
|
10
10
|
import { buildRunSnapshot } from './snapshots.js';
|
|
@@ -39,6 +39,7 @@ class AgentImpl {
|
|
|
39
39
|
llm;
|
|
40
40
|
timeoutSetting;
|
|
41
41
|
modelOpt;
|
|
42
|
+
resolvedModel;
|
|
42
43
|
conversationWindowOpt;
|
|
43
44
|
interactionMode = null;
|
|
44
45
|
_messages = [];
|
|
@@ -100,6 +101,7 @@ class AgentImpl {
|
|
|
100
101
|
return {
|
|
101
102
|
name: this.agentName,
|
|
102
103
|
...(this.modelOpt ? { requestedModel: this.modelOpt } : {}),
|
|
104
|
+
...(this.resolvedModel ? { resolvedModel: this.resolvedModel } : {}),
|
|
103
105
|
transport: this.scenarioArtifact && (this.agentName === 'opencode' || this.agentName === 'cursor')
|
|
104
106
|
? 'acp'
|
|
105
107
|
: resolveExecutionTransport(this.agentName, this.transport),
|
|
@@ -145,6 +147,9 @@ class AgentImpl {
|
|
|
145
147
|
return Math.ceil((turns * 80_000 + 200_000) / 1000);
|
|
146
148
|
}
|
|
147
149
|
accumulateTurnUsage(turnResult) {
|
|
150
|
+
if (turnResult.resolvedModel) {
|
|
151
|
+
this.resolvedModel = turnResult.resolvedModel;
|
|
152
|
+
}
|
|
148
153
|
if (turnResult.inputTokens || turnResult.outputTokens) {
|
|
149
154
|
this.llm.addTokens?.(turnResult.inputTokens ?? 0, turnResult.outputTokens ?? 0);
|
|
150
155
|
}
|
|
@@ -303,7 +308,7 @@ class AgentImpl {
|
|
|
303
308
|
// scorer judge calls accumulate on the same tracker.
|
|
304
309
|
const agent = this;
|
|
305
310
|
const runStepScorers = async (scorers) => {
|
|
306
|
-
const result = await
|
|
311
|
+
const result = await evaluateStepScorers(agent, scorers, { llm: this.llm });
|
|
307
312
|
const score = result.score;
|
|
308
313
|
if (score === undefined) {
|
|
309
314
|
throw new Error('Step scorer evaluation did not produce a score');
|
package/dist/sdk/evaluate.d.ts
CHANGED
|
@@ -9,5 +9,7 @@ type EvaluateFromSnapshot = (snapshotPath: string, scorers: Scorer[], opts?: Eva
|
|
|
9
9
|
type EvaluateFn = ((agent: Agent, scorers: Scorer[], opts?: EvaluateOptions) => Promise<RecordedEvalResult>) & {
|
|
10
10
|
fromSnapshot: EvaluateFromSnapshot;
|
|
11
11
|
};
|
|
12
|
+
/** Internal runConversation hook: step scorers must not consume final-run attribution. */
|
|
13
|
+
export declare function evaluateStepScorers(agent: Agent, scorers: Scorer[], opts?: EvaluateOptions): Promise<RecordedEvalResult>;
|
|
12
14
|
export declare const evaluate: EvaluateFn;
|
|
13
15
|
export {};
|
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,5 +1,6 @@
|
|
|
1
1
|
import { getCurrentCaseContext } from './case-context.js';
|
|
2
2
|
import { buildDiagnosticsReport } from './diagnostics.js';
|
|
3
|
+
import { countShellCommandsFromLog } from '../tool-events.js';
|
|
3
4
|
const pendingAgents = new Set();
|
|
4
5
|
const agentOwners = new WeakMap();
|
|
5
6
|
const agentResults = new WeakMap();
|
|
@@ -131,7 +132,7 @@ async function flushCase(input) {
|
|
|
131
132
|
function synthesizeTrialFromAgent(agent) {
|
|
132
133
|
if (agent.log.length === 0)
|
|
133
134
|
return null;
|
|
134
|
-
const nCommands = agent.log
|
|
135
|
+
const nCommands = countShellCommandsFromLog(agent.log);
|
|
135
136
|
const tokenUsage = agent.llm.tokenUsage;
|
|
136
137
|
const conversationEnd = [...agent.log].reverse().find((entry) => entry.type === 'conversation_end');
|
|
137
138
|
const completionReason = conversationEnd?.completion_reason ?? (agent.log.some((entry) => entry.type === 'agent_result') ? 'completed' : undefined);
|
|
@@ -147,8 +148,12 @@ function synthesizeTrialFromAgent(agent) {
|
|
|
147
148
|
n_commands: nCommands,
|
|
148
149
|
input_tokens: 0,
|
|
149
150
|
output_tokens: 0,
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
...(tokenUsage && (tokenUsage.inputTokens > 0 || tokenUsage.outputTokens > 0)
|
|
152
|
+
? {
|
|
153
|
+
conversation_input_tokens: tokenUsage.inputTokens,
|
|
154
|
+
conversation_output_tokens: tokenUsage.outputTokens,
|
|
155
|
+
}
|
|
156
|
+
: {}),
|
|
152
157
|
session_log: [...agent.log],
|
|
153
158
|
},
|
|
154
159
|
diagnostics: buildDiagnosticsReport({
|
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
|
@@ -291,6 +291,8 @@ export interface AgentTurnResult {
|
|
|
291
291
|
timedOut?: boolean;
|
|
292
292
|
toolEvents: import('./tool-events.js').ToolEvent[];
|
|
293
293
|
runtimePoliciesApplied?: RuntimePolicyDescriptor[];
|
|
294
|
+
/** Concrete model that executed this turn, as resolved by the provider or runner. */
|
|
295
|
+
resolvedModel?: string;
|
|
294
296
|
inputTokens?: number;
|
|
295
297
|
outputTokens?: number;
|
|
296
298
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.27",
|
|
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": "5c7c088ce2f8c51308dde9f92d3f259465bbf34021afddb3af39d2d9"
|
|
144
144
|
}
|