chore(repo): reinitialize repository

This commit is contained in:
2026-03-18 11:29:54 +08:00
commit 24871e213a
288 changed files with 44369 additions and 0 deletions
@@ -0,0 +1,17 @@
package workspaceruntime
import (
"fmt"
"strings"
)
func commandError(action, output string, err error) error {
if err == nil {
return nil
}
output = strings.TrimSpace(output)
if output == "" {
return fmt.Errorf("%s: %w", action, err)
}
return fmt.Errorf("%s: %s: %w", action, output, err)
}
@@ -0,0 +1,325 @@
package workspaceruntime
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"inbox/internal/app/lanegit"
"inbox/internal/app/runtimecodex"
"inbox/internal/domain/lane"
"inbox/internal/domain/workspace"
)
const (
runtimeBaseImage = "localhost/ai-workflow-agent-runner:local"
runnerContainerPort = "31417/tcp"
runnerContainerPortNum = "31417"
)
type podmanInspect struct {
ImageName string `json:"ImageName"`
Path string `json:"Path"`
Args []string `json:"Args"`
Config struct {
Image string `json:"Image"`
User string `json:"User"`
Env []string `json:"Env"`
WorkingDir string `json:"WorkingDir"`
} `json:"Config"`
State struct {
Running bool `json:"Running"`
Status string `json:"Status"`
} `json:"State"`
NetworkSettings struct {
Ports map[string][]struct {
HostIP string `json:"HostIp"`
HostPort string `json:"HostPort"`
} `json:"Ports"`
} `json:"NetworkSettings"`
Mounts []struct {
Source string `json:"Source"`
Destination string `json:"Destination"`
} `json:"Mounts"`
}
type containerRuntime struct {
projectRoot string
serverPort int
runner lanegit.Runner
probe *endpointProbe
}
const laneWorkerContainerPath = "/usr/local/bin/lane-worker"
const inboxContainerPath = "/usr/local/bin/inbox"
func (r *containerRuntime) ensureRunnerImage(ctx context.Context) error {
out, err := r.runner.Run(ctx, r.projectRoot, nil, "podman", "image", "exists", runtimeBaseImage)
if err != nil {
if strings.TrimSpace(out) == "" {
return fmt.Errorf("runtime base image is missing: %s", runtimeBaseImage)
}
return commandError("runtime base image is missing: "+runtimeBaseImage, out, err)
}
return nil
}
func (r *containerRuntime) ensureLaneContainer(ctx context.Context, ws workspace.Workspace, item lane.Record, workerBinary, inboxBinary, workerCodexDir string) (string, error) {
codexFingerprint, err := runtimeCodexFingerprint(workerCodexDir)
if err != nil {
return "", err
}
info, found, err := r.inspect(ctx, item.ContainerName)
if err != nil {
return "", err
}
if found && r.laneDrifted(info, ws, item, workerBinary, inboxBinary, codexFingerprint) {
if err := r.stopAndRemoveContainer(ctx, item.ContainerName); err != nil {
return "", err
}
found = false
}
if !found {
args := []string{
"create",
"--name", item.ContainerName,
"--user", "root",
"--entrypoint", laneWorkerContainerPath,
"-p", "127.0.0.1::" + runnerContainerPortNum,
"-e", "INBOX_WORKSPACE=/workspace",
"-e", "INBOX_WORKSPACE_ID=" + ws.ID,
"-e", "INBOX_LANE_ID=" + item.ID,
"-e", "INBOX_API_URL=http://host.containers.internal:" + strconv.Itoa(r.serverPort),
"-e", "INBOX_RUNTIME_AGENT_ID=" + item.ContainerName,
"-e", "HOME=" + runtimecodex.ContainerUserHomeDir(),
"-v", filepath.Join(item.WorktreePath) + ":/workspace:z",
"-v", workerBinary + ":" + laneWorkerContainerPath + ":z,ro",
"-v", inboxBinary + ":" + inboxContainerPath + ":z,ro",
"-w", "/workspace",
}
if codexFingerprint != "" {
args = append(args, "-e", "INBOX_RUNTIME_CODEX_SHA="+codexFingerprint)
}
args = append(args, runtimeBaseImage)
out, err := r.runner.Run(ctx, r.projectRoot, nil, "podman", args...)
if err != nil {
return "", commandError("create lane container "+item.ContainerName, out, err)
}
if err := r.copyRuntimeCodex(ctx, item.ContainerName, workerCodexDir); err != nil {
_ = r.stopAndRemoveContainer(ctx, item.ContainerName)
return "", err
}
info, found, err = r.inspect(ctx, item.ContainerName)
if err != nil {
return "", err
}
if !found {
return "", fmt.Errorf("container %s was not created", item.ContainerName)
}
}
if !info.State.Running {
out, err := r.runner.Run(ctx, r.projectRoot, nil, "podman", "start", item.ContainerName)
if err != nil {
return "", commandError("start lane container "+item.ContainerName, out, err)
}
}
endpoint, err := r.endpoint(ctx, item.ContainerName)
if err != nil {
return "", err
}
if err := r.probe.wait(endpoint); err != nil {
return endpoint, err
}
return endpoint, nil
}
func (r *containerRuntime) inspect(ctx context.Context, name string) (podmanInspect, bool, error) {
if strings.TrimSpace(name) == "" {
return podmanInspect{}, false, nil
}
out, err := r.runner.Run(ctx, r.projectRoot, nil, "podman", "inspect", name)
if err != nil {
if strings.Contains(out, "no such object") || strings.Contains(out, "no container with name or ID") {
return podmanInspect{}, false, nil
}
return podmanInspect{}, false, fmt.Errorf("inspect container %s: %w", name, err)
}
var items []podmanInspect
if err := json.Unmarshal([]byte(out), &items); err != nil {
return podmanInspect{}, false, fmt.Errorf("decode podman inspect %s: %w", name, err)
}
if len(items) == 0 {
return podmanInspect{}, false, nil
}
return items[0], true, nil
}
func (r *containerRuntime) laneDrifted(info podmanInspect, ws workspace.Workspace, item lane.Record, workerBinary, inboxBinary, codexFingerprint string) bool {
if info.ImageName != runtimeBaseImage && info.Config.Image != runtimeBaseImage {
return true
}
if strings.TrimSpace(info.Config.User) != "root" {
return true
}
if !hasPublishedPort(info, runnerContainerPort) {
return true
}
if info.Config.WorkingDir != "/workspace" {
return true
}
if filepath.Clean(strings.TrimSpace(info.Path)) != laneWorkerContainerPath {
return true
}
mounts := make(map[string]string, len(info.Mounts))
for _, mount := range info.Mounts {
mounts[filepath.Clean(mount.Destination)] = filepath.Clean(mount.Source)
}
if _, ok := mounts[filepath.Clean(runtimecodex.ContainerCodexDir())]; ok {
return true
}
expectedMounts := map[string]string{
"/workspace": filepath.Clean(item.WorktreePath),
laneWorkerContainerPath: filepath.Clean(workerBinary),
inboxContainerPath: filepath.Clean(inboxBinary),
}
for destination, source := range expectedMounts {
if filepath.Clean(mounts[destination]) != filepath.Clean(source) {
return true
}
}
envs := make(map[string]string)
for _, item := range info.Config.Env {
key, value, ok := strings.Cut(item, "=")
if !ok {
continue
}
envs[key] = value
}
expectedEnv := map[string]string{
"INBOX_WORKSPACE": "/workspace",
"INBOX_WORKSPACE_ID": ws.ID,
"INBOX_LANE_ID": item.ID,
"INBOX_RUNTIME_AGENT_ID": item.ContainerName,
"INBOX_API_URL": "http://host.containers.internal:" + strconv.Itoa(r.serverPort),
}
if codexFingerprint != "" {
expectedEnv["INBOX_RUNTIME_CODEX_SHA"] = codexFingerprint
} else if _, ok := envs["INBOX_RUNTIME_CODEX_SHA"]; ok {
return true
}
for key, value := range expectedEnv {
if envs[key] != value {
return true
}
}
return false
}
func (r *containerRuntime) copyRuntimeCodex(ctx context.Context, containerName, workerCodexDir string) error {
if strings.TrimSpace(workerCodexDir) == "" {
return nil
}
out, err := r.runner.Run(ctx, r.projectRoot, nil, "podman", "cp", workerCodexDir, containerName+":"+runtimecodex.ContainerUserHomeDir())
if err != nil {
return commandError("copy runtime codex into "+containerName, out, err)
}
return nil
}
func runtimeCodexFingerprint(workerCodexDir string) (string, error) {
if strings.TrimSpace(workerCodexDir) == "" {
return "", nil
}
names := make([]string, 0, 8)
err := filepath.Walk(workerCodexDir, func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
if info.IsDir() {
return nil
}
rel, err := filepath.Rel(workerCodexDir, path)
if err != nil {
return err
}
names = append(names, filepath.ToSlash(rel))
return nil
})
if err != nil {
return "", fmt.Errorf("walk runtime codex dir %s: %w", workerCodexDir, err)
}
sort.Strings(names)
sum := sha256.New()
for _, name := range names {
body, err := os.ReadFile(filepath.Join(workerCodexDir, filepath.FromSlash(name)))
if err != nil {
return "", fmt.Errorf("read runtime codex file %s: %w", filepath.Join(workerCodexDir, filepath.FromSlash(name)), err)
}
_, _ = sum.Write([]byte(name))
_, _ = sum.Write([]byte{0})
_, _ = sum.Write(body)
_, _ = sum.Write([]byte{0})
}
return fmt.Sprintf("%x", sum.Sum(nil)), nil
}
func hasPublishedPort(info podmanInspect, containerPort string) bool {
bindings, ok := info.NetworkSettings.Ports[containerPort]
if !ok || len(bindings) == 0 {
return false
}
for _, binding := range bindings {
if strings.TrimSpace(binding.HostPort) != "" {
return true
}
}
return false
}
func (r *containerRuntime) stopContainer(ctx context.Context, containerName string) error {
info, found, err := r.inspect(ctx, containerName)
if err != nil {
return err
}
if !found {
return nil
}
if info.State.Running {
out, err := r.runner.Run(ctx, r.projectRoot, nil, "podman", "stop", containerName)
if err != nil {
return commandError("stop container "+containerName, out, err)
}
}
return nil
}
func (r *containerRuntime) stopAndRemoveContainer(ctx context.Context, containerName string) error {
out, err := r.runner.Run(ctx, r.projectRoot, nil, "podman", "rm", "-f", containerName)
if err != nil {
return commandError("remove container "+containerName, out, err)
}
return nil
}
func (r *containerRuntime) endpoint(ctx context.Context, containerName string) (string, error) {
out, err := r.runner.Run(ctx, r.projectRoot, nil, "podman", "port", containerName, runnerContainerPort)
if err != nil {
return "", fmt.Errorf("read container port for %s: %w", containerName, err)
}
lines := strings.Split(strings.TrimSpace(out), "\n")
if len(lines) == 0 || strings.TrimSpace(lines[0]) == "" {
return "", fmt.Errorf("container %s does not expose %s", containerName, runnerContainerPort)
}
line := strings.TrimSpace(lines[0])
port := line[strings.LastIndex(line, ":")+1:]
if _, err := strconv.Atoi(port); err != nil {
return "", fmt.Errorf("parse runner port %q", line)
}
return "http://127.0.0.1:" + port, nil
}
@@ -0,0 +1,174 @@
package workspaceruntime
import (
"os"
"path/filepath"
"testing"
"inbox/internal/domain/lane"
"inbox/internal/domain/workspace"
)
func TestDriftedWhenRunnerPortIsMissing(t *testing.T) {
ws := workspace.Workspace{
ID: "ws_1",
RootPath: "/tmp/ws",
}
item := lane.Record{
ID: "chain_1",
ContainerName: "lane-demo",
WorktreePath: "/tmp/ws-chain",
}
runtime := &containerRuntime{serverPort: 3000}
info := validInspect(ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1")
delete(info.NetworkSettings.Ports, runnerContainerPort)
if !runtime.laneDrifted(info, ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1") {
t.Fatalf("expected container without %s mapping to drift", runnerContainerPort)
}
}
func TestDriftedWhenRunnerPortIsPublished(t *testing.T) {
ws := workspace.Workspace{
ID: "ws_1",
RootPath: "/tmp/ws",
}
item := lane.Record{
ID: "chain_1",
ContainerName: "lane-demo",
WorktreePath: "/tmp/ws-chain",
}
runtime := &containerRuntime{serverPort: 3000}
info := validInspect(ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1")
if runtime.laneDrifted(info, ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1") {
t.Fatalf("expected container with %s mapping to remain reusable", runnerContainerPort)
}
}
func TestDriftedWhenContainerUserIsNotRoot(t *testing.T) {
ws := workspace.Workspace{ID: "ws_1", RootPath: "/tmp/ws"}
item := lane.Record{
ID: "chain_1",
ContainerName: "lane-demo",
WorktreePath: "/tmp/ws-chain",
}
runtime := &containerRuntime{serverPort: 3000}
info := validInspect(ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1")
info.Config.User = "runner"
if !runtime.laneDrifted(info, ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1") {
t.Fatalf("expected non-root container user to drift")
}
}
func TestDriftedWhenRuntimeCodexFingerprintChanges(t *testing.T) {
ws := workspace.Workspace{ID: "ws_1", RootPath: "/tmp/ws"}
item := lane.Record{
ID: "chain_1",
ContainerName: "lane-demo",
WorktreePath: "/tmp/ws-chain",
}
runtime := &containerRuntime{serverPort: 3000}
info := validInspect(ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1")
if !runtime.laneDrifted(info, ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-2") {
t.Fatalf("expected runtime codex fingerprint mismatch to drift")
}
}
func TestRuntimeCodexFingerprint(t *testing.T) {
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, "config.toml"), []byte("model = \"gpt-5.3-codex\"\n"), 0644); err != nil {
t.Fatalf("WriteFile(config.toml) error = %v", err)
}
if err := os.WriteFile(filepath.Join(root, "auth.json"), []byte("{\"OPENAI_API_KEY\":\"token\"}\n"), 0600); err != nil {
t.Fatalf("WriteFile(auth.json) error = %v", err)
}
sum1, err := runtimeCodexFingerprint(root)
if err != nil {
t.Fatalf("runtimeCodexFingerprint() error = %v", err)
}
if err := os.WriteFile(filepath.Join(root, "auth.json"), []byte("{\"OPENAI_API_KEY\":\"changed\"}\n"), 0600); err != nil {
t.Fatalf("WriteFile(auth.json) error = %v", err)
}
sum2, err := runtimeCodexFingerprint(root)
if err != nil {
t.Fatalf("runtimeCodexFingerprint() error = %v", err)
}
if sum1 == "" || sum2 == "" {
t.Fatalf("expected non-empty fingerprints, got %q and %q", sum1, sum2)
}
if sum1 == sum2 {
t.Fatalf("expected fingerprint to change when runtime codex contents change")
}
}
func TestRuntimeCodexFingerprintChangesForNestedSkillFiles(t *testing.T) {
root := t.TempDir()
skillDir := filepath.Join(root, "skills", "inbox")
if err := os.MkdirAll(skillDir, 0755); err != nil {
t.Fatalf("MkdirAll(skillDir) error = %v", err)
}
if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("# Inbox\n"), 0644); err != nil {
t.Fatalf("WriteFile(SKILL.md) error = %v", err)
}
sum1, err := runtimeCodexFingerprint(root)
if err != nil {
t.Fatalf("runtimeCodexFingerprint() error = %v", err)
}
if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("# Inbox\n\nUpdated\n"), 0644); err != nil {
t.Fatalf("WriteFile(SKILL.md) error = %v", err)
}
sum2, err := runtimeCodexFingerprint(root)
if err != nil {
t.Fatalf("runtimeCodexFingerprint() error = %v", err)
}
if sum1 == sum2 {
t.Fatalf("expected nested skill file change to affect fingerprint")
}
}
func validInspect(ws workspace.Workspace, item lane.Record, workerBinary, inboxBinary, codexFingerprint string) podmanInspect {
var info podmanInspect
info.ImageName = runtimeBaseImage
info.Path = laneWorkerContainerPath
info.Config.Image = runtimeBaseImage
info.Config.User = "root"
info.Config.WorkingDir = "/workspace"
info.Config.Env = []string{
"INBOX_WORKSPACE=/workspace",
"INBOX_WORKSPACE_ID=" + ws.ID,
"INBOX_LANE_ID=" + item.ID,
"INBOX_RUNTIME_AGENT_ID=" + item.ContainerName,
"INBOX_API_URL=http://host.containers.internal:3000",
"HOME=/root",
"INBOX_RUNTIME_CODEX_SHA=" + codexFingerprint,
}
info.NetworkSettings.Ports = map[string][]struct {
HostIP string `json:"HostIp"`
HostPort string `json:"HostPort"`
}{
runnerContainerPort: {{
HostIP: "127.0.0.1",
HostPort: "40123",
}},
}
info.Mounts = []struct {
Source string `json:"Source"`
Destination string `json:"Destination"`
}{
{Source: item.WorktreePath, Destination: "/workspace"},
{Source: workerBinary, Destination: laneWorkerContainerPath},
{Source: inboxBinary, Destination: inboxContainerPath},
}
return info
}
@@ -0,0 +1,37 @@
package workspaceruntime
import (
"fmt"
"net"
"strings"
"time"
)
type endpointProbe struct {
attempts int
dialTimeout time.Duration
retryDelay time.Duration
}
func newEndpointProbe() *endpointProbe {
return &endpointProbe{
attempts: 20,
dialTimeout: 500 * time.Millisecond,
retryDelay: 250 * time.Millisecond,
}
}
func (p *endpointProbe) wait(endpoint string) error {
address := strings.TrimPrefix(endpoint, "http://")
var lastErr error
for attempt := 0; attempt < p.attempts; attempt++ {
conn, err := net.DialTimeout("tcp", address, p.dialTimeout)
if err == nil {
_ = conn.Close()
return nil
}
lastErr = err
time.Sleep(p.retryDelay)
}
return fmt.Errorf("runner endpoint %s is not reachable: %w", endpoint, lastErr)
}
@@ -0,0 +1,198 @@
package workspaceruntime
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"inbox/internal/app/lanegit"
)
type gitWorktree struct {
Path string
Branch string
}
type gitWorktreeManager struct {
projectRoot string
runner lanegit.Runner
}
func (g *gitWorktreeManager) ensureRepository(ctx context.Context, projectDir string) (string, error) {
isRepo, err := g.isRepository(ctx, projectDir)
if err != nil {
return "", err
}
if isRepo {
branch, err := g.currentBranch(ctx, projectDir)
if err == nil && strings.TrimSpace(branch) != "" {
return strings.TrimSpace(branch), nil
}
return "main", nil
}
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "init", "-b", "main")
if err != nil {
return "", commandError("git init "+projectDir, out, err)
}
env := map[string]string{
"GIT_AUTHOR_NAME": "Inbox",
"GIT_AUTHOR_EMAIL": "inbox@local",
"GIT_COMMITTER_NAME": "Inbox",
"GIT_COMMITTER_EMAIL": "inbox@local",
}
out, err = g.runner.Run(ctx, g.projectRoot, env, "git", "-C", projectDir, "commit", "--allow-empty", "-m", "Initialize workspace repository")
if err != nil {
return "", commandError("create initial empty commit", out, err)
}
return "main", nil
}
func (g *gitWorktreeManager) ensureWorktree(ctx context.Context, projectDir, worktreePath, baseBranch, worktreeBranch string) error {
worktreePath = filepath.Clean(worktreePath)
entries, err := g.listWorktrees(ctx, projectDir)
if err != nil {
return err
}
if stale, err := hasMissingWorktreePaths(entries); err != nil {
return err
} else if stale {
if err := g.pruneWorktrees(ctx, projectDir); err != nil {
return err
}
entries, err = g.listWorktrees(ctx, projectDir)
if err != nil {
return err
}
}
for _, entry := range entries {
if filepath.Clean(entry.Path) == worktreePath {
expected := "refs/heads/" + worktreeBranch
if strings.TrimSpace(entry.Branch) != "" && entry.Branch != expected {
return fmt.Errorf("worktree path %s is attached to %s, want %s", worktreePath, entry.Branch, expected)
}
return nil
}
if entry.Branch == "refs/heads/"+worktreeBranch && filepath.Clean(entry.Path) != worktreePath {
return fmt.Errorf("worktree branch %s already attached at %s", worktreeBranch, entry.Path)
}
}
if info, err := os.Stat(worktreePath); err == nil && info.IsDir() {
return fmt.Errorf("worktree path already exists but is not registered: %s", worktreePath)
} else if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("stat worktree path: %w", err)
}
if err := os.MkdirAll(filepath.Dir(worktreePath), 0755); err != nil {
return fmt.Errorf("create worktree parent: %w", err)
}
branchExists, err := g.branchExists(ctx, projectDir, worktreeBranch)
if err != nil {
return err
}
args := []string{"-C", projectDir, "worktree", "add"}
if !branchExists {
args = append(args, "-b", worktreeBranch)
}
args = append(args, worktreePath)
if branchExists {
args = append(args, worktreeBranch)
} else {
args = append(args, baseBranch)
}
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", args...)
if err != nil {
return commandError("create worktree "+worktreePath, out, err)
}
return nil
}
func (g *gitWorktreeManager) isRepository(ctx context.Context, projectDir string) (bool, error) {
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "rev-parse", "--is-inside-work-tree")
if err != nil {
if strings.Contains(out, "not a git repository") {
return false, nil
}
return false, fmt.Errorf("detect git repository: %w", err)
}
return strings.TrimSpace(out) == "true", nil
}
func (g *gitWorktreeManager) currentBranch(ctx context.Context, projectDir string) (string, error) {
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "symbolic-ref", "--quiet", "--short", "HEAD")
if err != nil {
return "", err
}
return strings.TrimSpace(out), nil
}
func (g *gitWorktreeManager) listWorktrees(ctx context.Context, projectDir string) ([]gitWorktree, error) {
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "worktree", "list", "--porcelain")
if err != nil {
return nil, fmt.Errorf("list git worktrees: %w", err)
}
lines := strings.Split(out, "\n")
items := make([]gitWorktree, 0)
var current gitWorktree
flush := func() {
if strings.TrimSpace(current.Path) == "" {
return
}
items = append(items, current)
current = gitWorktree{}
}
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
switch {
case strings.HasPrefix(line, "worktree "):
flush()
current.Path = strings.TrimSpace(strings.TrimPrefix(line, "worktree "))
case strings.HasPrefix(line, "branch "):
current.Branch = strings.TrimSpace(strings.TrimPrefix(line, "branch "))
}
}
flush()
return items, nil
}
func (g *gitWorktreeManager) branchExists(ctx context.Context, projectDir, branch string) (bool, error) {
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "show-ref", "--verify", "--quiet", "refs/heads/"+branch)
if err != nil {
if out == "" {
return false, nil
}
return false, fmt.Errorf("check git branch %s: %w", branch, err)
}
return true, nil
}
func (g *gitWorktreeManager) pruneWorktrees(ctx context.Context, projectDir string) error {
out, err := g.runner.Run(ctx, g.projectRoot, nil, "git", "-C", projectDir, "worktree", "prune", "--expire", "now")
if err != nil {
return commandError("prune stale worktrees", out, err)
}
return nil
}
func hasMissingWorktreePaths(entries []gitWorktree) (bool, error) {
for _, entry := range entries {
info, err := os.Stat(filepath.Clean(entry.Path))
if err == nil {
if !info.IsDir() {
return false, fmt.Errorf("worktree path is not a directory: %s", entry.Path)
}
continue
}
if os.IsNotExist(err) {
return true, nil
}
return false, fmt.Errorf("stat registered worktree path %s: %w", entry.Path, err)
}
return false, nil
}
@@ -0,0 +1,43 @@
package workspaceruntime
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"inbox/internal/app/lanegit"
)
type hostInboxBinary struct {
projectRoot string
runner lanegit.Runner
}
func (b *hostInboxBinary) ensure(ctx context.Context) (string, error) {
binaryPath := filepath.Join(b.projectRoot, ".runtime", "bin", "inbox")
needsBuild, err := b.needsBuild(binaryPath)
if err != nil {
return "", err
}
if !needsBuild {
return binaryPath, nil
}
if err := os.MkdirAll(filepath.Dir(binaryPath), 0755); err != nil {
return "", fmt.Errorf("create runtime bin dir: %w", err)
}
env := map[string]string{
"GOOS": "linux",
"GOARCH": runtime.GOARCH,
}
out, err := b.runner.Run(ctx, filepath.Join(b.projectRoot, "inbox"), env, "go", "build", "-o", binaryPath, "./cmd/inbox")
if err != nil {
return "", commandError("build inbox binary", out, err)
}
return binaryPath, nil
}
func (b *hostInboxBinary) needsBuild(binaryPath string) (bool, error) {
return sourceTreeNeedsBuild(b.projectRoot, "inbox", binaryPath)
}
@@ -0,0 +1,75 @@
package workspaceruntime
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"inbox/internal/app/lanegit"
)
type hostLaneWorkerBinary struct {
projectRoot string
runner lanegit.Runner
}
func (b *hostLaneWorkerBinary) ensure(ctx context.Context) (string, error) {
binaryPath := filepath.Join(b.projectRoot, ".runtime", "bin", "lane-worker")
needsBuild, err := b.needsBuild(binaryPath)
if err != nil {
return "", err
}
if !needsBuild {
return binaryPath, nil
}
if err := os.MkdirAll(filepath.Dir(binaryPath), 0755); err != nil {
return "", fmt.Errorf("create runtime bin dir: %w", err)
}
env := map[string]string{
"GOOS": "linux",
"GOARCH": runtime.GOARCH,
}
out, err := b.runner.Run(ctx, filepath.Join(b.projectRoot, "inbox"), env, "go", "build", "-o", binaryPath, "./cmd/lane-worker")
if err != nil {
return "", commandError("build lane worker binary", out, err)
}
return binaryPath, nil
}
func (b *hostLaneWorkerBinary) needsBuild(binaryPath string) (bool, error) {
return sourceTreeNeedsBuild(b.projectRoot, "inbox", binaryPath)
}
func sourceTreeNeedsBuild(projectRoot, sourceSubdir, binaryPath string) (bool, error) {
info, err := os.Stat(binaryPath)
if err != nil {
if os.IsNotExist(err) {
return true, nil
}
return false, fmt.Errorf("stat runtime binary: %w", err)
}
latest := info.ModTime()
sourceRoot := filepath.Join(projectRoot, sourceSubdir)
err = filepath.Walk(sourceRoot, func(path string, fileInfo os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
if fileInfo.IsDir() {
base := filepath.Base(path)
if base == ".git" || base == "bin" {
return filepath.SkipDir
}
return nil
}
if fileInfo.ModTime().After(latest) {
latest = fileInfo.ModTime()
}
return nil
})
if err != nil {
return false, fmt.Errorf("walk runtime source tree: %w", err)
}
return latest.After(info.ModTime()), nil
}
@@ -0,0 +1,262 @@
package workspaceruntime
import (
"context"
"path/filepath"
"strings"
"inbox/internal/app/lanegit"
"inbox/internal/app/runtimecodex"
"inbox/internal/app/runtimeconfig"
"inbox/internal/base/slug"
"inbox/internal/base/timeutil"
"inbox/internal/domain/lane"
"inbox/internal/domain/role"
"inbox/internal/domain/skill"
"inbox/internal/domain/workspace"
)
type store interface {
GetProject(context.Context, string) (workspace.Project, error)
UpdateProjectDefaultBranch(context.Context, string, string) error
GetWorkspace(context.Context, string) (workspace.Workspace, error)
UpdateWorkspace(context.Context, workspace.Workspace) error
GetLane(context.Context, string) (lane.Record, error)
UpdateLane(context.Context, lane.Record) (lane.Record, error)
}
type runtimeCodexStore interface {
store
ListRoles(context.Context) ([]role.Definition, error)
GetRole(context.Context, string) (role.Definition, error)
GetRoleConfig(context.Context, string) (role.Config, error)
ListRolePrompts(context.Context, string) ([]role.Prompt, error)
ListRoleSkillBindings(context.Context, string) ([]role.SkillBinding, error)
ListSkillsByIDs(context.Context, []string) (map[string]skill.Definition, error)
}
type Service struct {
store store
projectRoot string
clock timeutil.Clock
workspacesDir string
git *gitWorktreeManager
worker *hostLaneWorkerBinary
inbox *hostInboxBinary
codexHomes *runtimecodex.Materializer
runtime *containerRuntime
}
type Runtime struct {
ContainerName string `json:"container_name"`
ContainerState string `json:"container_state"`
WorktreePath string `json:"worktree_path"`
RunnerEndpoint string `json:"runner_endpoint"`
Ensured bool `json:"ensured"`
}
func NewService(store store, projectRoot, workspacesDir string, serverPort int, clock timeutil.Clock, runner lanegit.Runner) *Service {
if clock == nil {
clock = timeutil.SystemClock{}
}
if runner == nil {
runner = lanegit.ExecRunner{}
}
root := strings.TrimSpace(projectRoot)
probe := newEndpointProbe()
var codexHomes *runtimecodex.Materializer
if configStore, ok := store.(runtimeCodexStore); ok {
codexHomes = runtimecodex.NewMaterializer(
configStore,
runtimeconfig.NewService(configStore, configStore, clock),
)
}
return &Service{
store: store,
projectRoot: root,
clock: clock,
workspacesDir: strings.TrimSpace(workspacesDir),
git: &gitWorktreeManager{projectRoot: root, runner: runner},
worker: &hostLaneWorkerBinary{projectRoot: root, runner: runner},
inbox: &hostInboxBinary{projectRoot: root, runner: runner},
codexHomes: codexHomes,
runtime: &containerRuntime{projectRoot: root, serverPort: serverPort, runner: runner, probe: probe},
}
}
func (s *Service) EnsureRepository(ctx context.Context, projectDir string) (string, error) {
return s.git.ensureRepository(ctx, projectDir)
}
func (s *Service) Ensure(ctx context.Context, workspaceID string) (workspace.Workspace, Runtime, error) {
ws, err := s.store.GetWorkspace(ctx, strings.TrimSpace(workspaceID))
if err != nil {
return workspace.Workspace{}, Runtime{}, err
}
project, err := s.store.GetProject(ctx, ws.ProjectID)
if err != nil {
return workspace.Workspace{}, Runtime{}, err
}
baseBranch, err := s.git.ensureRepository(ctx, project.RootPath)
if err != nil {
_, _ = s.failWorkspace(ctx, ws, err)
return ws, Runtime{}, err
}
if project.DefaultBranch != baseBranch {
if err := s.store.UpdateProjectDefaultBranch(ctx, project.ID, baseBranch); err != nil {
return ws, Runtime{}, err
}
project.DefaultBranch = baseBranch
}
if strings.TrimSpace(s.workspacesDir) != "" {
ws.RootPath = filepath.Join(s.workspacesDir, ws.Slug)
}
ws = workspace.ApplyManagedRuntimeConfig(ws, s.workspacesDir, baseBranch)
return s.ensureWorkspaceHost(ctx, project, ws)
}
func (s *Service) ensureWorkspaceHost(ctx context.Context, project workspace.Project, ws workspace.Workspace) (workspace.Workspace, Runtime, error) {
runtime := Runtime{
ContainerName: "",
WorktreePath: ws.RootPath,
}
if err := s.git.ensureWorktree(ctx, project.RootPath, ws.RootPath, ws.BaseBranch, ws.WorktreeBranch); err != nil {
failed, _ := s.failWorkspace(ctx, ws, err)
return failed, runtime, err
}
now := timeutil.FormatRFC3339(s.clock.Now())
ws.RootPath = filepath.Clean(ws.RootPath)
ws.RuntimeBackend = "host"
ws.ProvisionState = "ready"
ws.ProvisionError = ""
ws.LastProvisionedAt = now
ws.ContainerState = ""
if err := s.store.UpdateWorkspace(ctx, ws); err != nil {
return ws, runtime, err
}
runtime.ContainerState = ""
runtime.RunnerEndpoint = ""
runtime.Ensured = true
return ws, runtime, nil
}
func (s *Service) failWorkspace(ctx context.Context, ws workspace.Workspace, cause error) (workspace.Workspace, error) {
ws.ProvisionState = "failed"
ws.ProvisionError = strings.TrimSpace(cause.Error())
ws.ContainerState = "missing"
if err := s.store.UpdateWorkspace(ctx, ws); err != nil {
return ws, err
}
return ws, cause
}
func (s *Service) EnsureLane(ctx context.Context, laneID string) (lane.Record, error) {
item, err := s.store.GetLane(ctx, strings.TrimSpace(laneID))
if err != nil {
return lane.Record{}, err
}
ws, err := s.store.GetWorkspace(ctx, item.WorkspaceID)
if err != nil {
return lane.Record{}, err
}
project, err := s.store.GetProject(ctx, ws.ProjectID)
if err != nil {
return lane.Record{}, err
}
baseBranch, err := s.git.ensureRepository(ctx, project.RootPath)
if err != nil {
return lane.Record{}, err
}
if ws, _, err = s.ensureWorkspaceHost(ctx, project, ws); err != nil {
return lane.Record{}, err
}
if item.BaseBranch == "" {
item.BaseBranch = firstNonEmpty(strings.TrimSpace(ws.BaseBranch), baseBranch)
}
if item.Slug == "" {
item.Slug = slug.Normalize(item.Name)
}
if item.BranchName == "" {
item.BranchName = lane.DefaultBranchName(ws.Slug, item.TopicID, item.Slug)
}
if item.WorktreePath == "" {
item.WorktreePath = lane.DefaultWorktreePath(ws.RootPath, ws.Slug, item.TopicID, item.Slug)
}
if item.ContainerName == "" {
item.ContainerName = lane.DefaultContainerName(ws.Slug, item.TopicID, item.Slug)
}
if err := s.git.ensureWorktree(ctx, project.RootPath, item.WorktreePath, item.BaseBranch, item.BranchName); err != nil {
return lane.Record{}, err
}
workerCodexDir := ""
if s.codexHomes != nil {
runtimeRoot := runtimecodex.HostContainerRuntimeRoot(s.projectRoot, ws.ID)
if err := s.codexHomes.Sync(ctx, ws.ID, runtimeRoot); err != nil {
return lane.Record{}, err
}
workerCodexDir = runtimecodex.HostContainerCodexDir(s.projectRoot, ws.ID, "worker")
}
workerBinary, err := s.worker.ensure(ctx)
if err != nil {
return lane.Record{}, err
}
inboxBinary, err := s.inbox.ensure(ctx)
if err != nil {
return lane.Record{}, err
}
if err := s.runtime.ensureRunnerImage(ctx); err != nil {
return lane.Record{}, err
}
endpoint, err := s.runtime.ensureLaneContainer(ctx, ws, item, workerBinary, inboxBinary, workerCodexDir)
if err != nil {
return lane.Record{}, err
}
item.RuntimeEndpoint = endpoint
item.Status = lane.StatusRunning
now := timeutil.FormatRFC3339(s.clock.Now())
if item.StartedAt == "" {
item.StartedAt = now
}
item.UpdatedAt = now
return s.store.UpdateLane(ctx, item)
}
func (s *Service) StopLane(ctx context.Context, laneID string) (lane.Record, error) {
item, err := s.store.GetLane(ctx, strings.TrimSpace(laneID))
if err != nil {
return lane.Record{}, err
}
if item.ContainerName != "" {
if err := s.runtime.stopContainer(ctx, item.ContainerName); err != nil {
return lane.Record{}, err
}
}
item.Status = lane.StatusCancelled
item.CompletedAt = timeutil.FormatRFC3339(s.clock.Now())
item.RuntimeEndpoint = ""
return s.store.UpdateLane(ctx, item)
}
func (s *Service) ReleaseLaneRuntime(ctx context.Context, laneID string) (lane.Record, error) {
item, err := s.store.GetLane(ctx, strings.TrimSpace(laneID))
if err != nil {
return lane.Record{}, err
}
if item.ContainerName != "" {
if err := s.runtime.stopContainer(ctx, item.ContainerName); err != nil {
return lane.Record{}, err
}
}
item.RuntimeEndpoint = ""
item.UpdatedAt = timeutil.FormatRFC3339(s.clock.Now())
return s.store.UpdateLane(ctx, item)
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}
@@ -0,0 +1,84 @@
package workspaceruntime
import (
"context"
"os"
"path/filepath"
"testing"
"time"
"inbox/internal/base/timeutil"
"inbox/internal/domain/workspace"
sqlitestore "inbox/internal/store/sqlite"
)
func TestEnsureRepairsMissingRegisteredWorkspaceWorktree(t *testing.T) {
ctx := context.Background()
clock := timeutil.FixedClock{Time: time.Date(2026, 3, 18, 2, 0, 0, 0, time.UTC)}
store, err := sqlitestore.OpenInMemory(clock)
if err != nil {
t.Fatalf("OpenInMemory() error = %v", err)
}
defer store.Close()
rootDir := t.TempDir()
projectDir := filepath.Join(rootDir, "project")
workspacesDir := filepath.Join(rootDir, "workspaces")
if err := os.MkdirAll(projectDir, 0755); err != nil {
t.Fatalf("MkdirAll(projectDir) error = %v", err)
}
project, err := store.CreateProject(ctx, workspace.NormalizeProjectForCreate(workspace.Project{
Slug: "demo-project",
Name: "demo-project",
RootPath: projectDir,
DefaultBranch: "main",
}))
if err != nil {
t.Fatalf("CreateProject() error = %v", err)
}
ws, err := store.CreateWorkspace(ctx, workspace.NormalizeWorkspaceForCreate(workspace.Workspace{
ProjectID: project.ID,
Slug: "todo",
Name: "todo",
RootPath: filepath.Join(workspacesDir, "todo"),
BaseBranch: "main",
WorktreeBranch: "worktree/todo",
}))
if err != nil {
t.Fatalf("CreateWorkspace() error = %v", err)
}
service := NewService(store, rootDir, workspacesDir, 3000, clock, nil)
ensured, runtime, err := service.Ensure(ctx, ws.ID)
if err != nil {
t.Fatalf("Ensure() initial error = %v", err)
}
if !runtime.Ensured {
t.Fatalf("expected initial ensure to succeed, got %#v", runtime)
}
if _, err := os.Stat(ensured.RootPath); err != nil {
t.Fatalf("Stat(initial worktree) error = %v", err)
}
if err := os.RemoveAll(ensured.RootPath); err != nil {
t.Fatalf("RemoveAll(worktree) error = %v", err)
}
if _, err := os.Stat(ensured.RootPath); !os.IsNotExist(err) {
t.Fatalf("expected worktree path to be removed, stat err = %v", err)
}
ensured, runtime, err = service.Ensure(ctx, ws.ID)
if err != nil {
t.Fatalf("Ensure() repair error = %v", err)
}
if !runtime.Ensured {
t.Fatalf("expected repaired ensure to succeed, got %#v", runtime)
}
if _, err := os.Stat(ensured.RootPath); err != nil {
t.Fatalf("Stat(repaired worktree) error = %v", err)
}
if ensured.ProvisionState != "ready" {
t.Fatalf("expected workspace to be ready after repair, got %#v", ensured)
}
}