diff --git a/Makefile b/Makefile index ad549c2..a9887c1 100644 --- a/Makefile +++ b/Makefile @@ -4,17 +4,18 @@ help: @echo "DeerFlow Development Commands:" - @echo " make check - Check if all required tools are installed" - @echo " make install - Install all dependencies (frontend + backend)" - @echo " make dev - Start all services (frontend + backend + nginx on localhost:2026)" - @echo " make stop - Stop all running services" - @echo " make clean - Clean up processes and temporary files" + @echo " make check - Check if all required tools are installed" + @echo " make install - Install all dependencies (frontend + backend)" + @echo " make setup-sandbox - Pre-pull sandbox container image (recommended)" + @echo " make dev - Start all services (frontend + backend + nginx on localhost:2026)" + @echo " make stop - Stop all running services" + @echo " make clean - Clean up processes and temporary files" @echo "" @echo "Docker Development Commands:" @echo " make docker-init - Initialize and install dependencies in Docker containers" @echo " make docker-start - Start all services in Docker (localhost:2026)" @echo " make docker-stop - Stop Docker development services" - @echo " make docker-logs - View Docker development logs" + @echo " make docker-logs - View Docker development logs" @echo " make docker-logs-web - View Docker frontend logs" @echo " make docker-logs-api - View Docker backend logs" @@ -100,6 +101,43 @@ install: @echo "Installing frontend dependencies..." @cd frontend && pnpm install @echo "✓ All dependencies installed" + @echo "" + @echo "==========================================" + @echo " Optional: Pre-pull Sandbox Image" + @echo "==========================================" + @echo "" + @echo "If you plan to use Docker/Container-based sandbox, you can pre-pull the image:" + @echo " make setup-sandbox" + @echo "" + +# Pre-pull sandbox Docker image (optional but recommended) +setup-sandbox: + @echo "==========================================" + @echo " Pre-pulling Sandbox Container Image" + @echo "==========================================" + @echo "" + @IMAGE=$$(grep -A 20 "# sandbox:" config.yaml 2>/dev/null | grep "image:" | awk '{print $$2}' | head -1); \ + if [ -z "$$IMAGE" ]; then \ + IMAGE="enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest"; \ + echo "Using default image: $$IMAGE"; \ + else \ + echo "Using configured image: $$IMAGE"; \ + fi; \ + echo ""; \ + if command -v container >/dev/null 2>&1 && [ "$$(uname)" = "Darwin" ]; then \ + echo "Detected Apple Container on macOS, pulling image..."; \ + container pull "$$IMAGE" || echo "⚠ Apple Container pull failed, will try Docker"; \ + fi; \ + if command -v docker >/dev/null 2>&1; then \ + echo "Pulling image using Docker..."; \ + docker pull "$$IMAGE"; \ + echo ""; \ + echo "✓ Sandbox image pulled successfully"; \ + else \ + echo "✗ Neither Docker nor Apple Container is available"; \ + echo " Please install Docker: https://docs.docker.com/get-docker/"; \ + exit 1; \ + fi # Start all services dev: @@ -110,7 +148,7 @@ dev: @-nginx -c $(PWD)/docker/nginx/nginx.local.conf -p $(PWD) -s quit 2>/dev/null || true @sleep 1 @-pkill -9 nginx 2>/dev/null || true - @-docker ps -q --filter "name=deer-flow-sandbox" | xargs -r docker stop 2>/dev/null || true + @-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true @sleep 1 @echo "" @echo "==========================================" @@ -132,7 +170,7 @@ dev: sleep 1; \ pkill -9 nginx 2>/dev/null || true; \ echo "Cleaning up sandbox containers..."; \ - docker ps -q --filter "name=deer-flow-sandbox" | xargs -r docker stop 2>/dev/null || true; \ + ./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true; \ echo "✓ All services stopped"; \ exit 0; \ }; \ @@ -183,7 +221,7 @@ stop: @sleep 1 @-pkill -9 nginx 2>/dev/null || true @echo "Cleaning up sandbox containers..." - @-docker ps -q --filter "name=deer-flow-sandbox" | xargs -r docker stop 2>/dev/null || true + @-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true @echo "✓ All services stopped" # Clean up diff --git a/README.md b/README.md index 288b539..2273c0b 100644 --- a/README.md +++ b/README.md @@ -41,18 +41,25 @@ If you prefer running services locally: make install ``` -3. **Start services**: +3. **(Optional) Pre-pull sandbox image**: + ```bash + # Recommended if using Docker/Container-based sandbox + make setup-sandbox + ``` + +4. **Start services**: ```bash make dev ``` -4. **Access**: http://localhost:2026 +5. **Access**: http://localhost:2026 See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed local development guide. ## Features - 🤖 **LangGraph-based Agents** - Multi-agent orchestration with sophisticated workflows +- 🧠 **Persistent Memory** - LLM-powered context retention across conversations with automatic fact extraction - 🔧 **Model Context Protocol (MCP)** - Extensible tool integration - 🎯 **Skills System** - Reusable agent capabilities - 🛡️ **Sandbox Execution** - Safe code execution environment diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index 1d48aad..d8c56c6 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -40,6 +40,17 @@ deer-flow/ └── custom/ # Custom skills (gitignored) ``` +## Important Development Guidelines + +### Documentation Update Policy +**CRITICAL: Always update README.md and CLAUDE.md after every code change** + +When making code changes, you MUST update the relevant documentation: +- Update `README.md` for user-facing changes (features, setup, usage instructions) +- Update `CLAUDE.md` for development changes (architecture, commands, workflows, internal systems) +- Keep documentation synchronized with the codebase at all times +- Ensure accuracy and timeliness of all documentation + ## Commands **Root directory** (for full application): @@ -202,7 +213,49 @@ Configuration priority: 5. `TitleMiddleware` - Generates conversation titles 6. `TodoListMiddleware` - Tracks multi-step tasks (if plan_mode enabled) 7. `ViewImageMiddleware` - Injects image details for vision models - 8. `ClarificationMiddleware` - Handles clarification requests (must be last) + 8. `MemoryMiddleware` - Automatic context retention and personalization (if enabled) + 9. `ClarificationMiddleware` - Handles clarification requests (must be last) + +**Memory System** (`src/agents/memory/`) +- LLM-powered personalization layer that automatically extracts and stores user context across conversations +- Components: + - `updater.py` - LLM-based memory updates with fact extraction and file I/O + - `queue.py` - Debounced update queue for batching and performance optimization + - `prompt.py` - Prompt templates and formatting utilities for memory updates +- `MemoryMiddleware` (`src/agents/middlewares/memory_middleware.py`) - Queues conversations for memory updates +- Gateway API (`src/gateway/routers/memory.py`) - REST endpoints for memory management +- Storage: JSON file at `backend/.deer-flow/memory.json` + +**Memory Data Structure**: +- **User Context** (current state): + - `workContext` - Work-related information (job, projects, technologies) + - `personalContext` - Preferences, communication style, background + - `topOfMind` - Current focus areas and immediate priorities +- **History** (temporal context): + - `recentMonths` - Recent activities and discussions + - `earlierContext` - Important historical context + - `longTermBackground` - Persistent background information +- **Facts** (structured knowledge): + - Discrete facts with categories: `preference`, `knowledge`, `context`, `behavior`, `goal` + - Each fact includes: `id`, `content`, `category`, `confidence` (0-1), `createdAt`, `source` (thread ID) + - Confidence threshold (default 0.7) filters low-quality facts + - Max facts limit (default 100) keeps highest-confidence facts + +**Memory Workflow**: +1. **Post-Interaction**: `MemoryMiddleware` filters messages (user inputs + final AI responses only) and queues conversation +2. **Debounced Processing**: Queue waits 30s (configurable), batches multiple updates, resets timer on new updates +3. **LLM-Based Update**: Background thread loads memory, formats conversation, invokes LLM to extract: + - Updated context summaries (1-3 sentences each) + - New facts with confidence scores and categories + - Facts to remove (contradictions) +4. **Storage**: Applies updates atomically to `memory.json` with cache invalidation (mtime-based) +5. **Injection**: Next interaction loads memory, formats top 15 facts + context, injects into `` tags in system prompt + +**Memory API Endpoints** (`/api/memory`): +- `GET /api/memory` - Retrieve current memory data +- `POST /api/memory/reload` - Force reload from file (invalidates cache) +- `GET /api/memory/config` - Get memory configuration +- `GET /api/memory/status` - Get both config and data ### Config Schema @@ -215,6 +268,15 @@ Models, tools, sandbox providers, skills, and middleware settings are configured - `skills.container_path`: Container mount path (default: `/mnt/skills`) - `title`: Automatic thread title generation configuration - `summarization`: Automatic conversation summarization configuration +- `memory`: Memory system configuration + - `enabled`: Master switch (boolean) + - `storage_path`: Path to memory.json file (relative to backend/) + - `debounce_seconds`: Wait time before processing updates (default: 30) + - `model_name`: LLM model for memory updates (null = use default model) + - `max_facts`: Maximum facts to store (default: 100) + - `fact_confidence_threshold`: Minimum confidence to store fact (default: 0.7) + - `injection_enabled`: Inject memory into system prompt (boolean) + - `max_injection_tokens`: Token limit for memory injection (default: 2000) **Extensions Configuration Schema** (`extensions_config.json`): - `mcpServers`: Map of MCP server name to configuration @@ -307,6 +369,29 @@ For models with `supports_vision: true`: - `view_image_tool` added to agent's toolset - Images automatically converted and injected into state +### Memory System + +Persistent context retention and personalization across conversations: +- **Automatic Extraction**: LLM analyzes conversations to extract user context, facts, and preferences +- **Structured Storage**: Maintains user context, history, and confidence-scored facts in JSON format +- **Smart Filtering**: Only processes meaningful messages (user inputs + final AI responses) +- **Debounced Updates**: Batches updates to minimize LLM calls (configurable wait time) +- **System Prompt Injection**: Automatically injects relevant memory context into agent prompts +- **Cache Optimization**: File modification time-based cache invalidation for external edits +- **Thread Safety**: Locks protect queue and cache for concurrent access +- **REST API**: Full CRUD operations via `/api/memory` endpoints +- **Frontend Integration**: Memory settings page for viewing and managing memory data + +**Configuration**: Controlled via `memory` section in `config.yaml` +- Enable/disable memory system +- Configure storage path, debounce timing, fact limits +- Control system prompt injection and token limits +- Set confidence thresholds for fact storage + +**Storage Location**: `backend/.deer-flow/memory.json` + +See configuration section for detailed settings. + ## Code Style - Uses `ruff` for linting and formatting diff --git a/backend/docs/APPLE_CONTAINER.md b/backend/docs/APPLE_CONTAINER.md new file mode 100644 index 0000000..6ef82d0 --- /dev/null +++ b/backend/docs/APPLE_CONTAINER.md @@ -0,0 +1,238 @@ +# Apple Container Support + +DeerFlow now supports Apple Container as the preferred container runtime on macOS, with automatic fallback to Docker. + +## Overview + +Starting with this version, DeerFlow automatically detects and uses Apple Container on macOS when available, falling back to Docker when: +- Apple Container is not installed +- Running on non-macOS platforms + +This provides better performance on Apple Silicon Macs while maintaining compatibility across all platforms. + +## Benefits + +### On Apple Silicon Macs with Apple Container: +- **Better Performance**: Native ARM64 execution without Rosetta 2 translation +- **Lower Resource Usage**: Lighter weight than Docker Desktop +- **Native Integration**: Uses macOS Virtualization.framework + +### Fallback to Docker: +- Full backward compatibility +- Works on all platforms (macOS, Linux, Windows) +- No configuration changes needed + +## Requirements + +### For Apple Container (macOS only): +- macOS 15.0 or later +- Apple Silicon (M1/M2/M3/M4) +- Apple Container CLI installed + +### Installation: +```bash +# Download from GitHub releases +# https://github.com/apple/container/releases + +# Verify installation +container --version + +# Start the service +container system start +``` + +### For Docker (all platforms): +- Docker Desktop or Docker Engine + +## How It Works + +### Automatic Detection + +The `AioSandboxProvider` automatically detects the available container runtime: + +1. On macOS: Try `container --version` + - Success → Use Apple Container + - Failure → Fall back to Docker + +2. On other platforms: Use Docker directly + +### Runtime Differences + +Both runtimes use nearly identical command syntax: + +**Container Startup:** +```bash +# Apple Container +container run --rm -d -p 8080:8080 -v /host:/container -e KEY=value image + +# Docker +docker run --rm -d -p 8080:8080 -v /host:/container -e KEY=value image +``` + +**Container Cleanup:** +```bash +# Apple Container (with --rm flag) +container stop # Auto-removes due to --rm + +# Docker (with --rm flag) +docker stop # Auto-removes due to --rm +``` + +### Implementation Details + +The implementation is in `backend/src/community/aio_sandbox/aio_sandbox_provider.py`: + +- `_detect_container_runtime()`: Detects available runtime at startup +- `_start_container()`: Uses detected runtime, skips Docker-specific options for Apple Container +- `_stop_container()`: Uses appropriate stop command for the runtime + +## Configuration + +No configuration changes are needed! The system works automatically. + +However, you can verify the runtime in use by checking the logs: + +``` +INFO:src.community.aio_sandbox.aio_sandbox_provider:Detected Apple Container: container version 0.1.0 +INFO:src.community.aio_sandbox.aio_sandbox_provider:Starting sandbox container using container: ... +``` + +Or for Docker: +``` +INFO:src.community.aio_sandbox.aio_sandbox_provider:Apple Container not available, falling back to Docker +INFO:src.community.aio_sandbox.aio_sandbox_provider:Starting sandbox container using docker: ... +``` + +## Container Images + +Both runtimes use OCI-compatible images. The default image works with both: + +```yaml +sandbox: + use: src.community.aio_sandbox:AioSandboxProvider + image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # Default image +``` + +Make sure your images are available for the appropriate architecture: +- ARM64 for Apple Container on Apple Silicon +- AMD64 for Docker on Intel Macs +- Multi-arch images work on both + +### Pre-pulling Images (Recommended) + +**Important**: Container images are typically large (500MB+) and are pulled on first use, which can cause a long wait time without clear feedback. + +**Best Practice**: Pre-pull the image during setup: + +```bash +# From project root +make setup-sandbox +``` + +This command will: +1. Read the configured image from `config.yaml` (or use default) +2. Detect available runtime (Apple Container or Docker) +3. Pull the image with progress indication +4. Verify the image is ready for use + +**Manual pre-pull**: + +```bash +# Using Apple Container +container pull enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest + +# Using Docker +docker pull enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest +``` + +If you skip pre-pulling, the image will be automatically pulled on first agent execution, which may take several minutes depending on your network speed. + +## Cleanup Scripts + +The project includes a unified cleanup script that handles both runtimes: + +**Script:** `scripts/cleanup-containers.sh` + +**Usage:** +```bash +# Clean up all DeerFlow sandbox containers +./scripts/cleanup-containers.sh deer-flow-sandbox + +# Custom prefix +./scripts/cleanup-containers.sh my-prefix +``` + +**Makefile Integration:** + +All cleanup commands in `Makefile` automatically handle both runtimes: +```bash +make stop # Stops all services and cleans up containers +make clean # Full cleanup including logs +``` + +## Testing + +Test the container runtime detection: + +```bash +cd backend +python test_container_runtime.py +``` + +This will: +1. Detect the available runtime +2. Optionally start a test container +3. Verify connectivity +4. Clean up + +## Troubleshooting + +### Apple Container not detected on macOS + +1. Check if installed: + ```bash + which container + container --version + ``` + +2. Check if service is running: + ```bash + container system start + ``` + +3. Check logs for detection: + ```bash + # Look for detection message in application logs + grep "container runtime" logs/*.log + ``` + +### Containers not cleaning up + +1. Manually check running containers: + ```bash + # Apple Container + container list + + # Docker + docker ps + ``` + +2. Run cleanup script manually: + ```bash + ./scripts/cleanup-containers.sh deer-flow-sandbox + ``` + +### Performance issues + +- Apple Container should be faster on Apple Silicon +- If experiencing issues, you can force Docker by temporarily renaming the `container` command: + ```bash + # Temporary workaround - not recommended for permanent use + sudo mv /opt/homebrew/bin/container /opt/homebrew/bin/container.bak + ``` + +## References + +- [Apple Container GitHub](https://github.com/apple/container) +- [Apple Container Documentation](https://github.com/apple/container/blob/main/docs/) +- [OCI Image Spec](https://github.com/opencontainers/image-spec) diff --git a/backend/docs/SETUP.md b/backend/docs/SETUP.md index 411268b..9e9214f 100644 --- a/backend/docs/SETUP.md +++ b/backend/docs/SETUP.md @@ -49,6 +49,22 @@ The backend searches for `config.yaml` in this order: **Recommended**: Place `config.yaml` in project root (`deer-flow/config.yaml`). +## Sandbox Setup (Optional but Recommended) + +If you plan to use Docker/Container-based sandbox (configured in `config.yaml` under `sandbox.use: src.community.aio_sandbox:AioSandboxProvider`), it's highly recommended to pre-pull the container image: + +```bash +# From project root +make setup-sandbox +``` + +**Why pre-pull?** +- The sandbox image (~500MB+) is pulled on first use, causing a long wait +- Pre-pulling provides clear progress indication +- Avoids confusion when first using the agent + +If you skip this step, the image will be automatically pulled on first agent execution, which may take several minutes depending on your network speed. + ## Troubleshooting ### Config file not found diff --git a/backend/src/community/aio_sandbox/aio_sandbox_provider.py b/backend/src/community/aio_sandbox/aio_sandbox_provider.py index c47caaf..8edb36b 100644 --- a/backend/src/community/aio_sandbox/aio_sandbox_provider.py +++ b/backend/src/community/aio_sandbox/aio_sandbox_provider.py @@ -32,14 +32,17 @@ IDLE_CHECK_INTERVAL = 60 # Check every 60 seconds class AioSandboxProvider(SandboxProvider): - """Sandbox provider that manages Docker containers running the AIO sandbox. + """Sandbox provider that manages containers running the AIO sandbox. + + On macOS, automatically prefers Apple Container if available, otherwise falls back to Docker. + On other platforms, uses Docker. Configuration options in config.yaml under sandbox: use: src.community.aio_sandbox:AioSandboxProvider - image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # Docker image to use + image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # Container image to use (works with both runtimes) port: 8080 # Base port for sandbox containers base_url: http://localhost:8080 # If set, uses existing sandbox instead of starting new container - auto_start: true # Whether to automatically start Docker container + auto_start: true # Whether to automatically start container container_prefix: deer-flow-sandbox # Prefix for container names idle_timeout: 600 # Idle timeout in seconds (default: 600 = 10 minutes). Set to 0 to disable. mounts: # List of volume mounts @@ -62,6 +65,7 @@ class AioSandboxProvider(SandboxProvider): self._shutdown_called = False self._idle_checker_stop = threading.Event() self._idle_checker_thread: threading.Thread | None = None + self._container_runtime = self._detect_container_runtime() # Register shutdown handler to clean up containers on exit atexit.register(self.shutdown) @@ -184,6 +188,35 @@ class AioSandboxProvider(SandboxProvider): resolved[key] = str(value) return resolved + def _detect_container_runtime(self) -> str: + """Detect which container runtime to use. + + On macOS, prefer Apple Container if available, otherwise fall back to Docker. + On other platforms, use Docker. + + Returns: + "container" for Apple Container, "docker" for Docker. + """ + import platform + + # Only try Apple Container on macOS + if platform.system() == "Darwin": + try: + result = subprocess.run( + ["container", "--version"], + capture_output=True, + text=True, + check=True, + timeout=5, + ) + logger.info(f"Detected Apple Container: {result.stdout.strip()}") + return "container" + except (FileNotFoundError, subprocess.CalledProcessError, subprocess.TimeoutExpired): + logger.info("Apple Container not available, falling back to Docker") + + # Default to Docker + return "docker" + def _is_sandbox_ready(self, base_url: str, timeout: int = 30) -> bool: """Check if sandbox is ready to accept connections. @@ -253,7 +286,10 @@ class AioSandboxProvider(SandboxProvider): return None def _start_container(self, sandbox_id: str, port: int, extra_mounts: list[tuple[str, str, bool]] | None = None) -> str: - """Start a new Docker container for the sandbox. + """Start a new container for the sandbox. + + On macOS, prefers Apple Container if available, otherwise uses Docker. + On other platforms, uses Docker. Args: sandbox_id: Unique identifier for the sandbox. @@ -267,17 +303,22 @@ class AioSandboxProvider(SandboxProvider): container_name = f"{self._config['container_prefix']}-{sandbox_id}" cmd = [ - "docker", + self._container_runtime, "run", - "--security-opt", - "seccomp=unconfined", + ] + + # Add Docker-specific security options + if self._container_runtime == "docker": + cmd.extend(["--security-opt", "seccomp=unconfined"]) + + cmd.extend([ "--rm", "-d", "-p", f"{port}:8080", "--name", container_name, - ] + ]) # Add configured environment variables for key, value in self._config["environment"].items(): @@ -303,26 +344,28 @@ class AioSandboxProvider(SandboxProvider): cmd.append(image) - logger.info(f"Starting sandbox container: {' '.join(cmd)}") + logger.info(f"Starting sandbox container using {self._container_runtime}: {' '.join(cmd)}") try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) container_id = result.stdout.strip() - logger.info(f"Started sandbox container {container_name} with ID {container_id}") + logger.info(f"Started sandbox container {container_name} with ID {container_id} using {self._container_runtime}") return container_id except subprocess.CalledProcessError as e: - logger.error(f"Failed to start sandbox container: {e.stderr}") + logger.error(f"Failed to start sandbox container using {self._container_runtime}: {e.stderr}") raise RuntimeError(f"Failed to start sandbox container: {e.stderr}") def _stop_container(self, container_id: str) -> None: - """Stop and remove a Docker container. + """Stop and remove a container. + + Since we use --rm flag, the container is automatically removed after stopping. Args: container_id: The container ID to stop. """ try: - subprocess.run(["docker", "stop", container_id], capture_output=True, text=True, check=True) - logger.info(f"Stopped sandbox container {container_id}") + subprocess.run([self._container_runtime, "stop", container_id], capture_output=True, text=True, check=True) + logger.info(f"Stopped sandbox container {container_id} using {self._container_runtime} (--rm will auto-remove)") except subprocess.CalledProcessError as e: logger.warning(f"Failed to stop sandbox container {container_id}: {e.stderr}") diff --git a/config.example.yaml b/config.example.yaml index f1d62b1..3c339d0 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -144,17 +144,20 @@ tools: sandbox: use: src.sandbox.local:LocalSandboxProvider -# Option 2: Docker-based AIO Sandbox -# Executes commands in isolated Docker containers +# Option 2: Container-based AIO Sandbox +# Executes commands in isolated containers (Docker or Apple Container) +# On macOS: Automatically prefers Apple Container if available, falls back to Docker +# On other platforms: Uses Docker # Uncomment to use: # sandbox: # use: src.community.aio_sandbox:AioSandboxProvider # -# # Optional: Use existing sandbox at this URL (no Docker container will be started) +# # Optional: Use existing sandbox at this URL (no container will be started) # # base_url: http://localhost:8080 # -# # Optional: Docker image to use +# # Optional: Container image to use (works with both Docker and Apple Container) # # Default: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest +# # Recommended: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest (works on both x86_64 and arm64) # # image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # # # Optional: Base port for sandbox containers (default: 8080) diff --git a/scripts/cleanup-containers.sh b/scripts/cleanup-containers.sh new file mode 100755 index 0000000..7d69a0b --- /dev/null +++ b/scripts/cleanup-containers.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# +# cleanup-containers.sh - Clean up DeerFlow sandbox containers +# +# This script cleans up both Docker and Apple Container runtime containers +# to ensure compatibility across different container runtimes. +# + +set -e + +PREFIX="${1:-deer-flow-sandbox}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo "Cleaning up sandbox containers with prefix: ${PREFIX}" + +# Function to clean up Docker containers +cleanup_docker() { + if command -v docker &> /dev/null; then + echo -n "Checking Docker containers... " + DOCKER_CONTAINERS=$(docker ps -q --filter "name=${PREFIX}" 2>/dev/null || echo "") + + if [ -n "$DOCKER_CONTAINERS" ]; then + echo "" + echo "Found Docker containers to clean up:" + docker ps --filter "name=${PREFIX}" --format "table {{.ID}}\t{{.Names}}\t{{.Status}}" + echo "Stopping Docker containers..." + echo "$DOCKER_CONTAINERS" | xargs docker stop 2>/dev/null || true + echo -e "${GREEN}✓ Docker containers stopped${NC}" + else + echo -e "${GREEN}none found${NC}" + fi + else + echo "Docker not found, skipping..." + fi +} + +# Function to clean up Apple Container containers +cleanup_apple_container() { + if command -v container &> /dev/null; then + echo -n "Checking Apple Container containers... " + + # List all containers and filter by name + CONTAINER_LIST=$(container list --format json 2>/dev/null || echo "[]") + + if [ "$CONTAINER_LIST" != "[]" ] && [ -n "$CONTAINER_LIST" ]; then + # Extract container IDs that match our prefix + CONTAINER_IDS=$(echo "$CONTAINER_LIST" | python3 -c " +import json +import sys +try: + containers = json.load(sys.stdin) + if isinstance(containers, list): + for c in containers: + if isinstance(c, dict): + name = c.get('name', '') + cid = c.get('id', '') + if '${PREFIX}' in name and cid: + print(cid) +except: + pass +" 2>/dev/null || echo "") + + if [ -n "$CONTAINER_IDS" ]; then + echo "" + echo "Found Apple Container containers to clean up:" + echo "$CONTAINER_IDS" | while read -r cid; do + echo " - $cid" + done + + echo "Stopping Apple Container containers..." + echo "$CONTAINER_IDS" | while read -r cid; do + container stop "$cid" 2>/dev/null || true + container delete "$cid" 2>/dev/null || true + done + echo -e "${GREEN}✓ Apple Container containers stopped${NC}" + else + echo -e "${GREEN}none found${NC}" + fi + else + echo -e "${GREEN}none found${NC}" + fi + else + echo "Apple Container not found, skipping..." + fi +} + +# Clean up both runtimes +cleanup_docker +cleanup_apple_container + +echo -e "${GREEN}✓ Container cleanup complete${NC}"