# codespace 可运行后端骨架设计 日期:2026-07-02 ## 目标 在当前目录 `/Users/taochen/llm/codespace` 生成一个 Go 后端项目骨架。第一版目标是可运行、可测试、边界清晰的 Workspace Service,而不是完整 Web IDE。 核心能力: - 每个 workspace 对应本地文件系统中的一个目录。 - 文件读写统一通过 `FileSystem` 接口,第一版实现 `LocalFS`。 - OpenCode 以本机进程方式启动,工作目录 `cwd` 指向 workspace root。 - HTTP handler 不直接操作文件系统或进程,必须经过 service 层。 - 预留 watcher、Git、LSP、AI Agent 的后续扩展点。 - 不依赖 Docker。 ## 非目标 第一版不实现: - 用户认证和权限系统。 - Docker / 容器隔离。 - Git 集成。 - LSP。 - AI Agent 编排。 - Web 前端。 - 完整 watcher / WebSocket 文件事件推送。 这些能力以后接入,但不能污染第一版的核心边界。 ## 目录结构 ```text . ├── cmd/ │ └── server/ │ └── main.go ├── configs/ │ └── config.yaml ├── internal/ │ ├── api/ │ │ ├── router.go │ │ ├── workspace_handler.go │ │ ├── file_handler.go │ │ └── process_handler.go │ ├── workspace/ │ │ ├── manager.go │ │ ├── workspace.go │ │ └── path.go │ ├── fs/ │ │ ├── filesystem.go │ │ ├── localfs.go │ │ └── fileinfo.go │ ├── process/ │ │ ├── manager.go │ │ ├── session.go │ │ └── opencode.go │ ├── watcher/ │ │ └── watcher.go │ ├── service/ │ │ ├── workspace_service.go │ │ ├── file_service.go │ │ └── process_service.go │ ├── model/ │ │ ├── workspace.go │ │ ├── file.go │ │ └── process.go │ └── util/ │ ├── path.go │ └── errors.go ├── pkg/ │ ├── config/ │ ├── logger/ │ └── response/ ├── web/ ├── docs/ ├── Makefile ├── go.mod └── README.md ``` ## 架构 ```text HTTP request ↓ internal/api handler ↓ internal/service ↓ workspace.Manager / fs.FileSystem / process.Manager ↓ local workspace directory + opencode process ``` 边界规则: 1. `api` 只负责 HTTP 编解码、路由参数、状态码。 2. `service` 负责业务编排和校验。 3. `workspace` 负责 workspace 元数据和 root 目录管理。 4. `fs` 负责 workspace 内文件操作。 5. `process` 负责进程生命周期。 6. `watcher` 第一版只保留接口/占位,不接入运行链路。 ## 核心数据结构 ### Workspace ```go type Workspace struct { ID string `json:"id"` Root string `json:"root"` } ``` 含义: - `ID` 是外部 API 使用的 workspace 标识。 - `Root` 是服务端本地绝对路径。 - 第一版不引入数据库,workspace 是否存在由目录和 manager 判断。 ### FileInfo ```go type FileInfo struct { Name string `json:"name"` Path string `json:"path"` IsDir bool `json:"isDir"` Size int64 `json:"size"` ModTime time.Time `json:"modTime"` } ``` `Path` 始终是 workspace 内相对路径,不向 API 暴露宿主机绝对路径。 ### ProcessStatus ```go type ProcessStatus struct { WorkspaceID string `json:"workspaceId"` Running bool `json:"running"` PID int `json:"pid,omitempty"` } ``` ## Workspace Manager 职责: - 创建 workspace 目录。 - 查询 workspace。 - 删除 workspace 目录。 - 生成 workspace root 路径。 接口: ```go type Manager interface { Create(id string) (*Workspace, error) Get(id string) (*Workspace, error) Delete(id string) error } ``` 路径策略: - 配置项 `workspaceRoot` 默认使用 `./workspaces`。 - workspace root 为 `/`。 - workspace ID 第一版限制为安全路径片段,只允许字母、数字、`-`、`_`、`.`。 ## FileSystem 所有文件操作必须走接口: ```go type FileSystem interface { List(path string) ([]FileInfo, error) Read(path string) ([]byte, error) Write(path string, data []byte) error Mkdir(path string) error Remove(path string) error Rename(oldPath, newPath string) error Stat(path string) (*FileInfo, error) } ``` 第一版实现 `LocalFS`: - `LocalFS` 绑定一个 workspace root。 - 所有传入路径都是 workspace 内相对路径。 - 解析路径时必须阻止 `../` 逃逸。 - API 不返回宿主机绝对路径。 ## Process Manager 职责:按 workspace 管理 OpenCode 进程。 接口: ```go type Manager interface { Start(workspaceID string, workspaceRoot string) error Stop(workspaceID string) error Restart(workspaceID string, workspaceRoot string) error Status(workspaceID string) Status } ``` 第一版行为: - 命令默认是 `opencode`,通过配置项 `opencodeCommand` 可改。 - `cmd.Dir = workspace.Root`。 - `HOME` 默认设置为 workspace root,避免不同 workspace 共享 CLI 状态。 - 同一个 workspace 已有运行进程时,再次 start 返回已运行错误,不重复启动。 - stop 使用进程 kill;优雅退出可以后续补。 ## Service 层 ### WorkspaceService - `CreateWorkspace(id string)` - `GetWorkspace(id string)` - `DeleteWorkspace(id string)` 删除 workspace 前,先停止对应进程,避免目录被运行进程占用。 ### FileService - `List(workspaceID, path string)` - `Read(workspaceID, path string)` - `Write(workspaceID, path string, data []byte)` - `Mkdir(workspaceID, path string)` - `Remove(workspaceID, path string)` - `Rename(workspaceID, oldPath, newPath string)` - `Stat(workspaceID, path string)` 每次通过 workspace manager 取 workspace,再构造绑定该 root 的 `LocalFS`。 ### ProcessService - `Start(workspaceID string)` - `Stop(workspaceID string)` - `Restart(workspaceID string)` - `Status(workspaceID string)` ProcessService 不自己拼路径,只通过 workspace manager 取 root。 ## HTTP API 基础路径:`/api` ### 健康检查 ```text GET /healthz ``` 返回: ```json {"status":"ok"} ``` ### Workspace ```text POST /api/workspaces GET /api/workspaces/{id} DELETE /api/workspaces/{id} ``` 创建请求: ```json {"id":"user1"} ``` ### Files ```text GET /api/workspaces/{id}/files?path=. GET /api/workspaces/{id}/files/read?path=main.go PUT /api/workspaces/{id}/files/write POST /api/workspaces/{id}/files/mkdir DELETE /api/workspaces/{id}/files?path=main.go POST /api/workspaces/{id}/files/rename ``` 写文件请求: ```json {"path":"main.go","content":"package main\n"} ``` 重命名请求: ```json {"oldPath":"old.go","newPath":"new.go"} ``` ### Process ```text POST /api/workspaces/{id}/process/start POST /api/workspaces/{id}/process/stop POST /api/workspaces/{id}/process/restart GET /api/workspaces/{id}/process/status ``` ## 配置 `configs/config.yaml`: ```yaml server: addr: ":8080" workspace: root: "./workspaces" process: opencodeCommand: "opencode" ``` 配置加载规则: 1. 先加载默认值。 2. 如果配置文件存在,覆盖默认值。 3. 环境变量可覆盖: - `CODESPACE_ADDR` - `CODESPACE_WORKSPACE_ROOT` - `CODESPACE_OPENCODE_COMMAND` ## 错误处理 统一 JSON 响应: ```json { "error": { "code": "bad_request", "message": "invalid workspace id" } } ``` 常见映射: - 参数错误:400 - workspace 不存在:404 - 文件不存在:404 - 进程已运行:409 - 内部错误:500 第一版只做简单错误类型,不引入复杂错误框架。 ## 测试策略 最小测试: - workspace ID 校验和路径生成。 - LocalFS 路径逃逸防护。 - LocalFS 基础读写/列表。 - Process manager 状态逻辑使用可配置命令测试,不依赖真实 OpenCode。 - HTTP handler 可做轻量测试,至少覆盖 health 和 workspace 创建。 验收命令: ```sh go test ./... go run ./cmd/server ``` ## 兼容性与风险 - 当前目录是项目根目录,不再额外创建 `codespace/` 子目录。 - 初始化会创建标准 Go 项目文件;如果目标文件已存在,实施阶段必须先检查,避免覆盖用户已有内容。 - 不改变公共 API,因为这是初始版本。 - 最大安全风险是路径逃逸,必须在 `LocalFS` 和 workspace ID 校验中处理。 - 最大复杂度风险是过早引入 watcher/Git/LSP/Agent;第一版明确不实现。 ## 骨架完成标准 完成后应满足: - 当前目录包含完整 Go module。 - `go test ./...` 通过。 - `go run ./cmd/server` 能启动 HTTP 服务。 - 可以创建 workspace、读写 workspace 内文件、查询/启动/停止进程。 - Handler 不直接调用 `os.*` 或 `exec.*`。 - 文件路径不能逃逸 workspace root。