feat: add exec pathPrepend config

This commit is contained in:
Peter Steinberger
2026-01-19 00:35:39 +00:00
parent d9384785a3
commit 953472bf25
10 changed files with 133 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ describe("normalizeConfigPaths", () => {
const { normalizeConfigPaths } = await import("./normalize-paths.js");
const cfg = normalizeConfigPaths({
tools: { exec: { pathPrepend: ["~/bin"] } },
plugins: { load: { paths: ["~/plugins/a"] } },
logging: { file: "~/.clawdbot/logs/clawdbot.log" },
hooks: {
@@ -49,6 +50,7 @@ describe("normalizeConfigPaths", () => {
expect(cfg.logging?.file).toBe(path.join(home, ".clawdbot", "logs", "clawdbot.log"));
expect(cfg.hooks?.path).toBe(path.join(home, ".clawdbot", "hooks.json5"));
expect(cfg.hooks?.transformsDir).toBe(path.join(home, "hooks-xform"));
expect(cfg.tools?.exec?.pathPrepend?.[0]).toBe(path.join(home, "bin"));
expect(cfg.channels?.telegram?.accounts?.personal?.tokenFile).toBe(
path.join(home, ".clawdbot", "telegram.token"),
);

View File

@@ -4,7 +4,7 @@ import type { ClawdbotConfig } from "./types.js";
const PATH_VALUE_RE = /^~(?=$|[\\/])/;
const PATH_KEY_RE = /(dir|path|paths|file|root|workspace)$/i;
const PATH_LIST_KEYS = new Set(["paths"]);
const PATH_LIST_KEYS = new Set(["paths", "pathPrepend"]);
function isPlainObject(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);

View File

@@ -146,6 +146,7 @@ const FIELD_LABELS: Record<string, string> = {
"tools.exec.security": "Exec Security",
"tools.exec.ask": "Exec Ask",
"tools.exec.node": "Exec Node Binding",
"tools.exec.pathPrepend": "Exec PATH Prepend",
"tools.message.allowCrossContextSend": "Allow Cross-Context Messaging",
"tools.message.crossContext.allowWithinProvider": "Allow Cross-Context (Same Provider)",
"tools.message.crossContext.allowAcrossProviders": "Allow Cross-Context (Across Providers)",
@@ -323,6 +324,8 @@ const FIELD_HELP: Record<string, string> = {
'Optional allowlist of model ids (e.g. "gpt-5.2" or "openai/gpt-5.2").',
"tools.exec.notifyOnExit":
"When true (default), backgrounded exec sessions enqueue a system event and request a heartbeat on exit.",
"tools.exec.pathPrepend":
"Directories to prepend to PATH for exec runs (gateway/sandbox).",
"tools.message.allowCrossContextSend":
"Legacy override: allow cross-context sends across all providers.",
"tools.message.crossContext.allowWithinProvider":

View File

@@ -129,6 +129,8 @@ export type ExecToolConfig = {
ask?: "off" | "on-miss" | "always";
/** Default node binding for exec.host=node (node id/name). */
node?: string;
/** Directories to prepend to PATH when running exec (gateway/sandbox). */
pathPrepend?: string[];
/** Default time (ms) before an exec command auto-backgrounds. */
backgroundMs?: number;
/** Default timeout (seconds) before auto-killing exec commands. */

View File

@@ -183,6 +183,25 @@ export const AgentToolsSchema = z
allowFrom: ElevatedAllowFromSchema,
})
.optional(),
exec: z
.object({
host: z.enum(["sandbox", "gateway", "node"]).optional(),
security: z.enum(["deny", "allowlist", "full"]).optional(),
ask: z.enum(["off", "on-miss", "always"]).optional(),
node: z.string().optional(),
pathPrepend: z.array(z.string()).optional(),
backgroundMs: z.number().int().positive().optional(),
timeoutSec: z.number().int().positive().optional(),
cleanupMs: z.number().int().positive().optional(),
notifyOnExit: z.boolean().optional(),
applyPatch: z
.object({
enabled: z.boolean().optional(),
allowModels: z.array(z.string()).optional(),
})
.optional(),
})
.optional(),
sandbox: z
.object({
tools: ToolPolicySchema,
@@ -362,6 +381,7 @@ export const ToolsSchema = z
security: z.enum(["deny", "allowlist", "full"]).optional(),
ask: z.enum(["off", "on-miss", "always"]).optional(),
node: z.string().optional(),
pathPrepend: z.array(z.string()).optional(),
backgroundMs: z.number().int().positive().optional(),
timeoutSec: z.number().int().positive().optional(),
cleanupMs: z.number().int().positive().optional(),