feat: implement repository clone mode with SSH key support

- Add clone_mode and branch fields to tool_instances
- Add ssh_key_id to git_repositories for per-repo SSH key assignment
- Implement host-side git cloning with branch selection (default: main)
- Mount SSH keys into containers for git operations in clone mode
- Add dirty state check on clone-mode instance deletion with confirmation
- Update SessionsPage with mount/clone selector, branch input, SSH key display
- Add SSH key selector to repository creation form
- Add dirty delete confirmation modal with changed files list
- Update API schemas and endpoints for new fields
- Sync delta specs to main specs (git-repo, tool-instances, repo-clone-mode)
- Archive completed OpenSpec change: repo-clone-mode-with-ssh
- Document git requirement for custom tool types

Quality gates: Frontend typecheck and build passed
OpenSpec: repo-clone-mode-with-ssh archived with all tasks complete
This commit is contained in:
Fusion
2026-05-22 22:56:35 +02:00
parent 952a9f3234
commit 063a839790
95 changed files with 2002 additions and 392 deletions
+20 -1
View File
@@ -6,7 +6,7 @@ Manage git repositories as bare repos on disk with metadata in database.
## Requirements
### Requirement: Repository Creation
The system SHALL allow creating new bare git repositories.
The system SHALL allow creating new bare git repositories with an optional SSH key association.
#### Scenario: Create repository
- GIVEN an authenticated user with a project
@@ -14,6 +14,25 @@ The system SHALL allow creating new bare git repositories.
- THEN a bare repo is initialized on disk at `/data/repos/{user_id}/{project_id}/{repo_name}.git`
- AND metadata is stored in the database
#### Scenario: Create repository with SSH key
- **GIVEN** an authenticated user with a project
- **WHEN** they create a new repository with `ssh_key_id`
- **THEN** a bare repo is initialized on disk
- **AND** the SSH key association is stored in the database
### Requirement: Repository SSH key assignment
The system SHALL allow associating an SSH key with a GitRepository for clone operations and container git access.
#### Scenario: Assign SSH key at repository creation
- **GIVEN** an authenticated user creating a repository
- **WHEN** they provide an `ssh_key_id`
- **THEN** the repository is associated with that SSH key
#### Scenario: Update repository SSH key
- **GIVEN** an authenticated user with an existing repository
- **WHEN** they call `PATCH /repositories/{id}/ssh-key` with a new `ssh_key_id`
- **THEN** the repository's SSH key association is updated
### Requirement: Repository Cloning
The system SHALL support cloning external repositories.
+41
View File
@@ -0,0 +1,41 @@
## ADDED Requirements
### Requirement: Proxy endpoint exists for running instances
The API SHALL expose an endpoint that forwards HTTP requests to a running tool instance.
#### Scenario: Access running instance
- **WHEN** an authenticated user sends a GET request to `/instances/{id}/proxy/`
- **THEN** the request is forwarded to the instance's container
- **AND** the response is returned to the user
#### Scenario: Access instance subpath
- **WHEN** an authenticated user sends a request to `/instances/{id}/proxy/api/status`
- **THEN** the request is forwarded to `{container_url}/api/status`
- **AND** the response is returned to the user
### Requirement: Only instance owner can access proxy
The proxy endpoint SHALL verify that the authenticated user owns the instance before forwarding.
#### Scenario: Owner accesses instance
- **WHEN** the instance owner requests `/instances/{id}/proxy/`
- **THEN** the request is forwarded to the instance
#### Scenario: Non-owner attempts access
- **WHEN** a user who does not own the instance requests `/instances/{id}/proxy/`
- **THEN** the API returns 403 Forbidden
### Requirement: Proxy handles WebSocket upgrades
The proxy endpoint SHALL support WebSocket upgrade requests for real-time features.
#### Scenario: WebSocket connection to instance
- **WHEN** a user sends a request with `Upgrade: websocket` header
- **THEN** the API establishes a bidirectional WebSocket connection to the instance
- **AND** messages are relayed between user and instance
### Requirement: Frontend uses proxy URL for instance access
The frontend SHALL link to the proxy endpoint instead of the internal container URL.
#### Scenario: User clicks Open button
- **WHEN** a user clicks "Open" on a running instance
- **THEN** a new tab opens to `/instances/{id}/proxy/`
- **AND** the proxied instance content is displayed
@@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: Runtime health endpoint
The system SHALL provide a health endpoint that checks both container and tunnel health.
#### Scenario: Full health check
- **GIVEN** a running web-enabled instance
- **WHEN** `GET /instances/{id}/health` is called
- **THEN** the response includes:
- `container_status`: "running", "exited", "restarting", or "not_found"
- `container_health`: "healthy", "unhealthy", or null (if no Docker healthcheck)
- `tunnel_status`: "healthy", "unreachable", or "error_response"
- `tunnel_status_code`: the HTTP status code from the tunnel URL, or null
- `probe_status`: "passed", "failed", "pending", or "not_configured"
- `healthy`: true only if container is running AND tunnel is healthy
#### Scenario: Health check for terminal-only instance
- **GIVEN** a running terminal-only instance
- **WHEN** `GET /instances/{id}/health` is called
- **THEN** the response includes `container_status: "running"`
- **AND** `tunnel_status: "not_applicable"`
- **AND** `healthy: true` if container is running
### Requirement: Continuous health polling
The system SHALL support periodic health checks from the frontend.
#### Scenario: Frontend health polling
- **GIVEN** active instances in the UI
- **WHEN** the frontend polls health every 30 seconds
- **THEN** the health status is displayed as a badge
- **AND** the badge shows "tunnel error" only when tunnel is unreachable
- **AND** the badge shows "app error" when tunnel returns 502/503/504
- **AND** the badge shows "starting" when container is up but probe is pending
### Requirement: Container state synchronization
The system SHALL update instance status when container state changes unexpectedly.
#### Scenario: Container crashes
- **GIVEN** an instance with status "running"
- **WHEN** the container exits (crash or OOM)
- **AND** a health check is performed
- **THEN** the instance status is updated to "error"
- **AND** the container exit code and logs are captured
#### Scenario: Container stopped externally
- **GIVEN** an instance with status "running"
- **WHEN** the container is stopped via docker command outside the system
- **AND** a health check is performed
- **THEN** the instance status is updated to "stopped"
## MODIFIED Requirements
None.
## REMOVED Requirements
None.
@@ -0,0 +1,83 @@
## ADDED Requirements
### Requirement: Container startup verification
The system SHALL verify that containers reach a running state before marking instances as "running".
#### Scenario: Container starts successfully
- **WHEN** `docker compose up` completes
- **THEN** the system polls `docker ps` every 2 seconds for up to 30 seconds
- **AND** when the container state is "running", the instance status becomes "starting"
- **AND** the readiness probe begins execution
#### Scenario: Container fails to start
- **WHEN** `docker compose up` completes
- **AND** the container exits within 30 seconds
- **THEN** the instance status becomes "error"
- **AND** the container exit code is stored in the error message
#### Scenario: Container stays in restarting loop
- **WHEN** `docker compose up` completes
- **AND** the container remains in "restarting" state after 30 seconds
- **THEN** the instance status becomes "error"
- **AND** the error message indicates the container is stuck restarting
### Requirement: Readiness probe execution
The system SHALL execute readiness probes for web-enabled tool instances before marking them as "running".
#### Scenario: Probe succeeds
- **GIVEN** a tool instance with status "starting"
- **AND** the tool type has a readiness probe configured
- **WHEN** the probe command returns exit code 0 within the timeout
- **THEN** the instance status becomes "running"
- **AND** the tunnel is created (for web tools)
#### Scenario: Probe times out
- **GIVEN** a tool instance with status "starting"
- **AND** the tool type has a readiness probe configured
- **WHEN** the probe does not succeed within the configured timeout (default 30s)
- **THEN** the instance status becomes "unhealthy"
- **AND** the tunnel is still created (the container is running)
- **AND** the last probe output is stored for diagnostics
#### Scenario: Terminal tool skips probe
- **GIVEN** a tool instance for a terminal-only tool type
- **WHEN** the container reaches "running" state
- **THEN** the instance status immediately becomes "running"
- **AND** no readiness probe is executed
### Requirement: Container health monitoring
The system SHALL check container health in addition to tunnel health.
#### Scenario: Container is healthy
- **GIVEN** a running instance
- **WHEN** the health endpoint is queried
- **THEN** the response includes `container_status: "running"`
- **AND** the response includes `container_health: "healthy"` if Docker healthcheck exists
#### Scenario: Container has crashed
- **GIVEN** a running instance
- **WHEN** the container exits or is stopped externally
- **AND** the health endpoint is queried
- **THEN** the response includes `container_status: "exited"`
- **AND** the response includes `healthy: false`
- **AND** the instance status in the database is updated to "error"
## MODIFIED Requirements
### Requirement: Status Monitoring
The system SHALL track tool status with startup and health states.
#### Scenario: Status check with health details
- **GIVEN** a tool instance
- **WHEN** status is queried
- **THEN** the real-time container status is returned:
- `pending`: Instance created, container not yet started
- `starting`: Container is running, readiness probe in progress
- `running`: Container is running and probe passed (or terminal tool)
- `unhealthy`: Container is running but probe failed/timed out
- `stopped`: Container was stopped by user
- `error`: Container failed to start or crashed
## REMOVED Requirements
None.
@@ -0,0 +1,39 @@
## ADDED Requirements
### Requirement: OpenCode runs a web server
The system SHALL configure OpenCode containers to run a web server accessible on port 3000.
#### Scenario: OpenCode container starts
- **GIVEN** an OpenCode tool instance
- **WHEN** the container starts
- **THEN** a web server is running on port 3000 inside the container
- **AND** the server serves a web terminal interface
### Requirement: OpenCode exposes web interface
The system SHALL mark OpenCode as having both terminal and web interfaces.
#### Scenario: OpenCode instance created
- **GIVEN** a new OpenCode instance
- **WHEN** the instance list is displayed
- **THEN** both "Open" and "Terminal" buttons are shown
### Requirement: OpenCode web terminal uses correct port
The system SHALL use port 3000 when creating tunnels for OpenCode instances.
#### Scenario: Tunnel created for OpenCode
- **GIVEN** an OpenCode instance with `default_port: 3000`
- **WHEN** the instance starts and creates a tunnel
- **THEN** the tunnel targets `http://container-name:3000`
### Requirement: OpenCode web terminal displays properly
The system SHALL serve a functional web terminal interface for OpenCode.
#### Scenario: User opens OpenCode web UI
- **GIVEN** a running OpenCode instance
- **WHEN** the user clicks the "Open" button
- **THEN** a new tab opens with the OpenCode web interface
- **AND** the interface shows a terminal connected to the OpenCode process
@@ -0,0 +1,51 @@
## ADDED Requirements
### Requirement: Readiness probe configuration
The system SHALL use tool type readiness probe configuration during instance startup.
#### Scenario: Web tool with custom probe
- **GIVEN** a tool type with `readiness_probe` configured as:
- `command: "curl -f http://localhost:8080/api/health"`
- `timeout: 60`
- `interval: 5`
- **WHEN** an instance of this type starts
- **THEN** the system executes the probe command inside the container
- **AND** retries every 5 seconds for up to 60 seconds
- **AND** the instance remains in "starting" status until probe succeeds
#### Scenario: Web tool with default probe
- **GIVEN** a web-enabled tool type with no `readiness_probe` configured
- **WHEN** an instance of this type starts
- **THEN** the system uses the default probe: `curl -f http://localhost:{port}`
- **AND** retries every 2 seconds for up to 30 seconds
#### Scenario: Probe command execution
- **GIVEN** a readiness probe command
- **WHEN** the system executes it inside the container
- **THEN** it runs via `docker exec {container_id} sh -c "{command}"`
- **AND** stdout/stderr are captured for diagnostics
- **AND** exit code 0 indicates success
### Requirement: Probe result storage
The system SHALL store readiness probe results for diagnostics.
#### Scenario: Successful probe logged
- **GIVEN** a readiness probe that succeeds
- **WHEN** the probe returns exit code 0
- **THEN** the success is logged with timestamp
- **AND** the instance status changes to "running"
#### Scenario: Failed probe logged
- **GIVEN** a readiness probe that fails or times out
- **WHEN** the probe reaches timeout
- **THEN** the failure is logged with last stdout/stderr output
- **AND** the instance status changes to "unhealthy"
- **AND** the probe output is available via the health endpoint
## MODIFIED Requirements
None.
## REMOVED Requirements
None.
+59
View File
@@ -0,0 +1,59 @@
# Repository Clone Mode Specification
## Purpose
Support host-side repository cloning for tool instances, enabling isolated development environments with full git history and SSH key access for container git operations.
## Requirements
### Requirement: Host-side repository cloning
The system SHALL clone repositories on the host filesystem before container startup when clone mode is selected.
#### Scenario: Clone repository with branch selection
- **GIVEN** a repository with a remote URL and SSH key
- **WHEN** an instance is created in clone mode with branch="feature-x"
- **THEN** the system runs `git clone --branch feature-x <remote_url> <instance_dir>/repo-clone/`
- **AND** the clone includes full history
#### Scenario: Clone repository with default branch
- **GIVEN** a repository with a remote URL and SSH key
- **WHEN** an instance is created in clone mode without specifying a branch
- **THEN** the system defaults to branch="main"
- **AND** runs `git clone --branch main <remote_url> <instance_dir>/repo-clone/`
### Requirement: SSH key preparation for containers
The system SHALL decrypt and prepare SSH keys for container mounting.
#### Scenario: Prepare SSH key files
- **GIVEN** a repository with an associated SSH key
- **WHEN** a clone-mode instance is started
- **THEN** the private key is decrypted and written to `instance_dir/.ssh/id_ed25519` with mode 600
- **AND** the public key is written to `instance_dir/.ssh/id_ed25519.pub`
- **AND** an SSH config is written to `instance_dir/.ssh/config` with `StrictHostKeyChecking no`
### Requirement: Repository dirty state detection
The system SHALL detect uncommitted changes in cloned repositories.
#### Scenario: Detect clean repository
- **GIVEN** a cloned repository with no changes
- **WHEN** dirty state is checked
- **THEN** the result indicates no uncommitted changes
#### Scenario: Detect dirty repository
- **GIVEN** a cloned repository with modified files
- **WHEN** dirty state is checked
- **THEN** the result indicates uncommitted changes with file details
## Dependencies
- Database models: GitRepository, ToolInstance, SSHKey
- Docker Compose volume mounting
- Git installed on host and in containers
## Quality Gates
- `pytest` must pass
- `mypy .` must pass
- `ruff check .` must pass
- `npm run typecheck` must pass
- `npm run lint` must pass
@@ -0,0 +1,34 @@
## ADDED Requirements
### Requirement: Stopping a session requires confirmation
The system SHALL display a confirmation dialog before stopping a running session.
#### Scenario: User initiates stop
- **WHEN** user clicks the "Stop" button on a running session
- **THEN** a confirmation dialog appears asking "Are you sure you want to stop this session?"
- **AND** the dialog provides "Cancel" and "Stop" options
#### Scenario: User confirms stop
- **WHEN** user clicks "Stop" in the confirmation dialog
- **THEN** the session stops
- **AND** the dialog closes
#### Scenario: User cancels stop
- **WHEN** user clicks "Cancel" in the confirmation dialog
- **THEN** the dialog closes
- **AND** the session remains running
### Requirement: Deleted sessions disappear from UI immediately
The system SHALL update the frontend state immediately after a session is successfully deleted.
#### Scenario: Delete session
- **WHEN** user deletes a session
- **AND** the delete API call returns success
- **THEN** the session is removed from the visible list
- **AND** no page reload is required
#### Scenario: Delete session failure
- **WHEN** user deletes a session
- **AND** the delete API call fails
- **THEN** the session remains in the list
- **AND** an error message is displayed
+115
View File
@@ -0,0 +1,115 @@
# Sessions Hub Specification
## Requirements
### Functional Requirements
1. **Sessions Tab**: Navigation item between Dashboard and Projects
2. **Active Sessions Display**: Show all running sessions with actions
3. **Last Session**: Prominently show last created/accessed session
4. **Quick Create**: Create sessions for any project from Sessions page
5. **Session Persistence**: Save last_session_id in user config
6. **Badge**: Show active session count in navigation
### Non-Functional Requirements
1. **Performance**: Load sessions in < 500ms
2. **Real-time**: Badge updates with active count
3. **Responsive**: Works on mobile and desktop
## API Specification
### Existing Endpoints Used
- `GET /users/me/sessions` - List all user sessions
- `POST /projects/{id}/repositories/{id}/instances` - Create instance
- `GET /projects` - List projects for selector
- `GET /projects/{id}/repositories` - List repos for selector
- `GET /tool-types` - List tool types for selector
- `GET /users/me/config` - Get user config (with last_session_id)
- `PATCH /users/me/config` - Update user config (last_session_id)
### User Config Schema Update
```python
class UserConfigUpdate(BaseModel):
theme: Optional[str] = None
default_editor: Optional[str] = None
git_user_name: Optional[str] = None
git_user_email: Optional[str] = None
last_session_id: Optional[str] = None # NEW
```
## UI Specification
### Sessions Page Layout
```
+------------------------------------------+
| Sessions [New Session]|
+------------------------------------------+
| |
| Last Session |
| +--------------------------------------+ |
| | VS Code Server - My Project [Open] | |
| | Running on port 8080 | |
| +--------------------------------------+ |
| |
| Active Sessions (3) |
| +----------+ +----------+ +----------+ |
| | Session 1| | Session 2| | Session 3| |
| | Running | | Running | | Running | |
| | [Open] | | [Open] | | [Open] | |
| +----------+ +----------+ +----------+ |
| |
| Recent Sessions |
| - Session 4 (stopped) |
| - Session 5 (stopped) |
| |
+------------------------------------------+
```
### Navigation Badge
```
[Dashboard] [Sessions (3)] [Projects] ...
```
Badge shows count of sessions with status === "running".
### Create Session Dialog
```
+------------------------------------------+
| Create New Session |
+------------------------------------------+
| Project: [Dropdown] |
| Repository: [Dropdown] |
| Tool Type: [Dropdown] |
| Name: [Input] |
| |
| [Cancel] [Create] |
+------------------------------------------+
```
## State Management
### Sessions Context (existing)
Already polls `/users/me/sessions` every 10s. Use this for:
- Active session count (badge)
- Active sessions list
- Recent sessions list
### User Config (existing)
Add `last_session_id` field. Update:
- On session creation
- On session open/resume
## Quality Gates
- TypeScript compilation passes
- ESLint passes
- All sessions load correctly
- Badge updates with active count
- Last session persists across reloads
- Create session works from Sessions page
@@ -0,0 +1,45 @@
## ADDED Requirements
### Requirement: Tunnel failure classification
The system SHALL distinguish tunnel failures from application errors when determining whether to recreate a tunnel.
#### Scenario: Tunnel is broken
- **GIVEN** a running instance with a tunnel URL
- **WHEN** the health check receives one of:
- Connection refused (ECONNREFUSED)
- Connection timeout (ETIMEDOUT)
- DNS resolution failure (ENOTFOUND)
- Empty response
- **THEN** the tunnel status is "unreachable"
- **AND** the frontend shows a "tunnel error" badge
- **AND** the "Recreate Tunnel" button is enabled
#### Scenario: Application returns error
- **GIVEN** a running instance with a tunnel URL
- **WHEN** the health check receives HTTP 502, 503, or 504
- **THEN** the tunnel status is "error_response"
- **AND** the frontend shows an "app error" badge
- **AND** the "Recreate Tunnel" button is NOT shown
- **AND** the status code is displayed for diagnostics
#### Scenario: Application is healthy
- **GIVEN** a running instance with a tunnel URL
- **WHEN** the health check receives HTTP 200-399
- **THEN** the tunnel status is "healthy"
- **AND** no error badge is shown
#### Scenario: Tunnel recreates successfully
- **GIVEN** an instance with a broken tunnel (status "unreachable")
- **WHEN** the user clicks "Recreate Tunnel"
- **THEN** the old cloudflared process is stopped
- **AND** a new cloudflared process is started
- **AND** the instance URL is updated
- **AND** the tunnel status becomes "healthy" (after verification)
## MODIFIED Requirements
None.
## REMOVED Requirements
None.
@@ -0,0 +1,53 @@
## ADDED Requirements
### Requirement: Tool config supports runtime fields
The system SHALL support additional configuration fields for tool instances: `start_command`, `port`, `working_directory`, `environment_variables`, and `volumes`.
#### Scenario: Create config with runtime fields
- **WHEN** user creates a tool config with start_command="npm start", port=3000, working_directory="/app"
- **THEN** the config is saved with all fields populated
#### Scenario: Environment variables as JSON
- **WHEN** user sets environment_variables to {"NODE_ENV": "production", "API_KEY": "secret"}
- **THEN** the system stores and returns the config with the JSON object preserved
#### Scenario: Volumes as JSON
- **WHEN** user sets volumes to [{"host": "/data", "container": "/app/data", "mode": "rw"}]
- **THEN** the system stores and returns the config with the JSON array preserved
### Requirement: Split-pane UI for tool configs
The system SHALL present tool configs in a split-pane layout with a list on the left and detail/edit panel on the right.
#### Scenario: Browse tool configs
- **WHEN** user navigates to /tool-configs
- **THEN** the left panel displays a scrollable list of all tool configs grouped by tool type
#### Scenario: Select config to edit
- **WHEN** user clicks on a config in the left panel
- **THEN** the right panel displays the config details in an editable form
#### Scenario: Create new config
- **WHEN** user clicks "New Config" button
- **THEN** a blank form appears in the right panel for creating a new config
### Requirement: JSON editor for complex fields
The system SHALL provide user-friendly editors for JSON fields (environment_variables and volumes) that validate JSON syntax.
#### Scenario: Valid JSON input
- **WHEN** user enters valid JSON in the environment_variables field
- **THEN** the form accepts the input and shows a green indicator
#### Scenario: Invalid JSON input
- **WHEN** user enters invalid JSON in the environment_variables field
- **THEN** the form shows a red error indicator and prevents saving
### Requirement: Config validation
The system SHALL validate tool config fields before saving.
#### Scenario: Invalid port number
- **WHEN** user enters port=70000
- **THEN** the system rejects the config with error "Port must be between 1 and 65535"
#### Scenario: Missing required fields
- **WHEN** user attempts to save a config without key or tool_type_id
- **THEN** the system rejects the config with error "Key is required"
+84 -79
View File
@@ -1,90 +1,95 @@
# Tool Instance Management Specification
## Purpose
Launch, monitor, and manage development tool instances in Docker containers.
## Requirements
### Requirement: Tool Instance Creation
The system SHALL create and launch tool instances from repositories.
#### Scenario: Launch tool
- GIVEN an authenticated user with a project and repository
- WHEN they create a tool instance
- THEN:
1. A unique subdomain is generated: `{tool-name}-{tool-id}.hq.local`
2. The Docker Compose template is rendered with project values
3. `docker compose up -d` is executed
4. Container ID and status are stored
### Requirement: Tool Lifecycle
The system SHALL manage tool lifecycle operations.
#### Scenario: Stop tool
- GIVEN a running tool instance
- WHEN the user stops it
- THEN `docker compose stop` is executed
- AND status is updated to "stopped"
#### Scenario: Start tool
- GIVEN a stopped tool instance
- WHEN the user starts it
- THEN `docker compose start` is executed
- AND status is updated to "running"
#### Scenario: Delete tool
- GIVEN a tool instance
- WHEN the user deletes it
- THEN the container and volumes are removed
- AND the database record is deleted
### Requirement: Traefik Integration
The system SHALL auto-generate Traefik labels for routing.
#### Scenario: Route generation
- GIVEN a running tool instance
- THEN these labels are set:
- `traefik.enable=true`
- `traefik.http.routers.{tool_id}.rule=Host(\`{subdomain}.hq.local\`)`
- `traefik.http.routers.{tool_id}.entrypoints=web`
- `traefik.http.services.{tool_id}.loadbalancer.server.port={port}`
## MODIFIED Requirements
### Requirement: Status Monitoring
The system SHALL track tool status with startup and health states.
The system SHALL track tool status.
#### Scenario: Status check with health details
- **GIVEN** a tool instance
- **WHEN** status is queried
- **THEN** the real-time container status is returned:
- `pending`: Instance created, container not yet started
- `starting`: Container is running, readiness probe in progress
- `running`: Container is running and probe passed (or terminal tool)
- `unhealthy`: Container is running but probe failed/timed out
- `stopped`: Container was stopped by user
- `error`: Container failed to start or crashed
#### Scenario: Status check
- GIVEN a tool instance
- WHEN status is queried
- THEN the real-time container status is returned:
- pending, building, running, stopped, error
## ADDED Requirements
### Requirement: Log Access
### Requirement: Health check endpoint enhancement
The system SHALL provide detailed health information through the health check endpoint.
The system SHALL provide access to container logs.
#### Scenario: Health check with container and tunnel status
- **GIVEN** a running instance
- **WHEN** `GET /instances/{id}/health` is called
- **THEN** the response includes:
- `healthy`: boolean - overall health
- `container_status`: "running", "exited", "restarting", or "not_found"
- `tunnel_status`: "healthy", "unreachable", "error_response", or "not_applicable"
- `tunnel_status_code`: HTTP status code or null
- `probe_status`: "passed", "failed", "pending", or "not_configured"
- `last_probe_output`: string or null
#### Scenario: View logs
- GIVEN a tool instance
- WHEN logs are requested
- THEN the last 100 lines are returned
- AND live streaming is available via WebSocket
### Requirement: Smart tunnel recreation
The system SHALL only allow tunnel recreation when the tunnel itself is broken.
## Dependencies
#### Scenario: Recreate tunnel for unreachable tunnel
- **GIVEN** an instance with `tunnel_status: "unreachable"`
- **WHEN** the recreate tunnel endpoint is called
- **THEN** the tunnel is recreated
- **AND** the new URL is returned
- tool-types (tool definitions)
- git-repo (repository access)
- project-management (project context)
- Docker runtime
- Traefik reverse proxy
#### Scenario: Block recreation for application errors
- **GIVEN** an instance with `tunnel_status: "error_response"` (e.g., HTTP 502)
- **WHEN** the recreate tunnel endpoint is called
- **THEN** the request is rejected with 400 Bad Request
- **AND** the error message explains the tunnel is working but the application is returning errors
## Quality Gates
### Requirement: Clone mode instance creation
The system SHALL support creating tool instances with a clone mode that clones the repository into the instance directory.
- `pytest` must pass
- `mypy .` must pass
- `ruff check .` must pass
- `npm run typecheck` must pass
- `npm run lint` must pass
#### Scenario: Create instance in clone mode
- **GIVEN** an authenticated user with a repository that has an SSH key and remote URL
- **WHEN** they create an instance with `clone_mode: "clone"` and `branch: "main"`
- **THEN** the system clones the repository into the instance directory
- **AND** the compose file uses the clone path as `REPO_PATH`
- **AND** the instance record stores `clone_mode="clone"` and `branch="main"`
#### Scenario: Create instance in mount mode
- **GIVEN** an authenticated user with a repository
- **WHEN** they create an instance with `clone_mode: "mount"` (or omit the field)
- **THEN** the compose file uses the host repository path as `REPO_PATH`
- **AND** the instance record stores `clone_mode="mount"`
### Requirement: SSH key mounting for git operations
The system SHALL mount the repository's SSH key into clone-mode containers for git operations.
#### Scenario: Start clone-mode instance
- **GIVEN** a clone-mode instance with an associated SSH key
- **WHEN** the instance is started
- **THEN** the SSH key is decrypted and written to `instance_dir/.ssh/`
- **AND** the `.ssh` directory is mounted into the container
- **AND** the container can perform git push/pull operations
### Requirement: Dirty check on clone deletion
The system SHALL check for uncommitted changes before deleting a clone-mode instance.
#### Scenario: Delete clean clone
- **GIVEN** a clone-mode instance with no uncommitted changes
- **WHEN** the user requests deletion
- **THEN** the instance is deleted successfully
#### Scenario: Delete dirty clone with confirmation
- **GIVEN** a clone-mode instance with uncommitted changes
- **WHEN** the user requests deletion
- **THEN** the system returns a warning with change details
- **AND** the user must confirm deletion
#### Scenario: Force delete dirty clone
- **GIVEN** a clone-mode instance with uncommitted changes
- **WHEN** the user requests deletion with `force=true`
- **THEN** the instance is deleted regardless of uncommitted changes
## REMOVED Requirements
None.
@@ -0,0 +1,38 @@
## ADDED Requirements
### Requirement: Tool types must define a default port
The system SHALL require all tool types to specify a `default_port`.
#### Scenario: Creating tool type without port
- **GIVEN** a user creating a new tool type
- **WHEN** they omit the `default_port` field
- **THEN** the system rejects the request with a 422 error
#### Scenario: Creating tool type with port
- **GIVEN** a user creating a new tool type with `default_port: 3000`
- **WHEN** the request is submitted
- **THEN** the tool type is created successfully
### Requirement: Tool type port must be exposed in compose template
The system SHALL validate that the compose template exposes the port defined in `default_port`.
#### Scenario: Port mismatch
- **GIVEN** a tool type with `default_port: 8443`
- **WHEN** the compose template only exposes port `3000`
- **THEN** the system rejects with an error indicating the port mismatch
#### Scenario: Port exposed correctly
- **GIVEN** a tool type with `default_port: 8443`
- **WHEN** the compose template exposes port `8443` via `ports: ["8443:8443"]`
- **THEN** the tool type is accepted
### Requirement: Tool types support multiple interfaces
The system SHALL allow tool types to specify multiple interfaces.
#### Scenario: Tool with web and terminal interfaces
- **GIVEN** a tool type with `interfaces: ["terminal", "web"]`
- **WHEN** an instance is created
- **THEN** the instance shows both "Open" (web) and "Terminal" buttons in the UI
+14 -33
View File
@@ -1,10 +1,4 @@
# Tool Type Definition Specification
## Purpose
Define and register tool types using Docker Compose templates for launching development tools.
## Requirements
## MODIFIED Requirements
### Requirement: Tool Type Model
@@ -20,21 +14,13 @@ The system SHALL store tool type definitions in the database.
- icon: Visual identifier
- category: Tool category
- default_env_vars: Default environment variables
- default_ports: Exposed ports
- default_port: **Required** primary port the tool listens on
- interfaces: List of supported interfaces ("web", "terminal")
### Requirement: Template Variables
The system SHALL support template variable substitution.
#### Scenario: Variable substitution
- GIVEN a Docker Compose template
- WHEN it's rendered for a tool instance
- THEN these variables are substituted:
- `{{REPO_PATH}}`: Path to the git repository
- `{{WORKSPACE_DIR}}`: Working directory inside container
- `{{USER_ID}}`: User identifier
- `{{PROJECT_ID}}`: Project identifier
- `{{TOOL_ID}}`: Tool instance identifier
#### Scenario: Tool type without port rejected
- GIVEN a user creating a tool type without `default_port`
- WHEN the request is submitted
- THEN the system rejects with a 422 validation error
### Requirement: Built-in Tools
@@ -43,9 +29,9 @@ The system SHALL include default tool types.
#### Scenario: Built-in tools
- GIVEN a fresh installation
- THEN these tool types are pre-configured:
- code-server: VS Code in browser
- jupyter-notebook: Jupyter notebooks
- opencode: OpenCode agent environment
- code-server: VS Code in browser (port 8443, interfaces: ["web"])
- jupyter-notebook: Jupyter notebooks (port 8888, interfaces: ["web"])
- opencode: OpenCode agent environment (port 3000, interfaces: ["terminal", "web"])
### Requirement: Template Validation
@@ -56,12 +42,7 @@ The system SHALL validate Docker Compose templates.
- WHEN a user tries to create/update a tool type
- THEN the system rejects with validation errors
## Dependencies
- Database models: ToolType
## Quality Gates
- `pytest` must pass
- `mypy .` must pass
- `ruff check .` must pass
#### Scenario: Port not exposed in template
- GIVEN a tool type with `default_port: 8443`
- WHEN the compose template does not expose port 8443
- THEN the system rejects with a validation error indicating the port mismatch
@@ -0,0 +1,35 @@
## ADDED Requirements
### Requirement: System monitors tunnel health
The system SHALL periodically check if active tunnel URLs are reachable and mark them as erroneous if not.
#### Scenario: Healthy tunnel
- **WHEN** a tunnel health check is performed on a running instance
- **THEN** the system receives an HTTP 2xx response
- **AND** the instance status remains "running"
#### Scenario: Broken tunnel
- **WHEN** a tunnel health check is performed on a running instance
- **AND** the response is not HTTP 2xx or the request fails
- **THEN** the instance is marked with tunnel_error status
- **AND** a visual error indicator is displayed in the UI
### Requirement: Users can recreate broken tunnels
The system SHALL allow users to regenerate a temporary tunnel for a running instance without restarting the instance.
#### Scenario: Recreate tunnel
- **WHEN** user clicks "Recreate Tunnel" button on an instance with a broken tunnel
- **THEN** the system stops the existing cloudflared process
- **AND** starts a new cloudflared tunnel
- **AND** updates the instance URL
- **AND** the new URL is displayed in the UI
#### Scenario: Recreate tunnel success
- **WHEN** tunnel recreation completes successfully
- **THEN** the error indicator is removed
- **AND** the instance shows as healthy
#### Scenario: Recreate tunnel failure
- **WHEN** tunnel recreation fails
- **THEN** the error indicator remains
- **AND** an error message is displayed to the user