Documentation
¶
Overview ¶
Package desktop implements the BuildMax desktop app (Wails) and is used by cmd/buildmax-desktop.
Index ¶
- Constants
- func Run(assets fs.FS) error
- type App
- func (a *App) CancelRun(projectID string) error
- func (a *App) ClearProjectSessions(projectID string) ([]string, error)
- func (a *App) ConnectToServer() (*AuthStatus, error)
- func (a *App) CreateProject(name, folderPath string) (*Project, error)
- func (a *App) DeleteProject(id string) error
- func (a *App) DeleteSession(sessionID string) error
- func (a *App) DoLogin(serverURL, email, otp string) (*AuthStatus, error)
- func (a *App) DoLoginWithPassword(serverURL, email, password string) (*AuthStatus, error)
- func (a *App) GetAuthStatus() (*AuthStatus, error)
- func (a *App) GetDefaultServerURL() string
- func (a *App) GetGitBranch(projectID string) (string, error)
- func (a *App) GetPlugins(projectID string) (PluginsResult, error)
- func (a *App) GetRunStatus(projectID, sessionID string) (RunStatusPayload, error)
- func (a *App) GetSession(sessionID string) (SessionDetail, error)
- func (a *App) GetSlashAgents(projectID string) (SlashAgentsResult, error)
- func (a *App) GetSlashMCP(projectID string) (SlashMCPResult, error)
- func (a *App) GetSlashModels(projectID string) (SlashModelsResult, error)
- func (a *App) GetSlashSkills(projectID string) (SlashSkillsResult, error)
- func (a *App) GetWorkspaceDiff(projectID string) (git.WorkspaceDiff, error)
- func (a *App) InstallPlugin(name, version string, update bool) (InstallPluginPlan, error)
- func (a *App) ListProjects() ([]Project, error)
- func (a *App) ListSessions() ([]session.SessionItem, error)
- func (a *App) Logout() error
- func (a *App) OpenFolderDialog() (string, error)
- func (a *App) PlanPluginInstall(name, version string, update bool) (InstallPluginPlan, error)
- func (a *App) QueuedMessages(projectID string) []string
- func (a *App) RenameProject(id, newName string) error
- func (a *App) RenameSession(sessionID, title string) error
- func (a *App) RequestOTP(serverURL, email, intent string) error
- func (a *App) RespondApproval(projectID string, decision string)
- func (a *App) SendMessageStream(projectID, sessionID, prompt string) (int, error)
- func (a *App) SetPluginDisabled(name string, disabled bool) error
- func (a *App) SetProjectModel(projectID, modelName string) error
- func (a *App) SetSessionPinned(sessionID string, pinned bool) error
- func (a *App) Shutdown(_ context.Context)
- func (a *App) Startup(ctx context.Context)
- func (a *App) UninstallPlugin(name string, force bool) error
- func (a *App) UseLocalMode() (*AuthStatus, error)
- type ApprovalRequestPayload
- type AuthStatus
- type DesktopApprovalHandler
- type InstallPluginPlan
- type MessageBlockedPayload
- type MessageDequeuedPayload
- type PluginEntry
- type PluginEnvVar
- type PluginsResult
- type Project
- type ReplyPayload
- type RunStatusPayload
- type SessionDetail
- type SlashAgentEntry
- type SlashAgentsResult
- type SlashMCPResult
- type SlashMCPServer
- type SlashModelEntry
- type SlashModelsResult
- type SlashSkillEntry
- type SlashSkillsResult
- type StreamErrorPayload
- type ToolEndPayload
- type ToolStartPayload
Constants ¶
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 ¶
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 (*App) CancelRun ¶
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 (*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 ¶
CreateProject creates a new project with the given name and folder.
func (*App) DeleteProject ¶
DeleteProject removes a project and closes its AgentApp if one was running.
func (*App) DeleteSession ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
RenameProject updates the name of a project.
func (*App) RenameSession ¶
func (*App) RequestOTP ¶
RequestOTP calls the server's OTP endpoint.
func (*App) RespondApproval ¶
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 ¶
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 ¶
SetPluginDisabled stops a plugin loading, or lets it load again.
func (*App) SetProjectModel ¶
SetProjectModel switches the active model for a project's agent.
func (*App) Shutdown ¶
Shutdown closes all per-project AgentApp instances and cancels any in-flight runs.
func (*App) Startup ¶
Startup is called by Wails when the app is starting. AgentApp instances are created lazily on first use per project.
func (*App) UninstallPlugin ¶
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 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 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 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.