Opencode improvements

This commit is contained in:
Samuel Aubertin
2026-04-15 03:31:04 +02:00
parent 6ca643830f
commit 273e42dd2d
8 changed files with 537 additions and 165 deletions

View File

@@ -20,3 +20,5 @@ Do not remove existing instructions unless they are outdated or wrong.
`bash tests/run_tests.sh` (you can also run them separately) `bash tests/run_tests.sh` (you can also run them separately)
- When running tests from inside sloptrap, inherited `CODEX_HOME=/codex` plus `SLOPTRAP_PREFER_CODEX_HOME=1` can leak into host-style child launches; ignore that preference when `HOME` has been redirected elsewhere and the runtime hints still point into the inherited `/codex` tree. - When running tests from inside sloptrap, inherited `CODEX_HOME=/codex` plus `SLOPTRAP_PREFER_CODEX_HOME=1` can leak into host-style child launches; ignore that preference when `HOME` has been redirected elsewhere and the runtime hints still point into the inherited `/codex` tree.
- Capability-enabled runs are Podman-only. `packet-capture` uses a dedicated helper container/pod, and host-network capture must prompt for an explicit acknowledgement on every runtime launch. - Capability-enabled runs are Podman-only. `packet-capture` uses a dedicated helper container/pod, and host-network capture must prompt for an explicit acknowledgement on every runtime launch.
- `agent=opencode` should download the latest Linux CLI release artifact from GitHub into the build context and verify its digest; it should not depend on a host-installed `opencode` binary.
- For isolated networking, sloptrap exposes the host inside the container as `sloptrap.host`; opencode localhost URLs should be rewritten to that alias, and Podman `slirp4netns` runs need `allow_host_loopback=true` for host-local servers.

View File

@@ -1,28 +0,0 @@
# Dockerfile.sloptrap
ARG BASE_IMAGE=debian:trixie-slim
FROM ${BASE_IMAGE}
ENV DEBIAN_FRONTEND=noninteractive
ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 ripgrep xxd file procps util-linux"
ARG EXTRA_PACKAGES=""
RUN apt-get update \
&& apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} \
&& rm -rf /var/lib/apt/lists/*
ARG CODEX_UID=1337
ARG CODEX_GID=1337
RUN groupadd --gid ${CODEX_GID} sloptrap \
&& useradd --create-home --home-dir /home/sloptrap \
--gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap
ARG CODEX_BIN=codex
ARG CODEX_BIN_PATH=/usr/local/bin/codex
COPY ${CODEX_BIN} ${CODEX_BIN_PATH}
RUN chmod 0755 ${CODEX_BIN_PATH} \
&& chown -R sloptrap:sloptrap /home/sloptrap
WORKDIR /workspace
ENV SHELL=/bin/bash HOME=/home/sloptrap
ENTRYPOINT ["${CODEX_BIN_PATH}"]

View File

@@ -63,8 +63,9 @@ Supported keys when the manifest is present:
| `name` | project directory name | Must match `^[A-Za-z0-9_.-]+$`. Used for image/container naming. | | `name` | project directory name | Must match `^[A-Za-z0-9_.-]+$`. Used for image/container naming. |
| `packages_extra` | *empty* | Additional Debian packages installed during `docker/podman build`. Tokens must be alphanumeric plus `+.-`. | | `packages_extra` | *empty* | Additional Debian packages installed during `docker/podman build`. Tokens must be alphanumeric plus `+.-`. |
| `agent` | `codex` | AI backend: `codex` (OpenAI Codex CLI) or `opencode` (Anomaly opencode CLI). | | `agent` | `codex` | AI backend: `codex` (OpenAI Codex CLI) or `opencode` (Anomaly opencode CLI). |
| `opencode_server` | `http://localhost:11434` | OpenAI-compatible server URL (opencode only). Supports llama.cpp, Ollama, vLLM, etc. | | `opencode_server` | `http://localhost:8080` | OpenAI-compatible server URL (opencode only). Supports llama.cpp, Ollama, vLLM, etc. |
| `opencode_model` | `llama3` | Model name on the server (opencode only). | | `opencode_model` | `bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0` | Model name on the server (opencode only). |
| `opencode_context` | `256K` | Context window for the opencode model. Accepts an integer optionally suffixed with `K`, `M`, or `G`. |
| `allow_host_network` | `false` | `true` opts into `--network host`; keep `false` unless the project absolutely requires direct access to host-local services. | | `allow_host_network` | `false` | `true` opts into `--network host`; keep `false` unless the project absolutely requires direct access to host-local services. |
Values containing `$`, `` ` ``, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start. Values containing `$`, `` ` ``, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start.
@@ -73,7 +74,7 @@ Values containing `$`, `` ` ``, or newlines are rejected to prevent command inje
**Codex** (default): Uses OpenAI Codex CLI with state stored in `~/.codex/`. Supports login mode for credential sharing. **Codex** (default): Uses OpenAI Codex CLI with state stored in `~/.codex/`. Supports login mode for credential sharing.
**opencode**: Uses Anomaly opencode CLI with state stored in `~/.opencode/`. Connects to any OpenAI-compatible inference server (llama.cpp, Ollama, vLLM, etc.). No authentication required for self-hosted models; API keys supported via manifest if needed. **opencode**: Uses Anomaly opencode CLI with state stored in `~/.opencode/`. sloptrap downloads the latest Linux CLI release artifact from Anomaly during image builds, verifies its digest from the GitHub release metadata, and copies it into the container image. Connects to any OpenAI-compatible inference server (llama.cpp, Ollama, vLLM, etc.). When `opencode_server` points at `localhost` under isolated networking, sloptrap rewrites it to `http://sloptrap.host:...` so host-local model servers remain reachable from inside the container. No authentication required for self-hosted models; API keys supported via manifest if needed.
### `.sloptrapignore` ### `.sloptrapignore`
@@ -142,7 +143,7 @@ The launcher executes targets sequentially, so `./sloptrap repo build run` perfo
- **Codex**: project directory at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` at `/codex`; auth at `/codex/auth.json`. - **Codex**: project directory at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` at `/codex`; auth at `/codex/auth.json`.
- **opencode**: project directory at `/workspace`; `${HOME}/.opencode/sloptrap/state/<project-hash>` at `/codex/state/opencode`; state at `/codex/state`. - **opencode**: project directory at `/workspace`; `${HOME}/.opencode/sloptrap/state/<project-hash>` at `/codex/state/opencode`; state at `/codex/state`.
- Ignore filter: `.sloptrapignore` entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to the agent. - Ignore filter: `.sloptrapignore` entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to the agent.
- Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`. - Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`. For isolated runs, sloptrap injects `sloptrap.host` as a container-side hostname for the host gateway. On Podman `slirp4netns`, opencode runs also enable host loopback access so host-local servers bound to `localhost` remain reachable.
- Process context: standard runs drop capabilities, set `no-new-privileges`, use a read-only root filesystem, and keep scratch paths (`/tmp`, `/run`, `/run/lock`) on tmpfs. - Process context: standard runs drop capabilities, set `no-new-privileges`, use a read-only root filesystem, and keep scratch paths (`/tmp`, `/run`, `/run/lock`) on tmpfs.
- Agent configuration: - Agent configuration:
- **Codex**: runtime flags fixed to `--sandbox danger-full-access --ask-for-approval never`. Supports login mode for credential sharing. - **Codex**: runtime flags fixed to `--sandbox danger-full-access --ask-for-approval never`. Supports login mode for credential sharing.

374
sloptrap
View File

@@ -242,7 +242,11 @@ ALLOW_HOST_NETWORK=false
BACKEND="codex" BACKEND="codex"
OPENCODE_SERVER="" OPENCODE_SERVER=""
OPENCODE_MODEL="" OPENCODE_MODEL=""
OPENCODE_CONTEXT=""
OPENCODE_STATE_HOME_HOST="" OPENCODE_STATE_HOME_HOST=""
OPENCODE_CONFIG_HOST=""
OPENCODE_CONFIG_CONT=""
SLOPTRAP_HOST_ALIAS=""
declare -a SLOPTRAP_TEMP_PATHS=() declare -a SLOPTRAP_TEMP_PATHS=()
@@ -302,18 +306,29 @@ create_temp_dir() {
write_embedded_dockerfile() { write_embedded_dockerfile() {
if [[ "$BACKEND" == "opencode" ]]; then if [[ "$BACKEND" == "opencode" ]]; then
cat <<'EOF' local opencode_pkg
case "$(uname -m)" in
x86_64|amd64)
opencode_pkg="opencode-desktop-linux-amd64.deb"
;;
arm64|aarch64)
opencode_pkg="opencode-desktop-linux-arm64.deb"
;;
*)
error "unsupported architecture for opencode"
;;
esac
local dockerfile_content
dockerfile_content=$(cat <<'DOCKERFILE_EOF'
# Dockerfile.sloptrap # Dockerfile.sloptrap
ARG BASE_IMAGE=debian:trixie-slim ARG BASE_IMAGE=debian:trixie-slim
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 ripgrep xxd file procps util-linux" ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 wget ripgrep xxd file procps util-linux binutils"
ARG EXTRA_PACKAGES="" RUN apt-get update && apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} && rm -rf /var/lib/apt/lists/*
RUN apt-get update \
&& apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} \
&& rm -rf /var/lib/apt/lists/*
ARG CODEX_UID=1337 ARG CODEX_UID=1337
ARG CODEX_GID=1337 ARG CODEX_GID=1337
@@ -330,8 +345,12 @@ RUN chmod 0755 /usr/local/bin/opencode \
WORKDIR /workspace WORKDIR /workspace
ENV SHELL=/bin/bash HOME=/home/sloptrap ENV SHELL=/bin/bash HOME=/home/sloptrap
ENTRYPOINT ["/usr/local/bin/opencode"] ENTRYPOINT ["opencode"]
EOF DOCKERFILE_EOF
)
# Replace placeholder with actual package name
dockerfile_content=$(printf '%s' "$dockerfile_content" | sed "s|PLACEHOLDER_OPENCODE_PKG|${opencode_pkg}|g")
printf '%s' "$dockerfile_content"
else else
cat <<'EOF' cat <<'EOF'
# Dockerfile.sloptrap # Dockerfile.sloptrap
@@ -725,6 +744,24 @@ validate_package_list() {
done done
} }
# Common package name aliases for user convenience
expand_package_alias() {
local pkg=$1
case "$pkg" in
rg) printf 'ripgrep' ;;
git) printf 'git' ;;
curl) printf 'curl' ;;
bash) printf 'bash' ;;
ca-certificates) printf 'ca-certificates' ;;
libstdc++6) printf 'libstdc++6' ;;
xxd) printf 'xxd' ;;
file) printf 'file' ;;
procps) printf 'procps' ;;
util-linux) printf 'util-linux' ;;
*) printf '%s' "$pkg" ;;
esac
}
detect_container_engine() { detect_container_engine() {
local override=${SLOPTRAP_CONTAINER_ENGINE-} local override=${SLOPTRAP_CONTAINER_ENGINE-}
if [[ -n $override ]]; then if [[ -n $override ]]; then
@@ -815,6 +852,26 @@ normalize_wizard_allow_host_network() {
esac esac
} }
parse_opencode_context() {
local raw=$1
local upper=${raw^^}
local number suffix multiplier=1
[[ -n $upper ]] || error "$MANIFEST_PATH: opencode_context must not be empty"
if [[ $upper =~ ^([0-9]+)([KMG]?)$ ]]; then
number=${BASH_REMATCH[1]}
suffix=${BASH_REMATCH[2]}
else
error "$MANIFEST_PATH: opencode_context must be an integer optionally suffixed with K, M, or G (got '$raw')"
fi
case "$suffix" in
"") multiplier=1 ;;
K) multiplier=1024 ;;
M) multiplier=$((1024 * 1024)) ;;
G) multiplier=$((1024 * 1024 * 1024)) ;;
esac
printf '%s' $(( number * multiplier ))
}
run_wizard() { run_wizard() {
local manifest_path=$1 local manifest_path=$1
if [[ -L $manifest_path ]]; then if [[ -L $manifest_path ]]; then
@@ -832,13 +889,15 @@ run_wizard() {
local default_allow_host_network local default_allow_host_network
local default_opencode_server local default_opencode_server
local default_opencode_model local default_opencode_model
local default_opencode_context
default_name=$(manifest_default_value "name" "$(basename "$CODE_DIR")") default_name=$(manifest_default_value "name" "$(basename "$CODE_DIR")")
default_packages_extra=$(manifest_default_value "packages_extra" "") default_packages_extra=$(manifest_default_value "packages_extra" "")
default_agent=$(manifest_default_value "agent" "codex") default_agent=$(manifest_default_value "agent" "codex")
default_allow_host_network=$(manifest_default_value "allow_host_network" "false") default_allow_host_network=$(manifest_default_value "allow_host_network" "false")
default_opencode_server=$(manifest_default_value "opencode_server" "http://localhost:11434") default_opencode_server=$(manifest_default_value "opencode_server" "http://localhost:8080")
default_opencode_model=$(manifest_default_value "opencode_model" "llama3") default_opencode_model=$(manifest_default_value "opencode_model" "bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0")
default_opencode_context=$(manifest_default_value "opencode_context" "256K")
local action="Creating" local action="Creating"
if [[ -f $manifest_path ]]; then if [[ -f $manifest_path ]]; then
@@ -886,22 +945,32 @@ run_wizard() {
# If opencode, prompt for server and model # If opencode, prompt for server and model
if [[ "$default_agent" == "opencode" ]]; then if [[ "$default_agent" == "opencode" ]]; then
while true; do while true; do
info_line "opencode_server: OpenAI-compatible server URL (e.g., http://localhost:11434).\n" info_line "opencode_server: OpenAI-compatible server URL (e.g., http://localhost:8080).\n"
value=$(prompt_manifest_value "opencode_server" "$default_opencode_server") value=$(prompt_manifest_value "opencode_server" "$default_opencode_server")
value=$(trim "$value") value=$(trim "$value")
[[ -n $value ]] || value="http://localhost:11434" [[ -n $value ]] || value="http://localhost:8080"
default_opencode_server=$value default_opencode_server=$value
break break
done done
while true; do while true; do
info_line "opencode_model: Model name on the server (e.g., llama3).\n" info_line "opencode_model: Model name on the server (e.g., bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0).\n"
value=$(prompt_manifest_value "opencode_model" "$default_opencode_model") value=$(prompt_manifest_value "opencode_model" "$default_opencode_model")
value=$(trim "$value") value=$(trim "$value")
[[ -n $value ]] || value="llama3" [[ -n $value ]] || value="bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0"
default_opencode_model=$value default_opencode_model=$value
break break
done done
while true; do
info_line "opencode_context: Context window for the model (e.g., 256K).\n"
value=$(prompt_manifest_value "opencode_context" "$default_opencode_context")
value=$(trim "$value")
[[ -n $value ]] || value="256K"
parse_opencode_context "$value" >/dev/null
default_opencode_context=$value
break
done
fi fi
while true; do while true; do
@@ -924,6 +993,7 @@ EOF
cat >> "$manifest_path" <<EOF cat >> "$manifest_path" <<EOF
opencode_server=$default_opencode_server opencode_server=$default_opencode_server
opencode_model=$default_opencode_model opencode_model=$default_opencode_model
opencode_context=$default_opencode_context
EOF EOF
fi fi
info_line "Wrote %s\n" "$manifest_path" info_line "Wrote %s\n" "$manifest_path"
@@ -948,7 +1018,10 @@ print_config() {
info_line "backend=%s\n" "opencode" info_line "backend=%s\n" "opencode"
info_line "opencode_server=%s\n" "$OPENCODE_SERVER" info_line "opencode_server=%s\n" "$OPENCODE_SERVER"
info_line "opencode_model=%s\n" "$OPENCODE_MODEL" info_line "opencode_model=%s\n" "$OPENCODE_MODEL"
info_line "opencode_context=%s\n" "$OPENCODE_CONTEXT"
info_line "opencode_state_home=%s\n" "$OPENCODE_STATE_HOME_HOST" info_line "opencode_state_home=%s\n" "$OPENCODE_STATE_HOME_HOST"
info_line "opencode_config=%s\n" "$OPENCODE_CONFIG_HOST"
info_line "runtime_flags=\n"
else else
info_line "backend=%s\n" "codex" info_line "backend=%s\n" "codex"
info_line "codex_home=%s\n" "$CODEX_STATE_HOME_HOST" info_line "codex_home=%s\n" "$CODEX_STATE_HOME_HOST"
@@ -959,9 +1032,10 @@ print_config() {
info_line "codex_home_bootstrap=%s\n" "$CODEX_HOME_BOOTSTRAP" info_line "codex_home_bootstrap=%s\n" "$CODEX_HOME_BOOTSTRAP"
info_line "codex_archive=%s\n" "$SLOPTRAP_CODEX_ARCHIVE" info_line "codex_archive=%s\n" "$SLOPTRAP_CODEX_ARCHIVE"
info_line "codex_url=%s\n" "$SLOPTRAP_CODEX_URL" info_line "codex_url=%s\n" "$SLOPTRAP_CODEX_URL"
fi
info_line "needs_login=%s\n" "$NEED_LOGIN"
info_line "runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY" info_line "runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
fi
info_line "host_alias=%s\n" "${SLOPTRAP_HOST_ALIAS:-}"
info_line "needs_login=%s\n" "$NEED_LOGIN"
info_line "ignore_stub_base=%s\n" "$IGNORE_STUB_BASE" info_line "ignore_stub_base=%s\n" "$IGNORE_STUB_BASE"
if [[ ${#SLOPTRAP_IGNORE_ENTRIES[@]} -gt 0 ]]; then if [[ ${#SLOPTRAP_IGNORE_ENTRIES[@]} -gt 0 ]]; then
local ignore_paths local ignore_paths
@@ -990,10 +1064,16 @@ print_manifest_summary() {
comment_line " backend=%s\n" "opencode" comment_line " backend=%s\n" "opencode"
comment_line " opencode_server=%s\n" "$OPENCODE_SERVER" comment_line " opencode_server=%s\n" "$OPENCODE_SERVER"
comment_line " opencode_model=%s\n" "$OPENCODE_MODEL" comment_line " opencode_model=%s\n" "$OPENCODE_MODEL"
comment_line " opencode_context=%s\n" "$OPENCODE_CONTEXT"
comment_line " opencode_config=%s\n" "$OPENCODE_CONFIG_CONT"
comment_line " runtime_flags=\n"
else else
comment_line " backend=%s\n" "codex" comment_line " backend=%s\n" "codex"
fi
comment_line " runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY" comment_line " runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
fi
if [[ -n $SLOPTRAP_HOST_ALIAS ]]; then
comment_line " host_alias=%s\n" "$SLOPTRAP_HOST_ALIAS"
fi
comment_line " allow_host_network=%s\n" "$ALLOW_HOST_NETWORK" comment_line " allow_host_network=%s\n" "$ALLOW_HOST_NETWORK"
} }
@@ -1019,6 +1099,10 @@ Current resolved sloptrap state:
- network_mode=$network_mode (host when host networking is enabled; otherwise isolated) - network_mode=$network_mode (host when host networking is enabled; otherwise isolated)
EOF EOF
) )
if [[ -n $SLOPTRAP_HOST_ALIAS ]]; then
prompt+=$'\n'
prompt+="- host_alias=$SLOPTRAP_HOST_ALIAS (container-side address for services running on the host)\n"
fi
printf '%s' "$prompt" printf '%s' "$prompt"
} }
@@ -1063,7 +1147,7 @@ get_env_default() {
validate_codex_archive_name() { validate_codex_archive_name() {
local name=$1 local name=$1
[[ $name =~ ^codex-[A-Za-z0-9_.-]+$ ]] || error "invalid Codex archive name '$name'" [[ $name =~ ^(codex|opencode)-[A-Za-z0-9_.-]+$ ]] || error "invalid binary name '$name'"
} }
detect_codex_archive_name() { detect_codex_archive_name() {
@@ -1186,6 +1270,106 @@ ensure_opencode_storage_paths() {
ensure_codex_directory "$state_root" "sloptrap Opencode namespace" ensure_codex_directory "$state_root" "sloptrap Opencode namespace"
ensure_codex_directory "$state_bucket" "sloptrap Opencode state root" ensure_codex_directory "$state_bucket" "sloptrap Opencode state root"
ensure_codex_directory "$CODEX_STATE_HOME_HOST" "project Opencode state" ensure_codex_directory "$CODEX_STATE_HOME_HOST" "project Opencode state"
ensure_codex_directory "$OPENCODE_STATE_HOME_HOST" "project Opencode runtime state"
}
ensure_opencode_config() {
local provider_id="llama.cpp"
local provider_name="llama-server (local)"
local base_url="$OPENCODE_SERVER"
local context_limit
context_limit=$(parse_opencode_context "$OPENCODE_CONTEXT")
local model_key="${OPENCODE_MODEL} - ${context_limit}"
local model_ref="${provider_id}/${model_key}"
local config_dir
case "$base_url" in
*/v1|*/v1/|*/v1\?*|*/v1\#*)
;;
*)
base_url="${base_url%/}/v1"
;;
esac
config_dir=$(dirname "$OPENCODE_CONFIG_HOST")
if $DRY_RUN; then
print_command mkdir -p "$config_dir"
print_command env OPENCODE_CONFIG="$OPENCODE_CONFIG_CONT" opencode
return 0
fi
ensure_codex_directory "$config_dir" "project Opencode config"
if ! jq -n \
--arg schema "https://opencode.ai/config.json" \
--arg provider_id "$provider_id" \
--arg provider_name "$provider_name" \
--arg base_url "$base_url" \
--arg model_id "$OPENCODE_MODEL" \
--arg model_key "$model_key" \
--arg model_ref "$model_ref" \
--argjson context_limit "$context_limit" \
'{
"$schema": $schema,
enabled_providers: [$provider_id],
share: "disabled",
autoupdate: false,
provider: {
($provider_id): {
npm: "@ai-sdk/openai-compatible",
name: $provider_name,
options: {
baseURL: $base_url,
timeout: 1800000,
chunkTimeout: 180000
},
models: {
($model_key): {
name: $model_id,
limit: {
context: $context_limit,
output: 32768
},
cost: {
input: 0.0221,
output: 0.1684,
cache_read: 0.0001,
cache_write: 0.0001
},
tool_call: true,
reasoning: true
}
}
}
},
model: $model_ref,
compaction: {
auto: true,
prune: true,
reserved: 16000
},
watcher: {
ignore: [
".git/**",
"node_modules/**",
"dist/**",
".run/**"
]
},
permission: {
read: "allow",
edit: "allow",
glob: "allow",
grep: "allow",
list: "allow",
bash: "allow",
task: "allow",
question: "allow",
webfetch: "allow",
websearch: "allow",
codesearch: "allow",
external_directory: "deny",
doom_loop: "ask"
}
}' >"$OPENCODE_CONFIG_HOST"; then
error "failed to write opencode config '$OPENCODE_CONFIG_HOST'"
fi
} }
fetch_latest_codex_digest() { fetch_latest_codex_digest() {
@@ -1208,28 +1392,29 @@ fetch_latest_codex_digest() {
printf '%s' "$digest_line" printf '%s' "$digest_line"
} }
detect_opencode_archive_name() { detect_opencode_asset_name() {
local os arch opencode_os opencode_arch local arch
os=$(uname -s 2>/dev/null || true)
arch=$(uname -m 2>/dev/null || true) arch=$(uname -m 2>/dev/null || true)
[[ -n $os ]] || error "failed to detect host OS for opencode download"
[[ -n $arch ]] || error "failed to detect host architecture for opencode download" [[ -n $arch ]] || error "failed to detect host architecture for opencode download"
case "$os" in
Linux|Darwin) opencode_os="unknown-linux-gnu" ;;
*) error "unsupported host OS '$os' for opencode download" ;;
esac
case "$arch" in case "$arch" in
x86_64|amd64) opencode_arch="x86_64" ;; x86_64|amd64)
arm64|aarch64) opencode_arch="arm64" ;; printf 'opencode-linux-x64.tar.gz'
*) error "unsupported host architecture '$arch' for opencode download" ;; ;;
arm64|aarch64)
printf 'opencode-linux-arm64.tar.gz'
;;
*)
error "unsupported host architecture '$arch' for opencode download"
;;
esac esac
printf 'opencode-%s-%s' "$opencode_arch" "$opencode_os"
} }
fetch_latest_opencode_digest() { fetch_latest_opencode_digest() {
local api_url="https://api.github.com/repos/anomalyco/opencode/releases/latest" local api_url="https://api.github.com/repos/anomalyco/opencode/releases/latest"
local target_asset="${SLOPTRAP_CODEX_BIN_NAME}.tar.gz" local target_asset
[[ -n $SLOPTRAP_CODEX_BIN_NAME ]] || error "opencode binary name is not set" target_asset=$(detect_opencode_asset_name)
[[ -n $target_asset ]] || error "opencode binary name is not set"
if ! command -v jq >/dev/null 2>&1; then if ! command -v jq >/dev/null 2>&1; then
error "jq is required to verify the opencode binary digest" error "jq is required to verify the opencode binary digest"
fi fi
@@ -1240,7 +1425,7 @@ fetch_latest_opencode_digest() {
local digest_line local digest_line
digest_line=$(jq -r --arg name "$target_asset" '.assets[] | select(.name == $name) | .digest' <<<"$response" | head -n 1) digest_line=$(jq -r --arg name "$target_asset" '.assets[] | select(.name == $name) | .digest' <<<"$response" | head -n 1)
if [[ -z $digest_line || $digest_line == "null" ]]; then if [[ -z $digest_line || $digest_line == "null" ]]; then
error "failed to resolve opencode digest from GitHub response" error "failed to resolve opencode digest from GitHub response (looking for $target_asset)"
fi fi
digest_line=${digest_line#sha256:} digest_line=${digest_line#sha256:}
printf '%s' "$digest_line" printf '%s' "$digest_line"
@@ -1285,28 +1470,30 @@ ensure_opencode_binary() {
if [[ -x $CODEX_BIN_PATH ]]; then if [[ -x $CODEX_BIN_PATH ]]; then
return 0 return 0
fi fi
local tar_transform="s/${SLOPTRAP_CODEX_BIN_NAME}/${SLOPTRAP_CODEX_BIN_NAME}/" local target_asset
target_asset=$(detect_opencode_asset_name)
local download_dir local download_dir
download_dir=$(create_temp_dir "opencode") download_dir=$(create_temp_dir "opencode")
local tmp_archive="$download_dir/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz" local tmp_archive="$download_dir/$target_asset"
local opencode_url="https://github.com/anomalyco/opencode/releases/latest/download/${target_asset}"
if $DRY_RUN; then if $DRY_RUN; then
print_command curl -Lso "$tmp_archive" "https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz" print_command curl -Lso "$tmp_archive" "$opencode_url"
print_command sha256sum -c - print_command sha256sum -c -
print_command tar -xzf "$tmp_archive" --transform="$tar_transform" -C "$SLOPTRAP_BUILD_CONTEXT" print_command tar -xzf "$tmp_archive" --transform="s/opencode/$SLOPTRAP_CODEX_BIN_NAME/" -C "$SLOPTRAP_BUILD_CONTEXT"
print_command chmod 0755 "$CODEX_BIN_PATH" print_command chmod 0755 "$CODEX_BIN_PATH"
return 0 return 0
fi fi
if ! curl -Lso "$tmp_archive" "https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"; then if ! curl -Lso "$tmp_archive" "$opencode_url"; then
rm -rf "$download_dir" rm -rf "$download_dir"
error "failed to download opencode binary from https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz" error "failed to download opencode binary from '$opencode_url'"
fi fi
local expected_digest local expected_digest
expected_digest=$(fetch_latest_opencode_digest) expected_digest=$(fetch_latest_opencode_digest)
if ! printf "%s %s\n" "$expected_digest" "$tmp_archive" | sha256sum -c - >/dev/null 2>&1; then if ! printf "%s %s\n" "$expected_digest" "$tmp_archive" | sha256sum -c - >/dev/null 2>&1; then
rm -rf "$download_dir" "$CODEX_BIN_PATH" rm -rf "$download_dir" "$CODEX_BIN_PATH"
error "checksum verification failed for ${SLOPTRAP_CODEX_BIN_NAME}" error "checksum verification failed for $SLOPTRAP_CODEX_BIN_NAME"
fi fi
if ! tar -xzf "$tmp_archive" --transform="$tar_transform" -C "$SLOPTRAP_BUILD_CONTEXT"; then if ! tar -xzf "$tmp_archive" --transform="s/opencode/$SLOPTRAP_CODEX_BIN_NAME/" -C "$SLOPTRAP_BUILD_CONTEXT"; then
rm -rf "$download_dir" rm -rf "$download_dir"
error "failed to extract opencode binary" error "failed to extract opencode binary"
fi fi
@@ -1345,7 +1532,13 @@ normalize_package_list() {
[[ -z $raw ]] && return 0 [[ -z $raw ]] && return 0
local -a tokens=() local -a tokens=()
read -r -a tokens <<< "$raw" read -r -a tokens <<< "$raw"
printf '%s' "${tokens[*]}" local -a normalized=()
local token
for token in "${tokens[@]}"; do
# Expand package aliases
normalized+=("$(expand_package_alias "$token")")
done
printf '%s' "${normalized[*]}"
} }
targets_need_build() { targets_need_build() {
@@ -1364,6 +1557,30 @@ targets_need_build() {
return 1 return 1
} }
normalize_local_server_url() {
local url=$1
local replacement_host=$2
if [[ $url =~ ^([A-Za-z][A-Za-z0-9+.-]*://)(localhost|127\.0\.0\.1|\[::1\])([:/?#].*)?$ ]]; then
printf '%s%s%s' "${BASH_REMATCH[1]}" "$replacement_host" "${BASH_REMATCH[3]}"
return 0
fi
printf '%s' "$url"
}
ensure_host_loopback_network_access() {
if [[ $SLOPTRAP_NETWORK_NAME != slirp4netns* ]]; then
return 0
fi
if [[ $SLOPTRAP_NETWORK_NAME == *allow_host_loopback=* ]]; then
return 0
fi
if [[ $SLOPTRAP_NETWORK_NAME == "slirp4netns" ]]; then
SLOPTRAP_NETWORK_NAME="slirp4netns:allow_host_loopback=true"
else
SLOPTRAP_NETWORK_NAME="${SLOPTRAP_NETWORK_NAME},allow_host_loopback=true"
fi
}
prepare_container_runtime() { prepare_container_runtime() {
resolve_container_workdir resolve_container_workdir
SLOPTRAP_PACKAGES_EXTRA_RESOLVED=$PACKAGES_EXTRA SLOPTRAP_PACKAGES_EXTRA_RESOLVED=$PACKAGES_EXTRA
@@ -1390,7 +1607,7 @@ prepare_container_runtime() {
SLOPTRAP_DOCKERFILE_SOURCE="" SLOPTRAP_DOCKERFILE_SOURCE=""
fi fi
SLOPTRAP_PACKAGES_BASE=$(get_env_default "SLOPTRAP_PACKAGES" "curl bash ca-certificates libstdc++6 git ripgrep xxd file procps util-linux") SLOPTRAP_PACKAGES_BASE=$(get_env_default "SLOPTRAP_PACKAGES" "curl bash ca-certificates libstdc++6 wget git ripgrep xxd file procps util-linux")
validate_package_list "SLOPTRAP_PACKAGES" "$SLOPTRAP_PACKAGES_BASE" "SLOPTRAP_PACKAGES" validate_package_list "SLOPTRAP_PACKAGES" "$SLOPTRAP_PACKAGES_BASE" "SLOPTRAP_PACKAGES"
local default_codex_archive local default_codex_archive
default_codex_archive=$(detect_codex_archive_name) default_codex_archive=$(detect_codex_archive_name)
@@ -1413,8 +1630,15 @@ prepare_container_runtime() {
SLOPTRAP_CODEX_ARCHIVE=$inferred_archive SLOPTRAP_CODEX_ARCHIVE=$inferred_archive
fi fi
fi fi
if [[ "$BACKEND" == "opencode" ]]; then
SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "opencode")
else
SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "codex") SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "codex")
fi
SLOPTRAP_CODEX_HOME_CONT=$(get_env_default "SLOPTRAP_CODEX_HOME_CONT" "/codex") SLOPTRAP_CODEX_HOME_CONT=$(get_env_default "SLOPTRAP_CODEX_HOME_CONT" "/codex")
if [[ "$BACKEND" == "opencode" ]]; then
OPENCODE_CONFIG_CONT="$SLOPTRAP_CODEX_HOME_CONT/config/opencode/opencode.json"
fi
SLOPTRAP_CODEX_UID=$(get_env_default "SLOPTRAP_CODEX_UID" "1337") SLOPTRAP_CODEX_UID=$(get_env_default "SLOPTRAP_CODEX_UID" "1337")
SLOPTRAP_CODEX_GID=$(get_env_default "SLOPTRAP_CODEX_GID" "1337") SLOPTRAP_CODEX_GID=$(get_env_default "SLOPTRAP_CODEX_GID" "1337")
SLOPTRAP_SECURITY_OPTS_EXTRA=$(get_env_default "SLOPTRAP_SECURITY_OPTS_EXTRA" "") SLOPTRAP_SECURITY_OPTS_EXTRA=$(get_env_default "SLOPTRAP_SECURITY_OPTS_EXTRA" "")
@@ -1432,6 +1656,9 @@ prepare_container_runtime() {
elif [[ $SLOPTRAP_NETWORK_NAME == "host" ]]; then elif [[ $SLOPTRAP_NETWORK_NAME == "host" ]]; then
error "$MANIFEST_PATH: host networking requires allow_host_network=true" error "$MANIFEST_PATH: host networking requires allow_host_network=true"
fi fi
if [[ "$BACKEND" == "opencode" && $ALLOW_HOST_NETWORK == false ]]; then
ensure_host_loopback_network_access
fi
SLOPTRAP_LIMITS_PID=$(get_env_default "SLOPTRAP_LIMITS_PID" "1024") SLOPTRAP_LIMITS_PID=$(get_env_default "SLOPTRAP_LIMITS_PID" "1024")
SLOPTRAP_LIMITS_RAM=$(get_env_default "SLOPTRAP_LIMITS_RAM" "1024m") SLOPTRAP_LIMITS_RAM=$(get_env_default "SLOPTRAP_LIMITS_RAM" "1024m")
SLOPTRAP_LIMITS_SWP=$(get_env_default "SLOPTRAP_LIMITS_SWP" "1024m") SLOPTRAP_LIMITS_SWP=$(get_env_default "SLOPTRAP_LIMITS_SWP" "1024m")
@@ -1510,6 +1737,7 @@ prepare_container_runtime() {
if [[ "$BACKEND" == "opencode" ]]; then if [[ "$BACKEND" == "opencode" ]]; then
env_args+=( env_args+=(
-e "OPENCODE_HOME=$SLOPTRAP_CODEX_HOME_CONT" -e "OPENCODE_HOME=$SLOPTRAP_CODEX_HOME_CONT"
-e "OPENCODE_CONFIG=$OPENCODE_CONFIG_CONT"
-e "OPENCODE_SERVER=$OPENCODE_SERVER" -e "OPENCODE_SERVER=$OPENCODE_SERVER"
-e "OPENCODE_MODEL=$OPENCODE_MODEL" -e "OPENCODE_MODEL=$OPENCODE_MODEL"
) )
@@ -1527,6 +1755,16 @@ prepare_container_runtime() {
env_args+=(-e "SLOPTRAP_HOST_USER=$user") env_args+=(-e "SLOPTRAP_HOST_USER=$user")
fi fi
local -a network_opts=(--network "$SLOPTRAP_NETWORK_NAME") local -a network_opts=(--network "$SLOPTRAP_NETWORK_NAME")
if [[ $ALLOW_HOST_NETWORK == false ]]; then
SLOPTRAP_HOST_ALIAS="sloptrap.host"
network_opts+=(--add-host "$SLOPTRAP_HOST_ALIAS:host-gateway")
env_args+=(-e "SLOPTRAP_HOST_ALIAS=$SLOPTRAP_HOST_ALIAS")
if [[ "$BACKEND" == "opencode" ]]; then
OPENCODE_SERVER=$(normalize_local_server_url "$OPENCODE_SERVER" "$SLOPTRAP_HOST_ALIAS")
fi
else
SLOPTRAP_HOST_ALIAS=""
fi
local -a user_opts=("--user" "$uid:$gid") local -a user_opts=("--user" "$uid:$gid")
if [[ $CONTAINER_ENGINE == "podman" ]]; then if [[ $CONTAINER_ENGINE == "podman" ]]; then
user_opts=(--userns="keep-id:uid=$uid,gid=$gid" "${user_opts[@]}") user_opts=(--userns="keep-id:uid=$uid,gid=$gid" "${user_opts[@]}")
@@ -1560,6 +1798,7 @@ prepare_container_runtime() {
} }
build_image() { build_image() {
prepare_build_context
if [[ "$BACKEND" == "opencode" ]]; then if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_binary ensure_opencode_binary
else else
@@ -1605,6 +1844,7 @@ build_image() {
} }
rebuild_image() { rebuild_image() {
prepare_build_context
if [[ "$BACKEND" == "opencode" ]]; then if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_binary ensure_opencode_binary
else else
@@ -1720,15 +1960,14 @@ run_codex_command() {
local -a auth_mount=() local -a auth_mount=()
if [[ "$BACKEND" == "opencode" ]]; then if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_storage_paths ensure_opencode_storage_paths
ensure_opencode_config
else else
ensure_codex_storage_paths ensure_codex_storage_paths
fi fi
append_auth_mount_arg false auth_mount append_auth_mount_arg false auth_mount
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "$BACKEND") local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}")
if [[ "$BACKEND" == "opencode" ]]; then if [[ "$BACKEND" == "opencode" ]]; then
cmd+=("--server" "$OPENCODE_SERVER") true
cmd+=("--model" "$OPENCODE_MODEL")
cmd+=("--sandbox" "workspace-write")
else else
if [[ ${#CODEX_ARGS_ARRAY[@]} -gt 0 ]]; then if [[ ${#CODEX_ARGS_ARRAY[@]} -gt 0 ]]; then
cmd+=("${CODEX_ARGS_ARRAY[@]}") cmd+=("${CODEX_ARGS_ARRAY[@]}")
@@ -1744,9 +1983,13 @@ run_codex() {
if ! $DRY_RUN; then if ! $DRY_RUN; then
status_line "Running %s\n" "$SLOPTRAP_IMAGE_NAME" status_line "Running %s\n" "$SLOPTRAP_IMAGE_NAME"
fi fi
if [[ "$BACKEND" == "opencode" ]]; then
run_codex_command
else
local runtime_prompt local runtime_prompt
runtime_prompt=$(build_runtime_context_prompt) runtime_prompt=$(build_runtime_context_prompt)
run_codex_command "$runtime_prompt" run_codex_command "$runtime_prompt"
fi
} }
run_login_target() { run_login_target() {
@@ -1757,19 +2000,23 @@ run_login_target() {
status_line "Login %s\n" "$SLOPTRAP_IMAGE_NAME" status_line "Login %s\n" "$SLOPTRAP_IMAGE_NAME"
fi fi
append_auth_mount_arg true auth_mount append_auth_mount_arg true auth_mount
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "codex" login) local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" login)
run_runtime_container_cmd "${cmd[@]}" run_runtime_container_cmd "${cmd[@]}"
} }
run_shell_target() { run_shell_target() {
if [[ "$BACKEND" == "opencode" ]]; then
ensure_opencode_storage_paths
else
ensure_codex_storage_paths ensure_codex_storage_paths
fi
local -a source_args=("$SLOPTRAP_IMAGE_NAME") local -a source_args=("$SLOPTRAP_IMAGE_NAME")
local -a auth_mount=() local -a auth_mount=()
if ! $DRY_RUN; then if ! $DRY_RUN; then
status_line "Shell %s\n" "$SLOPTRAP_IMAGE_NAME" status_line "Shell %s\n" "$SLOPTRAP_IMAGE_NAME"
fi fi
append_auth_mount_arg false auth_mount append_auth_mount_arg false auth_mount
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" /bin/bash) local -a cmd=("${BASE_CONTAINER_CMD[@]}" --entrypoint /bin/bash "${auth_mount[@]}" "${source_args[@]}")
run_runtime_container_cmd "${cmd[@]}" run_runtime_container_cmd "${cmd[@]}"
} }
@@ -1778,7 +2025,11 @@ run_resume_target() {
if ! $DRY_RUN; then if ! $DRY_RUN; then
status_line "Resume %s (%s)\n" "$SLOPTRAP_IMAGE_NAME" "$session_id" status_line "Resume %s (%s)\n" "$SLOPTRAP_IMAGE_NAME" "$session_id"
fi fi
if [[ "$BACKEND" == "opencode" ]]; then
run_codex_command --session "$session_id"
else
run_codex_command resume "$session_id" run_codex_command resume "$session_id"
fi
} }
process_resume_target() { process_resume_target() {
@@ -1930,10 +2181,6 @@ ensure_ignore_helper_root
IGNORE_STUB_BASE="$IGNORE_HELPER_ROOT/session-${BASHPID:-$$}" IGNORE_STUB_BASE="$IGNORE_HELPER_ROOT/session-${BASHPID:-$$}"
resolve_sloptrap_ignore "$CODE_DIR" resolve_sloptrap_ignore "$CODE_DIR"
resolve_container_workdir resolve_container_workdir
NEED_LOGIN=false
if [[ ! -s "$CODEX_AUTH_FILE_HOST" ]]; then
NEED_LOGIN=true
fi
TARGETS=("${TARGETS_INPUT[@]}") TARGETS=("${TARGETS_INPUT[@]}")
if [[ ${#TARGETS[@]} -eq 0 ]]; then if [[ ${#TARGETS[@]} -eq 0 ]]; then
@@ -1942,7 +2189,7 @@ fi
DEFAULT_TARGETS=("${TARGETS[@]}") DEFAULT_TARGETS=("${TARGETS[@]}")
PACKAGES_EXTRA=${MANIFEST[packages_extra]-} PACKAGES_EXTRA=$(normalize_package_list "${MANIFEST[packages_extra]:-}")
if [[ -n ${MANIFEST[allow_host_network]-} ]]; then if [[ -n ${MANIFEST[allow_host_network]-} ]]; then
case "${MANIFEST[allow_host_network],,}" in case "${MANIFEST[allow_host_network],,}" in
1|true|yes) 1|true|yes)
@@ -1982,22 +2229,29 @@ select_backend() {
opencode) BACKEND="opencode" ;; opencode) BACKEND="opencode" ;;
codex|*) BACKEND="codex" ;; codex|*) BACKEND="codex" ;;
esac esac
if [[ "$BACKEND" == "opencode" && ! -x "$(command -v opencode)" ]]; then
error "opencode CLI not found; install from https://opencode.ai"
fi
} }
select_backend select_backend
if [[ "$BACKEND" == "opencode" ]]; then if [[ "$BACKEND" == "opencode" ]]; then
OPENCODE_SERVER="${MANIFEST[opencode_server]:-http://localhost:11434}" OPENCODE_SERVER="${MANIFEST[opencode_server]:-http://localhost:8080}"
OPENCODE_MODEL="${MANIFEST[opencode_model]:-llama3}" OPENCODE_MODEL="${MANIFEST[opencode_model]:-bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0}"
OPENCODE_CONTEXT="${MANIFEST[opencode_context]:-256K}"
OPENCODE_STATE_HOME_HOST="$CODEX_STATE_HOME_HOST/opencode" OPENCODE_STATE_HOME_HOST="$CODEX_STATE_HOME_HOST/opencode"
OPENCODE_CONFIG_HOST="$CODEX_STATE_HOME_HOST/config/opencode/opencode.json"
OPENCODE_CONFIG_CONT=""
else else
OPENCODE_SERVER="" OPENCODE_SERVER=""
OPENCODE_MODEL="" OPENCODE_MODEL=""
OPENCODE_CONTEXT=""
OPENCODE_STATE_HOME_HOST="" OPENCODE_STATE_HOME_HOST=""
OPENCODE_CONFIG_HOST=""
OPENCODE_CONFIG_CONT=""
fi
NEED_LOGIN=false
if [[ "$BACKEND" != "opencode" && ! -s "$CODEX_AUTH_FILE_HOST" ]]; then
NEED_LOGIN=true
fi fi
CONTAINER_ENGINE="$(detect_container_engine)" CONTAINER_ENGINE="$(detect_container_engine)"

View File

@@ -189,6 +189,10 @@ EOF
cat >"$STUB_BIN/jq" <<'EOF' cat >"$STUB_BIN/jq" <<'EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
if [[ ${1-} == "-n" ]]; then
shift
exec /usr/bin/jq -n "$@"
fi
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
-r) -r)
@@ -454,12 +458,28 @@ run_resume_omits_runtime_context() {
if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then
record_failure "resume_omits_runtime_context: resume should not receive startup prompt" record_failure "resume_omits_runtime_context: resume should not receive startup prompt"
fi fi
if ! grep -q -- "codex --sandbox danger-full-access --ask-for-approval never resume $session_id" "$STUB_LOG"; then if ! grep -q -- "--sandbox danger-full-access --ask-for-approval never resume $session_id" "$STUB_LOG"; then
record_failure "resume_omits_runtime_context: resume invocation missing" record_failure "resume_omits_runtime_context: resume invocation missing"
fi fi
teardown_stub_env teardown_stub_env
} }
run_shell_target_uses_entrypoint() {
local scenario_dir="$TEST_ROOT/opencode_localhost"
printf '==> shell_target_uses_entrypoint\n'
setup_stub_env
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
"$SLOPTRAP_BIN" "$scenario_dir" shell </dev/null >/dev/null 2>&1; then
record_failure "shell_target_uses_entrypoint: shell target failed"
teardown_stub_env
return
fi
if ! grep -q -- "--entrypoint /bin/bash" "$STUB_LOG"; then
record_failure "shell_target_uses_entrypoint: missing entrypoint override"
fi
teardown_stub_env
}
run_auth_file_mount() { run_auth_file_mount() {
local scenario_dir local scenario_dir
scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P) scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P)
@@ -732,6 +752,125 @@ run_wizard_build_trigger() {
fi fi
} }
run_opencode_build_downloads_release_cli() {
local scenario_dir="$TEST_ROOT/opencode_build"
printf '==> opencode_build_downloads_release_cli\n'
setup_stub_env
mkdir -p "$scenario_dir"
cat > "$scenario_dir/.sloptrap" <<'EOF'
name=opencode-build
packages_extra=
agent=opencode
allow_host_network=false
EOF
if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
"$SLOPTRAP_BIN" "$scenario_dir" build >/dev/null 2>&1; then
record_failure "opencode_build_downloads_release_cli: build failed"
teardown_stub_env
return
fi
if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then
record_failure "opencode_build_downloads_release_cli: build not invoked"
fi
teardown_stub_env
}
run_opencode_localhost_rewrite() {
local scenario_dir="$TEST_ROOT/opencode_localhost"
printf '==> opencode_localhost_rewrite\n'
setup_stub_env
mkdir -p "$scenario_dir"
cat > "$scenario_dir/.sloptrap" <<'EOF'
name=opencode-localhost
packages_extra=
agent=opencode
opencode_server=http://localhost:8080
allow_host_network=false
EOF
cat > "$STUB_BIN/slirp4netns" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
chmod +x "$STUB_BIN/slirp4netns"
if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
"$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/dev/null 2>&1; then
record_failure "opencode_localhost_rewrite: run failed"
teardown_stub_env
return
fi
local config_path
config_path=$(find "$STUB_HOME" -path '*/config/opencode/opencode.json' | head -n 1 || true)
if [[ -z $config_path || ! -f $config_path ]]; then
record_failure "opencode_localhost_rewrite: opencode config not written"
teardown_stub_env
return
fi
if ! grep -q -- "--network slirp4netns:allow_host_loopback=true" "$STUB_LOG"; then
record_failure "opencode_localhost_rewrite: slirp host loopback not enabled"
fi
if ! grep -q -- "--add-host sloptrap.host:host-gateway" "$STUB_LOG"; then
record_failure "opencode_localhost_rewrite: host alias not injected"
fi
if ! grep -q -- "OPENCODE_CONFIG=/codex/config/opencode/opencode.json" "$STUB_LOG"; then
record_failure "opencode_localhost_rewrite: opencode config path not exported"
fi
if ! grep -q -- '"baseURL": "http://sloptrap.host:8080/v1"' "$config_path"; then
record_failure "opencode_localhost_rewrite: localhost server not rewritten in config"
fi
if [[ $(jq -r '.provider["llama.cpp"].name' "$config_path") != "llama-server (local)" ]]; then
record_failure "opencode_localhost_rewrite: provider name not merged into config"
fi
if [[ $(jq -r '.model' "$config_path") != "llama.cpp/bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0 - 262144" ]]; then
record_failure "opencode_localhost_rewrite: opencode model not written to config"
fi
if [[ $(jq -r '.enabled_providers[0]' "$config_path") != "llama.cpp" ]]; then
record_failure "opencode_localhost_rewrite: enabled_providers not merged into config"
fi
if [[ $(jq -r '.provider["llama.cpp"].models["bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0 - 262144"].limit.context' "$config_path") != "262144" ]]; then
record_failure "opencode_localhost_rewrite: opencode context not written to config"
fi
if grep -q -- "--server " "$STUB_LOG"; then
record_failure "opencode_localhost_rewrite: deprecated --server flag should not be passed"
fi
if grep -q -- "--sandbox workspace-write" "$STUB_LOG"; then
record_failure "opencode_localhost_rewrite: codex sandbox args leaked into opencode run"
fi
if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then
record_failure "opencode_localhost_rewrite: codex-style startup prompt should not be passed to opencode"
fi
teardown_stub_env
}
run_opencode_print_config_runtime_flags() {
local scenario_dir="$TEST_ROOT/opencode_print_config"
printf '==> opencode_print_config_runtime_flags\n'
setup_stub_env
mkdir -p "$scenario_dir"
cat > "$scenario_dir/.sloptrap" <<'EOF'
name=opencode-print-config
packages_extra=
agent=opencode
allow_host_network=false
EOF
local output
if ! output=$(env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
"$SLOPTRAP_BIN" --print-config "$scenario_dir" 2>/dev/null); then
record_failure "opencode_print_config_runtime_flags: print-config failed"
teardown_stub_env
return
fi
if ! grep -q 'runtime_flags=' <<<"$output"; then
record_failure "opencode_print_config_runtime_flags: runtime_flags line missing for opencode"
fi
if ! grep -q 'opencode_config=' <<<"$output"; then
record_failure "opencode_print_config_runtime_flags: opencode config path missing"
fi
if grep -q -- '--sandbox danger-full-access --ask-for-approval never' <<<"$output"; then
record_failure "opencode_print_config_runtime_flags: codex runtime flags leaked into opencode config"
fi
teardown_stub_env
}
run_symlink_escape run_symlink_escape
run_manifest_injection run_manifest_injection
run_helper_symlink run_helper_symlink
@@ -740,6 +879,7 @@ run_resume_target
run_runtime_context_prompt run_runtime_context_prompt
run_sh_reexec run_sh_reexec
run_resume_omits_runtime_context run_resume_omits_runtime_context
run_shell_target_uses_entrypoint
run_auth_file_mount run_auth_file_mount
run_codex_home_override run_codex_home_override
run_project_state_isolation run_project_state_isolation
@@ -749,6 +889,9 @@ run_root_directory_project
run_wizard_create_manifest run_wizard_create_manifest
run_wizard_existing_defaults run_wizard_existing_defaults
run_wizard_build_trigger run_wizard_build_trigger
run_opencode_build_downloads_release_cli
run_opencode_localhost_rewrite
run_opencode_print_config_runtime_flags
if [[ ${#failures[@]} -gt 0 ]]; then if [[ ${#failures[@]} -gt 0 ]]; then
printf '\nTest failures:\n' printf '\nTest failures:\n'

View File

@@ -1,4 +1,4 @@
name=wizard_build name=wizard_build
packages_extra= packages_extra=
capabilities= agent=codex
allow_host_network=false allow_host_network=false

View File

@@ -1,4 +1,4 @@
name=wizard_empty name=wizard_empty
packages_extra= packages_extra=
capabilities= agent=codex
allow_host_network=false allow_host_network=false

View File

@@ -1,4 +1,4 @@
name=custom-wizard name=custom-wizard
packages_extra=make git packages_extra=make git
capabilities=apt-install agent=codex
allow_host_network=true allow_host_network=true