Documentation
¶
Index ¶
- Constants
- Variables
- func AttachmentsRootForTask(dataHome, taskID string) string
- func BuildFlatItems(tasks []*orchestrator.Task, projectNames map[string]string) []components.TreeItem
- func BuildTreeItems(tasks []*orchestrator.Task, projectNames map[string]string) []components.TreeItem
- func EnsureAttachmentsDir(dataHome, taskID string) (string, error)
- func ResolveTaskField(task *orchestrator.Task, actions orchestrator.LifecycleStore, path string) (string, error)
- func SanitizeAttachmentName(raw string) (string, error)
- func SaveMultipartAttachments(dataHome, taskID string, files []*multipart.FileHeader) ([]string, error)
- func ValidateAttachmentHeaders(files []*multipart.FileHeader) error
- type ActionApplication
- type ActionHandler
- type ActionStore
- type AnswerTaskRequest
- type ApplyActionRequest
- type BlockingAskRegistry
- func (r *BlockingAskRegistry) Cancel(qid string)
- func (r *BlockingAskRegistry) Has(qid string) bool
- func (r *BlockingAskRegistry) Notify(qid, answer string) bool
- func (r *BlockingAskRegistry) Register(taskID, qid string) error
- func (r *BlockingAskRegistry) Wait(ctx context.Context, qid string) (string, error)
- type BrokerHandler
- type BrokerRegisterRequest
- type BrokerRegisterResponse
- type BrokerRegistry
- type CreateProjectRequest
- type CreateTaskRequest
- type DeviceGCStore
- type DispatchCoordinator
- type DuplicateTaskRequest
- type GCAppService
- type GCHandler
- type GCService
- type GCStore
- type GlobalJobStore
- type HookService
- type ImportError
- type ImportResult
- type Job
- type JobCompletion
- type JobDoneRequest
- type JobHandler
- func (h *JobHandler) AgentStop(w http.ResponseWriter, r *http.Request)
- func (h *JobHandler) Done(w http.ResponseWriter, r *http.Request)
- func (h *JobHandler) Get(w http.ResponseWriter, r *http.Request)
- func (h *JobHandler) List(w http.ResponseWriter, r *http.Request)
- func (h *JobHandler) Log(w http.ResponseWriter, r *http.Request)
- func (h *JobHandler) Patch(w http.ResponseWriter, r *http.Request)
- func (h *JobHandler) Routes() chi.Router
- type JobLifecycle
- type JobListFilter
- type JobLogReader
- type JobLogSSEHandler
- type JobStatus
- type JobStore
- type JobWithContext
- type LoginHandler
- type MetaStore
- type Notifier
- type NotifyTaskRequest
- type Pairer
- type ProjectAppService
- func (s *ProjectAppService) CreateProject(workDir string) (*orchestrator.Project, error)
- func (s *ProjectAppService) DeleteProject(id string) error
- func (s *ProjectAppService) GetProject(id string) (*orchestrator.Project, error)
- func (s *ProjectAppService) ListProjects(workspaceID string) ([]*orchestrator.Project, error)
- func (s *ProjectAppService) ListWorkspaces() ([]*orchestrator.WorkspaceSummary, error)
- func (s *ProjectAppService) ReloadProjects() (*ProjectReloadResult, error)
- func (s *ProjectAppService) ResolveProjectRef(ref string) ([]*orchestrator.Project, error)
- func (s *ProjectAppService) SetProjectWorkspace(id, workspaceID string) (*orchestrator.Project, error)
- type ProjectHandler
- func (h *ProjectHandler) Create(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) Delete(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) Get(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) List(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) Reload(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) Routes() chi.Router
- func (h *ProjectHandler) SetWorkspace(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) StartSession(w http.ResponseWriter, r *http.Request)
- type ProjectReloadResult
- type ProjectRepository
- type ProjectService
- type ProjectWorkDirLookup
- type ReopenTaskRequest
- type ReplayHookRequest
- type ReplayHookResult
- type RerunTaskRequest
- type SecretHandler
- func (h *SecretHandler) Delete(w http.ResponseWriter, r *http.Request)
- func (h *SecretHandler) GetValue(w http.ResponseWriter, r *http.Request)
- func (h *SecretHandler) List(w http.ResponseWriter, r *http.Request)
- func (h *SecretHandler) Routes() chi.Router
- func (h *SecretHandler) Set(w http.ResponseWriter, r *http.Request)
- type SecretStore
- type SessionDispatcher
- type SessionHandler
- type SetProjectWorkspaceRequest
- type StartSessionRequest
- type StartSessionResult
- type StatusError
- type TaskAnswerService
- type TaskAppService
- func (s *TaskAppService) AnswerTask(ctx context.Context, taskID, questionID, answer string) error
- func (s *TaskAppService) AskTaskBlocking(ctx context.Context, taskID, question string) (string, error)
- func (s *TaskAppService) CreateTask(req CreateTaskRequest) (*orchestrator.Task, error)
- func (s *TaskAppService) DeleteTask(id string, force bool) error
- func (s *TaskAppService) DuplicateTask(sourceID string, autoStart bool) (*orchestrator.Task, error)
- func (s *TaskAppService) GetTask(id string) (*orchestrator.Task, error)
- func (s *TaskAppService) GetTaskDetail(id string) (*TaskDetailView, error)
- func (s *TaskAppService) GetTaskField(id, path string) (string, error)
- func (s *TaskAppService) ImportTasks(reqs []CreateTaskRequest) (*ImportResult, error)
- func (s *TaskAppService) ListTasks(filter orchestrator.TaskFilter) ([]*orchestrator.Task, error)
- func (s *TaskAppService) NotifyTask(ctx context.Context, ...) error
- func (s *TaskAppService) RerunTask(id string, req RerunTaskRequest) (*orchestrator.Task, error)
- func (s *TaskAppService) UpdateTask(id string, req UpdateTaskRequest) (*orchestrator.Task, error)
- type TaskDetailView
- type TaskEvent
- type TaskEventHub
- type TaskHandler
- func (h *TaskHandler) Answer(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Create(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Delete(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Detail(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Duplicate(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Field(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Get(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Import(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) List(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) ListHooks(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Notify(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Patch(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) ReplayHook(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Rerun(w http.ResponseWriter, r *http.Request)
- func (h *TaskHandler) Routes() chi.Router
- type TaskNotifyService
- type TaskService
- type TaskStore
- type TaskWorkflowService
- func (s *TaskWorkflowService) ApplyAction(ctx context.Context, taskID string, req ApplyActionRequest) (*ActionApplication, error)
- func (s *TaskWorkflowService) CompleteJob(_ context.Context, jobID string, req JobDoneRequest) (*Job, error)
- func (s *TaskWorkflowService) InitDispatch(ctx context.Context)
- func (s *TaskWorkflowService) ListHooksForStatus(taskID, status string) ([]orchestrator.Hook, error)
- func (s *TaskWorkflowService) ReplayHook(ctx context.Context, taskID string, req ReplayHookRequest) (*ReplayHookResult, error)
- func (s *TaskWorkflowService) Shutdown()
- func (s *TaskWorkflowService) StopAgent(runtimeID string)
- func (s *TaskWorkflowService) TriggerDependents(ctx context.Context, taskID string)
- type Transactor
- type TxStore
- type UpdateJobRequest
- type UpdateTaskRequest
- type WSAttachHandler
- type WebAppService
- func (s *WebAppService) AnswerTask(ctx context.Context, taskID, questionID, answer string) error
- func (s *WebAppService) ApplyAction(taskID string, actionType string) error
- func (s *WebAppService) CreateTask(req CreateTaskRequest) (*orchestrator.Task, error)
- func (s *WebAppService) DeleteTask(id string, force bool) error
- func (s *WebAppService) DuplicateTask(id string) (string, error)
- func (s *WebAppService) GetJob(id string) (*JobWithContext, error)
- func (s *WebAppService) GetProjectByID(id string) (*orchestrator.Project, error)
- func (s *WebAppService) GetTaskDetail(id string) (*TaskDetailView, error)
- func (s *WebAppService) ListBehaviors() ([]string, error)
- func (s *WebAppService) ListHooksForStatus(taskID, status string) ([]orchestrator.Hook, error)
- func (s *WebAppService) ListJobs(status string) ([]JobWithContext, error)
- func (s *WebAppService) ListProjects() ([]*orchestrator.Project, error)
- func (s *WebAppService) ListSessions() ([]JobWithContext, error)
- func (s *WebAppService) ListTasks(filter orchestrator.TaskFilter) ([]*orchestrator.Task, error)
- func (s *WebAppService) ListWorkspaces() ([]*orchestrator.WorkspaceSummary, error)
- func (s *WebAppService) ReopenTask(id string, req ReopenTaskRequest) error
- func (s *WebAppService) ReplayHook(ctx context.Context, taskID string, req ReplayHookRequest) (*ReplayHookResult, error)
- func (s *WebAppService) RerunTask(id string, req RerunTaskRequest) error
- func (s *WebAppService) UpdateTask(id string, req UpdateTaskRequest) error
- type WebHandler
- func (h *WebHandler) GetTaskEdit(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) HookReplayList(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) JobDetail(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) JobTerminal(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostAction(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostAnswer(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostDelete(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostDuplicate(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostEdit(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostHookReplay(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostReopen(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostRerun(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostStartSession(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) PostTaskCreate(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) QuestionPage(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) ReopenForm(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) Routes() chi.Router
- func (h *WebHandler) SessionList(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) SessionNew(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) TaskDetail(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) TaskDetailFragment(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) TaskEvents(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) TaskList(w http.ResponseWriter, r *http.Request)
- func (h *WebHandler) TaskNew(w http.ResponseWriter, r *http.Request)
- type WebManagementHandler
- func (h *WebManagementHandler) DeleteAllDevices(w http.ResponseWriter, r *http.Request)
- func (h *WebManagementHandler) DeleteDevice(w http.ResponseWriter, r *http.Request)
- func (h *WebManagementHandler) GetDevices(w http.ResponseWriter, r *http.Request)
- func (h *WebManagementHandler) PostPair(w http.ResponseWriter, r *http.Request)
- func (h *WebManagementHandler) Routes() chi.Router
- type WebService
- type WorkflowService
- type WorkspaceHandler
- type WorktreeCleaner
Constants ¶
const ( AttachmentMaxFileBytes int64 = 10 * 1024 * 1024 // 10 MB per file AttachmentMaxTotalBytes int64 = 30 * 1024 * 1024 // 30 MB per task dir AttachmentMaxNameBytes = 255 )
Attachment-related constants. Values are also enforced client-side in web/static/boid-paste-attach.js; keep the two in sync.
Variables ¶
var AttachmentAllowedExts = map[string]bool{ ".png": true, ".jpg": true, ".jpeg": true, ".webp": true, ".txt": true, ".md": true, ".json": true, ".log": true, }
AttachmentAllowedExts is the file-extension allowlist for uploads. Both images (for screenshot paste) and a few text formats (for logs / json snippets) are accepted.
var ErrAskPending = errors.New("task_ask: another question is pending")
ErrAskPending is returned by BlockingAskRegistry.Register when the task already has an outstanding blocking ask. It implements decision B1: a second concurrent `boid task ask` for the same task fails immediately rather than queueing behind the first.
Functions ¶
func AttachmentsRootForTask ¶ added in v0.0.7
AttachmentsRootForTask resolves the on-disk attachments directory for the given task. dataHome is the data root (e.g. filepath.Dir(cfg.DBPath)), the same convention as runtimesDirFor.
func BuildFlatItems ¶
func BuildFlatItems(tasks []*orchestrator.Task, projectNames map[string]string) []components.TreeItem
BuildFlatItems returns tasks as a flat list (Depth=0, HasChildren=false, ParentID=""). Used for the "closed" status view where tree structure is irrelevant.
func BuildTreeItems ¶
func BuildTreeItems(tasks []*orchestrator.Task, projectNames map[string]string) []components.TreeItem
BuildTreeItems takes a flat task list and returns items in DFS tree order with depth and visual-parent info. Siblings appear in input order. Tasks whose ParentID is absent from the list are treated as roots. Cycles in ParentID references are detected via a visited set and skipped.
projectNames maps project ID to display name; pass nil to skip name resolution.
func EnsureAttachmentsDir ¶ added in v0.0.7
EnsureAttachmentsDir creates the per-task attachments directory if it does not already exist. Used by handlers right after task creation / before accepting answer attachments so the bind mount has a non-empty source.
func ResolveTaskField ¶
func ResolveTaskField(task *orchestrator.Task, actions orchestrator.LifecycleStore, path string) (string, error)
ResolveTaskField returns the value at the given dotted path against the task.
Path resolution:
- Each dot-separated segment is a JSON key.
- The first segment is matched against top-level Task fields (via JSON tags). When it doesn't match, the path is implicitly resolved inside `payload`, so traits like `awaiting.question`, `artifact.report`, and `lifecycle.abort.message` work without an explicit `payload.` prefix.
- `lifecycle` is a computed trait derived from action history. When lifecycle is referenced, ResolveTaskField calls DeriveLifecycle (when actions is non-nil) and injects the result under `payload.lifecycle` before traversal.
Output format:
- Strings: returned unquoted.
- Numbers and booleans: stringified.
- Objects and arrays: compact JSON.
- Missing path: returns "" with no error.
- Traversing into a scalar with remaining path segments: error.
func SanitizeAttachmentName ¶ added in v0.0.7
SanitizeAttachmentName validates a user-supplied filename and returns the cleaned basename. It rejects anything that doesn't match attachmentNameRe, exceeds the byte cap, has a disallowed extension, or contains only a dot prefix (".env" etc., which the regex would otherwise allow).
func SaveMultipartAttachments ¶ added in v0.0.7
func SaveMultipartAttachments(dataHome, taskID string, files []*multipart.FileHeader) ([]string, error)
SaveMultipartAttachments persists every file under the multipart form field "attachments" into the task-scoped attachments directory. It enforces:
- per-file size cap (AttachmentMaxFileBytes)
- aggregate task-dir size cap (AttachmentMaxTotalBytes)
- filename sanitization (SanitizeAttachmentName)
On success it returns the list of saved basenames. On any error it removes the files it had partially written during this call so the caller can surface the error without leaving orphans behind.
func ValidateAttachmentHeaders ¶ added in v0.0.7
func ValidateAttachmentHeaders(files []*multipart.FileHeader) error
ValidateAttachmentHeaders runs the size + name checks that SaveMultipartAttachments would apply, without touching the filesystem. Web handlers call this *before* creating the task so a bad upload (oversized, bad extension, disallowed name) doesn't leave a half-created task behind.
Types ¶
type ActionApplication ¶
type ActionApplication struct {
Task *orchestrator.Task `json:"task"`
Action *orchestrator.Action `json:"action"`
MatchedHooks []string `json:"matched_hooks,omitempty"`
}
type ActionHandler ¶
type ActionHandler struct {
Service WorkflowService
}
func (*ActionHandler) Apply ¶
func (h *ActionHandler) Apply(w http.ResponseWriter, r *http.Request)
func (*ActionHandler) Routes ¶
func (h *ActionHandler) Routes() chi.Router
type ActionStore ¶
type ActionStore interface {
CreateAction(action *orchestrator.Action) error
ListActionsByTask(taskID string) ([]*orchestrator.Action, error)
}
type AnswerTaskRequest ¶
type ApplyActionRequest ¶
type ApplyActionRequest struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload,omitempty"`
}
type BlockingAskRegistry ¶ added in v0.0.6
type BlockingAskRegistry struct {
// contains filtered or unexported fields
}
BlockingAskRegistry coordinates harness-independent blocking Q&A. When an agent calls `boid task ask`, the broker holds the RPC connection open and the server-side handler blocks in Wait until the user/supervisor answers (via TaskHandler.Answer → AnswerTask → Notify) or the context is cancelled (daemon shutdown / agent disconnect).
The registry is purely in-memory: a daemon restart drops every pending ask. That is consistent — the sandbox process backing the blocked RPC dies with the daemon too, so there is nothing to resume.
func NewBlockingAskRegistry ¶ added in v0.0.6
func NewBlockingAskRegistry() *BlockingAskRegistry
NewBlockingAskRegistry returns an initialised registry.
func (*BlockingAskRegistry) Cancel ¶ added in v0.0.6
func (r *BlockingAskRegistry) Cancel(qid string)
Cancel removes the registration for qid (and the task it belongs to). It does not unblock Wait by itself — a blocked Wait observes ctx cancellation — but it frees the B1 slot so the task can ask again. Idempotent and safe to defer.
func (*BlockingAskRegistry) Has ¶ added in v0.0.6
func (r *BlockingAskRegistry) Has(qid string) bool
Has reports whether qid currently has a registration. Intended for tests and diagnostics.
func (*BlockingAskRegistry) Notify ¶ added in v0.0.6
func (r *BlockingAskRegistry) Notify(qid, answer string) bool
Notify delivers answer to the waiter registered for qid. It returns true when a registration exists and the answer was accepted, false when no waiter is registered (e.g. the agent already disconnected and Cancel ran) or an answer was already delivered. The send is non-blocking thanks to the buffered channel, so Notify never blocks even if Wait has not yet been scheduled.
func (*BlockingAskRegistry) Register ¶ added in v0.0.6
func (r *BlockingAskRegistry) Register(taskID, qid string) error
Register reserves the answer channel for (taskID, qid). It MUST be called before the task transitions to awaiting so an answer that arrives immediately afterwards is never dropped. The check-and-insert is atomic under the registry lock, so two concurrent asks for the same task can never both succeed.
B1 (relaxed): a second pending ask for the same task fails with ErrAskPending ONLY when it carries a DIFFERENT question id. Re-registering the SAME qid is a re-attach — an agent whose `boid task ask` was killed by a harness command-timeout retrying the identical question — and is allowed. The re-attach installs a fresh channel; any prior waiter is already tearing down on its cancelled context. (Even if its deferred Cancel later clobbers this channel, the durable PendingAnswer path backstops delivery, so at worst the fast path degrades to one extra ask round-trip.)
func (*BlockingAskRegistry) Wait ¶ added in v0.0.6
Wait blocks until an answer is delivered for qid via Notify or ctx is cancelled. qid must have been registered; an unknown qid is a programming error and returns immediately. The caller is responsible for Cancel(qid) cleanup (typically via defer) on every exit path.
type BrokerHandler ¶
type BrokerHandler struct {
Registry BrokerRegistry
}
func (*BrokerHandler) Register ¶
func (h *BrokerHandler) Register(w http.ResponseWriter, r *http.Request)
func (*BrokerHandler) Routes ¶
func (h *BrokerHandler) Routes() chi.Router
type BrokerRegisterRequest ¶
type BrokerRegisterRequest struct {
Commands map[string]orchestrator.HostCommandSpec `json:"commands"`
BuiltinPolicies map[string]sandbox.BuiltinPolicy `json:"builtin_policies,omitempty"`
ProjectID string `json:"project_id,omitempty"`
}
type BrokerRegisterResponse ¶
type BrokerRegisterResponse struct {
Token string `json:"token"`
Socket string `json:"socket"`
// ResolvedHostCommands echoes back the absolute-path-keyed map produced by
// dispatcher.ResolveHostCommands. The caller (boid exec) feeds this into
// SandboxRuntimeInfo so shim bind-mount targets line up with the broker's
// policy keys without re-resolving on the client side.
ResolvedHostCommands map[string]orchestrator.CommandDef `json:"resolved_host_commands,omitempty"`
}
type BrokerRegistry ¶
type BrokerRegistry interface {
RegisterBrokerCommands(commands map[string]orchestrator.HostCommandSpec, builtinPolicies map[string]sandbox.BuiltinPolicy, projectID string) (*BrokerRegisterResponse, error)
}
type CreateProjectRequest ¶
type CreateProjectRequest struct {
WorkDir string `json:"work_dir"`
}
type CreateTaskRequest ¶
type CreateTaskRequest struct {
ID string `json:"id,omitempty"`
ProjectID string `json:"project_id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
Behavior string `json:"behavior,omitempty"`
BehaviorSpec *orchestrator.BehaviorSpec `json:"behavior_spec,omitempty"`
RemoteID string `json:"remote_id,omitempty"`
Payload json.RawMessage `json:"payload,omitempty"`
Instructions json.RawMessage `json:"instructions,omitempty"`
AutoStart bool `json:"auto_start,omitempty"`
Traits []string `json:"traits,omitempty"`
Ref string `json:"ref,omitempty"`
ParentID string `json:"parent_id,omitempty"`
Readonly *bool `json:"readonly,omitempty"`
}
type DeviceGCStore ¶
type DispatchCoordinator ¶
type DispatchCoordinator interface {
DispatchAndAdvance(ctx context.Context, task *orchestrator.Task, meta *orchestrator.ProjectMeta, sm *orchestrator.StateMachine) (*orchestrator.DispatchResult, error)
ReplayHook(ctx context.Context, task *orchestrator.Task, meta *orchestrator.ProjectMeta, sm *orchestrator.StateMachine, hookID string) (*orchestrator.ReplayResult, error)
}
type DuplicateTaskRequest ¶
type DuplicateTaskRequest struct {
AutoStart bool `json:"auto_start"`
}
type GCAppService ¶
type GCAppService struct {
Store GCStore
DeviceStore DeviceGCStore // optional; deletes revoked devices on GC
}
func (*GCAppService) GC ¶
func (s *GCAppService) GC(olderThan time.Duration, dryRun bool) (*orchestrator.GCResult, error)
GC implements orchestrator.GCStore so GCAppService can be passed to GCLoop.
func (*GCAppService) Run ¶
func (s *GCAppService) Run(olderThan time.Duration, dryRun bool) (*orchestrator.GCResult, error)
type GlobalJobStore ¶
type GlobalJobStore interface {
ListJobsWithContext(filter JobListFilter) ([]JobWithContext, error)
}
GlobalJobStore supports cross-task job listing with context (task title, project name).
type HookService ¶
type HookService interface {
ReplayHook(ctx context.Context, taskID string, req ReplayHookRequest) (*ReplayHookResult, error)
ListHooksForStatus(taskID, status string) ([]orchestrator.Hook, error)
}
HookService provides hook replay and hook listing operations.
type ImportError ¶
type ImportResult ¶
type ImportResult struct {
Created int `json:"created"`
Skipped int `json:"skipped"`
Errors []ImportError `json:"errors"`
}
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"`
Role string `json:"role"`
RuntimeID string `json:"runtime_id,omitempty"`
WorkspacePath string `json:"workspace_path,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"`
TranscriptSize int64 `json:"transcript_size,omitempty"`
TranscriptMtime *time.Time `json:"transcript_mtime,omitempty"`
TranscriptIdleSeconds int64 `json:"transcript_idle_seconds,omitempty"`
}
type JobCompletion ¶
type JobDoneRequest ¶
type JobHandler ¶
type JobHandler struct {
Jobs JobStore
Global GlobalJobStore // optional: enables cross-task listing when task_id is absent
Service WorkflowService
LogReader JobLogReader // optional: enables static GET /{id}/log
SSEHandler http.Handler // optional: enables SSE streaming for GET /{id}/log?follow=true
}
func (*JobHandler) AgentStop ¶
func (h *JobHandler) AgentStop(w http.ResponseWriter, r *http.Request)
AgentStop asks the daemon to deliver SIGUSR1 to the runtime pgrp. claude.Adapter.Run() catches the signal and forwards SIGTERM to the claude child while the surrounding runner-inner-child keeps running and posts `boid job done --output-file payload_patch.json` through the broker — that callback remains the canonical CompleteJob caller, preserving the agent's session id without racing against UnregisterJob. See WorkflowService.StopAgent for the lifecycle rationale.
func (*JobHandler) Done ¶
func (h *JobHandler) Done(w http.ResponseWriter, r *http.Request)
func (*JobHandler) Get ¶
func (h *JobHandler) Get(w http.ResponseWriter, r *http.Request)
func (*JobHandler) List ¶
func (h *JobHandler) List(w http.ResponseWriter, r *http.Request)
func (*JobHandler) Log ¶
func (h *JobHandler) Log(w http.ResponseWriter, r *http.Request)
func (*JobHandler) Patch ¶ added in v0.0.4
func (h *JobHandler) Patch(w http.ResponseWriter, r *http.Request)
func (*JobHandler) Routes ¶
func (h *JobHandler) Routes() chi.Router
type JobLifecycle ¶
type JobLifecycle interface {
CompleteJob(jobID string, result JobCompletion)
UnregisterJob(jobID string)
CleanupTaskWindow(taskID string)
StopJobRuntime(runtimeID string)
// SignalJobRuntime delivers a single Unix signal to the runtime's process
// group. Phase 3-b uses it to graceful-stop the agent (SIGUSR1) without
// tearing down the surrounding sandbox runtime: claude.Adapter.Run() has a
// signal.Notify(SIGUSR1) handler that translates the group signal into a
// SIGTERM toward the claude child, then normalises the resulting exit
// status into Result.StoppedByDaemon=true.
SignalJobRuntime(runtimeID string, sig syscall.Signal)
}
type JobListFilter ¶
type JobListFilter struct {
Status string
Interactive *bool // nil = no filter
TasklessOnly bool // true = only jobs where task_id IS NULL
}
JobListFilter specifies optional filters for global job listing.
type JobLogReader ¶
type JobLogReader interface {
ReadJobLog(runtimeID string) ([]byte, error)
StatJobLog(runtimeID string) (size int64, mtime time.Time, err error)
}
JobLogReader reads the transcript log for a given runtime.
type JobLogSSEHandler ¶
type JobLogSSEHandler struct {
Subscriber dispatcher.RuntimeSubscriber
Registry *auth.ConnectionRegistry
}
JobLogSSEHandler streams live job output as Server-Sent Events on GET /{id}/log?follow=true.
func (*JobLogSSEHandler) ServeHTTP ¶
func (h *JobLogSSEHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type JobWithContext ¶
type JobWithContext struct {
Job
TaskTitle string `json:"task_title"`
ProjectName string `json:"project_name"`
}
JobWithContext extends Job with task and project metadata for the TUI global view.
type LoginHandler ¶
type LoginHandler struct {
Pairing loginPairing
Signer loginSigner
Store loginDeviceStore
Limiter loginRateLimiter
}
LoginHandler handles /login and /auth.
func (*LoginHandler) GetAuth ¶
func (h *LoginHandler) GetAuth(w http.ResponseWriter, r *http.Request)
func (*LoginHandler) GetLogin ¶
func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request)
func (*LoginHandler) PostLogin ¶
func (h *LoginHandler) PostLogin(w http.ResponseWriter, r *http.Request)
type MetaStore ¶
type MetaStore interface {
Get(id string) (*orchestrator.ProjectMeta, bool)
// GetWithWorkspace returns the project meta with workspace.yaml (kits,
// env, capabilities) hydrated in. Use this whenever the caller dispatches
// hooks or otherwise needs the resolved runtime view.
GetWithWorkspace(ctx context.Context, projectID string) (*orchestrator.ProjectMeta, error)
}
type Notifier ¶
Notifier sends an agent-driven notification for a task. Implementations typically exec a user-configured command. nil-safe at the call site: TaskAppService.NotifyTask returns an error when Notify is unset.
type NotifyTaskRequest ¶
type Pairer ¶
WebManagementHandler serves the CLI management API at /api/web/*. All routes are accessible only via UNIX socket (CLI control plane). Pairer issues pairing codes.
type ProjectAppService ¶
type ProjectAppService struct {
Projects ProjectRepository
Meta interface {
Load(workDir string) (*orchestrator.ProjectMeta, error)
Get(id string) (*orchestrator.ProjectMeta, bool)
Remove(id string)
LoadAll(projects []*orchestrator.Project) []error
SetWorkspaceID(projectID, workspaceID string)
}
// Hydrator is optional. When set, callers that need fully-resolved meta
// (workspace-level capabilities / kits / env merged in + SecretNamespace
// injected) go through GetWithWorkspace instead of the bare Meta.Get path.
// Wired in internal/server/wire.go alongside the workspace store.
Hydrator orchestrator.MetaHydrator
}
func (*ProjectAppService) CreateProject ¶
func (s *ProjectAppService) CreateProject(workDir string) (*orchestrator.Project, error)
func (*ProjectAppService) DeleteProject ¶
func (s *ProjectAppService) DeleteProject(id string) error
func (*ProjectAppService) GetProject ¶
func (s *ProjectAppService) GetProject(id string) (*orchestrator.Project, error)
func (*ProjectAppService) ListProjects ¶
func (s *ProjectAppService) ListProjects(workspaceID string) ([]*orchestrator.Project, error)
func (*ProjectAppService) ListWorkspaces ¶
func (s *ProjectAppService) ListWorkspaces() ([]*orchestrator.WorkspaceSummary, error)
func (*ProjectAppService) ReloadProjects ¶
func (s *ProjectAppService) ReloadProjects() (*ProjectReloadResult, error)
func (*ProjectAppService) ResolveProjectRef ¶
func (s *ProjectAppService) ResolveProjectRef(ref string) ([]*orchestrator.Project, error)
ResolveProjectRef resolves ref to matching projects with the following priority:
- id exact match (returns immediately on first hit)
- name exact match (all projects with that name)
- name substring match, case-insensitive
Returns a single-element slice on unambiguous match, a multi-element slice on ambiguous match, or StatusError{404} when nothing matches.
func (*ProjectAppService) SetProjectWorkspace ¶
func (s *ProjectAppService) SetProjectWorkspace(id, workspaceID string) (*orchestrator.Project, error)
type ProjectHandler ¶
type ProjectHandler struct {
Service ProjectService
SessionDispatcher SessionDispatcher // optional; nil disables the start-session endpoint
}
func (*ProjectHandler) Create ¶
func (h *ProjectHandler) Create(w http.ResponseWriter, r *http.Request)
func (*ProjectHandler) Delete ¶
func (h *ProjectHandler) Delete(w http.ResponseWriter, r *http.Request)
func (*ProjectHandler) Get ¶
func (h *ProjectHandler) Get(w http.ResponseWriter, r *http.Request)
func (*ProjectHandler) List ¶
func (h *ProjectHandler) List(w http.ResponseWriter, r *http.Request)
func (*ProjectHandler) Reload ¶
func (h *ProjectHandler) Reload(w http.ResponseWriter, r *http.Request)
func (*ProjectHandler) Routes ¶
func (h *ProjectHandler) Routes() chi.Router
func (*ProjectHandler) SetWorkspace ¶
func (h *ProjectHandler) SetWorkspace(w http.ResponseWriter, r *http.Request)
func (*ProjectHandler) StartSession ¶ added in v0.0.6
func (h *ProjectHandler) StartSession(w http.ResponseWriter, r *http.Request)
StartSession handles POST /api/projects/{id}/sessions. The project is resolved from the URL ref (so refs like a name or short id work the same way other project routes do); the body specifies harness_type and the optional knobs (instruction / readonly / model / session_id / display_name).
type ProjectReloadResult ¶
type ProjectRepository ¶
type ProjectRepository interface {
CreateProject(project *orchestrator.Project) error
GetProject(id string) (*orchestrator.Project, error)
ListProjects() ([]*orchestrator.Project, error)
SetProjectWorkspace(projectID, workspaceID string) error
ListWorkspaces() ([]*orchestrator.WorkspaceSummary, error)
DeleteProject(id string) error
}
type ProjectService ¶
type ProjectService interface {
CreateProject(workDir string) (*orchestrator.Project, error)
ListProjects(workspaceID string) ([]*orchestrator.Project, error)
ListWorkspaces() ([]*orchestrator.WorkspaceSummary, error)
GetProject(id string) (*orchestrator.Project, error)
SetProjectWorkspace(id, workspaceID string) (*orchestrator.Project, error)
DeleteProject(id string) error
ReloadProjects() (*ProjectReloadResult, error)
// ResolveProjectRef resolves a ref string to one or more matching projects.
// Priority: id exact match > name exact match > name substring match (case-insensitive).
// Returns 1 project on unambiguous match, multiple on ambiguous match, StatusError{404} on no match.
ResolveProjectRef(ref string) ([]*orchestrator.Project, error)
}
type ProjectWorkDirLookup ¶
type ProjectWorkDirLookup interface {
GetProject(id string) (*orchestrator.Project, error)
}
ProjectWorkDirLookup provides read access to a project's working directory.
type ReopenTaskRequest ¶
type ReopenTaskRequest struct {
Message string `json:"message,omitempty"`
}
type ReplayHookRequest ¶
type ReplayHookRequest struct {
HookID string
Status string // optional: override task.Status before replay
}
ReplayHookRequest is the input for hook replay.
type ReplayHookResult ¶
type ReplayHookResult struct {
Task *orchestrator.Task `json:"task"`
FiredEvents []orchestrator.FiredEvent `json:"fired_events,omitempty"`
}
ReplayHookResult is the output of a hook replay.
type RerunTaskRequest ¶
type RerunTaskRequest struct {
AutoStart bool `json:"auto_start,omitempty"`
InstructionsOverride json.RawMessage `json:"instructions_override,omitempty"`
}
type SecretHandler ¶
type SecretHandler struct {
Store SecretStore
}
func (*SecretHandler) Delete ¶
func (h *SecretHandler) Delete(w http.ResponseWriter, r *http.Request)
func (*SecretHandler) GetValue ¶
func (h *SecretHandler) GetValue(w http.ResponseWriter, r *http.Request)
func (*SecretHandler) List ¶
func (h *SecretHandler) List(w http.ResponseWriter, r *http.Request)
func (*SecretHandler) Routes ¶
func (h *SecretHandler) Routes() chi.Router
func (*SecretHandler) Set ¶
func (h *SecretHandler) Set(w http.ResponseWriter, r *http.Request)
type SecretStore ¶
type SessionDispatcher ¶ added in v0.0.6
type SessionDispatcher interface {
StartSession(ctx context.Context, req StartSessionRequest) (*StartSessionResult, error)
}
SessionDispatcher launches a session job (claude / codex / opencode under a HarnessAdapter) and returns the runtime job id.
type SessionHandler ¶ added in v0.0.6
type SessionHandler struct {
Service ProjectService
Dispatcher SessionDispatcher
}
SessionHandler exposes the POST /api/sessions surface (and the project-scoped /api/projects/{id}/sessions variant mounted by ProjectHandler). Phase 3-d (PR1) introduced sessions as a first-class JobKind alongside hook and exec so user-initiated agent runs (WebUI [New Session] / `boid agent`) no longer have to piggyback on the project command path.
func (*SessionHandler) Routes ¶ added in v0.0.6
func (h *SessionHandler) Routes() chi.Router
func (*SessionHandler) Start ¶ added in v0.0.6
func (h *SessionHandler) Start(w http.ResponseWriter, r *http.Request)
Start handles POST /api/sessions. The request body must specify project_id. Use the project-scoped variant when the project is implied by the URL.
Session-id resume was removed: every Start launches a fresh agent process, so there is no `/api/sessions/{id}/resume` companion route anymore.
type SetProjectWorkspaceRequest ¶
type SetProjectWorkspaceRequest struct {
WorkspaceID string `json:"workspace_id"`
}
type StartSessionRequest ¶ added in v0.0.6
type StartSessionRequest struct {
// ProjectID names the project whose traits the session inherits. For the
// project-scoped route (`/api/projects/{id}/sessions`) it is taken from
// the URL and the body field is ignored.
ProjectID string `json:"project_id"`
// HarnessType selects the agent adapter. Must be one of "claude",
// "codex", "opencode", or "shell" — the shell harness drops the user
// into an interactive bash inside the project sandbox (`boid agent shell`).
HarnessType string `json:"harness_type"`
// Instruction is the optional bootstrap prompt for the first turn. Empty
// leaves the harness to pick its default (no positional for session mode
// on claude, since /boid-task is meaningless without a task.yaml).
Instruction string `json:"instruction,omitempty"`
// Readonly, when true, mounts the project workspace read-only. Sessions
// default to writable (developer ergonomics > fail-safety).
Readonly bool `json:"readonly,omitempty"`
// Model overrides the harness binary's default model selection.
Model string `json:"model,omitempty"`
// DisplayName is the human-readable session label persisted to
// jobs.display_name. Empty falls back to "<harness> session".
DisplayName string `json:"display_name,omitempty"`
}
StartSessionRequest is the body of POST /api/sessions and POST /api/projects/{id}/sessions. Phase 3-d (PR1) introduced the session concept as a first-class JobKind so the WebUI [New Session] dialog and the `boid agent` CLI share one daemon entry point.
type StartSessionResult ¶ added in v0.0.6
type StartSessionResult struct {
JobID string `json:"job_id"`
AttachURL string `json:"attach_url"`
}
StartSessionResult is the response shape for POST /api/sessions and the project-scoped variant.
type StatusError ¶
func (*StatusError) Error ¶
func (e *StatusError) Error() string
type TaskAnswerService ¶
type TaskAnswerService interface {
AnswerTask(ctx context.Context, taskID, questionID, answer string) error
}
TaskAnswerService records a user reply to a pending Q&A question and transitions the task back to executing.
type TaskAppService ¶
type TaskAppService struct {
Tasks TaskStore
Actions ActionStore
Jobs JobStore
Meta MetaStore
Workflow WorkflowService
Projects ProjectWorkDirLookup
RuntimesDir string
Notify Notifier
// BlockingAsk coordinates harness-independent blocking Q&A (boid task ask).
// Shared with the sandbox boid builtin executor (which calls AskTaskBlocking)
// and the answer path (AnswerTask), so both halves of a blocking ask use the
// same in-memory registry. Nil disables blocking ask (notify --ask still works).
BlockingAsk *BlockingAskRegistry
// AskDisconnectGrace is how long an awaiting task may sit with no live agent
// parked before the daemon reclaims it (a blocking ask whose foreground
// command was killed by a harness command-timeout). Zero falls back to
// defaultAskDisconnectGrace.
AskDisconnectGrace time.Duration
}
func (*TaskAppService) AnswerTask ¶
func (s *TaskAppService) AnswerTask(ctx context.Context, taskID, questionID, answer string) error
AnswerTask saves the user's reply and transitions the task awaiting → executing.
func (*TaskAppService) AskTaskBlocking ¶ added in v0.0.6
func (s *TaskAppService) AskTaskBlocking(ctx context.Context, taskID, question string) (string, error)
AskTaskBlocking implements the harness-independent blocking Q&A RPC behind `boid task ask <question>`. The agent stays alive and blocks inside this call until the user/supervisor answers, so the round-trip works for every harness uniformly — there is no session-resume path anywhere in boid (the legacy `notify --ask` → `claude --resume` flow was removed).
The call is disconnect-resilient and idempotent across re-asks. Harnesses kill long-running shell commands on a command-timeout (claude-code / opencode at ~120s), which severs the held connection. When that happens the model retries the identical `boid task ask`; this handler recognises the retry and recovers instead of treating it as a fresh question:
- executing → fresh ask: register the answer channel, transition executing → awaiting via ApplyAction("ask"), fire the user notification (root tasks only; child tasks are seen by their supervisor's monitor loop), then block in Wait.
- awaiting → re-ask: if an answer was parked durably while the agent was disconnected (PendingAnswer), consume it immediately and flip back to executing. Otherwise re-attach to the SAME question id and block again — no second "ask" transition, no new notification.
Register happens before the awaiting transition so an answer that races in immediately afterwards is never dropped. Decision B1 (relaxed): a concurrent ask carrying a DIFFERENT question id still fails with ErrAskPending; a re-ask of the same question is a re-attach and is allowed.
func (*TaskAppService) CreateTask ¶
func (s *TaskAppService) CreateTask(req CreateTaskRequest) (*orchestrator.Task, error)
func (*TaskAppService) DeleteTask ¶
func (s *TaskAppService) DeleteTask(id string, force bool) error
func (*TaskAppService) DuplicateTask ¶
func (s *TaskAppService) DuplicateTask(sourceID string, autoStart bool) (*orchestrator.Task, error)
func (*TaskAppService) GetTask ¶
func (s *TaskAppService) GetTask(id string) (*orchestrator.Task, error)
func (*TaskAppService) GetTaskDetail ¶
func (s *TaskAppService) GetTaskDetail(id string) (*TaskDetailView, error)
func (*TaskAppService) GetTaskField ¶
func (s *TaskAppService) GetTaskField(id, path string) (string, error)
GetTaskField resolves a dotted field path against the task. See ResolveTaskField for the path syntax (top-level fields, payload traits, computed lifecycle).
func (*TaskAppService) ImportTasks ¶
func (s *TaskAppService) ImportTasks(reqs []CreateTaskRequest) (*ImportResult, error)
func (*TaskAppService) ListTasks ¶
func (s *TaskAppService) ListTasks(filter orchestrator.TaskFilter) ([]*orchestrator.Task, error)
func (*TaskAppService) NotifyTask ¶
func (s *TaskAppService) NotifyTask(ctx context.Context, taskID, message, ask, questionID, progress, done, fail string) error
NotifyTask invokes the configured notify command for the given task. Returns 501 when no notifier is wired and ask is empty (notifications disabled in config). When ask is non-empty the task is transitioned to awaiting; the notification is best-effort and skipped if no notifier is configured. questionID identifies the Q&A turn (generated when empty). When progress is non-empty (progress mode), no hook fires and no state transition occurs — only a progress Action is written to the timeline. ask and progress are mutually exclusive.
Note: ask transitions the task to awaiting but does NOT spawn a resume dispatch when the user replies. The session-id resume path was removed (every dispatch is a fresh agent process); only `boid task ask` (the blocking RPC) can deliver an answer back to a live agent.
func (*TaskAppService) RerunTask ¶
func (s *TaskAppService) RerunTask(id string, req RerunTaskRequest) (*orchestrator.Task, error)
func (*TaskAppService) UpdateTask ¶
func (s *TaskAppService) UpdateTask(id string, req UpdateTaskRequest) (*orchestrator.Task, error)
type TaskDetailView ¶
type TaskDetailView struct {
Task *orchestrator.Task
Actions []*orchestrator.Action
Jobs []*Job
AvailableActions []string `json:"available_actions"`
Dependents []*orchestrator.Task `json:"dependents,omitempty"`
}
type TaskEventHub ¶
type TaskEventHub struct {
// contains filtered or unexported fields
}
TaskEventHub は taskID ごとに複数の subscriber へイベントを配送する pub-sub ハブ。
func (*TaskEventHub) Broadcast ¶
func (h *TaskEventHub) Broadcast(taskID string, ev TaskEvent)
Broadcast は taskID の全 subscriber に ev を非ブロッキングで配送する。 チャネルが満杯の subscriber はドロップする(hub 全体を止めない)。
type TaskHandler ¶
type TaskHandler struct {
Service TaskService
Hooks HookService // optional: enables hook replay/list when set
Notifier TaskNotifyService // optional: enables POST /{id}/notify when set
Answerer TaskAnswerService // optional: enables POST /{id}/answer when set
}
func (*TaskHandler) Answer ¶
func (h *TaskHandler) Answer(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Create ¶
func (h *TaskHandler) Create(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Delete ¶
func (h *TaskHandler) Delete(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Detail ¶
func (h *TaskHandler) Detail(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Duplicate ¶
func (h *TaskHandler) Duplicate(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Field ¶
func (h *TaskHandler) Field(w http.ResponseWriter, r *http.Request)
Field returns a single dotted-path value extracted from the task. The response is text/plain so it can be consumed directly by shell scripts (e.g. `STATUS=$(boid task show $ID --field status)`).
func (*TaskHandler) Get ¶
func (h *TaskHandler) Get(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Import ¶
func (h *TaskHandler) Import(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) List ¶
func (h *TaskHandler) List(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) ListHooks ¶
func (h *TaskHandler) ListHooks(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Notify ¶
func (h *TaskHandler) Notify(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Patch ¶
func (h *TaskHandler) Patch(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) ReplayHook ¶
func (h *TaskHandler) ReplayHook(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Rerun ¶
func (h *TaskHandler) Rerun(w http.ResponseWriter, r *http.Request)
func (*TaskHandler) Routes ¶
func (h *TaskHandler) Routes() chi.Router
type TaskNotifyService ¶
type TaskNotifyService interface {
NotifyTask(ctx context.Context, taskID, message, ask, questionID, progress, done, fail string) error
}
TaskNotifyService dispatches an agent-driven notification for a task. Wired to *TaskAppService at runtime; left optional on TaskHandler so existing tests do not need to satisfy this interface. ask/questionID are optional Q&A fields: when ask is non-empty the task is transitioned to awaiting after the notification is sent. The session-id resume path was removed, so an answer can only reach an agent that is still alive inside a blocking `boid task ask` RPC. progress is a no-state-change progress note (timeline action only). done / fail are agent self-completion / self-failure: they transition the task to done / aborted respectively and SIGTERM running hook runtimes — the parent supervisor's polling drives the response (verify + optional reopen). ask/progress/done/fail are mutually exclusive.
type TaskService ¶
type TaskService interface {
CreateTask(req CreateTaskRequest) (*orchestrator.Task, error)
ListTasks(filter orchestrator.TaskFilter) ([]*orchestrator.Task, error)
GetTask(id string) (*orchestrator.Task, error)
GetTaskDetail(id string) (*TaskDetailView, error)
GetTaskField(id, path string) (string, error)
UpdateTask(id string, req UpdateTaskRequest) (*orchestrator.Task, error)
DeleteTask(id string, force bool) error
ImportTasks(reqs []CreateTaskRequest) (*ImportResult, error)
DuplicateTask(sourceID string, autoStart bool) (*orchestrator.Task, error)
RerunTask(id string, req RerunTaskRequest) (*orchestrator.Task, error)
}
type TaskStore ¶
type TaskStore interface {
CreateTask(task *orchestrator.Task) error
GetTask(id string) (*orchestrator.Task, error)
ListTasks(filter orchestrator.TaskFilter) ([]*orchestrator.Task, error)
UpdateTask(task *orchestrator.Task) error
DeleteTask(id string) error
FindTaskByRemote(remoteID string) (*orchestrator.Task, error)
FindTaskByRef(ref, parentID string) (*orchestrator.Task, error)
FindDependentTasks(taskID string) ([]*orchestrator.Task, error)
// ListChildren returns direct children (one level only) of the given parent
// task, ordered by created_at ASC. Returns an empty slice (not nil) when the
// task has no children. Used by finalizeTerminal to sweep boid/<id8> branches
// once a supervisor reaches a terminal state.
ListChildren(parentID string) ([]*orchestrator.Task, error)
}
type TaskWorkflowService ¶
type TaskWorkflowService struct {
Tasks TaskStore
Jobs JobStore
Projects ProjectRepository
Tx Transactor
Meta MetaStore
Coordinator DispatchCoordinator
Lifecycle JobLifecycle
Worktrees WorktreeCleaner
Hub *TaskEventHub
// Locks pins the branch lock to the executing lifetime of each task.
// Optional: when nil, no branch locking is performed (matches pre-P0-2
// behaviour for tests that don't exercise concurrency).
Locks *orchestrator.BranchLockManager
// Adapter is the harness adapter used to query post-run usage. Phase 3-b
// dropped the StopAgent role: graceful stop is delivered as a SIGUSR1
// directly via Lifecycle.SignalJobRuntime, which claude.Adapter.Run()'s
// signal.Notify handler intercepts. Adapter remains optional; when nil,
// usage / future per-harness queries become no-ops.
Adapter adapters.HarnessAdapter
// contains filtered or unexported fields
}
func (*TaskWorkflowService) ApplyAction ¶
func (s *TaskWorkflowService) ApplyAction(ctx context.Context, taskID string, req ApplyActionRequest) (*ActionApplication, error)
func (*TaskWorkflowService) CompleteJob ¶
func (s *TaskWorkflowService) CompleteJob(_ context.Context, jobID string, req JobDoneRequest) (*Job, error)
func (*TaskWorkflowService) InitDispatch ¶
func (s *TaskWorkflowService) InitDispatch(ctx context.Context)
InitDispatch initialises the lifecycle context used by dispatch-loop goroutines. Must be called before the first action is applied. The returned cancel is stored internally; call Shutdown to invoke it.
func (*TaskWorkflowService) ListHooksForStatus ¶
func (s *TaskWorkflowService) ListHooksForStatus(taskID, status string) ([]orchestrator.Hook, error)
ListHooksForStatus returns hooks that match the given status for the task. If status is empty, the task's current status is used.
func (*TaskWorkflowService) ReplayHook ¶
func (s *TaskWorkflowService) ReplayHook(ctx context.Context, taskID string, req ReplayHookRequest) (*ReplayHookResult, error)
ReplayHook replays a single hook for the given task. If req.Status is non-empty the task's status is overwritten before dispatch. Running jobs on the same task cause a 409 Conflict.
func (*TaskWorkflowService) Shutdown ¶
func (s *TaskWorkflowService) Shutdown()
Shutdown cancels the dispatch context and blocks until all in-flight dispatch loops have returned. Call this before closing the database.
func (*TaskWorkflowService) StopAgent ¶
func (s *TaskWorkflowService) StopAgent(runtimeID string)
StopAgent gracefully stops the agent backing runtimeID by delivering SIGUSR1 to its process group. claude.Adapter.Run()'s signal.Notify(SIGUSR1) handler forwards a SIGTERM to the claude child and returns Result.StoppedByDaemon=true, so the surrounding sandbox runtime survives long enough to post `boid job done` through the broker normally. No-op when runtimeID is empty or no JobLifecycle has been configured.
func (*TaskWorkflowService) TriggerDependents ¶
func (s *TaskWorkflowService) TriggerDependents(ctx context.Context, taskID string)
TriggerDependents は taskID に依存する pending タスクを評価し、 auto_start=true かつ依存条件が満たされた場合に自動 start する。 auto_start=false のタスクは依存解決しても pending のまま残り、 ユーザが手動で start するまで待機する。
type Transactor ¶
type TxStore ¶
type TxStore interface {
TaskStore
ActionStore
JobStore
}
type UpdateJobRequest ¶ added in v0.0.4
type UpdateJobRequest struct {
DisplayName *string `json:"display_name"`
}
type UpdateTaskRequest ¶
type UpdateTaskRequest struct {
Title string `json:"title"`
Description string `json:"description"`
ProjectID string `json:"project_id,omitempty"`
RemoteID *string `json:"remote_id,omitempty"`
Payload json.RawMessage `json:"payload,omitempty"`
Instructions json.RawMessage `json:"instructions,omitempty"`
ParentID *string `json:"parent_id,omitempty"`
AutoStart *bool `json:"auto_start,omitempty"`
}
type WSAttachHandler ¶
type WSAttachHandler struct {
Subscriber dispatcher.RuntimeSubscriber
Writer dispatcher.RuntimeInputWriter
PublicURL string
Registry *auth.ConnectionRegistry
}
WSAttachHandler handles WebSocket connections for interactive PTY attach. Route: GET /api/jobs/{id}/attach/ws
func (*WSAttachHandler) ServeHTTP ¶
func (h *WSAttachHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type WebAppService ¶
type WebAppService struct {
Tasks TaskStore
Actions ActionStore
Jobs JobStore
GlobalJobs GlobalJobStore
Projects ProjectRepository
Meta MetaStore
Workflow WorkflowService
TaskSvc TaskService
Hooks HookService
Answerer TaskAnswerService // optional: enables POST /tasks/{id}/answer
}
func (*WebAppService) AnswerTask ¶
func (s *WebAppService) AnswerTask(ctx context.Context, taskID, questionID, answer string) error
func (*WebAppService) ApplyAction ¶
func (s *WebAppService) ApplyAction(taskID string, actionType string) error
func (*WebAppService) CreateTask ¶
func (s *WebAppService) CreateTask(req CreateTaskRequest) (*orchestrator.Task, error)
func (*WebAppService) DeleteTask ¶
func (s *WebAppService) DeleteTask(id string, force bool) error
DeleteTask delegates to the shared TaskService so the web UI uses the same delete semantics as the JSON API and TUI.
func (*WebAppService) DuplicateTask ¶
func (s *WebAppService) DuplicateTask(id string) (string, error)
DuplicateTask delegates to the shared TaskService so the Web UI uses the same duplication semantics as the JSON API: a fresh task is created via CreateTask + resolveBehavior so that Instructions and Payload come from the behavior's DefaultInstruction / DefaultPayload, not from the source task's runtime state. Without this delegation the duplicate inherited the source's runtime payload (awaiting trait and harness session state) and missing Instructions caused the hook evaluator to skip the agent hook, so no hook fired on Start.
The Web UI button does not auto-start the duplicate; the user clicks Start separately.
func (*WebAppService) GetJob ¶
func (s *WebAppService) GetJob(id string) (*JobWithContext, error)
func (*WebAppService) GetProjectByID ¶
func (s *WebAppService) GetProjectByID(id string) (*orchestrator.Project, error)
func (*WebAppService) GetTaskDetail ¶
func (s *WebAppService) GetTaskDetail(id string) (*TaskDetailView, error)
func (*WebAppService) ListBehaviors ¶
func (s *WebAppService) ListBehaviors() ([]string, error)
func (*WebAppService) ListHooksForStatus ¶
func (s *WebAppService) ListHooksForStatus(taskID, status string) ([]orchestrator.Hook, error)
func (*WebAppService) ListJobs ¶
func (s *WebAppService) ListJobs(status string) ([]JobWithContext, error)
func (*WebAppService) ListProjects ¶
func (s *WebAppService) ListProjects() ([]*orchestrator.Project, error)
func (*WebAppService) ListSessions ¶ added in v0.0.2
func (s *WebAppService) ListSessions() ([]JobWithContext, error)
func (*WebAppService) ListTasks ¶
func (s *WebAppService) ListTasks(filter orchestrator.TaskFilter) ([]*orchestrator.Task, error)
func (*WebAppService) ListWorkspaces ¶
func (s *WebAppService) ListWorkspaces() ([]*orchestrator.WorkspaceSummary, error)
func (*WebAppService) ReopenTask ¶
func (s *WebAppService) ReopenTask(id string, req ReopenTaskRequest) error
func (*WebAppService) ReplayHook ¶
func (s *WebAppService) ReplayHook(ctx context.Context, taskID string, req ReplayHookRequest) (*ReplayHookResult, error)
func (*WebAppService) RerunTask ¶
func (s *WebAppService) RerunTask(id string, req RerunTaskRequest) error
func (*WebAppService) UpdateTask ¶
func (s *WebAppService) UpdateTask(id string, req UpdateTaskRequest) error
type WebHandler ¶
type WebHandler struct {
Service WebService
Hub *TaskEventHub
SessionDispatcher SessionDispatcher
Registry *auth.ConnectionRegistry
// AttachmentsRoot is the data-home directory under which per-task
// attachments (`tasks/<id>/attachments`) are persisted. When empty (e.g.
// :memory: DB during tests) the multipart code path falls back to
// rejecting attachments while still accepting plain form-urlencoded
// submissions.
AttachmentsRoot string
}
func (*WebHandler) GetTaskEdit ¶
func (h *WebHandler) GetTaskEdit(w http.ResponseWriter, r *http.Request)
func (*WebHandler) HookReplayList ¶
func (h *WebHandler) HookReplayList(w http.ResponseWriter, r *http.Request)
func (*WebHandler) JobDetail ¶
func (h *WebHandler) JobDetail(w http.ResponseWriter, r *http.Request)
func (*WebHandler) JobTerminal ¶
func (h *WebHandler) JobTerminal(w http.ResponseWriter, r *http.Request)
JobTerminal redirects legacy deep-links (/jobs/{id}/terminal) to the job detail page (/jobs/{id}), which now renders the terminal inline.
func (*WebHandler) PostAction ¶
func (h *WebHandler) PostAction(w http.ResponseWriter, r *http.Request)
func (*WebHandler) PostAnswer ¶
func (h *WebHandler) PostAnswer(w http.ResponseWriter, r *http.Request)
func (*WebHandler) PostDelete ¶
func (h *WebHandler) PostDelete(w http.ResponseWriter, r *http.Request)
PostDelete deletes the task and redirects to the task list. Errors are surfaced via ?error= on the same task page so the user sees the reason (e.g. dependents exist).
func (*WebHandler) PostDuplicate ¶
func (h *WebHandler) PostDuplicate(w http.ResponseWriter, r *http.Request)
func (*WebHandler) PostEdit ¶
func (h *WebHandler) PostEdit(w http.ResponseWriter, r *http.Request)
func (*WebHandler) PostHookReplay ¶
func (h *WebHandler) PostHookReplay(w http.ResponseWriter, r *http.Request)
func (*WebHandler) PostReopen ¶
func (h *WebHandler) PostReopen(w http.ResponseWriter, r *http.Request)
func (*WebHandler) PostRerun ¶
func (h *WebHandler) PostRerun(w http.ResponseWriter, r *http.Request)
func (*WebHandler) PostStartSession ¶ added in v0.0.6
func (h *WebHandler) PostStartSession(w http.ResponseWriter, r *http.Request)
PostStartSession launches a HarnessAdapter-backed session for the project from the Web UI's [New Session] dialog. Phase 3-d (PR1) introduced this alongside PostProjectExecuteCommand so users can start a claude / codex / opencode session without going through the legacy commands path.
func (*WebHandler) PostTaskCreate ¶
func (h *WebHandler) PostTaskCreate(w http.ResponseWriter, r *http.Request)
func (*WebHandler) QuestionPage ¶
func (h *WebHandler) QuestionPage(w http.ResponseWriter, r *http.Request)
QuestionPage renders the dedicated Q&A turn page at `/tasks/{id}/questions/{question_id}`. The notification deep-link from `boid task notify --ask` lands here. The page shows the question and either an answer form (when this is the active awaiting turn) or the recorded answer (when an answer action exists for the same question_id).
func (*WebHandler) ReopenForm ¶
func (h *WebHandler) ReopenForm(w http.ResponseWriter, r *http.Request)
func (*WebHandler) Routes ¶
func (h *WebHandler) Routes() chi.Router
func (*WebHandler) SessionList ¶ added in v0.0.2
func (h *WebHandler) SessionList(w http.ResponseWriter, r *http.Request)
func (*WebHandler) SessionNew ¶ added in v0.0.2
func (h *WebHandler) SessionNew(w http.ResponseWriter, r *http.Request)
func (*WebHandler) TaskDetail ¶
func (h *WebHandler) TaskDetail(w http.ResponseWriter, r *http.Request)
func (*WebHandler) TaskDetailFragment ¶
func (h *WebHandler) TaskDetailFragment(w http.ResponseWriter, r *http.Request)
TaskDetailFragment returns a partial HTML fragment for the task detail page. The `kind` query parameter selects which section to render:
- "timeline": action history section
- "status": status card + available actions
- "jobs": jobs section
func (*WebHandler) TaskEvents ¶
func (h *WebHandler) TaskEvents(w http.ResponseWriter, r *http.Request)
TaskEvents streams Server-Sent Events for a specific task. Subscribes to the TaskEventHub and forwards events until the client disconnects or the device is revoked.
func (*WebHandler) TaskList ¶
func (h *WebHandler) TaskList(w http.ResponseWriter, r *http.Request)
func (*WebHandler) TaskNew ¶
func (h *WebHandler) TaskNew(w http.ResponseWriter, r *http.Request)
type WebManagementHandler ¶
type WebManagementHandler struct {
Pairing Pairer
Store *auth.Store
PublicURL string
Registry *auth.ConnectionRegistry
}
func (*WebManagementHandler) DeleteAllDevices ¶
func (h *WebManagementHandler) DeleteAllDevices(w http.ResponseWriter, r *http.Request)
func (*WebManagementHandler) DeleteDevice ¶
func (h *WebManagementHandler) DeleteDevice(w http.ResponseWriter, r *http.Request)
func (*WebManagementHandler) GetDevices ¶
func (h *WebManagementHandler) GetDevices(w http.ResponseWriter, r *http.Request)
func (*WebManagementHandler) PostPair ¶
func (h *WebManagementHandler) PostPair(w http.ResponseWriter, r *http.Request)
func (*WebManagementHandler) Routes ¶
func (h *WebManagementHandler) Routes() chi.Router
type WebService ¶
type WebService interface {
ListTasks(filter orchestrator.TaskFilter) ([]*orchestrator.Task, error)
GetTaskDetail(id string) (*TaskDetailView, error)
ListProjects() ([]*orchestrator.Project, error)
ListBehaviors() ([]string, error)
ListWorkspaces() ([]*orchestrator.WorkspaceSummary, error)
ApplyAction(taskID string, actionType string) error
DuplicateTask(id string) (string, error)
DeleteTask(id string, force bool) error
ListJobs(status string) ([]JobWithContext, error)
ListSessions() ([]JobWithContext, error)
GetJob(id string) (*JobWithContext, error)
CreateTask(req CreateTaskRequest) (*orchestrator.Task, error)
UpdateTask(id string, req UpdateTaskRequest) error
RerunTask(id string, req RerunTaskRequest) error
ReopenTask(id string, req ReopenTaskRequest) error
AnswerTask(ctx context.Context, taskID, questionID, answer string) error
ListHooksForStatus(taskID, status string) ([]orchestrator.Hook, error)
ReplayHook(ctx context.Context, taskID string, req ReplayHookRequest) (*ReplayHookResult, error)
GetProjectByID(id string) (*orchestrator.Project, error)
}
type WorkflowService ¶
type WorkflowService interface {
ApplyAction(ctx context.Context, taskID string, req ApplyActionRequest) (*ActionApplication, error)
CompleteJob(ctx context.Context, jobID string, req JobDoneRequest) (*Job, error)
TriggerDependents(ctx context.Context, taskID string)
// StopAgent asks the agent backing runtimeID to terminate gracefully,
// without tearing down the surrounding runner-inner-child. The broker's
// `boid job done` call still fires normally, preserving any payload
// patch the agent wrote up to that point. NotifyTask calls this after
// `ApplyAction("ask")` so the awaiting transition does not race with
// payload_patch capture. No-op when runtimeID is empty.
StopAgent(runtimeID string)
}
type WorkspaceHandler ¶
type WorkspaceHandler struct {
Service ProjectService
}
func (*WorkspaceHandler) List ¶
func (h *WorkspaceHandler) List(w http.ResponseWriter, r *http.Request)
func (*WorkspaceHandler) Routes ¶
func (h *WorkspaceHandler) Routes() chi.Router
type WorktreeCleaner ¶
type WorktreeCleaner interface {
CleanupForTask(taskID, projectDir, newStatus string) error
// SweepChildBranches deletes the boid/<id8> branches associated with the
// given child task IDs. Called by finalizeTerminal once a supervisor task
// reaches a terminal state — its children's branches were retained through
// CleanupForTask so the supervisor could merge them into the base branch;
// now that the supervisor is done, the merged refs are safe to drop.
// Branches that don't exist (already deleted, never created) are silently
// skipped. Non-boid/* branches (root-task base branches) are never deleted.
SweepChildBranches(projectDir string, taskIDs []string) error
}
Source Files
¶
- action.go
- attachments.go
- blocking_ask_registry.go
- broker.go
- depends_on_noop.go
- events_handler.go
- gc.go
- helpers.go
- job.go
- job_log_sse.go
- job_model.go
- project.go
- project_service.go
- secret.go
- service.go
- session.go
- store.go
- task.go
- task_ask.go
- task_create.go
- task_event_hub.go
- task_field.go
- task_notify.go
- task_service.go
- tree.go
- web.go
- web_service.go
- workflow.go
- workflow_action.go
- workflow_job.go
- workflow_replay.go
- workspace.go
- ws_attach.go