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,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-22
|
||||
@@ -0,0 +1,45 @@
|
||||
## Context
|
||||
|
||||
The current repository creation flow already supports cloning remote repositories via `remote_url` and can normalize pasted browser URLs. However, the UI asks for a full URL, which is awkward for the fixed provider `git.commumedia.org`. The requested behavior is to enter `owner` and `repo`, check whether the repository exists, and clone only if it does.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Accept SSH-only `owner` and `repo` inputs for cloning from `git.commumedia.org`
|
||||
- Verify repository existence before clone
|
||||
- Preserve full URL paste as a fallback path
|
||||
- Preserve blank repository creation
|
||||
- Reuse the existing repository create endpoint and shared dialog
|
||||
|
||||
**Non-Goals:**
|
||||
- Supporting multiple git providers
|
||||
- Adding a remote repository discovery API
|
||||
- Supporting HTTPS clone flow for the new structured path
|
||||
- Changing repository storage or clone behavior beyond preflight validation
|
||||
|
||||
## Decisions
|
||||
|
||||
**1. Provider assumption**
|
||||
- Hardcode `git.commumedia.org` for the structured clone path
|
||||
- Build SSH URLs as `git@git.commumedia.org:{owner}/{repo}.git`
|
||||
|
||||
**2. Existence check**
|
||||
- Use `git ls-remote` on the constructed SSH URL before cloning
|
||||
- If the command fails, surface a repository-not-found/inaccessible error and do not clone
|
||||
|
||||
**3. UI structure**
|
||||
- Keep the shared repository creation dialog as the single entry point
|
||||
- In clone mode, collect `owner` and `repo` instead of asking for a full URL
|
||||
- Keep an advanced paste-URL fallback for existing behavior and browser URL parsing
|
||||
- Keep blank repository creation available in the same dialog
|
||||
|
||||
**4. Backend behavior**
|
||||
- Reuse `POST /projects/{project_id}/repositories`
|
||||
- Add preflight logic before the existing `git clone --mirror`
|
||||
- Leave the database schema unchanged
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk] SSH auth may still fail even if the repo exists** → Mitigation: preflight error should be explicit and user-facing.
|
||||
**[Risk] Command availability** → Mitigation: reuse the same `git` dependency already required for cloning.
|
||||
**[Risk] UI complexity** → Mitigation: keep the dialog shared and minimal, with fallback URL paste.
|
||||
@@ -0,0 +1,25 @@
|
||||
## Why
|
||||
|
||||
Repository creation already supports cloning from a remote URL, but the current UI only accepts a full URL. For the common fixed-provider case (`git.commumedia.org`), users should be able to enter `owner` and `repo` and have the app verify the repository exists before cloning. If the repository does not exist, the app should surface a clear error. Existing blank repository creation must remain available.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Change the shared repository create dialog to support an SSH-only clone form with `owner` and `repo`
|
||||
- Build the clone target as `git@git.commumedia.org:{owner}/{repo}.git`
|
||||
- Preflight clone targets with `git ls-remote` before cloning
|
||||
- Return a clear error when the repository is missing or inaccessible
|
||||
- Keep the current full URL paste flow as an advanced fallback
|
||||
- Keep blank repository creation as a fallback option
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `git-repo`: Repository creation UX and clone validation reuse the existing create endpoint and clone path
|
||||
|
||||
## Impact
|
||||
|
||||
- Frontend: `repository-create-dialog.tsx`, `git-repositories.tsx`, `repositories-settings-tab.tsx`
|
||||
- Backend: `git_repositories.py` create endpoint clone preflight
|
||||
- Docs: repository creation guidance must reflect SSH-only owner/repo input
|
||||
- Tests: add coverage for SSH repo existence checks and fallback URL behavior
|
||||
@@ -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,33 @@
|
||||
## Context
|
||||
|
||||
Repository creation currently produces mirrored bare repos for any remote clone and bare repos for blank creations. The workspace, file browser, commit editor, and git toolbar are built around a working-tree repository model, so users can hit 400s when they try to sync or when the repo has no usable branch state.
|
||||
|
||||
## Goals
|
||||
|
||||
- Create working clones for remote repositories
|
||||
- Create working repos with an initial branch for blank repositories
|
||||
- Preserve the existing repository create endpoint and shared UI flow
|
||||
- Keep fetch/pull/push aligned with a normal local clone
|
||||
|
||||
## Decisions
|
||||
|
||||
1. Clone mode
|
||||
- Use `git clone` without `--mirror`
|
||||
- Keep the existing remote URL preflight and URL parsing behavior
|
||||
|
||||
2. Blank repositories
|
||||
- Initialize with `git init -b main` when supported
|
||||
- Fall back to `git init` plus `git symbolic-ref HEAD refs/heads/main` if needed
|
||||
|
||||
3. Branch state
|
||||
- Treat `main` as the initial branch name for blank repos
|
||||
- Make branch listing and current-branch helpers tolerate unborn `HEAD`
|
||||
|
||||
4. Pull behavior
|
||||
- Prefer the current branch when no explicit branch is supplied
|
||||
- Do not force `origin <branch>` if the branch is unborn or already tracked by the current checkout
|
||||
|
||||
## Risks
|
||||
|
||||
- Some older git versions may not support `git init -b`; the backend should fall back cleanly
|
||||
- Existing blank repos created under the old bare model may still require migration or cleanup outside this change
|
||||
@@ -0,0 +1,17 @@
|
||||
## Why
|
||||
|
||||
The current repository creation flow creates mirrored bare repositories for clone-based repos. That breaks the workspace model because the UI and file editing features expect a normal working clone with an initial branch, remote tracking, and pull/fetch behavior that works from a checked-out branch.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Create clone-based repositories as normal working clones instead of mirrors
|
||||
- Initialize blank repositories as working clones with an initial branch when needed
|
||||
- Ensure newly created repos have a usable current branch for workspace browsing and commits
|
||||
- Update pull semantics to use the current tracked branch when available
|
||||
- Keep fetch behavior available for remote-synced repositories
|
||||
|
||||
## Impact
|
||||
|
||||
- Backend: repository creation and git control helpers
|
||||
- Backend tests: clone, pull, and empty-repo branch behavior
|
||||
- Frontend: no intentional UX change beyond sync behavior becoming reliable
|
||||
@@ -0,0 +1,19 @@
|
||||
## 1. Backend - Repository Creation
|
||||
|
||||
- [x] 1.1 Switch clone-based repository creation from mirror clones to normal working clones
|
||||
- [x] 1.2 Initialize blank repositories with a default branch name
|
||||
- [x] 1.3 Preserve remote preflight and clear error handling
|
||||
|
||||
## 2. Backend - Git Sync Helpers
|
||||
|
||||
- [x] 2.1 Update pull behavior to use the current tracked branch when available
|
||||
- [x] 2.2 Make branch helpers tolerate unborn HEAD in blank repos
|
||||
|
||||
## 3. Tests
|
||||
|
||||
- [x] 3.1 Add unit coverage for clone creation and blank repo initialization
|
||||
- [x] 3.2 Add coverage for pull behavior on working clones and blank repos
|
||||
|
||||
## 4. Quality Gates
|
||||
|
||||
- [x] 4.1 Run targeted API tests
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-22
|
||||
@@ -0,0 +1,79 @@
|
||||
## Context
|
||||
|
||||
The current instance management has critical gaps in health monitoring that lead to poor user experience:
|
||||
|
||||
1. **Silent startup failures**: When `docker compose up` executes, the API immediately marks the instance as "running" without verifying the container actually reached a healthy state. Containers that crash on startup or fail to bind to their port appear "running" in the UI but serve 502 errors.
|
||||
|
||||
2. **Tunnel-only health checks**: The existing health check at `GET /instances/{id}/health` only performs an HTTP HEAD request to the tunnel URL. This cannot distinguish between:
|
||||
- Tunnel is broken (cloudflared process died) → should recreate tunnel
|
||||
- Tool crashed inside container → should show container error
|
||||
- Tool returns 502 because it's still starting → should wait for readiness probe
|
||||
|
||||
3. **Unused readiness probes**: The `readiness_probe.py` service was built during the tool-workshop change but is never called during instance startup. Tool types can configure readiness probes (e.g., `curl -f http://localhost:8080/health`) but these are ignored.
|
||||
|
||||
4. **Blind auto-recovery**: The frontend shows a "Recreate Tunnel" button when the health check fails, but this recreates the tunnel even when the application itself is returning 502 errors, wasting time and confusing users.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Verify containers actually start successfully before marking instances as "running"
|
||||
- Distinguish container health from tunnel health in monitoring
|
||||
- Integrate readiness probes into the instance startup flow
|
||||
- Only recreate tunnels when the tunnel itself is broken, not when the tool returns errors
|
||||
- Provide clear error messages when instances fail to start
|
||||
|
||||
**Non-Goals:**
|
||||
- Persistent tunnels (keeping temporary cloudflared tunnels)
|
||||
- Automatic restart of crashed containers (Docker already does this with restart policies)
|
||||
- Health check WebSocket push (polling is sufficient)
|
||||
- Changing the Docker compose architecture
|
||||
|
||||
## Decisions
|
||||
|
||||
**1. Startup verification via Docker API**
|
||||
- After `docker compose up`, poll `docker ps` for 30 seconds to verify container state transitions to "running"
|
||||
- If container exits or stays in "restarting" loop, mark instance as "error" with exit code
|
||||
- Rationale: Direct Docker API check is more reliable than HTTP checks during startup when ports may not be bound yet
|
||||
|
||||
**2. Readiness probe as gate to "running" status**
|
||||
- Instance status flow: `pending` → `starting` (container up) → `running` (probe passed)
|
||||
- If probe fails after timeout, status becomes `unhealthy` (not `error` - container is still up)
|
||||
- Rationale: Distinguishes "container won't start" from "container started but app isn't ready yet"
|
||||
|
||||
**3. Container + Tunnel dual health checks**
|
||||
- Health endpoint returns both `container_status` (from Docker API) and `tunnel_status` (HTTP check)
|
||||
- Frontend shows different badges: "container unhealthy" vs "tunnel error"
|
||||
- Rationale: Users need to know if they should wait (app starting) or recreate tunnel
|
||||
|
||||
**4. Smart tunnel failure detection**
|
||||
- Connection errors (ECONNREFUSED, ETIMEDOUT, DNS failure) → tunnel is broken → allow recreate
|
||||
- HTTP 502/503/504 → application error → show "app error" badge, don't recreate
|
||||
- HTTP 200-399 → healthy
|
||||
- Rationale: 502 from the tool means the tunnel is working fine, the tool just isn't responding
|
||||
|
||||
**5. Readiness probe configuration from ToolType**
|
||||
- Use existing `readiness_probe` JSON field on ToolType model
|
||||
- Default probe for web tools: `curl -f http://localhost:{port}`
|
||||
- Default probe for terminal tools: none (skip probe, mark running immediately)
|
||||
- Rationale: Leverages existing infrastructure, provides sensible defaults
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk] Startup polling adds latency** → Mitigation: Poll every 2 seconds with 30 second max timeout. Most containers start in <5 seconds.
|
||||
|
||||
**[Risk] Docker API calls from API container** → Mitigation: API container already has Docker CLI access for managing instances. Using `docker ps` is consistent with existing patterns.
|
||||
|
||||
**[Risk] False "unhealthy" from slow-starting tools** → Mitigation: 30 second default timeout with configurable override per tool type. Frontend shows "starting..." status during probe.
|
||||
|
||||
**[Risk] Probe commands may not exist in container** → Mitigation: Probe failures log stderr. If probe command missing, container still starts but marked as running without probe validation.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
No database migration needed. This change:
|
||||
1. Adds new status values ("starting", "unhealthy") to existing `status` enum
|
||||
2. Uses existing `readiness_probe` column on `tool_types` table
|
||||
3. Changes health check API response format (adds fields, doesn't remove)
|
||||
|
||||
## Open Questions
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,29 @@
|
||||
## Why
|
||||
|
||||
The current instance management has significant gaps in health monitoring. When starting instances, there's no verification that containers actually boot successfully - failures only surface when users try to access broken tunnels. The existing health check only validates tunnel URLs, not container health, leading to false positives where a "healthy" tunnel serves 502 errors from a crashed tool. Additionally, readiness probes exist as unused infrastructure, and auto-recovery blindly recreates tunnels on any HTTP error including legitimate 502s from the application itself.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Startup health checks**: Verify containers reach a running state after `docker compose up`, with clear failure messages when containers crash or fail to start
|
||||
- **Container health checks**: Check container status via Docker API (`docker ps`, `docker inspect`) in addition to tunnel URL checks
|
||||
- **Readiness probe integration**: Wire the existing `execute_probe()` service into the instance startup flow, using tool type configured probes
|
||||
- **Smart auto-recovery**: Only recreate tunnels when the tunnel endpoint itself is unreachable (connection refused, timeout, DNS failure), NOT when the tool returns 502/503/504 errors
|
||||
- **Instance status granularity**: Distinguish between "starting" (container booting), "running" (healthy), "unhealthy" (container up but probe failing), and "error" (failed to start)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `instance-startup-health`: Container startup verification and failure detection
|
||||
- `instance-runtime-health`: Continuous health monitoring combining container and tunnel checks
|
||||
- `readiness-probe-integration`: Tool-type configured readiness probes during instance startup
|
||||
- `smart-tunnel-recovery`: Context-aware tunnel recreation that distinguishes tunnel failures from application errors
|
||||
|
||||
### Modified Capabilities
|
||||
- `session-management-fixes`: Update health check endpoint to include container status, modify tunnel health logic to be smarter about error codes
|
||||
|
||||
## Impact
|
||||
|
||||
- **Backend**: `api/tool_instances.py` (start_instance, health check, recreate tunnel), `services/docker.py` (container status checks), `services/readiness_probe.py` (integration into startup flow)
|
||||
- **Frontend**: `pages/sessions.tsx` (display new status states, show startup errors, smarter health badges)
|
||||
- **Database**: No schema changes - uses existing `status` field with new state values
|
||||
- **API**: New response fields in health check endpoint (container_status, probe_result, last_probe_at)
|
||||
+57
@@ -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.
|
||||
+83
@@ -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.
|
||||
+51
@@ -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.
|
||||
+45
@@ -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.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
## 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
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Health check endpoint enhancement
|
||||
The system SHALL provide detailed health information through the health check endpoint.
|
||||
|
||||
#### 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
|
||||
|
||||
### Requirement: Smart tunnel recreation
|
||||
The system SHALL only allow tunnel recreation when the tunnel itself is broken.
|
||||
|
||||
#### 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
|
||||
|
||||
#### 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
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,56 @@
|
||||
## 1. Backend - Container Startup Verification
|
||||
|
||||
- [x] 1.1 Implement `wait_for_container_running()` in `services/docker.py` - polls `docker ps` until container reaches "running" state or timeout
|
||||
- [x] 1.2 Implement `get_container_status()` in `services/docker.py` - returns container state (running, exited, restarting, not_found) and exit code
|
||||
- [x] 1.3 Update `start_instance()` in `api/tool_instances.py` to call startup verification after `docker compose up`
|
||||
- [x] 1.4 Update instance status flow: "pending" → "starting" (after container verified running) → "running" (after probe)
|
||||
- [x] 1.5 Handle container startup failures: set status to "error" with exit code and logs
|
||||
|
||||
## 2. Backend - Readiness Probe Integration
|
||||
|
||||
- [x] 2.1 Update `start_instance()` to execute readiness probe after container is running
|
||||
- [x] 2.2 Read readiness probe config from ToolType model (command, timeout, interval)
|
||||
- [x] 2.3 Implement default probes: web tools use `curl -f http://localhost:{port}`, terminal tools skip probe
|
||||
- [x] 2.4 Store probe result (output, exit code, timestamp) on instance or in logs
|
||||
- [x] 2.5 Update instance status based on probe result: "running" on success, "unhealthy" on timeout
|
||||
|
||||
## 3. Backend - Health Check Enhancement
|
||||
|
||||
- [x] 3.1 Update `check_instance_tunnel_health()` to also check container status via Docker API
|
||||
- [x] 3.2 Enhance health response format with `container_status`, `container_health`, `tunnel_status`, `tunnel_status_code`, `probe_status`, `last_probe_output`
|
||||
- [x] 3.3 Implement `check_container_health()` helper that calls `docker inspect` for health status
|
||||
- [x] 3.4 Update overall `healthy` flag logic: true only if container running AND tunnel healthy
|
||||
|
||||
## 4. Backend - Smart Tunnel Recovery
|
||||
|
||||
- [x] 4.1 Enhance `check_tunnel_health()` to classify errors: connection errors vs HTTP errors
|
||||
- [x] 4.2 Update `recreate_tunnel_endpoint()` to validate tunnel is actually broken before recreating
|
||||
- [x] 4.3 Return 400 Bad Request with explanation when trying to recreate tunnel for 502/503 errors
|
||||
- [x] 4.4 Update tunnel health response: `tunnel_status` values ("healthy", "unreachable", "error_response", "not_applicable")
|
||||
|
||||
## 5. Frontend - Status Display
|
||||
|
||||
- [x] 5.1 Update session status badges to show new states: "starting", "unhealthy"
|
||||
- [x] 5.2 Show container error messages when instance fails to start
|
||||
- [x] 5.3 Display "tunnel error" badge only when `tunnel_status === "unreachable"`
|
||||
- [x] 5.4 Display "app error" badge when `tunnel_status === "error_response"` with status code
|
||||
- [x] 5.5 Show "starting..." badge when `container_status === "running"` but `probe_status === "pending"`
|
||||
|
||||
## 6. Frontend - Health Polling
|
||||
|
||||
- [x] 6.1 Update health polling to use enhanced health endpoint response
|
||||
- [x] 6.2 Store full health state (container + tunnel) in component state
|
||||
- [x] 6.3 Update "Recreate Tunnel" button visibility: only show when `tunnel_status === "unreachable"`
|
||||
- [x] 6.4 Show probe output in a collapsible section for diagnostics
|
||||
|
||||
## 7. Testing and Quality Gates
|
||||
|
||||
- [x] 7.1 Test container startup verification with fast-starting container
|
||||
- [x] 7.2 Test container startup failure (container exits immediately)
|
||||
- [x] 7.3 Test readiness probe success and timeout scenarios
|
||||
- [x] 7.4 Test health endpoint with various container states
|
||||
- [x] 7.5 Test smart tunnel recovery (connection error vs 502)
|
||||
- [x] 7.6 Run backend linting (ruff) - skipped (not installed)
|
||||
- [x] 7.7 Run backend type checking (mypy) - skipped (not installed)
|
||||
- [x] 7.8 Run frontend type checking (tsc) - PASSED
|
||||
- [x] 7.9 Build frontend and verify no errors - PASSED
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-20
|
||||
@@ -0,0 +1,83 @@
|
||||
## Context
|
||||
|
||||
Currently, tool instances run as Docker containers on the internal Docker network. The backend stores their URL as `http://localhost:{port}`, which is only accessible from inside the API container. Users clicking "Open" in the frontend get a 404 because their browser can't reach the internal container.
|
||||
|
||||
The API and containers share a Docker network, so the API can reach containers by their container name or IP.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Users can access running tool instances through the API via HTTPS
|
||||
- Proxy enforces ownership (only instance owner can access)
|
||||
- Support both HTTP and WebSocket traffic
|
||||
- Minimal latency overhead
|
||||
- Works with existing Docker setup
|
||||
|
||||
**Non-Goals:**
|
||||
- Public URLs / custom domains for instances (that's Option 2/3)
|
||||
- Load balancing across multiple instances
|
||||
- Advanced path rewriting (just pass-through)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Proxy via FastAPI route (not separate service)
|
||||
|
||||
**Decision:** Implement proxying as a FastAPI endpoint using `httpx` for async forwarding.
|
||||
|
||||
**Rationale:**
|
||||
- Keeps everything in one deployable unit
|
||||
- Easy access to existing auth dependencies (`get_current_user_id`)
|
||||
- Can reuse existing session cookie auth
|
||||
- No extra infrastructure needed
|
||||
|
||||
**Alternative considered:** Separate nginx/traefik proxy service
|
||||
- Rejected: adds operational complexity for a single feature
|
||||
|
||||
### Use container name for internal routing
|
||||
|
||||
**Decision:** Store container name in ToolInstance model and route to `http://{container_name}:{port}`
|
||||
|
||||
**Rationale:**
|
||||
- Container names are stable and DNS-resolvable within Docker network
|
||||
- More reliable than IPs which can change
|
||||
- Already using container names in docker.py
|
||||
|
||||
### Path: `/instances/{id}/proxy/{path:path}`
|
||||
|
||||
**Decision:** All proxied traffic goes through `/instances/{id}/proxy/*`
|
||||
|
||||
**Rationale:**
|
||||
- Clear URL structure
|
||||
- Easy to apply auth middleware
|
||||
- `path:path` captures everything after `/proxy/`
|
||||
|
||||
### WebSocket upgrade handling
|
||||
|
||||
**Decision:** Support WebSocket upgrade by inspecting the `Upgrade: websocket` header and establishing a bidirectional pipe.
|
||||
|
||||
**Rationale:**
|
||||
- code-server and jupyter use WebSockets for real-time features
|
||||
- FastAPI doesn't natively support proxying WebSockets, but we can use `starlette.websockets` to handle the upgrade
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk]** API becomes bandwidth bottleneck for all instance traffic
|
||||
→ **Mitigation:** Document this limitation. Future migration to Option 2 (Traefik labels) possible.
|
||||
|
||||
**[Risk]** Container name collision
|
||||
→ **Mitigation:** Instance names already include UUID suffix, collision probability is negligible.
|
||||
|
||||
**[Risk]** Large file uploads/downloads through proxy
|
||||
→ **Mitigation:** Use streaming response in httpx. Monitor memory usage.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Deploy backend changes (proxy endpoint + model updates)
|
||||
2. Update frontend links to use proxy URL
|
||||
3. Test with code-server instance
|
||||
4. Monitor API performance
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should we add rate limiting to the proxy endpoint?
|
||||
- Do we need to rewrite response headers (Location, Set-Cookie)?
|
||||
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
Tool instances (code-server, jupyter-notebook) run inside Docker containers with internal network addresses. Currently the "Open" button links to `http://localhost:{port}`, which only works from inside the API container and fails when opened from the user's browser. We need a way to expose these instances to users over HTTPS.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a proxy endpoint to the backend API: `/instances/{id}/proxy/{path:path}`
|
||||
- Proxy requests from the API to the running container (via docker network or internal IP)
|
||||
- Update frontend "Open" button to use the proxy URL instead of `localhost`
|
||||
- Add WebSocket proxy support for real-time features (terminal already uses WebSocket)
|
||||
- Ensure only the instance owner can access the proxied content
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `instance-proxy`: HTTP proxying for running tool instances through the API
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- None (this is purely an infrastructure/transport feature, not a change to existing capability requirements)
|
||||
|
||||
## Impact
|
||||
|
||||
- Backend: New proxy endpoint, container network discovery, request forwarding
|
||||
- Frontend: Update instance "Open" link to use proxy URL
|
||||
- Docker: Containers must be reachable from API container (already true via docker network)
|
||||
- Security: Owner-only access enforced at proxy level
|
||||
@@ -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,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,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-20
|
||||
@@ -0,0 +1,71 @@
|
||||
## Context
|
||||
|
||||
Currently, tool types have inconsistent port configuration:
|
||||
- `code-server`: default_port=8443, interfaces=["web"]
|
||||
- `jupyter-notebook`: default_port=8888, interfaces=["web"]
|
||||
- `opencode`: default_port=undefined, interfaces=["terminal"]
|
||||
|
||||
The tunnel creation code falls back to port 8080 when no default_port is set, which causes 502 Bad Gateway errors since OpenCode doesn't listen on any port.
|
||||
|
||||
OpenCode currently runs `tail -f /dev/null` in its container, keeping it alive for terminal access via WebSocket but providing no web interface. The user wants OpenCode accessible via a web terminal in the browser.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Make `default_port` a required field for all tool types with validation
|
||||
- Add a web server to OpenCode so it exposes a port for browser access
|
||||
- Ensure tunnel creation always uses the correct port from tool type config
|
||||
- Support tools with both terminal and web interfaces
|
||||
- Add compose template validation to ensure defined ports are actually exposed
|
||||
|
||||
**Non-Goals:**
|
||||
- Changing the existing WebSocket terminal implementation
|
||||
- Adding new authentication or authorization
|
||||
- Supporting non-HTTP protocols for tunnels
|
||||
- Modifying code-server or jupyter configurations
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision: OpenCode exposes a web terminal on port 3000
|
||||
|
||||
**Rationale:** OpenCode needs a web interface for browser access. We'll run a lightweight web server (using `npx serve` or a simple Node.js HTTP server) alongside the OpenCode CLI.
|
||||
|
||||
**Alternative considered:** Use a separate web terminal service (like ttyd or wetty). Rejected because it adds complexity and another dependency.
|
||||
|
||||
### Decision: Tools can have multiple interfaces
|
||||
|
||||
**Rationale:** OpenCode should support both terminal (via WebSocket) and web (via browser) access. The `interfaces` field should allow `["terminal", "web"]`.
|
||||
|
||||
### Decision: Validate ports in compose templates
|
||||
|
||||
**Rationale:** Prevent misconfiguration where a tool type claims to use port 8443 but the compose template doesn't expose it.
|
||||
|
||||
**Implementation:** When creating/updating tool types, parse the compose template YAML and verify the port is in the `ports` section.
|
||||
|
||||
### Decision: Store tunnel URL in instance.url, not public_url
|
||||
|
||||
**Rationale:** Simplify the data model. The `url` field is what the frontend uses to open tools. `public_url` is redundant.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[Risk]** OpenCode web terminal may not work well without proper TTY support
|
||||
→ **Mitigation**: Test thoroughly, fall back to raw terminal if needed
|
||||
|
||||
- **[Risk]** Running a web server in OpenCode container increases resource usage
|
||||
→ **Mitigation**: Use a minimal static file server (~5MB memory)
|
||||
|
||||
- **[Risk]** Port conflicts if multiple instances use the same default_port
|
||||
→ **Mitigation**: Docker maps container ports to host ports automatically, internal ports can overlap
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Update OpenCode compose template to include a web server
|
||||
2. Add `default_port: 3000` to OpenCode seed data
|
||||
3. Add port validation to tool type API
|
||||
4. Update instance list to show both Open and Terminal buttons for dual-interface tools
|
||||
5. Test OpenCode instance creation and tunnel access
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should we use `npx serve` or a custom Node.js server for OpenCode web UI?
|
||||
- Should the web terminal use the existing xterm.js component or redirect to a separate page?
|
||||
@@ -0,0 +1,29 @@
|
||||
## Why
|
||||
|
||||
Tool instances currently have inconsistent port configuration. OpenCode lacks a default port and doesn't expose a web interface, while code-server and jupyter have hardcoded ports. We need a systematic way to define tool ports and ensure OpenCode works properly via the web terminal interface.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Tool Port Configuration**: Make `default_port` required for all tool types and validate it during tool type creation
|
||||
- **OpenCode Web Terminal**: Configure OpenCode to run a web server (e.g., on port 3000) so it can be accessed via browser, not just through the raw WebSocket terminal
|
||||
- **Tunnel Port Discovery**: Ensure cloudflared tunnels use the correct internal port from the tool type definition
|
||||
- **Terminal-First Tools**: Add support for tools that primarily use the terminal interface but may also expose a web UI
|
||||
- **Tool Validation**: Add validation to ensure tool compose templates expose the port defined in `default_port`
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `tool-port-configuration`: Systematic port definition and validation for tool types
|
||||
- `opencode-web-server`: Running OpenCode with a web interface accessible via browser
|
||||
|
||||
### Modified Capabilities
|
||||
- `tool-types`: Adding port validation requirements and web interface support for terminal tools
|
||||
- `tool-instances`: Tunnel creation must read port from tool type configuration
|
||||
- `tool-terminal`: Terminal tools may optionally expose web endpoints
|
||||
|
||||
## Impact
|
||||
|
||||
- Backend: Tool type model, validation, seed data, tunnel creation logic
|
||||
- Frontend: Instance list may show both Open (web) and Terminal buttons for tools with dual interfaces
|
||||
- Docker: OpenCode compose template needs a web server command
|
||||
- Infrastructure: Cloudflared tunnels must target the correct internal port
|
||||
+39
@@ -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
|
||||
+38
@@ -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
|
||||
@@ -0,0 +1,48 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Tool Type Model
|
||||
|
||||
The system SHALL store tool type definitions in the database.
|
||||
|
||||
#### Scenario: Create tool type
|
||||
- GIVEN an admin user
|
||||
- WHEN they define a new tool type
|
||||
- THEN the following fields are stored:
|
||||
- name: Tool identifier
|
||||
- description: Human-readable description
|
||||
- docker_compose_template: Compose file template
|
||||
- icon: Visual identifier
|
||||
- category: Tool category
|
||||
- default_env_vars: Default environment variables
|
||||
- default_port: **Required** primary port the tool listens on
|
||||
- interfaces: List of supported interfaces ("web", "terminal")
|
||||
|
||||
#### 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
|
||||
|
||||
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 (port 8443, interfaces: ["web"])
|
||||
- jupyter-notebook: Jupyter notebooks (port 8888, interfaces: ["web"])
|
||||
- opencode: OpenCode agent environment (port 3000, interfaces: ["terminal", "web"])
|
||||
|
||||
### Requirement: Template Validation
|
||||
|
||||
The system SHALL validate Docker Compose templates.
|
||||
|
||||
#### Scenario: Invalid template
|
||||
- GIVEN an invalid Docker Compose template
|
||||
- WHEN a user tries to create/update a tool type
|
||||
- THEN the system rejects with validation errors
|
||||
|
||||
#### 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,39 @@
|
||||
## 1. Tool Type Port Configuration
|
||||
|
||||
- [x] 1.1 Update ToolType model to make `default_port` required (non-nullable)
|
||||
- [x] 1.2 Add validation in tool type API to reject missing `default_port`
|
||||
- [x] 1.3 Add compose template validation to verify port is exposed in `ports` section
|
||||
- [x] 1.4 Update tool type creation/update endpoints to validate port configuration
|
||||
|
||||
## 2. OpenCode Web Server
|
||||
|
||||
- [x] 2.1 Update OpenCode compose template to run a web server on port 3000
|
||||
- [x] 2.2 Add `default_port: 3000` to OpenCode seed data
|
||||
- [x] 2.3 Update OpenCode `interfaces` to `["terminal", "web"]`
|
||||
- [x] 2.4 Create a simple web terminal HTML page served by OpenCode container
|
||||
|
||||
## 3. Tunnel Port Fix
|
||||
|
||||
- [x] 3.1 Update tunnel creation to use `tool_type.default_port` instead of hardcoded 8080
|
||||
- [x] 3.2 Ensure tunnel creation fails gracefully if port is not defined
|
||||
- [x] 3.3 Remove fallback to port 8080 in tunnel creation
|
||||
|
||||
## 4. Frontend Updates
|
||||
|
||||
- [x] 4.1 Update instance list to show both "Open" and "Terminal" buttons for dual-interface tools
|
||||
- [x] 4.2 Update ToolType interface in frontend to include `default_port`
|
||||
- [x] 4.3 Update tool type creation form to require port input
|
||||
|
||||
## 5. Database Migration
|
||||
|
||||
- [x] 5.1 Create Alembic migration to make `default_port` non-nullable
|
||||
- [x] 5.2 Set `default_port` for existing tool types (code-server=8443, jupyter=8888, opencode=3000)
|
||||
|
||||
## 6. Testing & Quality Gates
|
||||
|
||||
- [x] 6.1 Test creating tool type without port fails validation
|
||||
- [x] 6.2 Test creating tool type with port mismatch fails validation
|
||||
- [x] 6.3 Test OpenCode instance creates tunnel on port 3000
|
||||
- [x] 6.4 Run backend quality gates (ruff, mypy) - skipped (not installed)
|
||||
- [x] 6.5 Run frontend quality gates (typecheck, lint, build) - PASSED
|
||||
- [x] 6.6 Commit and push changes
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-22
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-20
|
||||
@@ -0,0 +1,62 @@
|
||||
## Context
|
||||
|
||||
The session management system currently has three UX and reliability issues:
|
||||
|
||||
1. **No stop confirmation**: Clicking "Stop" immediately stops the session without asking the user, leading to accidental interruptions
|
||||
2. **Stale state after delete**: When a session is deleted, the frontend React state is not updated, so the deleted session remains visible until the page is manually reloaded
|
||||
3. **No tunnel recovery**: If a temporary Cloudflare tunnel breaks (e.g., cloudflared process dies), there's no way to recreate it without stopping and restarting the entire instance
|
||||
|
||||
The system uses temporary Cloudflare tunnels (`cloudflared tunnel --url`) which run as background processes inside the API container. These tunnels can fail silently.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Prevent accidental session stops with a confirmation dialog
|
||||
- Update frontend state immediately after successful deletion
|
||||
- Monitor tunnel health by checking HTTP responses
|
||||
- Allow tunnel recreation without instance restart
|
||||
- Display tunnel health status to users
|
||||
|
||||
**Non-Goals:**
|
||||
- Persistent tunnels (we're keeping temporary tunnels)
|
||||
- Auto-recovery of broken tunnels (manual button only)
|
||||
- Changing the Docker compose architecture
|
||||
- Adding WebSocket health checks
|
||||
|
||||
## Decisions
|
||||
|
||||
**1. Frontend confirmation dialog**
|
||||
- Use a simple inline confirmation (not a modal) to match existing patterns in the codebase
|
||||
- Show "Confirm stop? [Cancel] [Stop]" when stop is clicked
|
||||
- Reuse existing CSS button styles
|
||||
|
||||
**2. Frontend state update after delete**
|
||||
- Filter out the deleted session from local React state immediately after delete API call succeeds
|
||||
- Don't wait for the next polling cycle
|
||||
|
||||
**3. Tunnel health check**
|
||||
- Poll tunnel health every 30 seconds via HEAD request to the tunnel URL
|
||||
- Check only running instances (status === "running")
|
||||
- Mark as "error" if response is not 2xx or request fails
|
||||
- Show error badge next to session name
|
||||
|
||||
**4. Tunnel recreation**
|
||||
- New backend endpoint: `POST /instances/{id}/recreate-tunnel`
|
||||
- Kills old cloudflared process (if any) via stored PID
|
||||
- Starts new cloudflared process with `start_cloudflared_tunnel()`
|
||||
- Updates instance.url and instance.tunnel_id in database
|
||||
- Frontend button: "Recreate Tunnel" appears when tunnel is in error state
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk] Health check adds network overhead** → Mitigation: Only check every 30s, only for running instances
|
||||
**[Risk] Recreating tunnel while user is connected** → Mitigation: User-initiated action, brief downtime (5-10s)
|
||||
**[Risk] PID reuse could kill wrong process** → Mitigation: Check process name before killing (optional enhancement)
|
||||
|
||||
## Migration Plan
|
||||
|
||||
No migration needed. These are UI/UX improvements on existing data model.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
The session management UI has critical UX and reliability issues that make it frustrating to use. Users can accidentally stop sessions without confirmation, deleted sessions remain visible until manual reload, and broken tunnels require full instance restart to fix. These bugs degrade the core user experience of the tool instance system.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Add confirmation dialog for stopping sessions** - Prevent accidental session stops with a "Are you sure?" dialog
|
||||
- **Fix frontend state after session deletion** - Update React state immediately when delete succeeds so the session disappears without reload
|
||||
- **Add tunnel health monitoring** - Periodically check if tunnel URLs respond with HTTP 200, mark as erroneous if not
|
||||
- **Add "Recreate Tunnel" button** - Allow users to regenerate a broken tunnel without restarting the entire instance
|
||||
- **Display tunnel health status** - Show visual indicator (error badge) when a tunnel is broken
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `tunnel-health-monitoring`: Background health checks for temporary Cloudflare tunnels with status indicators
|
||||
- `session-lifecycle-ux`: Improved session stop/delete interactions with confirmations and state updates
|
||||
|
||||
### Modified Capabilities
|
||||
- `tool-instances`: Update instance model and API to support tunnel recreation without full restart
|
||||
|
||||
## Impact
|
||||
|
||||
- Frontend: `sessions.tsx`, `instance-list.tsx`, `api/sessions.ts`
|
||||
- Backend: `tool_instances.py` (tunnel recreation endpoint), `docker.py` (tunnel restart utility)
|
||||
- Database: No schema changes needed (existing `tunnel_id` and `url` fields reused)
|
||||
- Docker: No changes needed
|
||||
+34
@@ -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
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Tool Lifecycle
|
||||
|
||||
The system SHALL manage tool lifecycle operations including tunnel recreation.
|
||||
|
||||
#### Scenario: Stop tool
|
||||
- GIVEN a running tool instance
|
||||
- WHEN the user stops it
|
||||
- THEN `docker compose stop` is executed
|
||||
- AND the cloudflared tunnel process is terminated
|
||||
- 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 a new temporary Cloudflare tunnel is created
|
||||
- AND status is updated to "running"
|
||||
|
||||
#### Scenario: Recreate tunnel
|
||||
- GIVEN a running tool instance with a broken tunnel
|
||||
- WHEN the user requests tunnel recreation
|
||||
- THEN the existing cloudflared process is terminated
|
||||
- AND a new temporary Cloudflare tunnel is created
|
||||
- AND the instance URL is updated
|
||||
- AND the instance shows as healthy
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Tunnel Health Check
|
||||
|
||||
The system SHALL check tunnel health for running instances.
|
||||
|
||||
#### Scenario: Healthy tunnel check
|
||||
- GIVEN a running instance with an active tunnel
|
||||
- WHEN the health check runs
|
||||
- THEN the tunnel URL responds with HTTP 2xx
|
||||
- AND the instance is marked as healthy
|
||||
|
||||
#### Scenario: Broken tunnel check
|
||||
- GIVEN a running instance with a broken tunnel
|
||||
- WHEN the health check runs
|
||||
- THEN the tunnel URL does not respond with HTTP 2xx
|
||||
- AND the instance is marked with tunnel_error
|
||||
- AND a "Recreate Tunnel" button is shown
|
||||
+35
@@ -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
|
||||
@@ -0,0 +1,42 @@
|
||||
## 1. Backend - Tunnel Recreation
|
||||
|
||||
- [x] 1.1 Add `recreate_tunnel` function to docker.py
|
||||
- [x] 1.2 Create `POST /instances/{id}/recreate-tunnel` endpoint in tool_instances.py
|
||||
- [x] 1.3 Update stop_instance to also stop the tunnel process
|
||||
|
||||
## 2. Backend - Tunnel Health Check
|
||||
|
||||
- [x] 2.1 Add `check_tunnel_health(url)` function to docker.py
|
||||
- [x] 2.2 Create `GET /instances/{id}/health` endpoint in tool_instances.py
|
||||
- [x] 2.3 Add tunnel_url_health field to ToolInstance model (optional, can use status)
|
||||
|
||||
## 3. Frontend - Stop Confirmation
|
||||
|
||||
- [x] 3.1 Add confirmation dialog component for stop action
|
||||
- [x] 3.2 Update SessionsPage stop handler to show confirmation
|
||||
- [x] 3.3 Update InstanceList stop handler to show confirmation
|
||||
|
||||
## 4. Frontend - Delete State Update
|
||||
|
||||
- [x] 4.1 Update delete handler in SessionsPage to filter state immediately
|
||||
- [x] 4.2 Update delete handler in InstanceList to filter state immediately
|
||||
- [x] 4.3 Ensure error handling shows message on failure
|
||||
|
||||
## 5. Frontend - Tunnel Health & Recreate
|
||||
|
||||
- [x] 5.1 Add tunnel health check API function in sessions.ts
|
||||
- [x] 5.2 Add recreate tunnel API function in sessions.ts
|
||||
- [x] 5.3 Implement health check polling (30s interval) in SessionsPage
|
||||
- [x] 5.4 Show error badge when tunnel is unhealthy
|
||||
- [x] 5.5 Add "Recreate Tunnel" button next to "Open" button
|
||||
- [x] 5.6 Update InstanceList to show health status and recreate button
|
||||
|
||||
## 6. Quality Gates
|
||||
|
||||
- [x] 6.1 Run Python syntax check
|
||||
- [x] 6.2 Run frontend typecheck - PASSED
|
||||
- [x] 6.3 Run frontend lint - PASSED
|
||||
- [x] 6.4 Test stop confirmation dialog
|
||||
- [x] 6.5 Test delete state update
|
||||
- [x] 6.6 Test tunnel recreation
|
||||
- [x] 6.7 Commit and push changes
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
name: sessions-hub
|
||||
@@ -0,0 +1,120 @@
|
||||
# Sessions Hub - Design
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Sessions Hub
|
||||
├── Navigation
|
||||
│ └── "Sessions" tab (between Dashboard and Projects)
|
||||
│ └── Badge with active session count
|
||||
├── SessionsPage
|
||||
│ ├── Last Session Section
|
||||
│ │ └── Quick access card with resume button
|
||||
│ ├── Active Sessions Section
|
||||
│ │ └── Grid of active session cards
|
||||
│ ├── Recent Sessions Section
|
||||
│ │ └── List of recent sessions
|
||||
│ └── Create Session Section
|
||||
│ └── Project selector + tool type selector
|
||||
└── User Config
|
||||
└── last_session_id field
|
||||
```
|
||||
|
||||
## Component Design
|
||||
|
||||
### SessionsPage
|
||||
|
||||
**Sections:**
|
||||
1. **Last Session** (if exists)
|
||||
- Large card showing last session details
|
||||
- "Resume" button to open the workspace
|
||||
- Shows project, repository, tool type
|
||||
|
||||
2. **Active Sessions**
|
||||
- Grid of cards for running instances
|
||||
- Each card: name, type, status badge, action buttons
|
||||
- Actions: Open, Stop, Restart, Delete
|
||||
|
||||
3. **Recent Sessions**
|
||||
- List of last 5 sessions (any status)
|
||||
- Compact list view with status indicators
|
||||
- Click to navigate to workspace
|
||||
|
||||
4. **Create New Session**
|
||||
- Project dropdown (all user's projects)
|
||||
- Repository dropdown (filtered by project)
|
||||
- Tool type dropdown
|
||||
- Display name input
|
||||
- "Create" button
|
||||
|
||||
### AppShell Updates
|
||||
|
||||
**Navigation:**
|
||||
```
|
||||
Dashboard | Sessions (3) | Projects | SSH Keys | Tool Types | Settings
|
||||
```
|
||||
|
||||
**Badge:**
|
||||
- Shows count of active (running) sessions
|
||||
- Updates via existing sessions polling
|
||||
|
||||
### User Config Extension
|
||||
|
||||
**New field:**
|
||||
```typescript
|
||||
interface UserConfig {
|
||||
// existing fields...
|
||||
last_session_id: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
**Update timing:**
|
||||
- Set when creating a new session
|
||||
- Set when opening/resuming a session
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Loading Sessions Page
|
||||
1. Fetch user config (for last_session_id)
|
||||
2. Fetch all user sessions via `/users/me/sessions`
|
||||
3. Filter into active vs recent
|
||||
4. Display last session if available
|
||||
|
||||
### Creating Session
|
||||
1. User selects project, repo, tool type
|
||||
2. POST to `/projects/{id}/repositories/{id}/instances`
|
||||
3. On success: update user config with last_session_id
|
||||
4. Refresh sessions list
|
||||
|
||||
### Resuming Session
|
||||
1. User clicks "Resume" on last session
|
||||
2. Navigate to workspace with session active
|
||||
3. Update user config (reinforce as last)
|
||||
|
||||
## API Changes
|
||||
|
||||
### GET /users/me/sessions
|
||||
Already exists - returns all sessions for user.
|
||||
|
||||
### PATCH /users/me/config
|
||||
Already exists - add `last_session_id` to config schema.
|
||||
|
||||
## Technical Details
|
||||
|
||||
**Frontend:**
|
||||
- New page: `pages/sessions.tsx`
|
||||
- Update: `app-shell.tsx` for navigation
|
||||
- Update: `api/settings.ts` for config type
|
||||
- Update: `state/sessions.tsx` for badge count
|
||||
|
||||
**Backend:**
|
||||
- Update: `models/user_config.py` schema
|
||||
- Update: `api/user_config.py` to accept last_session_id
|
||||
|
||||
**No new backend endpoints needed** - reuse existing APIs.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- No sessions: Show empty state with "Create your first session" CTA
|
||||
- Failed to load: Show error with retry button
|
||||
- Create failed: Show error message, keep form open
|
||||
@@ -0,0 +1,51 @@
|
||||
# Sessions Hub
|
||||
|
||||
## Problem
|
||||
|
||||
Users currently have to navigate into individual projects and repositories to see their active tool instances (sessions). There's no centralized place to:
|
||||
- See all active/open sessions at a glance
|
||||
- Quickly access the last used session
|
||||
- Create new sessions without navigating deep into the project hierarchy
|
||||
|
||||
## Solution
|
||||
|
||||
Create a dedicated **Sessions Hub** page that serves as the central place for managing tool instances:
|
||||
|
||||
1. **Navigation tab** between Dashboard and Projects
|
||||
2. **Active sessions section** showing all running/open instances
|
||||
3. **Last session** prominently displayed for quick access
|
||||
4. **Quick create** - create sessions for any project from one place
|
||||
5. **Persist last session** in user config for easier access
|
||||
|
||||
## Key Features
|
||||
|
||||
### Sessions Page
|
||||
- Shows all active (running) sessions with status, type, and links
|
||||
- Shows recent sessions (last 5)
|
||||
- Shows last created session at the top for quick access
|
||||
- "New Session" button to create instances for any project
|
||||
|
||||
### Navigation
|
||||
- New "Sessions" tab in the app shell between Dashboard and Projects
|
||||
- Shows count of active sessions as a badge
|
||||
|
||||
### Quick Access
|
||||
- Last created session saved to user config
|
||||
- One-click to reopen/resume last session
|
||||
- Session history for quick navigation
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Faster workflow** - No need to navigate deep into projects
|
||||
- **Better visibility** - See all active work at a glance
|
||||
- **Quick resume** - Jump back to last work instantly
|
||||
- **Centralized management** - One place for all sessions
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Sessions tab visible in navigation between Dashboard and Projects
|
||||
- [ ] Sessions page shows active sessions
|
||||
- [ ] Last session displayed prominently
|
||||
- [ ] Can create session for any project from Sessions page
|
||||
- [ ] Last created session persists in user config
|
||||
- [ ] Badge shows count of active sessions
|
||||
@@ -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,93 @@
|
||||
# Sessions Hub - Tasks
|
||||
|
||||
## Phase 1: Backend Config Update
|
||||
|
||||
- [x] **Task 1.1**: Update UserConfig model
|
||||
- Add `last_session_id` field to `models/user_config.py`
|
||||
- Create Alembic migration
|
||||
|
||||
- [x] **Task 1.2**: Update config API
|
||||
- Accept `last_session_id` in `api/user_config.py`
|
||||
- Update Pydantic schemas
|
||||
|
||||
## Phase 2: Frontend Navigation
|
||||
|
||||
- [x] **Task 2.1**: Add Sessions tab to AppShell
|
||||
- Insert between Dashboard and Projects
|
||||
- Add sessions icon
|
||||
- Show badge with active count
|
||||
|
||||
- [x] **Task 2.2**: Update router
|
||||
- Add `/sessions` route
|
||||
- Create placeholder page
|
||||
|
||||
## Phase 3: Sessions Page
|
||||
|
||||
- [x] **Task 3.1**: Create SessionsPage component
|
||||
- Page layout with sections
|
||||
- Loading and error states
|
||||
|
||||
- [x] **Task 3.2**: Implement Last Session section
|
||||
- Fetch from user config
|
||||
- Show session card with resume button
|
||||
- Handle no last session state
|
||||
|
||||
- [x] **Task 3.3**: Implement Active Sessions section
|
||||
- Fetch from sessions context
|
||||
- Grid of session cards
|
||||
- Action buttons (Open, Stop, Restart, Delete)
|
||||
|
||||
- [x] **Task 3.4**: Implement Recent Sessions section
|
||||
- Show last 5 sessions
|
||||
- Compact list view
|
||||
- Status indicators
|
||||
|
||||
- [x] **Task 3.5**: Implement Create Session section
|
||||
- Project selector (fetch all projects)
|
||||
- Repository selector (filtered by project)
|
||||
- Tool type selector
|
||||
- Display name input
|
||||
- Create button with validation
|
||||
|
||||
## Phase 4: Session Actions
|
||||
|
||||
- [x] **Task 4.1**: Resume last session
|
||||
- Navigate to workspace
|
||||
- Update user config
|
||||
|
||||
- [x] **Task 4.2**: Open session
|
||||
- Navigate to workspace with 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
|
||||
|
||||
- [x] **Task 5.1**: Add CSS styles
|
||||
- Session cards layout
|
||||
- Badge styling
|
||||
- Responsive design
|
||||
|
||||
- [x] **Task 5.2**: Add icons
|
||||
- Session icon in navigation
|
||||
- Action icons on cards
|
||||
|
||||
## Phase 6: Quality Gates
|
||||
|
||||
- [x] **Task 6.1**: TypeScript check
|
||||
- `npm run typecheck`
|
||||
|
||||
- [x] **Task 6.2**: Lint check
|
||||
- `npm run lint`
|
||||
|
||||
- [x] **Task 6.3**: Build check
|
||||
- `npm run build`
|
||||
|
||||
- [x] **Task 6.4**: Manual verification
|
||||
- Navigation shows Sessions tab
|
||||
- Badge shows correct count
|
||||
- Last session displays
|
||||
- Can create session from page
|
||||
- Config persists
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-20
|
||||
@@ -0,0 +1,65 @@
|
||||
## Context
|
||||
|
||||
Tool instances currently start with hardcoded compose templates. There's no way for users to provide API keys (OpenAI, Anthropic), custom settings, or files that tools need. We need a flexible config system that supports both environment variables and file-based configs.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Store tool configs per user (global) and per project
|
||||
- Support env vars and file-based configs
|
||||
- Mount configs into containers at startup
|
||||
- Add tool categories (editor, notebook, ai-assistant)
|
||||
- Add interface types (web, terminal) to control UI
|
||||
- Add OpenCode as built-in terminal tool
|
||||
|
||||
**Non-Goals:**
|
||||
- Secret encryption at rest (for now)
|
||||
- Config validation beyond basic type checking
|
||||
- Per-instance configs (only global and project-scoped)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Config scope: user-global and user+project
|
||||
|
||||
**Decision:** Two scopes - global (user-level) and project-specific (user+project level)
|
||||
|
||||
**Rationale:** Some configs (like OpenAI API key) are user-global. Others (like project-specific paths) are per-project.
|
||||
|
||||
### Config types: env and file
|
||||
|
||||
**Decision:** Support two config types: `env` (injected as environment variables) and `file` (written to files and mounted)
|
||||
|
||||
**Rationale:** Most tools need env vars. Some (like OpenCode) need config files.
|
||||
|
||||
### Tool categories as enum
|
||||
|
||||
**Decision:** Predefined categories: `editor`, `notebook`, `ai-assistant`, `other`
|
||||
|
||||
**Rationale:** Simple, predictable, drives UI behavior.
|
||||
|
||||
### Interfaces as array
|
||||
|
||||
**Decision:** ToolType.interfaces is a JSON array of strings: `["web"]`, `["terminal"]`, `["web", "terminal"]`
|
||||
|
||||
**Rationale:** Flexible, allows combination interfaces.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Risk]** Config files in container filesystem are readable by any process in container
|
||||
→ **Mitigation:** Document this. Future: use Docker secrets for sensitive values.
|
||||
|
||||
**[Risk]** Storing API keys in plain text in database
|
||||
→ **Mitigation:** Acceptable for MVP. Future: encrypt sensitive configs.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Create migrations for tool_types (category, interfaces) and tool_configs tables
|
||||
2. Update seed data for built-in types
|
||||
3. Deploy backend changes
|
||||
4. Update frontend to show categories and config UI
|
||||
5. Test with OpenCode instance
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should we encrypt sensitive configs now or later?
|
||||
- Do we need config templates/tooling per tool type?
|
||||
@@ -0,0 +1,26 @@
|
||||
## Why
|
||||
|
||||
Tool instances need configuration (API keys, settings, files) that varies by user and project. Currently there's no way to manage these configs. Users need to store LLM API keys, editor preferences, and tool-specific settings that get mounted into containers at runtime.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add ToolConfig model for storing key-value configs per user/project/tool
|
||||
- Add category and interfaces fields to ToolType model
|
||||
- Create API for managing tool configs (global and project-scoped)
|
||||
- Mount configs into containers when starting instances
|
||||
- Add OpenCode as built-in tool type with terminal interface
|
||||
- Update frontend to show tool categories and interface-appropriate actions
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `tool-config-management`: Store and manage tool configurations
|
||||
- `tool-categories`: Categorize tools and expose appropriate interfaces
|
||||
|
||||
### Modified Capabilities
|
||||
- `tool-types`: Add category and interfaces fields
|
||||
|
||||
## Impact
|
||||
- Backend: New model, API endpoints, container startup changes
|
||||
- Frontend: Config management UI, category display
|
||||
- Database: New tool_configs table, migrations for tool_types
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Tool configs can be stored per user
|
||||
The system SHALL allow users to store configuration values for tool types.
|
||||
|
||||
#### Scenario: Save global config
|
||||
- **WHEN** a user saves a config value for a tool type
|
||||
- **THEN** the config is stored with user_id and tool_type_id
|
||||
- **AND** it is available for all future instances of that tool
|
||||
|
||||
#### Scenario: Save project-specific config
|
||||
- **WHEN** a user saves a config value with a project_id
|
||||
- **THEN** the config is scoped to that project
|
||||
- **AND** it overrides global config for that project
|
||||
|
||||
### Requirement: Configs support env and file types
|
||||
The system SHALL support environment variable configs and file-based configs.
|
||||
|
||||
#### Scenario: Env config
|
||||
- **WHEN** a config has type "env"
|
||||
- **THEN** it is injected as an environment variable when starting the container
|
||||
|
||||
#### Scenario: File config
|
||||
- **WHEN** a config has type "file"
|
||||
- **THEN** it is written to a file in the container
|
||||
- **AND** the file path is configurable
|
||||
|
||||
### Requirement: Tool types have categories and interfaces
|
||||
The system SHALL categorize tool types and declare their interfaces.
|
||||
|
||||
#### Scenario: Web interface tool
|
||||
- **WHEN** a tool type has interface "web"
|
||||
- **THEN** the UI shows an "Open" button
|
||||
|
||||
#### Scenario: Terminal interface tool
|
||||
- **WHEN** a tool type has interface "terminal"
|
||||
- **THEN** the UI shows a "Terminal" button
|
||||
|
||||
### Requirement: OpenCode is available as built-in tool
|
||||
The system SHALL include OpenCode as a built-in tool type with terminal interface.
|
||||
|
||||
#### Scenario: Create OpenCode instance
|
||||
- **WHEN** a user creates an OpenCode instance
|
||||
- **THEN** it starts a container with opencode installed
|
||||
- **AND** the repo is mounted at /workspace
|
||||
- **AND** the user can access it via terminal
|
||||
@@ -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,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-20
|
||||
@@ -0,0 +1,39 @@
|
||||
## Context
|
||||
|
||||
The current tool configuration page at `/tool-configs` uses a simple flat list with dropdown selection. Each config only has `key`, `value`, `config_type`, and `file_path` fields. Users have requested:
|
||||
1. A split-pane layout (list on left, detail on right) for better navigation
|
||||
2. Additional configuration options like start command, port, working directory
|
||||
3. Better organization of environment variables and volume mounts
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Implement split-pane layout with tool config list on left and detail/edit panel on right
|
||||
- Add new fields to tool config model: `start_command`, `port`, `working_directory`, `environment_variables`, `volumes`
|
||||
- Support JSON editing for complex fields (environment variables, volumes)
|
||||
- Maintain backward compatibility with existing configs
|
||||
- Improve UX for managing multiple tool configurations
|
||||
|
||||
**Non-Goals:**
|
||||
- Changing the underlying Docker/container runtime behavior
|
||||
- Adding new tool types
|
||||
- Modifying the tool instance creation flow beyond config injection
|
||||
- Real-time collaboration on configs
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Split-pane layout**: Use a responsive 2-column layout (30/70 split) that stacks on mobile. Left panel shows scrollable list of configs grouped by tool type. Right panel shows form for selected config.
|
||||
|
||||
2. **New fields as JSON columns**: Store `environment_variables` and `volumes` as JSON in PostgreSQL to allow flexible key-value structures without rigid schema changes.
|
||||
|
||||
3. **Port field**: Store as integer with validation (1-65535). Null means "use tool type default".
|
||||
|
||||
4. **Form design**: Use tabs or sections within the right panel to organize: Basic (key, value), Runtime (start_command, port, working_directory), Advanced (env vars, volumes).
|
||||
|
||||
5. **Validation**: Validate JSON structure on backend before saving. Show clear error messages in the UI.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Migration complexity**: Existing configs need default values for new columns. Mitigation: All new fields are nullable with sensible defaults.
|
||||
- **JSON editing UX**: Raw JSON editing is error-prone. Mitigation: Provide structured key-value editors that generate JSON under the hood.
|
||||
- **Mobile experience**: Split-pane may be cramped on small screens. Mitigation: Stack panels vertically on mobile breakpoints.
|
||||
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
The current tool configuration page (`/tool-configs`) presents all configs in a flat list with a basic form. As the number of tool types and configuration options grows, this becomes unwieldy. Users need a more organized way to browse, edit, and manage configurations per tool type.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Rework the tool config UI** from a flat list to a **split-pane layout**: list of tool configs on the left, detail/edit panel on the right
|
||||
- **Add new config fields**: `start_command`, `port`, `working_directory`, `environment_variables` (JSON), `volumes` (JSON)
|
||||
- **Update backend model** to support these new fields
|
||||
- **Create database migration** for the new columns
|
||||
- **Update API endpoints** to handle new fields
|
||||
- **Update frontend types and API client**
|
||||
- **Redesign the page** with proper navigation and editing experience
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `tool-config-management`: Enhanced tool configuration management with extended fields and split-pane UI
|
||||
|
||||
### Modified Capabilities
|
||||
- `tool-types`: Tool type display will show associated configs in the new UI (presentation layer change only, no API changes)
|
||||
|
||||
## Impact
|
||||
|
||||
- **Backend**: `tool_configs` model, API endpoints, database migration
|
||||
- **Frontend**: Complete rework of `ToolConfigsPage` component, new types, updated API client
|
||||
- **Database**: New columns on `tool_configs` table
|
||||
- **User Experience**: Significantly improved configuration management workflow
|
||||
+53
@@ -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"
|
||||
@@ -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
|
||||
@@ -0,0 +1,98 @@
|
||||
# UI Redesign - Design
|
||||
|
||||
## Information Architecture
|
||||
|
||||
```
|
||||
App
|
||||
├── Home
|
||||
│ ├── Hero / status
|
||||
│ ├── Open sessions
|
||||
│ ├── Available projects
|
||||
│ └── Session creation
|
||||
├── Sessions
|
||||
├── Projects
|
||||
├── Tool Workshop
|
||||
├── Settings
|
||||
│ ├── General
|
||||
│ └── SSH Keys
|
||||
└── Legacy routes
|
||||
└── Redirect to new locations
|
||||
```
|
||||
|
||||
## Home Page
|
||||
|
||||
### Purpose
|
||||
|
||||
Provide a fast, glanceable overview of the user's active work.
|
||||
|
||||
### Sections
|
||||
|
||||
1. **Hero**
|
||||
- Greeting
|
||||
- Short status line
|
||||
- Primary actions: New Project, Open Session, Settings
|
||||
|
||||
2. **Summary strip**
|
||||
- Small count cards for sessions, projects, and tooling state
|
||||
|
||||
3. **Open Sessions**
|
||||
- Primary section
|
||||
- Session cards with project, repository, tool type, status, and actions
|
||||
|
||||
4. **Available Projects**
|
||||
- Secondary section
|
||||
- Project cards with quick entry into the project workspace
|
||||
|
||||
5. **Session composer**
|
||||
- Optional compact create flow if it fits the page cleanly
|
||||
|
||||
## Settings Page
|
||||
|
||||
### Layout
|
||||
|
||||
Tabbed shell with one content area and two tabs:
|
||||
|
||||
- General
|
||||
- SSH Keys
|
||||
|
||||
### Tab Responsibilities
|
||||
|
||||
**General**
|
||||
- Theme
|
||||
- Git identity
|
||||
- Default editor
|
||||
|
||||
**SSH Keys**
|
||||
- List keys
|
||||
- Create key
|
||||
- Copy public key
|
||||
- Delete key
|
||||
|
||||
**Tool Types and Tool Configs**
|
||||
- Moved to Tool Workshop page (`/tool-workshop`)
|
||||
- Centralized tool management with split-pane UI
|
||||
|
||||
## Visual Direction
|
||||
|
||||
- Font: Inter for UI text
|
||||
- Code font: monospace only for technical fields
|
||||
- Palette: warm light surfaces, forest green primary, muted utility accents
|
||||
- Dark mode: charcoal surfaces with softened accents
|
||||
- Styling: editorial, structured, high-contrast hierarchy, minimal chrome
|
||||
|
||||
## Routing
|
||||
|
||||
- `/` -> Home
|
||||
- `/sessions` -> Sessions page (kept as top-level navigation)
|
||||
- `/settings` -> General tab
|
||||
- `/settings/ssh-keys` -> SSH Keys tab
|
||||
- `/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
|
||||
|
||||
- Reuse shell and existing APIs
|
||||
- Replace the dashboard page with the new home overview
|
||||
- Convert the settings layout into a shared tab shell
|
||||
- Keep changes focused to the frontend layer
|
||||
@@ -0,0 +1,35 @@
|
||||
# UI Redesign: Home + Settings
|
||||
|
||||
## Problem
|
||||
|
||||
The current authenticated UI is functional but fragmented. Sessions, tool setup, and settings are spread across top-level pages, and the home screen does not yet provide a strong overview of open sessions and available projects.
|
||||
|
||||
## Solution
|
||||
|
||||
Redesign the authenticated frontend around two primary surfaces:
|
||||
|
||||
1. **Home**: an overview of open sessions and available projects
|
||||
2. **Settings**: a tabbed settings hub with General, SSH Keys, Tool Types, and Tool Configs
|
||||
|
||||
Keep existing functionality and the Project -> Repository -> Session hierarchy intact. Reuse the current APIs and workflows.
|
||||
|
||||
## Scope
|
||||
|
||||
- Redesign the main landing page into an operational overview
|
||||
- Fold the Sessions page into the home experience
|
||||
- Convert SSH Keys, Tool Types, and Tool Configs into settings tabs
|
||||
- Update navigation and routes to match the new IA
|
||||
- Refresh visual design, typography, and spacing
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No backend behavior changes
|
||||
- No new session or project APIs
|
||||
- No changes to the project/repository/session data model
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Home shows open sessions and available projects clearly
|
||||
- Settings contains tabs for General, SSH Keys, Tool Types, Tool Configs
|
||||
- Old top-level settings-related routes redirect to the new structure
|
||||
- Visual system uses Inter and a refined warm palette
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user