rdev/internal/handlers/sessions_webui.go
jordan e42c18a9a3 feat: add session web UI mode + aeries-daeya cookbook tree
Session WebUI:
- Add `web_ui` flag to session create — launches claude-code-ui in pod on port 3001
- Install @siteboon/claude-code-ui in claudebox Dockerfile, expose port 3001
- Migration 027: add web_ui column to sessions table
- startWebUI/stopWebUI fire-and-forget helpers in SessionsHandler
- Service selects preview port 3001 (web UI) vs 8080 (sidecar) based on flag

Aeries Daeya cookbook:
- Add cookbooks/trees/aeries-daeya.yaml: privacy-first avatar social platform
  (infra → avatar data model → AI generation pipeline → studio UI)
- Add cookbooks/scripts/aeries-daeya-test.sh: run/status/diagnose/teardown harness
- Fix race condition in common.sh wait_for_pipeline: detect already-running pipelines
  at startup and track directly instead of waiting for a newer one

Docs/tooling:
- Add SDK Update Workflow section to CLAUDE.md
- Add `make sdk` and `make sdk-check` targets for OpenAPI spec management

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 23:14:08 -07:00

70 lines
2.2 KiB
Go

package handlers
import (
"context"
"fmt"
"time"
"github.com/orchard9/rdev/internal/domain"
"github.com/orchard9/rdev/internal/logging"
"github.com/orchard9/rdev/internal/service"
)
// startWebUI launches claude-code-ui as a background process in the session pod.
// This is fire-and-forget — UI startup failure does not block session creation.
func (h *SessionsHandler) startWebUI(ctx context.Context, session *domain.Session) {
log := logging.FromContext(ctx).WithService("SessionsHandler")
bgCtx := context.WithoutCancel(ctx)
go func() {
startCtx, cancel := context.WithTimeout(bgCtx, TimeoutStandard)
defer cancel()
cmd := &domain.Command{
ID: domain.CommandID(fmt.Sprintf("webui-%s", session.ID)),
ProjectID: session.ProjectID,
Type: domain.CommandTypeShell,
Args: []string{fmt.Sprintf("nohup claude-code-ui --port %d > /tmp/claude-code-ui.log 2>&1 &", service.WebUIPort)},
StartedAt: time.Now(),
}
if _, err := h.executor.Execute(startCtx, cmd, session.PodName, func(_ domain.OutputLine) {}); err != nil {
log.Error("failed to start claude-code-ui",
logging.FieldError, err,
"session_id", session.ID,
logging.FieldProjectID, session.ProjectID,
)
} else {
log.Info("claude-code-ui started",
"session_id", session.ID,
"port", service.WebUIPort,
)
}
}()
}
// stopWebUI kills the claude-code-ui process in the session pod.
// Best-effort — failure is logged but does not block session teardown.
func (h *SessionsHandler) stopWebUI(ctx context.Context, session *domain.Session) {
log := logging.FromContext(ctx).WithService("SessionsHandler")
killCtx, cancel := context.WithTimeout(ctx, TimeoutFastLookup)
defer cancel()
cmd := &domain.Command{
ID: domain.CommandID(fmt.Sprintf("webui-stop-%s", session.ID)),
ProjectID: session.ProjectID,
Type: domain.CommandTypeShell,
Args: []string{"pkill -f claude-code-ui || true"},
StartedAt: time.Now(),
}
if _, err := h.executor.Execute(killCtx, cmd, session.PodName, func(_ domain.OutputLine) {}); err != nil {
log.Warn("failed to stop claude-code-ui",
logging.FieldError, err,
"session_id", session.ID,
logging.FieldProjectID, session.ProjectID,
)
}
}