fix: make binary shims for custom tools cross-device compatible so users can't accidentally break sandboxes via sbx cp ~/.config/coyote <sbx-name>:/home/agent/.config

This commit is contained in:
2026-08-31 13:48:56 -06:00
parent c102da5665
commit 016f2654e4
7 changed files with 278 additions and 103 deletions
+21 -5
View File
@@ -27,14 +27,30 @@ def _ensure_cwd_venv():
_ensure_cwd_venv()
def resolve_dir(env_name, default_path):
"""Resolve a directory at run time.
Prefer the override env var when set, otherwise fall back to the default
path derived from this script's own location, so the shim keeps working
when the config dir moves or is shared across environments with different
home directories.
"""
value = os.environ.get(env_name)
if value:
return value
return os.path.normpath(default_path)
def main():
(agent_func, raw_data) = parse_argv()
agent_data = parse_raw_data(raw_data)
root_dir = "{config_dir}"
setup_env(root_dir, agent_func, raw_data)
self_dir = os.path.dirname(os.path.abspath(__file__))
agent_dir = os.path.normpath(os.path.join(self_dir, ".."))
root_dir = resolve_dir("{root_dir_env}", os.path.join(self_dir, "{root_dir_rel}"))
setup_env(root_dir, agent_dir, agent_func, raw_data)
agent_tools_path = os.path.join(root_dir, "agents/{agent_name}/tools.py")
agent_tools_path = os.path.join(agent_dir, "tools.py")
run(agent_tools_path, agent_func, agent_data)
@@ -65,12 +81,12 @@ def parse_argv():
return agent_func, agent_data
def setup_env(root_dir, agent_func, raw_data):
def setup_env(root_dir, agent_dir, agent_func, raw_data):
load_env(os.path.join(root_dir, ".env"))
os.environ["LLM_ROOT_DIR"] = root_dir
os.environ["LLM_AGENT_NAME"] = "{agent_name}"
os.environ["LLM_AGENT_FUNC"] = agent_func
os.environ["LLM_AGENT_ROOT_DIR"] = os.path.join(root_dir, "agents", "{agent_name}")
os.environ["LLM_AGENT_ROOT_DIR"] = agent_dir
os.environ["LLM_AGENT_CACHE_DIR"] = os.path.join(root_dir, "cache", "{agent_name}")
os.environ["LLM_AGENT_RAW_JSON"] = raw_data
+25 -5
View File
@@ -5,13 +5,30 @@
set -e
main() {
root_dir="{config_dir}"
self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
agent_dir="$(cd "$self_dir/.." && pwd)"
root_dir="$(resolve_dir "{root_dir_env}" "$self_dir/{root_dir_rel}")"
functions_dir="$(resolve_dir "{functions_dir_env}" "$self_dir/{functions_dir_rel}")"
parse_argv "$@"
setup_env
tools_path="$root_dir/agents/{agent_name}/tools.sh"
tools_path="$agent_dir/tools.sh"
run
}
# Resolve a directory at run time: prefer the override env var ($1) when set,
# otherwise fall back to the default path ($2) derived from this script's own
# location, so the shim keeps working when the config dir moves or is shared
# across environments with different home directories.
resolve_dir() {
local override
override="$(printenv "$1" 2>/dev/null || true)"
if [[ -n "$override" ]]; then
echo "$override"
else
(cd "$2" 2>/dev/null && pwd) || echo "$2"
fi
}
parse_argv() {
agent_func="$1"
if [[ -n "$LLM_TOOL_DATA_FILE" ]] && [[ -f "$LLM_TOOL_DATA_FILE" ]]; then
@@ -29,9 +46,9 @@ setup_env() {
export LLM_ROOT_DIR="$root_dir"
export LLM_AGENT_NAME="{agent_name}"
export LLM_AGENT_FUNC="$agent_func"
export LLM_AGENT_ROOT_DIR="$LLM_ROOT_DIR/agents/{agent_name}"
export LLM_AGENT_ROOT_DIR="$agent_dir"
export LLM_AGENT_CACHE_DIR="$LLM_ROOT_DIR/cache/{agent_name}"
export LLM_PROMPT_UTILS_FILE="{prompt_utils_file}"
export LLM_PROMPT_UTILS_FILE="$functions_dir/utils/prompt-utils.sh"
export LLM_AGENT_RAW_JSON="$agent_data"
}
@@ -59,6 +76,10 @@ run() {
die "error: no JSON data"
fi
if [[ ! -f "$tools_path" ]]; then
die "error: agent tools script not found: $tools_path"
fi
if [[ "$OS" == "Windows_NT" ]]; then
set -o igncr
tools_path="$(cygpath -w "$tools_path")"
@@ -122,4 +143,3 @@ die() {
}
main "$@"
+33 -7
View File
@@ -3,17 +3,38 @@
// Usage: ./{agent_name}.ts <agent-func> <agent-data>
import { readFileSync, writeFileSync, existsSync } from "fs";
import { join } from "path";
import { pathToFileURL } from "url";
import { dirname, join, resolve } from "path";
import { fileURLToPath, pathToFileURL } from "url";
function selfDir(): string {
if (typeof __dirname !== "undefined") {
return __dirname;
}
return dirname(fileURLToPath(import.meta.url));
}
// Resolve a directory at run time: prefer the override env var when set,
// otherwise fall back to the default path derived from this script's own
// location, so the shim keeps working when the config dir moves or is shared
// across environments with different home directories.
function resolveDir(envName: string, defaultPath: string): string {
const value = process.env[envName];
if (value) {
return value;
}
return resolve(defaultPath);
}
async function main(): Promise<void> {
const { agentFunc, rawData } = parseArgv();
const agentData = parseRawData(rawData);
const configDir = "{config_dir}";
setupEnv(configDir, agentFunc, rawData);
const binDir = selfDir();
const agentDir = resolve(binDir, "..");
const configDir = resolveDir("{root_dir_env}", join(binDir, "{root_dir_rel}"));
setupEnv(configDir, agentDir, agentFunc, rawData);
const agentToolsPath = join(configDir, "agents", "{agent_name}", "tools.ts");
const agentToolsPath = join(agentDir, "tools.ts");
await run(agentToolsPath, agentFunc, agentData);
}
@@ -48,12 +69,17 @@ function parseArgv(): { agentFunc: string; rawData: string } {
return { agentFunc, rawData: agentData };
}
function setupEnv(configDir: string, agentFunc: string, rawData: string): void {
function setupEnv(
configDir: string,
agentDir: string,
agentFunc: string,
rawData: string,
): void {
loadEnv(join(configDir, ".env"));
process.env["LLM_ROOT_DIR"] = configDir;
process.env["LLM_AGENT_NAME"] = "{agent_name}";
process.env["LLM_AGENT_FUNC"] = agentFunc;
process.env["LLM_AGENT_ROOT_DIR"] = join(configDir, "agents", "{agent_name}");
process.env["LLM_AGENT_ROOT_DIR"] = agentDir;
process.env["LLM_AGENT_CACHE_DIR"] = join(configDir, "cache", "{agent_name}");
process.env["LLM_AGENT_RAW_JSON"] = rawData;
}
+18 -2
View File
@@ -27,14 +27,30 @@ def _ensure_cwd_venv():
_ensure_cwd_venv()
def resolve_dir(env_name, default_path):
"""Resolve a directory at run time.
Prefer the override env var when set, otherwise fall back to the default
path derived from this script's own location, so the shim keeps working
when the config dir moves or is shared across environments with different
home directories.
"""
value = os.environ.get(env_name)
if value:
return value
return os.path.normpath(default_path)
def main():
raw_data = parse_argv()
tool_data = parse_raw_data(raw_data)
root_dir = "{root_dir}"
self_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = resolve_dir("{root_dir_env}", os.path.join(self_dir, "{root_dir_rel}"))
functions_dir = resolve_dir("{functions_dir_env}", os.path.join(self_dir, "{functions_dir_rel}"))
setup_env(root_dir, raw_data)
tool_path = "{tool_path}.py"
tool_path = os.path.join(functions_dir, "tools", "{function_name}.py")
run(tool_path, "run", tool_data)
+23 -3
View File
@@ -5,13 +5,29 @@
set -e
main() {
root_dir="{root_dir}"
self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root_dir="$(resolve_dir "{root_dir_env}" "$self_dir/{root_dir_rel}")"
functions_dir="$(resolve_dir "{functions_dir_env}" "$self_dir/{functions_dir_rel}")"
parse_argv "$@"
setup_env
tool_path="{tool_path}.sh"
tool_path="$functions_dir/tools/{function_name}.sh"
run
}
# Resolve a directory at run time: prefer the override env var ($1) when set,
# otherwise fall back to the default path ($2) derived from this script's own
# location, so the shim keeps working when the config dir moves or is shared
# across environments with different home directories.
resolve_dir() {
local override
override="$(printenv "$1" 2>/dev/null || true)"
if [[ -n "$override" ]]; then
echo "$override"
else
(cd "$2" 2>/dev/null && pwd) || echo "$2"
fi
}
parse_argv() {
if [[ -n "$LLM_TOOL_DATA_FILE" ]] && [[ -f "$LLM_TOOL_DATA_FILE" ]]; then
tool_data="$(cat "$LLM_TOOL_DATA_FILE")"
@@ -28,7 +44,7 @@ setup_env() {
export LLM_ROOT_DIR="$root_dir"
export LLM_TOOL_NAME="{function_name}"
export LLM_TOOL_CACHE_DIR="$LLM_ROOT_DIR/cache/{function_name}"
export LLM_PROMPT_UTILS_FILE="{prompt_utils_file}"
export LLM_PROMPT_UTILS_FILE="$functions_dir/utils/prompt-utils.sh"
export LLM_TOOL_RAW_JSON="$tool_data"
}
@@ -56,6 +72,10 @@ run() {
die "error: no JSON data"
fi
if [[ ! -f "$tool_path" ]]; then
die "error: tool script not found: $tool_path"
fi
if [[ "$OS" == "Windows_NT" ]]; then
set -o igncr
tool_path="$(cygpath -w "$tool_path")"
+28 -4
View File
@@ -3,17 +3,41 @@
// Usage: ./{function_name}.ts <tool-data>
import { readFileSync, writeFileSync, existsSync } from "fs";
import { join } from "path";
import { pathToFileURL } from "url";
import { dirname, join, resolve } from "path";
import { fileURLToPath, pathToFileURL } from "url";
function selfDir(): string {
if (typeof __dirname !== "undefined") {
return __dirname;
}
return dirname(fileURLToPath(import.meta.url));
}
// Resolve a directory at run time: prefer the override env var when set,
// otherwise fall back to the default path derived from this script's own
// location, so the shim keeps working when the config dir moves or is shared
// across environments with different home directories.
function resolveDir(envName: string, defaultPath: string): string {
const value = process.env[envName];
if (value) {
return value;
}
return resolve(defaultPath);
}
async function main(): Promise<void> {
const rawData = parseArgv();
const toolData = parseRawData(rawData);
const rootDir = "{root_dir}";
const binDir = selfDir();
const rootDir = resolveDir("{root_dir_env}", join(binDir, "{root_dir_rel}"));
const functionsDir = resolveDir(
"{functions_dir_env}",
join(binDir, "{functions_dir_rel}"),
);
setupEnv(rootDir, rawData);
const toolPath = "{tool_path}.ts";
const toolPath = join(functionsDir, "tools", "{function_name}.ts");
await run(toolPath, "run", toolData);
}