Files
openclaw/src/plugins/host-tool-param-parsers.test.ts
Peter Steinberger e4bae42d63 feat(plugin-sdk): derive tool target paths for hooks
Summary:
- derive apply_patch target paths for before_tool_call and trusted policy events
- route native Codex PreToolUse cwd/sandbox path facts through the host parser
- document the additive derivedPaths hook field and refresh the SDK API baseline

Verification:
- pnpm test src/agents/apply-patch-paths.test.ts src/plugins/host-tool-param-parsers.test.ts src/agents/pi-tools.before-tool-call.e2e.test.ts src/agents/harness/native-hook-relay.test.ts src/plugins/contracts/host-hooks.contract.test.ts
- pnpm check:test-types
- pnpm lint:core
- pnpm plugin-sdk:api:gen
- pnpm plugin-sdk:api:check
- pnpm run check:no-conflict-markers
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md docs/plugins/hooks.md docs/.generated/plugin-sdk-api-baseline.sha256 src/agents/apply-patch-paths.test.ts src/agents/apply-patch-paths.ts src/agents/harness/native-hook-relay.test.ts src/agents/harness/native-hook-relay.ts src/agents/pi-tools.before-tool-call.e2e.test.ts src/agents/pi-tools.before-tool-call.ts src/agents/pi-tools.ts src/auto-reply/reply/dispatch-from-config.test.ts src/plugins/contracts/host-hooks.contract.test.ts src/plugins/hook-types.ts src/plugins/host-tool-param-parsers.test.ts src/plugins/host-tool-param-parsers.ts src/plugins/trusted-tool-policy.ts
- git diff --check origin/main...HEAD && git diff --check
- pnpm build

Co-authored-by: Eva <eva@100yen.org>
Co-authored-by: Josh Lehman <josh@martian.engineering>
2026-05-09 03:31:42 -04:00

120 lines
4.0 KiB
TypeScript

import path from "node:path";
import { describe, expect, it } from "vitest";
import { deriveToolParams } from "./host-tool-param-parsers.js";
const defaultCwd = process.cwd();
const cwdPath = (...segments: string[]) => path.join(defaultCwd, ...segments);
describe("deriveToolParams", () => {
it("returns an empty object for tools that have no registered parser", () => {
expect(deriveToolParams("exec", { command: "ls" })).toEqual({});
expect(deriveToolParams("read_file", { path: "/tmp/x" })).toEqual({});
});
it("ignores prototype-key tool names when looking up parsers", () => {
expect(deriveToolParams("__proto__", { input: "anything" })).toEqual({});
expect(deriveToolParams("hasOwnProperty", { input: "anything" })).toEqual({});
});
it("derives apply_patch destination paths from the input envelope", () => {
const patch = [
"*** Begin Patch",
"*** Add File: src/new.ts",
"+x",
"*** Update File: src/old.ts",
"*** Move to: src/renamed.ts",
"@@",
"+y",
"*** Delete File: src/dead.ts",
"*** End Patch",
].join("\n");
expect(deriveToolParams("apply_patch", { input: patch })).toEqual({
derivedPaths: [
cwdPath("src/new.ts"),
cwdPath("src/old.ts"),
cwdPath("src/renamed.ts"),
cwdPath("src/dead.ts"),
],
});
});
it("returns immutable derived path snapshots", () => {
const patch = ["*** Begin Patch", "*** Add File: src/new.ts", "+x", "*** End Patch"].join("\n");
const derived = deriveToolParams("apply_patch", { input: patch });
expect(Array.isArray(derived.derivedPaths)).toBe(true);
expect(Object.isFrozen(derived.derivedPaths)).toBe(true);
});
it("resolves derived apply_patch paths against the tool cwd when provided", () => {
const patch = ["*** Begin Patch", "*** Add File: @src/../new.ts", "+x", "*** End Patch"].join(
"\n",
);
const cwd = path.join("/tmp", "openclaw-derived");
expect(deriveToolParams("apply_patch", { input: patch }, { cwd })).toEqual({
derivedPaths: [path.join(cwd, "new.ts")],
});
});
it("preserves apply_patch backslashes when deriving path facts", () => {
const patch = [
"*** Begin Patch",
String.raw`*** Add File: safe\evil.ts`,
"+x",
"*** End Patch",
].join("\n");
expect(deriveToolParams("apply_patch", { input: patch })).toEqual({
derivedPaths: [path.resolve(defaultCwd, String.raw`safe\evil.ts`)],
});
});
it("preserves apply_patch marker payload bytes after the executor header trim", () => {
const patch = ["*** Begin Patch", "*** Add File: src/new.ts", "+x", "*** End Patch"].join(
"\n",
);
expect(deriveToolParams("apply_patch", { input: patch })).toEqual({
derivedPaths: [path.resolve(defaultCwd, " src/new.ts")],
});
});
it("resolves sandboxed apply_patch paths through the execution bridge", () => {
const patch = [
"*** Begin Patch",
"*** Add File: /workspace/src/new.ts",
"+x",
"*** End Patch",
].join("\n");
expect(
deriveToolParams(
"apply_patch",
{ input: patch },
{
cwd: "/workspace",
sandbox: {
root: "/workspace",
bridge: {
resolvePath: ({ filePath }: { filePath: string }) => ({
containerPath: filePath,
hostPath: "/host/sandbox/src/new.ts",
relativePath: "src/new.ts",
}),
} as never,
},
},
),
).toEqual({
derivedPaths: ["/host/sandbox/src/new.ts"],
});
});
it("returns an empty object when apply_patch input has no recognised paths", () => {
expect(deriveToolParams("apply_patch", { input: "not a patch" })).toEqual({});
expect(deriveToolParams("apply_patch", {})).toEqual({});
expect(deriveToolParams("apply_patch", undefined)).toEqual({});
});
it("does not throw for malformed param shapes", () => {
expect(deriveToolParams("apply_patch", null)).toEqual({});
expect(deriveToolParams("apply_patch", 42)).toEqual({});
});
});