desktop

package
v0.1.0-alpha.2 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package desktop implements the BuildMax desktop app (Wails) and is used by cmd/buildmax-desktop.

Index

Constants

View Source
const (
	ModeLocal  = "local"
	ModeServer = "server"
)

Desktop runs in one of two modes, the same two the CLI has always had:

  • local: the agent runs here against the models in settings.yaml, and no server is involved. This is the single-user install.
  • server: the same local agent, plus a signed-in BuildMax account — managed models, and the bridge to a team's work.

The CLI reaches both without asking: it runs locally and `buildmax login` is optional. Desktop used to open on a login form, which made a server look required to run an agent on your own machine. The choice is remembered so the app opens where it was left.

Variables

This section is empty.

Functions

func Run

func Run(assets fs.FS) error

Run starts the Wails desktop application. assets is the embedded frontend filesystem (e.g. from //go:embed all:frontend in the main package). It creates an App, wires lifecycle hooks, and calls wails.Run.

Types

type App

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

App holds desktop application state and implements Wails lifecycle hooks. Each project gets its own AgentApp and ApprovalHandler instance, created lazily on first use.

func NewApp

func NewApp() *App

NewApp returns a new App instance.

func (*App) CancelRun

func (a *App) CancelRun(projectID string) error

CancelRun cancels the in-flight run for the given project, if any. Cancellation is cooperative: the agent loop returns the partial assistant reply produced so far and emits desktop/stream-done as a normal completion. Calling CancelRun when no run is in flight is a no-op.

Stopping also discards anything queued behind the run. Those prompts were written for work the user has just called off; delivering them afterwards would restart it in their name.

func (*App) ClearProjectSessions

func (a *App) ClearProjectSessions(projectID string) ([]string, error)

func (*App) ConnectToServer

func (a *App) ConnectToServer() (*AuthStatus, error)

ConnectToServer leaves local mode so the sign-in form is shown again. It touches no credentials — there are none to touch in local mode.

func (*App) CreateProject

func (a *App) CreateProject(name, folderPath string) (*Project, error)

CreateProject creates a new project with the given name and folder.

func (*App) DeleteProject

func (a *App) DeleteProject(id string) error

DeleteProject removes a project and closes its AgentApp if one was running.

func (*App) DeleteSession

func (a *App) DeleteSession(sessionID string) error

func (*App) DoLogin

func (a *App) DoLogin(serverURL, email, otp string) (*AuthStatus, error)

DoLogin authenticates with a single-use login code and saves credentials on success. It is the recovery path: claiming a new account, or getting back in after a forgotten password.

func (*App) DoLoginWithPassword

func (a *App) DoLoginWithPassword(serverURL, email, password string) (*AuthStatus, error)

DoLoginWithPassword authenticates with a password and saves credentials on success. This is the everyday path.

func (*App) GetAuthStatus

func (a *App) GetAuthStatus() (*AuthStatus, error)

GetAuthStatus returns the current mode and authentication state.

A usable login wins over a remembered local choice: the credentials are the stronger statement, and someone who signed in should not land in local mode because they once picked it.

func (*App) GetDefaultServerURL

func (a *App) GetDefaultServerURL() string

GetDefaultServerURL is what the sign-in form starts with. It reads the same settings.yaml key `buildmax login` does, so the two entry points offer the same address instead of the Desktop insisting on a local server someone has already configured away from.

func (*App) GetGitBranch

func (a *App) GetGitBranch(projectID string) (string, error)

GetGitBranch returns the current git branch for the given project's folder, or an empty string if the folder is not a git repository.

func (*App) GetPlugins

func (a *App) GetPlugins(projectID string) (PluginsResult, error)

GetPlugins returns what this project's runtime loaded.

It reads the runtime's snapshot rather than scanning again, so what is shown is what the running agent has — an install completed a moment ago appears once the runtime is rebuilt, which is the same rule every other surface has.

func (*App) GetRunStatus

func (a *App) GetRunStatus(projectID, sessionID string) (RunStatusPayload, error)

func (*App) GetSession

func (a *App) GetSession(sessionID string) (SessionDetail, error)

GetSession loads one session by ID and returns it for display.

func (*App) GetSlashAgents

func (a *App) GetSlashAgents(projectID string) (SlashAgentsResult, error)

GetSlashAgents returns all agent types (builtin + user-defined) for the project.

func (*App) GetSlashMCP

func (a *App) GetSlashMCP(projectID string) (SlashMCPResult, error)

GetSlashMCP returns the MCP server status for the given project.

func (*App) GetSlashModels

func (a *App) GetSlashModels(projectID string) (SlashModelsResult, error)

GetSlashModels returns configured models and the active model for a project.

func (*App) GetSlashSkills

func (a *App) GetSlashSkills(projectID string) (SlashSkillsResult, error)

GetSlashSkills returns all skills discovered for the given project.

func (*App) GetWorkspaceDiff

func (a *App) GetWorkspaceDiff(projectID string) (git.WorkspaceDiff, error)

GetWorkspaceDiff returns the current git-backed changed-file view for a project.

func (*App) InstallPlugin

func (a *App) InstallPlugin(name, version string, update bool) (InstallPluginPlan, error)

InstallPlugin downloads and installs a release.

It resolves again rather than taking the plan the app was shown: a release published between the two is a different install, and acting on a stale plan would put bytes on disk that nobody was shown.

func (*App) ListProjects

func (a *App) ListProjects() ([]Project, error)

ListProjects returns all saved projects.

func (*App) ListSessions

func (a *App) ListSessions() ([]session.SessionItem, error)

ListSessions returns all sessions across all projects.

func (*App) Logout

func (a *App) Logout() error

Logout clears stored credentials and revokes the session on the server.

A server that cannot be reached is not a failed logout: the credentials are gone from this machine either way, and returning an error would leave the UI showing someone as signed in when they are not.

func (*App) OpenFolderDialog

func (a *App) OpenFolderDialog() (string, error)

OpenFolderDialog opens a native directory picker and returns the selected path.

func (*App) PlanPluginInstall

func (a *App) PlanPluginInstall(name, version string, update bool) (InstallPluginPlan, error)

PlanPluginInstall resolves which release an install would take.

func (*App) QueuedMessages

func (a *App) QueuedMessages(projectID string) []string

QueuedMessages returns the prompts waiting behind the project's in-flight run, oldest first. The frontend reads it when it switches back to a project whose queue events it was not mounted for.

func (*App) RenameProject

func (a *App) RenameProject(id, newName string) error

RenameProject updates the name of a project.

func (*App) RenameSession

func (a *App) RenameSession(sessionID, title string) error

func (*App) RequestOTP

func (a *App) RequestOTP(serverURL, email, intent string) error

RequestOTP calls the server's OTP endpoint.

func (*App) RespondApproval

func (a *App) RespondApproval(projectID string, decision string)

RespondApproval is called by the frontend when the user answers a tool approval prompt. projectID must match the project that triggered the desktop/approval-request event. decision is "once", "session", or "deny"; anything else denies, so a frontend that falls out of step fails closed.

func (*App) SendMessageStream

func (a *App) SendMessageStream(projectID, sessionID, prompt string) (int, error)

SendMessageStream runs a prompt in the given project and session with streaming. It returns immediately and emits desktop/stream-delta, then desktop/stream-done or desktop/stream-error. sessionID may be empty to start a new session.

At most one run per project may be in flight. A prompt submitted while one is active is queued and runs as its own turn once the current one finishes; the return value is that prompt's 1-based position in the queue, and 0 when the prompt started a run of its own. The position comes back as a return value rather than an event because it answers the caller's own call.

func (*App) SetPluginDisabled

func (a *App) SetPluginDisabled(name string, disabled bool) error

SetPluginDisabled stops a plugin loading, or lets it load again.

func (*App) SetProjectModel

func (a *App) SetProjectModel(projectID, modelName string) error

SetProjectModel switches the active model for a project's agent.

func (*App) SetSessionPinned

func (a *App) SetSessionPinned(sessionID string, pinned bool) error

func (*App) Shutdown

func (a *App) Shutdown(_ context.Context)

Shutdown closes all per-project AgentApp instances and cancels any in-flight runs.

func (*App) Startup

func (a *App) Startup(ctx context.Context)

Startup is called by Wails when the app is starting. AgentApp instances are created lazily on first use per project.

func (*App) UninstallPlugin

func (a *App) UninstallPlugin(name string, force bool) error

UninstallPlugin removes an installed plugin.

func (*App) UseLocalMode

func (a *App) UseLocalMode() (*AuthStatus, error)

UseLocalMode runs the agent here, against the models in settings.yaml, with no server involved.

type ApprovalRequestPayload

type ApprovalRequestPayload struct {
	ProjectID string         `json:"project_id"`
	ToolName  string         `json:"tool_name"`
	Args      map[string]any `json:"args"`
}

ApprovalRequestPayload is emitted to the frontend when a tool call needs approval.

type AuthStatus

type AuthStatus struct {
	Mode      string `json:"mode"`
	LoggedIn  bool   `json:"logged_in"`
	ServerURL string `json:"server_url,omitempty"`
	UserID    string `json:"user_id,omitempty"`
	Email     string `json:"email,omitempty"`
	Name      string `json:"name,omitempty"`
}

AuthStatus is what the frontend needs to decide which surface to show: the mode the app is in, and who is signed in when that mode is server.

Mode is empty only before anyone has chosen, which is what makes the app ask.

type DesktopApprovalHandler

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

DesktopApprovalHandler implements agent.ApprovalHandler for the Wails desktop app. It emits a Wails event to the frontend and blocks until RespondApproval is called.

func (*DesktopApprovalHandler) RequestApproval

func (h *DesktopApprovalHandler) RequestApproval(ctx context.Context, name string, args map[string]any) agent.ApprovalDecision

RequestApproval emits an approval-request event to the frontend and blocks until the user responds via RespondApproval. Denies if the app context is not ready.

type InstallPluginPlan

type InstallPluginPlan struct {
	Name             string   `json:"name"`
	Version          string   `json:"version"`
	Digest           string   `json:"digest"`
	SizeBytes        int64    `json:"size_bytes"`
	PublishedBy      string   `json:"published_by,omitempty"`
	AlreadyInstalled bool     `json:"already_installed"`
	Skills           []string `json:"skills,omitempty"`
	Subagents        []string `json:"subagents,omitempty"`
	MCP              []string `json:"mcp,omitempty"`
	Hooks            []string `json:"hooks,omitempty"`
	// MissingEnv names the variables this release reads that are not set here,
	// which is the usual reason a plugin looks installed and does nothing.
	MissingEnv []string `json:"missing_env,omitempty"`
	// DirtySource says the release was packed from a working tree that was not
	// the commit it names.
	DirtySource bool `json:"dirty_source,omitempty"`
}

InstallPluginPlan is what an install would do, resolved before anything is downloaded so the app can show it while the decision is still open.

type MessageBlockedPayload

type MessageBlockedPayload struct {
	Prompt string   `json:"prompt"`
	Reason string   `json:"reason"`
	Queued []string `json:"queued"`
}

MessageBlockedPayload is emitted when a hook refuses a queued prompt (event desktop/message-blocked). It reports one message, not the end of the run — the run carries on with what it already had.

type MessageDequeuedPayload

type MessageDequeuedPayload struct {
	Prompt string   `json:"prompt"`
	Queued []string `json:"queued"`
}

MessageDequeuedPayload is emitted just before a queued prompt starts its own turn (event desktop/message-dequeued), so the transcript can show it as sent.

type PluginEntry

type PluginEntry struct {
	Name        string `json:"name"`
	DisplayName string `json:"display_name"`
	Description string `json:"description,omitempty"`
	Path        string `json:"path"`
	Source      string `json:"source"`
	// State is active, disabled, refused, or error — a decision and a defect
	// are different things and a reader should not have to tell them apart
	// from a message.
	State string `json:"state"`

	Version string `json:"version,omitempty"`
	Digest  string `json:"digest,omitempty"`

	RemoteURL string `json:"remote_url,omitempty"`
	Branch    string `json:"branch,omitempty"`
	Commit    string `json:"commit,omitempty"`
	Dirty     bool   `json:"dirty,omitempty"`

	Skills    []string       `json:"skills,omitempty"`
	Subagents []string       `json:"subagents,omitempty"`
	MCP       []string       `json:"mcp,omitempty"`
	Hooks     []string       `json:"hooks,omitempty"`
	Env       []PluginEnvVar `json:"env,omitempty"`
	// Shadowed names what a higher layer overrode, so a plugin does not read
	// as fully active while part of it never loads.
	Shadowed []string `json:"shadowed,omitempty"`
	Problems []string `json:"problems,omitempty"`
}

PluginEntry is one installed plugin as the app shows it.

type PluginEnvVar

type PluginEnvVar struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Required    bool   `json:"required"`
	Set         bool   `json:"set"`
}

PluginEnvVar is one declared environment variable and whether it is set here.

type PluginsResult

type PluginsResult struct {
	Dir     string        `json:"dir"`
	Plugins []PluginEntry `json:"plugins"`
	// AllowedSources is the operator's restriction, empty when there is none.
	AllowedSources []string `json:"allowed_sources,omitempty"`
	// Notes are directory-level findings: a stray directory, unreadable state.
	Notes []string `json:"notes,omitempty"`
}

PluginsResult is the project's plugin inventory.

type Project

type Project struct {
	ID         string `json:"id"`
	Name       string `json:"name"`
	FolderPath string `json:"folder_path"`
	CreatedAt  string `json:"created_at"`
	LastUsedAt string `json:"last_used_at"`
}

Project is a named local folder that groups desktop sessions.

type ReplyPayload

type ReplyPayload struct {
	Reply                 string `json:"reply"`
	SessionID             string `json:"session_id"`
	ContextTokens         int    `json:"context_tokens"`
	ContextWindow         int    `json:"context_window"`
	PromptTokens          int    `json:"prompt_tokens"`
	CompletionTokens      int    `json:"completion_tokens"`
	TotalPromptTokens     int    `json:"total_prompt_tokens"`
	TotalCompletionTokens int    `json:"total_completion_tokens"`
}

ReplyPayload is returned when a desktop prompt completes successfully.

type RunStatusPayload

type RunStatusPayload struct {
	ContextTokens         int `json:"context_tokens"`
	ContextWindow         int `json:"context_window"`
	PromptTokens          int `json:"prompt_tokens"`
	CompletionTokens      int `json:"completion_tokens"`
	TotalPromptTokens     int `json:"total_prompt_tokens"`
	TotalCompletionTokens int `json:"total_completion_tokens"`
}

type SessionDetail

type SessionDetail struct {
	ID        string        `json:"id"`
	Title     string        `json:"title,omitempty"`
	CreatedAt string        `json:"created_at"`
	Messages  []llm.Message `json:"messages,omitempty"`
}

SessionDetail is the session payload returned to the frontend for display.

type SlashAgentEntry

type SlashAgentEntry struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	IsBuiltin   bool   `json:"is_builtin"`
}

type SlashAgentsResult

type SlashAgentsResult struct {
	Agents []SlashAgentEntry `json:"agents"`
}

type SlashMCPResult

type SlashMCPResult struct {
	LoadError string           `json:"load_error,omitempty"`
	Servers   []SlashMCPServer `json:"servers"`
}

type SlashMCPServer

type SlashMCPServer struct {
	ID        string `json:"id"`
	Type      string `json:"type"`
	OK        bool   `json:"ok"`
	ToolCount int    `json:"tool_count"`
	Error     string `json:"error,omitempty"`
}

type SlashModelEntry

type SlashModelEntry struct {
	Name          string `json:"name"`
	ProviderModel string `json:"provider_model,omitempty"`
	IsCurrent     bool   `json:"is_current"`
	// Managed and Destination say where this model sends prompts. Two entries
	// can share a display name and reach different places, so the selector has
	// to be able to tell them apart.
	Managed     bool   `json:"managed"`
	Destination string `json:"destination,omitempty"`
}

type SlashModelsResult

type SlashModelsResult struct {
	Current string            `json:"current"`
	Models  []SlashModelEntry `json:"models"`
}

type SlashSkillEntry

type SlashSkillEntry struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Path        string `json:"path"`
}

type SlashSkillsResult

type SlashSkillsResult struct {
	Skills []SlashSkillEntry `json:"skills"`
}

type StreamErrorPayload

type StreamErrorPayload struct {
	Message string `json:"message"`
}

StreamErrorPayload is emitted when streaming fails (event desktop/stream-error).

type ToolEndPayload

type ToolEndPayload struct {
	ToolCallID string `json:"tool_call_id"`
	ToolName   string `json:"tool_name"`
	DurationMs int64  `json:"duration_ms,omitempty"`
	IsError    bool   `json:"is_error,omitempty"`
	Denied     bool   `json:"denied,omitempty"`
	Reason     string `json:"reason,omitempty"`
}

ToolEndPayload is emitted when a tool call finishes or is denied.

type ToolStartPayload

type ToolStartPayload struct {
	ToolCallID string `json:"tool_call_id"`
	ToolName   string `json:"tool_name"`
	Args       string `json:"args"`
}

ToolStartPayload is emitted when a tool call begins executing.

Jump to

Keyboard shortcuts

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