Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
aa4ed81
fix(copilot): inject actual working directory into SDK system prompt
majdyz Feb 26, 2026
a092c24
Merge branch 'master' of github.com:Significant-Gravitas/AutoGPT into…
majdyz Feb 26, 2026
224a8ca
fix(copilot): guard cwd precompute and remove unrelated frontend changes
majdyz Feb 26, 2026
a5d6e7d
fix(copilot): move makedirs into early cwd try/except
majdyz Feb 26, 2026
15bdb09
fix(copilot): replace _precomputed_cwd with sdk_cwd directly
majdyz Feb 26, 2026
cfb0afb
feat(copilot): add E2B cloud sandbox integration for persistent bash …
majdyz Feb 26, 2026
5167948
fix(copilot): guard e2b integration test against pytest collection crash
majdyz Feb 26, 2026
7d98f1b
fix(copilot): address CodeRabbit review comments
majdyz Feb 26, 2026
cb35d46
fix(copilot/e2b): path traversal guard + race-free sandbox creation
majdyz Feb 26, 2026
bb59655
merge: resolve conflict with master (cwd inside try/finally)
majdyz Feb 27, 2026
408155e
feat(copilot): route file tools through E2B sandbox when active
majdyz Feb 27, 2026
600851b
fix(copilot): make workspace_files tests async to match _resolve_writ…
majdyz Feb 27, 2026
e1b2a31
fix(copilot): use integer timeouts for E2B sandbox commands
majdyz Feb 27, 2026
70da86b
fix(copilot): harden E2B file tools and sandbox path handling
majdyz Feb 27, 2026
03fdc28
fix(copilot): validate sandbox path boundaries and cast timeout to int
majdyz Feb 27, 2026
d3d5b88
fix(copilot): remove dead sync code, fix config description, align lo…
majdyz Feb 28, 2026
5fb5f4e
Merge remote-tracking branch 'origin/dev' into feat/improve-copilot-file
majdyz Feb 28, 2026
ae8e669
fix(copilot): simplify E2B file tools, harden DB sanitization, add ou…
majdyz Feb 28, 2026
47c21b2
fix(copilot): disable SDK built-in Read in E2B mode
majdyz Feb 28, 2026
b5957ca
fix(copilot): harden file access — session-scoped tool-results, share…
majdyz Feb 28, 2026
08973c5
fix(copilot): prevent duplicate sandbox creation on lock contention
majdyz Feb 28, 2026
e594aff
fix(copilot): clean up e2b_file_tools imports, remove manual test
majdyz Feb 28, 2026
08287f0
fix(copilot): set _current_project_dir in security_hooks_test
majdyz Feb 28, 2026
008146b
fix(copilot): add defense-in-depth to _read_local and path validation…
majdyz Feb 28, 2026
3ff7327
fix(copilot): cap MCP tool response size to prevent SDK buffer overflow
majdyz Feb 28, 2026
76b1232
fix(copilot): centralize MCP tool truncation and stash, improve front…
majdyz Feb 28, 2026
0e2e0b5
fix(copilot): cleanup — E2B default on, revert unrelated frontend cha…
majdyz Feb 28, 2026
a14e88b
fix(copilot): simplify e2b_sandbox.py, move imports to top-level
majdyz Feb 28, 2026
3959aff
fix(copilot): clean up e2b_file_tools — merge MCP helpers, reduce boi…
majdyz Feb 28, 2026
a3dc0e1
fix(copilot): deduplicate E2B_WORKDIR, move imports to top-level, add…
majdyz Feb 28, 2026
69073d1
fix(copilot): add error handling for sandbox write in _save_to_path
majdyz Feb 28, 2026
060661b
fix(copilot): add error handling for local write in _save_to_path
majdyz Mar 1, 2026
7ee1e8b
fix(copilot): read sandbox files as bytes with utf-8 decode fallback
majdyz Mar 1, 2026
85dfaf1
fix(copilot): try reconnecting before raising on lock contention
majdyz Mar 1, 2026
97097fe
Merge remote-tracking branch 'origin/dev' into feat/improve-copilot-file
majdyz Mar 1, 2026
a878261
fix(copilot): address reviewer should-fix items
majdyz Mar 1, 2026
0b3faa7
feat(copilot): add kill_sandbox() for explicit E2B cleanup on session…
majdyz Mar 1, 2026
bc57672
fix(copilot): address reviewer v7 should-fix items
majdyz Mar 1, 2026
41da564
test(copilot): add kill_sandbox timeout test
majdyz Mar 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion autogpt_platform/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.ignore.*
*.ign.*
*.ign.*
.application.logs
2 changes: 1 addition & 1 deletion autogpt_platform/backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ ENV DEBIAN_FRONTEND=noninteractive

# Install Python, FFmpeg, ImageMagick, and CLI tools for agent use.
# bubblewrap provides OS-level sandbox (whitelist-only FS + no network)
# for the bash_exec MCP tool.
# for the bash_exec MCP tool (fallback when E2B is not configured).
# Using --no-install-recommends saves ~650MB by skipping unnecessary deps like llvm, mesa, etc.
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.13 \
Expand Down
37 changes: 37 additions & 0 deletions autogpt_platform/backend/backend/copilot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,49 @@ class ChatConfig(BaseSettings):
"history compression. Falls back to compression when unavailable.",
)

# E2B Sandbox Configuration
use_e2b_sandbox: bool = Field(
default=True,
description="Use E2B cloud sandboxes for persistent bash/python execution. "
"When enabled, bash_exec routes commands to E2B and SDK file tools "
"operate directly on the sandbox via E2B's filesystem API.",
)
e2b_api_key: str | None = Field(
default=None,
description="E2B API key. Falls back to E2B_API_KEY environment variable.",
)
e2b_sandbox_template: str = Field(
default="base",
description="E2B sandbox template to use for copilot sessions.",
)
e2b_sandbox_timeout: int = Field(
default=43200, # 12 hours — same as session_ttl
description="E2B sandbox keepalive timeout in seconds.",
)

# Extended thinking configuration for Claude models
thinking_enabled: bool = Field(
default=True,
description="Enable adaptive thinking for Claude models via OpenRouter",
)

@field_validator("use_e2b_sandbox", mode="before")
@classmethod
def get_use_e2b_sandbox(cls, v):
"""Get use_e2b_sandbox from environment if not provided."""
env_val = os.getenv("CHAT_USE_E2B_SANDBOX", "").lower()
if env_val:
return env_val in ("true", "1", "yes", "on")
return True if v is None else v

@field_validator("e2b_api_key", mode="before")
@classmethod
def get_e2b_api_key(cls, v):
"""Get E2B API key from environment if not provided."""
if v is None:
v = os.getenv("CHAT_E2B_API_KEY") or os.getenv("E2B_API_KEY")
return v
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@field_validator("api_key", mode="before")
@classmethod
def get_api_key(cls, v):
Expand Down
18 changes: 10 additions & 8 deletions autogpt_platform/backend/backend/copilot/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)

from backend.data import db
from backend.util.json import SafeJson
from backend.util.json import SafeJson, sanitize_string

from .model import ChatMessage, ChatSession, ChatSessionInfo

Expand Down Expand Up @@ -101,15 +101,16 @@ async def add_chat_message(
"sequence": sequence,
}

# Add optional string fields
# Add optional string fields — sanitize to strip PostgreSQL-incompatible
# control characters (null bytes etc.) that may appear in tool outputs.
if content is not None:
data["content"] = content
data["content"] = sanitize_string(content)
if name is not None:
data["name"] = name
if tool_call_id is not None:
data["toolCallId"] = tool_call_id
if refusal is not None:
data["refusal"] = refusal
data["refusal"] = sanitize_string(refusal)

# Add optional JSON fields only when they have values
if tool_calls is not None:
Expand Down Expand Up @@ -170,15 +171,16 @@ async def add_chat_messages_batch(
"createdAt": now,
}

# Add optional string fields
# Add optional string fields — sanitize to strip
# PostgreSQL-incompatible control characters.
if msg.get("content") is not None:
data["content"] = msg["content"]
data["content"] = sanitize_string(msg["content"])
if msg.get("name") is not None:
data["name"] = msg["name"]
if msg.get("tool_call_id") is not None:
data["toolCallId"] = msg["tool_call_id"]
if msg.get("refusal") is not None:
data["refusal"] = msg["refusal"]
data["refusal"] = sanitize_string(msg["refusal"])

# Add optional JSON fields only when they have values
if msg.get("tool_calls") is not None:
Expand Down Expand Up @@ -312,7 +314,7 @@ async def update_tool_message_content(
"toolCallId": tool_call_id,
},
data={
"content": new_content,
"content": sanitize_string(new_content),
},
)
if result == 0:
Expand Down
8 changes: 8 additions & 0 deletions autogpt_platform/backend/backend/copilot/response_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pydantic import BaseModel, Field

from backend.util.json import dumps as json_dumps
from backend.util.truncate import truncate

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -150,6 +151,9 @@ class StreamToolInputAvailable(StreamBaseResponse):
)


_MAX_TOOL_OUTPUT_SIZE = 100_000 # ~100 KB; truncate to avoid bloating SSE/DB


class StreamToolOutputAvailable(StreamBaseResponse):
"""Tool execution result."""

Expand All @@ -164,6 +168,10 @@ class StreamToolOutputAvailable(StreamBaseResponse):
default=True, description="Whether the tool execution succeeded"
)

def model_post_init(self, __context: Any) -> None:
"""Truncate oversized outputs after construction."""
self.output = truncate(self.output, _MAX_TOOL_OUTPUT_SIZE)

def to_sse(self) -> str:
"""Convert to SSE format, excluding non-spec fields."""
data = {
Expand Down
Loading
Loading