mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-18 02:38:54 +00:00
* fix(sandbox): stop blocking bash commands from hanging the turn Starting a server through the host bash tool (e.g. `python -m http.server`) could hang the whole turn for the full 600s timeout. `LocalSandbox.execute_command` used `subprocess.run(capture_output=True)`, whose captured pipes are inherited by any process the command spawns — so a backgrounded long-lived process (`server &`) keeps the read end open and blocks `communicate()` until the timeout fires, even though the foreground command already returned. Commands that read stdin blocked the same way, and on timeout only the direct child was killed, leaving orphaned process groups. Rework the POSIX path to capture stdout/stderr via temp files instead of pipes, take stdin from /dev/null, and run the command in its own session/process group: - Backgrounded long-lived processes (servers) now return immediately while the process keeps running. - A command reading stdin gets immediate EOF instead of blocking. - A genuinely blocking foreground command is bounded by a configurable wall-clock timeout; on timeout the whole process group is killed and the agent gets an explanatory notice telling it to background long-lived processes. The timeout is configurable via `sandbox.bash_command_timeout` (default 600). The Windows path is unchanged. Adds focused regression tests and updates docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sandbox): instruct the agent to background long-lived processes The code fix bounds a foreground server with a timeout, but the turn still waits the full timeout before the run continues. Add the prompt-side half: the bash tool description now tells the model to ALWAYS start long-lived processes (e.g. web servers) in the background with output redirected, so the tool returns immediately. The timeout notice points at the same readable workspace log path. Pins the guidance with a test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sandbox): make fallback-kill exception explicit and observable Address automated review: the inner `except OSError: pass` in _terminate_process_group silently swallowed the case where the direct-child fallback kill found the process already gone. Make the intent explicit with a comment and a debug log instead of a bare pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sandbox): address bash timeout review feedback * fix(sandbox): document fd cleanup races --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
4.9 KiB
Python
105 lines
4.9 KiB
Python
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
class VolumeMountConfig(BaseModel):
|
|
"""Configuration for a volume mount."""
|
|
|
|
host_path: str = Field(
|
|
...,
|
|
description=(
|
|
"Source path for the mount. Resolution depends on the active provider: "
|
|
"``LocalSandboxProvider`` checks this path from the gateway process — in "
|
|
"``make dev`` that is the host machine, but in Docker deployments "
|
|
"(``make up`` / docker-compose) it is the path *inside* the "
|
|
"``deer-flow-gateway`` container, so the host directory must also be "
|
|
"bind-mounted into the gateway service for the mount to take effect. "
|
|
"``AioSandboxProvider`` (DooD) passes this value straight to ``docker -v`` "
|
|
"for the sandbox container, where it is resolved by the host Docker daemon "
|
|
"from the host machine's perspective."
|
|
),
|
|
)
|
|
container_path: str = Field(..., description="Path inside the container")
|
|
read_only: bool = Field(default=False, description="Whether the mount is read-only")
|
|
|
|
|
|
class SandboxConfig(BaseModel):
|
|
"""Config section for a sandbox.
|
|
|
|
Common options:
|
|
use: Class path of the sandbox provider (required)
|
|
allow_host_bash: Enable host-side bash execution for LocalSandboxProvider.
|
|
Dangerous and intended only for fully trusted local workflows.
|
|
|
|
AioSandboxProvider specific options:
|
|
image: Docker image to use (default: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest)
|
|
port: Base port for sandbox containers (default: 8080)
|
|
replicas: Maximum number of concurrent sandbox containers (default: 3). When the limit is reached the least-recently-used sandbox is evicted to make room.
|
|
container_prefix: Prefix for container names (default: deer-flow-sandbox)
|
|
idle_timeout: Idle timeout in seconds before sandbox is released (default: 600 = 10 minutes). Set to 0 to disable.
|
|
mounts: List of volume mounts to share directories with the container
|
|
environment: Environment variables to inject into the container (values starting with $ are resolved from host env)
|
|
"""
|
|
|
|
use: str = Field(
|
|
...,
|
|
description="Class path of the sandbox provider (e.g. deerflow.sandbox.local:LocalSandboxProvider)",
|
|
)
|
|
allow_host_bash: bool = Field(
|
|
default=False,
|
|
description="Allow the bash tool to execute directly on the host when using LocalSandboxProvider. Dangerous; intended only for fully trusted local environments.",
|
|
)
|
|
image: str | None = Field(
|
|
default=None,
|
|
description="Docker image to use for the sandbox container",
|
|
)
|
|
port: int | None = Field(
|
|
default=None,
|
|
description="Base port for sandbox containers",
|
|
)
|
|
replicas: int | None = Field(
|
|
default=None,
|
|
description="Maximum number of concurrent sandbox containers (default: 3). When the limit is reached the least-recently-used sandbox is evicted to make room.",
|
|
)
|
|
container_prefix: str | None = Field(
|
|
default=None,
|
|
description="Prefix for container names",
|
|
)
|
|
idle_timeout: int | None = Field(
|
|
default=None,
|
|
description="Idle timeout in seconds before sandbox is released (default: 600 = 10 minutes). Set to 0 to disable.",
|
|
)
|
|
mounts: list[VolumeMountConfig] = Field(
|
|
default_factory=list,
|
|
description="List of volume mounts to share directories between host and container",
|
|
)
|
|
environment: dict[str, str] = Field(
|
|
default_factory=dict,
|
|
description="Environment variables to inject into the sandbox container. Values starting with $ will be resolved from host environment variables.",
|
|
)
|
|
|
|
bash_output_max_chars: int = Field(
|
|
default=20000,
|
|
ge=0,
|
|
description="Maximum characters to keep from bash tool output. Output exceeding this limit is middle-truncated (head + tail), preserving the first and last half. Set to 0 to disable truncation.",
|
|
)
|
|
read_file_output_max_chars: int = Field(
|
|
default=50000,
|
|
ge=0,
|
|
description="Maximum characters to keep from read_file tool output. Output exceeding this limit is head-truncated. Set to 0 to disable truncation.",
|
|
)
|
|
ls_output_max_chars: int = Field(
|
|
default=20000,
|
|
ge=0,
|
|
description="Maximum characters to keep from ls tool output. Output exceeding this limit is head-truncated. Set to 0 to disable truncation.",
|
|
)
|
|
bash_command_timeout: int = Field(
|
|
default=600,
|
|
gt=0,
|
|
description=(
|
|
"Maximum wall-clock seconds a host bash command may run before it is terminated, process group and all (LocalSandboxProvider). "
|
|
"Keeps a blocking foreground command (e.g. an un-backgrounded server) from hanging the turn; background `&` processes return immediately."
|
|
),
|
|
)
|
|
|
|
model_config = ConfigDict(extra="allow")
|