oh-my-promptfoo 1.23.0 → 1.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import { a as nativeMcpAttributes, c as graderConfig, d as object, f as validate
6
6
  import { builtinModules } from "node:module";
7
7
  import fs, { lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
8
8
  import { access, lstat, mkdtemp, readFile, readdir, rm, stat } from "node:fs/promises";
9
- import path, { basename, dirname, isAbsolute, join, resolve } from "node:path";
9
+ import path, { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
10
10
  import { fileURLToPath, pathToFileURL } from "node:url";
11
11
  import assert from "node:assert";
12
12
  import process$1 from "node:process";
@@ -14,6 +14,7 @@ import v8 from "node:v8";
14
14
  import { format, inspect } from "node:util";
15
15
  import "node:child_process";
16
16
  import { tmpdir } from "node:os";
17
+ import { createHash } from "node:crypto";
17
18
  // node_modules/.pnpm/@opentelemetry+context-async-hooks@2.12.0_@opentelemetry+api@1.9.1/node_modules/@opentelemetry/context-async-hooks/build/src/AbstractAsyncHooksContextManager.js
18
19
  var require_AbstractAsyncHooksContextManager = /* @__PURE__ */ __commonJSMin(((exports) => {
19
20
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -8808,6 +8809,134 @@ var NativeToolInventory = class {
8808
8809
  }
8809
8810
  };
8810
8811
  // packages/oh-my-promptfoo/src/skill-evidence.ts
8812
+ function fields(value) {
8813
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
8814
+ }
8815
+ function literalWords(command, powershell) {
8816
+ if (/[\r\n\0$`;&|<>()[\]{}*?#!,]/.test(command) || !powershell && command.includes("\\")) return;
8817
+ const words = [];
8818
+ let rest = command.trim();
8819
+ while (rest) {
8820
+ const match = /^(?:"([^"]*)"|'([^']*)'|([^\s'"]+))(?=[ \t]|$)/.exec(rest);
8821
+ if (!match) return;
8822
+ words.push(match[1] ?? match[2] ?? match[3]);
8823
+ if (words.length > 8) return;
8824
+ rest = rest.slice(match[0].length).replace(/^[ \t]+/, "");
8825
+ }
8826
+ return words;
8827
+ }
8828
+ function literalPath(path, powershell) {
8829
+ const native = powershell ? path.replace(/\\/g, sep) : path;
8830
+ if (!native || native.startsWith("-") || native.startsWith("~") || native.split(/[\\/]/).includes("..")) return;
8831
+ if (native.replace(process.platform === "win32" ? /^[A-Za-z]:/ : /^$/, "").includes(":")) return;
8832
+ return native;
8833
+ }
8834
+ function containedShellDirectory(cwd, workingDir) {
8835
+ let cursor = cwd;
8836
+ const root = resolve(workingDir);
8837
+ while (cursor !== root) {
8838
+ if (!lstatSync(cursor).isDirectory()) return false;
8839
+ const parent = dirname(cursor);
8840
+ if (parent === cursor) return false;
8841
+ cursor = parent;
8842
+ }
8843
+ return true;
8844
+ }
8845
+ /** Capture bounded local evidence at tool start; nothing is credited until completion. */
8846
+ function observedShellSkillRead(toolName, args, workingDir) {
8847
+ if (toolName !== "bash" && toolName !== "powershell") return;
8848
+ const input = fields(args);
8849
+ if (!input || typeof input.command !== "string" || input.command.length > 8192) return;
8850
+ const powershell = toolName === "powershell";
8851
+ const parts = powershell ? [input.command] : input.command.split(";");
8852
+ if (parts.length > 2) return;
8853
+ const words = literalWords(parts[0], powershell);
8854
+ if (!words) return;
8855
+ let path;
8856
+ if (!powershell) {
8857
+ if (words[0] !== "cat") return;
8858
+ if (words[1] === "--") words.splice(1, 1);
8859
+ if (words.length !== 2) return;
8860
+ path = words[1];
8861
+ } else {
8862
+ if (!/^get-content[ \t]/i.test(parts[0].trim())) return;
8863
+ if (words.shift()?.toLowerCase() !== "get-content") return;
8864
+ let raw = false;
8865
+ let pathOption = false;
8866
+ for (const word of words) {
8867
+ const option = word.toLowerCase();
8868
+ if (option === "-raw" && !raw) raw = true;
8869
+ else if ((option === "-path" || option === "-literalpath") && !pathOption && !path) pathOption = true;
8870
+ else if (!word.startsWith("-") && !path) path = word;
8871
+ else return;
8872
+ }
8873
+ }
8874
+ const nativePath = path && literalPath(path, powershell);
8875
+ if (!nativePath) return;
8876
+ if (input.workdir !== void 0 || input.workingDirectory !== void 0 || input.working_dir !== void 0) return;
8877
+ const requestedCwd = input.cwd === void 0 ? "." : input.cwd;
8878
+ if (typeof requestedCwd !== "string") return;
8879
+ const nativeCwd = literalPath(requestedCwd, powershell);
8880
+ if (!nativeCwd) return;
8881
+ const cwd = resolve(workingDir, nativeCwd);
8882
+ const candidate = resolve(cwd, nativePath);
8883
+ const skill = observedSkillRead("read", { path: candidate }, workingDir);
8884
+ if (!skill) return;
8885
+ try {
8886
+ if (!containedShellDirectory(cwd, workingDir)) return;
8887
+ if (parts.length === 2) {
8888
+ const listing = literalWords(parts[1], false);
8889
+ const directory = listing?.[1] && literalPath(listing[1], false);
8890
+ if (listing?.length !== 2 || listing[0] !== "ls" || !directory || resolve(cwd, directory) !== dirname(candidate)) return;
8891
+ }
8892
+ if (statSync(candidate).size > 1048576) return;
8893
+ const body = readFileSync(candidate, "utf8").replace(/\r\n/g, "\n");
8894
+ if (!body.trim()) return;
8895
+ return {
8896
+ skill,
8897
+ cwd,
8898
+ bodyHash: createHash("sha256").update(body).digest("hex")
8899
+ };
8900
+ } catch {
8901
+ return;
8902
+ }
8903
+ }
8904
+ /** Only model-facing output containing the complete unchanged file proves a read. */
8905
+ function completedShellSkillRead(read, completion, workingDir) {
8906
+ const data = fields(completion);
8907
+ const result = fields(data?.result);
8908
+ if (data?.success !== true || data.error || typeof result?.content !== "string") return;
8909
+ const execution = fields(data.shellExecution);
8910
+ if (data.shellExecution !== void 0 && execution?.exitCode !== 0) return;
8911
+ if (result.contents !== void 0 && !Array.isArray(result.contents)) return;
8912
+ let exited = execution?.exitCode === 0;
8913
+ let terminal = false;
8914
+ for (const value of result.contents ?? []) {
8915
+ const block = fields(value);
8916
+ if (block?.type !== "shell_exit" && block?.type !== "terminal") continue;
8917
+ terminal = true;
8918
+ if (block.exitCode !== void 0 && block.exitCode !== 0) return;
8919
+ if (block.type === "shell_exit" && block.exitCode !== 0) return;
8920
+ exited ||= block.exitCode === 0;
8921
+ if (block.cwd !== void 0) {
8922
+ const cwd = typeof block.cwd === "string" && literalPath(block.cwd, true);
8923
+ if (!cwd || resolve(read.cwd, cwd) !== read.cwd) return;
8924
+ }
8925
+ }
8926
+ if (terminal && !exited) return;
8927
+ const local = observedSkillRead("read", { path: read.skill.path }, workingDir);
8928
+ if (!local) return;
8929
+ try {
8930
+ if (!containedShellDirectory(read.cwd, workingDir)) return;
8931
+ if (statSync(local.path).size > 1048576) return;
8932
+ const body = readFileSync(local.path, "utf8").replace(/\r\n/g, "\n");
8933
+ if (createHash("sha256").update(body).digest("hex") !== read.bodyHash) return;
8934
+ if (!result.content.replace(/\r\n/g, "\n").includes(body)) return;
8935
+ return read.skill;
8936
+ } catch {
8937
+ return;
8938
+ }
8939
+ }
8811
8940
  const skillParentDirectories = [
8812
8941
  ".agents",
8813
8942
  ".claude",
@@ -9542,6 +9671,7 @@ async function runCopilot(frame, signal) {
9542
9671
  const cleanupErrors = [];
9543
9672
  const tools = /* @__PURE__ */ new Set();
9544
9673
  const skillReads = /* @__PURE__ */ new Map();
9674
+ const shellSkillReads = /* @__PURE__ */ new Map();
9545
9675
  const skillCalls = [];
9546
9676
  const inventory = new ToolInventoryBuilder(sensitive);
9547
9677
  const subagents = /* @__PURE__ */ new Map();
@@ -9688,6 +9818,8 @@ async function runCopilot(frame, signal) {
9688
9818
  if (!data.mcpServerName && !data.mcpConfigServerName) {
9689
9819
  const skillRead = observedSkillRead(data.toolName, data.arguments, frame.workingDir);
9690
9820
  if (skillRead) skillReads.set(data.toolCallId, skillRead);
9821
+ const shellRead = observedShellSkillRead(data.toolName, data.arguments, frame.workingDir);
9822
+ if (shellRead) shellSkillReads.set(data.toolCallId, shellRead);
9691
9823
  }
9692
9824
  const attributes = JSON.parse(redact(jsonSafe(mcpAttributes), sensitive));
9693
9825
  try {
@@ -9708,6 +9840,10 @@ async function runCopilot(frame, signal) {
9708
9840
  const skillRead = skillReads.get(data.toolCallId);
9709
9841
  skillReads.delete(data.toolCallId);
9710
9842
  if (skillRead && data.success === true) skillCalls.push(skillRead);
9843
+ const shellRead = shellSkillReads.get(data.toolCallId);
9844
+ shellSkillReads.delete(data.toolCallId);
9845
+ const completedRead = shellRead && completedShellSkillRead(shellRead, data, frame.workingDir);
9846
+ if (completedRead) skillCalls.push(completedRead);
9711
9847
  emit({
9712
9848
  version: 1,
9713
9849
  type: "tool",