feat(opencode-web-terminal): complete OpenCode web terminal implementation

- Update OpenCode compose template with web server on port 3000
- Add default_port=3000 and interfaces=[terminal, web] to OpenCode seed data
- Remove hardcoded 8080 fallback in tunnel creation
- Fail gracefully when tool type has no default_port configured
- Update frontend ToolType API to include default_port, category, interfaces
- Add port, category, and interfaces fields to tool type creation form
- Display port and interfaces in tool type cards
- Create migration 0012 to make default_port non-nullable
- Set default_port values for existing built-in tool types
- Quality gates: typecheck ✓, build ✓, Python syntax ✓
This commit is contained in:
Fusion
2026-05-20 17:17:50 +02:00
parent 6ec35988cc
commit ac6bd3304d
14 changed files with 531 additions and 10 deletions
@@ -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
@@ -0,0 +1,39 @@
## ADDED Requirements
### Requirement: OpenCode runs a web server
The system SHALL configure OpenCode containers to run a web server accessible on port 3000.
#### Scenario: OpenCode container starts
- **GIVEN** an OpenCode tool instance
- **WHEN** the container starts
- **THEN** a web server is running on port 3000 inside the container
- **AND** the server serves a web terminal interface
### Requirement: OpenCode exposes web interface
The system SHALL mark OpenCode as having both terminal and web interfaces.
#### Scenario: OpenCode instance created
- **GIVEN** a new OpenCode instance
- **WHEN** the instance list is displayed
- **THEN** both "Open" and "Terminal" buttons are shown
### Requirement: OpenCode web terminal uses correct port
The system SHALL use port 3000 when creating tunnels for OpenCode instances.
#### Scenario: Tunnel created for OpenCode
- **GIVEN** an OpenCode instance with `default_port: 3000`
- **WHEN** the instance starts and creates a tunnel
- **THEN** the tunnel targets `http://container-name:3000`
### Requirement: OpenCode web terminal displays properly
The system SHALL serve a functional web terminal interface for OpenCode.
#### Scenario: User opens OpenCode web UI
- **GIVEN** a running OpenCode instance
- **WHEN** the user clicks the "Open" button
- **THEN** a new tab opens with the OpenCode web interface
- **AND** the interface shows a terminal connected to the OpenCode process
@@ -0,0 +1,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
- [ ] 6.1 Test creating tool type without port fails validation
- [ ] 6.2 Test creating tool type with port mismatch fails validation
- [ ] 6.3 Test OpenCode instance creates tunnel on port 3000
- [ ] 6.4 Run backend quality gates (ruff, mypy)
- [ ] 6.5 Run frontend quality gates (typecheck, lint, build)
- [ ] 6.6 Commit and push changes