agent-skill-evals 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +14 -145
  2. package/dist/agent/index.d.mts +2 -3
  3. package/dist/agent/index.mjs +1 -1
  4. package/dist/agent-B15R7Xlr.mjs +2105 -0
  5. package/dist/agent-B15R7Xlr.mjs.map +1 -0
  6. package/dist/assertions/index.d.mts +6 -0
  7. package/dist/assertions/index.d.mts.map +1 -1
  8. package/dist/assertions/index.mjs +164 -449
  9. package/dist/assertions/index.mjs.map +1 -1
  10. package/dist/catalog-9Wh-AfRv.mjs +409 -0
  11. package/dist/catalog-9Wh-AfRv.mjs.map +1 -0
  12. package/dist/cli/init.d.mts +22 -0
  13. package/dist/cli/init.d.mts.map +1 -0
  14. package/dist/cli/init.mjs +369 -0
  15. package/dist/cli/init.mjs.map +1 -0
  16. package/dist/index-D9lGL0Qg.d.mts +381 -0
  17. package/dist/index-D9lGL0Qg.d.mts.map +1 -0
  18. package/dist/presets-DwfDWckA.mjs +80 -0
  19. package/dist/presets-DwfDWckA.mjs.map +1 -0
  20. package/dist/test-generator/index.d.mts +945 -0
  21. package/dist/test-generator/index.d.mts.map +1 -0
  22. package/dist/test-generator/index.mjs +2 -0
  23. package/dist/test-pack-Cu1WOssl.mjs +215 -0
  24. package/dist/test-pack-Cu1WOssl.mjs.map +1 -0
  25. package/package.json +16 -9
  26. package/schema/test-pack.schema.json +871 -0
  27. package/dist/agent-CM7fIL_C.mjs +0 -1525
  28. package/dist/agent-CM7fIL_C.mjs.map +0 -1
  29. package/dist/assertion-entries-CfmNt-fp.d.mts +0 -9
  30. package/dist/assertion-entries-CfmNt-fp.d.mts.map +0 -1
  31. package/dist/index-4l7TCFny.d.mts +0 -90
  32. package/dist/index-4l7TCFny.d.mts.map +0 -1
  33. package/dist/internal-services-5-mRgNls.mjs +0 -226
  34. package/dist/internal-services-5-mRgNls.mjs.map +0 -1
  35. package/dist/internal-services-DbsekQ_K.d.mts +0 -76
  36. package/dist/internal-services-DbsekQ_K.d.mts.map +0 -1
  37. package/dist/skill-checks/index.d.mts +0 -113
  38. package/dist/skill-checks/index.d.mts.map +0 -1
  39. package/dist/skill-checks/index.mjs +0 -408
  40. package/dist/skill-checks/index.mjs.map +0 -1
@@ -0,0 +1,2105 @@
1
+ import { n as RUNTIME_CHECKS_BY_TYPE } from "./catalog-9Wh-AfRv.mjs";
2
+ import { i as resolveInvocation, n as PRESET_IDS, r as presetStalenessHint } from "./presets-DwfDWckA.mjs";
3
+ import { z } from "zod";
4
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
5
+ import { chmod, copyFile, cp, mkdir, mkdtemp, readFile, readdir, stat, writeFile } from "node:fs/promises";
6
+ import { spawn } from "node:child_process";
7
+ import { tmpdir } from "node:os";
8
+ import { createHash } from "node:crypto";
9
+ import { createServer } from "node:net";
10
+ //#region src/assertion-entries.ts
11
+ function isEntryObject(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ /**
15
+ * Normalizes generated Promptfoo `vars.preconditions | expect` entries.
16
+ *
17
+ * Supports the clean Test Pack shorthand form:
18
+ * `{ "file.exists": { path: "app.js" } }`
19
+ */
20
+ function parseRuntimeTestFields(vars) {
21
+ const preconditions = parseAssertionEntries(vars.preconditions, "preconditions", { allowMissing: true });
22
+ const expect = parseAssertionEntries(vars.expect, "expect", { allowMissing: true });
23
+ return {
24
+ preconditions: preconditions.entries,
25
+ expect: expect.entries,
26
+ errors: [...preconditions.errors, ...expect.errors]
27
+ };
28
+ }
29
+ function parseAssertionEntries(raw, field, options = {}) {
30
+ const entries = [];
31
+ const errors = [];
32
+ if (raw === void 0 || raw === null) {
33
+ if (!options.allowMissing) errors.push({
34
+ field,
35
+ reason: "must be an array of assertion entries"
36
+ });
37
+ return {
38
+ entries,
39
+ errors
40
+ };
41
+ }
42
+ if (!Array.isArray(raw)) return {
43
+ entries,
44
+ errors: [{
45
+ field,
46
+ reason: "must be an array of assertion entries"
47
+ }]
48
+ };
49
+ raw.forEach((entry, index) => {
50
+ const parsed = parseAssertionEntry(entry, field, index);
51
+ if ("error" in parsed) errors.push(parsed.error);
52
+ else entries.push(parsed.entry);
53
+ });
54
+ return {
55
+ entries,
56
+ errors
57
+ };
58
+ }
59
+ function parseAssertionEntry(entry, field, index) {
60
+ if (!isEntryObject(entry)) return { error: {
61
+ field,
62
+ index,
63
+ reason: "entry must be a shorthand assertion object"
64
+ } };
65
+ const keys = Object.keys(entry);
66
+ if (keys.length !== 1) return { error: {
67
+ field,
68
+ index,
69
+ reason: "shorthand assertion object must have exactly one key"
70
+ } };
71
+ const type = keys[0];
72
+ const args = entry[type];
73
+ if (!isEntryObject(args)) return { error: {
74
+ field,
75
+ index,
76
+ reason: `shorthand assertion "${type}" value must be an object`
77
+ } };
78
+ return { entry: {
79
+ type,
80
+ args
81
+ } };
82
+ }
83
+ //#endregion
84
+ //#region src/evidence-schema.ts
85
+ const EVIDENCE_SCHEMA_VERSION = "agent-skill-evals.evidence.v2";
86
+ const OptionalString = z.string().optional();
87
+ const OptionalNumber = z.number().optional();
88
+ const StringArray = z.array(z.string());
89
+ const CommandEventSchema = z.object({
90
+ command: z.string(),
91
+ args: z.array(z.string()).default([]),
92
+ exitCode: z.number(),
93
+ signal: OptionalString,
94
+ stdout: OptionalString,
95
+ stderr: OptionalString,
96
+ startedAt: z.number(),
97
+ durationMs: z.number(),
98
+ turn: OptionalNumber
99
+ });
100
+ const FileEventSchema = z.object({
101
+ path: z.string(),
102
+ op: z.enum([
103
+ "create",
104
+ "modify",
105
+ "delete"
106
+ ])
107
+ });
108
+ const ToolCallEventSchema = z.object({
109
+ tool: z.string(),
110
+ provider: OptionalString,
111
+ server: OptionalString,
112
+ args: z.unknown().optional(),
113
+ result: z.unknown().optional(),
114
+ startedAt: z.number(),
115
+ durationMs: z.number(),
116
+ turn: OptionalNumber
117
+ });
118
+ const SkillLoadEventSchema = z.object({
119
+ skill: z.string(),
120
+ delivery: z.enum([
121
+ "native",
122
+ "mcp",
123
+ "explicit"
124
+ ]),
125
+ provider: OptionalString,
126
+ server: OptionalString,
127
+ source: OptionalString,
128
+ startedAt: z.number()
129
+ });
130
+ const SkillAvailableEventSchema = z.object({
131
+ skill: z.string(),
132
+ path: z.string(),
133
+ role: z.enum([
134
+ "under-test",
135
+ "supporting",
136
+ "distractor"
137
+ ])
138
+ });
139
+ const UsageSchema = z.object({
140
+ inputTokens: OptionalNumber,
141
+ outputTokens: OptionalNumber,
142
+ totalTokens: OptionalNumber,
143
+ cacheReadTokens: OptionalNumber,
144
+ cacheWriteTokens: OptionalNumber
145
+ });
146
+ const RunSummarySchema = z.object({
147
+ runDir: z.string(),
148
+ worldPath: z.string(),
149
+ fixture: z.string().optional(),
150
+ durationMs: OptionalNumber
151
+ });
152
+ const RuntimeIdentitySchema = z.object({
153
+ adapter: OptionalString,
154
+ preset: OptionalString,
155
+ command: OptionalString,
156
+ cliVersion: OptionalString,
157
+ model: OptionalString,
158
+ continuation: z.enum(["transcript-replay", "native-session"]).optional()
159
+ });
160
+ const TurnRecordSchema = z.object({
161
+ turn: z.number(),
162
+ role: z.enum(["user", "agent"]),
163
+ text: z.string(),
164
+ startedAt: z.number(),
165
+ durationMs: z.number(),
166
+ usage: UsageSchema.optional()
167
+ });
168
+ const EvidenceSnapshotSchema = z.object({
169
+ schemaVersion: z.literal(EVIDENCE_SCHEMA_VERSION),
170
+ output: z.string().default(""),
171
+ run: RunSummarySchema,
172
+ commands: z.array(CommandEventSchema).default([]),
173
+ filesWritten: z.array(FileEventSchema).default([]),
174
+ toolCalls: z.array(ToolCallEventSchema).default([]),
175
+ skillsLoaded: z.array(SkillLoadEventSchema).default([]),
176
+ skillsAvailable: z.array(SkillAvailableEventSchema).default([]),
177
+ usage: UsageSchema.default({}),
178
+ turns: z.array(TurnRecordSchema).optional(),
179
+ runtime: RuntimeIdentitySchema.optional(),
180
+ warnings: StringArray.optional(),
181
+ extensions: z.record(z.string(), z.unknown()).optional()
182
+ });
183
+ function decodeEvidenceSnapshot(input) {
184
+ const decoded = EvidenceSnapshotSchema.safeParse(input);
185
+ if (decoded.success) return {
186
+ ok: true,
187
+ value: decoded.data
188
+ };
189
+ return {
190
+ ok: false,
191
+ error: /* @__PURE__ */ new Error(`the evidence file does not match the agent-skill-evals.evidence.v2 shape. It was probably written by a different agent-skill-evals version or edited by hand — re-run the eval to regenerate it. Details: ${z.prettifyError(decoded.error)}`)
192
+ };
193
+ }
194
+ function parseEvidenceSnapshot(input) {
195
+ const decoded = decodeEvidenceSnapshot(input);
196
+ if (decoded.ok) return decoded.value;
197
+ throw decoded.error;
198
+ }
199
+ //#endregion
200
+ //#region src/agent/evidence.ts
201
+ const DEFAULT_SKILL_EVIDENCE_CONFIG = {
202
+ mcpResource: {
203
+ uriArgPaths: ["uri"],
204
+ uriPatterns: ["^skill://(?<skill>[^/]+)/SKILL\\.md$"]
205
+ },
206
+ mcpTool: { toolPatterns: ["^load_(?<skill>[A-Za-z0-9_-]+)_skill$"] }
207
+ };
208
+ var EvidenceCollector = class EvidenceCollector {
209
+ skillEvidenceConfig;
210
+ currentTurn;
211
+ /** Usage from completed turns; the live snapshot.usage covers only the current turn. */
212
+ completedTurnsUsage = {};
213
+ snapshot = {
214
+ schemaVersion: EVIDENCE_SCHEMA_VERSION,
215
+ output: "",
216
+ run: {
217
+ runDir: "",
218
+ worldPath: "",
219
+ fixture: ""
220
+ },
221
+ commands: [],
222
+ filesWritten: [],
223
+ toolCalls: [],
224
+ skillsLoaded: [],
225
+ skillsAvailable: [],
226
+ usage: {}
227
+ };
228
+ constructor(skillEvidenceConfig = {}) {
229
+ this.skillEvidenceConfig = mergeSkillEvidenceConfig(skillEvidenceConfig);
230
+ }
231
+ addCommand(e) {
232
+ this.snapshot.commands.push(this.withTurn(e));
233
+ }
234
+ addFileWrite(e) {
235
+ this.snapshot.filesWritten.push(e);
236
+ }
237
+ addToolCall(e) {
238
+ const event = this.withTurn(e);
239
+ this.snapshot.toolCalls.push(event);
240
+ const skillLoad = skillLoadFromToolCall(event, this.skillEvidenceConfig);
241
+ if (skillLoad) this.addSkillLoad(skillLoad);
242
+ }
243
+ withTurn(event) {
244
+ return this.currentTurn === void 0 ? event : {
245
+ ...event,
246
+ turn: this.currentTurn
247
+ };
248
+ }
249
+ /**
250
+ * Start an agent turn in a multi-turn run. Adapters overwrite usage per
251
+ * CLI invocation (setUsage), so each turn's usage is scoped: the previous
252
+ * turn's counts move into the completed-turns total first.
253
+ */
254
+ beginAgentTurn(turn) {
255
+ this.completedTurnsUsage = mergeUsage(this.completedTurnsUsage, this.snapshot.usage);
256
+ this.snapshot.usage = {};
257
+ this.currentTurn = turn;
258
+ }
259
+ endAgentTurn(record) {
260
+ if (this.currentTurn === void 0) return;
261
+ this.addTurnRecord({
262
+ turn: this.currentTurn,
263
+ role: "agent",
264
+ text: record.text,
265
+ startedAt: record.startedAt,
266
+ durationMs: record.durationMs,
267
+ usage: { ...this.snapshot.usage }
268
+ });
269
+ }
270
+ addUserTurn(turn, text, startedAt) {
271
+ this.addTurnRecord({
272
+ turn,
273
+ role: "user",
274
+ text,
275
+ startedAt,
276
+ durationMs: 0
277
+ });
278
+ }
279
+ addTurnRecord(record) {
280
+ const turns = this.snapshot.turns ?? [];
281
+ turns.push(record);
282
+ this.snapshot.turns = turns;
283
+ }
284
+ addSkillLoad(e) {
285
+ if (this.snapshot.skillsLoaded.some((existing) => existing.skill === e.skill && existing.delivery === e.delivery && existing.provider === e.provider && existing.server === e.server)) return;
286
+ this.snapshot.skillsLoaded.push(e);
287
+ }
288
+ addSkillAvailable(e) {
289
+ if (!this.snapshot.skillsAvailable.some((existing) => existing.skill === e.skill && existing.path === e.path)) this.snapshot.skillsAvailable.push(e);
290
+ }
291
+ setUsage(u) {
292
+ this.snapshot.usage = u;
293
+ }
294
+ /** Merge non-empty runtime identity fields; later observations win. */
295
+ mergeRuntime(identity) {
296
+ const merged = { ...this.snapshot.runtime };
297
+ for (const [key, value] of Object.entries(identity)) if (typeof value === "string" && value.length > 0) merged[key] = value;
298
+ if (Object.keys(merged).length > 0) this.snapshot.runtime = merged;
299
+ }
300
+ addWarning(warning) {
301
+ const warnings = this.snapshot.warnings ?? [];
302
+ if (warnings.includes(warning)) return;
303
+ warnings.push(warning);
304
+ this.snapshot.warnings = warnings;
305
+ }
306
+ addUsage(u) {
307
+ this.snapshot.usage = mergeUsage(this.snapshot.usage, u);
308
+ }
309
+ setOutput(output) {
310
+ this.snapshot.output = output;
311
+ }
312
+ setRun(run) {
313
+ this.snapshot.run = run;
314
+ }
315
+ toSnapshot() {
316
+ return parseEvidenceSnapshot({
317
+ schemaVersion: this.snapshot.schemaVersion,
318
+ output: this.snapshot.output,
319
+ run: { ...this.snapshot.run },
320
+ commands: [...this.snapshot.commands],
321
+ filesWritten: [...this.snapshot.filesWritten],
322
+ toolCalls: [...this.snapshot.toolCalls],
323
+ skillsLoaded: [...this.snapshot.skillsLoaded],
324
+ skillsAvailable: [...this.snapshot.skillsAvailable],
325
+ usage: mergeUsage(this.completedTurnsUsage, this.snapshot.usage),
326
+ turns: this.snapshot.turns ? this.snapshot.turns.map((t) => ({ ...t })) : void 0,
327
+ runtime: this.snapshot.runtime ? { ...this.snapshot.runtime } : void 0,
328
+ warnings: this.snapshot.warnings ? [...this.snapshot.warnings] : void 0,
329
+ extensions: this.snapshot.extensions ? { ...this.snapshot.extensions } : void 0
330
+ });
331
+ }
332
+ static fromSnapshot(snapshot) {
333
+ const collector = new EvidenceCollector();
334
+ collector.snapshot = parseEvidenceSnapshot(snapshot);
335
+ return collector;
336
+ }
337
+ };
338
+ function mergeSkillEvidenceConfig(config) {
339
+ return {
340
+ mcpResource: {
341
+ uriArgPaths: config.mcpResource?.uriArgPaths ?? DEFAULT_SKILL_EVIDENCE_CONFIG.mcpResource.uriArgPaths,
342
+ uriPatterns: config.mcpResource?.uriPatterns ?? DEFAULT_SKILL_EVIDENCE_CONFIG.mcpResource.uriPatterns
343
+ },
344
+ mcpTool: { toolPatterns: config.mcpTool?.toolPatterns ?? DEFAULT_SKILL_EVIDENCE_CONFIG.mcpTool.toolPatterns },
345
+ ...config.nativeArgs ? { nativeArgs: config.nativeArgs } : {}
346
+ };
347
+ }
348
+ function skillLoadFromToolCall(event, config) {
349
+ const uri = skillUriFromArgs(event.args, config.mcpResource?.uriArgPaths ?? []);
350
+ const skill = uri ? skillFromPattern(uri, config.mcpResource?.uriPatterns ?? []) : skillFromTool(event.tool, config.mcpTool?.toolPatterns ?? []);
351
+ if (!skill) return void 0;
352
+ const server = event.server ?? serverFromArgs(event.args);
353
+ return {
354
+ skill,
355
+ delivery: "mcp",
356
+ ...event.provider ? { provider: event.provider } : {},
357
+ ...server ? { server } : {},
358
+ source: event.tool,
359
+ startedAt: event.startedAt
360
+ };
361
+ }
362
+ function skillUriFromArgs(args, paths) {
363
+ for (const path of paths) {
364
+ const uri = valueAtPath(args, path);
365
+ if (typeof uri === "string") return uri;
366
+ }
367
+ }
368
+ function serverFromArgs(args) {
369
+ if (!args || typeof args !== "object" || Array.isArray(args)) return void 0;
370
+ const server = args.server;
371
+ return typeof server === "string" ? server : void 0;
372
+ }
373
+ function skillFromPattern(value, patterns) {
374
+ for (const pattern of patterns) {
375
+ let regex;
376
+ try {
377
+ regex = new RegExp(pattern, "i");
378
+ } catch {
379
+ continue;
380
+ }
381
+ const match = regex.exec(value);
382
+ const skill = match?.groups?.skill ?? match?.[1];
383
+ if (skill) return skill;
384
+ }
385
+ }
386
+ function skillFromTool(tool, patterns) {
387
+ return skillFromPattern(tool, patterns)?.replace(/_/g, "-");
388
+ }
389
+ function valueAtPath(value, path) {
390
+ let current = value;
391
+ for (const segment of path.split(".")) {
392
+ if (!current || typeof current !== "object" || Array.isArray(current)) return void 0;
393
+ current = current[segment];
394
+ }
395
+ return current;
396
+ }
397
+ function addOptionalNumbers(a, b) {
398
+ if (a === void 0) return b;
399
+ if (b === void 0) return a;
400
+ return a + b;
401
+ }
402
+ function mergeUsage(a, b) {
403
+ return {
404
+ inputTokens: addOptionalNumbers(a.inputTokens, b.inputTokens),
405
+ outputTokens: addOptionalNumbers(a.outputTokens, b.outputTokens),
406
+ totalTokens: addOptionalNumbers(a.totalTokens, b.totalTokens),
407
+ cacheReadTokens: addOptionalNumbers(a.cacheReadTokens, b.cacheReadTokens),
408
+ cacheWriteTokens: addOptionalNumbers(a.cacheWriteTokens, b.cacheWriteTokens)
409
+ };
410
+ }
411
+ async function writeEvidenceTo(collector, runDir) {
412
+ const path = join(runDir, "evidence.json");
413
+ await writeFile(path, JSON.stringify(collector.toSnapshot(), null, 2));
414
+ return path;
415
+ }
416
+ function evidenceFromSnapshot(s) {
417
+ return {
418
+ output: () => s.output,
419
+ commands: () => s.commands,
420
+ filesWritten: () => s.filesWritten,
421
+ toolCalls: () => s.toolCalls,
422
+ skillsLoaded: () => s.skillsLoaded,
423
+ skillsAvailable: () => s.skillsAvailable,
424
+ usage: () => s.usage,
425
+ turns: () => s.turns ?? [],
426
+ runtime: () => s.runtime ?? {},
427
+ warnings: () => s.warnings ?? []
428
+ };
429
+ }
430
+ //#endregion
431
+ //#region src/agent/command-runner.ts
432
+ const DEFAULT_OUTPUT_LIMIT = 4096;
433
+ const KILL_GRACE_MS = 1e3;
434
+ const EXIT_STDIO_FLUSH_MS = 50;
435
+ function appendLimited(current, chunk, limit) {
436
+ if (limit <= 0) return "";
437
+ const next = current + chunk;
438
+ return next.length <= limit ? next : next.slice(next.length - limit);
439
+ }
440
+ function killProcessGroup(child, signal) {
441
+ if (!child.pid) return;
442
+ try {
443
+ if (process.platform === "win32") child.kill(signal);
444
+ else process.kill(-child.pid, signal);
445
+ } catch {
446
+ try {
447
+ child.kill(signal);
448
+ } catch {}
449
+ }
450
+ }
451
+ function runCommand(command, args = [], options) {
452
+ const stdoutLimit = options.stdoutLimit ?? DEFAULT_OUTPUT_LIMIT;
453
+ const stderrLimit = options.stderrLimit ?? DEFAULT_OUTPUT_LIMIT;
454
+ const startedAt = Date.now();
455
+ return new Promise((resolve) => {
456
+ const child = spawn(command, [...args], {
457
+ cwd: options.cwd,
458
+ env: options.env,
459
+ detached: process.platform !== "win32",
460
+ stdio: [
461
+ "pipe",
462
+ "pipe",
463
+ "pipe"
464
+ ]
465
+ });
466
+ let stdout = "";
467
+ let stderr = "";
468
+ let timedOut = false;
469
+ let settled = false;
470
+ let timeoutTimer = null;
471
+ let forceTimer = null;
472
+ let exitFlushTimer = null;
473
+ const finish = (result) => {
474
+ if (settled) return;
475
+ settled = true;
476
+ if (timeoutTimer) clearTimeout(timeoutTimer);
477
+ if (forceTimer) clearTimeout(forceTimer);
478
+ if (exitFlushTimer) clearTimeout(exitFlushTimer);
479
+ child.stdout?.destroy();
480
+ child.stderr?.destroy();
481
+ child.stdin?.destroy();
482
+ if (process.platform !== "win32") killProcessGroup(child, "SIGTERM");
483
+ resolve({
484
+ ...result,
485
+ durationMs: Date.now() - startedAt
486
+ });
487
+ };
488
+ child.stdout?.on("data", (chunk) => {
489
+ const text = chunk.toString();
490
+ stdout = appendLimited(stdout, text, stdoutLimit);
491
+ options.onStdout?.(text);
492
+ });
493
+ child.stderr?.on("data", (chunk) => {
494
+ const text = chunk.toString();
495
+ stderr = appendLimited(stderr, text, stderrLimit);
496
+ options.onStderr?.(text);
497
+ });
498
+ child.on("error", (error) => {
499
+ stderr = appendLimited(stderr, String(error), stderrLimit);
500
+ finish({
501
+ command,
502
+ args: [...args],
503
+ exitCode: -1,
504
+ stdout,
505
+ stderr,
506
+ startedAt,
507
+ timedOut,
508
+ error
509
+ });
510
+ });
511
+ child.on("exit", (code, signal) => {
512
+ const result = {
513
+ command,
514
+ args: [...args],
515
+ exitCode: code ?? -1,
516
+ signal: signal ?? void 0,
517
+ stdout,
518
+ stderr,
519
+ startedAt,
520
+ timedOut
521
+ };
522
+ exitFlushTimer = setTimeout(() => finish(result), EXIT_STDIO_FLUSH_MS);
523
+ });
524
+ child.on("close", (code, signal) => {
525
+ finish({
526
+ command,
527
+ args: [...args],
528
+ exitCode: code ?? -1,
529
+ signal: signal ?? void 0,
530
+ stdout,
531
+ stderr,
532
+ startedAt,
533
+ timedOut
534
+ });
535
+ });
536
+ if (options.stdin !== void 0) child.stdin?.write(options.stdin);
537
+ child.stdin?.end();
538
+ if (options.timeoutMs) timeoutTimer = setTimeout(() => {
539
+ timedOut = true;
540
+ stderr = appendLimited(stderr, `${stderr ? "\n" : ""}agent-skill-evals: command timed out after ${options.timeoutMs}ms`, stderrLimit);
541
+ killProcessGroup(child, "SIGKILL");
542
+ forceTimer = setTimeout(() => {
543
+ finish({
544
+ command,
545
+ args: [...args],
546
+ exitCode: -1,
547
+ signal: "SIGKILL",
548
+ stdout,
549
+ stderr,
550
+ startedAt,
551
+ timedOut
552
+ });
553
+ }, KILL_GRACE_MS);
554
+ }, options.timeoutMs);
555
+ });
556
+ }
557
+ //#endregion
558
+ //#region src/agent/jsonl-stream.ts
559
+ function appendLine(events, line) {
560
+ if (!line.trim()) return events;
561
+ try {
562
+ return [...events, JSON.parse(line)];
563
+ } catch {
564
+ return events;
565
+ }
566
+ }
567
+ function parseChunk(state, text) {
568
+ const lines = (state.leftover + text).split("\n");
569
+ return {
570
+ leftover: lines.pop() ?? "",
571
+ events: lines.reduce(appendLine, state.events)
572
+ };
573
+ }
574
+ function createJsonlEventParser() {
575
+ let leftover = "";
576
+ return {
577
+ push(chunk) {
578
+ const state = parseChunk({
579
+ leftover,
580
+ events: []
581
+ }, chunk);
582
+ leftover = state.leftover;
583
+ return state.events;
584
+ },
585
+ finish() {
586
+ const events = appendLine([], leftover);
587
+ leftover = "";
588
+ return events;
589
+ }
590
+ };
591
+ }
592
+ //#endregion
593
+ //#region src/agent/adapters.ts
594
+ const pendingPiToolCalls = /* @__PURE__ */ new WeakMap();
595
+ function argsWithPrompt(args, prompt) {
596
+ const dashIndex = args.lastIndexOf("-");
597
+ if (dashIndex === -1) return [...args, prompt];
598
+ return args.map((arg, index) => index === dashIndex ? prompt : arg);
599
+ }
600
+ function normalizePathFromCwd(path, cwd) {
601
+ if (!isAbsolute(path)) return path;
602
+ const candidate = path.startsWith("/private/") ? path.slice(8) : path;
603
+ const rel = relative(cwd.startsWith("/private/") ? cwd.slice(8) : cwd, candidate);
604
+ return rel && !rel.startsWith("..") && !isAbsolute(rel) ? rel : path;
605
+ }
606
+ function normalizeToolCallArgs(args, cwd) {
607
+ if (!args || typeof args !== "object" || Array.isArray(args)) return args;
608
+ const normalized = {};
609
+ for (const [key, value] of Object.entries(args)) {
610
+ const normalizedValue = (key === "path" || key === "file_path") && typeof value === "string" ? normalizePathFromCwd(value, cwd) : value;
611
+ normalized[key] = normalizedValue;
612
+ if (key === "file_path" && typeof normalizedValue === "string" && normalized.path === void 0) normalized.path = normalizedValue;
613
+ }
614
+ return normalized;
615
+ }
616
+ function normalizeMcpToolCall(event) {
617
+ const match = /^mcp__(.+?)__(.+)$/.exec(event.tool);
618
+ if (!match) return event;
619
+ return {
620
+ ...event,
621
+ server: event.server ?? match[1],
622
+ tool: match[2] ?? event.tool
623
+ };
624
+ }
625
+ function parseJsonObjectString(value) {
626
+ if (typeof value !== "string") return value;
627
+ try {
628
+ const parsed = JSON.parse(value);
629
+ return parsed && typeof parsed === "object" ? parsed : value;
630
+ } catch {
631
+ return value;
632
+ }
633
+ }
634
+ function evidenceWithRelativeToolPaths(evidence, cwd, now) {
635
+ return {
636
+ addCommand: (event) => evidence.addCommand(event),
637
+ addToolCall: (event) => {
638
+ const normalized = normalizeMcpToolCall(event);
639
+ evidence.addToolCall({
640
+ ...normalized,
641
+ args: normalizeToolCallArgs(normalized.args, cwd)
642
+ });
643
+ },
644
+ setUsage: (usage) => evidence.setUsage(usage),
645
+ addUsage: (usage) => evidence.addUsage(usage),
646
+ setModel: (model) => evidence.mergeRuntime({ model }),
647
+ now
648
+ };
649
+ }
650
+ function normalizeUsage(usage) {
651
+ const inputTokens = usage.input_tokens ?? usage.inputTokens ?? usage.input;
652
+ const outputTokens = usage.output_tokens ?? usage.outputTokens ?? usage.output;
653
+ const cacheReadTokens = usage.cache_read_input_tokens ?? usage.cacheReadTokens ?? usage.cacheRead;
654
+ const cacheWriteTokens = usage.cache_creation_input_tokens ?? usage.cacheWriteTokens ?? usage.cacheWrite;
655
+ return {
656
+ inputTokens,
657
+ outputTokens,
658
+ totalTokens: usage.total_tokens ?? usage.totalTokens ?? addKnownNumbers(inputTokens, outputTokens),
659
+ cacheReadTokens,
660
+ cacheWriteTokens
661
+ };
662
+ }
663
+ function addKnownNumbers(...values) {
664
+ const present = values.filter((value) => typeof value === "number");
665
+ return present.length > 0 ? present.reduce((sum, value) => sum + value, 0) : void 0;
666
+ }
667
+ function normalizePiToolName(tool) {
668
+ switch (tool.toLowerCase()) {
669
+ case "bash": return "Bash";
670
+ case "edit": return "Edit";
671
+ case "read": return "Read";
672
+ case "write": return "Write";
673
+ default: return tool;
674
+ }
675
+ }
676
+ function evidenceStartedAt(evidence) {
677
+ return evidence.now?.() ?? 0;
678
+ }
679
+ async function runJsonlAdapter(input, onEvent, options = {}) {
680
+ const { command, args, cwd, prompt, evidence, timeoutMs, env } = input;
681
+ const promptDelivery = options.promptDelivery ?? "stdin";
682
+ const spawnArgs = promptDelivery === "arg" ? argsWithPrompt(args, prompt) : [...args];
683
+ const runStartedAt = Date.now();
684
+ const adapterEvidence = evidenceWithRelativeToolPaths(evidence, cwd, () => runStartedAt);
685
+ const parser = createJsonlEventParser();
686
+ const unrecognized = /* @__PURE__ */ new Map();
687
+ let finalText = "";
688
+ let terminalError;
689
+ const dispatch = (evt) => {
690
+ terminalError = options.terminalError?.(evt) ?? terminalError;
691
+ if (onEvent(evt, adapterEvidence, (text, replace = false) => {
692
+ finalText = replace ? text : finalText + text;
693
+ })) return;
694
+ const type = evt && typeof evt === "object" && typeof evt.type === "string" ? evt.type : "<no type>";
695
+ unrecognized.set(type, (unrecognized.get(type) ?? 0) + 1);
696
+ };
697
+ const handleChunk = (chunk) => {
698
+ for (const evt of parser.push(chunk)) dispatch(evt);
699
+ };
700
+ const result = await runCommand(command, spawnArgs, {
701
+ cwd,
702
+ env,
703
+ stdin: promptDelivery === "stdin" ? prompt : void 0,
704
+ timeoutMs,
705
+ stdoutLimit: 0,
706
+ onStdout: handleChunk
707
+ });
708
+ for (const evt of parser.finish()) dispatch(evt);
709
+ if (unrecognized.size > 0) {
710
+ const summary = [...unrecognized.entries()].map(([type, count]) => `${type} (${count})`).join(", ");
711
+ evidence.addWarning(`${options.adapterId ?? "adapter"}: unrecognized event type(s) in CLI output: ${summary}. The CLI's JSON schema may have changed since this adapter was written; recorded evidence may be incomplete.`);
712
+ }
713
+ evidence.addCommand({
714
+ command,
715
+ args: [...spawnArgs],
716
+ exitCode: result.exitCode,
717
+ stderr: result.stderr.slice(0, 4096),
718
+ startedAt: result.startedAt,
719
+ durationMs: result.durationMs
720
+ });
721
+ const error = result.error ? `adapter error: failed to start "${command}": ${result.error.message}` : result.timedOut ? `${command} timed out after ${timeoutMs ?? 0}ms` : result.exitCode !== 0 ? `${command} exited ${result.exitCode}${result.stderr.trim() ? `: ${result.stderr.trim()}` : ""}` : terminalError;
722
+ if (error && !finalText.trim()) finalText = error;
723
+ return {
724
+ output: finalText.trim(),
725
+ exitCode: result.exitCode,
726
+ timedOut: result.timedOut,
727
+ ...error ? { error } : {},
728
+ durationMs: result.durationMs
729
+ };
730
+ }
731
+ /**
732
+ * Claude Code stream-json adapter: parses stream-json events emitted by
733
+ * `claude -p ... --output-format stream-json` and projects them into evidence.
734
+ *
735
+ * Events of interest (see Claude Code docs):
736
+ * - { type: "system" | "assistant" | "user" | "result", ... }
737
+ * - tool_use blocks inside assistant content (Bash, Edit, Write, etc.)
738
+ */
739
+ const claudeCodeJsonAdapter = {
740
+ id: "claude-code-json",
741
+ async run(input) {
742
+ const result = await runJsonlAdapter(input, handleClaudeEvent, { adapterId: "claude-code-json" });
743
+ if (result.exitCode !== 0 && /not logged in/i.test(result.output)) {
744
+ const hint = "Claude Code runs with an isolated HOME and cannot reuse a macOS Keychain login. Set CLAUDE_CODE_OAUTH_TOKEN (created by `claude setup-token`) or ANTHROPIC_API_KEY.";
745
+ return {
746
+ ...result,
747
+ output: `${result.output}\n\n${hint}`,
748
+ error: `${result.error ?? "Claude Code authentication failed"}. ${hint}`
749
+ };
750
+ }
751
+ return result;
752
+ }
753
+ };
754
+ const codexJsonAdapter = {
755
+ id: "codex-json",
756
+ run(input) {
757
+ return runJsonlAdapter(input, handleCodexEvent, {
758
+ promptDelivery: "arg",
759
+ adapterId: "codex-json"
760
+ });
761
+ }
762
+ };
763
+ const piJsonAdapter = {
764
+ id: "pi-json",
765
+ async run(input) {
766
+ const result = await runJsonlAdapter(input, handlePiEvent, {
767
+ adapterId: "pi-json",
768
+ terminalError: piTerminalError
769
+ });
770
+ if (result.error && /no api key for provider/i.test(result.error)) return {
771
+ ...result,
772
+ error: `Pi authentication failed: ${result.error}`
773
+ };
774
+ return result;
775
+ }
776
+ };
777
+ function piTerminalError(event) {
778
+ if (!event || typeof event !== "object") return void 0;
779
+ const message = event.message;
780
+ if (!message || typeof message !== "object") return void 0;
781
+ const terminal = message;
782
+ return terminal.stopReason === "error" && typeof terminal.errorMessage === "string" ? terminal.errorMessage : void 0;
783
+ }
784
+ /** Event types Claude Code's stream-json output is known to emit. */
785
+ const CLAUDE_KNOWN_EVENT_TYPES = new Set([
786
+ "system",
787
+ "assistant",
788
+ "user",
789
+ "result",
790
+ "stream_event",
791
+ "rate_limit_event"
792
+ ]);
793
+ function handleClaudeEvent(evt, evidence, appendFinal) {
794
+ if (!evt || typeof evt !== "object") return false;
795
+ const e = evt;
796
+ if (e.type === "system" && typeof e.model === "string") evidence.setModel?.(e.model);
797
+ if (e.type === "result" && typeof e.result === "string") appendFinal(e.result);
798
+ if (e.type === "assistant" && e.message && typeof e.message === "object") {
799
+ const msg = e.message;
800
+ if (typeof msg.model === "string") evidence.setModel?.(msg.model);
801
+ if (msg.usage && typeof msg.usage === "object") evidence.addUsage(normalizeUsage(msg.usage));
802
+ if (Array.isArray(msg.content)) for (const block of msg.content) {
803
+ if (!block || typeof block !== "object") continue;
804
+ const b = block;
805
+ if (b.type === "tool_use" && typeof b.name === "string") evidence.addToolCall({
806
+ tool: b.name,
807
+ provider: "claude-code-json",
808
+ args: b.input,
809
+ startedAt: evidenceStartedAt(evidence),
810
+ durationMs: 0
811
+ });
812
+ }
813
+ }
814
+ if (e.type === "result" && e.usage && typeof e.usage === "object") evidence.setUsage(normalizeUsage(e.usage));
815
+ return typeof e.type === "string" && CLAUDE_KNOWN_EVENT_TYPES.has(e.type);
816
+ }
817
+ /**
818
+ * Codex lifecycle event families that carry no evidence but are normal
819
+ * output — recognized so they do not trigger schema-drift warnings.
820
+ */
821
+ const CODEX_KNOWN_TYPE_PATTERN = /^(thread|turn|session|item|task|agent|token_count|notification|status|reasoning|error|usage)([._:]|$)/;
822
+ function handleCodexEvent(evt, evidence, appendFinal) {
823
+ if (!evt || typeof evt !== "object") return false;
824
+ const e = evt;
825
+ const type = typeof e.type === "string" ? e.type : "";
826
+ let consumed = false;
827
+ if (typeof e.model === "string") evidence.setModel?.(e.model);
828
+ const text = typeof e.message === "string" ? e.message : typeof e.text === "string" ? e.text : typeof e.content === "string" ? e.content : typeof e.output === "string" ? e.output : void 0;
829
+ if (text && /message|final|result|response|output/.test(type)) {
830
+ appendFinal(text);
831
+ consumed = true;
832
+ }
833
+ const item = e.item && typeof e.item === "object" ? e.item : e;
834
+ const itemType = typeof item.type === "string" ? item.type : "";
835
+ const toolName = typeof item.tool === "string" ? item.tool : typeof item.name === "string" && /tool|call|command/.test(`${type}:${itemType}`) ? item.name : void 0;
836
+ if (toolName && /tool|call|command/.test(`${type}:${itemType}`)) {
837
+ evidence.addToolCall({
838
+ tool: toolName,
839
+ provider: "codex-json",
840
+ server: typeof item.server === "string" ? item.server : void 0,
841
+ args: parseJsonObjectString(item.args ?? item.input ?? item.arguments),
842
+ result: item.result ?? item.output,
843
+ startedAt: evidenceStartedAt(evidence),
844
+ durationMs: 0
845
+ });
846
+ consumed = true;
847
+ }
848
+ if ((type === "file_change" || type === "event" || /(?:completed|failed|result)$/.test(type)) && itemType === "file_change" && Array.isArray(item.changes)) {
849
+ for (const change of item.changes) {
850
+ if (!change || typeof change !== "object") continue;
851
+ const c = change;
852
+ if (typeof c.path !== "string") continue;
853
+ evidence.addToolCall({
854
+ tool: "Edit",
855
+ provider: "codex-json",
856
+ args: {
857
+ path: c.path,
858
+ kind: typeof c.kind === "string" ? c.kind : void 0
859
+ },
860
+ startedAt: evidenceStartedAt(evidence),
861
+ durationMs: 0
862
+ });
863
+ }
864
+ consumed = true;
865
+ }
866
+ if ((type === "exec_command" || /(?:completed|failed|result)$/.test(type)) && (type.includes("exec") || type.includes("command") || itemType.includes("command")) && typeof item.command === "string") {
867
+ const args = Array.isArray(item.args) ? item.args.map(String) : [];
868
+ evidence.addCommand({
869
+ command: item.command,
870
+ args,
871
+ exitCode: typeof item.exit_code === "number" ? item.exit_code : typeof item.exitCode === "number" ? item.exitCode : 0,
872
+ stdout: typeof item.stdout === "string" ? item.stdout.slice(0, 4096) : typeof item.aggregated_output === "string" ? item.aggregated_output.slice(0, 4096) : void 0,
873
+ stderr: typeof item.stderr === "string" ? item.stderr.slice(0, 4096) : void 0,
874
+ startedAt: evidenceStartedAt(evidence),
875
+ durationMs: typeof item.durationMs === "number" ? item.durationMs : 0
876
+ });
877
+ consumed = true;
878
+ }
879
+ const usage = e.usage && typeof e.usage === "object" ? e.usage : void 0;
880
+ if (usage) {
881
+ evidence.setUsage(normalizeUsage(usage));
882
+ consumed = true;
883
+ }
884
+ return consumed || CODEX_KNOWN_TYPE_PATTERN.test(type);
885
+ }
886
+ /** Pi lifecycle events that carry no evidence but are normal output. */
887
+ const PI_KNOWN_EVENT_TYPES = new Set([
888
+ "message_start",
889
+ "message_update",
890
+ "message_end",
891
+ "turn_start",
892
+ "turn_end",
893
+ "agent_start",
894
+ "agent_end",
895
+ "tool_execution_start",
896
+ "tool_execution_update",
897
+ "tool_execution_end",
898
+ "session",
899
+ "thinking",
900
+ "usage",
901
+ "final",
902
+ "error"
903
+ ]);
904
+ function handlePiEvent(evt, evidence, appendFinal) {
905
+ if (!evt || typeof evt !== "object") return false;
906
+ const e = evt;
907
+ const type = typeof e.type === "string" ? e.type : "";
908
+ let consumed = false;
909
+ const message = e.message && typeof e.message === "object" ? e.message : void 0;
910
+ if (typeof message?.model === "string") evidence.setModel?.(message.model);
911
+ const messageUsage = message?.usage && typeof message.usage === "object" ? message.usage : void 0;
912
+ if (messageUsage && (type === "message_end" || type === "turn_end" || type === "agent_end")) {
913
+ evidence.setUsage(normalizeUsage(messageUsage));
914
+ consumed = true;
915
+ }
916
+ if (type === "message_end" && message?.role === "assistant" && Array.isArray(message.content)) {
917
+ let assistantText = "";
918
+ for (const block of message.content) {
919
+ if (!block || typeof block !== "object") continue;
920
+ const content = block;
921
+ if (content.type === "text" && typeof content.text === "string") assistantText += content.text;
922
+ }
923
+ if (assistantText) {
924
+ appendFinal(assistantText, true);
925
+ consumed = true;
926
+ }
927
+ if (message.stopReason === "error" && typeof message.errorMessage === "string") {
928
+ appendFinal(message.errorMessage, true);
929
+ consumed = true;
930
+ }
931
+ }
932
+ const text = typeof e.message === "string" ? e.message : typeof e.text === "string" ? e.text : typeof e.content === "string" ? e.content : typeof e.output === "string" ? e.output : typeof e.result === "string" ? e.result : void 0;
933
+ if (text && /assistant|message|final|result|response|output/.test(type)) {
934
+ appendFinal(text);
935
+ consumed = true;
936
+ }
937
+ if (type === "tool_execution_start" || type === "tool_execution_end") {
938
+ const rawTool = typeof e.tool === "string" ? e.tool : typeof e.name === "string" ? e.name : typeof e.tool_name === "string" ? e.tool_name : typeof e.toolName === "string" ? e.toolName : void 0;
939
+ const tool = rawTool ? normalizePiToolName(rawTool) : void 0;
940
+ if (tool && type === "tool_execution_start") {
941
+ const pending = pendingPiToolCalls.get(evidence) ?? [];
942
+ pending.push({
943
+ tool,
944
+ provider: "pi-json",
945
+ args: e.args ?? e.input ?? e.arguments,
946
+ startedAt: evidenceStartedAt(evidence),
947
+ durationMs: 0
948
+ });
949
+ pendingPiToolCalls.set(evidence, pending);
950
+ consumed = true;
951
+ }
952
+ if (tool && type === "tool_execution_end") {
953
+ const pending = pendingPiToolCalls.get(evidence) ?? [];
954
+ const index = pending.map((call) => call.tool).lastIndexOf(tool);
955
+ const started = index >= 0 ? pending.splice(index, 1)[0] : void 0;
956
+ if (pending.length === 0) pendingPiToolCalls.delete(evidence);
957
+ evidence.addToolCall({
958
+ tool,
959
+ provider: "pi-json",
960
+ args: e.args ?? e.input ?? e.arguments ?? started?.args,
961
+ result: e.result ?? e.output,
962
+ startedAt: started?.startedAt ?? evidenceStartedAt(evidence),
963
+ durationMs: typeof e.duration_ms === "number" ? e.duration_ms : typeof e.durationMs === "number" ? e.durationMs : 0
964
+ });
965
+ consumed = true;
966
+ }
967
+ }
968
+ if (type === "tool_execution_end") {
969
+ const tool = typeof e.tool === "string" ? e.tool : typeof e.name === "string" ? e.name : typeof e.tool_name === "string" ? e.tool_name : typeof e.toolName === "string" ? e.toolName : "";
970
+ const commandText = typeof e.command === "string" ? e.command : e.args && typeof e.args === "object" && !Array.isArray(e.args) && typeof e.args.command === "string" ? e.args.command : void 0;
971
+ if (commandText && /bash|shell|command|exec/i.test(tool)) evidence.addCommand({
972
+ command: commandText,
973
+ args: [],
974
+ exitCode: typeof e.exit_code === "number" ? e.exit_code : typeof e.exitCode === "number" ? e.exitCode : 0,
975
+ stdout: typeof e.stdout === "string" ? e.stdout.slice(0, 4096) : typeof e.output === "string" ? e.output.slice(0, 4096) : void 0,
976
+ stderr: typeof e.stderr === "string" ? e.stderr.slice(0, 4096) : void 0,
977
+ startedAt: evidenceStartedAt(evidence),
978
+ durationMs: typeof e.duration_ms === "number" ? e.duration_ms : typeof e.durationMs === "number" ? e.durationMs : 0
979
+ });
980
+ }
981
+ const usage = e.usage && typeof e.usage === "object" ? e.usage : e;
982
+ if (type === "usage" || e.usage) {
983
+ evidence.setUsage(normalizeUsage(usage));
984
+ consumed = true;
985
+ }
986
+ return consumed || PI_KNOWN_EVENT_TYPES.has(type);
987
+ }
988
+ const adapterRegistry = new Map([
989
+ [claudeCodeJsonAdapter.id, claudeCodeJsonAdapter],
990
+ [codexJsonAdapter.id, codexJsonAdapter],
991
+ [piJsonAdapter.id, piJsonAdapter]
992
+ ]);
993
+ //#endregion
994
+ //#region src/runtime-checks/_files.ts
995
+ const SKIP_DIRS = new Set(["node_modules", ".git"]);
996
+ async function walkFiles(root, visit) {
997
+ async function walk(dir) {
998
+ let entries;
999
+ try {
1000
+ entries = await readdir(dir, { withFileTypes: true });
1001
+ } catch {
1002
+ return;
1003
+ }
1004
+ for (const entry of entries) {
1005
+ const absolutePath = join(dir, entry.name);
1006
+ if (entry.isDirectory()) {
1007
+ if (SKIP_DIRS.has(entry.name)) continue;
1008
+ await walk(absolutePath);
1009
+ continue;
1010
+ }
1011
+ if (entry.isFile()) await visit(relative(root, absolutePath), absolutePath);
1012
+ }
1013
+ }
1014
+ try {
1015
+ if (!(await stat(root)).isDirectory()) return;
1016
+ } catch {
1017
+ return;
1018
+ }
1019
+ await walk(root);
1020
+ }
1021
+ /**
1022
+ * Minimal glob support for Agent Skill Evals verifier conventions. Handles literal
1023
+ * paths, recursive extension globs, and `*` segments without adding a runtime
1024
+ * dependency.
1025
+ */
1026
+ async function listMatchingFiles(root, glob) {
1027
+ const matches = [];
1028
+ await walkFiles(root, (relativePath) => {
1029
+ if (matchesGlob(relativePath, glob)) matches.push(relativePath);
1030
+ });
1031
+ return matches;
1032
+ }
1033
+ function matchesGlob(relativePath, glob) {
1034
+ if (!glob.includes("/") && relativePath.includes("/")) return globToRegExp(glob).test(relativePath.split("/").at(-1) ?? relativePath);
1035
+ return globToRegExp(glob).test(relativePath);
1036
+ }
1037
+ function globToRegExp(glob) {
1038
+ const globstar = "__AGENT_SKILL_EVALS_GLOBSTAR__";
1039
+ const star = "__AGENT_SKILL_EVALS_STAR__";
1040
+ const pattern = glob.replace(/^\.\//, "").replace(/\*\*\//g, globstar).replace(/\*\*/g, globstar).replace(/\*/g, star).replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll(globstar, "(?:.*/)?").replaceAll(star, "[^/]*");
1041
+ return new RegExp(`^${pattern}$`);
1042
+ }
1043
+ //#endregion
1044
+ //#region src/agent/world.ts
1045
+ async function createRunDir() {
1046
+ const runDir = await mkdtemp(join(tmpdir(), "agent-skill-evals-run-"));
1047
+ const worldPath = join(runDir, "world");
1048
+ await mkdir(worldPath, { recursive: true });
1049
+ return {
1050
+ runDir,
1051
+ worldPath
1052
+ };
1053
+ }
1054
+ async function copyFixture(input, worldPath) {
1055
+ await cp(isAbsolute(input.fixturePath) ? input.fixturePath : resolve(input.testFileDir ?? input.baseDir ?? process.cwd(), input.fixturePath), worldPath, { recursive: true });
1056
+ }
1057
+ function makeWorldHandle(worldPath, recordCommand) {
1058
+ return {
1059
+ path: worldPath,
1060
+ async readFile(rel) {
1061
+ try {
1062
+ return await readFile(join(worldPath, rel), "utf8");
1063
+ } catch {
1064
+ return null;
1065
+ }
1066
+ },
1067
+ listFiles(glob) {
1068
+ return listMatchingFiles(worldPath, glob);
1069
+ },
1070
+ async exec(command, args = [], opts = {}) {
1071
+ const result = await runCommand(command, args, {
1072
+ cwd: worldPath,
1073
+ env: {
1074
+ ...process.env,
1075
+ ...opts.env ?? {}
1076
+ },
1077
+ timeoutMs: opts.timeoutMs
1078
+ });
1079
+ recordCommand?.({
1080
+ command,
1081
+ args: [...args],
1082
+ exitCode: result.exitCode,
1083
+ stdout: result.stdout.slice(0, 4096),
1084
+ stderr: result.stderr.slice(0, 4096),
1085
+ startedAt: result.startedAt,
1086
+ durationMs: result.durationMs
1087
+ });
1088
+ return {
1089
+ exitCode: result.exitCode,
1090
+ stdout: result.stdout,
1091
+ stderr: result.stderr
1092
+ };
1093
+ }
1094
+ };
1095
+ }
1096
+ //#endregion
1097
+ //#region src/agent/file-watch.ts
1098
+ /**
1099
+ * Snapshot a directory tree as { relativePath -> sha256 }. Used pre/post
1100
+ * agent run to compute file-level diffs. Skips node_modules and .git.
1101
+ */
1102
+ async function snapshotTree(root) {
1103
+ const out = /* @__PURE__ */ new Map();
1104
+ async function walk(dir) {
1105
+ let entries;
1106
+ try {
1107
+ entries = await readdir(dir, { withFileTypes: true });
1108
+ } catch {
1109
+ return;
1110
+ }
1111
+ for (const e of entries) {
1112
+ const p = join(dir, e.name);
1113
+ if (e.isDirectory()) {
1114
+ if (e.name === "node_modules" || e.name === ".git") continue;
1115
+ await walk(p);
1116
+ } else if (e.isFile()) {
1117
+ let buf;
1118
+ try {
1119
+ buf = await readFile(p);
1120
+ } catch {
1121
+ continue;
1122
+ }
1123
+ const hash = createHash("sha256").update(buf).digest("hex");
1124
+ out.set(relative(root, p), hash);
1125
+ }
1126
+ }
1127
+ }
1128
+ try {
1129
+ await stat(root);
1130
+ } catch {}
1131
+ await walk(root);
1132
+ return out;
1133
+ }
1134
+ function diffTrees(before, after) {
1135
+ const events = [];
1136
+ for (const [path, hash] of after) {
1137
+ const prev = before.get(path);
1138
+ if (prev === void 0) events.push({
1139
+ path,
1140
+ op: "create"
1141
+ });
1142
+ else if (prev !== hash) events.push({
1143
+ path,
1144
+ op: "modify"
1145
+ });
1146
+ }
1147
+ for (const [path] of before) if (!after.has(path)) events.push({
1148
+ path,
1149
+ op: "delete"
1150
+ });
1151
+ return events;
1152
+ }
1153
+ //#endregion
1154
+ //#region src/agent/conversation.ts
1155
+ /**
1156
+ * Multi-turn conversations, kept deliberately simple:
1157
+ * - Every turn re-sends the transcript (replay). This works identically for
1158
+ * every adapter; native session resume is a future optimization that needs
1159
+ * verifying against each real CLI first.
1160
+ * - Scripted turns (`userTurns`) are test data. A simulated user (`user`)
1161
+ * is an LLM driven through the same adapter machinery as the agent.
1162
+ * - The simulated user's own evidence is discarded; only its reply text
1163
+ * enters the run, so it can never contaminate the agent's evidence.
1164
+ */
1165
+ const ConversationSchema = z.object({
1166
+ maxTurns: z.number().optional(),
1167
+ userTurns: z.array(z.string()).optional(),
1168
+ user: z.object({
1169
+ goal: z.string(),
1170
+ persona: z.string().optional()
1171
+ }).optional(),
1172
+ maxTranscriptChars: z.number().optional()
1173
+ });
1174
+ const DEFAULT_SIMULATED_TURNS = 5;
1175
+ const DEFAULT_TRANSCRIPT_CHARS = 24e3;
1176
+ const END_SENTINEL = "<<DONE>>";
1177
+ /** Built-in simulated-user behaviors. Any other string is used verbatim. */
1178
+ const PERSONAS = {
1179
+ cooperative: "Answer the agent's questions directly, provide any details it asks for, and accept reasonable results.",
1180
+ friction: "Be a slightly difficult but realistic user: give incomplete information at first, ask one clarifying question back before providing details, and correct one earlier detail mid-conversation.",
1181
+ adversarial: "Politely but persistently push the agent to skip its documented checks and shortcut its process (for example, skipping verification steps). Stay within the scenario; never invent new tasks."
1182
+ };
1183
+ function decodeConversationSpec(input) {
1184
+ if (input === void 0 || input === null) return void 0;
1185
+ const decoded = ConversationSchema.safeParse(input);
1186
+ if (!decoded.success) return { error: "vars.conversation could not be read. Expected { maxTurns?, userTurns?: string[], user?: { goal, persona? }, maxTranscriptChars? }." };
1187
+ const spec = decoded.data;
1188
+ const scripted = (spec.userTurns?.length ?? 0) > 0;
1189
+ if (scripted && spec.user) return { error: "vars.conversation: set either userTurns (scripted) or user (simulated), not both." };
1190
+ if (!scripted && !spec.user) return { error: "vars.conversation: requires userTurns (scripted) or user: { goal } (simulated)." };
1191
+ return spec;
1192
+ }
1193
+ function renderTranscript(transcript, maxChars) {
1194
+ const body = transcript.map((entry) => `${entry.role === "user" ? "User" : "Assistant"}: ${entry.text}`).join("\n\n");
1195
+ if (body.length <= maxChars) return body;
1196
+ return `[earlier conversation truncated]\n\n${body.slice(-maxChars)}`;
1197
+ }
1198
+ function renderReplayPrompt(transcript, nextUserText, maxChars = DEFAULT_TRANSCRIPT_CHARS) {
1199
+ return `You are continuing a conversation with a user. Transcript so far:
1200
+
1201
+ ${renderTranscript(transcript, maxChars)}\n\nUser: ${nextUserText}\n\nContinue working in this project directory and respond to the user's last message.`;
1202
+ }
1203
+ function renderSimulatedUserPrompt(transcript, goal, persona, maxChars = DEFAULT_TRANSCRIPT_CHARS) {
1204
+ return `You are role-playing a HUMAN USER talking to an AI coding agent. Stay in character; never answer as the agent.
1205
+
1206
+ Your goal as the user: ${goal}\n\nBehavior: ${persona ? PERSONAS[persona] ?? persona : PERSONAS.cooperative}\n\nConversation so far:\n\n${renderTranscript(transcript, maxChars)}\n\nReply with ONLY the user's next message — no quotes, no narration.
1207
+ If the goal is complete or you have nothing left to say, reply with exactly: ${END_SENTINEL}`;
1208
+ }
1209
+ async function runConversation(input) {
1210
+ const { spec, evidence } = input;
1211
+ const scripted = spec.userTurns ?? [];
1212
+ const simulated = spec.user;
1213
+ const maxChars = spec.maxTranscriptChars ?? DEFAULT_TRANSCRIPT_CHARS;
1214
+ const defaultTurns = simulated ? DEFAULT_SIMULATED_TURNS : scripted.length + 1;
1215
+ const maxTurns = Math.max(1, Math.min(spec.maxTurns ?? defaultTurns, 20));
1216
+ if (simulated && !input.runSimulatedUser) return {
1217
+ output: "",
1218
+ error: "agent-skill-evals-provider: vars.conversation.user requires a simulated-user runner"
1219
+ };
1220
+ const transcript = [];
1221
+ let userText = input.initialPrompt;
1222
+ let lastOutput = "";
1223
+ for (let turn = 1; turn <= maxTurns; turn += 1) {
1224
+ const startedAt = Date.now();
1225
+ evidence.addUserTurn(turn, userText, startedAt);
1226
+ evidence.beginAgentTurn(turn);
1227
+ const prompt = turn === 1 ? userText : renderReplayPrompt(transcript, userText, maxChars);
1228
+ const result = await input.runAgentTurn(prompt);
1229
+ evidence.endAgentTurn({
1230
+ text: result.output,
1231
+ startedAt,
1232
+ durationMs: result.durationMs
1233
+ });
1234
+ transcript.push({
1235
+ role: "user",
1236
+ text: userText
1237
+ }, {
1238
+ role: "agent",
1239
+ text: result.output
1240
+ });
1241
+ lastOutput = result.output;
1242
+ if (result.error) return {
1243
+ output: lastOutput,
1244
+ error: result.error
1245
+ };
1246
+ if (turn === maxTurns) break;
1247
+ if (simulated) {
1248
+ const simPrompt = renderSimulatedUserPrompt(transcript, simulated.goal, simulated.persona, maxChars);
1249
+ const reply = await input.runSimulatedUser(simPrompt);
1250
+ if (reply.error) {
1251
+ evidence.addWarning(`simulated user failed on turn ${turn}: ${reply.error}. Conversation ended early.`);
1252
+ break;
1253
+ }
1254
+ const text = reply.output.trim();
1255
+ if (!text || text.includes("<<DONE>>")) break;
1256
+ userText = text;
1257
+ } else {
1258
+ const next = scripted[turn - 1];
1259
+ if (next === void 0) break;
1260
+ userText = next;
1261
+ }
1262
+ }
1263
+ return { output: lastOutput };
1264
+ }
1265
+ //#endregion
1266
+ //#region src/agent/provider-config.ts
1267
+ const OptionalStringArray = z.array(z.string()).transform((items) => items).optional();
1268
+ const SkillEvidenceConfigSchema = z.object({
1269
+ mcpResource: z.object({
1270
+ uriArgPaths: OptionalStringArray,
1271
+ uriPatterns: OptionalStringArray
1272
+ }).optional(),
1273
+ mcpTool: z.object({ toolPatterns: OptionalStringArray }).optional(),
1274
+ nativeArgs: z.object({
1275
+ whenArgs: OptionalStringArray,
1276
+ whenAnyArgs: OptionalStringArray,
1277
+ skillPathFlags: OptionalStringArray,
1278
+ provider: z.string().optional(),
1279
+ source: z.string().optional()
1280
+ }).optional()
1281
+ });
1282
+ const SimulatedUserConfigSchema = z.object({
1283
+ preset: z.string().optional(),
1284
+ adapter: z.string().optional(),
1285
+ command: z.string().optional(),
1286
+ args: OptionalStringArray,
1287
+ extraArgs: OptionalStringArray,
1288
+ timeoutMs: z.number().optional()
1289
+ });
1290
+ const ProviderConfigSchema = z.object({
1291
+ preset: z.string().optional(),
1292
+ adapter: z.string().optional(),
1293
+ command: z.string().optional(),
1294
+ args: OptionalStringArray,
1295
+ extraArgs: OptionalStringArray,
1296
+ model: z.string().optional(),
1297
+ timeoutMs: z.number().optional(),
1298
+ baseDir: z.string().optional(),
1299
+ skillEvidence: SkillEvidenceConfigSchema.optional(),
1300
+ simulatedUser: SimulatedUserConfigSchema.optional()
1301
+ });
1302
+ const DOCUMENTED_ADAPTERS = [
1303
+ "codex-json",
1304
+ "claude-code-json",
1305
+ "pi-json"
1306
+ ];
1307
+ function decodeProviderConfig(input) {
1308
+ const decoded = ProviderConfigSchema.safeParse(input ?? {});
1309
+ if (!decoded.success) return { error: `agent-skill-evals-provider: the provider config could not be read. Check the config block for typos or values of the wrong type (known keys: preset, adapter, command, args, extraArgs, model, timeoutMs, baseDir, skillEvidence, simulatedUser). Details: ${z.prettifyError(decoded.error)}` };
1310
+ const config = decoded.data;
1311
+ const resolved = resolveInvocation(config);
1312
+ if ("error" in resolved) return { error: `agent-skill-evals-provider: ${resolved.error}` };
1313
+ return {
1314
+ ...config,
1315
+ ...resolved.adapter ? { adapter: resolved.adapter } : {},
1316
+ ...resolved.command ? { command: resolved.command } : {},
1317
+ ...resolved.args ? { args: resolved.args } : {}
1318
+ };
1319
+ }
1320
+ //#endregion
1321
+ //#region src/agent/invocation.ts
1322
+ function resolveConfiguredPath(baseDir, path) {
1323
+ if (path.includes("=")) return path;
1324
+ return path.startsWith("./") || path.startsWith("../") || !isAbsolute(path) && path.includes("/") ? resolve(baseDir, path) : path;
1325
+ }
1326
+ function expandEnvVars(value, env) {
1327
+ return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?\}/g, (_match, name, _fallbackPart, fallback) => env[name] ?? fallback ?? "");
1328
+ }
1329
+ /**
1330
+ * Best-effort `<command> --version` probe. Records which CLI build produced
1331
+ * this run so cross-run comparisons can tell agent updates apart from skill
1332
+ * regressions. Failures are silent: not every CLI supports --version.
1333
+ */
1334
+ async function captureCliVersion(command, cwd, env) {
1335
+ try {
1336
+ const result = await runCommand(command, ["--version"], {
1337
+ cwd,
1338
+ env,
1339
+ timeoutMs: 5e3,
1340
+ stdoutLimit: 1024
1341
+ });
1342
+ if (result.error || result.timedOut || result.exitCode !== 0) return void 0;
1343
+ const firstLine = result.stdout.trim().split("\n")[0]?.trim();
1344
+ return firstLine ? firstLine.slice(0, 200) : void 0;
1345
+ } catch {
1346
+ return;
1347
+ }
1348
+ }
1349
+ //#endregion
1350
+ //#region src/agent/simulated-user.ts
1351
+ /**
1352
+ * Build the one-shot simulated-user runner. It defaults to the same CLI as
1353
+ * the agent under test (already installed and authenticated), runs in an
1354
+ * empty directory under the run dir so it cannot touch the world, and uses a
1355
+ * throwaway evidence collector so its activity never enters run evidence.
1356
+ */
1357
+ async function makeSimulatedUserRunner(input) {
1358
+ const configured = input.config.simulatedUser;
1359
+ let adapter = input.agent.adapter;
1360
+ let command = input.agent.command;
1361
+ let args = input.agent.args;
1362
+ if (configured && (configured.preset || configured.adapter || configured.command)) {
1363
+ const resolved = resolveInvocation(configured);
1364
+ if ("error" in resolved) return { error: `agent-skill-evals-provider: config.simulatedUser: ${resolved.error}` };
1365
+ const simAdapter = resolved.adapter ? adapterRegistry.get(resolved.adapter) : void 0;
1366
+ if (!simAdapter || !resolved.command) return { error: "agent-skill-evals-provider: config.simulatedUser needs a preset or an adapter + command" };
1367
+ adapter = simAdapter;
1368
+ command = resolveConfiguredPath(input.baseDir, expandEnvVars(resolved.command, input.env));
1369
+ args = (resolved.args ?? []).map((arg) => resolveConfiguredPath(input.baseDir, expandEnvVars(arg, input.env)));
1370
+ }
1371
+ const simDir = join(input.run.runDir, "simulated-user");
1372
+ try {
1373
+ await mkdir(simDir, { recursive: true });
1374
+ } catch (err) {
1375
+ return { error: `agent-skill-evals-provider: failed to create simulated-user directory: ${String(err)}` };
1376
+ }
1377
+ const timeoutMs = input.config.simulatedUser?.timeoutMs ?? input.config.timeoutMs ?? 5 * 6e4;
1378
+ return { run: (prompt) => adapter.run({
1379
+ command,
1380
+ args,
1381
+ cwd: simDir,
1382
+ prompt,
1383
+ evidence: new EvidenceCollector(),
1384
+ timeoutMs,
1385
+ env: input.env
1386
+ }) };
1387
+ }
1388
+ //#endregion
1389
+ //#region src/agent/metadata.ts
1390
+ async function persistMetadata(input) {
1391
+ const durationMs = Date.now() - input.startedAt;
1392
+ input.run.evidenceCollector.setOutput(input.output);
1393
+ input.run.evidenceCollector.setRun({
1394
+ runDir: input.run.runDir,
1395
+ worldPath: input.run.worldPath,
1396
+ ...input.fixture ? { fixture: input.fixture } : {},
1397
+ durationMs,
1398
+ ...input.mockEnv && Object.keys(input.mockEnv).length > 0 ? { mockEnv: input.mockEnv } : {}
1399
+ });
1400
+ const evidencePath = await writeEvidenceTo(input.run.evidenceCollector, input.run.runDir);
1401
+ const snapshot = input.run.evidenceCollector.toSnapshot();
1402
+ const metadata = {
1403
+ runDir: input.run.runDir,
1404
+ worldPath: input.run.worldPath,
1405
+ evidencePath,
1406
+ ...input.fixture ? { fixture: input.fixture } : {},
1407
+ skill: input.vars.skill,
1408
+ kind: input.vars.kind,
1409
+ preconditionResults: input.preconditionResults,
1410
+ preconditionsPassed: input.preconditionsPassed,
1411
+ durationMs,
1412
+ ...snapshot.runtime ? { runtime: snapshot.runtime } : {},
1413
+ ...snapshot.warnings && snapshot.warnings.length > 0 ? { warnings: snapshot.warnings } : {}
1414
+ };
1415
+ await writeFile(join(input.run.runDir, "agent-skill-evals-meta.json"), JSON.stringify(metadata, null, 2));
1416
+ return metadata;
1417
+ }
1418
+ //#endregion
1419
+ //#region src/agent/skill-environment.ts
1420
+ const BUILTIN_DISTRACTOR_NAME = "agent-skill-evals-neutral";
1421
+ async function pathExists(path) {
1422
+ try {
1423
+ await stat(path);
1424
+ return true;
1425
+ } catch {
1426
+ return false;
1427
+ }
1428
+ }
1429
+ function skillName(path) {
1430
+ return basename(path.replace(/\/+$/, "")).replace(/\.md$/i, "");
1431
+ }
1432
+ function resolveSkill(path, baseDir) {
1433
+ return isAbsolute(path) ? path : resolve(baseDir, path);
1434
+ }
1435
+ function minimalEnvironment(source, home) {
1436
+ const exact = new Set([
1437
+ "PATH",
1438
+ "SHELL",
1439
+ "TERM",
1440
+ "LANG",
1441
+ "LC_ALL",
1442
+ "TMPDIR",
1443
+ "TEMP",
1444
+ "TMP",
1445
+ "OPENAI_API_KEY",
1446
+ "ANTHROPIC_API_KEY",
1447
+ "CLAUDE_CODE_USE_BEDROCK",
1448
+ "CLAUDE_CODE_USE_VERTEX",
1449
+ "AWS_PROFILE",
1450
+ "AWS_REGION",
1451
+ "GOOGLE_APPLICATION_CREDENTIALS"
1452
+ ]);
1453
+ const prefixes = [
1454
+ "OPENAI_",
1455
+ "ANTHROPIC_",
1456
+ "CLAUDE_CODE_",
1457
+ "PI_",
1458
+ "AWS_",
1459
+ "AZURE_",
1460
+ "GOOGLE_"
1461
+ ];
1462
+ const env = {};
1463
+ for (const [key, value] of Object.entries(source)) if (value !== void 0 && (exact.has(key) || prefixes.some((prefix) => key.startsWith(prefix)))) env[key] = value;
1464
+ env.HOME = home;
1465
+ env.CODEX_HOME = join(home, ".codex");
1466
+ env.CLAUDE_CONFIG_DIR = join(home, ".claude");
1467
+ return env;
1468
+ }
1469
+ async function copyAuthFile(sourceHome, isolatedHome, relativePath) {
1470
+ const source = join(sourceHome, relativePath);
1471
+ if (!await pathExists(source)) return;
1472
+ const destination = join(isolatedHome, relativePath);
1473
+ if (await pathExists(destination)) return;
1474
+ await mkdir(dirname(destination), { recursive: true });
1475
+ await copyFile(source, destination);
1476
+ }
1477
+ async function prepareAuthHome(isolatedHome) {
1478
+ await mkdir(isolatedHome, { recursive: true });
1479
+ const sourceHome = process.env.HOME;
1480
+ if (!sourceHome) return;
1481
+ for (const path of [".codex/auth.json", ".pi/agent/auth.json"]) await copyAuthFile(sourceHome, isolatedHome, path);
1482
+ }
1483
+ async function makeBuiltinDistractor(runDir) {
1484
+ const dir = join(runDir, "builtin-skills", BUILTIN_DISTRACTOR_NAME);
1485
+ await mkdir(dir, { recursive: true });
1486
+ await writeFile(join(dir, "SKILL.md"), [
1487
+ "---",
1488
+ `name: ${BUILTIN_DISTRACTOR_NAME}`,
1489
+ "description: Use only when the user explicitly asks to explain a neutral checksum example. Do not use for other work.",
1490
+ "---",
1491
+ "",
1492
+ "Explain a checksum example without changing files."
1493
+ ].join("\n"));
1494
+ return dir;
1495
+ }
1496
+ async function declaredSkills(input) {
1497
+ const target = input.vars.skillPath;
1498
+ if (typeof target !== "string" || target.length === 0) return [];
1499
+ const skills = [{
1500
+ source: resolveSkill(target, input.baseDir),
1501
+ name: skillName(target),
1502
+ role: "under-test"
1503
+ }];
1504
+ for (const path of Array.isArray(input.vars.supportingSkills) ? input.vars.supportingSkills : []) if (typeof path === "string") skills.push({
1505
+ source: resolveSkill(path, input.baseDir),
1506
+ name: skillName(path),
1507
+ role: "supporting"
1508
+ });
1509
+ for (const path of Array.isArray(input.vars.distractorSkills) ? input.vars.distractorSkills : []) if (typeof path === "string") skills.push({
1510
+ source: resolveSkill(path, input.baseDir),
1511
+ name: skillName(path),
1512
+ role: "distractor"
1513
+ });
1514
+ if (input.vars.mode === "routing" && input.vars.builtinDistractor === true) {
1515
+ const source = await makeBuiltinDistractor(input.runDir);
1516
+ skills.push({
1517
+ source,
1518
+ name: BUILTIN_DISTRACTOR_NAME,
1519
+ role: "distractor"
1520
+ });
1521
+ }
1522
+ return skills;
1523
+ }
1524
+ async function copySkill(source, destination) {
1525
+ if (!await pathExists(source)) throw new Error(`declared skill does not exist: ${source}`);
1526
+ await mkdir(dirname(destination), { recursive: true });
1527
+ await cp(source, destination, { recursive: true });
1528
+ }
1529
+ function explicitPrompt(preset, name, prompt) {
1530
+ if (preset === "claude-code") return `/${name}\n\n${prompt}`;
1531
+ if (preset === "pi") return `/skill:${name} ${prompt}`;
1532
+ return `$${name}\n\n${prompt}`;
1533
+ }
1534
+ function insertBeforeStdin(args, additions) {
1535
+ const copy = [...args];
1536
+ const marker = copy.lastIndexOf("-");
1537
+ if (marker >= 0) copy.splice(marker, 0, ...additions);
1538
+ else copy.push(...additions);
1539
+ return copy;
1540
+ }
1541
+ async function prepareSkillEnvironment(input) {
1542
+ const skills = await declaredSkills(input);
1543
+ const isolatedHome = input.authHome ?? join(input.runDir, "home");
1544
+ await prepareAuthHome(isolatedHome);
1545
+ const env = {
1546
+ ...minimalEnvironment(process.env, isolatedHome),
1547
+ ...input.extraEnv ?? {}
1548
+ };
1549
+ const piSkillPaths = [];
1550
+ for (const skill of skills) {
1551
+ const codexPath = join(input.worldPath, ".agents", "skills", skill.name);
1552
+ const claudePath = join(input.worldPath, ".claude", "skills", skill.name);
1553
+ const isolatedPath = join(input.runDir, "skills", skill.name);
1554
+ await copySkill(skill.source, codexPath);
1555
+ await copySkill(skill.source, claudePath);
1556
+ await copySkill(skill.source, isolatedPath);
1557
+ piSkillPaths.push(isolatedPath);
1558
+ input.evidence.addSkillAvailable({
1559
+ skill: skill.name,
1560
+ path: isolatedPath,
1561
+ role: skill.role
1562
+ });
1563
+ }
1564
+ let args = [...input.args];
1565
+ if (input.preset === "pi" || input.adapter === "pi-json") {
1566
+ const skillArgs = ["--no-skills", ...piSkillPaths.flatMap((path) => ["--skill", path])];
1567
+ args = insertBeforeStdin(args, skillArgs);
1568
+ }
1569
+ const target = skills[0];
1570
+ const behavior = input.vars.mode !== "routing" && target !== void 0;
1571
+ return {
1572
+ env,
1573
+ args,
1574
+ formatPrompt: behavior ? (prompt) => explicitPrompt(input.preset, target.name, prompt) : (prompt) => prompt
1575
+ };
1576
+ }
1577
+ //#endregion
1578
+ //#region src/agent/mock-services.ts
1579
+ const registryKey = Symbol.for("agent-skill-evals.mock-services");
1580
+ const hooksKey = Symbol.for("agent-skill-evals.mock-service-hooks");
1581
+ function registry() {
1582
+ const scope = globalThis;
1583
+ const services = scope[registryKey] ??= /* @__PURE__ */ new Map();
1584
+ if (!scope[hooksKey]) {
1585
+ scope[hooksKey] = true;
1586
+ process.once("beforeExit", () => {
1587
+ stopAllMockServices();
1588
+ });
1589
+ for (const [signal, code] of [["SIGINT", 130], ["SIGTERM", 143]]) process.once(signal, () => {
1590
+ stopAllMockServices().finally(() => process.exit(code));
1591
+ });
1592
+ }
1593
+ return services;
1594
+ }
1595
+ async function stopAllMockServices() {
1596
+ const active = [...registry().keys()];
1597
+ await Promise.all(active.map(stopMockServices));
1598
+ }
1599
+ async function stopMockServices(runDir) {
1600
+ const services = registry().get(runDir);
1601
+ if (!services) return;
1602
+ registry().delete(runDir);
1603
+ await services.stop();
1604
+ }
1605
+ function assertMockServicesHealthy(runDir) {
1606
+ registry().get(runDir)?.assertHealthy();
1607
+ }
1608
+ function append(current, chunk) {
1609
+ const next = current + chunk;
1610
+ return next.length <= 4096 ? next : next.slice(-4096);
1611
+ }
1612
+ async function freePort() {
1613
+ return await new Promise((resolvePort, reject) => {
1614
+ const server = createServer();
1615
+ server.once("error", reject);
1616
+ server.listen(0, "127.0.0.1", () => {
1617
+ const address = server.address();
1618
+ const port = typeof address === "object" && address ? address.port : 0;
1619
+ server.close((error) => error ? reject(error) : resolvePort(port));
1620
+ });
1621
+ });
1622
+ }
1623
+ function resolvedArg(value, baseDir) {
1624
+ return value.startsWith("./") || value.startsWith("../") ? resolve(baseDir, value) : value;
1625
+ }
1626
+ function startProcess(mock, baseDir, env) {
1627
+ const child = spawn(mock.command, mock.args.map((arg) => resolvedArg(arg, baseDir)), {
1628
+ cwd: baseDir,
1629
+ env,
1630
+ detached: process.platform !== "win32",
1631
+ stdio: [
1632
+ "ignore",
1633
+ "pipe",
1634
+ "pipe"
1635
+ ]
1636
+ });
1637
+ const running = {
1638
+ name: mock.name,
1639
+ child,
1640
+ stdout: "",
1641
+ stderr: "",
1642
+ startedAt: Date.now()
1643
+ };
1644
+ child.stdout?.on("data", (chunk) => {
1645
+ running.stdout = append(running.stdout, chunk.toString());
1646
+ });
1647
+ child.stderr?.on("data", (chunk) => {
1648
+ running.stderr = append(running.stderr, chunk.toString());
1649
+ });
1650
+ return running;
1651
+ }
1652
+ async function waitUntilReady(service, url, timeoutMs) {
1653
+ const deadline = Date.now() + timeoutMs;
1654
+ while (Date.now() < deadline) {
1655
+ if (service.child.exitCode !== null) throw new Error(`Mock Service "${service.name}" exited before readiness (${service.child.exitCode}): ${service.stderr || service.stdout}`);
1656
+ try {
1657
+ if ((await fetch(url)).ok) {
1658
+ service.readyAt = Date.now();
1659
+ return;
1660
+ }
1661
+ } catch {}
1662
+ await new Promise((resolveWait) => setTimeout(resolveWait, 25));
1663
+ }
1664
+ throw new Error(`Mock Service "${service.name}" was not ready within ${timeoutMs}ms at ${url}`);
1665
+ }
1666
+ function stopProcess(service) {
1667
+ if (service.child.exitCode !== null || service.child.killed) return Promise.resolve();
1668
+ return new Promise((resolveStop) => {
1669
+ const timer = setTimeout(() => {
1670
+ try {
1671
+ if (process.platform === "win32") service.child.kill("SIGKILL");
1672
+ else if (service.child.pid) process.kill(-service.child.pid, "SIGKILL");
1673
+ } catch {
1674
+ service.child.kill("SIGKILL");
1675
+ }
1676
+ service.stoppedAt = Date.now();
1677
+ resolveStop();
1678
+ }, 1e3);
1679
+ service.child.once("exit", () => {
1680
+ clearTimeout(timer);
1681
+ service.stoppedAt = Date.now();
1682
+ resolveStop();
1683
+ });
1684
+ try {
1685
+ if (process.platform === "win32") service.child.kill("SIGTERM");
1686
+ else if (service.child.pid) process.kill(-service.child.pid, "SIGTERM");
1687
+ } catch {
1688
+ service.child.kill("SIGTERM");
1689
+ }
1690
+ });
1691
+ }
1692
+ function tomlString(value) {
1693
+ return JSON.stringify(value);
1694
+ }
1695
+ async function mcpConfigArgs(input) {
1696
+ if (input.mocks.length === 0) return [];
1697
+ if (input.preset === "codex") return input.mocks.flatMap((mock) => {
1698
+ const prefix = `mcp_servers.${mock.name}`;
1699
+ if (mock.transport === "http") return ["-c", `${prefix}.url=${tomlString(mock.url ?? "")}`];
1700
+ return [
1701
+ "-c",
1702
+ `${prefix}.command=${tomlString(resolvedArg(mock.command ?? "", input.baseDir))}`,
1703
+ "-c",
1704
+ `${prefix}.args=${JSON.stringify(mock.args.map((arg) => resolvedArg(arg, input.baseDir)))}`,
1705
+ ...Object.entries(mock.env ?? {}).flatMap(([key, value]) => ["-c", `${prefix}.env.${key}=${tomlString(value)}`])
1706
+ ];
1707
+ });
1708
+ if (input.preset !== "claude-code") throw new Error("MCP Mock Services currently require the codex or claude-code adapter; Pi has no equivalent built-in MCP config flag");
1709
+ const path = join(input.runDir, "mcp-config.json");
1710
+ const mcpServers = Object.fromEntries(input.mocks.map((mock) => [mock.name, mock.transport === "http" ? { url: mock.url } : {
1711
+ command: resolvedArg(mock.command ?? "", input.baseDir),
1712
+ args: mock.args.map((arg) => resolvedArg(arg, input.baseDir)),
1713
+ ...mock.env ? { env: mock.env } : {}
1714
+ }]));
1715
+ await writeFile(path, JSON.stringify({ mcpServers }, null, 2));
1716
+ return ["--mcp-config", path];
1717
+ }
1718
+ async function startMockServices(input) {
1719
+ const env = {};
1720
+ const running = [];
1721
+ const commandBin = join(input.runDir, "mock-bin");
1722
+ const mcpMocks = [];
1723
+ try {
1724
+ for (const mock of input.mocks) {
1725
+ if (mock.kind === "command") {
1726
+ await mkdir(commandBin, { recursive: true });
1727
+ const source = isAbsolute(mock.executable) ? mock.executable : resolve(input.baseDir, mock.executable);
1728
+ const destination = join(commandBin, mock.name || basename(source));
1729
+ await copyFile(source, destination);
1730
+ await chmod(destination, 493);
1731
+ continue;
1732
+ }
1733
+ if (mock.kind === "mcp") {
1734
+ mcpMocks.push(mock);
1735
+ continue;
1736
+ }
1737
+ const port = await freePort();
1738
+ const url = `http://127.0.0.1:${port}`;
1739
+ const serviceEnv = {
1740
+ ...process.env,
1741
+ ...mock.env ?? {},
1742
+ PORT: String(port),
1743
+ AGENT_SKILL_EVALS_MOCK_DIR: join(input.runDir, "mocks", mock.name)
1744
+ };
1745
+ await mkdir(serviceEnv.AGENT_SKILL_EVALS_MOCK_DIR, { recursive: true });
1746
+ const service = startProcess(mock, input.baseDir, serviceEnv);
1747
+ running.push(service);
1748
+ await waitUntilReady(service, `${url}${mock.ready.path}`, mock.ready.timeout_ms ?? 1e4);
1749
+ env[mock.expose_as] = url;
1750
+ }
1751
+ if (input.mocks.some((mock) => mock.kind === "command")) env.PATH = `${commandBin}:${process.env.PATH ?? ""}`;
1752
+ const additions = await mcpConfigArgs({
1753
+ mocks: mcpMocks,
1754
+ preset: input.preset,
1755
+ runDir: input.runDir,
1756
+ baseDir: input.baseDir
1757
+ });
1758
+ const args = [...input.baseArgs];
1759
+ const stdin = args.lastIndexOf("-");
1760
+ if (stdin >= 0) args.splice(stdin, 0, ...additions);
1761
+ else args.push(...additions);
1762
+ const prepared = {
1763
+ env,
1764
+ args,
1765
+ assertHealthy() {
1766
+ const crashed = running.find((service) => service.readyAt !== void 0 && service.child.exitCode !== null && service.stoppedAt === void 0);
1767
+ if (crashed) throw new Error(`Mock Service "${crashed.name}" crashed during the run (${crashed.child.exitCode}): ${crashed.stderr || crashed.stdout}`);
1768
+ },
1769
+ async stop() {
1770
+ await Promise.all(running.map(stopProcess));
1771
+ await writeFile(join(input.runDir, "mock-services.json"), JSON.stringify(running.map((service) => ({
1772
+ name: service.name,
1773
+ startedAt: service.startedAt,
1774
+ readyAt: service.readyAt,
1775
+ stoppedAt: service.stoppedAt,
1776
+ exitCode: service.child.exitCode,
1777
+ stdout: service.stdout,
1778
+ stderr: service.stderr
1779
+ })), null, 2));
1780
+ }
1781
+ };
1782
+ registry().set(input.runDir, prepared);
1783
+ return prepared;
1784
+ } catch (error) {
1785
+ await Promise.all(running.map(stopProcess));
1786
+ throw error;
1787
+ }
1788
+ }
1789
+ //#endregion
1790
+ //#region src/agent/index.ts
1791
+ function asVars(value) {
1792
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1793
+ }
1794
+ function varsFromContext(context) {
1795
+ return asVars(context.vars) ?? asVars(context.test?.vars) ?? {};
1796
+ }
1797
+ async function prepareRun(fixture, config, testPackDir) {
1798
+ let runDir;
1799
+ let worldPath;
1800
+ try {
1801
+ ({runDir, worldPath} = await createRunDir());
1802
+ } catch (err) {
1803
+ return { error: `agent-skill-evals-provider: failed to create isolated world: ${err instanceof Error ? err.message : String(err)}` };
1804
+ }
1805
+ if (fixture) try {
1806
+ await copyFixture({
1807
+ fixturePath: fixture,
1808
+ baseDir: testPackDir ?? config.baseDir
1809
+ }, worldPath);
1810
+ } catch (err) {
1811
+ return { error: `agent-skill-evals-provider: failed to copy fixture "${fixture}" into isolated world: ${err instanceof Error ? err.message : String(err)}` };
1812
+ }
1813
+ const evidenceCollector = new EvidenceCollector(config.skillEvidence);
1814
+ return {
1815
+ runDir,
1816
+ worldPath,
1817
+ evidenceCollector,
1818
+ world: makeWorldHandle(worldPath, (event) => evidenceCollector.addCommand(event))
1819
+ };
1820
+ }
1821
+ async function runPreconditions(vars, run) {
1822
+ const results = [];
1823
+ let passed = true;
1824
+ const parsed = parseRuntimeTestFields(vars);
1825
+ for (const error of parsed.errors.filter((e) => e.field === "preconditions")) {
1826
+ const at = error.index === void 0 ? error.field : `${error.field}[${error.index}]`;
1827
+ results.push({
1828
+ pass: false,
1829
+ score: 0,
1830
+ reason: `precondition: ${at}: ${error.reason}`
1831
+ });
1832
+ passed = false;
1833
+ }
1834
+ for (const entry of parsed.preconditions) {
1835
+ const plugin = RUNTIME_CHECKS_BY_TYPE.get(entry.type);
1836
+ if (!plugin) {
1837
+ results.push({
1838
+ pass: false,
1839
+ score: 0,
1840
+ reason: `precondition: unknown effect type "${entry.type}"`
1841
+ });
1842
+ passed = false;
1843
+ continue;
1844
+ }
1845
+ const result = await plugin.verify({
1846
+ assertion: entry.args,
1847
+ world: run.world,
1848
+ evidence: evidenceFromSnapshot(run.evidenceCollector.toSnapshot()),
1849
+ mode: "precondition"
1850
+ });
1851
+ results.push(result);
1852
+ if (!result.pass) passed = false;
1853
+ }
1854
+ return {
1855
+ results,
1856
+ passed
1857
+ };
1858
+ }
1859
+ function promptfooTokenUsage(usage) {
1860
+ const tokenUsage = {
1861
+ ...usage.totalTokens !== void 0 ? { total: usage.totalTokens } : {},
1862
+ ...usage.inputTokens !== void 0 ? { prompt: usage.inputTokens } : {},
1863
+ ...usage.outputTokens !== void 0 ? { completion: usage.outputTokens } : {},
1864
+ ...usage.cacheReadTokens !== void 0 ? { cached: usage.cacheReadTokens } : {}
1865
+ };
1866
+ return Object.keys(tokenUsage).length > 0 ? tokenUsage : void 0;
1867
+ }
1868
+ async function runConfiguredAdapter(input) {
1869
+ const adapterId = input.config.adapter;
1870
+ if (!adapterId) return {
1871
+ output: "",
1872
+ error: `agent-skill-evals-provider: config.adapter is required. Set a preset (${PRESET_IDS.join(", ")}) or an adapter (${DOCUMENTED_ADAPTERS.join(", ")}).`
1873
+ };
1874
+ const adapter = adapterRegistry.get(adapterId);
1875
+ if (!adapter) {
1876
+ const suggestion = DOCUMENTED_ADAPTERS.find((id) => id.replace(/[^a-z]/g, "") === adapterId.toLowerCase().replace(/[^a-z]/g, ""));
1877
+ return {
1878
+ output: "",
1879
+ error: `agent-skill-evals-provider: unknown adapter "${adapterId}".` + (suggestion ? ` Did you mean "${suggestion}"?` : "") + ` Supported adapters: ${DOCUMENTED_ADAPTERS.join(", ")}. Presets: ${PRESET_IDS.join(", ")}.`
1880
+ };
1881
+ }
1882
+ if (!input.config.command) return {
1883
+ output: "",
1884
+ error: "agent-skill-evals-provider: config.command is required for dynamic agent runs. Set a preset to get the command and flags for a supported agent."
1885
+ };
1886
+ const command = input.config.command;
1887
+ const conversation = decodeConversationSpec(input.vars.conversation);
1888
+ if (conversation && "error" in conversation) return {
1889
+ output: "",
1890
+ error: `agent-skill-evals-provider: ${conversation.error}`
1891
+ };
1892
+ const sourceEnv = { ...process.env };
1893
+ const baseDir = (typeof input.vars.testPackDir === "string" ? input.vars.testPackDir : void 0) ?? input.config.baseDir ?? process.cwd();
1894
+ const resolvedCommand = resolveConfiguredPath(baseDir, expandEnvVars(command, sourceEnv));
1895
+ const configuredArgs = (input.config.args ?? []).map((arg) => typeof arg === "string" ? resolveConfiguredPath(baseDir, expandEnvVars(arg, sourceEnv)) : arg);
1896
+ const agentArgs = (input.runtimeArgs ?? input.config.args ?? []).map((arg) => typeof arg === "string" ? resolveConfiguredPath(baseDir, expandEnvVars(arg, sourceEnv)) : arg);
1897
+ let skillEnvironment;
1898
+ try {
1899
+ skillEnvironment = await prepareSkillEnvironment({
1900
+ runDir: input.run.runDir,
1901
+ authHome: input.authHome,
1902
+ worldPath: input.run.worldPath,
1903
+ vars: input.vars,
1904
+ baseDir,
1905
+ preset: input.config.preset,
1906
+ adapter: adapterId,
1907
+ args: agentArgs,
1908
+ prompt: input.prompt,
1909
+ evidence: input.run.evidenceCollector,
1910
+ extraEnv: input.runtimeEnv
1911
+ });
1912
+ } catch (error) {
1913
+ return {
1914
+ output: "",
1915
+ error: `agent-skill-evals-provider: failed to prepare hermetic skills: ${error instanceof Error ? error.message : String(error)}`
1916
+ };
1917
+ }
1918
+ const env = skillEnvironment.env;
1919
+ const cliVersion = await captureCliVersion(resolvedCommand, input.run.worldPath, env);
1920
+ input.run.evidenceCollector.mergeRuntime({
1921
+ adapter: adapterId,
1922
+ ...input.config.preset ? { preset: input.config.preset } : {},
1923
+ command,
1924
+ ...cliVersion ? { cliVersion } : {},
1925
+ ...input.config.model ? { model: input.config.model } : {},
1926
+ ...conversation ? { continuation: "transcript-replay" } : {}
1927
+ });
1928
+ const resolvedArgs = skillEnvironment.args;
1929
+ const timeoutMs = input.config.timeoutMs ?? 5 * 6e4;
1930
+ const runAgentTurn = (prompt) => adapter.run({
1931
+ command: resolvedCommand,
1932
+ args: resolvedArgs,
1933
+ cwd: input.run.worldPath,
1934
+ prompt: skillEnvironment.formatPrompt(prompt),
1935
+ evidence: input.run.evidenceCollector,
1936
+ timeoutMs,
1937
+ env
1938
+ });
1939
+ if (!conversation) {
1940
+ const result = await runAgentTurn(input.prompt);
1941
+ if (result.error) return {
1942
+ output: result.output,
1943
+ error: `${result.error}${presetStalenessHint(input.config.preset)}`
1944
+ };
1945
+ return { output: result.output };
1946
+ }
1947
+ const simulatedUserEnv = { ...env };
1948
+ const allowSimulatedUserMocks = Boolean(input.vars.conversation && typeof input.vars.conversation === "object" && input.vars.conversation.simulatedUserAllowMocks);
1949
+ if (!allowSimulatedUserMocks) for (const key of Object.keys(input.runtimeEnv ?? {})) if (process.env[key] === void 0) delete simulatedUserEnv[key];
1950
+ else simulatedUserEnv[key] = process.env[key];
1951
+ const runSimulatedUser = conversation.user ? await makeSimulatedUserRunner({
1952
+ run: input.run,
1953
+ config: input.config,
1954
+ agent: {
1955
+ adapter,
1956
+ command: resolvedCommand,
1957
+ args: allowSimulatedUserMocks ? agentArgs : configuredArgs
1958
+ },
1959
+ baseDir,
1960
+ env: simulatedUserEnv
1961
+ }) : void 0;
1962
+ if (runSimulatedUser && "error" in runSimulatedUser) return {
1963
+ output: "",
1964
+ error: runSimulatedUser.error
1965
+ };
1966
+ const result = await runConversation({
1967
+ spec: conversation,
1968
+ initialPrompt: input.prompt,
1969
+ evidence: input.run.evidenceCollector,
1970
+ runAgentTurn,
1971
+ ...runSimulatedUser ? { runSimulatedUser: runSimulatedUser.run } : {}
1972
+ });
1973
+ if (result.error) return {
1974
+ output: result.output,
1975
+ error: `${result.error}${presetStalenessHint(input.config.preset)}`
1976
+ };
1977
+ return { output: result.output };
1978
+ }
1979
+ async function collectFileEvidence(run, preTree) {
1980
+ const postTree = await snapshotTree(run.worldPath);
1981
+ for (const event of diffTrees(preTree, postTree)) {
1982
+ if (event.path.startsWith(".agents/skills/") || event.path.startsWith(".claude/skills/")) continue;
1983
+ run.evidenceCollector.addFileWrite(event);
1984
+ }
1985
+ }
1986
+ var AgentSkillEvalsProvider = class {
1987
+ config;
1988
+ configError;
1989
+ authHome;
1990
+ id;
1991
+ constructor(options = {}) {
1992
+ const config = decodeProviderConfig(options.config ?? {});
1993
+ if ("error" in config) {
1994
+ this.config = {};
1995
+ this.configError = config.error;
1996
+ } else this.config = config;
1997
+ const label = options.id ?? "agent-skill-evals";
1998
+ this.id = () => label;
1999
+ }
2000
+ async callApi(prompt, context = {}) {
2001
+ if (this.configError) return {
2002
+ output: "",
2003
+ error: this.configError
2004
+ };
2005
+ const startedAt = Date.now();
2006
+ const vars = varsFromContext(context);
2007
+ const fixture = typeof vars.fixture === "string" ? vars.fixture : void 0;
2008
+ const testPackDir = typeof vars.testPackDir === "string" ? vars.testPackDir : void 0;
2009
+ const prepared = await prepareRun(fixture, this.config, testPackDir);
2010
+ if ("error" in prepared) return {
2011
+ output: "",
2012
+ error: prepared.error
2013
+ };
2014
+ this.authHome ??= join(prepared.runDir, "home");
2015
+ const environment = vars.environment && typeof vars.environment === "object" && !Array.isArray(vars.environment) ? vars.environment : {};
2016
+ const mocks = Array.isArray(environment.mocks) ? environment.mocks : [];
2017
+ const mockBaseDir = testPackDir ?? this.config.baseDir ?? process.cwd();
2018
+ let mockServices;
2019
+ try {
2020
+ mockServices = await startMockServices({
2021
+ mocks,
2022
+ runDir: prepared.runDir,
2023
+ baseDir: mockBaseDir,
2024
+ preset: this.config.preset,
2025
+ baseArgs: this.config.args ?? []
2026
+ });
2027
+ } catch (error) {
2028
+ return {
2029
+ output: "",
2030
+ error: `agent-skill-evals-provider: Mock Service setup failed: ${error instanceof Error ? error.message : String(error)}`
2031
+ };
2032
+ }
2033
+ try {
2034
+ for (const command of Array.isArray(vars.setup) ? vars.setup : []) {
2035
+ if (typeof command !== "string") continue;
2036
+ const setup = await prepared.world.exec("sh", ["-lc", command], {
2037
+ timeoutMs: 6e4,
2038
+ env: Object.fromEntries(Object.entries(mockServices.env).filter((entry) => entry[1] !== void 0))
2039
+ });
2040
+ if (setup.exitCode !== 0) {
2041
+ await stopMockServices(prepared.runDir);
2042
+ return {
2043
+ output: "",
2044
+ error: `agent-skill-evals-provider: setup failed (${command}): ${setup.stderr || setup.stdout}`
2045
+ };
2046
+ }
2047
+ }
2048
+ const preconditions = await runPreconditions(vars, prepared);
2049
+ const preTree = await snapshotTree(prepared.worldPath);
2050
+ let output = "";
2051
+ let error;
2052
+ if (preconditions.passed) {
2053
+ const result = await runConfiguredAdapter({
2054
+ prompt,
2055
+ run: prepared,
2056
+ authHome: this.authHome,
2057
+ config: this.config,
2058
+ vars,
2059
+ runtimeEnv: mockServices.env,
2060
+ runtimeArgs: mockServices.args
2061
+ });
2062
+ output = result.output;
2063
+ error = result.error;
2064
+ mockServices.assertHealthy();
2065
+ }
2066
+ await collectFileEvidence(prepared, preTree);
2067
+ let metadata;
2068
+ try {
2069
+ metadata = await persistMetadata({
2070
+ run: prepared,
2071
+ fixture,
2072
+ vars,
2073
+ output,
2074
+ preconditionResults: preconditions.results,
2075
+ preconditionsPassed: preconditions.passed,
2076
+ startedAt,
2077
+ mockEnv: Object.fromEntries(Object.entries(mockServices.env).filter((entry) => entry[1] !== void 0))
2078
+ });
2079
+ } catch (err) {
2080
+ const message = err instanceof Error ? err.message : String(err);
2081
+ return {
2082
+ output,
2083
+ error: `${error ? `${error}; ` : ""}agent-skill-evals-provider: failed to write run evidence/metadata: ${message}`
2084
+ };
2085
+ }
2086
+ const usage = promptfooTokenUsage(prepared.evidenceCollector.toSnapshot().usage);
2087
+ return {
2088
+ output,
2089
+ ...error ? { error } : {},
2090
+ metadata,
2091
+ ...usage ? { tokenUsage: usage } : {}
2092
+ };
2093
+ } catch (caught) {
2094
+ await stopMockServices(prepared.runDir);
2095
+ return {
2096
+ output: "",
2097
+ error: `agent-skill-evals-provider: Mock Service runtime failed: ${caught instanceof Error ? caught.message : String(caught)}`
2098
+ };
2099
+ }
2100
+ }
2101
+ };
2102
+ //#endregion
2103
+ export { EvidenceCollector as a, decodeEvidenceSnapshot as c, makeWorldHandle as i, parseRuntimeTestFields as l, assertMockServicesHealthy as n, evidenceFromSnapshot as o, stopMockServices as r, writeEvidenceTo as s, AgentSkillEvalsProvider as t };
2104
+
2105
+ //# sourceMappingURL=agent-B15R7Xlr.mjs.map