Skip to content

service

View on pkg.go.dev

import "github.com/thesimonho/warden/service"

Package service provides the business logic for Warden operations.

Service is the single orchestration layer — all lifecycle management (session watchers, event directory watchers), business logic, and state lives here. HTTP handlers, TUI adapters, and Go library consumers all call the same Service methods.

ErrBudgetExceeded is returned when a project operation is blocked because the project has exceeded its cost budget.

var ErrBudgetExceeded = errors.New("project cost budget exceeded")

ErrDockerUnavailable is returned by container-mutating operations when Docker was not reachable at startup.

var ErrDockerUnavailable = errors.New(
"Docker is required but not available. " +
"Install Docker (https://docs.docker.com/get-docker/) " +
"and make sure the daemon is running",
)

ErrInvalidInput indicates the caller provided invalid parameters.

var ErrInvalidInput = errors.New("invalid input")

ErrNotFound indicates the requested resource does not exist.

var ErrNotFound = errors.New("not found")

func SessionEventToContainerEvent(parsed agent.ParsedEvent, ctx SessionContext) *event.ContainerEvent

SessionEventToContainerEvent converts a ParsedEvent from the JSONL parser into a ContainerEvent for the event pipeline (store → broker → SSE → frontend, audit log). Returns nil for events that don’t map to container event types.

func StandardAuditEvents() map[string]bool

StandardAuditEvents returns the set of event names logged in standard audit mode. Derived from auditEventsByCategory using the standard categories defined in api.StandardAuditCategories. This is the single source of truth — pass the result to db.NewAuditWriter.

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AddProjectRequest = api.AddProjectRequest

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AddProjectResponse = api.AddProjectResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AgentTemplateOverride = api.AgentTemplateOverride

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AuditCategory = api.AuditCategory

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AuditEntry = api.AuditEntry

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AuditFilters = api.AuditFilters

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AuditLevel = api.AuditLevel

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AuditLogMode = api.AuditLogMode

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AuditSource = api.AuditSource

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type AuditSummary = api.AuditSummary

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type BatchProjectAction = api.BatchProjectAction

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type BatchProjectRef = api.BatchProjectRef

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type BatchProjectRequest = api.BatchProjectRequest

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type BatchProjectResponse = api.BatchProjectResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type BatchProjectResult = api.BatchProjectResult

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type BudgetSource = api.BudgetSource

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type BudgetStatusResponse = api.BudgetStatusResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ClipboardUploadResponse = api.ClipboardUploadResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ContainerConfig = api.ContainerConfig

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ContainerResult = api.ContainerResult

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type CreateContainerRequest = api.CreateContainerRequest

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type CreateWorktreeRequest = api.CreateWorktreeRequest

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type DefaultEnvVar = api.DefaultEnvVar

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type DefaultMount = api.DefaultMount

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type DefaultsResponse = api.DefaultsResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type DiffFileSummary = api.DiffFileSummary

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type DiffResponse = api.DiffResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type DirEntry = api.DirEntry

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type Mount = api.Mount

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type NetworkMode = api.NetworkMode

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type PostAuditEventRequest = api.PostAuditEventRequest

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ProjectCostsResponse = api.ProjectCostsResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ProjectResponse = api.ProjectResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ProjectResult = api.ProjectResult

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ProjectTemplate = api.ProjectTemplate

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type RuntimeDefault = api.RuntimeDefault

Service provides business logic for all Warden operations. It is the single orchestration layer between external consumers (HTTP handlers, TUI, Go library callers) and the lower-level engine, database, and event subsystems.

Service manages all container lifecycle including session watcher start/stop and event directory registration. Callers never need to manage these directly.

type Service struct {
// contains filtered or unexported fields
}

func New(deps ServiceDeps) *Service

New creates a Service with the given dependencies. The lifecycle deps (Registry, EventWatcher, EventHandler, HomeDir) may be nil — session watcher operations degrade gracefully when absent.

func (s *Service) AddProject(name, hostPath, agentType, cloneURL string, temporary bool) (*ProjectResult, error)

AddProject registers a project in the database. The project ID is computed deterministically from the host path (local) or clone URL (remote). If a project for this path/URL and agent type already exists, returns the existing project without error.

func (s *Service) AddProjectWithContainer(ctx context.Context, req api.AddProjectRequest) (*api.AddProjectResponse, error)

AddProjectWithContainer registers a project and creates a container atomically. If container creation fails, the project is removed and the error is returned.

func (s *Service) BatchProjectOperation(ctx context.Context, req api.BatchProjectRequest) *api.BatchProjectResponse

BatchProjectOperation performs an action on multiple projects. Each project is processed independently — failures don’t stop the remaining operations.

func (s *Service) CleanupWorktrees(ctx context.Context, projectID, agentType string) ([]string, error)

CleanupWorktrees removes orphaned worktree directories and stale terminal tracking directories. Returns the list of removed IDs.

func (s *Service) ClearCostFallbackNegCache(projectID, agentType string)

ClearCostFallbackNegCache removes the negative cache entry for a project. Called when a JSONL cost event arrives, indicating the container now has cost data and the fallback should be re-enabled.

func (s *Service) Close()

Close releases Service resources. Currently stops all session watchers. Called by the top-level Warden.Close().

func (s *Service) ConnectTerminal(ctx context.Context, projectID, agentType, worktreeID string) (*WorktreeResult, error)

ConnectTerminal starts a terminal for a worktree in the given container. For background reconnects (tmux session alive, no script needed), pushes a synthetic terminal_connected event so the store transitions from background to connected.

func (s *Service) CreateAccessItem(req api.CreateAccessItemRequest) (*access.Item, error)

CreateAccessItem creates a user-defined access item.

func (s *Service) CreateContainer(ctx context.Context, req api.CreateContainerRequest) (*ContainerResult, error)

CreateContainer creates a new project container and saves full project metadata to the database.

func (s *Service) CreateWorktree(ctx context.Context, projectID, agentType, name string) (*WorktreeResult, error)

CreateWorktree creates a new git worktree and connects a terminal.

func (s *Service) DeleteAccessItem(id string) error

DeleteAccessItem removes a user-defined access item. Built-in items cannot be deleted (use ResetAccessItem instead).

func (s *Service) DeleteAuditEvents(filters api.AuditFilters) (int64, error)

DeleteAuditEvents removes events matching the given filters. With no filters, clears all events. Also deletes matching session costs so the total cost stays consistent with the remaining events.

func (s *Service) DeleteContainer(ctx context.Context, projectID, agentType string) (*ContainerResult, error)

DeleteContainer stops and removes a container.

func (s *Service) DisconnectTerminal(ctx context.Context, projectID, agentType, worktreeID string) (*WorktreeResult, error)

DisconnectTerminal disconnects the terminal viewer for a worktree. The tmux session continues running in the background. Pushes a synthetic terminal_disconnected event so the store transitions from connected to background without relying on the container script’s async curl delivery.

func (s *Service) GetAccessItem(id string) (*api.AccessItemResponse, error)

GetAccessItem returns a single access item by ID. For built-in items, returns the DB override if one exists, otherwise the default.

func (s *Service) GetAuditLog(filters api.AuditFilters) ([]db.Entry, error)

GetAuditLog returns filtered events from the audit log. When a category is specified, only matching event types are returned. When no category is specified, all events are returned. Each entry includes its computed audit category.

func (s *Service) GetAuditLogMode() api.AuditLogMode

GetAuditLogMode returns the current audit log mode.

func (s *Service) GetAuditProjects() ([]string, error)

GetAuditProjects returns distinct project (container) names from the audit log.

func (s *Service) GetAuditSummary(_ context.Context, filters api.AuditFilters) (*api.AuditSummary, error)

GetAuditSummary returns aggregate statistics for audit events.

func (s *Service) GetBudgetStatus(_ context.Context, projectID, agentType string) (*api.BudgetStatusResponse, error)

GetBudgetStatus returns the budget state for a project.

func (s *Service) GetDefaultProjectBudget() float64

GetDefaultProjectBudget returns the global default per-project budget. Returns 0 (unlimited) if not configured.

func (s *Service) GetDefaults(projectPath string) DefaultsResponse

GetDefaults returns server-resolved default values for the create container form, including auto-detected bind mounts and runtimes. When projectPath is non-empty, runtime detection scans that directory for marker files.

func (s *Service) GetEffectiveBudget(projectID, agentType string) float64

GetEffectiveBudget returns the effective cost budget for a project+agent pair. Uses per-project budget if > 0, otherwise the global default. Returns 0 (unlimited) if neither is set.

func (s *Service) GetProject(projectID, agentType string) (*db.ProjectRow, error)

GetProject returns a project row by compound key, or nil if not found.

func (s *Service) GetProjectCosts(_ context.Context, projectID, agentType string) (*api.ProjectCostsResponse, error)

GetProjectCosts returns session-level cost data for a project.

func (s *Service) GetProjectDetails(ctx context.Context, projectID, agentType string) (*api.ProjectResponse, error)

GetProjectDetails returns a single project enriched with Docker state, cost, attention, and agent version data. Only queries Docker for the requested project’s container, avoiding the O(N) enrichment of all containers that listProjectsInternal performs.

func (s *Service) GetSettings() SettingsResponse

GetSettings returns the current server-side settings.

func (s *Service) GetWorktree(ctx context.Context, projectID, agentType, worktreeID string) (*engine.Worktree, error)

GetWorktree returns a single worktree by ID with terminal state. Internally fetches all worktrees and filters — acceptable for the typical 1-5 worktrees per project. A targeted single-worktree docker exec would be premature optimization at this scale.

func (s *Service) GetWorktreeDiff(ctx context.Context, projectID, agentType, worktreeID string) (*api.DiffResponse, error)

GetWorktreeDiff returns uncommitted changes for a worktree.

func (s *Service) HandleContainerAlive(projectID, agentType, containerName string)

HandleContainerAlive is called when the event bus detects a container sending events for the first time (or after being marked stale). It starts a session watcher if one isn’t already running.

This handles edge cases that ResumeSessionWatchers misses: containers that start after the server, containers that restart after being marked stale, and containers created by external tools.

func (s *Service) HandleContainerStale(containerName string)

HandleContainerStale writes an audit entry when a container’s heartbeat goes stale. Called by the event bus stale callback so the audit entry includes full project context (project ID and container name).

When the container is crash-looping, an additional container_startup_failed event is written with the container’s log tail for diagnostics.

func (s *Service) HandleContainerStart(containerID, containerName string)

HandleContainerStart is called when a Warden container emits a Docker start event (including auto-restarts by the Docker daemon). Re-applies network isolation if the project uses restricted or none mode.

Called synchronously from the Docker events watcher goroutine. The actual isolation work runs in a separate goroutine to avoid blocking the events stream while waiting for installs.

func (s *Service) InspectContainer(ctx context.Context, projectID, agentType string) (*api.ContainerConfig, error)

InspectContainer returns the editable configuration of a container. Docker-derived fields come from the engine; DB metadata is overlaid directly from the project row.

func (s *Service) IsOverBudget(projectID, agentType string) bool

IsOverBudget returns true if the project has exceeded its cost budget and the preventStart enforcement action is enabled.

func (s *Service) KillWorktreeProcess(ctx context.Context, projectID, agentType, worktreeID string) (*WorktreeResult, error)

KillWorktreeProcess kills the tmux session and all child processes for a worktree, destroying the terminal entirely.

func (s *Service) ListAccessItems() ([]api.AccessItemResponse, error)

ListAccessItems returns all access items (built-in + user-created) enriched with host detection status. If a built-in item has been customized (saved to DB), the customized version is returned instead.

func (s *Service) ListDirectories(path string, includeFiles bool) ([]api.DirEntry, error)

ListDirectories returns filesystem entries at the given path for the browser. The path must be absolute. When includeFiles is false, only directories are returned (default behavior). When true, both directories and files are returned with IsDir set accordingly.

func (s *Service) ListProjects(ctx context.Context) ([]api.ProjectResponse, error)

ListProjects returns all projects from the database, enriched with container state, DB metadata, and cost data from the event store.

func (s *Service) ListRuntimes(ctx context.Context) docker.Info

ListRuntimes returns available container runtimes.

func (s *Service) ListWorktrees(ctx context.Context, projectID, agentType string) ([]engine.Worktree, error)

ListWorktrees returns all worktrees for the given project with their terminal state, enriched with real-time data from the event store when available.

func (*Service) NotifyTerminalDisconnected

Section titled “func (*Service) NotifyTerminalDisconnected”
func (s *Service) NotifyTerminalDisconnected(_ context.Context, project *db.ProjectRow, worktreeID string)

NotifyTerminalDisconnected pushes a terminal_disconnected event to the event store. Called by the WebSocket handler when the last viewer closes.

func (s *Service) PersistSessionCost(projectID, agentType, containerName, sessionID string, cost float64, isEstimated bool)

PersistSessionCost is the single gateway for all cost mutations. It persists session cost to the DB (when valid data is provided) and always triggers budget enforcement afterward.

All code paths that write cost data MUST go through this method to guarantee enforcement is never skipped. This is analogous to how all audit writes go through [db.AuditWriter.Write].

It is safe to call with empty sessionID or zero cost — the DB write is skipped but enforcement still runs against previously persisted data.

func (s *Service) PostAuditEvent(req api.PostAuditEventRequest) error

PostAuditEvent writes a custom event to the audit log.

func (s *Service) ProxyPort(ctx context.Context, projectID, agentType string, port int) (string, error)

ProxyPort validates that a port is declared for the given project and returns the container’s bridge network IP address. Returns ErrNotFound if the project doesn’t exist, ErrInvalidInput if the port is not in the declared forwarded ports list.

func (s *Service) PurgeProjectAudit(projectID, agentType string) (int64, error)

PurgeProjectAudit removes all audit events for a project. The audit_purged event is written before the purge but will be deleted by it — the event serves as a write-ahead record for external log consumers that process events before they are purged.

func (s *Service) ReadProjectTemplate(filePath string) (*api.ProjectTemplate, error)

ReadProjectTemplate reads a .warden.json from an arbitrary file path. Unlike readProjectTemplate, this returns an error since the user explicitly requested the import.

func (s *Service) RemoveProject(projectID, agentType string) (*ProjectResult, error)

RemoveProject removes a project from the database by compound key. When audit logging is enabled, cost data and events are preserved so the audit log remains accurate. When audit logging is off, all associated data is cleaned up.

func (s *Service) RemoveWorktree(ctx context.Context, projectID, agentType, worktreeID string) (*WorktreeResult, error)

RemoveWorktree fully removes a worktree: kills processes, runs `git worktree remove`, and cleans up tracking state.

func (s *Service) ResetAccessItem(id string) (*access.Item, error)

ResetAccessItem restores a built-in access item to its default by removing any DB override. Returns ErrInvalidInput for non-built-in items.

func (s *Service) ResetProjectCosts(projectID, agentType string) error

ResetProjectCosts removes all cost history for a project+agent pair. This is an audit event itself — the fact that costs were reset is recorded.

func (s *Service) ResetWorktree(ctx context.Context, projectID, agentType, worktreeID string) (*WorktreeResult, error)

ResetWorktree clears all session state for a worktree without removing it. Kills the process, clears JSONL session files in the container, and removes terminal tracking state. Audit events are preserved. The session watcher is restarted so it picks up the clean state instead of replaying deleted files.

func (s *Service) ResolveAccessItems(items []access.Item) (*api.ResolveAccessItemsResponse, error)

ResolveAccessItems resolves the given access items and returns their injections. Used by the “Test” button in the UI. Accepts items directly — no DB lookup is performed.

Refreshes the shell environment cache before resolving so newly exported env vars are picked up without restarting Warden.

func (*Service) ResolveAccessItemsForContainer

Section titled “func (*Service) ResolveAccessItemsForContainer”
func (s *Service) ResolveAccessItemsForContainer(req *api.CreateContainerRequest) error

ResolveAccessItemsForContainer resolves the given access item IDs and merges the resulting env vars and mounts into the container request. Looks up items from the DB/built-ins by ID before resolving.

Refreshes the shell environment cache to ensure the container gets the latest env vars from the user’s shell configuration.

func (s *Service) RestartProject(ctx context.Context, projectID, agentType string) (*ProjectResult, error)

RestartProject restarts the container for the given project. If bind mount sources are stale (e.g. after a Nix Home Manager generation switch), the restart is blocked and a StaleMountsError is returned so the UI can warn the user. Returns ErrBudgetExceeded if the project is over budget and the preventStart enforcement action is enabled.

func (s *Service) RestartSessionWatcher(projectID, containerName, agentType, workspaceDir string)

RestartSessionWatcher stops any existing watcher for the project and starts a new one. Used when a container is restarted or renamed.

func (s *Service) ResumeSessionWatchers(ctx context.Context)

ResumeSessionWatchers starts session watchers for all projects that have a running container. Called at startup so JSONL event parsing resumes without requiring a container restart.

func (s *Service) RevealInFileManager(path string) error

RevealInFileManager opens the given directory in the host’s file manager. Returns an error if the path does not exist or is not a directory.

func (s *Service) SendWorktreeInput(ctx context.Context, projectID, agentType, worktreeID string, req api.WorktreeInputRequest) error

SendWorktreeInput sends text to a worktree’s tmux pane via docker exec. Returns ErrNotFound if the project or tmux session doesn’t exist.

func (s *Service) StartSessionWatcher(projectID, containerName, agentType, workspaceDir string)

StartSessionWatcher creates and starts a JSONL session file watcher for a project. The watcher tails session files, parses events, and feeds them into the eventbus pipeline via the event handler callback.

No-op if the project is already being watched, or if the agent registry or event handler are not configured.

func (s *Service) StopAllSessionWatchers()

StopAllSessionWatchers stops all active session watchers. Called during graceful shutdown.

func (s *Service) StopProject(ctx context.Context, projectID, agentType string) (*ProjectResult, error)

StopProject stops the container for the given project. Before stopping, it captures cost from the agent’s config file via docker exec and persists it to the DB so cost data survives the container stop.

func (s *Service) StopSessionWatcher(projectID, agentType string)

StopSessionWatcher stops and removes the session watcher for a project+agent. Records a cooldown timestamp to prevent rapid restarts during crash-loops. No-op if no watcher is running for the given key.

func (s *Service) UpdateAccessItem(id string, req api.UpdateAccessItemRequest) (*access.Item, error)

UpdateAccessItem updates an access item. For built-in items, this saves a customized copy to the DB (overriding the default). For user items, this updates the existing DB row.

func (s *Service) UpdateContainer(ctx context.Context, projectID, agentType string, req api.CreateContainerRequest) (*ContainerResult, error)

UpdateContainer updates a project’s container configuration. If only lightweight settings changed (name, skipPermissions, costBudget), the container is updated in-place without recreation. Otherwise the container is fully recreated with the new configuration.

func (s *Service) UpdateSettings(ctx context.Context, req UpdateSettingsRequest) (*UpdateSettingsResult, error)

UpdateSettings applies setting changes and returns whether a restart is required.

func (s *Service) UploadClipboard(ctx context.Context, projectID, agentType string, content []byte, mimeType string) (*api.ClipboardUploadResponse, error)

UploadClipboard stages a file in the container’s clipboard directory for the xclip shim to serve. Used by the web frontend to enable image paste — the browser uploads the image, then sends Ctrl+V to the PTY. The agent calls xclip, and the shim returns the staged file.

func (s *Service) ValidateContainer(ctx context.Context, projectID, agentType string) (*ValidateContainerResult, error)

ValidateContainer checks whether a container has the required Warden terminal infrastructure installed.

func (s *Service) ValidateProjectTemplate(data []byte) (*api.ProjectTemplate, error)

ValidateProjectTemplate unmarshals and sanitizes a raw JSON template body. Used by the import-from-file flow where the frontend sends the file contents rather than a host path.

func (s *Service) WriteAuditCSV(w io.Writer, filters api.AuditFilters) error

WriteAuditCSV writes audit entries as CSV to the given writer.

ServiceDeps holds all dependencies for constructing a Service. Using a struct because the constructor has many parameters.

type ServiceDeps struct {
Engine engine.Client
DB *db.Store
Store *eventbus.Store
Audit *db.AuditWriter
Registry *agent.Registry
EventWatcher *hook.Watcher
EventHandler func(event.ContainerEvent)
HomeDir string
DockerAvailable bool
// EnvResolver provides environment variable lookup for access item
// detection and resolution. When nil, a default ProcessEnvResolver
// is used (direct os.LookupEnv delegation).
EnvResolver access.EnvResolver
}

SessionContext identifies the project and worktree a parsed event belongs to.

type SessionContext struct {
ProjectID string
ContainerName string
AgentType string
WorktreeID string
}

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type SessionCostEntry = api.SessionCostEntry

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type SettingsResponse = api.SettingsResponse

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type TimeRange = api.TimeRange

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ToolCount = api.ToolCount

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type UpdateSettingsRequest = api.UpdateSettingsRequest

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type UpdateSettingsResult = api.UpdateSettingsResult

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type ValidateContainerResult = api.ValidateContainerResult

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type WorktreeInputRequest = api.WorktreeInputRequest

Type aliases for backward compatibility. Service methods return these types; the canonical definitions live in the api package.

type WorktreeResult = api.WorktreeResult

Generated by gomarkdoc