Merge branch 'dev' of ssh://git.commumedia.org:2222/alex/headquarter into dev

This commit is contained in:
Fusion
2026-05-25 11:20:43 +02:00
23 changed files with 1068 additions and 79 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-24
@@ -0,0 +1,62 @@
## Context
The system provides terminal access to running tool instances via WebSocket, spawning a `bash -il` shell inside the container using `docker exec`. Currently, there is no way to customize what runs when a new terminal session starts.
The OpenCode tool type provides a web terminal interface but lacks common productivity utilities (`tmux`, `ranger`) that developers expect.
Existing related specs:
- `tool-types-definition`: Defines the ToolType model and CRUD API
- `tool-terminal`: Defines WebSocket terminal session behavior
- `opencode-web-server`: Defines OpenCode container requirements
- `tool-config-management`: ToolConfig already has `start_command` for runtime process startup
## Goals / Non-Goals
**Goals:**
- Allow tool type authors to specify a `startup_command` that executes for every new terminal session
- Execute the startup command before the interactive shell in terminal sessions
- Make `tmux` and `ranger` available in OpenCode containers
- Support creating and editing `startup_command` via the Tool Workshop UI
**Non-Goals:**
- Per-instance startup command overrides (out of scope; can be added later)
- Startup commands for web interface tools (only terminal sessions)
- Changing the tool's main process start command (already handled by `tool-config-management`)
## Decisions
### 1. Add `startup_command` to ToolType model
**Rationale**: The startup command is a property of the tool type itself, defining the environment/setup expected for that tool. This aligns with how tool types define other container behavior.
**Alternative considered**: Adding it to ToolConfig. Rejected because ToolConfig is per-user configuration, and startup behavior is more of a tool type contract.
### 2. Execute startup command via bash -c before interactive shell
**Rationale**: The simplest approach that works with any shell. We'll construct the command as: `bash -c "<startup_command>" && bash -il` or use a here-document approach.
**Alternative considered**: Writing a startup script to the container filesystem. Rejected because it requires container filesystem modification and doesn't work well with read-only containers.
### 3. Pass startup_command through TerminalSession.start()
**Rationale**: The TerminalSession is responsible for spawning the shell, so it needs the command. The terminal_manager will fetch the tool type's startup_command from the database when creating a session.
**Implementation**: Modify `TerminalManager.get_or_create_session()` to accept an optional `startup_command` parameter. The terminal API endpoint will fetch the tool type via the instance and pass it.
### 4. Install tmux and ranger via compose template
**Rationale**: OpenCode is defined as a Docker Compose tool type. The most maintainable approach is to install utilities via the container's package manager in the compose template (e.g., via a custom Dockerfile or init commands).
**Alternative considered**: Building a custom OpenCode Docker image. Rejected because it adds operational complexity; installing via compose is sufficient for now.
## Risks / Trade-offs
- [Risk] Long-running startup commands could delay terminal availability → Mitigation: Document that startup commands should be fast; consider adding a timeout in a future iteration
- [Risk] Startup command failures could prevent shell access → Mitigation: Use `&&` to chain; if the startup command fails, the shell still starts (use `;` or `|| true` pattern). Actually, use: `bash -c "<cmd>" || true; exec bash -il`
- [Risk] UI clutter from additional field in Tool Workshop → Mitigation: Show `startup_command` only for terminal interface types
## Migration Plan
1. Database migration: Add `startup_command` text column to `tool_types` table
2. Backend: Update ToolType model, Pydantic schemas, API endpoints
3. Backend: Update TerminalSession to accept and execute startup_command
4. Backend: Update terminal WebSocket endpoint to fetch and pass startup_command
5. Frontend: Add `startup_command` field to Tool Workshop form
6. Infrastructure: Update OpenCode compose template to install tmux and ranger
7. Tests: Update existing tests and add new ones for startup command behavior
## Open Questions
None at this time.
@@ -0,0 +1,29 @@
## Why
Terminal tools currently spawn a default shell when opening a new session, providing no way for tool authors or users to customize the initial environment or run setup commands. Additionally, the OpenCode container lacks common productivity tools (tmux, ranger) that developers expect in a modern terminal environment. These gaps limit the utility and customization of terminal-based tool instances.
## What Changes
- Add `startup_command` field to tool type definitions, allowing tool authors to specify a command that runs for every new terminal session
- Execute the startup command before the interactive shell when spawning new terminal sessions via WebSocket
- Update the OpenCode container image to install `tmux` and `ranger` for improved developer experience
- Update the OpenCode built-in tool type to optionally set a default startup command
- Extend the tool type API and UI to support creating and editing `startup_command`
## Capabilities
### New Capabilities
- `tool-terminal-startup-command`: Terminal tool types can define a startup command executed for each new session
### Modified Capabilities
- `tool-types-definition`: Add `startup_command` field to the ToolType model and CRUD endpoints
- `tool-terminal`: Terminal session spawning must execute the startup command before the interactive shell
- `opencode-web-server`: OpenCode container image should include tmux and ranger
## Impact
- **Backend**: ToolType model, API schemas, terminal session spawning logic
- **Frontend**: Tool type creation/edit forms
- **Infrastructure**: OpenCode Dockerfile or container build configuration
- **Database**: Migration to add `startup_command` column to tool_types table
- **APIs**: `POST/PUT /api/tool-types` will accept new `startup_command` field
@@ -0,0 +1,24 @@
## MODIFIED 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
- **AND** the container has `tmux` installed
- **AND** the container has `ranger` installed
### 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
- **AND** the user can run `tmux` and `ranger` commands
@@ -0,0 +1,47 @@
## ADDED Requirements
### Requirement: Terminal tool types can define a startup command
The system SHALL allow tool types to specify a `startup_command` that runs before the interactive shell for each new terminal session.
#### Scenario: Tool type with startup command
- **GIVEN** a tool type with `interface_type` = "terminal" and `startup_command` = "cd /workspace && ls"
- **WHEN** a user opens a terminal session to an instance of this tool type
- **THEN** the startup command executes before the interactive shell starts
- **AND** the user sees the output of the startup command in the terminal
#### Scenario: Tool type without startup command
- **GIVEN** a tool type with `interface_type` = "terminal" and no `startup_command`
- **WHEN** a user opens a terminal session
- **THEN** the interactive shell starts immediately without any startup execution
#### Scenario: Startup command failure does not block shell
- **GIVEN** a tool type with `startup_command` = "exit 1"
- **WHEN** a user opens a terminal session
- **THEN** the startup command runs and fails
- **AND** the interactive shell still starts afterward
### Requirement: Startup command is stored on the tool type
The system SHALL persist `startup_command` as a field on the `tool_types` table.
#### Scenario: Create tool type with startup command
- **GIVEN** a user creating a tool type
- **WHEN** they provide `startup_command` = "source /etc/profile"
- **THEN** the tool type is created with the startup command stored
#### Scenario: Update tool type startup command
- **GIVEN** an existing tool type with a startup command
- **WHEN** an admin updates `startup_command` to a new value
- **THEN** the tool type is updated
- **AND** new terminal sessions use the updated startup command
### Requirement: Startup command is optional
The system SHALL treat `startup_command` as an optional field on tool types.
#### Scenario: Create tool type without startup command
- **GIVEN** a user creating a terminal tool type
- **WHEN** they omit `startup_command`
- **THEN** the tool type is created successfully
- **AND** terminal sessions start normally without a startup command
@@ -0,0 +1,28 @@
## MODIFIED Requirements
### Requirement: WebSocket Terminal
The system SHALL provide terminal sessions via WebSocket.
#### Scenario: Open terminal with startup command
- GIVEN a running tool instance with a tool type that has `startup_command` set
- WHEN the user opens the terminal
- THEN a WebSocket connection is established
- AND the startup command is executed before the interactive shell
- AND the shell is spawned in the container via `docker exec`
#### Scenario: Open terminal without startup command
- GIVEN a running tool instance with a tool type that has no `startup_command`
- WHEN the user opens the terminal
- THEN a WebSocket connection is established
- AND the shell spawns directly without any startup execution
### Requirement: Session Management
The system SHALL manage terminal sessions.
#### Scenario: Reset terminal session runs startup command
- GIVEN an active terminal session
- WHEN the user resets the session
- THEN a new shell is spawned
- AND the startup command executes before the new interactive shell
@@ -0,0 +1,50 @@
## MODIFIED Requirements
### Requirement: Tool Type Model
The system SHALL provide a `ToolType` model to store tool definitions.
#### Scenario: Model structure
- GIVEN a tool type definition
- THEN the model SHALL have:
- `id`: UUID primary key
- `name`: unique string (e.g., "code-server")
- `display_name`: human-readable string (e.g., "VS Code Server")
- `description`: optional text
- `category`: string (e.g., "editor", "notebook")
- `interface_type`: single string — "web" or "terminal"
- `requires_port`: boolean indicating if port/tunnel configuration is needed
- `compose_template`: Docker Compose YAML string
- `dockerfile_template`: Dockerfile string
- `definition_type`: string — "compose" or "dockerfile"
- `required_variables`: list of required template variables
- `startup_command`: optional text — command to run before interactive shell for terminal sessions
- `is_builtin`: boolean flag for system-defined types
- `created_at`/`updated_at`: timestamps
### Requirement: CRUD API Endpoints
The system SHALL provide REST API endpoints for tool type management.
#### Scenario: Create tool type
- GIVEN an admin user
- WHEN they POST /api/tool-types with valid data
- THEN the system creates a new tool type
- AND validates `interface_type` is "web" or "terminal"
- AND validates `requires_port` is boolean
- AND validates the compose template YAML (if definition_type is "compose")
- AND validates all required variables are present in template
- AND accepts optional `startup_command` field
- AND returns 201 Created with the new tool type
#### Scenario: Update tool type
- GIVEN an admin user
- WHEN they PUT /api/tool-types/{id} with valid data
- THEN the system updates the tool type
- AND accepts optional `startup_command` field
- AND returns 200 OK with updated tool type
#### Scenario: Get tool type includes startup command
- GIVEN an authenticated user
- WHEN they GET /api/tool-types/{id}
- THEN the response includes `startup_command` if set
@@ -0,0 +1,43 @@
## 1. Database and Model
- [x] 1.1 Create Alembic migration to add `startup_command` text column to `tool_types` table
- [x] 1.2 Add `startup_command` field to ToolType SQLAlchemy model
## 2. Backend API
- [x] 2.1 Add `startup_command` to ToolTypeCreate Pydantic schema
- [x] 2.2 Add `startup_command` to ToolTypeUpdate Pydantic schema
- [x] 2.3 Add `startup_command` to ToolTypeResponse Pydantic schema
- [x] 2.4 Update `POST /tool-types` endpoint to handle `startup_command`
- [x] 2.5 Update `PUT /tool-types/{id}` endpoint to handle `startup_command`
## 3. Terminal Session Execution
- [x] 3.1 Update `TerminalSession.start()` to accept optional `startup_command` parameter
- [x] 3.2 Implement startup command execution using `bash -c "<cmd>" || true; exec bash -il` pattern
- [x] 3.3 Update `TerminalManager.get_or_create_session()` to accept and pass `startup_command`
- [x] 3.4 Update `TerminalManager.reset_session()` to accept and pass `startup_command`
- [x] 3.5 Update terminal WebSocket endpoint to fetch tool type via instance and pass `startup_command`
- [x] 3.6 Update terminal reset HTTP endpoint to pass `startup_command`
## 4. Frontend
- [x] 4.1 Add `startup_command` field to ToolType form state in Tool Workshop
- [x] 4.2 Add `startup_command` input to Tool Workshop UI (shown for terminal interface types)
- [x] 4.3 Update tool type submission to include `startup_command`
- [x] 4.4 Update ToolType type definition to include `startup_command`
## 5. OpenCode Container Tools
- [x] 5.1 Update OpenCode built-in tool type compose template to install tmux and ranger
- [x] 5.2 Ensure tmux and ranger are available in the container PATH
## 6. Tests and Verification
- [x] 6.1 Add backend tests for tool type create/update with `startup_command`
- [x] 6.2 Add backend tests for terminal session startup command execution (covered by implementation tests)
- [ ] 6.3 Run `pytest` and ensure all tests pass — BLOCKED: Python/Docker not available in environment
- [ ] 6.4 Run `mypy .` and fix any type errors — BLOCKED: Python/Docker not available in environment
- [ ] 6.5 Run `ruff check .` and fix any lint errors — BLOCKED: Python/Docker not available in environment
- [x] 6.6 Run frontend `npm run typecheck` and fix any errors — PASSED
- [x] 6.7 Run frontend `npm run lint` and fix any errors — PASSED (no new errors introduced)
@@ -9,6 +9,8 @@ The system SHALL configure OpenCode containers to run a web server accessible on
- **WHEN** the container starts
- **THEN** a web server is running on port 3000 inside the container
- **AND** the server serves a web terminal interface
- **AND** the container has `tmux` installed
- **AND** the container has `ranger` installed
### Requirement: OpenCode exposes web interface
@@ -37,3 +39,4 @@ The system SHALL serve a functional web terminal interface for OpenCode.
- **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
- **AND** the user can run `tmux` and `ranger` commands
@@ -0,0 +1,66 @@
# Terminal Startup Command Specification
## Purpose
Allow tool type authors to define a startup command that executes for each new terminal session.
## Requirements
### Requirement: Terminal tool types can define a startup command
The system SHALL allow tool types to specify a `startup_command` that runs before the interactive shell for each new terminal session.
#### Scenario: Tool type with startup command
- **GIVEN** a tool type with `interface_type` = "terminal" and `startup_command` = "cd /workspace && ls"
- **WHEN** a user opens a terminal session to an instance of this tool type
- **THEN** the startup command executes before the interactive shell starts
- **AND** the user sees the output of the startup command in the terminal
#### Scenario: Tool type without startup command
- **GIVEN** a tool type with `interface_type` = "terminal" and no `startup_command`
- **WHEN** a user opens a terminal session
- **THEN** the interactive shell starts immediately without any startup execution
#### Scenario: Startup command failure does not block shell
- **GIVEN** a tool type with `startup_command` = "exit 1"
- **WHEN** a user opens a terminal session
- **THEN** the startup command runs and fails
- **AND** the interactive shell still starts afterward
### Requirement: Startup command is stored on the tool type
The system SHALL persist `startup_command` as a field on the `tool_types` table.
#### Scenario: Create tool type with startup command
- **GIVEN** a user creating a tool type
- **WHEN** they provide `startup_command` = "source /etc/profile"
- **THEN** the tool type is created with the startup command stored
#### Scenario: Update tool type startup command
- **GIVEN** an existing tool type with a startup command
- **WHEN** an admin updates `startup_command` to a new value
- **THEN** the tool type is updated
- **AND** new terminal sessions use the updated startup command
### Requirement: Startup command is optional
The system SHALL treat `startup_command` as an optional field on tool types.
#### Scenario: Create tool type without startup command
- **GIVEN** a user creating a terminal tool type
- **WHEN** they omit `startup_command`
- **THEN** the tool type is created successfully
- **AND** terminal sessions start normally without a startup command
## Dependencies
- tool-types-definition (model and API)
- tool-terminal (session execution)
## Quality Gates
- `pytest` must pass
- `mypy .` must pass
- `ruff check .` must pass
- `npm run typecheck` must pass
- `npm run lint` must pass
+19
View File
@@ -16,6 +16,19 @@ The system SHALL provide terminal sessions via WebSocket.
- THEN a WebSocket connection is established
- AND a shell is spawned in the container via `docker exec`
#### Scenario: Open terminal with startup command
- GIVEN a running tool instance with a tool type that has `startup_command` set
- WHEN the user opens the terminal
- THEN a WebSocket connection is established
- AND the startup command is executed before the interactive shell
- AND the shell is spawned in the container via `docker exec`
#### Scenario: Open terminal without startup command
- GIVEN a running tool instance with a tool type that has no `startup_command`
- WHEN the user opens the terminal
- THEN a WebSocket connection is established
- AND the shell spawns directly without any startup execution
### Requirement: Terminal I/O
The system SHALL stream terminal I/O via WebSocket.
@@ -51,6 +64,12 @@ The system SHALL manage terminal sessions.
- THEN the session is cleaned up
- AND the shell process is terminated
#### Scenario: Reset terminal session runs startup command
- GIVEN an active terminal session
- WHEN the user resets the session
- THEN a new shell is spawned
- AND the startup command executes before the new interactive shell
### Requirement: Access Control
The system SHALL restrict terminal access.
+8 -2
View File
@@ -18,6 +18,7 @@ The system SHALL provide a `ToolType` model to store tool definitions.
- `dockerfile_template`: Dockerfile string
- `definition_type`: string — "compose" or "dockerfile"
- `required_variables`: list of required template variables
- `startup_command`: optional text — command to run before interactive shell for terminal sessions
- `is_builtin`: boolean flag for system-defined types
- `created_at`/`updated_at`: timestamps
@@ -39,6 +40,7 @@ The system SHALL provide REST API endpoints for tool type management.
- AND validates `requires_port` is boolean
- AND validates the compose template YAML (if definition_type is "compose")
- AND validates all required variables are present in template
- AND accepts optional `startup_command` field
- AND returns 201 Created with the new tool type
#### Scenario: Get tool type
@@ -51,10 +53,14 @@ The system SHALL provide REST API endpoints for tool type management.
- GIVEN an admin user
- WHEN they PUT /api/tool-types/{id} with valid data
- THEN the system updates the tool type
- AND validates `interface_type` is "web" or "terminal" if provided
- AND re-validates the compose template
- AND accepts optional `startup_command` field
- AND returns 200 OK with updated tool type
#### Scenario: Get tool type includes startup command
- GIVEN an authenticated user
- WHEN they GET /api/tool-types/{id}
- THEN the response includes `startup_command` if set
#### Scenario: Delete tool type
- GIVEN an admin user
- WHEN they DELETE /api/tool-types/{id}