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.
- package/dist/delegate-runner.js +137 -1
- package/dist/delegate-runner.js.map +1 -1
- package/package.json +1 -1
package/dist/delegate-runner.js
CHANGED
|
@@ -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",
|