@brainbase-labs/cli 0.25.0-eng1209.7 → 0.25.0-eng1209.8

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 +178 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -36008,7 +36008,7 @@ function padStart(s, n) {
36008
36008
  // package.json
36009
36009
  var package_default = {
36010
36010
  name: "@brainbase-labs/cli",
36011
- version: "0.25.0-eng1209.7",
36011
+ version: "0.25.0-eng1209.8",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -78492,6 +78492,9 @@ var MAX_CRITERION_EXPLANATION_LENGTH = 16384;
78492
78492
  var MAX_CRITERION_EVIDENCE_IDS = 100;
78493
78493
  var MAX_CRITERION_EVIDENCE_ID_LENGTH = 256;
78494
78494
  var MAX_ALLOWED_EVIDENCE_IDS = 1e4;
78495
+ var COMMAND_PROCESS_MARKER_ENV = "BRAINBASE_BENCHMARK_COMMAND_MARKER";
78496
+ var COMMAND_PROCESS_POLL_MS = 10;
78497
+ var COMMAND_PROCESS_CLEANUP_MS = 500;
78495
78498
  var RESERVED_WORKSPACE_PATHS = new Set([
78496
78499
  ".brainbase",
78497
78500
  ".git",
@@ -78712,6 +78715,20 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78712
78715
  HydrateSpecSchema,
78713
78716
  EvaluateSpecSchema
78714
78717
  ]).superRefine((value, context) => {
78718
+ if (Object.hasOwn(value.environment, COMMAND_PROCESS_MARKER_ENV)) {
78719
+ context.addIssue({
78720
+ code: exports_external.ZodIssueCode.custom,
78721
+ path: ["environment", COMMAND_PROCESS_MARKER_ENV],
78722
+ message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
78723
+ });
78724
+ }
78725
+ if (value.secret_env.includes(COMMAND_PROCESS_MARKER_ENV)) {
78726
+ context.addIssue({
78727
+ code: exports_external.ZodIssueCode.custom,
78728
+ path: ["secret_env"],
78729
+ message: `${COMMAND_PROCESS_MARKER_ENV} is reserved for command supervision`
78730
+ });
78731
+ }
78715
78732
  for (const name of Object.keys(value.environment)) {
78716
78733
  if (SENSITIVE_ENV_NAME_RE.test(name)) {
78717
78734
  context.addIssue({
@@ -79920,7 +79937,7 @@ function buildEnvironment(spec, secretNames, additions = {}) {
79920
79937
  Object.assign(env3, additions);
79921
79938
  return env3;
79922
79939
  }
79923
- function redactCommandOutput(data, spec) {
79940
+ function redactCommandOutput(data, spec, additionalSecrets = []) {
79924
79941
  let value = data.toString("utf8");
79925
79942
  const sensitiveNames = new Set([
79926
79943
  ...spec.secret_env,
@@ -79931,6 +79948,10 @@ function redactCommandOutput(data, spec) {
79931
79948
  if (secret)
79932
79949
  value = value.split(secret).join("[REDACTED]");
79933
79950
  }
79951
+ for (const secret of additionalSecrets) {
79952
+ if (secret)
79953
+ value = value.split(secret).join("[REDACTED]");
79954
+ }
79934
79955
  return Buffer.from(value);
79935
79956
  }
79936
79957
  function descendantPids(parentPid) {
@@ -79982,6 +80003,86 @@ function terminate(child) {
79982
80003
  child.kill("SIGKILL");
79983
80004
  }
79984
80005
  }
80006
+ function markedProcessPids(marker) {
80007
+ const assignment = `${COMMAND_PROCESS_MARKER_ENV}=${marker}`;
80008
+ if (process.platform === "linux") {
80009
+ const matches2 = [];
80010
+ let entries;
80011
+ try {
80012
+ entries = fs81.readdirSync("/proc");
80013
+ } catch {
80014
+ return matches2;
80015
+ }
80016
+ for (const entry of entries) {
80017
+ if (!/^\d+$/.test(entry))
80018
+ continue;
80019
+ const pid = Number(entry);
80020
+ if (pid === process.pid)
80021
+ continue;
80022
+ try {
80023
+ const environment = fs81.readFileSync(path88.join("/proc", entry, "environ"), "utf8");
80024
+ if (environment.split("\x00").includes(assignment))
80025
+ matches2.push(pid);
80026
+ } catch {}
80027
+ }
80028
+ return matches2;
80029
+ }
80030
+ if (process.platform === "darwin") {
80031
+ try {
80032
+ const output = execFileSync2("ps", ["eww", "-axo", "pid=,command="], {
80033
+ encoding: "utf8",
80034
+ maxBuffer: 16 * 1024 * 1024,
80035
+ stdio: ["ignore", "pipe", "ignore"]
80036
+ });
80037
+ const matches2 = [];
80038
+ for (const line of output.split(`
80039
+ `)) {
80040
+ const match = line.match(/^\s*(\d+)\s+(.*)$/);
80041
+ if (!match || !match[2].includes(assignment))
80042
+ continue;
80043
+ const pid = Number(match[1]);
80044
+ if (Number.isInteger(pid) && pid !== process.pid)
80045
+ matches2.push(pid);
80046
+ }
80047
+ return matches2;
80048
+ } catch {
80049
+ return [];
80050
+ }
80051
+ }
80052
+ return [];
80053
+ }
80054
+ function processExists(pid) {
80055
+ try {
80056
+ process.kill(pid, 0);
80057
+ return true;
80058
+ } catch (error2) {
80059
+ return error2.code !== "ESRCH";
80060
+ }
80061
+ }
80062
+ async function terminateCommandProcesses(child, marker, observedDescendants) {
80063
+ if (child.pid !== undefined) {
80064
+ for (const pid of descendantPids(child.pid))
80065
+ observedDescendants.add(pid);
80066
+ }
80067
+ terminate(child);
80068
+ const deadline = Date.now() + COMMAND_PROCESS_CLEANUP_MS;
80069
+ while (true) {
80070
+ for (const pid of markedProcessPids(marker))
80071
+ observedDescendants.add(pid);
80072
+ const active = [...observedDescendants].filter(processExists);
80073
+ for (const pid of active) {
80074
+ try {
80075
+ process.kill(pid, "SIGKILL");
80076
+ } catch {}
80077
+ }
80078
+ if (active.length === 0)
80079
+ return;
80080
+ if (Date.now() >= deadline) {
80081
+ throw new BenchmarkPhaseError("command_cleanup_failed", "command descendants remained after bounded cleanup");
80082
+ }
80083
+ await new Promise((resolve) => setTimeout(resolve, COMMAND_PROCESS_POLL_MS));
80084
+ }
80085
+ }
79985
80086
  async function runCommand(command, root, spec, context, additions = {}) {
79986
80087
  const cwdRel = normalizedRootRelative(command.cwd);
79987
80088
  assertNoSymlinkTraversal(root, cwdRel);
@@ -80001,27 +80102,56 @@ async function runCommand(command, root, spec, context, additions = {}) {
80001
80102
  const reachesPhaseDeadline = command.timeout_ms === undefined || command.timeout_ms >= remainingMs;
80002
80103
  const timeoutMs2 = reachesPhaseDeadline ? remainingMs : command.timeout_ms;
80003
80104
  const started = Date.now();
80105
+ const commandMarker = crypto6.randomBytes(32).toString("hex");
80004
80106
  return await new Promise((resolve, reject2) => {
80005
80107
  const child = spawn4(command.argv[0], command.argv.slice(1), {
80006
80108
  cwd: cwd2,
80007
- env: buildEnvironment(spec, command.secret_env, additions),
80109
+ env: buildEnvironment(spec, command.secret_env, {
80110
+ ...additions,
80111
+ [COMMAND_PROCESS_MARKER_ENV]: commandMarker
80112
+ }),
80008
80113
  stdio: ["ignore", "pipe", "pipe"],
80009
80114
  detached: process.platform !== "win32"
80010
80115
  });
80011
80116
  const stdout = [];
80012
80117
  const stderr = [];
80118
+ const observedDescendants = new Set;
80013
80119
  let captured = 0;
80014
80120
  let settled = false;
80015
80121
  let timer;
80122
+ let observer;
80123
+ let cleanup;
80124
+ const observeDescendants = () => {
80125
+ if (child.pid === undefined)
80126
+ return;
80127
+ for (const pid of descendantPids(child.pid))
80128
+ observedDescendants.add(pid);
80129
+ };
80130
+ const cleanupProcesses = () => {
80131
+ if (observer)
80132
+ clearInterval(observer);
80133
+ cleanup ??= terminateCommandProcesses(child, commandMarker, observedDescendants);
80134
+ return cleanup;
80135
+ };
80136
+ const sanitizedOutput = (chunks) => redactCommandOutput(Buffer.concat(chunks), spec, [commandMarker]);
80137
+ const sanitizedError = (error2) => {
80138
+ if (!(error2 instanceof BenchmarkCommandExecutionError))
80139
+ return error2;
80140
+ return new BenchmarkCommandExecutionError(error2.code, error2.message, sanitizedOutput(stdout), sanitizedOutput(stderr), error2.durationMs);
80141
+ };
80016
80142
  const fail = (error2) => {
80017
80143
  if (settled)
80018
80144
  return;
80019
80145
  settled = true;
80020
80146
  if (timer)
80021
80147
  clearTimeout(timer);
80022
- terminate(child);
80023
- reject2(error2);
80148
+ cleanupProcesses().then(() => reject2(sanitizedError(error2)), reject2);
80024
80149
  };
80150
+ if (process.platform !== "linux") {
80151
+ observeDescendants();
80152
+ observer = setInterval(observeDescendants, COMMAND_PROCESS_POLL_MS);
80153
+ observer.unref();
80154
+ }
80025
80155
  const capture = (target, chunk2) => {
80026
80156
  if (settled)
80027
80157
  return;
@@ -80036,7 +80166,9 @@ async function runCommand(command, root, spec, context, additions = {}) {
80036
80166
  child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
80037
80167
  child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
80038
80168
  child.once("exit", () => {
80039
- terminate(child);
80169
+ if (timer)
80170
+ clearTimeout(timer);
80171
+ cleanupProcesses();
80040
80172
  });
80041
80173
  child.on("error", (error2) => {
80042
80174
  fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
@@ -80053,16 +80185,19 @@ async function runCommand(command, root, spec, context, additions = {}) {
80053
80185
  return;
80054
80186
  settled = true;
80055
80187
  clearTimeout(timer);
80056
- if (code === null) {
80057
- reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
80058
- return;
80059
- }
80060
- resolve({
80061
- exitCode: code,
80062
- stdout: Buffer.concat(stdout),
80063
- stderr: Buffer.concat(stderr),
80064
- durationMs: Date.now() - started
80065
- });
80188
+ cleanupProcesses().then(() => {
80189
+ if (code === null) {
80190
+ reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
80191
+ return;
80192
+ }
80193
+ resolve({
80194
+ exitCode: code,
80195
+ stdout: sanitizedOutput(stdout),
80196
+ stderr: sanitizedOutput(stderr),
80197
+ durationMs: Date.now() - started,
80198
+ redactions: [commandMarker]
80199
+ });
80200
+ }, reject2);
80066
80201
  });
80067
80202
  });
80068
80203
  }
@@ -80255,6 +80390,13 @@ async function workspaceManifest(spec, context) {
80255
80390
  }
80256
80391
  return records.sort((a3, b4) => a3.path.localeCompare(b4.path));
80257
80392
  }
80393
+ async function verifyWorkspaceManifestUnchanged(expected, spec, context) {
80394
+ await verifyRecordsUnchanged(expected, spec);
80395
+ const actual = await workspaceManifest(spec, context);
80396
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
80397
+ throw new BenchmarkPhaseError("evidence_tampered", "candidate workspace changed during read-only evaluation");
80398
+ }
80399
+ }
80258
80400
  function treeBytes(root, context) {
80259
80401
  let totalBytes = 0;
80260
80402
  const stack = [path88.resolve(root)];
@@ -80516,7 +80658,7 @@ var CriterionResultSchema = exports_external.object({
80516
80658
  var CriteriaResultFileSchema = exports_external.object({
80517
80659
  criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
80518
80660
  }).strict();
80519
- function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec) {
80661
+ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec, additionalSecrets = []) {
80520
80662
  let opened;
80521
80663
  try {
80522
80664
  opened = openRegularFileNoFollow(resultPath, "criterion result", path88.dirname(resultPath));
@@ -80564,7 +80706,7 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spe
80564
80706
  const criterion = byKey.get(key2);
80565
80707
  return {
80566
80708
  ...criterion,
80567
- explanation: redactCommandOutput(Buffer.from(criterion.explanation), spec).toString("utf8")
80709
+ explanation: redactCommandOutput(Buffer.from(criterion.explanation), spec, additionalSecrets).toString("utf8")
80568
80710
  };
80569
80711
  });
80570
80712
  } finally {
@@ -80723,9 +80865,13 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80723
80865
  let isolatedWorkspace;
80724
80866
  let isolatedTestsRoot;
80725
80867
  let privateResultRoot;
80868
+ const usesLiveWorkspace = evaluator.workspace_mode === "read_only";
80726
80869
  try {
80727
- const evaluatorWorkspace = await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot);
80728
- isolatedWorkspace = evaluatorWorkspace;
80870
+ const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot);
80871
+ if (!usesLiveWorkspace)
80872
+ isolatedWorkspace = evaluatorWorkspace;
80873
+ else
80874
+ await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80729
80875
  isolatedTestsRoot = await copyEvaluatorTests(spec, context);
80730
80876
  const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
80731
80877
  const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
@@ -80750,7 +80896,7 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80750
80896
  const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80751
80897
  let criterionResults;
80752
80898
  try {
80753
- criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? [], spec) : undefined;
80899
+ criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? [], spec, result2.redactions) : undefined;
80754
80900
  } catch (error2) {
80755
80901
  const normalized = stableError(error2);
80756
80902
  return {
@@ -80779,11 +80925,17 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
80779
80925
  ...criterionResults ? { criterion_results: criterionResults } : {}
80780
80926
  };
80781
80927
  } finally {
80782
- for (const temporary of [privateResultRoot, isolatedTestsRoot, isolatedWorkspace]) {
80783
- if (!temporary)
80784
- continue;
80785
- fs81.rmSync(temporary, { recursive: true, force: true });
80786
- context.temporaryRoots.delete(temporary);
80928
+ try {
80929
+ if (usesLiveWorkspace) {
80930
+ await verifyWorkspaceManifestUnchanged(manifest, spec, context);
80931
+ }
80932
+ } finally {
80933
+ for (const temporary of [privateResultRoot, isolatedTestsRoot, isolatedWorkspace]) {
80934
+ if (!temporary)
80935
+ continue;
80936
+ fs81.rmSync(temporary, { recursive: true, force: true });
80937
+ context.temporaryRoots.delete(temporary);
80938
+ }
80787
80939
  }
80788
80940
  }
80789
80941
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.25.0-eng1209.7",
3
+ "version": "0.25.0-eng1209.8",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {