bitfab-cli 0.2.227 → 0.2.229

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 (2) hide show
  1. package/dist/index.js +131 -103
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4974,7 +4974,7 @@ var require_compile = __commonJS({
4974
4974
  const schOrFunc = root.refs[ref];
4975
4975
  if (schOrFunc)
4976
4976
  return schOrFunc;
4977
- let _sch = resolve2.call(this, root, ref);
4977
+ let _sch = resolve4.call(this, root, ref);
4978
4978
  if (_sch === void 0) {
4979
4979
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
4980
4980
  const { schemaId } = this.opts;
@@ -5001,7 +5001,7 @@ var require_compile = __commonJS({
5001
5001
  function sameSchemaEnv(s1, s2) {
5002
5002
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
5003
5003
  }
5004
- function resolve2(root, ref) {
5004
+ function resolve4(root, ref) {
5005
5005
  let sch;
5006
5006
  while (typeof (sch = this.refs[ref]) == "string")
5007
5007
  ref = sch;
@@ -5632,7 +5632,7 @@ var require_fast_uri = __commonJS({
5632
5632
  }
5633
5633
  return uri;
5634
5634
  }
5635
- function resolve2(baseURI, relativeURI, options) {
5635
+ function resolve4(baseURI, relativeURI, options) {
5636
5636
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
5637
5637
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
5638
5638
  schemelessOptions.skipEscape = true;
@@ -5890,7 +5890,7 @@ var require_fast_uri = __commonJS({
5890
5890
  var fastUri = {
5891
5891
  SCHEMES,
5892
5892
  normalize,
5893
- resolve: resolve2,
5893
+ resolve: resolve4,
5894
5894
  resolveComponent,
5895
5895
  equal,
5896
5896
  serialize: serialize2,
@@ -9028,14 +9028,14 @@ async function pollAgentSessionEvents(opts) {
9028
9028
  opts.onError(err instanceof Error ? err : new Error(String(err)));
9029
9029
  }
9030
9030
  }
9031
- await new Promise((resolve2) => {
9031
+ await new Promise((resolve4) => {
9032
9032
  const onAbort = () => {
9033
9033
  clearTimeout(timer);
9034
- resolve2();
9034
+ resolve4();
9035
9035
  };
9036
9036
  const timer = setTimeout(() => {
9037
9037
  opts.abortSignal.removeEventListener("abort", onAbort);
9038
- resolve2();
9038
+ resolve4();
9039
9039
  }, interval);
9040
9040
  opts.abortSignal.addEventListener("abort", onAbort, { once: true });
9041
9041
  });
@@ -9097,9 +9097,9 @@ var PING_PONG_TIMEOUT_MS = 5e3;
9097
9097
  function awaitNavigateAck(client, path21, ackTimeoutMs, startCursor) {
9098
9098
  const abortController = new AbortController();
9099
9099
  let timer = null;
9100
- const result = new Promise((resolve2) => {
9100
+ const result = new Promise((resolve4) => {
9101
9101
  timer = setTimeout(() => {
9102
- resolve2({ acked: false, reason: "timeout" });
9102
+ resolve4({ acked: false, reason: "timeout" });
9103
9103
  abortController.abort();
9104
9104
  }, ackTimeoutMs);
9105
9105
  const pathOnly = path21.split("?")[0];
@@ -9119,7 +9119,7 @@ function awaitNavigateAck(client, path21, ackTimeoutMs, startCursor) {
9119
9119
  if (timer) {
9120
9120
  clearTimeout(timer);
9121
9121
  }
9122
- resolve2({ acked: true });
9122
+ resolve4({ acked: true });
9123
9123
  abortController.abort();
9124
9124
  return;
9125
9125
  }
@@ -9127,7 +9127,7 @@ function awaitNavigateAck(client, path21, ackTimeoutMs, startCursor) {
9127
9127
  if (timer) {
9128
9128
  clearTimeout(timer);
9129
9129
  }
9130
- resolve2({
9130
+ resolve4({
9131
9131
  acked: false,
9132
9132
  reason: "blocked",
9133
9133
  blockedReason: event.data.reason
@@ -9139,7 +9139,7 @@ function awaitNavigateAck(client, path21, ackTimeoutMs, startCursor) {
9139
9139
  if (timer) {
9140
9140
  clearTimeout(timer);
9141
9141
  }
9142
- resolve2({ acked: false, reason: "window-closed" });
9142
+ resolve4({ acked: false, reason: "window-closed" });
9143
9143
  abortController.abort();
9144
9144
  }
9145
9145
  },
@@ -9152,18 +9152,18 @@ function awaitNavigateAck(client, path21, ackTimeoutMs, startCursor) {
9152
9152
  if (timer) {
9153
9153
  clearTimeout(timer);
9154
9154
  }
9155
- resolve2({ acked: false, reason: "timeout" });
9155
+ resolve4({ acked: false, reason: "timeout" });
9156
9156
  });
9157
9157
  });
9158
9158
  return result;
9159
9159
  }
9160
9160
  function awaitPong(client, pingTs) {
9161
- return new Promise((resolve2) => {
9161
+ return new Promise((resolve4) => {
9162
9162
  const abortController = new AbortController();
9163
9163
  let received = false;
9164
9164
  const timer = setTimeout(() => {
9165
9165
  abortController.abort();
9166
- resolve2(false);
9166
+ resolve4(false);
9167
9167
  }, PING_PONG_TIMEOUT_MS);
9168
9168
  pollAgentSessionEvents({
9169
9169
  serviceUrl: client.serviceUrl,
@@ -9175,7 +9175,7 @@ function awaitPong(client, pingTs) {
9175
9175
  received = true;
9176
9176
  clearTimeout(timer);
9177
9177
  abortController.abort();
9178
- resolve2(true);
9178
+ resolve4(true);
9179
9179
  }
9180
9180
  },
9181
9181
  onError: () => {
@@ -9183,7 +9183,7 @@ function awaitPong(client, pingTs) {
9183
9183
  }).catch(() => {
9184
9184
  if (!received) {
9185
9185
  clearTimeout(timer);
9186
- resolve2(false);
9186
+ resolve4(false);
9187
9187
  }
9188
9188
  });
9189
9189
  });
@@ -10413,10 +10413,10 @@ function readPid(pidPath) {
10413
10413
  }
10414
10414
  }
10415
10415
  async function pingSocket(socketPath) {
10416
- return new Promise((resolve2) => {
10416
+ return new Promise((resolve4) => {
10417
10417
  const timeout = setTimeout(() => {
10418
10418
  socket.destroy();
10419
- resolve2({ alive: false, build: null });
10419
+ resolve4({ alive: false, build: null });
10420
10420
  }, PING_TIMEOUT_MS);
10421
10421
  const socket = net.createConnection(socketPath, () => {
10422
10422
  socket.write('{"cmd":"ping"}\n');
@@ -10429,15 +10429,15 @@ async function pingSocket(socketPath) {
10429
10429
  socket.destroy();
10430
10430
  try {
10431
10431
  const msg = JSON.parse(buffer.split("\n")[0]);
10432
- resolve2({ alive: msg.ok === true, build: msg.build ?? null });
10432
+ resolve4({ alive: msg.ok === true, build: msg.build ?? null });
10433
10433
  } catch {
10434
- resolve2({ alive: false, build: null });
10434
+ resolve4({ alive: false, build: null });
10435
10435
  }
10436
10436
  }
10437
10437
  });
10438
10438
  socket.on("error", () => {
10439
10439
  clearTimeout(timeout);
10440
- resolve2({ alive: false, build: null });
10440
+ resolve4({ alive: false, build: null });
10441
10441
  });
10442
10442
  });
10443
10443
  }
@@ -10668,10 +10668,10 @@ var DaemonClient = class {
10668
10668
  }
10669
10669
  async connect() {
10670
10670
  await ensureDaemon(this.paths);
10671
- return new Promise((resolve2, reject) => {
10671
+ return new Promise((resolve4, reject) => {
10672
10672
  const socket = net2.createConnection(this.socketPath, () => {
10673
10673
  this.socket = socket;
10674
- resolve2();
10674
+ resolve4();
10675
10675
  });
10676
10676
  socket.on("data", (chunk2) => {
10677
10677
  this.buffer += chunk2.toString();
@@ -10725,7 +10725,7 @@ var DaemonClient = class {
10725
10725
  }
10726
10726
  const timeoutMs = opts.timeoutMs ?? this.commandTimeoutMs;
10727
10727
  const cmdName = cmd.cmd ?? "command";
10728
- return new Promise((resolve2, reject) => {
10728
+ return new Promise((resolve4, reject) => {
10729
10729
  let settled = false;
10730
10730
  const timer = setTimeout(() => {
10731
10731
  if (settled) {
@@ -10752,7 +10752,7 @@ var DaemonClient = class {
10752
10752
  }
10753
10753
  settled = true;
10754
10754
  clearTimeout(timer);
10755
- resolve2(msg);
10755
+ resolve4(msg);
10756
10756
  };
10757
10757
  this.responseQueue.push(resolver);
10758
10758
  try {
@@ -10926,9 +10926,9 @@ function readStudioEventLogTail(key, fromLine = 1) {
10926
10926
  function tailStudioEvents(key, onLine, opts) {
10927
10927
  const pollMs = opts.pollMs ?? 200;
10928
10928
  let cursor = opts.fromLine ?? 1;
10929
- return new Promise((resolve2) => {
10929
+ return new Promise((resolve4) => {
10930
10930
  if (opts.abortSignal.aborted) {
10931
- resolve2();
10931
+ resolve4();
10932
10932
  return;
10933
10933
  }
10934
10934
  const tick = () => {
@@ -10948,7 +10948,7 @@ function tailStudioEvents(key, onLine, opts) {
10948
10948
  }, pollMs);
10949
10949
  opts.abortSignal.addEventListener("abort", () => {
10950
10950
  clearInterval(timer);
10951
- resolve2();
10951
+ resolve4();
10952
10952
  }, { once: true });
10953
10953
  tick();
10954
10954
  });
@@ -11134,8 +11134,8 @@ var DaemonChannel = class _DaemonChannel {
11134
11134
  });
11135
11135
  }
11136
11136
  subscribe(_sessionId, onEvent, onError) {
11137
- const done = new Promise((resolve2) => {
11138
- this.doneResolve = resolve2;
11137
+ const done = new Promise((resolve4) => {
11138
+ this.doneResolve = resolve4;
11139
11139
  });
11140
11140
  this.client.onEvent((push) => {
11141
11141
  const event = {
@@ -11718,7 +11718,7 @@ async function openStudioTo(path21, opts = {}) {
11718
11718
  emit({ event: "navigated", sessionId: sessionId2, path: buildSignInPath(sessionId2) });
11719
11719
  }
11720
11720
  emit({ event: "auth-required", sessionId: sessionId2, signInUrl });
11721
- const loginApiKey = await new Promise((resolve2, reject) => {
11721
+ const loginApiKey = await new Promise((resolve4, reject) => {
11722
11722
  const timer = setTimeout(() => {
11723
11723
  sub.abort();
11724
11724
  reject(new Error("Login polling aborted"));
@@ -11732,7 +11732,7 @@ async function openStudioTo(path21, opts = {}) {
11732
11732
  if (event.type === "studio:authenticated" && typeof event.data?.token === "string") {
11733
11733
  cleanup();
11734
11734
  sub.abort();
11735
- resolve2(event.data.token);
11735
+ resolve4(event.data.token);
11736
11736
  }
11737
11737
  }, (err) => {
11738
11738
  cleanup();
@@ -26433,11 +26433,21 @@ import net3 from "net";
26433
26433
 
26434
26434
  // ../bitfab-plugin-lib/dist/commands/replayProgress.js
26435
26435
  import { spawn as spawn4 } from "child_process";
26436
- import { randomUUID } from "crypto";
26437
- import { appendFileSync, createWriteStream, existsSync, mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, statSync, writeFileSync as writeFileSync2 } from "fs";
26438
- import { dirname, join as join2, resolve } from "path";
26436
+ import { createWriteStream, mkdirSync as mkdirSync4, rmSync as rmSync2 } from "fs";
26437
+ import { dirname as dirname3, resolve as resolve3 } from "path";
26439
26438
  import { createInterface } from "readline";
26440
- var HEARTBEAT_MS = Number(process.env.BITFAB_REPLAY_HEARTBEAT_MS) || 12e3;
26439
+
26440
+ // ../bitfab-plugin-lib/dist/replayItemWriter.js
26441
+ import { mkdirSync as mkdirSync2, renameSync, writeFileSync as writeFileSync3 } from "fs";
26442
+ import { join as join2 } from "path";
26443
+
26444
+ // ../bitfab-plugin-lib/dist/bestEffortFs.js
26445
+ import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
26446
+ import { dirname } from "path";
26447
+
26448
+ // ../bitfab-plugin-lib/dist/replayResultResolver.js
26449
+ import { readFileSync as readFileSync2, rmSync } from "fs";
26450
+ import { resolve } from "path";
26441
26451
  var replayResultSchema = external_exports.object({
26442
26452
  items: external_exports.array(external_exports.unknown()),
26443
26453
  testRunId: external_exports.string().optional(),
@@ -26446,6 +26456,14 @@ var replayResultSchema = external_exports.object({
26446
26456
  test_run_url: external_exports.string().optional()
26447
26457
  }).passthrough().refine(({ testRunId, test_run_id }) => testRunId !== void 0 || test_run_id !== void 0);
26448
26458
 
26459
+ // ../bitfab-plugin-lib/dist/replayRunDir.js
26460
+ import { randomUUID } from "crypto";
26461
+ import { appendFileSync, existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync } from "fs";
26462
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
26463
+
26464
+ // ../bitfab-plugin-lib/dist/commands/replayProgress.js
26465
+ var HEARTBEAT_MS = Number(process.env.BITFAB_REPLAY_HEARTBEAT_MS) || 12e3;
26466
+
26449
26467
  // ../bitfab-plugin-lib/dist/activePreviewSession.js
26450
26468
  import fs15 from "fs";
26451
26469
  import os12 from "os";
@@ -27988,8 +28006,8 @@ var dateOnlyStringSchema = external_exports.string().regex(DATE_PATTERN, "Must b
27988
28006
  const date5 = parseDateOnly(value);
27989
28007
  return date5.getFullYear() === year && date5.getMonth() === month - 1 && date5.getDate() === day;
27990
28008
  }, "Must be a valid calendar date");
27991
- var READ_TRACE_LABELS_MAX_IDS = 100;
27992
- var READ_GRADER_LABELS_MAX_IDS = 100;
28009
+ var GET_TRACE_LABELS_MAX_IDS = 100;
28010
+ var GET_GRADER_LABELS_MAX_IDS = 100;
27993
28011
  var DEFAULT_GRADER_LABEL_LIMIT = 50;
27994
28012
  var MAX_GRADER_LABEL_LIMIT = 200;
27995
28013
  function parseJsonString(val) {
@@ -28099,7 +28117,7 @@ var listTraceFunctions = {
28099
28117
  var searchTraces = {
28100
28118
  name: "search_traces",
28101
28119
  title: "Search Traces",
28102
- description: "Search and filter traces for a traced function. Returns matching traces with IDs, status, timestamps, total duration (ms), total token usage (in/out/cached/total), model, and output preview (all when available). Replay traces (from test runs) carry the same totals. Use read_traces for per-span duration, tokens, time-to-first-token, and model. Supports keyword search, date range, status filter, and regex matching. Use drillDown to refine previous results.",
28120
+ description: "Search and filter traces for a traced function. Returns matching traces with IDs, status, timestamps, total duration (ms), total token usage (in/out/cached/total), model, and output preview (all when available). Replay traces (from test runs) carry the same totals. Use get_traces for per-span duration, tokens, time-to-first-token, and model. Supports keyword search, date range, status filter, and regex matching. Use drillDown to refine previous results.",
28103
28121
  inputSchema: {
28104
28122
  traceFunctionKey: external_exports.string().describe("The trace function key to search"),
28105
28123
  searchQuery: external_exports.string().optional().describe("Full-text keyword search"),
@@ -28120,38 +28138,38 @@ var searchTraces = {
28120
28138
  hasDbSnapshot: external_exports.preprocess(parseJsonString, external_exports.boolean()).optional().describe("Filter by whether the trace captured a database snapshot reference. true returns only traces that can be replayed against a pinned historical DB branch (the 'Snapshot captured' traces); false returns only traces with no snapshot. Omit to include both.")
28121
28139
  }
28122
28140
  };
28123
- var readTraces = {
28124
- name: "read_traces",
28125
- title: "Read Traces",
28126
- description: 'Read one or more traces by ID. Includes inputs, outputs, status, environment tag, trace-level duration (ms), token usage (in/out/cached/total), and model (when available). Use these to compare latency and cost between a trace and its replay. Returns span details (input, output, reasoning, content, context, errors) plus per-span duration, tokens, time-to-first-token, and model when available. Use `scope: "summary"` when scanning many traces to keep the response small; `scope: "full"` when you need the complete untruncated detail of a few traces. To load only labels + annotations for many traces at once (no span content), use read_trace_labels instead.',
28141
+ var getTraces = {
28142
+ name: "get_traces",
28143
+ title: "Get Traces",
28144
+ description: 'Read one or more traces by ID. Includes inputs, outputs, status, environment tag, trace-level duration (ms), token usage (in/out/cached/total), and model (when available). Use these to compare latency and cost between a trace and its replay. Returns span details (input, output, reasoning, content, context, errors) plus per-span duration, tokens, time-to-first-token, and model when available. Use `scope: "summary"` when scanning many traces to keep the response small; `scope: "full"` when you need the complete untruncated detail of a few traces. To load only labels + annotations for many traces at once (no span content), use get_trace_labels instead.',
28127
28145
  inputSchema: {
28128
28146
  traceIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(10)).describe("Trace IDs to read (1-10)"),
28129
28147
  scope: external_exports.enum(["summary", "full"]).optional().default("summary").describe('Level of span detail. "summary" (default) renders the same span structure as full but caps each span (its fields share a ~1500-char budget) and each trace-level field (~2000 chars), keeping the response small enough not to spill to disk: prefer it when scanning or identifying candidate traces. "full" renders every field untruncated up to 10k chars each, with no per-span ceiling, so reading several large traces can produce a big response: use it when you need complete detail on a handful of traces.')
28130
28148
  }
28131
28149
  };
28132
- var readTraceLabels = {
28133
- name: "read_trace_labels",
28134
- title: "Read Trace Labels",
28135
- description: `Read just the labels for one or more traces by ID: returns each trace's pass/fail verdict, its annotation (the reviewer's reasoning), and whether the label is human-validated (a human authored or approved it). No span content, inputs, or outputs are loaded, so the response is small and one call accepts up to ${READ_TRACE_LABELS_MAX_IDS} IDs. Use this to load a whole dataset's verdicts into context in a single call (e.g. when building or confirming a dataset). When you need a trace's actual inputs/outputs/spans, use read_traces instead.`,
28150
+ var getTraceLabels = {
28151
+ name: "get_trace_labels",
28152
+ title: "Get Trace Labels",
28153
+ description: `Read just the labels for one or more traces by ID: returns each trace's pass/fail verdict, its annotation (the reviewer's reasoning), and whether the label is human-validated (a human authored or approved it). No span content, inputs, or outputs are loaded, so the response is small and one call accepts up to ${GET_TRACE_LABELS_MAX_IDS} IDs. Use this to load a whole dataset's verdicts into context in a single call (e.g. when building or confirming a dataset). When you need a trace's actual inputs/outputs/spans, use get_traces instead.`,
28136
28154
  inputSchema: {
28137
- traceIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(READ_TRACE_LABELS_MAX_IDS)).describe(`Trace IDs to read labels for (1-${READ_TRACE_LABELS_MAX_IDS})`)
28155
+ traceIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(GET_TRACE_LABELS_MAX_IDS)).describe(`Trace IDs to read labels for (1-${GET_TRACE_LABELS_MAX_IDS})`)
28138
28156
  }
28139
28157
  };
28140
- var readSpanField = {
28141
- name: "read_span_field",
28142
- title: "Read Span Field",
28143
- description: "Fetch the COMPLETE, untruncated value of a single span field. read_traces truncates large span fields and prints a note telling you to call this when you need the full text. Pass the traceId, the spanId (the `[ID: ...]` shown for that span in read_traces output), and the field to retrieve. Returns up to 100k chars by default; pass a higher maxChars to read more of an extremely large field. Prefer this over re-reading the whole trace when you only need one large field in full.",
28158
+ var getSpanField = {
28159
+ name: "get_span_field",
28160
+ title: "Get Span Field",
28161
+ description: "Fetch the COMPLETE, untruncated value of a single span field. get_traces truncates large span fields and prints a note telling you to call this when you need the full text. Pass the traceId, the spanId (the `[ID: ...]` shown for that span in get_traces output), and the field to retrieve. Returns up to 100k chars by default; pass a higher maxChars to read more of an extremely large field. Prefer this over re-reading the whole trace when you only need one large field in full.",
28144
28162
  inputSchema: {
28145
28163
  traceId: external_exports.uuid().describe("The trace ID the span belongs to"),
28146
- spanId: external_exports.uuid().describe("The span ID to read, taken from the `[ID: ...]` shown for the span in read_traces output"),
28164
+ spanId: external_exports.uuid().describe("The span ID to read, taken from the `[ID: ...]` shown for the span in get_traces output"),
28147
28165
  field: external_exports.enum(["input", "output", "reasoning", "content", "errors", "contexts"]).describe("Which span field to return in full: input, output, reasoning, content, errors, or contexts"),
28148
28166
  maxChars: external_exports.preprocess(parseJsonString, external_exports.number().int().positive()).optional().describe("Maximum characters to return (default 100000). Raise it to read further into an extremely large field.")
28149
28167
  }
28150
28168
  };
28151
- var updateAgentLabels = {
28152
- name: "update_agent_labels",
28153
- title: "Update Agent Labels",
28154
- description: "Set, skip, or archive the agent's pass/fail verdict on one or more traces (`labelSource=\"agent\"`). Use this AFTER you have read the traces with read_traces and decided yourself whether each one looks like a pass, a fail, or genuinely cannot be judged. To set a verdict, pass `label` (true=PASS, false=FAIL) and `annotation` (your reasoning, shown to the human reviewer in the labeling UI). Optionally pass `confidence` (`VeryLow|Low|Medium|High|VeryHigh`) to record how confident you are - surfaced in the labeling UI so reviewers can prioritize low-confidence verdicts. To explicitly skip a trace you cannot decide on (instead of leaving it unlabeled), pass `skip: true` and omit label/annotation - this records an explicit skip so coverage checks know you intentionally did not verdict it. To clear a previously-set agent verdict (e.g., you changed your mind or labeled the wrong trace), pass `archive: true` and omit label/annotation. New verdicts start unapproved (`approvedAt=null`); once a human approves one, it joins the validated dataset (queryable via `search_traces` with `validated: true`). Archiving is non-destructive - the row is hidden from all reads but kept for audit, and you can immediately re-label the trace from scratch. For replay results, key each label by the replay item's `originalTraceId` (the original trace it was replayed from; `sourceTraceId` is accepted as a deprecated alias) plus the top-level `testRunId` instead of a `traceId`: the server resolves it to the replay trace via lineage, so you never need a server-generated replay trace id. Returns an agent-readable summary with one parseable effective label line per updated trace, keyed by the id you supplied (`originalTraceId` for replay verdicts, otherwise `traceId`), so command callers can verify persistence.",
28169
+ var saveAgentLabels = {
28170
+ name: "save_agent_labels",
28171
+ title: "Save Agent Labels",
28172
+ description: "Set, skip, or archive the agent's pass/fail verdict on one or more traces (`labelSource=\"agent\"`). Use this AFTER you have read the traces with get_traces and decided yourself whether each one looks like a pass, a fail, or genuinely cannot be judged. To set a verdict, pass `label` (true=PASS, false=FAIL) and `annotation` (your reasoning, shown to the human reviewer in the labeling UI). Optionally pass `confidence` (`VeryLow|Low|Medium|High|VeryHigh`) to record how confident you are - surfaced in the labeling UI so reviewers can prioritize low-confidence verdicts. To explicitly skip a trace you cannot decide on (instead of leaving it unlabeled), pass `skip: true` and omit label/annotation - this records an explicit skip so coverage checks know you intentionally did not verdict it. To clear a previously-set agent verdict (e.g., you changed your mind or labeled the wrong trace), pass `archive: true` and omit label/annotation. New verdicts start unapproved (`approvedAt=null`); once a human approves one, it joins the validated dataset (queryable via `search_traces` with `validated: true`). Archiving is non-destructive - the row is hidden from all reads but kept for audit, and you can immediately re-label the trace from scratch. For replay results, key each label by the replay item's `originalTraceId` (the original trace it was replayed from; `sourceTraceId` is accepted as a deprecated alias) plus the top-level `testRunId` instead of a `traceId`: the server resolves it to the replay trace via lineage, so you never need a server-generated replay trace id. Returns an agent-readable summary with one parseable effective label line per updated trace, keyed by the id you supplied (`originalTraceId` for replay verdicts, otherwise `traceId`), so command callers can verify persistence.",
28155
28173
  inputSchema: {
28156
28174
  testRunId: external_exports.uuid().optional().describe("The replay test run id. Required when any label targets a trace by originalTraceId (replay verdicts); ignored otherwise."),
28157
28175
  labels: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.object({
@@ -28166,10 +28184,10 @@ var updateAgentLabels = {
28166
28184
  })).min(1).max(50)).describe("One update per trace (1-50)")
28167
28185
  }
28168
28186
  };
28169
- var setHumanLabels = {
28170
- name: "set_human_labels",
28171
- title: "Set Human Labels",
28172
- description: 'Record one or more human-authored pass/fail verdicts that are VALIDATED immediately (`labelSource="human"`, no approval step). Unlike update_agent_labels (which writes agent suggestions that start unapproved), labels set here join the validated dataset the instant they are written and satisfy `search_traces` with `validated: true`. Pass `label` (true=PASS, false=FAIL), `annotation` (the reasoning behind the verdict), and optionally `confidence` (`VeryLow|Low|Medium|High|VeryHigh`). USE ONLY when a human has explicitly decided the verdict, for example saving a known production bug as a test case. Do NOT use this for the agent\'s own first-pass guesses on traces awaiting human review; use update_agent_labels for those so they go through the normal approve/edit loop.',
28187
+ var saveHumanLabels = {
28188
+ name: "save_human_labels",
28189
+ title: "Save Human Labels",
28190
+ description: 'Record one or more human-authored pass/fail verdicts that are VALIDATED immediately (`labelSource="human"`, no approval step). Unlike save_agent_labels (which writes agent suggestions that start unapproved), labels set here join the validated dataset the instant they are written and satisfy `search_traces` with `validated: true`. Pass `label` (true=PASS, false=FAIL), `annotation` (the reasoning behind the verdict), and optionally `confidence` (`VeryLow|Low|Medium|High|VeryHigh`). USE ONLY when a human has explicitly decided the verdict, for example saving a known production bug as a test case. Do NOT use this for the agent\'s own first-pass guesses on traces awaiting human review; use save_agent_labels for those so they go through the normal approve/edit loop.',
28173
28191
  inputSchema: {
28174
28192
  labels: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.object({
28175
28193
  traceId: external_exports.uuid().describe("The trace ID to label"),
@@ -28210,10 +28228,10 @@ var listGraders = {
28210
28228
  cursor: external_exports.string().min(1).optional().describe("Opaque cursor from the previous page; reuse the same filters")
28211
28229
  }
28212
28230
  };
28213
- var createDataset = {
28214
- name: "create_dataset",
28215
- title: "Create Dataset",
28216
- description: "Create a labeled dataset for a traced function. Datasets are named buckets of traces (e.g. 'Hallucination failures', 'Edge cases for grader X') that humans review and that experiments replay against. Pass traceFunctionKey, a short human-readable name, and an optional description. Returns the new dataset id; use it with add_traces_to_dataset to populate.",
28231
+ var saveDataset = {
28232
+ name: "save_dataset",
28233
+ title: "Save Dataset",
28234
+ description: "Create or edit a labeled dataset for a traced function. Datasets are named buckets of traces (e.g. 'Hallucination failures', 'Edge cases for grader X') that humans review and that experiments replay against. Pass traceFunctionKey, a short human-readable name, and an optional description. Saving with the name of an existing dataset on the same trace function updates that dataset instead of creating a duplicate, so re-running a flow is safe; pass a new description to change it, or omit it to leave the current one. Returns the dataset id; use it with add_traces_to_dataset to populate. Call list_datasets first when you need to see what already exists.",
28217
28235
  inputSchema: {
28218
28236
  traceFunctionKey: external_exports.string().min(1).describe("The trace function key the dataset is scoped to (must match traces you'll add)"),
28219
28237
  name: external_exports.string().min(1).describe("Short human-readable name shown in the labeling UI (e.g. 'Hallucination failures')"),
@@ -28223,7 +28241,7 @@ var createDataset = {
28223
28241
  var listDatasets = {
28224
28242
  name: "list_datasets",
28225
28243
  title: "List Datasets",
28226
- description: "List all datasets for a traced function, with id, name, description, current trace count, and assigned graders for each. Call this before changing trace or grader assignments to discover dataset ids and current membership, or before create_dataset to avoid duplicates.",
28244
+ description: "List all datasets for a traced function, with id, name, description, current trace count, and assigned graders for each. Call this before changing trace or grader assignments to discover dataset ids and current membership, or before save_dataset to avoid duplicates.",
28227
28245
  inputSchema: {
28228
28246
  traceFunctionKey: external_exports.string().min(1).describe("The trace function key to list datasets for")
28229
28247
  }
@@ -28233,7 +28251,7 @@ var addTracesToDataset = {
28233
28251
  title: "Add Traces to Dataset",
28234
28252
  description: "Add one or more traces to a dataset. Idempotent: re-adding a trace already in the dataset is a no-op. Traces are validated against the dataset's traceFunctionKey; ids that don't belong to this organization, or to the dataset's trace function, are silently skipped (the response reports how many were added vs skipped).",
28235
28253
  inputSchema: {
28236
- datasetId: external_exports.uuid().describe("The dataset id (from create_dataset or list_datasets)"),
28254
+ datasetId: external_exports.uuid().describe("The dataset id (from save_dataset or list_datasets)"),
28237
28255
  traceIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Trace ids to add (1-100)")
28238
28256
  }
28239
28257
  };
@@ -28251,7 +28269,7 @@ var addGradersToDataset = {
28251
28269
  title: "Add Graders to Dataset",
28252
28270
  description: "Assign one or more graders to a dataset. Idempotent: assigning a grader that is already attached is a no-op. Graders must belong to this organization and match the dataset's traceFunctionKey; invalid ids are skipped and reported.",
28253
28271
  inputSchema: {
28254
- datasetId: external_exports.uuid().describe("The dataset id (from create_dataset or list_datasets)"),
28272
+ datasetId: external_exports.uuid().describe("The dataset id (from save_dataset or list_datasets)"),
28255
28273
  graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to assign (1-100)")
28256
28274
  }
28257
28275
  };
@@ -28282,13 +28300,22 @@ var removeGradersFromExperiment = {
28282
28300
  graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to detach (1-100)")
28283
28301
  }
28284
28302
  };
28285
- var addGraderToExperimentGroup = {
28286
- name: "add_grader_to_experiment_group",
28287
- title: "Add Grader to Experiment Group",
28288
- description: "Assign one grader to every experiment (test run) currently in an experiment group. The grader assignment is stored on each experiment, not on the group, so experiments added later do not inherit it automatically. Completed experiments immediately queue any missing grader evaluations; pending experiments use the grader when they complete. Idempotent when the grader is already assigned.",
28303
+ var addGradersToExperimentGroup = {
28304
+ name: "add_graders_to_experiment_group",
28305
+ title: "Add Graders to Experiment Group",
28306
+ description: "Assign one or more graders to every experiment (test run) currently in an experiment group. Grader assignments are stored on each experiment, not on the group, so experiments added later do not inherit them automatically. Completed experiments immediately queue any missing grader evaluations; pending experiments use the graders when they complete. Idempotent: assigning a grader already attached to an experiment is a no-op.",
28307
+ inputSchema: {
28308
+ experimentGroupId: external_exports.uuid().describe("The experiment group id"),
28309
+ graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to assign (1-100)")
28310
+ }
28311
+ };
28312
+ var removeGradersFromExperimentGroup = {
28313
+ name: "remove_graders_from_experiment_group",
28314
+ title: "Remove Graders from Experiment Group",
28315
+ description: "Detach one or more graders from every experiment (test run) currently in an experiment group, undoing add_graders_to_experiment_group. Grader ids not assigned to any experiment in the group are ignored and reported. The graders themselves are not deleted, and archived graders can still be detached. Because assignments live on each experiment rather than on the group, this only affects experiments currently in the group. Effect depends on run state, matching remove_graders_from_experiment: an in-progress experiment re-adds a detached grader from its dataset when it completes if the dataset still assigns it, while an already-completed experiment has a finalized grader set, so detaching permanently drops that grader from its results.",
28289
28316
  inputSchema: {
28290
28317
  experimentGroupId: external_exports.uuid().describe("The experiment group id"),
28291
- graderId: external_exports.uuid().describe("The grader id to assign")
28318
+ graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to detach (1-100)")
28292
28319
  }
28293
28320
  };
28294
28321
  var listExperiments = {
@@ -28300,9 +28327,9 @@ var listExperiments = {
28300
28327
  limit: external_exports.preprocess(parseJsonString, external_exports.number().int().min(1).max(50)).optional().describe("Max experiments to return (default 20, max 50)")
28301
28328
  }
28302
28329
  };
28303
- var getExperimentTraces = {
28304
- name: "get_experiment_traces",
28305
- title: "Get Experiment Traces",
28330
+ var listExperimentTraces = {
28331
+ name: "list_experiment_traces",
28332
+ title: "List Experiment Traces",
28306
28333
  description: "Get individual trace results for an experiment (test run), including each replay trace's verdict (fixed, regressed, still-passing, still-failing, unpaired) by comparing against the original trace's label, plus token usage (input, output, cached, total) for the replay and the paired original so you can reason about cost and cache-read deltas. Use after list_experiments to drill into a specific experiment's results.",
28307
28334
  inputSchema: {
28308
28335
  testRunId: external_exports.uuid().describe("The experiment (test run) ID to get traces for")
@@ -28319,21 +28346,21 @@ var getReplayStatus = {
28319
28346
  var getTemplateReference = {
28320
28347
  name: "get_template_reference",
28321
28348
  title: "Get Template Reference",
28322
- description: "Read the agent-facing reference for Bitfab span templates: the engine (Nunjucks), the render-context schema (variables bound at render time), the composition model (no `{% extends %}` - assembler injects into base.njk), the registered custom filters and tests, common patterns from the live default templates, and error behavior. **Call this once per session before your first `get_template` or `update_template`** - without it, edits typically use the wrong syntax dialect, the wrong variable names, or break the layout. Read-only, no organization data, safe to cache.",
28349
+ description: "Read the agent-facing reference for Bitfab span templates: the engine (Nunjucks), the render-context schema (variables bound at render time), the composition model (no `{% extends %}` - assembler injects into base.njk), the registered custom filters and tests, common patterns from the live default templates, and error behavior. **Call this once per session before your first `get_template` or `save_template`** - without it, edits typically use the wrong syntax dialect, the wrong variable names, or break the layout. Read-only, no organization data, safe to cache.",
28323
28350
  inputSchema: {}
28324
28351
  };
28325
28352
  var getTemplate = {
28326
28353
  name: "get_template",
28327
28354
  title: "Get Template",
28328
- description: "Read the rendering template for a span type. Templates control how a span's input/output renders in the Bitfab UI. **Pass `traceFunctionKey` to scope to a specific trace function** (this is the normal mode for the `templates` skill). Resolution: per-key row \u2192 org-global override \u2192 file default. The response labels the source so you know whether you're editing a per-key row, seeding from the org-global, or starting from the file default. Without `traceFunctionKey`, returns the org-global override. Span types: llm, agent, function, guardrail, handoff, custom. Always call this before update_template so your edit starts from the live content. **If you haven't called `get_template_reference` this session, call it first** - it documents the Nunjucks engine, available variables, and base-template composition model, which you'll need to write a correct edit.",
28355
+ description: "Read the rendering template for a span type. Templates control how a span's input/output renders in the Bitfab UI. **Pass `traceFunctionKey` to scope to a specific trace function** (this is the normal mode for the `templates` skill). Resolution: per-key row \u2192 org-global override \u2192 file default. The response labels the source so you know whether you're editing a per-key row, seeding from the org-global, or starting from the file default. Without `traceFunctionKey`, returns the org-global override. Span types: llm, agent, function, guardrail, handoff, custom. Always call this before save_template so your edit starts from the live content. **If you haven't called `get_template_reference` this session, call it first** - it documents the Nunjucks engine, available variables, and base-template composition model, which you'll need to write a correct edit.",
28329
28356
  inputSchema: {
28330
28357
  spanType: external_exports.enum(["llm", "agent", "function", "guardrail", "handoff", "custom"]).describe("Span type whose template to read (llm | agent | function | guardrail | handoff | custom)"),
28331
28358
  traceFunctionKey: external_exports.string().min(1).optional().describe("Trace function key to scope the template to. When set, returns the per-key row if present; otherwise seeds from the org-global override; otherwise the file default. Omit to read the org-global template.")
28332
28359
  }
28333
28360
  };
28334
- var updateTemplate = {
28335
- name: "update_template",
28336
- title: "Update Template",
28361
+ var saveTemplate = {
28362
+ name: "save_template",
28363
+ title: "Save Template",
28337
28364
  description: "Upsert the rendering template for a span type. **Pass `traceFunctionKey` to scope the override to a single trace function** (this is the normal mode for the `templates` skill); the per-key row is created on first save and applied at render time only for spans on traces of that function. Without `traceFunctionKey`, edits the org-global override (applies when no per-key row matches). Updates are in-place: no version bump, no row juggling. The `template` body is the full template source you want stored, passed verbatim - do NOT wrap in `<![CDATA[ ... ]]>`, triple-backtick code fences, or any other escape construct; the value is stored exactly as received and a stray wrapper will silently break rendering (e.g. an opening `<![CDATA[` swallows the next `<style>` tag). Optional `name` and `description` are metadata shown in the templates UI. Span types: llm, agent, function, guardrail, handoff, custom. Always call get_template first so you're editing the current content rather than overwriting unrelated changes. **If you haven't called `get_template_reference` this session, call it first** - edits that don't match the documented Nunjucks variable shape will silently render empty in production.",
28338
28365
  inputSchema: {
28339
28366
  spanType: external_exports.enum(["llm", "agent", "function", "guardrail", "handoff", "custom"]).describe("Span type whose template to upsert (llm | agent | function | guardrail | handoff | custom)"),
@@ -28397,18 +28424,18 @@ var getExperiment = {
28397
28424
  testRunId: external_exports.uuid().describe("The experiment (test run) id, from list_experiments")
28398
28425
  }
28399
28426
  };
28400
- var readGraderLabels = {
28401
- name: "read_grader_labels",
28402
- title: "Read Grader Labels",
28403
- description: `Read the INDIVIDUAL verdicts each automated grader (LLM-as-judge) recorded, one row per grader per trace, with the grader's reason, its failure diagnostic, the confidence, and whether the verdict came from a human or from a grader run. This is the per-grader breakdown that read_trace_labels does not show: that tool returns one grader-agnostic verdict per trace, so use it for a dataset's overall pass/fail and use this one to see WHICH check failed and why. Pass traceIds to see every grader's verdict on those traces, graderId to see that grader's most recent verdicts across traces (newest first, use it to diagnose a grader that is misfiring), or both to narrow to one grader on specific traces. Call list_graders first if you need grader ids.`,
28427
+ var getGraderLabels = {
28428
+ name: "get_grader_labels",
28429
+ title: "Get Grader Labels",
28430
+ description: `Read the INDIVIDUAL verdicts each automated grader (LLM-as-judge) recorded, one row per grader per trace, with the grader's reason, its failure diagnostic, the confidence, and whether the verdict came from a human or from a grader run. This is the per-grader breakdown that get_trace_labels does not show: that tool returns one grader-agnostic verdict per trace, so use it for a dataset's overall pass/fail and use this one to see WHICH check failed and why. Pass traceIds to see every grader's verdict on those traces, graderId to see that grader's most recent verdicts across traces (newest first, use it to diagnose a grader that is misfiring), or both to narrow to one grader on specific traces. Call list_graders first if you need grader ids.`,
28404
28431
  inputSchema: {
28405
- traceIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(READ_GRADER_LABELS_MAX_IDS)).optional().describe(`Trace IDs to read grader verdicts for (1-${READ_GRADER_LABELS_MAX_IDS}). Omit to read by grader instead.`),
28432
+ traceIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(GET_GRADER_LABELS_MAX_IDS)).optional().describe(`Trace IDs to read grader verdicts for (1-${GET_GRADER_LABELS_MAX_IDS}). Omit to read by grader instead.`),
28406
28433
  graderId: external_exports.uuid().optional().describe("Restrict to one grader (from list_graders). On its own, returns that grader's most recent verdicts across all traces."),
28407
28434
  limit: external_exports.preprocess(parseJsonString, external_exports.number().int().min(1).max(MAX_GRADER_LABEL_LIMIT)).optional().describe(`Max labels to return when reading by grader alone (default ${DEFAULT_GRADER_LABEL_LIMIT}, max ${MAX_GRADER_LABEL_LIMIT}). Ignored when traceIds is given.`)
28408
28435
  }
28409
28436
  };
28410
28437
  var ALL_TOOL_CONTRACTS = [
28411
- readGraderLabels,
28438
+ getGraderLabels,
28412
28439
  getExperiment,
28413
28440
  saveExperimentGroup,
28414
28441
  getBitfabApiKey,
@@ -28417,14 +28444,14 @@ var ALL_TOOL_CONTRACTS = [
28417
28444
  getDatabaseConnectionStatus,
28418
28445
  listTraceFunctions,
28419
28446
  searchTraces,
28420
- readTraces,
28421
- readTraceLabels,
28422
- readSpanField,
28423
- updateAgentLabels,
28424
- setHumanLabels,
28447
+ getTraces,
28448
+ getTraceLabels,
28449
+ getSpanField,
28450
+ saveAgentLabels,
28451
+ saveHumanLabels,
28425
28452
  saveGrader,
28426
28453
  listGraders,
28427
- createDataset,
28454
+ saveDataset,
28428
28455
  listDatasets,
28429
28456
  addTracesToDataset,
28430
28457
  removeTracesFromDataset,
@@ -28432,13 +28459,14 @@ var ALL_TOOL_CONTRACTS = [
28432
28459
  removeGradersFromDataset,
28433
28460
  addGradersToExperiment,
28434
28461
  removeGradersFromExperiment,
28435
- addGraderToExperimentGroup,
28462
+ addGradersToExperimentGroup,
28463
+ removeGradersFromExperimentGroup,
28436
28464
  listExperiments,
28437
- getExperimentTraces,
28465
+ listExperimentTraces,
28438
28466
  getReplayStatus,
28439
28467
  getTemplateReference,
28440
28468
  getTemplate,
28441
- updateTemplate,
28469
+ saveTemplate,
28442
28470
  createTracePlan,
28443
28471
  confirmTracePlan,
28444
28472
  getTracePlan
@@ -28784,7 +28812,7 @@ async function runClaudeAnalyzeRepo(captureOverride, limit, prompt) {
28784
28812
  }
28785
28813
  };
28786
28814
  try {
28787
- const exitCode = await new Promise((resolve2, reject) => {
28815
+ const exitCode = await new Promise((resolve4, reject) => {
28788
28816
  const child = spawn5(
28789
28817
  CLI,
28790
28818
  [
@@ -28823,7 +28851,7 @@ async function runClaudeAnalyzeRepo(captureOverride, limit, prompt) {
28823
28851
  );
28824
28852
  return;
28825
28853
  }
28826
- resolve2(code);
28854
+ resolve4(code);
28827
28855
  });
28828
28856
  });
28829
28857
  flushRedactors();
@@ -29663,7 +29691,7 @@ async function runCodexAnalyzeRepo(captureOverride, limit, prompt) {
29663
29691
  }
29664
29692
  };
29665
29693
  try {
29666
- const exitCode = await new Promise((resolve2, reject) => {
29694
+ const exitCode = await new Promise((resolve4, reject) => {
29667
29695
  const child = spawn6(
29668
29696
  CLI2,
29669
29697
  [
@@ -29705,7 +29733,7 @@ async function runCodexAnalyzeRepo(captureOverride, limit, prompt) {
29705
29733
  );
29706
29734
  return;
29707
29735
  }
29708
- resolve2(code);
29736
+ resolve4(code);
29709
29737
  });
29710
29738
  });
29711
29739
  flushRedactors();
@@ -30040,7 +30068,7 @@ async function runCursorAnalyzeRepo(captureOverride, limit, prompt) {
30040
30068
  }
30041
30069
  };
30042
30070
  try {
30043
- const exitCode = await new Promise((resolve2, reject) => {
30071
+ const exitCode = await new Promise((resolve4, reject) => {
30044
30072
  const child = spawn7(
30045
30073
  "cursor",
30046
30074
  [
@@ -30085,7 +30113,7 @@ async function runCursorAnalyzeRepo(captureOverride, limit, prompt) {
30085
30113
  );
30086
30114
  return;
30087
30115
  }
30088
- resolve2(code);
30116
+ resolve4(code);
30089
30117
  });
30090
30118
  });
30091
30119
  flushRedactors();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitfab-cli",
3
- "version": "0.2.227",
3
+ "version": "0.2.229",
4
4
  "description": "Install and configure the Bitfab plugin in Claude Code, Codex, or Cursor.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",