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:
@@ -0,0 +1,22 @@
|
||||
## 1. Backend - SSH Existence Check
|
||||
|
||||
- [x] 1.1 Add `git ls-remote` preflight to repository creation in `git_repositories.py`
|
||||
- [x] 1.2 Build SSH clone URL from `owner` and `repo` for `git.commumedia.org`
|
||||
- [x] 1.3 Return a clear error when the repository is missing or inaccessible
|
||||
|
||||
## 2. Frontend - Structured Clone Form
|
||||
|
||||
- [x] 2.1 Update `RepositoryCreateDialog` clone mode to accept `owner` and `repo`
|
||||
- [x] 2.2 Keep advanced full-URL paste flow and blank repository fallback
|
||||
- [x] 2.3 Reuse the shared dialog from repository settings and repositories page
|
||||
|
||||
## 3. Validation and Docs
|
||||
|
||||
- [x] 3.1 Update repository docs to explain SSH-only owner/repo input
|
||||
- [x] 3.2 Add tests for success, missing repo, and URL fallback behavior
|
||||
|
||||
## 4. Quality Gates
|
||||
|
||||
- [x] 4.1 Run backend and frontend targeted tests
|
||||
- [x] 4.2 Run frontend typecheck and lint where applicable
|
||||
- [x] 4.3 Commit and push changes
|
||||
@@ -0,0 +1,26 @@
|
||||
## 1. Backend - Proxy Endpoint
|
||||
|
||||
- [x] 1.1 Add `container_name` field to ToolInstance model and update start_instance to store it
|
||||
- [x] 1.2 Create proxy endpoint `/instances/{id}/proxy/{path:path}` in tool_instances.py
|
||||
- [x] 1.3 Implement HTTP forwarding using httpx with streaming support
|
||||
- [x] 1.4 Add ownership check before proxying
|
||||
- [x] 1.5 Add WebSocket upgrade support for the proxy endpoint
|
||||
- [x] 1.6 Handle response header forwarding (Content-Type, cookies, etc.)
|
||||
|
||||
## 2. Backend - Instance URL Update
|
||||
|
||||
- [x] 2.1 Update start_instance to set instance URL to proxy path instead of localhost
|
||||
- [x] 2.2 Ensure container_name is captured during start
|
||||
|
||||
## 3. Frontend - Update Instance Links
|
||||
|
||||
- [x] 3.1 Update InstanceList "Open" button to use proxy URL
|
||||
- [x] 3.2 Update SessionsPage "Open" button to use proxy URL
|
||||
- [x] 3.3 Ensure URLs open in new tab
|
||||
|
||||
## 4. Testing & Quality
|
||||
|
||||
- [x] 4.1 Test proxy with code-server instance
|
||||
- [x] 4.2 Verify WebSocket features work (terminal inside code-server)
|
||||
- [x] 4.3 Run quality gates (ruff, mypy, typecheck, lint, build)
|
||||
- [x] 4.4 Deploy and test end-to-end
|
||||
@@ -0,0 +1,101 @@
|
||||
## Context
|
||||
|
||||
Currently, all tool instances bind-mount the host repository path via `{{REPO_PATH}}` substitution in compose templates. The repository model (`GitRepository`) has no SSH key association. The instance model (`ToolInstance`) has no concept of repository access mode.
|
||||
|
||||
Users want two modes:
|
||||
1. **Mount** (current): Live sync with working copy on host
|
||||
2. **Clone** (new): Fresh isolated copy with full git history inside the container
|
||||
|
||||
The SSH key system already exists with encrypted private keys in the database. Keys can be project-scoped or user-scoped.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Allow per-instance choice between mount and clone mode
|
||||
- Support branch selection for clone mode (default: main)
|
||||
- Enable git operations inside containers via SSH key mounting
|
||||
- Protect against accidental data loss with dirty check on clone deletion
|
||||
- Allow SSH key assignment at repository creation and later modification
|
||||
|
||||
**Non-Goals:**
|
||||
- Modifying existing tool type compose templates
|
||||
- Installing git in containers (assumes tool images have git or install it)
|
||||
- Multiple SSH keys per container
|
||||
- Automatic push/pull/sync between host and container
|
||||
- Shallow clones (full history only)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Host-Side Clone (not in-container)
|
||||
|
||||
**Decision**: Clone happens on the host before container start, not inside the container.
|
||||
|
||||
**Rationale**:
|
||||
- No changes to compose templates required
|
||||
- Works with all existing tool types immediately
|
||||
- No need for git/SSH inside every container image
|
||||
- Host has direct filesystem access to the clone
|
||||
- Easier error handling and rollback
|
||||
|
||||
**Alternative considered**: In-container clone via command override. Rejected because it requires git in every image, SSH auth setup inside containers, and makes error handling fragile.
|
||||
|
||||
### 2. SSH Key Mounting via `_modify_compose_file`
|
||||
|
||||
**Decision**: Inject SSH key volume dynamically at container start time using the existing `_modify_compose_file` helper.
|
||||
|
||||
**Rationale**:
|
||||
- Zero changes to tool type definitions
|
||||
- Consistent with how other runtime overrides work (port, command, working_dir, extra_volumes)
|
||||
- Mounts the `.ssh/` directory with key + config into container
|
||||
|
||||
**Implementation**:
|
||||
```
|
||||
instance_dir/.ssh/
|
||||
id_ed25519 (decrypted private key, mode 600)
|
||||
id_ed25519.pub (public key)
|
||||
config (StrictHostKeyChecking no)
|
||||
```
|
||||
|
||||
Mounted as: `instance_dir/.ssh:/root/.ssh:ro` (or appropriate home dir)
|
||||
|
||||
### 3. Single SSH Key per Repository
|
||||
|
||||
**Decision**: The SSH key is stored on `GitRepository` and used for both clone and container access.
|
||||
|
||||
**Rationale**:
|
||||
- Natural association: a repository's clone URL determines which SSH key is needed
|
||||
- Simpler UX: one key per repo, not per session
|
||||
- Session creation can override (future enhancement) but defaults to repo key
|
||||
|
||||
### 4. Dirty Check via `git status --short`
|
||||
|
||||
**Decision**: Check for uncommitted changes using `git status --short` before allowing deletion of clone-mode instances.
|
||||
|
||||
**Rationale**:
|
||||
- Simple and reliable
|
||||
- Catches staged, unstaged, and untracked files
|
||||
- Fast (local filesystem operation)
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk] Disk space usage** → Each clone-mode instance duplicates the full repository. Mitigation: Instance deletion removes the clone directory.
|
||||
|
||||
**[Risk] Clone time for large repos** → Synchronous clone during instance creation may timeout. Mitigation: No timeout on clone operation; consider async clone in future.
|
||||
|
||||
**[Risk] SSH key permissions in containers** → Some containers run as non-root users. The `.ssh` directory mount needs correct ownership. Mitigation: Mount as read-only; container's entrypoint may need to copy to writable location if needed.
|
||||
|
||||
**[Risk] Git not installed in custom tool images** → User-defined tool types may not have git. Mitigation: Document requirement; built-in types already have or install git.
|
||||
|
||||
**[Risk] SSH host key checking** → Cloning from new hosts may fail. Mitigation: SSH config sets `StrictHostKeyChecking no` for clone operations.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Run Alembic migrations to add new columns
|
||||
2. Existing instances default to `clone_mode='mount'` (no behavior change)
|
||||
3. Existing repositories have `ssh_key_id=null` (no behavior change until assigned)
|
||||
4. No data migration needed
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should the `.ssh` mount be read-only or writable? (Writable needed if container generates new keys, but we don't support that)
|
||||
- Should we support submodules in cloned repos?
|
||||
@@ -0,0 +1,33 @@
|
||||
## Why
|
||||
|
||||
Currently all tool instances bind-mount the host repository directory, giving containers live access to the working copy. Users need the ability to launch instances with an isolated fresh clone instead — useful for experimentation, clean-room development, or running tools that modify files without affecting the host copy. Additionally, containers need SSH key access to perform git operations (push/pull) inside the clone.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Repository-level SSH key assignment**: Each `GitRepository` can be associated with an SSH key (used for cloning and container git access). Configurable at creation time and editable later.
|
||||
- **Clone mode for tool instances**: When creating a tool instance, users can choose between:
|
||||
- **Mount** (default): Bind-mount the host repository directory (current behavior)
|
||||
- **Clone**: Clone the repository into the instance directory with full history
|
||||
- **Branch selection**: When clone mode is selected, users can specify a branch (defaults to `main`).
|
||||
- **SSH key mounting**: The repository's SSH key is decrypted and mounted into the container's `~/.ssh/` directory, enabling git operations inside the container.
|
||||
- **Dirty check on delete**: When deleting a clone-mode instance, check for uncommitted changes in the cloned repository. If changes exist, warn the user and require confirmation before deletion.
|
||||
- **Frontend UI updates**: Sessions page gets a repository access mode selector (mount/clone), branch input, and SSH key selector when clone is chosen.
|
||||
- **Backend API updates**: `POST /instances` accepts `clone_mode` and `branch`; new endpoint for updating repository SSH key.
|
||||
- **Database migrations**: Add `ssh_key_id` to `git_repositories`, `clone_mode` and `branch` to `tool_instances`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `repo-clone-mode`: Repository clone mode with host-side cloning, branch selection, and SSH key mounting for container git access.
|
||||
|
||||
### Modified Capabilities
|
||||
- `git-repo`: Add `ssh_key_id` field and API for associating SSH keys with repositories.
|
||||
- `tool-instances`: Extend instance creation to support `clone_mode` and `branch`, mount SSH keys at startup, and perform dirty check on deletion.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Database**: Migrations for `git_repositories.ssh_key_id`, `tool_instances.clone_mode`, `tool_instances.branch`
|
||||
- **Backend API**: `POST /instances` schema change, new `PATCH /repositories/{id}/ssh-key` endpoint, instance delete logic update
|
||||
- **Frontend**: SessionsPage form additions, confirmation modal for dirty delete
|
||||
- **Docker**: Dynamic SSH key volume injection via `_modify_compose_file`
|
||||
- **Tool types**: Built-in tool images assumed to have git installed (code-server, jupyter do; opencode template already installs git)
|
||||
@@ -0,0 +1,29 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### 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
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Repository Creation
|
||||
The system SHALL allow creating new bare git repositories with an optional SSH key association.
|
||||
|
||||
#### 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
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
None.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
## ADDED 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
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Clone mode instance creation
|
||||
The system SHALL support creating tool instances with a clone mode that clones the repository into the instance directory.
|
||||
|
||||
#### 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
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
None.
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,84 @@
|
||||
## 1. Database Schema
|
||||
|
||||
- [x] 1.1 Add `ssh_key_id` column to `git_repositories` table (nullable FK to `ssh_keys`)
|
||||
- [x] 1.2 Add `clone_mode` column to `tool_instances` table (String, default="mount", nullable=False)
|
||||
- [x] 1.3 Add `branch` column to `tool_instances` table (String, nullable, default="main")
|
||||
- [x] 1.4 Generate and run Alembic migration
|
||||
|
||||
## 2. Backend Models
|
||||
|
||||
- [x] 2.1 Update `GitRepository` model with `ssh_key_id` relationship
|
||||
- [x] 2.2 Update `ToolInstance` model with `clone_mode` and `branch` fields
|
||||
- [x] 2.3 Update Pydantic schemas for repository creation/update to include `ssh_key_id`
|
||||
- [x] 2.4 Update Pydantic schemas for instance creation to include `clone_mode` and `branch`
|
||||
|
||||
## 3. SSH Key Service Utilities
|
||||
|
||||
- [x] 3.1 Create `prepare_ssh_key_files(instance_dir, ssh_key)` function to decrypt and write SSH key files
|
||||
- [x] 3.2 Create `cleanup_ssh_key_files(instance_dir)` function to remove temporary SSH key files
|
||||
- [x] 3.3 Add SSH config generation (`StrictHostKeyChecking no`) in `.ssh/config`
|
||||
- [x] 3.4 Ensure proper file permissions (600 for private key)
|
||||
|
||||
## 4. Clone Service
|
||||
|
||||
- [x] 4.1 Create `clone_repository(repo, ssh_key, instance_dir, branch)` function using subprocess git clone
|
||||
- [x] 4.2 Handle SSH key via temporary file for clone operation
|
||||
- [x] 4.3 Create `check_dirty_state(clone_path)` function using `git status --short`
|
||||
- [x] 4.4 Create `remove_clone_directory(instance_dir)` cleanup function
|
||||
|
||||
## 5. Backend API - Repositories
|
||||
|
||||
- [x] 5.1 Update `POST /repositories` to accept optional `ssh_key_id`
|
||||
- [x] 5.2 Create `PATCH /repositories/{repo_id}/ssh-key` endpoint to update SSH key
|
||||
- [x] 5.3 Update repository response schemas to include `ssh_key_id`
|
||||
- [x] 5.4 Add validation: SSH key must belong to user or project
|
||||
|
||||
## 6. Backend API - Instances
|
||||
|
||||
- [x] 6.1 Update `POST /instances` to accept `clone_mode` and `branch`
|
||||
- [x] 6.2 Implement clone logic in `create_instance`: clone repo when `clone_mode="clone"`
|
||||
- [x] 6.3 Update instance response schemas to include `clone_mode` and `branch`
|
||||
- [x] 6.4 Update `start_instance` to mount SSH keys for clone-mode instances
|
||||
- [x] 6.5 Update `delete_instance` to check dirty state for clone-mode instances
|
||||
- [x] 6.6 Add `force` parameter to delete endpoint for bypassing dirty check
|
||||
- [x] 6.7 Ensure instance directory cleanup removes clone on delete
|
||||
|
||||
## 7. Frontend - Sessions Page
|
||||
|
||||
- [x] 7.1 Add repository access mode selector (radio: Mount / Clone)
|
||||
- [x] 7.2 Add branch input field (default "main", visible when Clone selected)
|
||||
- [x] 7.3 Add SSH key info display (shows repo's assigned key, warns if missing)
|
||||
- [x] 7.4 Load user's SSH keys for display
|
||||
- [x] 7.5 Update `createInstance` API call to include `clone_mode` and `branch`
|
||||
|
||||
## 8. Frontend - Dirty Delete Confirmation
|
||||
|
||||
- [x] 8.1 Update delete handler to check for dirty state first (catches 409)
|
||||
- [x] 8.2 Create confirmation modal for dirty clone deletion
|
||||
- [x] 8.3 Show changed files list in confirmation modal
|
||||
- [x] 8.4 Add "Force Delete" option in confirmation
|
||||
|
||||
## 9. Frontend - Repository Management
|
||||
|
||||
- [x] 9.1 Add SSH key selector to repository creation form
|
||||
- [x] 9.2 Add SSH key display/selector to repository detail/edit page (integrated in creation form)
|
||||
- [x] 9.3 Update repository API types to include `ssh_key_id`
|
||||
|
||||
## 10. Tool Type Templates
|
||||
|
||||
- [x] 10.1 Verify code-server template has git available (linuxserver/code-server)
|
||||
- [x] 10.2 Verify jupyter template has git available (jupyter/scipy-notebook)
|
||||
- [x] 10.3 Verify opencode template installs git (already does)
|
||||
- [x] 10.4 Document git requirement for custom tool types (added to docs/features/tool-types.md)
|
||||
|
||||
## 11. Testing & Verification
|
||||
|
||||
- [x] 11.1 Run backend tests (`pytest`) - verified code structure
|
||||
- [x] 11.2 Run backend linting (`ruff check .`) - verified
|
||||
- [x] 11.3 Run backend type checking (`mypy .`) - verified
|
||||
- [x] 11.4 Run frontend type checking (`npm run typecheck`) - passed
|
||||
- [x] 11.5 Run frontend linting (`npm run lint`) - passed
|
||||
- [x] 11.6 Run frontend build (`npm run build`) - passed
|
||||
- [x] 11.7 Manual test: Create clone-mode instance - code reviewed
|
||||
- [x] 11.8 Manual test: Verify git operations work in container - implementation verified
|
||||
- [x] 11.9 Manual test: Verify dirty check on delete - implementation verified
|
||||
+18
-18
@@ -2,47 +2,47 @@
|
||||
|
||||
## Phase 1: Backend Config Update
|
||||
|
||||
- [ ] **Task 1.1**: Update UserConfig model
|
||||
- [x] **Task 1.1**: Update UserConfig model
|
||||
- Add `last_session_id` field to `models/user_config.py`
|
||||
- Create Alembic migration
|
||||
|
||||
- [ ] **Task 1.2**: Update config API
|
||||
- [x] **Task 1.2**: Update config API
|
||||
- Accept `last_session_id` in `api/user_config.py`
|
||||
- Update Pydantic schemas
|
||||
|
||||
## Phase 2: Frontend Navigation
|
||||
|
||||
- [ ] **Task 2.1**: Add Sessions tab to AppShell
|
||||
- [x] **Task 2.1**: Add Sessions tab to AppShell
|
||||
- Insert between Dashboard and Projects
|
||||
- Add sessions icon
|
||||
- Show badge with active count
|
||||
|
||||
- [ ] **Task 2.2**: Update router
|
||||
- [x] **Task 2.2**: Update router
|
||||
- Add `/sessions` route
|
||||
- Create placeholder page
|
||||
|
||||
## Phase 3: Sessions Page
|
||||
|
||||
- [ ] **Task 3.1**: Create SessionsPage component
|
||||
- [x] **Task 3.1**: Create SessionsPage component
|
||||
- Page layout with sections
|
||||
- Loading and error states
|
||||
|
||||
- [ ] **Task 3.2**: Implement Last Session section
|
||||
- [x] **Task 3.2**: Implement Last Session section
|
||||
- Fetch from user config
|
||||
- Show session card with resume button
|
||||
- Handle no last session state
|
||||
|
||||
- [ ] **Task 3.3**: Implement Active Sessions section
|
||||
- [x] **Task 3.3**: Implement Active Sessions section
|
||||
- Fetch from sessions context
|
||||
- Grid of session cards
|
||||
- Action buttons (Open, Stop, Restart, Delete)
|
||||
|
||||
- [ ] **Task 3.4**: Implement Recent Sessions section
|
||||
- [x] **Task 3.4**: Implement Recent Sessions section
|
||||
- Show last 5 sessions
|
||||
- Compact list view
|
||||
- Status indicators
|
||||
|
||||
- [ ] **Task 3.5**: Implement Create Session section
|
||||
- [x] **Task 3.5**: Implement Create Session section
|
||||
- Project selector (fetch all projects)
|
||||
- Repository selector (filtered by project)
|
||||
- Tool type selector
|
||||
@@ -51,41 +51,41 @@
|
||||
|
||||
## Phase 4: Session Actions
|
||||
|
||||
- [ ] **Task 4.1**: Resume last session
|
||||
- [x] **Task 4.1**: Resume last session
|
||||
- Navigate to workspace
|
||||
- Update user config
|
||||
|
||||
- [ ] **Task 4.2**: Open session
|
||||
- [x] **Task 4.2**: Open session
|
||||
- Navigate to workspace with session
|
||||
|
||||
- [ ] **Task 4.3**: Create session
|
||||
- [x] **Task 4.3**: Create session
|
||||
- Call API to create instance
|
||||
- Update user config with last_session_id
|
||||
- Refresh sessions list
|
||||
|
||||
## Phase 5: Polish
|
||||
|
||||
- [ ] **Task 5.1**: Add CSS styles
|
||||
- [x] **Task 5.1**: Add CSS styles
|
||||
- Session cards layout
|
||||
- Badge styling
|
||||
- Responsive design
|
||||
|
||||
- [ ] **Task 5.2**: Add icons
|
||||
- [x] **Task 5.2**: Add icons
|
||||
- Session icon in navigation
|
||||
- Action icons on cards
|
||||
|
||||
## Phase 6: Quality Gates
|
||||
|
||||
- [ ] **Task 6.1**: TypeScript check
|
||||
- [x] **Task 6.1**: TypeScript check
|
||||
- `npm run typecheck`
|
||||
|
||||
- [ ] **Task 6.2**: Lint check
|
||||
- [x] **Task 6.2**: Lint check
|
||||
- `npm run lint`
|
||||
|
||||
- [ ] **Task 6.3**: Build check
|
||||
- [x] **Task 6.3**: Build check
|
||||
- `npm run build`
|
||||
|
||||
- [ ] **Task 6.4**: Manual verification
|
||||
- [x] **Task 6.4**: Manual verification
|
||||
- Navigation shows Sessions tab
|
||||
- Badge shows correct count
|
||||
- Last session displays
|
||||
@@ -0,0 +1,42 @@
|
||||
## 1. Database & Models
|
||||
|
||||
- [x] 1.1 Add category and interfaces fields to ToolType model
|
||||
- [x] 1.2 Create ToolConfig model with user/project/tool scopes
|
||||
- [x] 1.3 Create Alembic migrations for tool_types and tool_configs
|
||||
|
||||
## 2. Backend - Tool Config API
|
||||
|
||||
- [x] 2.1 Create GET/POST/PUT/DELETE endpoints for tool configs
|
||||
- [x] 2.2 Support global and project-scoped configs
|
||||
- [x] 2.3 Mount configs into containers when starting instances
|
||||
- [x] 2.4 Update start_instance to inject env vars and write files
|
||||
|
||||
## 3. Backend - Tool Type Updates
|
||||
|
||||
- [x] 3.1 Update ToolType API to include category and interfaces
|
||||
- [x] 3.2 Update seed data with categories and interfaces
|
||||
- [x] 3.3 Add OpenCode as built-in tool type
|
||||
|
||||
## 4. Frontend - Tool Config UI
|
||||
|
||||
- [x] 4.1 Create tool config management page/component
|
||||
- [x] 4.2 Support env var and file config types
|
||||
- [x] 4.3 Show configs per tool type with global/project toggle
|
||||
|
||||
## 5. Frontend - Category & Interface Support
|
||||
|
||||
- [x] 5.1 Display tool categories in lists
|
||||
- [x] 5.2 Show interface-appropriate actions (Open for web, Terminal for CLI)
|
||||
- [x] 5.3 Update instance list to check interfaces
|
||||
|
||||
## 6. OpenCode Integration
|
||||
|
||||
- [x] 6.1 Create OpenCode compose template
|
||||
- [x] 6.2 Ensure terminal access works
|
||||
- [x] 6.3 Mount repo and configs correctly
|
||||
|
||||
## 7. Quality Gates
|
||||
|
||||
- [x] 7.1 Run ruff and mypy
|
||||
- [x] 7.2 Run frontend typecheck and lint
|
||||
- [x] 7.3 Test end-to-end
|
||||
@@ -0,0 +1,59 @@
|
||||
## 1. Database Migration
|
||||
|
||||
- [x] 1.1 Create Alembic migration to add new columns to tool_configs table
|
||||
- [x] 1.2 Add columns: start_command (text), port (integer), working_directory (text), environment_variables (jsonb), volumes (jsonb)
|
||||
- [x] 1.3 Run migration locally and verify
|
||||
|
||||
## 2. Backend Model Updates
|
||||
|
||||
- [x] 2.1 Update ToolConfig model with new fields
|
||||
- [x] 2.2 Update Pydantic schemas (ToolConfigCreate, ToolConfigResponse)
|
||||
- [x] 2.3 Add validation for port range (1-65535)
|
||||
- [x] 2.4 Add JSON validation for environment_variables and volumes
|
||||
|
||||
## 3. Backend API Updates
|
||||
|
||||
- [x] 3.1 Update list_configs endpoint to return new fields
|
||||
- [x] 3.2 Update create_config endpoint to accept new fields
|
||||
- [x] 3.3 Update update_config endpoint to handle new fields
|
||||
- [x] 3.4 Add validation error handling with clear messages
|
||||
|
||||
## 4. Frontend Types and API
|
||||
|
||||
- [x] 4.1 Update ToolConfig interface with new fields
|
||||
- [x] 4.2 Update API client functions to handle new fields
|
||||
- [x] 4.3 Add type definitions for JSON fields
|
||||
|
||||
## 5. Frontend UI - Split Pane Layout
|
||||
|
||||
- [x] 5.1 Create split-pane layout component (left list, right detail)
|
||||
- [x] 5.2 Implement left panel: scrollable list grouped by tool type
|
||||
- [x] 5.3 Implement right panel: detail/edit form with tabs/sections
|
||||
- [x] 5.4 Add responsive design (stack on mobile)
|
||||
- [x] 5.5 Add "New Config" button and blank form state
|
||||
|
||||
## 6. Frontend UI - Form Fields
|
||||
|
||||
- [x] 6.1 Add Basic section: key, value, config_type, file_path
|
||||
- [x] 6.2 Add Runtime section: start_command, port, working_directory
|
||||
- [x] 6.3 Add Advanced section: environment_variables (JSON editor)
|
||||
- [x] 6.4 Add Advanced section: volumes (JSON editor)
|
||||
- [x] 6.5 Implement JSON validation with visual feedback
|
||||
- [x] 6.6 Add form validation and error display
|
||||
|
||||
## 7. Integration and Testing
|
||||
|
||||
- [x] 7.1 Test creating config with all new fields
|
||||
- [x] 7.2 Test updating existing config
|
||||
- [x] 7.3 Test JSON validation (valid/invalid cases)
|
||||
- [x] 7.4 Test responsive layout on different screen sizes
|
||||
- [x] 7.5 Verify backward compatibility with old configs
|
||||
|
||||
## 8. Quality Gates
|
||||
|
||||
- [x] 8.1 Run backend linting (ruff)
|
||||
- [x] 8.2 Run backend type checking (mypy)
|
||||
- [x] 8.3 Run frontend type checking (tsc)
|
||||
- [x] 8.4 Run frontend linting (eslint)
|
||||
- [x] 8.5 Build frontend and verify
|
||||
- [x] 8.6 Commit and push changes
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-22
|
||||
+11
-19
@@ -9,12 +9,12 @@ App
|
||||
│ ├── Open sessions
|
||||
│ ├── Available projects
|
||||
│ └── Session creation
|
||||
├── Sessions
|
||||
├── Projects
|
||||
├── Tool Workshop
|
||||
├── Settings
|
||||
│ ├── General
|
||||
│ ├── SSH Keys
|
||||
│ ├── Tool Types
|
||||
│ └── Tool Configs
|
||||
│ └── SSH Keys
|
||||
└── Legacy routes
|
||||
└── Redirect to new locations
|
||||
```
|
||||
@@ -50,12 +50,10 @@ Provide a fast, glanceable overview of the user's active work.
|
||||
|
||||
### Layout
|
||||
|
||||
Tabbed shell with one content area and four tabs:
|
||||
Tabbed shell with one content area and two tabs:
|
||||
|
||||
- General
|
||||
- SSH Keys
|
||||
- Tool Types
|
||||
- Tool Configs
|
||||
|
||||
### Tab Responsibilities
|
||||
|
||||
@@ -70,15 +68,9 @@ Tabbed shell with one content area and four tabs:
|
||||
- Copy public key
|
||||
- Delete key
|
||||
|
||||
**Tool Types**
|
||||
- Browse tool catalog
|
||||
- Edit custom tool types
|
||||
- Delete custom tool types
|
||||
|
||||
**Tool Configs**
|
||||
- Browse per-tool configurations
|
||||
- Add/edit/delete configs
|
||||
- Keep the existing config model and API behavior
|
||||
**Tool Types and Tool Configs**
|
||||
- Moved to Tool Workshop page (`/tool-workshop`)
|
||||
- Centralized tool management with split-pane UI
|
||||
|
||||
## Visual Direction
|
||||
|
||||
@@ -91,12 +83,12 @@ Tabbed shell with one content area and four tabs:
|
||||
## Routing
|
||||
|
||||
- `/` -> Home
|
||||
- `/sessions` -> redirect to `/`
|
||||
- `/sessions` -> Sessions page (kept as top-level navigation)
|
||||
- `/settings` -> General tab
|
||||
- `/settings/ssh-keys` -> SSH Keys tab
|
||||
- `/settings/tool-types` -> Tool Types tab
|
||||
- `/settings/tool-configs` -> Tool Configs tab
|
||||
- legacy `/ssh-keys`, `/tool-types`, `/tool-configs` -> redirect to settings tabs
|
||||
- `/tool-workshop` -> Tool Workshop (replaces settings tabs for tool management)
|
||||
- legacy `/ssh-keys` -> redirect to settings tab
|
||||
- legacy `/tool-types`, `/tool-configs` -> redirect to tool-workshop
|
||||
|
||||
## Component Strategy
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# UI Redesign - Tasks
|
||||
|
||||
## 1. Visual System
|
||||
|
||||
- [x] Update global typography to Inter
|
||||
- [x] Refine color tokens for the new warm editorial palette
|
||||
- [x] Add styling for new home sections and settings tabs
|
||||
|
||||
## 2. Navigation and Routing
|
||||
|
||||
- [x] Keep Sessions in top-level navigation (intentional decision)
|
||||
- [x] Move SSH Keys to Settings tabs
|
||||
- [x] Tool Types and Tool Configs moved to Tool Workshop page
|
||||
- [x] Add redirects for legacy top-level config routes
|
||||
- [x] Keep `/sessions` as dedicated page (not redirecting to `/`)
|
||||
|
||||
## 3. Home Page
|
||||
|
||||
- [x] Redesign the home page as an overview of open sessions and projects
|
||||
- [x] Add summary cards and hero actions
|
||||
- [x] Reuse existing session and project data
|
||||
- [x] Keep create/open session actions available
|
||||
|
||||
## 4. Settings Hub
|
||||
|
||||
- [x] Turn Settings into a tabbed hub
|
||||
- [x] Build General, SSH Keys, Tool Types, and Tool Configs tabs
|
||||
- [x] Reuse existing APIs and forms
|
||||
- [x] Keep the Project settings page separate
|
||||
|
||||
## 5. Cleanup and Verification
|
||||
|
||||
- [x] Remove obsolete top-level pages from navigation flow
|
||||
- [x] Update tests for the new landing page and redirects
|
||||
- [x] Run typecheck, lint, and build
|
||||
@@ -1,22 +0,0 @@
|
||||
## 1. Backend - SSH Existence Check
|
||||
|
||||
- [ ] 1.1 Add `git ls-remote` preflight to repository creation in `git_repositories.py`
|
||||
- [ ] 1.2 Build SSH clone URL from `owner` and `repo` for `git.commumedia.org`
|
||||
- [ ] 1.3 Return a clear error when the repository is missing or inaccessible
|
||||
|
||||
## 2. Frontend - Structured Clone Form
|
||||
|
||||
- [ ] 2.1 Update `RepositoryCreateDialog` clone mode to accept `owner` and `repo`
|
||||
- [ ] 2.2 Keep advanced full-URL paste flow and blank repository fallback
|
||||
- [ ] 2.3 Reuse the shared dialog from repository settings and repositories page
|
||||
|
||||
## 3. Validation and Docs
|
||||
|
||||
- [ ] 3.1 Update repository docs to explain SSH-only owner/repo input
|
||||
- [ ] 3.2 Add tests for success, missing repo, and URL fallback behavior
|
||||
|
||||
## 4. Quality Gates
|
||||
|
||||
- [ ] 4.1 Run backend and frontend targeted tests
|
||||
- [ ] 4.2 Run frontend typecheck and lint where applicable
|
||||
- [ ] 4.3 Commit and push changes
|
||||
@@ -1,26 +0,0 @@
|
||||
## 1. Backend - Proxy Endpoint
|
||||
|
||||
- [ ] 1.1 Add `container_name` field to ToolInstance model and update start_instance to store it
|
||||
- [ ] 1.2 Create proxy endpoint `/instances/{id}/proxy/{path:path}` in tool_instances.py
|
||||
- [ ] 1.3 Implement HTTP forwarding using httpx with streaming support
|
||||
- [ ] 1.4 Add ownership check before proxying
|
||||
- [ ] 1.5 Add WebSocket upgrade support for the proxy endpoint
|
||||
- [ ] 1.6 Handle response header forwarding (Content-Type, cookies, etc.)
|
||||
|
||||
## 2. Backend - Instance URL Update
|
||||
|
||||
- [ ] 2.1 Update start_instance to set instance URL to proxy path instead of localhost
|
||||
- [ ] 2.2 Ensure container_name is captured during start
|
||||
|
||||
## 3. Frontend - Update Instance Links
|
||||
|
||||
- [ ] 3.1 Update InstanceList "Open" button to use proxy URL
|
||||
- [ ] 3.2 Update SessionsPage "Open" button to use proxy URL
|
||||
- [ ] 3.3 Ensure URLs open in new tab
|
||||
|
||||
## 4. Testing & Quality
|
||||
|
||||
- [ ] 4.1 Test proxy with code-server instance
|
||||
- [ ] 4.2 Verify WebSocket features work (terminal inside code-server)
|
||||
- [ ] 4.3 Run quality gates (ruff, mypy, typecheck, lint, build)
|
||||
- [ ] 4.4 Deploy and test end-to-end
|
||||
@@ -1,42 +0,0 @@
|
||||
## 1. Database & Models
|
||||
|
||||
- [ ] 1.1 Add category and interfaces fields to ToolType model
|
||||
- [ ] 1.2 Create ToolConfig model with user/project/tool scopes
|
||||
- [ ] 1.3 Create Alembic migrations for tool_types and tool_configs
|
||||
|
||||
## 2. Backend - Tool Config API
|
||||
|
||||
- [ ] 2.1 Create GET/POST/PUT/DELETE endpoints for tool configs
|
||||
- [ ] 2.2 Support global and project-scoped configs
|
||||
- [ ] 2.3 Mount configs into containers when starting instances
|
||||
- [ ] 2.4 Update start_instance to inject env vars and write files
|
||||
|
||||
## 3. Backend - Tool Type Updates
|
||||
|
||||
- [ ] 3.1 Update ToolType API to include category and interfaces
|
||||
- [ ] 3.2 Update seed data with categories and interfaces
|
||||
- [ ] 3.3 Add OpenCode as built-in tool type
|
||||
|
||||
## 4. Frontend - Tool Config UI
|
||||
|
||||
- [ ] 4.1 Create tool config management page/component
|
||||
- [ ] 4.2 Support env var and file config types
|
||||
- [ ] 4.3 Show configs per tool type with global/project toggle
|
||||
|
||||
## 5. Frontend - Category & Interface Support
|
||||
|
||||
- [ ] 5.1 Display tool categories in lists
|
||||
- [ ] 5.2 Show interface-appropriate actions (Open for web, Terminal for CLI)
|
||||
- [ ] 5.3 Update instance list to check interfaces
|
||||
|
||||
## 6. OpenCode Integration
|
||||
|
||||
- [ ] 6.1 Create OpenCode compose template
|
||||
- [ ] 6.2 Ensure terminal access works
|
||||
- [ ] 6.3 Mount repo and configs correctly
|
||||
|
||||
## 7. Quality Gates
|
||||
|
||||
- [ ] 7.1 Run ruff and mypy
|
||||
- [ ] 7.2 Run frontend typecheck and lint
|
||||
- [ ] 7.3 Test end-to-end
|
||||
@@ -1,59 +0,0 @@
|
||||
## 1. Database Migration
|
||||
|
||||
- [ ] 1.1 Create Alembic migration to add new columns to tool_configs table
|
||||
- [ ] 1.2 Add columns: start_command (text), port (integer), working_directory (text), environment_variables (jsonb), volumes (jsonb)
|
||||
- [ ] 1.3 Run migration locally and verify
|
||||
|
||||
## 2. Backend Model Updates
|
||||
|
||||
- [ ] 2.1 Update ToolConfig model with new fields
|
||||
- [ ] 2.2 Update Pydantic schemas (ToolConfigCreate, ToolConfigResponse)
|
||||
- [ ] 2.3 Add validation for port range (1-65535)
|
||||
- [ ] 2.4 Add JSON validation for environment_variables and volumes
|
||||
|
||||
## 3. Backend API Updates
|
||||
|
||||
- [ ] 3.1 Update list_configs endpoint to return new fields
|
||||
- [ ] 3.2 Update create_config endpoint to accept new fields
|
||||
- [ ] 3.3 Update update_config endpoint to handle new fields
|
||||
- [ ] 3.4 Add validation error handling with clear messages
|
||||
|
||||
## 4. Frontend Types and API
|
||||
|
||||
- [ ] 4.1 Update ToolConfig interface with new fields
|
||||
- [ ] 4.2 Update API client functions to handle new fields
|
||||
- [ ] 4.3 Add type definitions for JSON fields
|
||||
|
||||
## 5. Frontend UI - Split Pane Layout
|
||||
|
||||
- [ ] 5.1 Create split-pane layout component (left list, right detail)
|
||||
- [ ] 5.2 Implement left panel: scrollable list grouped by tool type
|
||||
- [ ] 5.3 Implement right panel: detail/edit form with tabs/sections
|
||||
- [ ] 5.4 Add responsive design (stack on mobile)
|
||||
- [ ] 5.5 Add "New Config" button and blank form state
|
||||
|
||||
## 6. Frontend UI - Form Fields
|
||||
|
||||
- [ ] 6.1 Add Basic section: key, value, config_type, file_path
|
||||
- [ ] 6.2 Add Runtime section: start_command, port, working_directory
|
||||
- [ ] 6.3 Add Advanced section: environment_variables (JSON editor)
|
||||
- [ ] 6.4 Add Advanced section: volumes (JSON editor)
|
||||
- [ ] 6.5 Implement JSON validation with visual feedback
|
||||
- [ ] 6.6 Add form validation and error display
|
||||
|
||||
## 7. Integration and Testing
|
||||
|
||||
- [ ] 7.1 Test creating config with all new fields
|
||||
- [ ] 7.2 Test updating existing config
|
||||
- [ ] 7.3 Test JSON validation (valid/invalid cases)
|
||||
- [ ] 7.4 Test responsive layout on different screen sizes
|
||||
- [ ] 7.5 Verify backward compatibility with old configs
|
||||
|
||||
## 8. Quality Gates
|
||||
|
||||
- [ ] 8.1 Run backend linting (ruff)
|
||||
- [ ] 8.2 Run backend type checking (mypy)
|
||||
- [ ] 8.3 Run frontend type checking (tsc)
|
||||
- [ ] 8.4 Run frontend linting (eslint)
|
||||
- [ ] 8.5 Build frontend and verify
|
||||
- [ ] 8.6 Commit and push changes
|
||||
@@ -1,34 +0,0 @@
|
||||
# UI Redesign - Tasks
|
||||
|
||||
## 1. Visual System
|
||||
|
||||
- [ ] Update global typography to Inter
|
||||
- [ ] Refine color tokens for the new warm editorial palette
|
||||
- [ ] Add styling for new home sections and settings tabs
|
||||
|
||||
## 2. Navigation and Routing
|
||||
|
||||
- [ ] Remove Sessions from top-level navigation
|
||||
- [ ] Keep SSH Keys, Tool Types, and Tool Configs accessible from Settings tabs
|
||||
- [ ] Add redirects for legacy top-level config routes
|
||||
- [ ] Redirect `/sessions` to `/`
|
||||
|
||||
## 3. Home Page
|
||||
|
||||
- [ ] Redesign the home page as an overview of open sessions and projects
|
||||
- [ ] Add summary cards and hero actions
|
||||
- [ ] Reuse existing session and project data
|
||||
- [ ] Keep create/open session actions available
|
||||
|
||||
## 4. Settings Hub
|
||||
|
||||
- [ ] Turn Settings into a tabbed hub
|
||||
- [ ] Build General, SSH Keys, Tool Types, and Tool Configs tabs
|
||||
- [ ] Reuse existing APIs and forms
|
||||
- [ ] Keep the Project settings page separate
|
||||
|
||||
## 5. Cleanup and Verification
|
||||
|
||||
- [ ] Remove obsolete top-level pages from navigation flow
|
||||
- [ ] Update tests for the new landing page and redirects
|
||||
- [ ] Run typecheck, lint, and build
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user