- Update OpenCode compose template with web server on port 3000
- Add default_port=3000 and interfaces=[terminal, web] to OpenCode seed data
- Remove hardcoded 8080 fallback in tunnel creation
- Fail gracefully when tool type has no default_port configured
- Update frontend ToolType API to include default_port, category, interfaces
- Add port, category, and interfaces fields to tool type creation form
- Display port and interfaces in tool type cards
- Create migration 0012 to make default_port non-nullable
- Set default_port values for existing built-in tool types
- Quality gates: typecheck ✓, build ✓, Python syntax ✓
- Add inline confirmation dialog before stopping instances
- Delete instances from state immediately without page reload
- Add health check polling every 30s for running instances
- Show tunnel error badge when tunnel is unreachable
- Add 'Fix Tunnel' button to recreate broken tunnels
- Update API client with health check and tunnel recreation endpoints
Replace persistent Cloudflare tunnels (API-based) with temporary tunnels using
'cloudflared tunnel --url'. This removes the need for Cloudflare API tokens,
DNS records, and persistent tunnel management.
Changes:
- Install cloudflared binary in API Dockerfile
- Add start_cloudflared_tunnel() and stop_cloudflared_tunnel() to docker.py
- Update instance start/stop/restart/delete to use temporary tunnels
- Store tunnel PID in tunnel_id field, temporary URL in url/public_url
- Remove Cloudflare API service (cloudflare_tunnel.py)
- Remove cloudflared container from docker-compose
- Remove Cloudflare env vars (CLOUDFLARE_API_TOKEN, ZONE_ID, etc.)
- Remove Cloudflare configuration from config.py
- Remove Cloudflare startup check from main.py
- Remove /health/cloudflare endpoint
- Fix delete_tunnel calls to use correct subdomain format (instance-{id[:8]})
- Add Cloudflare configuration check at startup with clear warnings
- Help diagnose why tunnels aren't being created
- Add 'backend' external network to all compose templates so cloudflared can reach tool containers
- Add better error handling and logging to create_tunnel() with specific error messages for auth failures
- Add check_cloudflare_config() diagnostic function
- Add /health/cloudflare endpoint to verify Cloudflare configuration
- Import Any type for type hints
The compose template uses {{TOOL_NAME}} for container_name but
we weren't passing it in the variables dict, causing YAML parse error.
Error was:
yaml: cannot use 'map[string]interface {}{"TOOL_NAME":interface {}(nil)}' as a map key
Add comprehensive logging to trace 500 error:
- Log each step of docker compose up (returncode, stdout, stderr)
- Log container ID and name after start
- Log tool type and port being used
- Log each step of Cloudflare tunnel creation with API responses
- Log cloudflared config updates
This will help identify exactly where the failure occurs.
Cloudflared was hardcoded to route to port 8080, but containers
listen on different ports (8443 for code-server, 8888 for jupyter).
- Add instance_port parameter to create_tunnel and update_cloudflared_config
- Fetch tool type default_port when creating tunnels
- Route to correct internal port instead of hardcoded 8080
- Backend now returns stopped and error sessions too
- Active sessions include running/building/pending
- Recent sessions show stopped/error only
- Status badge shows actual status (running/building/pending)
Backend:
- Add cloudflare_tunnel.py service for creating/deleting tunnels via Cloudflare API
- Add public_url and tunnel_id fields to ToolInstance model
- Update start_instance to create Cloudflare tunnel after container starts
- Update stop_instance to delete tunnel before stopping container
- Update delete_instance to cleanup tunnel before deletion
- Update restart_instance to recreate tunnel on restart
- Create Alembic migration 0011 for tunnel fields
- Add Cloudflare config settings (API token, zone ID, account ID, base domain)
Infrastructure:
- Add cloudflared service to docker-compose.traefik.yml
- Mount shared cloudflared_config volume between API and cloudflared containers
- Add Cloudflare env vars to API service
Frontend:
- Update instance Open button to handle both full URLs and proxy paths
The instance URL is now set to the Cloudflare tunnel public URL when available,
falling back to the API proxy path if tunnel creation fails.
The proxy was using instance.port which is a dynamically allocated
host port (e.g., 10001). But containers communicate on the Docker
network using their internal ports (8443 for code-server, 8888 for
jupyter). This caused connection failures when opening instances.
- Add default_port field to ToolType model (null for terminal-only tools)
- Create migration 0010 for default_port column
- Update seed data: code-server=8443, jupyter=8888, opencode=null
- Update proxy to use tool type's default_port instead of instance.port
- Update frontend ToolType interface to include default_port
Fixes: Opening instances now routes to correct internal container port
Settings save:
- Remove exclude_none=True from user_config.py model_dump() call
- Fixes fields not updating when cleared or set to null/undefined
Session deletion:
- Add project_id and repository_id to get_user_sessions response
- Update frontend Session interface with new fields
- Fix handleDelete to use IDs instead of names, resolving 404 errors
- Create ToolConfigsPage with tool type selector, config list, and add/edit form
- Support both env and file config types
- Add route /tool-configs and navigation item
- Update API client with tool config endpoints
- Build passes successfully
Add support for tool categories, interface types, and per-tool configuration.
Backend:
- Add category and interfaces fields to ToolType model
- Create ToolConfig model for storing tool-specific settings
- Add tool_configs API endpoints (CRUD)
- Update built-in tool types with categories and interfaces:
- code-server: editor, [web]
- jupyter-notebook: notebook, [web]
- opencode: ai-assistant, [terminal]
- Update instance API to include tool type interfaces
- Create Alembic migrations 0008 and 0009
Frontend:
- Update ToolType and Session interfaces with new fields
- Conditionally show Open/Terminal buttons based on tool interfaces
- Add API client for tool configs
OpenSpec: tool-config-management change created and implemented.
Add API proxy endpoint so users can access running tool instances
through the backend API instead of internal Docker network.
Backend:
- Add container_name field to ToolInstance model
- Create /instances/{id}/proxy/{path:path} endpoint with ownership checks
- Proxy HTTP requests to containers via docker network using container names
- Support all HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
- Store proxy URL in instance.url instead of localhost
- Add Alembic migration 0007 for container_name column
- Add get_container_name() utility to docker.py
Frontend:
- Update Open button to use full proxy URL (API_BASE_URL + instance.url)
Closes instance-proxy OpenSpec change.
- Add INSTANCE_BASE_PATH config option (defaults to /data/instances)
- Update docker.py to use configured path instead of hardcoded 'data/instances'
- Update Dockerfile to create /data/instances and chown to appuser
- Add instance_data volume to docker-compose.traefik.yml and docker-compose.yml
- Set INSTANCE_BASE_PATH env var in both compose files
This fixes the PermissionError when creating tool instances because
appuser can now write to /data/instances.
- Add API base URL to CORS allowed origins alongside web base URL
- Add CORS origin logging on startup for debugging
- Wrap instance creation in try/except with detailed error logging
- Return proper error message instead of raw 500 for instance creation failures
This fixes CORS errors when the frontend makes cross-origin requests and
provides better diagnostics for instance creation failures.
The OAuth callback was setting the session cookie on the 'response'
parameter but returning a brand new RedirectResponse, causing the
cookie to be lost. This created an infinite login loop where the
callback succeeded but /auth/me always returned 401.
- Set cookies on the RedirectResponse instead of the unused response param
- Remove unused 'response: Response' parameter from callback handler
- Fixes login loop in production with cross-domain cookies
- Add RequestValidationError handler to log validation errors
- Add extra=ignore to CreateInstanceRequest to be more lenient
- Add logging to create_instance endpoint to see received data
- Add missing logger import in tool_instances.py
- Add Sessions tab to navigation between Dashboard and Projects
- Show active session count badge in navigation
- Create SessionsPage with:
- Last session section with resume button
- Active sessions grid with open/stop actions
- Recent sessions list
- Create session form with project/repo/tool selectors
- Add last_session_id to user config
- Update UserConfig schemas (backend and frontend)
- Add comprehensive CSS for sessions page
Quality gates: typecheck ✓, lint ✓, build ✓
The create_instance endpoint was expecting tool_type_id and display_name
as query parameters, but the frontend sends them in the JSON body.
Added CreateInstanceRequest Pydantic model to properly parse the request body.
Fixes 422 Unprocessable Content error on instance creation.
- Create enhanced health endpoints with /health and /health/db
- Add comprehensive docstrings to all API endpoints
- Add Pydantic response models with Field descriptions
- Create apps/api/README.md with setup guide
- Create ADR-001 for session auth decision
- Create ADR-002 for async SQLAlchemy decision
- Quality gates: Python syntax OK, TypeScript OK
Backend:
- Create ToolInstance model with status tracking
- Add Alembic migration for tool_instances table
- Create Docker service for compose template rendering and container execution
- Add CRUD API endpoints for tool instances
- Add lifecycle endpoints (start/stop/restart)
- Add user sessions endpoint for navigation
- Register routers in main.py
Frontend:
- Create SessionsProvider with React context
- Create sessions API client
- Update AppShell with sessions section in navigation
- Add session status indicators and polling
- Add CSS for session navigation
Quality gates: typecheck ✓, lint ✓, build ✓
- Remove useless git diff --numstat call that failed in bare repos
- Use raw bytes instead of text decoding to avoid encoding issues
- Properly check subprocess return codes
Fixes false positive binary detection for text files like .env.sample
Frontend workspace was calling GET /projects/{id} which didn't exist,
causing 405 errors and preventing WorkspaceHeader from rendering.
Add get_project endpoint that returns a single project by ID with
ownership verification.
Using 'git log --all <branch>' creates ambiguous behavior. Now:
- Without branch: uses --all to show all commits from all refs
- With branch: shows only commits from that specific branch
This ensures consistent commit counts between git CLI and API.
Backend was sending Unix timestamps as strings (e.g. '1716112800')
which JavaScript Date couldn't parse. Now sends proper ISO 8601
format dates that work with new Date() in the browser.
Backend was returning fields like 'author', 'email', 'date' but frontend
expected 'author_name', 'author_email', 'author_date'. Also 'branches' and
'tags' were separate but frontend expects unified 'refs' array.
- Update _commit_to_dict to return frontend-compatible field names
- Add graph_symbol and graph_depth for commit graph display
- Update get_commit_detail to return matching field names
- Include diff as top-level field for detail view
The git log --graph output uses | characters in the ASCII art,
which conflicts with using | as a format separator. Switch to
NULL bytes (\x00) which won't appear in commit data.
Also removed --graph flag since we build graph data from
parent relationships instead.