This repository has been archived on 2026-07-17 . You can view files and clone it. You cannot open issues or pull requests or push a commit.
3d34de96bfebb393a74257fa8344c959cc975f34
Same shape as the process package refactor:
- LocalManager.mu (sync.Mutex) -> sessionsMu (sync.RWMutex).
Read paths (Status, Subscribe, Stdin, ExitStatus, Resize) take
RLock; write paths (Start, Stop, Restart, List) take Lock.
- LocalManager.exited: was a hand-rolled map[workspaceID]map[shellID]struct{}
guarded by exitedMu; now a sync.Map keyed by shellID only (UUID
is globally unique, no need for the nested map). Helpers
IsExited / MarkAsExited / ClearExited.
- shellOrder stays a plain map; read+written under sessionsMu.
- waitExit remains the sole caller of MarkAsExited; Start /
Stop / Restart call ClearExited.
- New TestShellIsExitedHelpers covers the helper semantics.
go test -race -count=2 ./... clean. Same caveat as the process
package: at this app's concurrency level, neither sync.RWMutex nor
sync.Map measurably beats the previous pair — the change is mostly
stylistic (one fewer lock, no nested maps, more idiomatic Go).
Conversation: 019f3673-d2d5-78f0-a7a9-5e3e91b65933
codespace
A Go backend skeleton for managing local workspaces and file operations with process orchestration.
V1 scope
- Workspace creation, listing, lookup, and deletion backed by local directories.
- File read/write/list/mkdir/remove/rename/stat through a
FileSysteminterface with path-escape protection. - Workspace root deletion protection — cannot remove the workspace root directory through the file API.
- Configurable per-file write size limit — oversized writes are rejected early before hitting disk.
- OpenCode process start/stop/restart/status per workspace (launched with
opencode acpby default; raw stdio is still piped through the process WebSocket for backward compatibility). - ACP (Agent Client Protocol) client — minimal JSON-RPC over NDJSON against the running opencode process. Exposes
initialize/session/new/session/prompt/session/cancel. Servesfs/read_text_fileandfs/write_text_filefrom the workspace filesystem. The web UI surfaces this as a chat panel alongside the terminal. - HTTP API using Gin for routing with JSON responses.
- Explicit
http.Servercomposition with configurable timeouts and max header bytes. - Structured logging using Go standard library
log/slogwith JSON/text format and level control.
Safety
- File paths are workspace-relative; absolute paths are rejected.
..escape attempts are rejected before filesystem operations.- Deleting the workspace root through the file API is explicitly blocked.
- File content is never logged; structured logging only includes metadata.
- Workspace root paths are not exposed in API responses or logs.
Run
make run
The server listens on :8080 by default.
Configuration
configs/config.yaml:
server:
addr: ":8080"
readTimeout: "15s"
writeTimeout: "15s"
idleTimeout: "60s"
maxHeaderBytes: 1048576
workspace:
root: "./workspaces"
process:
opencodeCommand: "opencode"
args:
- "acp"
shell:
command: "bash"
args:
- "-i"
file:
maxWriteBytes: 1048576
gin:
mode: "release"
log:
level: "info"
format: "json"
Environment overrides
| Variable | Description | Default |
|---|---|---|
CODESPACE_ADDR |
Listen address | :8080 |
CODESPACE_WORKSPACE_ROOT |
Workspace storage root | ./workspaces |
CODESPACE_OPENCODE_COMMAND |
OpenCode binary path | opencode |
CODESPACE_OPENCODE_ARGS |
Comma-separated extra args passed to OpenCode (sets process.args) |
acp |
CODESPACE_SHELL_COMMAND |
Shell binary path | bash |
CODESPACE_SHELL_ARGS |
Comma-separated shell arguments | -i |
CODESPACE_FILE_MAX_WRITE_BYTES |
Max bytes per file write | 1048576 |
CODESPACE_GIN_MODE |
Gin mode (debug, release, test) |
release |
CODESPACE_READ_TIMEOUT |
HTTP read timeout | 15s |
CODESPACE_WRITE_TIMEOUT |
HTTP write timeout | 15s |
CODESPACE_IDLE_TIMEOUT |
HTTP idle timeout | 60s |
CODESPACE_MAX_HEADER_BYTES |
Max HTTP header bytes | 1048576 |
CODESPACE_LOG_LEVEL |
Log level (debug, info, warn, error) |
info |
CODESPACE_LOG_FORMAT |
Log format (json, text) |
json |
Logging
codespace uses the Go standard library log/slog for application logs.
All runtime logging uses slog; fmt, log.Printf, and the Gin default logger are not used.
API
Workspaces
| Method | Path | Description |
|---|---|---|
GET |
/api/workspaces |
List all workspaces |
POST |
/api/workspaces |
Create a workspace ({"id": "name"}) |
GET |
/api/workspaces/:id |
Get workspace by ID |
DELETE |
/api/workspaces/:id |
Delete workspace (stops running process first) |
Files
| Method | Path | Description |
|---|---|---|
GET |
/api/workspaces/:id/files?path=... |
List directory entries |
GET |
/api/workspaces/:id/files/read?path=... |
Read file content |
GET |
/api/workspaces/:id/files/stat?path=... |
Get file metadata (name, path, isDir, size, modTime) |
PUT |
/api/workspaces/:id/files/write |
Write file ({"path": "...", "content": "..."}) |
POST |
/api/workspaces/:id/files/mkdir |
Create directory ({"path": "..."}) |
DELETE |
/api/workspaces/:id/files?path=... |
Remove file or directory recursively |
POST |
/api/workspaces/:id/files/rename |
Rename file or directory ({"oldPath": "...", "newPath": "..."}) |
Process
| Method | Path | Description |
|---|---|---|
POST |
/api/workspaces/:id/process/start |
Start OpenCode process for workspace |
POST |
/api/workspaces/:id/process/stop |
Stop OpenCode process |
POST |
/api/workspaces/:id/process/restart |
Restart OpenCode process |
GET |
/api/workspaces/:id/process/status |
Get process running status and PID |
GET |
/api/workspaces/:id/process/ws |
WebSocket: stream process stdout/stderr, send stdin (text frames, raw bytes) |
Shell
| Method | Path | Description |
|---|---|---|
POST |
/api/workspaces/:id/shell/start |
Start interactive shell for workspace |
POST |
/api/workspaces/:id/shell/stop |
Stop interactive shell |
POST |
/api/workspaces/:id/shell/restart |
Restart interactive shell |
GET |
/api/workspaces/:id/shell/status |
Get shell running status and PID |
GET |
/api/workspaces/:id/shell/ws |
WebSocket: stream shell stdout/stderr, send stdin (text frames, raw bytes) |
ACP (Agent Client Protocol)
Sits on top of the OpenCode process started above. The ACP service starts the process on the first prompt if it isn't already running, performs the initialize + session/new handshake, and accumulates the agent's responses until the session/prompt reply arrives.
| Method | Path | Description |
|---|---|---|
GET |
/api/workspaces/:id/acp/status |
Observational status: {workspaceId, ready, sessionId, running, pid, error}. Does not start the process. |
GET |
/api/workspaces/:id/acp/history |
Accumulated conversation: {sessionId, messages: [{role, text, time}]}, oldest first. In-memory only — cleared when the process restarts. |
POST |
/api/workspaces/:id/acp/prompt |
Body {"content": "..."}. Response {"sessionId, stopReason, text}. Starts the process + session on demand. Synchronous (waits up to 5 min). |
POST |
/api/workspaces/:id/acp/cancel |
Best-effort interrupt of an in-flight prompt. |
terminal/* and other unsupported agent-initiated requests return JSON-RPC Method not found (-32601).
Health
| Method | Path | Description |
|---|---|---|
GET |
/healthz |
Server health check |
Examples
Health check
curl -s http://localhost:8080/healthz
Create a workspace
curl -s -X POST http://localhost:8080/api/workspaces \
-H 'Content-Type: application/json' \
-d '{"id":"user1"}'
List workspaces
curl -s http://localhost:8080/api/workspaces
Write a file
curl -s -X PUT http://localhost:8080/api/workspaces/user1/files/write \
-H 'Content-Type: application/json' \
-d '{"path":"main.go","content":"package main\n"}'
Read a file
curl -s 'http://localhost:8080/api/workspaces/user1/files/read?path=main.go'
Get file stat
curl -s 'http://localhost:8080/api/workspaces/user1/files/stat?path=main.go'
Send a prompt to the agent
curl -s -X POST http://localhost:8080/api/workspaces/user1/acp/prompt \
-H 'Content-Type: application/json' \
-d '{"content":"list the files in this workspace"}'
Test
make test
Languages
Go
52.7%
TypeScript
46%
CSS
1%
JavaScript
0.2%