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
+22
View File
@@ -581,6 +581,28 @@ func (c *Client) PatchLogs(ctx context.Context, req PatchLogs) error {
return nil
}
// PatchAppStatus updates the status of a workspace app.
type PatchAppStatus struct {
AppSlug string `json:"app_slug"`
NeedsUserAttention bool `json:"needs_user_attention"`
State codersdk.WorkspaceAppStatusState `json:"state"`
Message string `json:"message"`
URI string `json:"uri"`
Icon string `json:"icon"`
}
func (c *Client) PatchAppStatus(ctx context.Context, req PatchAppStatus) error {
res, err := c.SDK.Request(ctx, http.MethodPatch, "/api/v2/workspaceagents/me/app-status", req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return codersdk.ReadBodyAsError(res)
}
return nil
}
type PostLogSourceRequest struct {
// ID is a unique identifier for the log source.
// It is scoped to a workspace agent, and can be statically
+30
View File
@@ -1,6 +1,8 @@
package codersdk
import (
"time"
"github.com/google/uuid"
)
@@ -13,6 +15,14 @@ const (
WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy"
)
type WorkspaceAppStatusState string
const (
WorkspaceAppStatusStateWorking WorkspaceAppStatusState = "working"
WorkspaceAppStatusStateComplete WorkspaceAppStatusState = "complete"
WorkspaceAppStatusStateFailure WorkspaceAppStatusState = "failure"
)
var MapWorkspaceAppHealths = map[WorkspaceAppHealth]struct{}{
WorkspaceAppHealthDisabled: {},
WorkspaceAppHealthInitializing: {},
@@ -75,6 +85,9 @@ type WorkspaceApp struct {
Health WorkspaceAppHealth `json:"health"`
Hidden bool `json:"hidden"`
OpenIn WorkspaceAppOpenIn `json:"open_in"`
// Statuses is a list of statuses for the app.
Statuses []WorkspaceAppStatus `json:"statuses"`
}
type Healthcheck struct {
@@ -85,3 +98,20 @@ type Healthcheck struct {
// Threshold specifies the number of consecutive failed health checks before returning "unhealthy".
Threshold int32 `json:"threshold"`
}
type WorkspaceAppStatus struct {
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"`
AgentID uuid.UUID `json:"agent_id" format:"uuid"`
AppID uuid.UUID `json:"app_id" format:"uuid"`
State WorkspaceAppStatusState `json:"state"`
NeedsUserAttention bool `json:"needs_user_attention"`
Message string `json:"message"`
// URI is the URI of the resource that the status is for.
// e.g. https://github.com/org/repo/pull/123
// e.g. file:///path/to/file
URI string `json:"uri"`
// Icon is an external URL to an icon that will be rendered in the UI.
Icon string `json:"icon"`
}
+22 -21
View File
@@ -26,27 +26,28 @@ const (
// Workspace is a deployment of a template. It references a specific
// version and can be updated.
type Workspace struct {
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
OwnerID uuid.UUID `json:"owner_id" format:"uuid"`
OwnerName string `json:"owner_name"`
OwnerAvatarURL string `json:"owner_avatar_url"`
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
OrganizationName string `json:"organization_name"`
TemplateID uuid.UUID `json:"template_id" format:"uuid"`
TemplateName string `json:"template_name"`
TemplateDisplayName string `json:"template_display_name"`
TemplateIcon string `json:"template_icon"`
TemplateAllowUserCancelWorkspaceJobs bool `json:"template_allow_user_cancel_workspace_jobs"`
TemplateActiveVersionID uuid.UUID `json:"template_active_version_id" format:"uuid"`
TemplateRequireActiveVersion bool `json:"template_require_active_version"`
LatestBuild WorkspaceBuild `json:"latest_build"`
Outdated bool `json:"outdated"`
Name string `json:"name"`
AutostartSchedule *string `json:"autostart_schedule,omitempty"`
TTLMillis *int64 `json:"ttl_ms,omitempty"`
LastUsedAt time.Time `json:"last_used_at" format:"date-time"`
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
OwnerID uuid.UUID `json:"owner_id" format:"uuid"`
OwnerName string `json:"owner_name"`
OwnerAvatarURL string `json:"owner_avatar_url"`
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
OrganizationName string `json:"organization_name"`
TemplateID uuid.UUID `json:"template_id" format:"uuid"`
TemplateName string `json:"template_name"`
TemplateDisplayName string `json:"template_display_name"`
TemplateIcon string `json:"template_icon"`
TemplateAllowUserCancelWorkspaceJobs bool `json:"template_allow_user_cancel_workspace_jobs"`
TemplateActiveVersionID uuid.UUID `json:"template_active_version_id" format:"uuid"`
TemplateRequireActiveVersion bool `json:"template_require_active_version"`
LatestBuild WorkspaceBuild `json:"latest_build"`
LatestAppStatus *WorkspaceAppStatus `json:"latest_app_status"`
Outdated bool `json:"outdated"`
Name string `json:"name"`
AutostartSchedule *string `json:"autostart_schedule,omitempty"`
TTLMillis *int64 `json:"ttl_ms,omitempty"`
LastUsedAt time.Time `json:"last_used_at" format:"date-time"`
// DeletingAt indicates the time at which the workspace will be permanently deleted.
// A workspace is eligible for deletion if it is dormant (a non-nil dormant_at value)