feat: add app status tracking to the backend (#17163)

This does ~95% of the backend work required to integrate the AI work.

Most left to integrate from the tasks branch is just frontend, which
will be a lot smaller I believe.

The real difference between this branch and that one is the abstraction
-- this now attaches statuses to apps, and returns the latest status
reported as part of a workspace.

This change enables us to have a similar UX to in the tasks branch, but
for agents other than Claude Code as well. Any app can report status
now.
This commit is contained in:
Kyle Carberry
2025-03-31 10:55:44 -04:00
committed by GitHub
parent 489641d0be
commit 8ea956fc11
35 changed files with 1668 additions and 69 deletions

View File

@ -2414,6 +2414,67 @@ func AllWorkspaceAppOpenInValues() []WorkspaceAppOpenIn {
}
}
type WorkspaceAppStatusState string
const (
WorkspaceAppStatusStateWorking WorkspaceAppStatusState = "working"
WorkspaceAppStatusStateComplete WorkspaceAppStatusState = "complete"
WorkspaceAppStatusStateFailure WorkspaceAppStatusState = "failure"
)
func (e *WorkspaceAppStatusState) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = WorkspaceAppStatusState(s)
case string:
*e = WorkspaceAppStatusState(s)
default:
return fmt.Errorf("unsupported scan type for WorkspaceAppStatusState: %T", src)
}
return nil
}
type NullWorkspaceAppStatusState struct {
WorkspaceAppStatusState WorkspaceAppStatusState `json:"workspace_app_status_state"`
Valid bool `json:"valid"` // Valid is true if WorkspaceAppStatusState is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullWorkspaceAppStatusState) Scan(value interface{}) error {
if value == nil {
ns.WorkspaceAppStatusState, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.WorkspaceAppStatusState.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullWorkspaceAppStatusState) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.WorkspaceAppStatusState), nil
}
func (e WorkspaceAppStatusState) Valid() bool {
switch e {
case WorkspaceAppStatusStateWorking,
WorkspaceAppStatusStateComplete,
WorkspaceAppStatusStateFailure:
return true
}
return false
}
func AllWorkspaceAppStatusStateValues() []WorkspaceAppStatusState {
return []WorkspaceAppStatusState{
WorkspaceAppStatusStateWorking,
WorkspaceAppStatusStateComplete,
WorkspaceAppStatusStateFailure,
}
}
type WorkspaceTransition string
const (
@ -3515,6 +3576,19 @@ type WorkspaceAppStat struct {
Requests int32 `db:"requests" json:"requests"`
}
type WorkspaceAppStatus struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
AppID uuid.UUID `db:"app_id" json:"app_id"`
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
State WorkspaceAppStatusState `db:"state" json:"state"`
NeedsUserAttention bool `db:"needs_user_attention" json:"needs_user_attention"`
Message string `db:"message" json:"message"`
Uri sql.NullString `db:"uri" json:"uri"`
Icon sql.NullString `db:"icon" json:"icon"`
}
// Joins in the username + avatar url of the initiated by user.
type WorkspaceBuild struct {
ID uuid.UUID `db:"id" json:"id"`