dispatcher

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 4, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrRuntimeUnsupported = errors.New("job runtime operation is not supported")

Functions

func BuildCommandJobSpec

func BuildCommandJobSpec(input CommandJobInput) *orchestrator.JobSpec

BuildCommandJobSpec converts a resolved CommandJobInput into a JobSpec. It is the canonical place for CommandSpec → JobSpec translation: builtin policy construction, host command conversion, and visibility derivation. Neither sandbox construction nor broker registration is performed here.

func BuildSandboxSpec

func BuildSandboxSpec(spec *orchestrator.JobSpec, rt SandboxRuntimeInfo) (sandbox.Spec, error)

BuildSandboxSpec turns a business-level JobSpec and dispatcher-side runtime facts into a primitive sandbox.Spec. It contains no role-aware switch: the mount set and environment are derived purely from JobSpec.Visibility, HostCommands, Instruction and Argv.

func ClearWorktreeCleaned

func ClearWorktreeCleaned(dbtx db.DBTX, taskID string) error

ClearWorktreeCleaned sets cleaned_at to NULL for the given task's worktree.

func CreateJob

func CreateJob(dbtx db.DBTX, j *Job) error

func CreateWorktree

func CreateWorktree(dbtx db.DBTX, w *Worktree) error

func FindDaemonShutdownAbortedTasks

func FindDaemonShutdownAbortedTasks(conn *sql.DB) ([]string, error)

FindDaemonShutdownAbortedTasks returns IDs of tasks currently in aborted status whose most recent aborted-transition action carries payload.code == "daemon_shutdown". Use this on daemon startup to auto-reopen tasks that were interrupted by the previous shutdown.

"Most recent" means the latest action with to_status='aborted' for that task (ordered by created_at desc). If a task was aborted by daemon_shutdown and then aborted again later for another reason, the later code wins and the task is NOT returned — matching the intuition that only freshly-shutdown tasks deserve auto-reopen.

func GenerateKey

func GenerateKey() []byte

GenerateKey creates a random 32-byte AES-256 key.

func LoadOrCreateKey

func LoadOrCreateKey(path string) ([]byte, error)

LoadOrCreateKey loads the master key from the given path, or creates a new one if it doesn't exist.

func MarkStaleExecutingTasksAborted

func MarkStaleExecutingTasksAborted(conn *sql.DB) (int, error)

MarkStaleExecutingTasksAborted transitions all tasks in "executing" status to "aborted" and records a daemon_restart abort action for each. Call this on server startup after MarkStaleJobsFailed. Returns the number of tasks transitioned.

func MarkStaleJobsFailed

func MarkStaleJobsFailed(dbtx db.DBTX) error

MarkStaleJobsFailed marks all running jobs as failed. Call this on server startup to clean up jobs left in running state from a previous crash or restart.

func MarkWorktreeCleaned

func MarkWorktreeCleaned(dbtx db.DBTX, taskID string) error

func PoliciesToSandbox

func PoliciesToSandbox(in map[string]orchestrator.BuiltinPolicy) map[string]sandbox.BuiltinPolicy

PoliciesToSandbox converts the orchestrator-owned neutral BuiltinPolicy representation into the sandbox-layer BuiltinPolicy the broker understands. dispatcher is the only layer allowed to bridge both sides.

func ReadTranscript

func ReadTranscript(rootDir, runtimeID string) ([]byte, error)

ReadTranscript reads the transcript.log for the given runtimeID from rootDir. Returns os.ErrNotExist if the transcript file does not exist (e.g. runtime was gc'd).

func ResolveHostCommands

func ResolveHostCommands(
	builtins []string,
	hostCommands map[string]orchestrator.CommandDef,
	projectDir string,
	lookPath func(string) (string, error),
) (map[string]orchestrator.CommandDef, error)

ResolveHostCommands turns the orchestrator-side host command map (keyed by the user-declared name) into a map keyed by the absolute path that the boid shim will be bind-mounted at inside the sandbox. The absolute path is also written back into each entry's Path so the broker spawns the right binary on the host without a second lookup.

The same map is used both as the broker's policy table and as the source of shim mount targets; sharing a single resolved view guarantees that the `os.Executable()` value the shim sends will match a key the broker holds.

Builtins ("boid", "git") are excluded — they have dedicated mounts and builtin policies elsewhere.

`projectDir` is used to resolve relative paths declared in host_commands.<name>.path. `lookPath` is parameterized for tests; production callers pass exec.LookPath.

func StatTranscript

func StatTranscript(rootDir, runtimeID string) (os.FileInfo, error)

StatTranscript returns os.FileInfo for the transcript.log of the given runtimeID. Returns os.ErrNotExist if the file does not exist (e.g. runtime was gc'd).

func UpdateJob

func UpdateJob(dbtx db.DBTX, j *Job) error

Types

type CommandBroker

type CommandBroker interface {
	RegisterCommands(commands map[string]orchestrator.CommandDef, builtinPolicies map[string]sandbox.BuiltinPolicy, ctx sandbox.TokenContext, resolve SecretResolver) string
	UnregisterCommandToken(token string)
	SocketPath() string
}

CommandBroker is the dispatcher-owned behavior contract for host command brokering. The execution context is the canonical sandbox.TokenContext so adapters do not need to translate between dispatcher- and sandbox-side context shapes.

type CommandJobInput

type CommandJobInput struct {
	ProjectID          string
	ProjectWorkDir     string
	Argv               []string
	Env                map[string]string
	HostCommands       map[string]orchestrator.HostCommandSpec
	AdditionalBindings []orchestrator.BindMount
	Readonly           bool
	// Interactive forces TTY allocation. CLI exec detects the real terminal
	// state and sets this before calling BuildCommandJobSpec; daemon API (Web
	// UI) sets this to true unconditionally.
	Interactive bool
	// Name is the human-readable display name for the session. Callers set
	// this to either an explicit user-provided value or the project command
	// name as an automatic fallback. Empty leaves DisplayName unset.
	Name string
}

CommandJobInput carries the resolved data from a project command definition needed to build an orchestrator.JobSpec. It is shared between the CLI exec path (cmd/exec.go) and the daemon API path (POST /api/projects/.../execute).

type CreateOpts

type CreateOpts struct {
	// CheckoutBranch, when non-empty, causes Create to check out an existing
	// branch directly (git worktree add <path> <branch>) rather than creating
	// a new boid/<id8> branch. Used for root tasks (ParentID == "") so the
	// worktree HEAD matches task.BaseBranch directly (P2).
	CheckoutBranch string

	// ForkPoint overrides the start-point used when creating a new boid/<id8>
	// branch (CheckoutBranch == ""). Defaults to baseBranch when empty.
	// Reserved for P3 (child fork from parent HEAD branch); unused in P2.
	ForkPoint string

	// BaseBranchForkPoint is the start point used when the requested
	// baseBranch does not exist yet (ClassifyBaseBranch case 3) and must
	// be created locally. Sourced from ProjectMeta.ForkPoint via
	// Visibility.ForkPoint. Empty falls back to refs/remotes/origin/HEAD;
	// if that is also unset, Create returns an error.
	BaseBranchForkPoint string
}

CreateOpts controls optional worktree creation behaviour.

type Job

type Job struct {
	ID             string    `json:"id"`
	TaskID         string    `json:"task_id"`
	ProjectID      string    `json:"project_id"`
	HandlerID      string    `json:"handler_id"`
	DisplayName    string    `json:"display_name,omitempty"` // persisted via the jobs.display_name column (migration 0027)
	Role           string    `json:"role"`
	RuntimeID      string    `json:"runtime_id,omitempty"`
	Interactive    bool      `json:"interactive"`
	TTY            bool      `json:"tty"`
	Status         JobStatus `json:"status"`
	ExitCode       int       `json:"exit_code,omitempty"`
	Output         string    `json:"output,omitempty"`
	ExecutionState string    `json:"execution_state,omitempty"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
}

func GetJob

func GetJob(dbtx db.DBTX, id string) (*Job, error)

func ListJobsByTask

func ListJobsByTask(dbtx db.DBTX, taskID string) ([]*Job, error)

func ListJobsFiltered

func ListJobsFiltered(dbtx db.DBTX, filter JobFilter) ([]*Job, error)

ListJobsFiltered returns jobs across all tasks matching the given filter.

type JobCompletionResult

type JobCompletionResult struct {
	Output   string
	ExitCode int
}

JobCompletionResult is the result delivered via WaitForJobCtx/CompleteJob.

type JobEventSink

type JobEventSink interface {
	JobCreated(taskID, jobID string)
}

JobEventSink lets the runner report job lifecycle events to a subscriber (typically the web SSE hub) without taking a hard dependency on it. All methods are best-effort: implementations should not block or fail the caller — they exist to push UI refresh hints.

type JobFilter

type JobFilter struct {
	Status       string
	Interactive  *bool // nil = no filter
	TasklessOnly bool  // true = only jobs where task_id IS NULL
}

JobFilter specifies optional filters for listing jobs globally.

type JobRepository

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

func NewJobRepository

func NewJobRepository(db db.DBTX) *JobRepository

func (*JobRepository) CreateJob

func (r *JobRepository) CreateJob(job *Job) error

func (*JobRepository) GetJob

func (r *JobRepository) GetJob(id string) (*Job, error)

func (*JobRepository) ListJobsByTask

func (r *JobRepository) ListJobsByTask(taskID string) ([]*Job, error)

func (*JobRepository) ListJobsFiltered

func (r *JobRepository) ListJobsFiltered(filter JobFilter) ([]*Job, error)

func (*JobRepository) UpdateJob

func (r *JobRepository) UpdateJob(job *Job) error

type JobRuntime

type JobRuntime interface {
	Start(ctx context.Context, spec RuntimeStartSpec) (*RuntimeHandle, error)
	Attach(ctx context.Context, runtimeID string, req RuntimeAttachRequest) error
	Resize(ctx context.Context, runtimeID string, size TerminalSize) error
	Wait(ctx context.Context, runtimeID string) (RuntimeExit, error)
	Stop(ctx context.Context, runtimeID string) error
	// Signal sends a single signal to the runtime's process group without
	// any follow-up SIGKILL. Used by NotifyTask to drive an "agent-stop"
	// SIGUSR1 to run-agent.py while leaving bash / EXIT trap intact (see
	// generateOuterScript / generateInnerScript for the matching `trap ”
	// USR1`). Implementations should be no-op when the runtime has already
	// exited.
	Signal(ctx context.Context, runtimeID string, sig syscall.Signal) error
}

type JobStatus

type JobStatus string
const (
	JobStatusRunning   JobStatus = "running"
	JobStatusCompleted JobStatus = "completed"
	JobStatusFailed    JobStatus = "failed"
)

type LocalRuntime

type LocalRuntime struct {
	RootDir string
	// contains filtered or unexported fields
}

func (*LocalRuntime) Attach

func (r *LocalRuntime) Attach(ctx context.Context, runtimeID string, req RuntimeAttachRequest) error

func (*LocalRuntime) Resize

func (r *LocalRuntime) Resize(_ context.Context, runtimeID string, size TerminalSize) error

func (*LocalRuntime) Signal

func (r *LocalRuntime) Signal(_ context.Context, runtimeID string, sig syscall.Signal) error

Signal delivers a single signal to the runtime's process group without any SIGKILL follow-up. NotifyTask uses this for SIGUSR1 (agent-stop) — the signal is delivered process-group-wide (kill(-pgid, sig)) and processes configured to ignore it via `trap ” USR1` / SIG_IGN survive unaffected. No-op when the runtime session has already exited.

func (*LocalRuntime) Start

func (*LocalRuntime) Stop

func (r *LocalRuntime) Stop(ctx context.Context, runtimeID string) error

func (*LocalRuntime) SubscribeRuntime

func (r *LocalRuntime) SubscribeRuntime(runtimeID string) ([]byte, <-chan []byte, func(), bool)

SubscribeRuntime subscribes to live output of the session identified by runtimeID. Returns the current transcript snapshot, a channel of subsequent chunks, a cancel function to unsubscribe, and whether live streaming is available.

func (*LocalRuntime) SupportsAttach

func (r *LocalRuntime) SupportsAttach(runtimeID string) bool

func (*LocalRuntime) Wait

func (r *LocalRuntime) Wait(ctx context.Context, runtimeID string) (RuntimeExit, error)

func (*LocalRuntime) WriteInputRuntime

func (r *LocalRuntime) WriteInputRuntime(runtimeID string, data []byte) error

WriteInputRuntime writes data to the PTY master of the given runtime. Returns nil if the session is not running or has already exited.

type OrchestratorAdapter

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

OrchestratorAdapter adapts dispatcher execution to orchestrator interfaces.

func NewOrchestratorAdapter

func NewOrchestratorAdapter(dispatcher dispatchBackend, planner *orchestrator.DispatchPlanner) *OrchestratorAdapter

func (*OrchestratorAdapter) ExecuteHook

func (a *OrchestratorAdapter) ExecuteHook(ctx context.Context, event *orchestrator.HookFireEvent) (string, error)

func (*OrchestratorAdapter) WaitForJob

type PreparedSandbox

type PreparedSandbox struct {
	OuterPath   string
	RootDir     string
	ScriptPaths []string
	StagingDir  string
}

PreparedSandbox is the concrete launch artifact returned by a provider. RootDir, ScriptPaths, and StagingDir are populated so the runner can remove them after the sandbox runtime has exited. They are optional: zero values mean "nothing to clean up here" (e.g. legacy paths or provider opted out).

type ProjectLookup

type ProjectLookup interface {
	GetProject(id string) (*orchestrator.Project, error)
	ListProjects() ([]*orchestrator.Project, error)
}

ProjectLookup lets dispatcher resolve ProjectID → WorkspaceID and enumerate workspace peers, so workspace-peer authorization and peer-visibility concerns stay inside dispatcher instead of leaking into JobSpec.

type Runner

type Runner struct {
	DB           *sql.DB
	Runtime      JobRuntime
	Broker       CommandBroker
	Sandbox      SandboxPreparer
	SecretStore  *SecretStore
	Worktrees    *WorktreeManager
	TaskLookup   TaskLookup
	Projects     ProjectLookup
	BoidBinary   string
	ServerSocket string
	ProxyPort    *int
	RuntimesDir  string
	JobEvents    JobEventSink // optional; nil disables job lifecycle broadcasts
	// contains filtered or unexported fields
}

func Wire

func Wire(cfg WireConfig) *Runner

func (*Runner) CleanupTaskWindow

func (r *Runner) CleanupTaskWindow(taskID string)

CleanupTaskWindow stops all tracked runtimes associated with a task.

func (*Runner) CompleteJob

func (r *Runner) CompleteJob(jobID string, result JobCompletionResult)

CompleteJob signals the waiting dispatcher that a job has completed.

func (*Runner) Dispatch

func (r *Runner) Dispatch(ctx context.Context, spec *orchestrator.JobSpec, cleanup orchestrator.CleanupFunc) (string, error)

Dispatch launches a sandbox for the given JobSpec. The optional cleanup callback (typically provided by orchestrator's PlanHook for staging dir teardown) runs after the sandbox process has exited.

func (*Runner) ResizeRuntime

func (r *Runner) ResizeRuntime(jobID string, size TerminalSize) error

ResizeRuntime implements RuntimeInputWriter for Runner. It resolves jobID to a runtimeID via the jobs table, then delegates to JobRuntime.Resize.

func (*Runner) SignalJobRuntime

func (r *Runner) SignalJobRuntime(runtimeID string, sig syscall.Signal)

SignalJobRuntime delivers a single signal to the runtime's process group without any SIGKILL follow-up. NotifyTask uses this for SIGUSR1 to ask the agent (run-agent.py) to stop the claude session gracefully — bash and the EXIT trap stay alive (via `trap ” USR1` propagated as SIG_IGN across execve), so payload_patch capture and `boid job done --output-file` continue through the normal completion path. Best-effort: errors at debug level only.

func (*Runner) StopJobRuntime

func (r *Runner) StopJobRuntime(runtimeID string)

StopJobRuntime stops the runtime identified by runtimeID. It is a best-effort operation: errors are logged at debug level only.

func (*Runner) Subscribe

func (r *Runner) Subscribe(jobID string) (snapshot []byte, ch <-chan []byte, cancel func(), ok bool)

Subscribe implements RuntimeSubscriber for Runner. It resolves jobID to a runtimeID via the jobs table, then delegates to LocalRuntime if the runtime supports live streaming.

func (*Runner) UnregisterJob

func (r *Runner) UnregisterJob(jobID string)

UnregisterJob removes the broker token associated with the given job.

func (*Runner) WaitForJob

func (r *Runner) WaitForJob(jobID string) <-chan JobCompletionResult

WaitForJob registers a channel that will receive the job completion result.

func (*Runner) WaitForJobCtx

func (r *Runner) WaitForJobCtx(ctx context.Context, jobID string) (JobCompletionResult, error)

WaitForJobCtx waits for job completion with context cancellation.

A non-zero exit is NOT reported as an error — the caller inspects result.ExitCode. Only true wait-machinery failures (ctx cancel) produce a non-nil error. This lets the orchestrator record `hook_fired` actions for failing hooks the same way as successful ones; prior behavior discarded the partial FiredEvents when any hook exited non-zero.

func (*Runner) WriteInput

func (r *Runner) WriteInput(jobID string, data []byte) error

WriteInput implements RuntimeInputWriter for Runner. It resolves jobID to a runtimeID via the jobs table, then delegates to LocalRuntime.WriteInputRuntime.

type RuntimeAttachRequest

type RuntimeAttachRequest struct {
	Input  io.Reader
	Output io.Writer
	Error  io.Writer
}

type RuntimeExit

type RuntimeExit struct {
	ExitCode int
	// TranscriptPath は子プロセスの stdout/stderr 全量を保存しているファイルへの
	// パス。 silent な exit_code!=0 (transcript が 0 byte) ケースを diag log で
	// 一発判別できるようにするために提供する。 サポートしない runtime は空文字。
	TranscriptPath string
}

type RuntimeHandle

type RuntimeHandle struct {
	ID          string
	Interactive bool
	TTY         bool
}

type RuntimeInputWriter

type RuntimeInputWriter interface {
	WriteInput(jobID string, data []byte) error
	ResizeRuntime(jobID string, size TerminalSize) error
}

RuntimeInputWriter provides write access to a running job's PTY input.

type RuntimeStartSpec

type RuntimeStartSpec struct {
	JobID       string
	TaskID      string
	ProjectID   string
	HandlerID   string
	Role        string
	Command     string
	Interactive bool
	TTY         bool
	// DesiredID, when non-empty, asks the runtime to use this UUID as its
	// session identifier instead of generating a fresh one. The caller uses
	// this to pre-allocate a runtime directory (e.g. for a per-sandbox docker
	// proxy socket) before Start is called. The runtime honours the request
	// on a best-effort basis: if the directory already exists or the ID is
	// otherwise unusable, Start returns an error.
	DesiredID string
}

type RuntimeSubscriber

type RuntimeSubscriber interface {
	Subscribe(jobID string) (snapshot []byte, ch <-chan []byte, cancel func(), ok bool)
}

RuntimeSubscriber subscribes to live output of a running job identified by jobID.

type SandboxPreparer

type SandboxPreparer interface {
	PrepareSandbox(spec sandbox.Spec) (*PreparedSandbox, error)
}

SandboxPreparer prepares concrete launch artifacts from a sandbox.Spec. The orchestrator-owned BuildSandboxSpec builds the spec; dispatcher only materializes scripts and tracks artifacts.

func NewSandboxPreparer

func NewSandboxPreparer() SandboxPreparer

NewSandboxPreparer returns the sandbox provider adapter. It is a thin wrapper over sandbox.Prepare: all role-aware translation lives in orchestrator.BuildSandboxSpec.

type SandboxRuntimeInfo

type SandboxRuntimeInfo struct {
	JobID        string
	BoidBinary   string
	ServerSocket string
	ProxyPort    int

	BrokerSocket string
	BrokerToken  string

	// WorktreeDir is set by dispatcher when Visibility.UseWorktree is true,
	// having been resolved through its WorktreeManager. Empty otherwise.
	WorktreeDir string

	// WorkspacePeers maps peer project IDs (same workspace, excluding self) to
	// host paths. Dispatcher resolves this from its ProjectLookup so peer
	// visibility/authorization does not leak into orchestrator.JobSpec.
	WorkspacePeers map[string]string

	// StagingDir, when non-empty, is added to CleanupPaths so the sandbox
	// setup script removes it on teardown in addition to the caller-supplied
	// CleanupFunc.
	StagingDir string

	// RootDir, when non-empty, overrides the default per-sandbox ROOT.
	RootDir string

	// Foreground indicates whether the job runs in the foreground (user-facing
	// stdout/stderr, no trap-based completion callback). boid exec sets this
	// to true; hook/gate jobs leave it false so stdout is captured and a
	// `boid job done` trap posts completion back to the daemon.
	Foreground bool

	// ResolvedHostCommands is the absolute-path-keyed view of spec.HostCommands
	// produced by ResolveHostCommands. The same map is registered with the
	// broker so the shim's os.Executable() lookup hits a known key. Empty when
	// the job declares no host commands.
	ResolvedHostCommands map[string]orchestrator.CommandDef

	// DockerEnabled, when true, indicates capabilities.docker is declared in
	// project.yaml.
	DockerEnabled bool

	// ProxySocketPath, when non-empty, is the host-side Unix socket path of the
	// per-sandbox docker proxy. sandbox_builder bind-mounts it into the sandbox
	// at the fixed sandbox path (see dockerProxySandboxSocket) and injects
	// DOCKER_HOST / CONTAINER_HOST / TESTCONTAINERS_* env vars.
	// Set by the runner before BuildSandboxSpec when DockerEnabled is true.
	ProxySocketPath string
}

SandboxRuntimeInfo carries the dispatcher-internal facts that are required to turn an orchestrator.JobSpec into a sandbox.Spec but that orchestrator never needs to know: job id, broker plumbing, proxy port, boid binary location, server socket path, resolved worktree directory, staging dirs.

type SecretResolver

type SecretResolver func(key string) (string, error)

SecretResolver resolves a secret key into its plaintext value.

type SecretStore

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

SecretStore provides encrypted secret storage backed by SQLite.

func NewSecretStore

func NewSecretStore(d *sql.DB, key []byte) (*SecretStore, error)

NewSecretStore creates a store with the given database and encryption key.

func (*SecretStore) Delete

func (s *SecretStore) Delete(namespace, key string) error

func (*SecretStore) Get

func (s *SecretStore) Get(namespace, key string) (string, error)

func (*SecretStore) List

func (s *SecretStore) List(namespace string) ([]string, error)

func (*SecretStore) Set

func (s *SecretStore) Set(namespace, key, value string) error

type TaskLookup

type TaskLookup interface {
	GetTask(id string) (*orchestrator.Task, error)
}

TaskLookup mirrors the subset of orchestrator.TaskLookup the dispatcher needs for worktree resolution. Kept as an interface so tests can stub it.

type TerminalSize

type TerminalSize struct {
	Rows int
	Cols int
}

type WireConfig

type WireConfig struct {
	DB          *sql.DB
	Runtime     JobRuntime
	Broker      CommandBroker
	Sandbox     SandboxPreparer
	SecretStore *SecretStore

	// Worktrees resolves per-task git worktrees when a JobSpec declares
	// Visibility.UseWorktree. Pass nil to disable worktree-backed jobs.
	Worktrees  *WorktreeManager
	TaskLookup TaskLookup
	Projects   ProjectLookup

	// BoidBinary is the host path to the boid executable that should be
	// bind-mounted into sandboxes.
	BoidBinary string
	// ServerSocket is the host path to the daemon UNIX socket (for boid exec
	// jobs that talk to boid over HTTP from inside the sandbox).
	ServerSocket string
	// ProxyPort, when non-zero, enables HTTP(S) proxy environment variables
	// pointing at host-gateway:<ProxyPort>.
	ProxyPort *int
	// RuntimesDir is the root directory where per-sandbox runtime directories
	// are created. When non-empty and DockerEnabled, the runner pre-allocates a
	// runtime directory here to host the per-sandbox docker proxy socket and
	// resource ledger.
	RuntimesDir string
}

type Worktree

type Worktree struct {
	ID         string     `json:"id"`
	TaskID     string     `json:"task_id"`
	ProjectID  string     `json:"project_id"`
	Path       string     `json:"path"`
	Branch     string     `json:"branch"`
	BaseBranch string     `json:"base_branch"`
	CreatedAt  time.Time  `json:"created_at"`
	CleanedAt  *time.Time `json:"cleaned_at,omitempty"`
}

func GetWorktreeByTask

func GetWorktreeByTask(dbtx db.DBTX, taskID string) (*Worktree, error)

func ListActiveWorktrees

func ListActiveWorktrees(dbtx db.DBTX) ([]*Worktree, error)

type WorktreeManager

type WorktreeManager struct {
	RootDir string // e.g. ~/.local/share/boid/worktrees
	DB      *sql.DB
	GitBin  string // path to git binary; defaults to "git"
}

WorktreeManager handles git worktree lifecycle for task isolation.

func (*WorktreeManager) CleanOrphaned

func (m *WorktreeManager) CleanOrphaned(resolve func(taskID, projectID string) (string, string, error)) error

func (*WorktreeManager) CleanupForTask

func (m *WorktreeManager) CleanupForTask(taskID, projectDir, newStatus string) error

func (*WorktreeManager) Create

func (m *WorktreeManager) Create(projectDir, projectID, taskID, baseBranch string, opts CreateOpts) (*Worktree, error)

func (*WorktreeManager) EnforceHeadOnBaseBranch

func (m *WorktreeManager) EnforceHeadOnBaseBranch(projectDir, baseBranch string) error

EnforceHeadOnBaseBranch is the Phase 2-2 case 1 HEAD guard. Supervisors running in the project dir (worktree=false) require the project HEAD to remain on the resolved baseBranch from task creation time through job dispatch. A mismatch means the user (or another process) has moved the project branch while a supervisor task was queued; running the supervisor against an unexpected branch silently is the foot-gun that this guard rejects.

Returns nil when baseBranch is empty (no expectation to enforce), nil on a successful match, and an error otherwise. Detached HEAD is reported as an error: a case 1 task should never have been classified for a detached project at creation time, so this code path indicates a state divergence that must abort the run.

func (*WorktreeManager) EnsureBindingTargets

func (m *WorktreeManager) EnsureBindingTargets(worktreePath string, bindings []orchestrator.BindMount, projectDir string) error

EnsureBindingTargets pre-creates additional_bindings mount targets that fall under worktreePath, so a subsequent readonly bind-remount of the worktree does not trigger EROFS when the sandbox setup script tries to mkdir those targets. Bindings whose target lives outside the worktree (and bindings that would escape the worktree via path traversal) are skipped — the sandbox layer handles those during normal mount setup.

projectDir is used to expand the ${PROJECT_WORKDIR} token. ${WORKTREE} expands to worktreePath. Other tokens are passed through unchanged (matching expandWorktreeBindings, which keeps them literal for debuggability).

Idempotent: dirs that already exist are left alone (os.MkdirAll). Safe to call after either Create or Recreate.

func (*WorktreeManager) Get

func (m *WorktreeManager) Get(taskID string) (*Worktree, error)

func (*WorktreeManager) Recreate

func (m *WorktreeManager) Recreate(projectDir string, taskID string) (*Worktree, error)

Recreate reconstructs a previously cleaned worktree by fetching from the remote branch. It reads the existing DB record (even if cleaned_at is set), fetches the remote branch, creates a new worktree, and clears the cleaned_at timestamp.

func (*WorktreeManager) Remove

func (m *WorktreeManager) Remove(projectDir, taskID string, deleteBranch bool) error

type WorktreeRepository

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

func NewWorktreeRepository

func NewWorktreeRepository(db db.DBTX) *WorktreeRepository

func (*WorktreeRepository) CreateWorktree

func (r *WorktreeRepository) CreateWorktree(worktree *Worktree) error

func (*WorktreeRepository) GetWorktreeByTask

func (r *WorktreeRepository) GetWorktreeByTask(taskID string) (*Worktree, error)

func (*WorktreeRepository) ListActiveWorktrees

func (r *WorktreeRepository) ListActiveWorktrees() ([]*Worktree, error)

func (*WorktreeRepository) MarkWorktreeCleaned

func (r *WorktreeRepository) MarkWorktreeCleaned(taskID string) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL