@fre4x/benchmark 1.1.0-beta.0 → 1.1.0-beta.3

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/dist/index.js CHANGED
@@ -3223,8 +3223,8 @@ var require_utils = __commonJS({
3223
3223
  }
3224
3224
  return ind;
3225
3225
  }
3226
- function removeDotSegments(path2) {
3227
- let input = path2;
3226
+ function removeDotSegments(path4) {
3227
+ let input = path4;
3228
3228
  const output = [];
3229
3229
  let nextSlash = -1;
3230
3230
  let len = 0;
@@ -3423,8 +3423,8 @@ var require_schemes = __commonJS({
3423
3423
  wsComponent.secure = void 0;
3424
3424
  }
3425
3425
  if (wsComponent.resourceName) {
3426
- const [path2, query] = wsComponent.resourceName.split("?");
3427
- wsComponent.path = path2 && path2 !== "/" ? path2 : void 0;
3426
+ const [path4, query] = wsComponent.resourceName.split("?");
3427
+ wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
3428
3428
  wsComponent.query = query;
3429
3429
  wsComponent.resourceName = void 0;
3430
3430
  }
@@ -7878,10 +7878,10 @@ function mergeDefs(...defs) {
7878
7878
  function cloneDef(schema) {
7879
7879
  return mergeDefs(schema._zod.def);
7880
7880
  }
7881
- function getElementAtPath(obj, path2) {
7882
- if (!path2)
7881
+ function getElementAtPath(obj, path4) {
7882
+ if (!path4)
7883
7883
  return obj;
7884
- return path2.reduce((acc, key) => acc?.[key], obj);
7884
+ return path4.reduce((acc, key) => acc?.[key], obj);
7885
7885
  }
7886
7886
  function promiseAllObject(promisesObj) {
7887
7887
  const keys = Object.keys(promisesObj);
@@ -8264,11 +8264,11 @@ function aborted(x, startIndex = 0) {
8264
8264
  }
8265
8265
  return false;
8266
8266
  }
8267
- function prefixIssues(path2, issues) {
8267
+ function prefixIssues(path4, issues) {
8268
8268
  return issues.map((iss) => {
8269
8269
  var _a2;
8270
8270
  (_a2 = iss).path ?? (_a2.path = []);
8271
- iss.path.unshift(path2);
8271
+ iss.path.unshift(path4);
8272
8272
  return iss;
8273
8273
  });
8274
8274
  }
@@ -8451,7 +8451,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
8451
8451
  }
8452
8452
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
8453
8453
  const result = { errors: [] };
8454
- const processError = (error49, path2 = []) => {
8454
+ const processError = (error49, path4 = []) => {
8455
8455
  var _a2, _b;
8456
8456
  for (const issue2 of error49.issues) {
8457
8457
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8461,7 +8461,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
8461
8461
  } else if (issue2.code === "invalid_element") {
8462
8462
  processError({ issues: issue2.issues }, issue2.path);
8463
8463
  } else {
8464
- const fullpath = [...path2, ...issue2.path];
8464
+ const fullpath = [...path4, ...issue2.path];
8465
8465
  if (fullpath.length === 0) {
8466
8466
  result.errors.push(mapper(issue2));
8467
8467
  continue;
@@ -8493,8 +8493,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
8493
8493
  }
8494
8494
  function toDotPath(_path) {
8495
8495
  const segs = [];
8496
- const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8497
- for (const seg of path2) {
8496
+ const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8497
+ for (const seg of path4) {
8498
8498
  if (typeof seg === "number")
8499
8499
  segs.push(`[${seg}]`);
8500
8500
  else if (typeof seg === "symbol")
@@ -20471,13 +20471,13 @@ function resolveRef(ref, ctx) {
20471
20471
  if (!ref.startsWith("#")) {
20472
20472
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
20473
20473
  }
20474
- const path2 = ref.slice(1).split("/").filter(Boolean);
20475
- if (path2.length === 0) {
20474
+ const path4 = ref.slice(1).split("/").filter(Boolean);
20475
+ if (path4.length === 0) {
20476
20476
  return ctx.rootSchema;
20477
20477
  }
20478
20478
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
20479
- if (path2[0] === defsKey) {
20480
- const key = path2[1];
20479
+ if (path4[0] === defsKey) {
20480
+ const key = path4[1];
20481
20481
  if (!key || !ctx.defs[key]) {
20482
20482
  throw new Error(`Reference not found: ${ref}`);
20483
20483
  }
@@ -21269,8 +21269,8 @@ function getErrorMap2() {
21269
21269
 
21270
21270
  // ../node_modules/zod/v3/helpers/parseUtil.js
21271
21271
  var makeIssue = (params) => {
21272
- const { data, path: path2, errorMaps, issueData } = params;
21273
- const fullPath = [...path2, ...issueData.path || []];
21272
+ const { data, path: path4, errorMaps, issueData } = params;
21273
+ const fullPath = [...path4, ...issueData.path || []];
21274
21274
  const fullIssue = {
21275
21275
  ...issueData,
21276
21276
  path: fullPath
@@ -21385,11 +21385,11 @@ var errorUtil;
21385
21385
 
21386
21386
  // ../node_modules/zod/v3/types.js
21387
21387
  var ParseInputLazyPath = class {
21388
- constructor(parent, value, path2, key) {
21388
+ constructor(parent, value, path4, key) {
21389
21389
  this._cachedPath = [];
21390
21390
  this.parent = parent;
21391
21391
  this.data = value;
21392
- this._path = path2;
21392
+ this._path = path4;
21393
21393
  this._key = key;
21394
21394
  }
21395
21395
  get path() {
@@ -30432,45 +30432,161 @@ var StdioServerTransport = class {
30432
30432
  }
30433
30433
  };
30434
30434
 
30435
- // src/gaia.ts
30436
- import { readFile } from "node:fs/promises";
30435
+ // src/attemptStore.ts
30436
+ import { randomUUID } from "node:crypto";
30437
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
30438
+ import os from "node:os";
30439
+ import path from "node:path";
30437
30440
 
30438
30441
  // src/types.ts
30439
- var challengeSummarySchema = external_exports.object({
30440
- challenge_id: external_exports.string(),
30441
- benchmark_id: external_exports.string(),
30442
- title: external_exports.string(),
30443
- description: external_exports.string(),
30444
- version: external_exports.string(),
30445
- total_questions: external_exports.number().int().min(1),
30446
- difficulty_levels: external_exports.array(external_exports.number().int().min(1).max(3)),
30447
- supports_assets: external_exports.boolean()
30448
- });
30442
+ var primitiveValueSchema = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()]);
30443
+ var benchmarkFamilySchema = external_exports.enum(["code", "web", "os"]);
30444
+ var runnerKindSchema = external_exports.enum([
30445
+ "code_runner",
30446
+ "browser_runner",
30447
+ "os_runner"
30448
+ ]);
30449
+ var responseFormatSchema = external_exports.enum(["text", "json"]);
30450
+ var assetEncodingSchema = external_exports.enum(["utf8", "base64"]);
30451
+ var assetTransportSchema = external_exports.enum(["inline"]);
30452
+ var assetMaterializationSchema = external_exports.enum(["inline", "workspace_file"]);
30453
+ var checkerKindSchema = external_exports.enum([
30454
+ "exact_text",
30455
+ "normalized_text",
30456
+ "regex_match",
30457
+ "contains_all_text",
30458
+ "json_field_equals",
30459
+ "runner_fact_equals",
30460
+ "runner_log_contains_text"
30461
+ ]);
30462
+ var catalogModeSchema = external_exports.enum([
30463
+ "builtin",
30464
+ "file",
30465
+ "remote_url",
30466
+ "remote_cache",
30467
+ "mock"
30468
+ ]);
30449
30469
  var assetSchema = external_exports.object({
30450
30470
  asset_id: external_exports.string(),
30451
30471
  name: external_exports.string(),
30452
30472
  mime_type: external_exports.string(),
30453
- encoding: external_exports.enum(["utf8", "base64"]).default("utf8"),
30473
+ transport: assetTransportSchema.default("inline"),
30474
+ materialization: assetMaterializationSchema.default("inline"),
30475
+ encoding: assetEncodingSchema.default("utf8"),
30454
30476
  content: external_exports.string(),
30455
30477
  description: external_exports.string().optional()
30456
30478
  });
30457
- var questionSchema = external_exports.object({
30458
- question_id: external_exports.string(),
30479
+ var checkerBaseSchema = external_exports.object({
30480
+ checker_id: external_exports.string()
30481
+ });
30482
+ var exactTextCheckerSchema = checkerBaseSchema.extend({
30483
+ kind: external_exports.literal("exact_text"),
30484
+ expected: external_exports.string(),
30485
+ case_sensitive: external_exports.boolean().default(true)
30486
+ });
30487
+ var normalizedTextCheckerSchema = checkerBaseSchema.extend({
30488
+ kind: external_exports.literal("normalized_text"),
30489
+ expected: external_exports.string()
30490
+ });
30491
+ var regexMatchCheckerSchema = checkerBaseSchema.extend({
30492
+ kind: external_exports.literal("regex_match"),
30493
+ pattern: external_exports.string(),
30494
+ flags: external_exports.string().optional()
30495
+ });
30496
+ var containsAllTextCheckerSchema = checkerBaseSchema.extend({
30497
+ kind: external_exports.literal("contains_all_text"),
30498
+ terms: external_exports.array(external_exports.string()).min(1)
30499
+ });
30500
+ var jsonFieldEqualsCheckerSchema = checkerBaseSchema.extend({
30501
+ kind: external_exports.literal("json_field_equals"),
30502
+ field: external_exports.string(),
30503
+ expected: primitiveValueSchema
30504
+ });
30505
+ var runnerFactEqualsCheckerSchema = checkerBaseSchema.extend({
30506
+ kind: external_exports.literal("runner_fact_equals"),
30507
+ fact: external_exports.string(),
30508
+ expected: primitiveValueSchema
30509
+ });
30510
+ var runnerLogContainsTextCheckerSchema = checkerBaseSchema.extend({
30511
+ kind: external_exports.literal("runner_log_contains_text"),
30512
+ terms: external_exports.array(external_exports.string()).min(1)
30513
+ });
30514
+ var checkerSchema = external_exports.discriminatedUnion("kind", [
30515
+ exactTextCheckerSchema,
30516
+ normalizedTextCheckerSchema,
30517
+ regexMatchCheckerSchema,
30518
+ containsAllTextCheckerSchema,
30519
+ jsonFieldEqualsCheckerSchema,
30520
+ runnerFactEqualsCheckerSchema,
30521
+ runnerLogContainsTextCheckerSchema
30522
+ ]);
30523
+ var runnerArtifactKindSchema = external_exports.enum([
30524
+ "materialized_asset",
30525
+ "submission",
30526
+ "runner_report"
30527
+ ]);
30528
+ var taskSchema = external_exports.object({
30529
+ task_id: external_exports.string(),
30530
+ title: external_exports.string(),
30459
30531
  prompt: external_exports.string(),
30460
- expected_answer: external_exports.string(),
30461
- grading_mode: external_exports.enum(["exact", "normalized"]).default("normalized"),
30532
+ response_format: responseFormatSchema.default("text"),
30462
30533
  difficulty: external_exports.number().int().min(1).max(3),
30463
- assets: external_exports.array(assetSchema).default([])
30534
+ assets: external_exports.array(assetSchema).default([]),
30535
+ checkers: external_exports.array(checkerSchema).min(1)
30464
30536
  });
30465
- var challengeDefinitionSchema = challengeSummarySchema.extend({
30466
- questions: external_exports.array(questionSchema).min(1)
30537
+ var challengeDefinitionSchema = external_exports.object({
30538
+ challenge_id: external_exports.string(),
30539
+ benchmark_id: external_exports.string(),
30540
+ family: benchmarkFamilySchema,
30541
+ runner_kind: runnerKindSchema,
30542
+ title: external_exports.string(),
30543
+ description: external_exports.string(),
30544
+ version: external_exports.string(),
30545
+ source: external_exports.string().default("external"),
30546
+ tasks: external_exports.array(taskSchema).min(1)
30467
30547
  });
30468
- var submissionRecordSchema = external_exports.object({
30469
- question_id: external_exports.string(),
30548
+ var challengeSummarySchema = external_exports.object({
30549
+ challenge_id: external_exports.string(),
30550
+ benchmark_id: external_exports.string(),
30551
+ family: benchmarkFamilySchema,
30552
+ runner_kind: runnerKindSchema,
30553
+ title: external_exports.string(),
30554
+ description: external_exports.string(),
30555
+ version: external_exports.string(),
30556
+ source: external_exports.string(),
30557
+ total_tasks: external_exports.number().int().min(1),
30558
+ difficulty_levels: external_exports.array(external_exports.number().int().min(1).max(3)),
30559
+ supports_assets: external_exports.boolean(),
30560
+ checker_kinds: external_exports.array(checkerKindSchema).min(1)
30561
+ });
30562
+ var checkerEvidenceSchema = external_exports.object({
30563
+ checker_id: external_exports.string(),
30564
+ kind: checkerKindSchema,
30565
+ passed: external_exports.boolean(),
30566
+ message: external_exports.string()
30567
+ });
30568
+ var runnerExecutionArtifactSchema = external_exports.object({
30569
+ artifact_id: external_exports.string(),
30570
+ kind: runnerArtifactKindSchema,
30571
+ path: external_exports.string(),
30572
+ mime_type: external_exports.string(),
30573
+ description: external_exports.string().optional()
30574
+ });
30575
+ var runnerExecutionSchema = external_exports.object({
30576
+ runner_kind: runnerKindSchema,
30577
+ workspace_dir: external_exports.string(),
30578
+ logs: external_exports.array(external_exports.string()),
30579
+ facts: external_exports.record(external_exports.string(), primitiveValueSchema),
30580
+ artifacts: external_exports.array(runnerExecutionArtifactSchema)
30581
+ });
30582
+ var evaluationRecordSchema = external_exports.object({
30583
+ task_id: external_exports.string(),
30470
30584
  solution: external_exports.string(),
30471
30585
  accepted: external_exports.boolean(),
30472
30586
  score_delta: external_exports.number().int().min(0),
30473
- submitted_at: external_exports.string()
30587
+ submitted_at: external_exports.string(),
30588
+ evidence: external_exports.array(checkerEvidenceSchema),
30589
+ execution: runnerExecutionSchema
30474
30590
  });
30475
30591
  var attemptStatusSchema = external_exports.enum(["active", "completed", "cancelled"]);
30476
30592
  var persistedAttemptSchema = external_exports.object({
@@ -30478,17 +30594,18 @@ var persistedAttemptSchema = external_exports.object({
30478
30594
  benchmark_id: external_exports.string(),
30479
30595
  challenge_id: external_exports.string(),
30480
30596
  challenge_title: external_exports.string(),
30597
+ family: benchmarkFamilySchema,
30598
+ runner_kind: runnerKindSchema,
30481
30599
  dataset_version: external_exports.string(),
30600
+ source: external_exports.string(),
30482
30601
  status: attemptStatusSchema,
30483
- current_question_index: external_exports.number().int().min(0),
30484
- total_questions: external_exports.number().int().min(1),
30602
+ revision: external_exports.number().int().min(1),
30603
+ current_task_index: external_exports.number().int().min(0),
30604
+ total_tasks: external_exports.number().int().min(1),
30485
30605
  started_at: external_exports.string(),
30486
30606
  updated_at: external_exports.string(),
30487
- questions: external_exports.array(questionSchema),
30488
- submissions: external_exports.array(submissionRecordSchema)
30489
- });
30490
- var persistedAttemptsFileSchema = external_exports.object({
30491
- attempts: external_exports.array(persistedAttemptSchema)
30607
+ tasks: external_exports.array(taskSchema),
30608
+ evaluations: external_exports.array(evaluationRecordSchema)
30492
30609
  });
30493
30610
  var listChallengesInputSchema = paginationSchema;
30494
30611
  var startChallengeInputSchema = external_exports.object({
@@ -30496,210 +30613,773 @@ var startChallengeInputSchema = external_exports.object({
30496
30613
  });
30497
30614
  var submitSolutionInputSchema = external_exports.object({
30498
30615
  attempt_id: external_exports.string().min(1).describe("Attempt id"),
30499
- solution: external_exports.string().min(1).describe("Answer text")
30616
+ solution: external_exports.string().min(1).describe("Answer text or JSON")
30500
30617
  });
30501
30618
  var getAssetInputSchema = external_exports.object({
30502
30619
  attempt_id: external_exports.string().min(1).describe("Attempt id"),
30503
30620
  asset_id: external_exports.string().min(1).describe("Asset id")
30504
30621
  });
30505
- var getAttemptInputSchema = external_exports.object({
30622
+ var getAttemptInputSchema = paginationSchema.extend({
30506
30623
  attempt_id: external_exports.string().min(1).describe("Attempt id")
30507
30624
  });
30508
30625
  var cancelAttemptInputSchema = external_exports.object({
30509
30626
  attempt_id: external_exports.string().min(1).describe("Attempt id")
30510
30627
  });
30511
-
30512
- // src/gaia.ts
30513
- var DEFAULT_GAIA_CHALLENGES = [
30514
- {
30515
- challenge_id: "gaia_public_validation_sample",
30516
- benchmark_id: "gaia",
30517
- title: "GAIA Public Validation Sample",
30518
- description: "A GAIA-style multi-step sample with asset reading and exact grading.",
30519
- version: "sample-v1",
30520
- total_questions: 2,
30521
- difficulty_levels: [1, 2],
30522
- supports_assets: true,
30523
- questions: [
30524
- {
30525
- question_id: "gaia-q1",
30526
- prompt: "Use the attached CSV and return only the full name with the highest total score.",
30527
- expected_answer: "Mina Park",
30528
- grading_mode: "normalized",
30529
- difficulty: 1,
30530
- assets: [
30531
- {
30532
- asset_id: "gaia-q1-scores",
30533
- name: "lab_scores.csv",
30534
- mime_type: "text/csv",
30535
- encoding: "utf8",
30536
- description: "Score table for three candidates.",
30537
- content: [
30538
- "name,logic,search,tool_use",
30539
- "Mina Park,8,9,10",
30540
- "Jon Ortiz,9,7,8",
30541
- "Asha Singh,7,8,9"
30542
- ].join("\n")
30543
- }
30544
- ]
30545
- },
30546
- {
30547
- question_id: "gaia-q2",
30548
- prompt: "Read the attached briefing and reply with the two-word checkpoint callsign in lowercase.",
30549
- expected_answer: "ember clock",
30550
- grading_mode: "normalized",
30551
- difficulty: 2,
30552
- assets: [
30553
- {
30554
- asset_id: "gaia-q2-brief",
30555
- name: "mission_brief.txt",
30556
- mime_type: "text/plain",
30557
- encoding: "utf8",
30558
- description: "Briefing memo with a repeated callsign.",
30559
- content: [
30560
- "Field memo",
30561
- "",
30562
- "Checkpoint callsign: ember clock.",
30563
- "All couriers must repeat ember clock before hand-off.",
30564
- "The phrase ember clock remains active until rotation."
30565
- ].join("\n")
30566
- }
30567
- ]
30568
- }
30569
- ]
30570
- }
30571
- ];
30572
- async function loadChallengesFromFile(filePath) {
30573
- const raw = await readFile(filePath, "utf8");
30574
- const parsed = JSON.parse(raw);
30575
- if (!Array.isArray(parsed)) {
30576
- throw new Error("GAIA data file must contain a JSON array.");
30577
- }
30578
- return parsed.map((entry) => challengeDefinitionSchema.parse(entry));
30579
- }
30580
- var GaiaAdapter = class {
30581
- benchmarkId = "gaia";
30582
- dataFile = process.env.BENCHMARK_GAIA_DATA_FILE;
30583
- async getChallenges() {
30584
- if (!this.dataFile) {
30585
- return DEFAULT_GAIA_CHALLENGES;
30586
- }
30587
- return await loadChallengesFromFile(this.dataFile);
30588
- }
30589
- async listChallenges() {
30590
- const challenges = await this.getChallenges();
30591
- return challenges.map(
30592
- ({ questions: _questions, ...summary }) => summary
30593
- );
30594
- }
30595
- async getChallengeDefinition(challengeId) {
30596
- const challenges = await this.getChallenges();
30597
- return challenges.find(
30598
- (challenge) => challenge.challenge_id === challengeId
30599
- ) ?? null;
30600
- }
30601
- };
30602
- function createGaiaAdapter() {
30603
- return new GaiaAdapter();
30628
+ var getCatalogStatusInputSchema = external_exports.object({});
30629
+ var syncCatalogInputSchema = external_exports.object({
30630
+ force: external_exports.boolean().default(false).describe("Ignore cache freshness and refetch")
30631
+ });
30632
+ function summarizeChallenge(challenge) {
30633
+ const difficultyLevels = [
30634
+ ...new Set(challenge.tasks.map((task) => task.difficulty))
30635
+ ].sort((left, right) => left - right);
30636
+ const checkerKinds = [
30637
+ ...new Set(
30638
+ challenge.tasks.flatMap(
30639
+ (task) => task.checkers.map((checker) => checker.kind)
30640
+ )
30641
+ )
30642
+ ];
30643
+ return {
30644
+ challenge_id: challenge.challenge_id,
30645
+ benchmark_id: challenge.benchmark_id,
30646
+ family: challenge.family,
30647
+ runner_kind: challenge.runner_kind,
30648
+ title: challenge.title,
30649
+ description: challenge.description,
30650
+ version: challenge.version,
30651
+ source: challenge.source,
30652
+ total_tasks: challenge.tasks.length,
30653
+ difficulty_levels: difficultyLevels,
30654
+ supports_assets: challenge.tasks.some((task) => task.assets.length > 0),
30655
+ checker_kinds: checkerKinds
30656
+ };
30604
30657
  }
30605
30658
 
30606
30659
  // src/attemptStore.ts
30607
- import { mkdir, readFile as readFile2, rm, writeFile } from "node:fs/promises";
30608
- import { randomUUID } from "node:crypto";
30609
- import os from "node:os";
30610
- import path from "node:path";
30611
30660
  function nowIso() {
30612
30661
  return (/* @__PURE__ */ new Date()).toISOString();
30613
30662
  }
30614
30663
  function getStateDir() {
30615
30664
  return process.env.BENCHMARK_STATE_DIR ?? path.join(os.tmpdir(), "fre4x-benchmark-state");
30616
30665
  }
30617
- function getStateFilePath() {
30618
- return path.join(getStateDir(), "attempts.json");
30666
+ function getAttemptWorkspaceDir(attemptId) {
30667
+ return path.join(getStateDir(), "artifacts", attemptId);
30668
+ }
30669
+ function getAttemptsDir() {
30670
+ return path.join(getStateDir(), "attempts");
30671
+ }
30672
+ function getLocksDir() {
30673
+ return path.join(getStateDir(), "locks");
30674
+ }
30675
+ function getAttemptFilePath(attemptId) {
30676
+ return path.join(getAttemptsDir(), `${attemptId}.json`);
30677
+ }
30678
+ function getLockDirPath(attemptId) {
30679
+ return path.join(getLocksDir(), attemptId);
30619
30680
  }
30620
- async function readStateFile() {
30621
- const filePath = getStateFilePath();
30681
+ async function sleep(milliseconds) {
30682
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
30683
+ }
30684
+ async function ensureStateDirs() {
30685
+ await mkdir(getAttemptsDir(), { recursive: true });
30686
+ await mkdir(getLocksDir(), { recursive: true });
30687
+ }
30688
+ async function readAttemptFile(attemptId) {
30689
+ const filePath = getAttemptFilePath(attemptId);
30622
30690
  try {
30623
- const raw = await readFile2(filePath, "utf8");
30691
+ const raw = await readFile(filePath, "utf8");
30624
30692
  const parsed = JSON.parse(raw);
30625
- return persistedAttemptsFileSchema.parse(parsed);
30693
+ return persistedAttemptSchema.parse(parsed);
30626
30694
  } catch (error48) {
30627
30695
  if (error48.code === "ENOENT") {
30628
- return { attempts: [] };
30696
+ return null;
30629
30697
  }
30630
30698
  throw error48;
30631
30699
  }
30632
30700
  }
30633
- async function writeStateFile(state) {
30634
- const dir = getStateDir();
30635
- await mkdir(dir, { recursive: true });
30636
- await writeFile(getStateFilePath(), JSON.stringify(state, null, 2), "utf8");
30701
+ async function writeAttemptFile(attempt) {
30702
+ await ensureStateDirs();
30703
+ const filePath = getAttemptFilePath(attempt.attempt_id);
30704
+ const tempPath = `${filePath}.${randomUUID()}.tmp`;
30705
+ const serialized = JSON.stringify(attempt, null, 2);
30706
+ await writeFile(tempPath, serialized, "utf8");
30707
+ await rename(tempPath, filePath);
30708
+ }
30709
+ async function withAttemptLock(attemptId, operation) {
30710
+ await ensureStateDirs();
30711
+ const lockDir = getLockDirPath(attemptId);
30712
+ let retries = 0;
30713
+ while (true) {
30714
+ try {
30715
+ await mkdir(lockDir);
30716
+ break;
30717
+ } catch (error48) {
30718
+ const code = error48.code;
30719
+ if (code !== "EEXIST") {
30720
+ throw error48;
30721
+ }
30722
+ retries += 1;
30723
+ if (retries > 200) {
30724
+ throw new Error(`Attempt ${attemptId} is locked for too long.`);
30725
+ }
30726
+ await sleep(10);
30727
+ }
30728
+ }
30729
+ try {
30730
+ return await operation();
30731
+ } finally {
30732
+ await rm(lockDir, { recursive: true, force: true });
30733
+ }
30637
30734
  }
30638
30735
  async function createAttempt(challenge) {
30639
- const state = await readStateFile();
30640
30736
  const timestamp = nowIso();
30641
30737
  const attempt = {
30642
30738
  attempt_id: `attempt_${randomUUID()}`,
30643
30739
  benchmark_id: challenge.benchmark_id,
30644
30740
  challenge_id: challenge.challenge_id,
30645
30741
  challenge_title: challenge.title,
30742
+ family: challenge.family,
30743
+ runner_kind: challenge.runner_kind,
30646
30744
  dataset_version: challenge.version,
30745
+ source: challenge.source,
30647
30746
  status: "active",
30648
- current_question_index: 0,
30649
- total_questions: challenge.questions.length,
30747
+ revision: 1,
30748
+ current_task_index: 0,
30749
+ total_tasks: challenge.tasks.length,
30650
30750
  started_at: timestamp,
30651
30751
  updated_at: timestamp,
30652
- questions: challenge.questions,
30653
- submissions: []
30752
+ tasks: challenge.tasks,
30753
+ evaluations: []
30654
30754
  };
30655
- state.attempts.push(attempt);
30656
- await writeStateFile(state);
30755
+ await writeAttemptFile(attempt);
30756
+ await mkdir(getAttemptWorkspaceDir(attempt.attempt_id), {
30757
+ recursive: true
30758
+ });
30657
30759
  return attempt;
30658
30760
  }
30659
30761
  async function getAttempt(attemptId) {
30660
- const state = await readStateFile();
30661
- return state.attempts.find((attempt) => attempt.attempt_id === attemptId) ?? null;
30762
+ return await readAttemptFile(attemptId);
30662
30763
  }
30663
- async function saveAttempt(updated) {
30664
- const state = await readStateFile();
30665
- const index = state.attempts.findIndex(
30666
- (attempt) => attempt.attempt_id === updated.attempt_id
30667
- );
30668
- if (index === -1) {
30669
- throw new Error(`Attempt ${updated.attempt_id} not found.`);
30764
+ async function appendEvaluation(attemptId, expectedTaskId, evaluation) {
30765
+ return await withAttemptLock(attemptId, async () => {
30766
+ const attempt = await readAttemptFile(attemptId);
30767
+ if (!attempt) {
30768
+ throw new Error(`Attempt ${attemptId} not found.`);
30769
+ }
30770
+ if (attempt.status !== "active") {
30771
+ throw new Error(`Attempt ${attemptId} is ${attempt.status}.`);
30772
+ }
30773
+ const currentTask = attempt.tasks[attempt.current_task_index];
30774
+ if (!currentTask) {
30775
+ throw new Error(`Attempt ${attemptId} has no current task.`);
30776
+ }
30777
+ if (currentTask.task_id !== expectedTaskId) {
30778
+ throw new Error(
30779
+ `Attempt ${attemptId} advanced to ${currentTask.task_id} before this submission was stored.`
30780
+ );
30781
+ }
30782
+ const nextAttempt = {
30783
+ ...attempt,
30784
+ revision: attempt.revision + 1,
30785
+ evaluations: [...attempt.evaluations, evaluation],
30786
+ current_task_index: attempt.current_task_index + 1,
30787
+ updated_at: nowIso()
30788
+ };
30789
+ if (nextAttempt.current_task_index >= nextAttempt.total_tasks) {
30790
+ nextAttempt.status = "completed";
30791
+ }
30792
+ await writeAttemptFile(nextAttempt);
30793
+ return nextAttempt;
30794
+ });
30795
+ }
30796
+ async function cancelAttempt(attemptId) {
30797
+ return await withAttemptLock(attemptId, async () => {
30798
+ const attempt = await readAttemptFile(attemptId);
30799
+ if (!attempt) {
30800
+ throw new Error(`Attempt ${attemptId} not found.`);
30801
+ }
30802
+ const nextAttempt = {
30803
+ ...attempt,
30804
+ revision: attempt.revision + 1,
30805
+ status: "cancelled",
30806
+ updated_at: nowIso()
30807
+ };
30808
+ await writeAttemptFile(nextAttempt);
30809
+ return nextAttempt;
30810
+ });
30811
+ }
30812
+
30813
+ // src/catalog.ts
30814
+ import os2 from "node:os";
30815
+ import path2 from "node:path";
30816
+ import { fileURLToPath } from "node:url";
30817
+ import { mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat, writeFile as writeFile2 } from "node:fs/promises";
30818
+ import { randomUUID as randomUUID2 } from "node:crypto";
30819
+
30820
+ // src/mock.ts
30821
+ function isMockEnabled() {
30822
+ return process.env.MOCK === "true" || process.env.BENCHMARK_MOCK === "true";
30823
+ }
30824
+ var IS_MOCK = isMockEnabled();
30825
+
30826
+ // src/catalog.ts
30827
+ var cachedCatalogSchema = external_exports.object({
30828
+ url: external_exports.string().url(),
30829
+ fetched_at: external_exports.string(),
30830
+ challenges: external_exports.array(challengeDefinitionSchema)
30831
+ });
30832
+ var builtinChallengesPromise = null;
30833
+ function getConfiguredCatalogFile() {
30834
+ return process.env.BENCHMARK_CATALOG_FILE ?? process.env.BENCHMARK_GAIA_DATA_FILE ?? null;
30835
+ }
30836
+ function getConfiguredCatalogUrl() {
30837
+ return process.env.BENCHMARK_CATALOG_URL ?? null;
30838
+ }
30839
+ function getStateDir2() {
30840
+ return process.env.BENCHMARK_STATE_DIR ?? path2.join(os2.tmpdir(), "fre4x-benchmark-state");
30841
+ }
30842
+ function getCacheDir() {
30843
+ return process.env.BENCHMARK_CACHE_DIR ?? path2.join(getStateDir2(), "catalog-cache");
30844
+ }
30845
+ function getCacheFilePath() {
30846
+ return path2.join(getCacheDir(), "catalog.json");
30847
+ }
30848
+ function getCacheTtlSeconds() {
30849
+ const raw = process.env.BENCHMARK_CACHE_TTL_SECONDS;
30850
+ if (!raw) {
30851
+ return 3600;
30852
+ }
30853
+ const parsed = Number.parseInt(raw, 10);
30854
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 3600;
30855
+ }
30856
+ async function ensureCacheDir() {
30857
+ await mkdir2(getCacheDir(), { recursive: true });
30858
+ }
30859
+ async function loadChallengesFromFile(filePath) {
30860
+ const raw = await readFile2(filePath, "utf8");
30861
+ const parsed = JSON.parse(raw);
30862
+ if (!Array.isArray(parsed)) {
30863
+ throw new Error("Benchmark catalog file must contain a JSON array.");
30670
30864
  }
30671
- state.attempts[index] = updated;
30672
- await writeStateFile(state);
30865
+ return parsed.map((entry) => challengeDefinitionSchema.parse(entry));
30673
30866
  }
30674
- async function appendSubmission(attempt, submission) {
30675
- const nextAttempt = {
30676
- ...attempt,
30677
- submissions: [...attempt.submissions, submission],
30678
- current_question_index: attempt.current_question_index + 1,
30679
- updated_at: nowIso()
30867
+ async function getBuiltinChallenges() {
30868
+ builtinChallengesPromise ??= (async () => {
30869
+ const candidatePaths = [
30870
+ fileURLToPath(new URL("./fallback-catalog.json", import.meta.url)),
30871
+ fileURLToPath(
30872
+ new URL("../catalogs/expanded-catalog.json", import.meta.url)
30873
+ )
30874
+ ];
30875
+ for (const candidatePath of candidatePaths) {
30876
+ try {
30877
+ return await loadChallengesFromFile(candidatePath);
30878
+ } catch (error48) {
30879
+ if (error48.code === "ENOENT") {
30880
+ continue;
30881
+ }
30882
+ throw error48;
30883
+ }
30884
+ }
30885
+ throw new Error("No bundled fallback catalog was found.");
30886
+ })();
30887
+ return await builtinChallengesPromise;
30888
+ }
30889
+ async function mergeWithBuiltins(externalChallenges) {
30890
+ const builtinChallenges = await getBuiltinChallenges();
30891
+ const merged = /* @__PURE__ */ new Map();
30892
+ for (const challenge of builtinChallenges) {
30893
+ merged.set(challenge.challenge_id, challenge);
30894
+ }
30895
+ for (const challenge of externalChallenges) {
30896
+ merged.set(challenge.challenge_id, challenge);
30897
+ }
30898
+ return [...merged.values()];
30899
+ }
30900
+ async function readCachedCatalog() {
30901
+ try {
30902
+ const raw = await readFile2(getCacheFilePath(), "utf8");
30903
+ const parsed = JSON.parse(raw);
30904
+ return cachedCatalogSchema.parse(parsed);
30905
+ } catch (error48) {
30906
+ if (error48.code === "ENOENT") {
30907
+ return null;
30908
+ }
30909
+ throw error48;
30910
+ }
30911
+ }
30912
+ async function writeCachedCatalog(url2, challenges) {
30913
+ await ensureCacheDir();
30914
+ const cache = {
30915
+ url: url2,
30916
+ fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
30917
+ challenges
30680
30918
  };
30681
- if (nextAttempt.current_question_index >= nextAttempt.total_questions) {
30682
- nextAttempt.status = "completed";
30919
+ const filePath = getCacheFilePath();
30920
+ const tempPath = `${filePath}.${randomUUID2()}.tmp`;
30921
+ await writeFile2(tempPath, JSON.stringify(cache, null, 2), "utf8");
30922
+ await rename2(tempPath, filePath);
30923
+ return cache;
30924
+ }
30925
+ function getAgeSeconds(timestamp) {
30926
+ const now = Date.now();
30927
+ const value = Date.parse(timestamp);
30928
+ if (!Number.isFinite(value)) {
30929
+ return Number.MAX_SAFE_INTEGER;
30930
+ }
30931
+ return Math.max(0, Math.floor((now - value) / 1e3));
30932
+ }
30933
+ async function isCacheFresh(cache) {
30934
+ if (!cache) {
30935
+ return false;
30936
+ }
30937
+ return getAgeSeconds(cache.fetched_at) <= getCacheTtlSeconds();
30938
+ }
30939
+ async function fetchChallengesFromUrl(url2) {
30940
+ const response = await fetch(url2, {
30941
+ headers: {
30942
+ accept: "application/json"
30943
+ }
30944
+ });
30945
+ if (!response.ok) {
30946
+ throw new Error(
30947
+ `Catalog fetch failed with status ${response.status} ${response.statusText}.`
30948
+ );
30949
+ }
30950
+ const parsed = await response.json();
30951
+ if (!Array.isArray(parsed)) {
30952
+ throw new Error("Remote catalog must contain a JSON array.");
30953
+ }
30954
+ return parsed.map((entry) => challengeDefinitionSchema.parse(entry));
30955
+ }
30956
+ async function loadRemoteCatalog(forceRefresh) {
30957
+ const configuredUrl = getConfiguredCatalogUrl();
30958
+ if (!configuredUrl) {
30959
+ return {
30960
+ challenges: await getBuiltinChallenges(),
30961
+ mode: "builtin",
30962
+ lastError: null
30963
+ };
30964
+ }
30965
+ const cache = await readCachedCatalog();
30966
+ if (cache && !forceRefresh && await isCacheFresh(cache)) {
30967
+ return {
30968
+ challenges: await mergeWithBuiltins(cache.challenges),
30969
+ mode: "remote_cache",
30970
+ lastError: null
30971
+ };
30972
+ }
30973
+ try {
30974
+ const remoteChallenges = await fetchChallengesFromUrl(configuredUrl);
30975
+ await writeCachedCatalog(configuredUrl, remoteChallenges);
30976
+ return {
30977
+ challenges: await mergeWithBuiltins(remoteChallenges),
30978
+ mode: "remote_url",
30979
+ lastError: null
30980
+ };
30981
+ } catch (error48) {
30982
+ if (cache) {
30983
+ const message = error48 instanceof Error ? error48.message : "Remote catalog fetch failed.";
30984
+ return {
30985
+ challenges: await mergeWithBuiltins(cache.challenges),
30986
+ mode: "remote_cache",
30987
+ lastError: message
30988
+ };
30989
+ }
30990
+ throw error48;
30683
30991
  }
30684
- await saveAttempt(nextAttempt);
30685
- return nextAttempt;
30686
30992
  }
30687
- async function cancelAttempt(attempt) {
30688
- const nextAttempt = {
30689
- ...attempt,
30690
- status: "cancelled",
30691
- updated_at: nowIso()
30993
+ async function loadCatalog(forceRefresh = false) {
30994
+ if (isMockEnabled()) {
30995
+ return {
30996
+ challenges: await getBuiltinChallenges(),
30997
+ mode: "mock",
30998
+ lastError: null
30999
+ };
31000
+ }
31001
+ const catalogFile = getConfiguredCatalogFile();
31002
+ if (catalogFile) {
31003
+ return {
31004
+ challenges: await mergeWithBuiltins(
31005
+ await loadChallengesFromFile(catalogFile)
31006
+ ),
31007
+ mode: "file",
31008
+ lastError: null
31009
+ };
31010
+ }
31011
+ return await loadRemoteCatalog(forceRefresh);
31012
+ }
31013
+ async function listChallenges() {
31014
+ const catalog = await loadCatalog();
31015
+ return catalog.challenges.map((challenge) => summarizeChallenge(challenge));
31016
+ }
31017
+ async function getChallengeDefinition(challengeId) {
31018
+ const catalog = await loadCatalog();
31019
+ return catalog.challenges.find(
31020
+ (challenge) => challenge.challenge_id === challengeId
31021
+ ) ?? null;
31022
+ }
31023
+ async function syncCatalog(forceRefresh) {
31024
+ if (isMockEnabled()) {
31025
+ return {
31026
+ status: await getCatalogStatus(),
31027
+ challenges: await getBuiltinChallenges()
31028
+ };
31029
+ }
31030
+ const configuredFile = getConfiguredCatalogFile();
31031
+ if (configuredFile) {
31032
+ const challenges = await mergeWithBuiltins(
31033
+ await loadChallengesFromFile(configuredFile)
31034
+ );
31035
+ return {
31036
+ status: await getCatalogStatus(challenges, "file", null),
31037
+ challenges
31038
+ };
31039
+ }
31040
+ const configuredUrl = getConfiguredCatalogUrl();
31041
+ if (!configuredUrl) {
31042
+ throw new Error(
31043
+ "BENCHMARK_CATALOG_URL is required for remote catalog sync."
31044
+ );
31045
+ }
31046
+ const loaded = await loadCatalog(forceRefresh);
31047
+ return {
31048
+ status: await getCatalogStatus(
31049
+ loaded.challenges,
31050
+ loaded.mode,
31051
+ loaded.lastError
31052
+ ),
31053
+ challenges: loaded.challenges
31054
+ };
31055
+ }
31056
+ async function getCatalogStatus(knownChallenges, forcedMode, lastErrorOverride) {
31057
+ const configuredFile = getConfiguredCatalogFile();
31058
+ const configuredUrl = getConfiguredCatalogUrl();
31059
+ const cacheFile = getCacheFilePath();
31060
+ const ttlSeconds = getCacheTtlSeconds();
31061
+ const cache = await readCachedCatalog();
31062
+ const cacheFetchedAt = cache?.fetched_at ?? null;
31063
+ const cacheAgeSeconds = cacheFetchedAt ? getAgeSeconds(cacheFetchedAt) : null;
31064
+ const mode = forcedMode ? catalogModeSchema.parse(forcedMode) : isMockEnabled() ? "mock" : configuredFile ? "file" : configuredUrl ? cache ? "remote_cache" : "remote_url" : "builtin";
31065
+ let challengeCount = knownChallenges?.length ?? null;
31066
+ if (challengeCount === null) {
31067
+ if (mode === "mock" || mode === "builtin") {
31068
+ challengeCount = (await getBuiltinChallenges()).length;
31069
+ } else if (configuredFile) {
31070
+ try {
31071
+ challengeCount = (await mergeWithBuiltins(
31072
+ await loadChallengesFromFile(configuredFile)
31073
+ )).length;
31074
+ } catch {
31075
+ challengeCount = null;
31076
+ }
31077
+ } else if (cache) {
31078
+ challengeCount = (await mergeWithBuiltins(cache.challenges)).length;
31079
+ }
31080
+ }
31081
+ let cacheExists = false;
31082
+ try {
31083
+ await stat(cacheFile);
31084
+ cacheExists = true;
31085
+ } catch (error48) {
31086
+ if (error48.code !== "ENOENT") {
31087
+ throw error48;
31088
+ }
31089
+ }
31090
+ return {
31091
+ mode,
31092
+ configured_file: configuredFile,
31093
+ configured_url: configuredUrl,
31094
+ cache_file: cacheFile,
31095
+ cache_exists: cacheExists,
31096
+ cache_ttl_seconds: mode === "remote_cache" || mode === "remote_url" ? ttlSeconds : null,
31097
+ cache_fetched_at: cacheFetchedAt,
31098
+ cache_age_seconds: cacheAgeSeconds,
31099
+ can_sync: !isMockEnabled() && !configuredFile && Boolean(configuredUrl),
31100
+ challenge_count: challengeCount,
31101
+ last_error: lastErrorOverride ?? null
30692
31102
  };
30693
- await saveAttempt(nextAttempt);
30694
- return nextAttempt;
30695
31103
  }
30696
31104
 
30697
- // src/mock.ts
30698
- var IS_MOCK = process.env.MOCK === "true" || process.env.BENCHMARK_MOCK === "true";
31105
+ // src/checkers.ts
31106
+ function normalizeText(value) {
31107
+ return value.trim().toLowerCase().replace(/\s+/g, " ");
31108
+ }
31109
+ function isRecord(value) {
31110
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31111
+ }
31112
+ function getPrimitiveAtPath(value, fieldPath) {
31113
+ let current = value;
31114
+ for (const segment of fieldPath.split(".")) {
31115
+ if (!isRecord(current) || !(segment in current)) {
31116
+ return void 0;
31117
+ }
31118
+ current = current[segment];
31119
+ }
31120
+ if (typeof current === "string" || typeof current === "number" || typeof current === "boolean") {
31121
+ return current;
31122
+ }
31123
+ return void 0;
31124
+ }
31125
+ function renderPrimitive(value) {
31126
+ return typeof value === "string" ? value : String(value);
31127
+ }
31128
+ function evaluateChecker(checker, solution, execution) {
31129
+ switch (checker.kind) {
31130
+ case "exact_text": {
31131
+ const actual = checker.case_sensitive ? solution.trim() : solution.trim().toLowerCase();
31132
+ const expected = checker.case_sensitive ? checker.expected.trim() : checker.expected.trim().toLowerCase();
31133
+ const passed = actual === expected;
31134
+ return {
31135
+ checker_id: checker.checker_id,
31136
+ kind: checker.kind,
31137
+ passed,
31138
+ message: passed ? "Exact text matched." : `Expected "${checker.expected}" but received "${solution.trim()}".`
31139
+ };
31140
+ }
31141
+ case "normalized_text": {
31142
+ const actual = normalizeText(solution);
31143
+ const expected = normalizeText(checker.expected);
31144
+ const passed = actual === expected;
31145
+ return {
31146
+ checker_id: checker.checker_id,
31147
+ kind: checker.kind,
31148
+ passed,
31149
+ message: passed ? "Normalized text matched." : `Expected normalized "${checker.expected}" but received "${solution.trim()}".`
31150
+ };
31151
+ }
31152
+ case "regex_match": {
31153
+ try {
31154
+ const expression = new RegExp(checker.pattern, checker.flags);
31155
+ const passed = expression.test(solution);
31156
+ return {
31157
+ checker_id: checker.checker_id,
31158
+ kind: checker.kind,
31159
+ passed,
31160
+ message: passed ? "Regex matched the solution." : `Pattern /${checker.pattern}/${checker.flags ?? ""} did not match.`
31161
+ };
31162
+ } catch (error48) {
31163
+ const message = error48 instanceof Error ? error48.message : "Invalid regular expression.";
31164
+ return {
31165
+ checker_id: checker.checker_id,
31166
+ kind: checker.kind,
31167
+ passed: false,
31168
+ message
31169
+ };
31170
+ }
31171
+ }
31172
+ case "contains_all_text": {
31173
+ const actual = normalizeText(solution);
31174
+ const missing = checker.terms.filter(
31175
+ (term) => !actual.includes(normalizeText(term))
31176
+ );
31177
+ return {
31178
+ checker_id: checker.checker_id,
31179
+ kind: checker.kind,
31180
+ passed: missing.length === 0,
31181
+ message: missing.length === 0 ? "All required terms were present." : `Missing terms: ${missing.join(", ")}.`
31182
+ };
31183
+ }
31184
+ case "json_field_equals": {
31185
+ try {
31186
+ const parsed = JSON.parse(solution);
31187
+ const actual = getPrimitiveAtPath(parsed, checker.field);
31188
+ const passed = actual === checker.expected;
31189
+ return {
31190
+ checker_id: checker.checker_id,
31191
+ kind: checker.kind,
31192
+ passed,
31193
+ message: actual === void 0 ? `Field "${checker.field}" was missing or not primitive.` : passed ? `Field "${checker.field}" matched ${renderPrimitive(checker.expected)}.` : `Field "${checker.field}" expected ${renderPrimitive(checker.expected)} but received ${renderPrimitive(actual)}.`
31194
+ };
31195
+ } catch (error48) {
31196
+ const message = error48 instanceof Error ? error48.message : "Solution was not valid JSON.";
31197
+ return {
31198
+ checker_id: checker.checker_id,
31199
+ kind: checker.kind,
31200
+ passed: false,
31201
+ message
31202
+ };
31203
+ }
31204
+ }
31205
+ case "runner_fact_equals": {
31206
+ const actual = execution.facts[checker.fact];
31207
+ const passed = actual === checker.expected;
31208
+ return {
31209
+ checker_id: checker.checker_id,
31210
+ kind: checker.kind,
31211
+ passed,
31212
+ message: actual === void 0 ? `Runner fact "${checker.fact}" was missing.` : passed ? `Runner fact "${checker.fact}" matched ${renderPrimitive(checker.expected)}.` : `Runner fact "${checker.fact}" expected ${renderPrimitive(checker.expected)} but received ${renderPrimitive(actual)}.`
31213
+ };
31214
+ }
31215
+ case "runner_log_contains_text": {
31216
+ const normalizedLogs = normalizeText(execution.logs.join("\n"));
31217
+ const missing = checker.terms.filter(
31218
+ (term) => !normalizedLogs.includes(normalizeText(term))
31219
+ );
31220
+ return {
31221
+ checker_id: checker.checker_id,
31222
+ kind: checker.kind,
31223
+ passed: missing.length === 0,
31224
+ message: missing.length === 0 ? "Runner logs contained all required terms." : `Runner logs missing terms: ${missing.join(", ")}.`
31225
+ };
31226
+ }
31227
+ }
31228
+ }
31229
+ function evaluateTask(task, solution, execution) {
31230
+ const evidence = task.checkers.map(
31231
+ (checker) => evaluateChecker(checker, solution, execution)
31232
+ );
31233
+ return {
31234
+ accepted: evidence.every((entry) => entry.passed),
31235
+ evidence
31236
+ };
31237
+ }
31238
+
31239
+ // src/runners.ts
31240
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
31241
+ import path3 from "node:path";
31242
+ import { randomUUID as randomUUID3 } from "node:crypto";
31243
+ function nowStamp() {
31244
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
31245
+ }
31246
+ function createArtifact(kind, filePath, mimeType, description) {
31247
+ return {
31248
+ artifact_id: `${kind}_${randomUUID3()}`,
31249
+ kind,
31250
+ path: filePath,
31251
+ mime_type: mimeType,
31252
+ ...description ? { description } : {}
31253
+ };
31254
+ }
31255
+ async function ensureTaskWorkspace(attemptId, taskId) {
31256
+ const workspaceDir = path3.join(getAttemptWorkspaceDir(attemptId), taskId);
31257
+ await mkdir3(workspaceDir, { recursive: true });
31258
+ return workspaceDir;
31259
+ }
31260
+ async function materializeAsset(workspaceDir, taskId, asset) {
31261
+ if (asset.materialization !== "workspace_file") {
31262
+ return null;
31263
+ }
31264
+ const assetDir = path3.join(workspaceDir, "assets");
31265
+ await mkdir3(assetDir, { recursive: true });
31266
+ const fileName = `${taskId}-${asset.name}`;
31267
+ const filePath = path3.join(assetDir, fileName);
31268
+ await writeFile3(filePath, asset.content, "utf8");
31269
+ return createArtifact(
31270
+ "materialized_asset",
31271
+ filePath,
31272
+ asset.mime_type,
31273
+ asset.description
31274
+ );
31275
+ }
31276
+ async function writeSubmissionArtifact(workspaceDir, task, solution) {
31277
+ const extension = task.response_format === "json" ? "json" : "txt";
31278
+ const filePath = path3.join(workspaceDir, `submission.${extension}`);
31279
+ await writeFile3(filePath, solution, "utf8");
31280
+ return createArtifact(
31281
+ "submission",
31282
+ filePath,
31283
+ task.response_format === "json" ? "application/json" : "text/plain",
31284
+ "Submitted solution payload."
31285
+ );
31286
+ }
31287
+ async function writeRunnerReport(workspaceDir, task, logs, facts) {
31288
+ const reportPath = path3.join(
31289
+ workspaceDir,
31290
+ `runner-report-${nowStamp()}.json`
31291
+ );
31292
+ await writeFile3(
31293
+ reportPath,
31294
+ JSON.stringify(
31295
+ {
31296
+ task_id: task.task_id,
31297
+ logs,
31298
+ facts
31299
+ },
31300
+ null,
31301
+ 2
31302
+ ),
31303
+ "utf8"
31304
+ );
31305
+ return createArtifact(
31306
+ "runner_report",
31307
+ reportPath,
31308
+ "application/json",
31309
+ "Execution report with runner facts and logs."
31310
+ );
31311
+ }
31312
+ function countWorkspaceAssets(task) {
31313
+ return task.assets.filter(
31314
+ (asset) => asset.materialization === "workspace_file"
31315
+ ).length;
31316
+ }
31317
+ async function executeTaskRunner(attempt, task, solution) {
31318
+ const workspaceDir = await ensureTaskWorkspace(
31319
+ attempt.attempt_id,
31320
+ task.task_id
31321
+ );
31322
+ const artifacts = [];
31323
+ const logs = [
31324
+ `runner ${attempt.runner_kind} started for task ${task.task_id}`
31325
+ ];
31326
+ for (const asset of task.assets) {
31327
+ const artifact = await materializeAsset(
31328
+ workspaceDir,
31329
+ task.task_id,
31330
+ asset
31331
+ );
31332
+ if (artifact) {
31333
+ artifacts.push(artifact);
31334
+ logs.push(
31335
+ `materialized asset ${asset.asset_id} to ${artifact.path}`
31336
+ );
31337
+ } else {
31338
+ logs.push(`kept asset ${asset.asset_id} inline`);
31339
+ }
31340
+ }
31341
+ const submissionArtifact = await writeSubmissionArtifact(
31342
+ workspaceDir,
31343
+ task,
31344
+ solution
31345
+ );
31346
+ artifacts.push(submissionArtifact);
31347
+ logs.push(`wrote submission artifact ${submissionArtifact.path}`);
31348
+ const facts = {
31349
+ materialized_asset_count: countWorkspaceAssets(task),
31350
+ total_asset_count: task.assets.length,
31351
+ response_format: task.response_format,
31352
+ family: attempt.family
31353
+ };
31354
+ if (attempt.runner_kind === "code_runner") {
31355
+ facts.domain = "code";
31356
+ logs.push("prepared code workspace inputs");
31357
+ } else if (attempt.runner_kind === "browser_runner") {
31358
+ facts.domain = "web";
31359
+ logs.push("prepared browser snapshot workspace");
31360
+ } else {
31361
+ facts.domain = "os";
31362
+ logs.push("prepared os workspace inventory");
31363
+ }
31364
+ const reportArtifact = await writeRunnerReport(
31365
+ workspaceDir,
31366
+ task,
31367
+ logs,
31368
+ facts
31369
+ );
31370
+ artifacts.push(reportArtifact);
31371
+ logs.push(`wrote runner report ${reportArtifact.path}`);
31372
+ return {
31373
+ runner_kind: attempt.runner_kind,
31374
+ workspace_dir: workspaceDir,
31375
+ logs,
31376
+ facts,
31377
+ artifacts
31378
+ };
31379
+ }
30699
31380
 
30700
31381
  // src/index.ts
30701
31382
  var PACKAGE_VERSION = getPackageVersion(import.meta.url);
30702
- var ADAPTERS = [createGaiaAdapter()];
30703
31383
  var server = new McpServer({
30704
31384
  name: "@fre4x/benchmark",
30705
31385
  version: PACKAGE_VERSION
@@ -30715,29 +31395,47 @@ function toPublicAssetDescriptor(asset) {
30715
31395
  asset_id: asset.asset_id,
30716
31396
  name: asset.name,
30717
31397
  mime_type: asset.mime_type,
31398
+ transport: asset.transport,
31399
+ materialization: asset.materialization,
30718
31400
  ...asset.description ? { description: asset.description } : {}
30719
31401
  };
30720
31402
  }
30721
- function toPublicQuestion(question) {
31403
+ function toPublicTask(task) {
30722
31404
  return {
30723
- question_id: question.question_id,
30724
- prompt: question.prompt,
30725
- difficulty: question.difficulty,
30726
- assets: question.assets.map(toPublicAssetDescriptor)
30727
- };
30728
- }
30729
- function normalizeAnswer(value) {
30730
- return value.trim().toLowerCase().replace(/\s+/g, " ");
30731
- }
30732
- function gradeQuestion(question, solution) {
30733
- if (question.grading_mode === "exact") {
30734
- return question.expected_answer.trim() === solution.trim();
30735
- }
30736
- return normalizeAnswer(question.expected_answer) === normalizeAnswer(solution);
31405
+ task_id: task.task_id,
31406
+ title: task.title,
31407
+ prompt: task.prompt,
31408
+ response_format: task.response_format,
31409
+ difficulty: task.difficulty,
31410
+ assets: task.assets.map(toPublicAssetDescriptor),
31411
+ checker_kinds: task.checkers.map((checker) => checker.kind)
31412
+ };
31413
+ }
31414
+ function formatRunnerExecution(execution) {
31415
+ const logLines = execution.logs.map((entry) => `- ${entry}`).join("\n");
31416
+ const factLines = Object.entries(execution.facts).map(([key, value]) => `- \`${key}\`: ${value}`).join("\n");
31417
+ const artifactLines = execution.artifacts.map((artifact) => `- \`${artifact.kind}\`: ${artifact.path}`).join("\n");
31418
+ return [
31419
+ "## Runner Execution",
31420
+ "",
31421
+ `**Workspace Dir:** ${execution.workspace_dir}`,
31422
+ "",
31423
+ "### Facts",
31424
+ "",
31425
+ factLines || "_No facts recorded._",
31426
+ "",
31427
+ "### Logs",
31428
+ "",
31429
+ logLines || "_No logs recorded._",
31430
+ "",
31431
+ "### Artifacts",
31432
+ "",
31433
+ artifactLines || "_No artifacts recorded._"
31434
+ ].join("\n");
30737
31435
  }
30738
31436
  function sumScore(attempt) {
30739
- return attempt.submissions.reduce(
30740
- (total, submission) => total + submission.score_delta,
31437
+ return attempt.evaluations.reduce(
31438
+ (total, evaluation) => total + evaluation.score_delta,
30741
31439
  0
30742
31440
  );
30743
31441
  }
@@ -30749,16 +31447,19 @@ function createGuidanceStep(action, recommendedTool, reason, exampleArguments) {
30749
31447
  ...exampleArguments ? { example_arguments: exampleArguments } : {}
30750
31448
  };
30751
31449
  }
30752
- function getQuestionGuidance(attemptId, question) {
30753
- if (question.assets.length > 0) {
31450
+ function createSubmitExample(task) {
31451
+ return task.response_format === "json" ? '{"field":"value"}' : "your final answer";
31452
+ }
31453
+ function getTaskGuidance(attemptId, task) {
31454
+ if (task.assets.length > 0) {
30754
31455
  return {
30755
31456
  primary: createGuidanceStep(
30756
31457
  "review_asset",
30757
31458
  "benchmark_get_asset",
30758
- "This question includes an attachment.",
31459
+ "This task includes an attachment.",
30759
31460
  {
30760
31461
  attempt_id: attemptId,
30761
- asset_id: question.assets[0].asset_id
31462
+ asset_id: task.assets[0].asset_id
30762
31463
  }
30763
31464
  ),
30764
31465
  alternatives: [
@@ -30768,7 +31469,7 @@ function getQuestionGuidance(attemptId, question) {
30768
31469
  "Submit after reviewing the attachment.",
30769
31470
  {
30770
31471
  attempt_id: attemptId,
30771
- solution: "your final answer"
31472
+ solution: createSubmitExample(task)
30772
31473
  }
30773
31474
  )
30774
31475
  ]
@@ -30778,10 +31479,10 @@ function getQuestionGuidance(attemptId, question) {
30778
31479
  primary: createGuidanceStep(
30779
31480
  "submit_solution",
30780
31481
  "benchmark_submit_solution",
30781
- "This question is ready for grading.",
31482
+ "This task is ready for deterministic grading.",
30782
31483
  {
30783
31484
  attempt_id: attemptId,
30784
- solution: "your final answer"
31485
+ solution: createSubmitExample(task)
30785
31486
  }
30786
31487
  ),
30787
31488
  alternatives: [
@@ -30789,24 +31490,17 @@ function getQuestionGuidance(attemptId, question) {
30789
31490
  "inspect_attempt",
30790
31491
  "benchmark_get_attempt",
30791
31492
  "Review the current attempt state.",
30792
- { attempt_id: attemptId }
31493
+ {
31494
+ attempt_id: attemptId,
31495
+ limit: "20",
31496
+ offset: "0"
31497
+ }
30793
31498
  )
30794
31499
  ]
30795
31500
  };
30796
31501
  }
30797
- function findAdapterForChallengeId(challengeId) {
30798
- return ADAPTERS.find(
30799
- (adapter) => challengeId.startsWith(`${adapter.benchmarkId}_`)
30800
- ) ?? null;
30801
- }
30802
- async function listAllChallenges() {
30803
- const groups = await Promise.all(
30804
- ADAPTERS.map(async (adapter) => await adapter.listChallenges())
30805
- );
30806
- return groups.flat();
30807
- }
30808
- function getCurrentQuestion(attempt) {
30809
- return attempt.questions[attempt.current_question_index] ?? null;
31502
+ function getCurrentTask(attempt) {
31503
+ return attempt.tasks[attempt.current_task_index] ?? null;
30810
31504
  }
30811
31505
  function formatChallengeSummary(summary) {
30812
31506
  return [
@@ -30814,25 +31508,40 @@ function formatChallengeSummary(summary) {
30814
31508
  formatFields([
30815
31509
  ["Challenge ID", summary.challenge_id],
30816
31510
  ["Benchmark", summary.benchmark_id],
31511
+ ["Family", summary.family],
31512
+ ["Runner", summary.runner_kind],
30817
31513
  ["Version", summary.version],
30818
- ["Questions", summary.total_questions],
31514
+ ["Source", summary.source],
31515
+ ["Tasks", summary.total_tasks],
30819
31516
  ["Difficulties", summary.difficulty_levels.join(", ")],
31517
+ ["Checker Kinds", summary.checker_kinds.join(", ")],
30820
31518
  ["Assets", summary.supports_assets ? "yes" : "no"]
30821
31519
  ]),
30822
31520
  "",
30823
31521
  summary.description
30824
31522
  ].join("\n");
30825
31523
  }
30826
- function formatQuestionBlock(attemptId, questionIndex, totalQuestions, question) {
30827
- const assetLines = question.assets.length > 0 ? formatListItems(
30828
- question.assets,
30829
- (asset) => `- \`${asset.asset_id}\` (${asset.name}, ${asset.mime_type})`
31524
+ function formatTaskBlock(attemptId, taskIndex, totalTasks, task) {
31525
+ const assetLines = task.assets.length > 0 ? formatListItems(
31526
+ task.assets,
31527
+ (asset) => `- \`${asset.asset_id}\` (${asset.name}, ${asset.mime_type}, ${asset.materialization})`
30830
31528
  ) : "_No assets attached._";
30831
- const guidance = getQuestionGuidance(attemptId, question);
31529
+ const guidance = getTaskGuidance(attemptId, task);
30832
31530
  return [
30833
- `# Question ${questionIndex} of ${totalQuestions}`,
31531
+ `# Task ${taskIndex} of ${totalTasks}`,
30834
31532
  "",
30835
- question.prompt,
31533
+ `## ${task.title}`,
31534
+ "",
31535
+ task.prompt,
31536
+ "",
31537
+ formatFields([
31538
+ ["Response Format", task.response_format],
31539
+ ["Difficulty", task.difficulty],
31540
+ [
31541
+ "Checker Kinds",
31542
+ task.checkers.map((checker) => checker.kind).join(", ")
31543
+ ]
31544
+ ]),
30836
31545
  "",
30837
31546
  "## Assets",
30838
31547
  "",
@@ -30845,9 +31554,71 @@ function formatQuestionBlock(attemptId, questionIndex, totalQuestions, question)
30845
31554
  `- **Reason**: ${guidance.primary.reason}`
30846
31555
  ].join("\n");
30847
31556
  }
31557
+ function formatCatalogStatus(status) {
31558
+ return [
31559
+ "# Catalog Status",
31560
+ "",
31561
+ formatFields([
31562
+ ["Mode", status.mode],
31563
+ ["Configured File", status.configured_file],
31564
+ ["Configured URL", status.configured_url],
31565
+ ["Cache File", status.cache_file],
31566
+ ["Cache Exists", status.cache_exists ? "yes" : "no"],
31567
+ ["Cache TTL Seconds", status.cache_ttl_seconds],
31568
+ ["Cache Fetched At", status.cache_fetched_at],
31569
+ ["Cache Age Seconds", status.cache_age_seconds],
31570
+ ["Can Sync", status.can_sync ? "yes" : "no"],
31571
+ ["Challenge Count", status.challenge_count],
31572
+ ["Last Error", status.last_error]
31573
+ ])
31574
+ ].join("\n");
31575
+ }
31576
+ async function handleGetCatalogStatus() {
31577
+ try {
31578
+ const status = await getCatalogStatus();
31579
+ return buildTextResult(formatCatalogStatus(status), {
31580
+ catalog: status
31581
+ });
31582
+ } catch (error48) {
31583
+ return createInternalError(error48);
31584
+ }
31585
+ }
31586
+ async function handleSyncCatalog(params) {
31587
+ try {
31588
+ const result = await syncCatalog(params.force);
31589
+ const status = result.status;
31590
+ return buildTextResult(
31591
+ [
31592
+ "# Catalog Synced",
31593
+ "",
31594
+ formatFields([
31595
+ ["Mode", status.mode],
31596
+ ["Configured URL", status.configured_url],
31597
+ ["Cache File", status.cache_file],
31598
+ ["Challenge Count", status.challenge_count],
31599
+ ["Last Error", status.last_error]
31600
+ ])
31601
+ ].join("\n"),
31602
+ {
31603
+ catalog: status,
31604
+ synced_challenges: result.challenges.map(
31605
+ (challenge) => challenge.challenge_id
31606
+ )
31607
+ }
31608
+ );
31609
+ } catch (error48) {
31610
+ if (error48 instanceof Error && error48.message.includes("BENCHMARK_CATALOG_URL")) {
31611
+ return createValidationError(
31612
+ "BENCHMARK_CATALOG_URL",
31613
+ "configure a remote catalog URL before syncing"
31614
+ );
31615
+ }
31616
+ return createInternalError(error48);
31617
+ }
31618
+ }
30848
31619
  async function handleListChallenges(params) {
30849
31620
  try {
30850
- const challenges = await listAllChallenges();
31621
+ const challenges = await listChallenges();
30851
31622
  const paginated = applyPagination(challenges, params);
30852
31623
  const guidance = paginated.items.length > 0 ? createGuidanceStep(
30853
31624
  "start_challenge",
@@ -30860,7 +31631,7 @@ async function handleListChallenges(params) {
30860
31631
  "No benchmark suites are currently available."
30861
31632
  );
30862
31633
  const markdown = [
30863
- "# Benchmark Challenges",
31634
+ "# Deterministic Benchmark Challenges",
30864
31635
  "",
30865
31636
  formatListItems(
30866
31637
  paginated.items,
@@ -30896,29 +31667,18 @@ async function handleListChallenges(params) {
30896
31667
  }
30897
31668
  async function handleStartChallenge(params) {
30898
31669
  try {
30899
- const adapter = findAdapterForChallengeId(params.challenge_id);
30900
- if (!adapter) {
30901
- return createNotFoundError(`challenge ${params.challenge_id}`);
30902
- }
30903
- const challenge = await adapter.getChallengeDefinition(
30904
- params.challenge_id
30905
- );
31670
+ const challenge = await getChallengeDefinition(params.challenge_id);
30906
31671
  if (!challenge) {
30907
31672
  return createNotFoundError(`challenge ${params.challenge_id}`);
30908
31673
  }
30909
31674
  const attempt = await createAttempt(challenge);
30910
- const currentQuestion = getCurrentQuestion(attempt);
30911
- if (!currentQuestion) {
31675
+ const currentTask = getCurrentTask(attempt);
31676
+ if (!currentTask) {
30912
31677
  return createInternalError(
30913
- new Error(
30914
- `Challenge ${challenge.challenge_id} has no questions.`
30915
- )
31678
+ new Error(`Challenge ${challenge.challenge_id} has no tasks.`)
30916
31679
  );
30917
31680
  }
30918
- const guidance = getQuestionGuidance(
30919
- attempt.attempt_id,
30920
- currentQuestion
30921
- );
31681
+ const guidance = getTaskGuidance(attempt.attempt_id, currentTask);
30922
31682
  return buildTextResult(
30923
31683
  [
30924
31684
  `# ${challenge.title}`,
@@ -30926,15 +31686,17 @@ async function handleStartChallenge(params) {
30926
31686
  formatFields([
30927
31687
  ["Attempt ID", attempt.attempt_id],
30928
31688
  ["Benchmark", attempt.benchmark_id],
31689
+ ["Family", attempt.family],
31690
+ ["Runner", attempt.runner_kind],
30929
31691
  ["Version", attempt.dataset_version],
30930
- ["Questions", attempt.total_questions]
31692
+ ["Tasks", attempt.total_tasks]
30931
31693
  ]),
30932
31694
  "",
30933
- formatQuestionBlock(
31695
+ formatTaskBlock(
30934
31696
  attempt.attempt_id,
30935
31697
  1,
30936
- attempt.total_questions,
30937
- currentQuestion
31698
+ attempt.total_tasks,
31699
+ currentTask
30938
31700
  )
30939
31701
  ].join("\n"),
30940
31702
  {
@@ -30942,9 +31704,14 @@ async function handleStartChallenge(params) {
30942
31704
  challenge_id: attempt.challenge_id,
30943
31705
  benchmark_id: attempt.benchmark_id,
30944
31706
  challenge_title: attempt.challenge_title,
31707
+ family: attempt.family,
31708
+ runner_kind: attempt.runner_kind,
31709
+ task_index: 1,
30945
31710
  question_index: 1,
30946
- total_questions: attempt.total_questions,
30947
- question: toPublicQuestion(currentQuestion),
31711
+ total_tasks: attempt.total_tasks,
31712
+ total_questions: attempt.total_tasks,
31713
+ task: toPublicTask(currentTask),
31714
+ question: toPublicTask(currentTask),
30948
31715
  done: false,
30949
31716
  guidance
30950
31717
  }
@@ -30965,26 +31732,39 @@ async function handleSubmitSolution(params) {
30965
31732
  `attempt is ${attempt.status} and cannot accept submissions`
30966
31733
  );
30967
31734
  }
30968
- const currentQuestion = getCurrentQuestion(attempt);
30969
- if (!currentQuestion) {
31735
+ const currentTask = getCurrentTask(attempt);
31736
+ if (!currentTask) {
30970
31737
  return createInternalError(
30971
- new Error(
30972
- `Attempt ${attempt.attempt_id} has no current question.`
30973
- )
31738
+ new Error(`Attempt ${attempt.attempt_id} has no current task.`)
30974
31739
  );
30975
31740
  }
30976
- const accepted = gradeQuestion(currentQuestion, params.solution);
30977
- const scoreDelta = accepted ? 1 : 0;
30978
- const updatedAttempt = await appendSubmission(attempt, {
30979
- question_id: currentQuestion.question_id,
30980
- solution: params.solution,
30981
- accepted,
30982
- score_delta: scoreDelta,
30983
- submitted_at: (/* @__PURE__ */ new Date()).toISOString()
30984
- });
31741
+ const execution = await executeTaskRunner(
31742
+ attempt,
31743
+ currentTask,
31744
+ params.solution
31745
+ );
31746
+ const evaluation = evaluateTask(
31747
+ currentTask,
31748
+ params.solution,
31749
+ execution
31750
+ );
31751
+ const scoreDelta = evaluation.accepted ? 1 : 0;
31752
+ const updatedAttempt = await appendEvaluation(
31753
+ attempt.attempt_id,
31754
+ currentTask.task_id,
31755
+ {
31756
+ task_id: currentTask.task_id,
31757
+ solution: params.solution,
31758
+ accepted: evaluation.accepted,
31759
+ score_delta: scoreDelta,
31760
+ submitted_at: (/* @__PURE__ */ new Date()).toISOString(),
31761
+ evidence: evaluation.evidence,
31762
+ execution
31763
+ }
31764
+ );
30985
31765
  const totalScore = sumScore(updatedAttempt);
30986
- const nextQuestion = getCurrentQuestion(updatedAttempt);
30987
- if (!nextQuestion) {
31766
+ const nextTask = getCurrentTask(updatedAttempt);
31767
+ if (!nextTask) {
30988
31768
  const finalGuidance = {
30989
31769
  primary: createGuidanceStep(
30990
31770
  "complete",
@@ -30996,25 +31776,37 @@ async function handleSubmitSolution(params) {
30996
31776
  "inspect_attempt",
30997
31777
  "benchmark_get_attempt",
30998
31778
  "Review the completed attempt summary.",
30999
- { attempt_id: updatedAttempt.attempt_id }
31779
+ {
31780
+ attempt_id: updatedAttempt.attempt_id,
31781
+ limit: "20",
31782
+ offset: "0"
31783
+ }
31000
31784
  )
31001
31785
  ]
31002
31786
  };
31787
+ const evidenceLines2 = formatListItems(
31788
+ evaluation.evidence,
31789
+ (entry) => `- ${entry.passed ? "pass" : "fail"} \`${entry.checker_id}\`: ${entry.message}`
31790
+ );
31003
31791
  return buildTextResult(
31004
31792
  [
31005
31793
  "# Challenge Complete",
31006
31794
  "",
31007
31795
  formatFields([
31008
31796
  ["Attempt ID", updatedAttempt.attempt_id],
31009
- ["Accepted", accepted ? "yes" : "no"],
31797
+ ["Accepted", evaluation.accepted ? "yes" : "no"],
31010
31798
  ["Score Delta", scoreDelta],
31011
31799
  [
31012
31800
  "Total Score",
31013
- `${totalScore}/${updatedAttempt.total_questions}`
31801
+ `${totalScore}/${updatedAttempt.total_tasks}`
31014
31802
  ]
31015
31803
  ]),
31016
31804
  "",
31017
- accepted ? "The final answer was accepted." : "The final answer was not accepted.",
31805
+ "## Checker Evidence",
31806
+ "",
31807
+ evidenceLines2,
31808
+ "",
31809
+ formatRunnerExecution(execution),
31018
31810
  "",
31019
31811
  "## Next Step",
31020
31812
  "",
@@ -31024,22 +31816,25 @@ async function handleSubmitSolution(params) {
31024
31816
  ].join("\n"),
31025
31817
  {
31026
31818
  attempt_id: updatedAttempt.attempt_id,
31027
- accepted,
31819
+ accepted: evaluation.accepted,
31028
31820
  score_delta: scoreDelta,
31029
31821
  total_score: totalScore,
31030
31822
  done: true,
31823
+ evidence: evaluation.evidence,
31824
+ execution,
31031
31825
  final_result: {
31032
31826
  total_score: totalScore,
31033
- max_score: updatedAttempt.total_questions,
31034
- passed: totalScore === updatedAttempt.total_questions
31827
+ max_score: updatedAttempt.total_tasks,
31828
+ passed: totalScore === updatedAttempt.total_tasks
31035
31829
  },
31036
31830
  guidance: finalGuidance
31037
31831
  }
31038
31832
  );
31039
31833
  }
31040
- const guidance = getQuestionGuidance(
31041
- updatedAttempt.attempt_id,
31042
- nextQuestion
31834
+ const guidance = getTaskGuidance(updatedAttempt.attempt_id, nextTask);
31835
+ const evidenceLines = formatListItems(
31836
+ evaluation.evidence,
31837
+ (entry) => `- ${entry.passed ? "pass" : "fail"} \`${entry.checker_id}\`: ${entry.message}`
31043
31838
  );
31044
31839
  return buildTextResult(
31045
31840
  [
@@ -31047,31 +31842,40 @@ async function handleSubmitSolution(params) {
31047
31842
  "",
31048
31843
  formatFields([
31049
31844
  ["Attempt ID", updatedAttempt.attempt_id],
31050
- ["Accepted", accepted ? "yes" : "no"],
31845
+ ["Accepted", evaluation.accepted ? "yes" : "no"],
31051
31846
  ["Score Delta", scoreDelta],
31052
31847
  [
31053
31848
  "Total Score",
31054
- `${totalScore}/${updatedAttempt.total_questions}`
31849
+ `${totalScore}/${updatedAttempt.total_tasks}`
31055
31850
  ]
31056
31851
  ]),
31057
31852
  "",
31058
- accepted ? "Answer accepted. Moving to the next question." : "Answer recorded. Moving to the next question.",
31853
+ "## Checker Evidence",
31854
+ "",
31855
+ evidenceLines,
31856
+ "",
31857
+ formatRunnerExecution(execution),
31059
31858
  "",
31060
- formatQuestionBlock(
31859
+ formatTaskBlock(
31061
31860
  updatedAttempt.attempt_id,
31062
- updatedAttempt.current_question_index + 1,
31063
- updatedAttempt.total_questions,
31064
- nextQuestion
31861
+ updatedAttempt.current_task_index + 1,
31862
+ updatedAttempt.total_tasks,
31863
+ nextTask
31065
31864
  )
31066
31865
  ].join("\n"),
31067
31866
  {
31068
31867
  attempt_id: updatedAttempt.attempt_id,
31069
- accepted,
31868
+ accepted: evaluation.accepted,
31070
31869
  score_delta: scoreDelta,
31071
31870
  total_score: totalScore,
31072
- question_index: updatedAttempt.current_question_index + 1,
31073
- total_questions: updatedAttempt.total_questions,
31074
- next_question: toPublicQuestion(nextQuestion),
31871
+ task_index: updatedAttempt.current_task_index + 1,
31872
+ question_index: updatedAttempt.current_task_index + 1,
31873
+ total_tasks: updatedAttempt.total_tasks,
31874
+ total_questions: updatedAttempt.total_tasks,
31875
+ evidence: evaluation.evidence,
31876
+ execution,
31877
+ next_task: toPublicTask(nextTask),
31878
+ next_question: toPublicTask(nextTask),
31075
31879
  done: false,
31076
31880
  guidance
31077
31881
  }
@@ -31086,15 +31890,15 @@ async function handleGetAsset(params) {
31086
31890
  if (!attempt) {
31087
31891
  return createNotFoundError(`attempt ${params.attempt_id}`);
31088
31892
  }
31089
- const question = attempt.questions.find(
31893
+ const task = attempt.tasks.find(
31090
31894
  (candidate) => candidate.assets.some(
31091
31895
  (asset2) => asset2.asset_id === params.asset_id
31092
31896
  )
31093
31897
  );
31094
- const asset = question?.assets.find(
31898
+ const asset = task?.assets.find(
31095
31899
  (candidate) => candidate.asset_id === params.asset_id
31096
31900
  );
31097
- if (!asset || !question) {
31901
+ if (!asset || !task) {
31098
31902
  return createNotFoundError(`asset ${params.asset_id}`);
31099
31903
  }
31100
31904
  const guidance = {
@@ -31104,15 +31908,19 @@ async function handleGetAsset(params) {
31104
31908
  "Submit the answer after reviewing the asset.",
31105
31909
  {
31106
31910
  attempt_id: attempt.attempt_id,
31107
- solution: "your final answer"
31911
+ solution: createSubmitExample(task)
31108
31912
  }
31109
31913
  ),
31110
31914
  alternatives: [
31111
31915
  createGuidanceStep(
31112
31916
  "inspect_attempt",
31113
31917
  "benchmark_get_attempt",
31114
- "Review the current question again.",
31115
- { attempt_id: attempt.attempt_id }
31918
+ "Review the current task again.",
31919
+ {
31920
+ attempt_id: attempt.attempt_id,
31921
+ limit: "20",
31922
+ offset: "0"
31923
+ }
31116
31924
  )
31117
31925
  ]
31118
31926
  };
@@ -31122,8 +31930,10 @@ async function handleGetAsset(params) {
31122
31930
  "",
31123
31931
  formatFields([
31124
31932
  ["Attempt ID", attempt.attempt_id],
31125
- ["Question ID", question.question_id],
31933
+ ["Task ID", task.task_id],
31126
31934
  ["Mime Type", asset.mime_type],
31935
+ ["Transport", asset.transport],
31936
+ ["Materialization", asset.materialization],
31127
31937
  ["Encoding", asset.encoding]
31128
31938
  ]),
31129
31939
  "",
@@ -31143,6 +31953,8 @@ async function handleGetAsset(params) {
31143
31953
  asset_id: asset.asset_id,
31144
31954
  name: asset.name,
31145
31955
  mime_type: asset.mime_type,
31956
+ transport: asset.transport,
31957
+ materialization: asset.materialization,
31146
31958
  encoding: asset.encoding,
31147
31959
  description: asset.description,
31148
31960
  content: asset.content
@@ -31160,8 +31972,9 @@ async function handleGetAttempt(params) {
31160
31972
  if (!attempt) {
31161
31973
  return createNotFoundError(`attempt ${params.attempt_id}`);
31162
31974
  }
31163
- const currentQuestion = getCurrentQuestion(attempt);
31164
- const guidance = currentQuestion && attempt.status === "active" ? getQuestionGuidance(attempt.attempt_id, currentQuestion) : {
31975
+ const currentTask = getCurrentTask(attempt);
31976
+ const evaluationPage = applyPagination(attempt.evaluations, params);
31977
+ const guidance = currentTask && attempt.status === "active" ? getTaskGuidance(attempt.attempt_id, currentTask) : {
31165
31978
  primary: createGuidanceStep(
31166
31979
  "complete",
31167
31980
  null,
@@ -31169,6 +31982,27 @@ async function handleGetAttempt(params) {
31169
31982
  ),
31170
31983
  alternatives: []
31171
31984
  };
31985
+ const historyText = formatListItems(
31986
+ evaluationPage.items,
31987
+ (evaluation, index) => {
31988
+ const evidenceLines = evaluation.evidence.map(
31989
+ (entry) => ` - ${entry.passed ? "pass" : "fail"} \`${entry.checker_id}\`: ${entry.message}`
31990
+ ).join("\n");
31991
+ return [
31992
+ `### Evaluation ${evaluationPage.offset + index + 1}`,
31993
+ formatFields([
31994
+ ["Task ID", evaluation.task_id],
31995
+ ["Accepted", evaluation.accepted ? "yes" : "no"],
31996
+ ["Score Delta", evaluation.score_delta]
31997
+ ]),
31998
+ "",
31999
+ formatRunnerExecution(evaluation.execution),
32000
+ "",
32001
+ evidenceLines
32002
+ ].join("\n");
32003
+ },
32004
+ "_No submissions recorded yet._"
32005
+ );
31172
32006
  return buildTextResult(
31173
32007
  [
31174
32008
  "# Attempt Status",
@@ -31176,32 +32010,50 @@ async function handleGetAttempt(params) {
31176
32010
  formatFields([
31177
32011
  ["Attempt ID", attempt.attempt_id],
31178
32012
  ["Challenge", attempt.challenge_title],
32013
+ ["Family", attempt.family],
32014
+ ["Runner", attempt.runner_kind],
31179
32015
  ["Status", attempt.status],
32016
+ ["Revision", attempt.revision],
31180
32017
  [
31181
32018
  "Progress",
31182
- `${attempt.current_question_index}/${attempt.total_questions}`
32019
+ `${attempt.current_task_index}/${attempt.total_tasks}`
31183
32020
  ],
31184
- [
31185
- "Score",
31186
- `${sumScore(attempt)}/${attempt.total_questions}`
31187
- ]
32021
+ ["Score", `${sumScore(attempt)}/${attempt.total_tasks}`]
31188
32022
  ]),
31189
32023
  "",
31190
- currentQuestion ? formatQuestionBlock(
32024
+ currentTask ? formatTaskBlock(
31191
32025
  attempt.attempt_id,
31192
- attempt.current_question_index + 1,
31193
- attempt.total_questions,
31194
- currentQuestion
31195
- ) : "No current question is available."
31196
- ].join("\n"),
32026
+ attempt.current_task_index + 1,
32027
+ attempt.total_tasks,
32028
+ currentTask
32029
+ ) : "No current task is available.",
32030
+ "",
32031
+ "## Evaluation History",
32032
+ "",
32033
+ historyText,
32034
+ evaluationPage.hasMore ? formatPaginationFooter(
32035
+ evaluationPage.offset,
32036
+ evaluationPage.limit,
32037
+ evaluationPage.total
32038
+ ) : ""
32039
+ ].filter(Boolean).join("\n"),
31197
32040
  {
31198
32041
  attempt_id: attempt.attempt_id,
31199
32042
  challenge_id: attempt.challenge_id,
31200
32043
  status: attempt.status,
31201
32044
  total_score: sumScore(attempt),
31202
- total_questions: attempt.total_questions,
31203
- current_question_index: attempt.current_question_index,
31204
- current_question: currentQuestion ? toPublicQuestion(currentQuestion) : null,
32045
+ total_tasks: attempt.total_tasks,
32046
+ total_questions: attempt.total_tasks,
32047
+ current_task_index: attempt.current_task_index,
32048
+ current_question_index: attempt.current_task_index,
32049
+ current_task: currentTask ? toPublicTask(currentTask) : null,
32050
+ current_question: currentTask ? toPublicTask(currentTask) : null,
32051
+ evaluations: evaluationPage.items,
32052
+ history: evaluationPage.items,
32053
+ history_total: evaluationPage.total,
32054
+ offset: evaluationPage.offset,
32055
+ limit: evaluationPage.limit,
32056
+ has_more: evaluationPage.hasMore,
31205
32057
  guidance
31206
32058
  }
31207
32059
  );
@@ -31221,7 +32073,7 @@ async function handleCancelAttempt(params) {
31221
32073
  `attempt is already ${attempt.status}`
31222
32074
  );
31223
32075
  }
31224
- const cancelled = await cancelAttempt(attempt);
32076
+ const cancelled = await cancelAttempt(attempt.attempt_id);
31225
32077
  return buildTextResult(
31226
32078
  [
31227
32079
  "# Attempt Cancelled",
@@ -31229,7 +32081,8 @@ async function handleCancelAttempt(params) {
31229
32081
  formatFields([
31230
32082
  ["Attempt ID", cancelled.attempt_id],
31231
32083
  ["Challenge", cancelled.challenge_title],
31232
- ["Status", cancelled.status]
32084
+ ["Status", cancelled.status],
32085
+ ["Revision", cancelled.revision]
31233
32086
  ])
31234
32087
  ].join("\n"),
31235
32088
  {
@@ -31253,7 +32106,7 @@ server.registerTool(
31253
32106
  "benchmark_list_challenges",
31254
32107
  {
31255
32108
  title: "List Challenges",
31256
- description: "List benchmark suites and challenge metadata.",
32109
+ description: "List deterministic benchmark suites and metadata.",
31257
32110
  inputSchema: listChallengesInputSchema,
31258
32111
  annotations: {
31259
32112
  readOnlyHint: true,
@@ -31262,11 +32115,36 @@ server.registerTool(
31262
32115
  },
31263
32116
  handleListChallenges
31264
32117
  );
32118
+ server.registerTool(
32119
+ "benchmark_get_catalog_status",
32120
+ {
32121
+ title: "Get Catalog Status",
32122
+ description: "Inspect catalog source and cache status.",
32123
+ inputSchema: getCatalogStatusInputSchema,
32124
+ annotations: {
32125
+ readOnlyHint: true,
32126
+ idempotentHint: true
32127
+ }
32128
+ },
32129
+ handleGetCatalogStatus
32130
+ );
32131
+ server.registerTool(
32132
+ "benchmark_sync_catalog",
32133
+ {
32134
+ title: "Sync Catalog",
32135
+ description: "Fetch and cache the remote benchmark catalog.",
32136
+ inputSchema: syncCatalogInputSchema,
32137
+ annotations: {
32138
+ idempotentHint: true
32139
+ }
32140
+ },
32141
+ handleSyncCatalog
32142
+ );
31265
32143
  server.registerTool(
31266
32144
  "benchmark_start_challenge",
31267
32145
  {
31268
32146
  title: "Start Challenge",
31269
- description: "Start an attempt and return the first question.",
32147
+ description: "Start an attempt and return the first task.",
31270
32148
  inputSchema: startChallengeInputSchema
31271
32149
  },
31272
32150
  handleStartChallenge
@@ -31275,7 +32153,7 @@ server.registerTool(
31275
32153
  "benchmark_submit_solution",
31276
32154
  {
31277
32155
  title: "Submit Solution",
31278
- description: "Grade one answer and advance the attempt.",
32156
+ description: "Grade one task and advance the attempt.",
31279
32157
  inputSchema: submitSolutionInputSchema
31280
32158
  },
31281
32159
  handleSubmitSolution
@@ -31297,7 +32175,7 @@ server.registerTool(
31297
32175
  "benchmark_get_attempt",
31298
32176
  {
31299
32177
  title: "Get Attempt",
31300
- description: "Inspect attempt status and current question.",
32178
+ description: "Inspect attempt status, tasks, and evidence.",
31301
32179
  inputSchema: getAttemptInputSchema,
31302
32180
  annotations: {
31303
32181
  readOnlyHint: true,
@@ -31310,24 +32188,19 @@ server.registerTool(
31310
32188
  "benchmark_cancel_attempt",
31311
32189
  {
31312
32190
  title: "Cancel Attempt",
31313
- description: "Cancel an active benchmark attempt.",
32191
+ description: "Cancel an active attempt.",
31314
32192
  inputSchema: cancelAttemptInputSchema
31315
32193
  },
31316
32194
  handleCancelAttempt
31317
32195
  );
31318
32196
  async function main() {
31319
- if (IS_MOCK) {
31320
- console.error(
31321
- "[benchmark] Running in MOCK mode \u2014 using built-in benchmark fixtures."
31322
- );
31323
- }
31324
32197
  const transport = new StdioServerTransport();
31325
32198
  await server.connect(transport);
31326
32199
  console.error("[benchmark] MCP server running via stdio");
31327
32200
  }
31328
- if (process.env.NODE_ENV !== "test") {
32201
+ if (process.argv[1] && import.meta.url.endsWith(process.argv[1])) {
31329
32202
  main().catch((error48) => {
31330
- console.error("Server error:", error48);
32203
+ console.error("[benchmark] fatal error", error48);
31331
32204
  process.exit(1);
31332
32205
  });
31333
32206
  }
@@ -31335,8 +32208,10 @@ export {
31335
32208
  handleCancelAttempt,
31336
32209
  handleGetAsset,
31337
32210
  handleGetAttempt,
32211
+ handleGetCatalogStatus,
31338
32212
  handleListChallenges,
31339
32213
  handleStartChallenge,
31340
32214
  handleSubmitSolution,
32215
+ handleSyncCatalog,
31341
32216
  server
31342
32217
  };