fix(dify-agent): align agent and shell execution limits (#40043)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
盐粒 Yanli 2026-08-06 18:53:03 +08:00 committed by GitHub
parent d6336c5d49
commit 4d0bc32dd3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 115 additions and 24 deletions

View File

@ -42,7 +42,8 @@ DIFY_AGENT_ENTERPRISE_SANDBOX_PROXY_TIMEOUT=60
# E2B backend: API key, prepared shellctl template, and active Binding policy.
DIFY_AGENT_E2B_API_KEY=
DIFY_AGENT_E2B_TEMPLATE=difys-default-team/dify-agent-local-sandbox
# Maximum continuous active time. Binding resources pause; temporary Home initialization resources are killed.
# Maximum continuous active time for the RuntimeLease that spans one complete Agent run.
# Binding resources pause; temporary Home initialization resources are killed.
# This is not a retention TTL for paused resources or immutable snapshots.
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS=3600
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN=

View File

@ -192,8 +192,10 @@ its Binding and Workspace are one Sandbox. It also rejects binding-only destroy.
Neither path creates a fallback Workspace or switches backends.
`DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS` limits continuous active time for an E2B
resource. Runtime resources pause on timeout. It is not a retention TTL and
does not delete paused resources or immutable snapshots.
resource to one hour. The limit covers the complete Agent run held by one
RuntimeLease rather than an individual tool call. Runtime resources pause on
timeout. It is not a retention TTL and does not delete paused resources or
immutable snapshots.
See the [Shell layer](../../user-manual/shell-layer/index.md) for request
composition and the [Operations Guide](../../guide/index.md) for Local and E2B

View File

@ -51,7 +51,7 @@ also reads `.env` and `dify-agent/.env` when present.
| `DIFY_AGENT_ENTERPRISE_SANDBOX_PROXY_TIMEOUT` | `60` | Enterprise shellctl-proxy timeout in seconds. |
| `DIFY_AGENT_E2B_API_KEY` | empty | E2B API key; required for E2B. |
| `DIFY_AGENT_E2B_TEMPLATE` | `difys-default-team/dify-agent-local-sandbox` | Prepared E2B template containing shellctl and the deployment-default Home environment. |
| `DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS` | `3600` | Maximum continuous active time, up to 3600 seconds. Binding resources pause on timeout. This is not a retention TTL. |
| `DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS` | `3600` | Maximum continuous active time for the RuntimeLease spanning one complete Agent run. Binding resources pause on timeout. This is not a retention TTL. |
| `DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN` | empty | Optional bearer token expected by shellctl inside the E2B template. |
| `DIFY_AGENT_E2B_SHELLCTL_PORT` | `5004` | shellctl port exposed by the E2B template. |
| `DIFY_AGENT_SANDBOX_FILE_UPLOAD_MAX_BYTES` | `52428800` | Standalone Dify Agent maximum for whole-file Workspace upload capture; 50 MiB by default. Docker Compose derives it from `PLUGIN_MAX_FILE_SIZE`. |
@ -236,16 +236,16 @@ Run the real E2B contract with an explicit test credential and template:
cd dify-agent
DIFY_AGENT_TEST_E2B_API_KEY="$E2B_API_TOKEN" \
DIFY_AGENT_TEST_E2B_TEMPLATE=difys-default-team/dify-agent-local-sandbox \
DIFY_AGENT_TEST_E2B_ACTIVE_TIMEOUT_SECONDS=900 \
pdm run pytest --import-mode=importlib \
tests/integration/dify_agent/runtime_backend/test_working_environment.py \
-k e2b -q -rs
```
The Local auth token is optional when shellctl has authentication disabled.
The E2B test timeout value `900` means up to 15 minutes of continuous active
test time; it is not a post-test retention TTL. Both contracts create unique
resources and perform explicit cleanup in `finally` blocks.
The E2B contract uses the one-hour `E2B_MAX_ACTIVE_TIMEOUT_SECONDS` RuntimeLease
limit. This is continuous active test time, not a post-test retention TTL. Both
contracts create unique resources and perform explicit cleanup in `finally`
blocks.
## Scheduling and shutdown semantics
@ -255,6 +255,10 @@ automatic retry layer. Request-shaped runtime failures such as bad composition,
prompt, output, or snapshot inputs are reported later as failed runs rather than
rejected synchronously once the request DTO itself is accepted.
Each run explicitly limits Pydantic AI to 100 model-request steps. Tool calls do
not have a separate count limit, but every model request used to continue the
tool loop consumes one of those steps.
During FastAPI shutdown the scheduler rejects new runs, waits up to
`DIFY_AGENT_SHUTDOWN_GRACE_SECONDS` for active tasks, then cancels remaining tasks
and attempts to finalize them as failed. Success, failure, cancellation, and this

View File

@ -48,7 +48,7 @@ class _HasErrorCode(Protocol):
DEFAULT_TIMEOUT_SECONDS = 30.0
DEFAULT_TERMINATE_GRACE_SECONDS = 10.0
_SHELL_OUTPUT_PROMPT_EDGE_BYTES = 8 * 1024
_SHELL_OUTPUT_PROMPT_EDGE_BYTES = 4 * 1024
_SHELLCTL_OUTPUT_LIMIT_BYTES = 2 * _SHELL_OUTPUT_PROMPT_EDGE_BYTES
_REMOTE_COMPLETE_OUTPUT_MAX_BYTES = 1024 * 1024
_REMOTE_COMMAND_TIMEOUT_SECONDS = 60.0

View File

@ -40,6 +40,7 @@ from pydantic_ai.exceptions import ModelHTTPError
from pydantic_ai.messages import AgentStreamEvent, PartDeltaEvent, PartStartEvent, TextPart, TextPartDelta
from pydantic_ai.output import OutputSpec
from pydantic_ai.tools import DeferredToolRequests, DeferredToolResults
from pydantic_ai.usage import UsageLimits
from agenton.compositor import CompositorSessionSnapshot, LayerConfigInput, LayerProviderInput
from agenton.layers.types import PydanticAITool
@ -79,6 +80,7 @@ from dify_agent.runtime.user_prompt_validation import EMPTY_USER_PROMPTS_ERROR,
_AGENT_OUTPUT_ADAPTER = TypeAdapter(object)
_MAX_AGENT_STEPS_PER_RUN = 100
@runtime_checkable
@ -324,6 +326,7 @@ class AgentRunRunner:
message_history=message_history,
deferred_tool_results=deferred_tool_results,
event_stream_handler=handle_events,
usage_limits=UsageLimits(request_limit=_MAX_AGENT_STEPS_PER_RUN),
)
complete_usage = model.accumulated_usage if isinstance(model, _HasAccumulatedUsage) else None
usage = _serialize_agent_usage(complete_usage if complete_usage is not None else _result_usage(result))

View File

@ -40,6 +40,7 @@ from dify_agent.runtime_backend.shellctl import ShellctlRuntimeLease, create_own
if TYPE_CHECKING:
from e2b.connection_config import ApiParams
# One RuntimeLease spans the complete Agent run, not one Shell tool call.
E2B_MAX_ACTIVE_TIMEOUT_SECONDS = 60 * 60
_SHELLCTL_READY_MAX_ATTEMPTS = 3
_SHELLCTL_READY_RETRY_INTERVAL_SECONDS = 0.5

View File

@ -23,6 +23,7 @@ from shellctl.shared.constants import (
DEFAULT_OUTPUT_LIMIT_BYTES,
DEFAULT_TERMINATE_GRACE_SECONDS,
DEFAULT_TIMEOUT_SECONDS,
SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS,
)
from shellctl.shared.schemas import (
DeleteJobResponse,
@ -71,7 +72,7 @@ class ShellctlClient:
token: str | None = None,
client: httpx.AsyncClient | None = None,
transport: httpx.AsyncBaseTransport | None = None,
request_timeout_grace_seconds: float = 10.0,
request_timeout_grace_seconds: float = SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS,
) -> None:
self.base_url = base_url.rstrip("/")
self.output_limit = output_limit

View File

@ -31,6 +31,9 @@ if TYPE_CHECKING:
MAX_LIST_LIMIT,
MAX_OUTPUT_LIMIT_BYTES,
MAX_WAIT_TIMEOUT_SECONDS,
SHELL_TOOL_HARD_TIMEOUT_SECONDS,
SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS,
SHELL_TOOL_TIMEOUT_WITH_HTTP_GRACE_SECONDS,
SESSION_NAME_PREFIX,
)
from shellctl.shared.output import (
@ -87,6 +90,9 @@ __all__ = [
"MAX_LIST_LIMIT",
"MAX_OUTPUT_LIMIT_BYTES",
"MAX_WAIT_TIMEOUT_SECONDS",
"SHELL_TOOL_HARD_TIMEOUT_SECONDS",
"SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS",
"SHELL_TOOL_TIMEOUT_WITH_HTTP_GRACE_SECONDS",
"SESSION_NAME_PREFIX",
"TERMINAL_JOB_STATUSES",
"DeleteJobResponse",
@ -137,6 +143,9 @@ _EXPORTS = {
"MAX_LIST_LIMIT": "shellctl.shared.constants",
"MAX_OUTPUT_LIMIT_BYTES": "shellctl.shared.constants",
"MAX_WAIT_TIMEOUT_SECONDS": "shellctl.shared.constants",
"SHELL_TOOL_HARD_TIMEOUT_SECONDS": "shellctl.shared.constants",
"SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS": "shellctl.shared.constants",
"SHELL_TOOL_TIMEOUT_WITH_HTTP_GRACE_SECONDS": "shellctl.shared.constants",
"SESSION_NAME_PREFIX": "shellctl.shared.constants",
"OutputWindow": "shellctl.shared.output",
"read_output_window": "shellctl.shared.output",

View File

@ -12,7 +12,13 @@ DEFAULT_BASE_URL = "http://127.0.0.1:8765"
DEFAULT_OUTPUT_LIMIT_BYTES = 1024 * 8
MAX_OUTPUT_LIMIT_BYTES = 1024 * 1024
DEFAULT_TIMEOUT_SECONDS = 30.0
MAX_WAIT_TIMEOUT_SECONDS = 5.0 * 60.0
# Single source of truth for the hard business timeout exposed by Shell tools.
SHELL_TOOL_HARD_TIMEOUT_SECONDS = 5.0 * 60.0
# Transport grace lets the HTTP response arrive after the Shell wait budget expires.
SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS = 10.0
SHELL_TOOL_TIMEOUT_WITH_HTTP_GRACE_SECONDS = SHELL_TOOL_HARD_TIMEOUT_SECONDS + SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS
# Backward-compatible shellctl name; the Shell tool constant above owns the value.
MAX_WAIT_TIMEOUT_SECONDS = SHELL_TOOL_HARD_TIMEOUT_SECONDS
DEFAULT_IDLE_FLUSH_SECONDS = 0.5
DEFAULT_TERMINATE_GRACE_SECONDS = 5.0
DEFAULT_TERMINAL_COLS = 120
@ -45,5 +51,8 @@ __all__ = [
"MAX_LIST_LIMIT",
"MAX_OUTPUT_LIMIT_BYTES",
"MAX_WAIT_TIMEOUT_SECONDS",
"SHELL_TOOL_HARD_TIMEOUT_SECONDS",
"SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS",
"SHELL_TOOL_TIMEOUT_WITH_HTTP_GRACE_SECONDS",
"SESSION_NAME_PREFIX",
]

View File

@ -15,7 +15,7 @@ from shellctl.shared.constants import (
DEFAULT_TERMINATE_GRACE_SECONDS,
DEFAULT_TIMEOUT_SECONDS,
MAX_OUTPUT_LIMIT_BYTES,
MAX_WAIT_TIMEOUT_SECONDS,
SHELL_TOOL_HARD_TIMEOUT_SECONDS,
)
@ -139,7 +139,7 @@ class RunJobRequest(ShellctlModel):
cwd: str | None = None
env: dict[str, str] | None = None
terminal: TerminalSize | None = None
timeout: float = Field(default=DEFAULT_TIMEOUT_SECONDS, gt=0, le=MAX_WAIT_TIMEOUT_SECONDS)
timeout: float = Field(default=DEFAULT_TIMEOUT_SECONDS, gt=0, le=SHELL_TOOL_HARD_TIMEOUT_SECONDS)
output_limit: int = Field(default=DEFAULT_OUTPUT_LIMIT_BYTES, ge=1, le=MAX_OUTPUT_LIMIT_BYTES)
idle_flush_seconds: float = Field(default=DEFAULT_IDLE_FLUSH_SECONDS, ge=0, le=30)
@ -171,7 +171,7 @@ class RunJobRequest(ShellctlModel):
class WaitJobRequest(ShellctlModel):
"""HTTP request body for `POST /v1/jobs/{job_id}/wait`."""
timeout: float = Field(default=DEFAULT_TIMEOUT_SECONDS, ge=0, le=MAX_WAIT_TIMEOUT_SECONDS)
timeout: float = Field(default=DEFAULT_TIMEOUT_SECONDS, ge=0, le=SHELL_TOOL_HARD_TIMEOUT_SECONDS)
offset: int = Field(ge=0)
output_limit: int = Field(default=DEFAULT_OUTPUT_LIMIT_BYTES, ge=1, le=MAX_OUTPUT_LIMIT_BYTES)
idle_flush_seconds: float = Field(default=DEFAULT_IDLE_FLUSH_SECONDS, ge=0, le=30)
@ -181,7 +181,7 @@ class InputJobRequest(ShellctlModel):
"""HTTP request body for `POST /v1/jobs/{job_id}/input`."""
text: str
timeout: float = Field(default=DEFAULT_TIMEOUT_SECONDS, gt=0, le=MAX_WAIT_TIMEOUT_SECONDS)
timeout: float = Field(default=DEFAULT_TIMEOUT_SECONDS, gt=0, le=SHELL_TOOL_HARD_TIMEOUT_SECONDS)
offset: int = Field(ge=0)
output_limit: int = Field(default=DEFAULT_OUTPUT_LIMIT_BYTES, ge=1, le=MAX_OUTPUT_LIMIT_BYTES)
idle_flush_seconds: float = Field(default=DEFAULT_IDLE_FLUSH_SECONDS, ge=0, le=30)
@ -190,7 +190,7 @@ class InputJobRequest(ShellctlModel):
class TerminateJobRequest(ShellctlModel):
"""HTTP request body for `POST /v1/jobs/{job_id}/terminate`."""
grace_seconds: float = Field(default=DEFAULT_TERMINATE_GRACE_SECONDS, ge=0, le=300)
grace_seconds: float = Field(default=DEFAULT_TERMINATE_GRACE_SECONDS, ge=0, le=SHELL_TOOL_HARD_TIMEOUT_SECONDS)
__all__ = [

View File

@ -13,7 +13,12 @@ from dify_agent.runtime_backend import (
ExecutionBindingDestroySpec,
HomeSnapshotCreateSpec,
)
from dify_agent.runtime_backend.e2b import E2BExecutionBindingBackend, E2BHomeSnapshotBackend, E2BSDKControlPlane
from dify_agent.runtime_backend.e2b import (
E2B_MAX_ACTIVE_TIMEOUT_SECONDS,
E2BExecutionBindingBackend,
E2BHomeSnapshotBackend,
E2BSDKControlPlane,
)
from dify_agent.runtime_backend.local import LocalExecutionBindingBackend
pytestmark = pytest.mark.integration
@ -106,7 +111,11 @@ async def test_e2b_binding_checkpoint_and_collection() -> None:
marker = uuid.uuid4().hex
control = E2BSDKControlPlane(api_key=api_key)
snapshots = E2BHomeSnapshotBackend(control_plane=control)
bindings = E2BExecutionBindingBackend(control_plane=control, template=template, active_timeout_seconds=3600)
bindings = E2BExecutionBindingBackend(
control_plane=control,
template=template,
active_timeout_seconds=E2B_MAX_ACTIVE_TIMEOUT_SECONDS,
)
checkpoint_ref: str | None = None
allocation = None
checkpoint_allocation = None

View File

@ -588,6 +588,7 @@ def test_shell_run_formats_large_non_truncated_output_without_tail_lookup() -> N
metadata, output = _parse_tagged_observation(result)
assert metadata["output_path"] == "/tmp/large.log"
assert output.startswith("head-y")
assert "max output size is limited to 8192 bytes" in output
assert output.endswith("(check the /tmp/large.log for full output)")
assert "-tail" in output

View File

@ -22,6 +22,7 @@ from pydantic_ai.messages import (
from pydantic_ai.models import ModelRequestParameters
from pydantic_ai.models.test import TestModel
from pydantic_ai.tools import DeferredToolRequests, DeferredToolResults
from pydantic_ai.usage import UsageLimits
from pydantic_ai.settings import ModelSettings
from agenton.compositor import CompositorSessionSnapshot, LayerProvider, LayerSessionSnapshot
@ -608,6 +609,36 @@ def test_runner_preserves_explicit_json_null_output(monkeypatch: pytest.MonkeyPa
assert sink.statuses["run-null-output"] == "succeeded"
def test_runner_passes_explicit_step_limit_to_agent(monkeypatch: pytest.MonkeyPatch) -> None:
def fake_get_model(_self: DifyPluginLLMLayer, *, http_client: httpx.AsyncClient):
assert http_client.is_closed is False
return TestModel(custom_output_text="unused") # pyright: ignore[reportReturnType]
class FakeAgent:
async def run(self, *_args: object, **kwargs: object) -> FakeAgentRunResult:
usage_limits = cast(UsageLimits, kwargs["usage_limits"])
assert usage_limits.request_limit == 100
return FakeAgentRunResult("done", [])
monkeypatch.setattr(DifyPluginLLMLayer, "get_model", fake_get_model)
monkeypatch.setattr("dify_agent.runtime.runner.create_agent", lambda *_args, **_kwargs: FakeAgent())
sink = InMemoryRunEventSink()
async def scenario() -> None:
async with httpx.AsyncClient() as client:
await AgentRunRunner(
sink=sink,
request=_request(),
run_id="run-explicit-step-limit",
plugin_daemon_http_client=client,
dify_api_http_client=client,
).run()
asyncio.run(scenario())
assert sink.statuses["run-explicit-step-limit"] == "succeeded"
def test_runner_emits_deferred_tool_call_and_persists_pending_history(monkeypatch: pytest.MonkeyPatch) -> None:
captured_output_types: list[object] = []
captured_user_prompts: list[object] = []

View File

@ -19,6 +19,7 @@ from dify_agent.runtime_backend import (
)
from dify_agent.runtime_backend import e2b as e2b_module
from dify_agent.runtime_backend.e2b import (
E2B_MAX_ACTIVE_TIMEOUT_SECONDS,
E2BExecutionBindingBackend,
E2BHomeSnapshotBackend,
E2BRuntimeLease,
@ -133,7 +134,7 @@ def _connected_backend(*, pause_error: Exception | None = None) -> tuple[E2BExec
E2BExecutionBindingBackend(
control_plane=control, # pyright: ignore[reportArgumentType]
template="prepared-template",
active_timeout_seconds=3600,
active_timeout_seconds=E2B_MAX_ACTIVE_TIMEOUT_SECONDS,
),
sandbox,
)
@ -148,7 +149,7 @@ async def test_e2b_binding_uses_default_template_or_exact_snapshot_and_couples_r
bindings = E2BExecutionBindingBackend(
control_plane=control, # pyright: ignore[reportArgumentType]
template="prepared-template",
active_timeout_seconds=3600,
active_timeout_seconds=E2B_MAX_ACTIVE_TIMEOUT_SECONDS,
)
default_allocation = await bindings.create_binding(
@ -199,7 +200,7 @@ async def test_e2b_rejects_shared_workspace_and_binding_only_destroy() -> None:
backend = E2BExecutionBindingBackend(
control_plane=control, # pyright: ignore[reportArgumentType]
template="prepared-template",
active_timeout_seconds=3600,
active_timeout_seconds=E2B_MAX_ACTIVE_TIMEOUT_SECONDS,
)
spec = ExecutionBindingCreateSpec(
tenant_id="tenant-1",
@ -224,7 +225,7 @@ async def test_e2b_binding_create_kills_sandbox_when_initialization_fails() -> N
backend = E2BExecutionBindingBackend(
control_plane=control, # pyright: ignore[reportArgumentType]
template="prepared-template",
active_timeout_seconds=3600,
active_timeout_seconds=E2B_MAX_ACTIVE_TIMEOUT_SECONDS,
)
with pytest.raises(BindingCreateError, match="pause failed"):
@ -255,7 +256,7 @@ async def test_e2b_missing_explicit_snapshot_does_not_fall_back_to_template() ->
backend = E2BExecutionBindingBackend(
control_plane=control, # pyright: ignore[reportArgumentType]
template="prepared-template",
active_timeout_seconds=3600,
active_timeout_seconds=E2B_MAX_ACTIVE_TIMEOUT_SECONDS,
)
with pytest.raises(BindingCreateError, match="snapshot unavailable"):

View File

@ -12,11 +12,12 @@ from dify_agent.runtime_backend.profile import (
)
def test_e2b_backend_uses_prepared_dify_template_by_default() -> None:
def test_e2b_backend_uses_prepared_dify_template_and_one_hour_lease_by_default() -> None:
settings = RuntimeBackendSettings(runtime_backend="e2b", e2b_api_key="secret")
assert settings.e2b_template == "difys-default-team/dify-agent-local-sandbox"
assert settings.e2b_template == DEFAULT_E2B_TEMPLATE
assert E2B_MAX_ACTIVE_TIMEOUT_SECONDS == 60 * 60
assert settings.e2b_active_timeout_seconds == E2B_MAX_ACTIVE_TIMEOUT_SECONDS

View File

@ -7,13 +7,27 @@ from pydantic import ValidationError
from shellctl.shared import (
JOB_ID_ALPHABET,
MAX_WAIT_TIMEOUT_SECONDS,
RunJobRequest,
SHELL_TOOL_HARD_TIMEOUT_SECONDS,
SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS,
SHELL_TOOL_TIMEOUT_WITH_HTTP_GRACE_SECONDS,
generate_job_id,
read_output_window,
tail_output_window,
)
def test_shell_tool_timeout_budget_has_one_source_of_truth() -> None:
assert MAX_WAIT_TIMEOUT_SECONDS == SHELL_TOOL_HARD_TIMEOUT_SECONDS == 300
assert SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS == 10
assert (
SHELL_TOOL_TIMEOUT_WITH_HTTP_GRACE_SECONDS
== SHELL_TOOL_HARD_TIMEOUT_SECONDS + SHELL_TOOL_HTTP_TIMEOUT_GRACE_SECONDS
== 310
)
def test_generate_job_id_matches_proposal_format() -> None:
job_id = generate_job_id(now=datetime(2026, 5, 21, 15, 30, tzinfo=UTC))

View File

@ -280,6 +280,7 @@ DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN=
# E2B_API_KEY and E2B_API_TOKEN remain accepted as deployment-level fallbacks.
DIFY_AGENT_E2B_API_KEY=
DIFY_AGENT_E2B_TEMPLATE=difys-default-team/dify-agent-local-sandbox
# One-hour RuntimeLease limit spanning a complete Agent run.
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS=3600
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN=
DIFY_AGENT_E2B_SHELLCTL_PORT=5004

View File

@ -676,6 +676,7 @@ services:
DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN: ${DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN:-${DIFY_AGENT_SHELLCTL_AUTH_TOKEN:-}}
DIFY_AGENT_E2B_API_KEY: ${DIFY_AGENT_E2B_API_KEY:-${E2B_API_KEY:-${E2B_API_TOKEN:-}}}
DIFY_AGENT_E2B_TEMPLATE: ${DIFY_AGENT_E2B_TEMPLATE:-difys-default-team/dify-agent-local-sandbox}
# One-hour RuntimeLease limit spanning a complete Agent run.
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS: ${DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS:-3600}
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN: ${DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN:-}
DIFY_AGENT_E2B_SHELLCTL_PORT: ${DIFY_AGENT_E2B_SHELLCTL_PORT:-5004}

View File

@ -682,6 +682,7 @@ services:
DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN: ${DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN:-${DIFY_AGENT_SHELLCTL_AUTH_TOKEN:-}}
DIFY_AGENT_E2B_API_KEY: ${DIFY_AGENT_E2B_API_KEY:-${E2B_API_KEY:-${E2B_API_TOKEN:-}}}
DIFY_AGENT_E2B_TEMPLATE: ${DIFY_AGENT_E2B_TEMPLATE:-difys-default-team/dify-agent-local-sandbox}
# One-hour RuntimeLease limit spanning a complete Agent run.
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS: ${DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS:-3600}
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN: ${DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN:-}
DIFY_AGENT_E2B_SHELLCTL_PORT: ${DIFY_AGENT_E2B_SHELLCTL_PORT:-5004}

View File

@ -29,6 +29,7 @@ DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN=
# E2B_API_KEY and E2B_API_TOKEN remain accepted as deployment-level fallbacks.
DIFY_AGENT_E2B_API_KEY=
DIFY_AGENT_E2B_TEMPLATE=difys-default-team/dify-agent-local-sandbox
# One-hour RuntimeLease limit spanning a complete Agent run.
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS=3600
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN=
DIFY_AGENT_E2B_SHELLCTL_PORT=5004