diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index 0a8b2c07793c3..ce08720096329 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -18653,6 +18653,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"ai_task_sidebar_app_id": {
+ "description": "Deprecated: This field is unused and will be removed in a future release.",
"type": "string",
"format": "uuid"
},
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index cd6537de0e210..4b38af880754e 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -17057,6 +17057,7 @@
"type": "object",
"properties": {
"ai_task_sidebar_app_id": {
+ "description": "Deprecated: This field is unused and will be removed in a future release.",
"type": "string",
"format": "uuid"
},
diff --git a/coderd/database/check_constraint.go b/coderd/database/check_constraint.go
index f9d54705a7cf5..d5472fa789e54 100644
--- a/coderd/database/check_constraint.go
+++ b/coderd/database/check_constraint.go
@@ -6,11 +6,10 @@ type CheckConstraint string
// CheckConstraint enums.
const (
- CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users
- CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs
- CheckValidationMonotonicOrder CheckConstraint = "validation_monotonic_order" // template_version_parameters
- CheckMaxLogsLength CheckConstraint = "max_logs_length" // workspace_agents
- CheckSubsystemsNotNone CheckConstraint = "subsystems_not_none" // workspace_agents
- CheckWorkspaceBuildsAiTaskSidebarAppIDRequired CheckConstraint = "workspace_builds_ai_task_sidebar_app_id_required" // workspace_builds
- CheckWorkspaceBuildsDeadlineBelowMaxDeadline CheckConstraint = "workspace_builds_deadline_below_max_deadline" // workspace_builds
+ CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users
+ CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs
+ CheckValidationMonotonicOrder CheckConstraint = "validation_monotonic_order" // template_version_parameters
+ CheckMaxLogsLength CheckConstraint = "max_logs_length" // workspace_agents
+ CheckSubsystemsNotNone CheckConstraint = "subsystems_not_none" // workspace_agents
+ CheckWorkspaceBuildsDeadlineBelowMaxDeadline CheckConstraint = "workspace_builds_deadline_below_max_deadline" // workspace_builds
)
diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go
index 82b7b47c892b2..d2f36c1097d9e 100644
--- a/coderd/database/dbauthz/dbauthz_test.go
+++ b/coderd/database/dbauthz/dbauthz_test.go
@@ -3252,13 +3252,9 @@ func (s *MethodTestSuite) TestWorkspace() {
WorkspaceID: w.ID,
TemplateVersionID: tv.ID,
})
- res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: b.JobID})
- agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
- app := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: agt.ID})
check.Args(database.UpdateWorkspaceBuildAITaskByIDParams{
- HasAITask: sql.NullBool{Bool: true, Valid: true},
- SidebarAppID: uuid.NullUUID{UUID: app.ID, Valid: true},
- ID: b.ID,
+ HasAITask: sql.NullBool{Bool: true, Valid: true},
+ ID: b.ID,
}).Asserts(w, policy.ActionUpdate)
}))
s.Run("SoftDeleteWorkspaceByID", s.Subtest(func(db database.Store, check *expects) {
diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go
index 11e02d0f651e9..1929c58c22ae2 100644
--- a/coderd/database/dbgen/dbgen.go
+++ b/coderd/database/dbgen/dbgen.go
@@ -436,7 +436,6 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil
buildID := takeFirst(orig.ID, uuid.New())
jobID := takeFirst(orig.JobID, uuid.New())
hasAITask := takeFirst(orig.HasAITask, sql.NullBool{})
- sidebarAppID := takeFirst(orig.AITaskSidebarAppID, uuid.NullUUID{})
var build database.WorkspaceBuild
err := db.InTx(func(db database.Store) error {
err := db.InsertWorkspaceBuild(genCtx, database.InsertWorkspaceBuildParams{
@@ -472,10 +471,9 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil
if hasAITask.Valid {
require.NoError(t, db.UpdateWorkspaceBuildAITaskByID(genCtx, database.UpdateWorkspaceBuildAITaskByIDParams{
- HasAITask: hasAITask,
- SidebarAppID: sidebarAppID,
- UpdatedAt: dbtime.Now(),
- ID: buildID,
+ HasAITask: hasAITask,
+ UpdatedAt: dbtime.Now(),
+ ID: buildID,
}))
}
diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql
index 7bea770248310..48c7d0411e96e 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -2235,8 +2235,6 @@ CREATE TABLE workspace_builds (
max_deadline timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
template_version_preset_id uuid,
has_ai_task boolean,
- ai_task_sidebar_app_id uuid,
- CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (((((has_ai_task IS NULL) OR (has_ai_task = false)) AND (ai_task_sidebar_app_id IS NULL)) OR ((has_ai_task = true) AND (ai_task_sidebar_app_id IS NOT NULL)))),
CONSTRAINT workspace_builds_deadline_below_max_deadline CHECK ((((deadline <> '0001-01-01 00:00:00+00'::timestamp with time zone) AND (deadline <= max_deadline)) OR (max_deadline = '0001-01-01 00:00:00+00'::timestamp with time zone)))
);
@@ -2257,7 +2255,6 @@ CREATE VIEW workspace_build_with_user AS
workspace_builds.max_deadline,
workspace_builds.template_version_preset_id,
workspace_builds.has_ai_task,
- workspace_builds.ai_task_sidebar_app_id,
COALESCE(visible_users.avatar_url, ''::text) AS initiator_by_avatar_url,
COALESCE(visible_users.username, ''::text) AS initiator_by_username,
COALESCE(visible_users.name, ''::text) AS initiator_by_name
@@ -3258,9 +3255,6 @@ ALTER TABLE ONLY workspace_apps
ALTER TABLE ONLY workspace_build_parameters
ADD CONSTRAINT workspace_build_parameters_workspace_build_id_fkey FOREIGN KEY (workspace_build_id) REFERENCES workspace_builds(id) ON DELETE CASCADE;
-ALTER TABLE ONLY workspace_builds
- ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_fkey FOREIGN KEY (ai_task_sidebar_app_id) REFERENCES workspace_apps(id);
-
ALTER TABLE ONLY workspace_builds
ADD CONSTRAINT workspace_builds_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go
index 33aa8edd69032..ee04ad64633ab 100644
--- a/coderd/database/foreign_key_constraint.go
+++ b/coderd/database/foreign_key_constraint.go
@@ -86,7 +86,6 @@ const (
ForeignKeyWorkspaceAppStatusesWorkspaceID ForeignKeyConstraint = "workspace_app_statuses_workspace_id_fkey" // ALTER TABLE ONLY workspace_app_statuses ADD CONSTRAINT workspace_app_statuses_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id);
ForeignKeyWorkspaceAppsAgentID ForeignKeyConstraint = "workspace_apps_agent_id_fkey" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
ForeignKeyWorkspaceBuildParametersWorkspaceBuildID ForeignKeyConstraint = "workspace_build_parameters_workspace_build_id_fkey" // ALTER TABLE ONLY workspace_build_parameters ADD CONSTRAINT workspace_build_parameters_workspace_build_id_fkey FOREIGN KEY (workspace_build_id) REFERENCES workspace_builds(id) ON DELETE CASCADE;
- ForeignKeyWorkspaceBuildsAiTaskSidebarAppID ForeignKeyConstraint = "workspace_builds_ai_task_sidebar_app_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_fkey FOREIGN KEY (ai_task_sidebar_app_id) REFERENCES workspace_apps(id);
ForeignKeyWorkspaceBuildsJobID ForeignKeyConstraint = "workspace_builds_job_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ForeignKeyWorkspaceBuildsTemplateVersionID ForeignKeyConstraint = "workspace_builds_template_version_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
ForeignKeyWorkspaceBuildsTemplateVersionPresetID ForeignKeyConstraint = "workspace_builds_template_version_preset_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_template_version_preset_id_fkey FOREIGN KEY (template_version_preset_id) REFERENCES template_version_presets(id) ON DELETE SET NULL;
diff --git a/coderd/database/migrations/000358_remove_sidebar_app_id.down.sql b/coderd/database/migrations/000358_remove_sidebar_app_id.down.sql
new file mode 100644
index 0000000000000..96c3aa59fc06e
--- /dev/null
+++ b/coderd/database/migrations/000358_remove_sidebar_app_id.down.sql
@@ -0,0 +1,52 @@
+DROP VIEW workspace_build_with_user;
+
+ALTER TABLE ONLY workspace_builds ADD COLUMN ai_task_sidebar_app_id UUID DEFAULT NULL;
+
+ALTER TABLE workspace_builds ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_fkey FOREIGN KEY (ai_task_sidebar_app_id) REFERENCES workspace_apps(id);
+
+ALTER TABLE workspace_builds
+ ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (
+ ((has_ai_task IS NULL OR has_ai_task = false) AND ai_task_sidebar_app_id IS NULL)
+ OR (has_ai_task = true AND ai_task_sidebar_app_id IS NOT NULL)
+ );
+
+
+CREATE VIEW workspace_build_with_user AS
+SELECT
+ workspace_builds.id,
+ workspace_builds.created_at,
+ workspace_builds.updated_at,
+ workspace_builds.workspace_id,
+ workspace_builds.template_version_id,
+ workspace_builds.build_number,
+ workspace_builds.transition,
+ workspace_builds.initiator_id,
+ workspace_builds.provisioner_state,
+ workspace_builds.job_id,
+ workspace_builds.deadline,
+ workspace_builds.reason,
+ workspace_builds.daily_cost,
+ workspace_builds.max_deadline,
+ workspace_builds.template_version_preset_id,
+ workspace_builds.has_ai_task,
+ workspace_builds.ai_task_sidebar_app_id,
+ COALESCE(
+ visible_users.avatar_url,
+ '' :: text
+ ) AS initiator_by_avatar_url,
+ COALESCE(
+ visible_users.username,
+ '' :: text
+ ) AS initiator_by_username,
+ COALESCE(visible_users.name, '' :: text) AS initiator_by_name
+FROM
+ (
+ workspace_builds
+ LEFT JOIN visible_users ON (
+ (
+ workspace_builds.initiator_id = visible_users.id
+ )
+ )
+ );
+
+COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.';
diff --git a/coderd/database/migrations/000358_remove_sidebar_app_id.up.sql b/coderd/database/migrations/000358_remove_sidebar_app_id.up.sql
new file mode 100644
index 0000000000000..47804d44b9123
--- /dev/null
+++ b/coderd/database/migrations/000358_remove_sidebar_app_id.up.sql
@@ -0,0 +1,43 @@
+DROP VIEW workspace_build_with_user;
+
+ALTER TABLE ONLY workspace_builds DROP CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required;
+ALTER TABLE ONLY workspace_builds DROP COLUMN ai_task_sidebar_app_id;
+
+CREATE VIEW workspace_build_with_user AS
+SELECT
+ workspace_builds.id,
+ workspace_builds.created_at,
+ workspace_builds.updated_at,
+ workspace_builds.workspace_id,
+ workspace_builds.template_version_id,
+ workspace_builds.build_number,
+ workspace_builds.transition,
+ workspace_builds.initiator_id,
+ workspace_builds.provisioner_state,
+ workspace_builds.job_id,
+ workspace_builds.deadline,
+ workspace_builds.reason,
+ workspace_builds.daily_cost,
+ workspace_builds.max_deadline,
+ workspace_builds.template_version_preset_id,
+ workspace_builds.has_ai_task,
+ COALESCE(
+ visible_users.avatar_url,
+ '' :: text
+ ) AS initiator_by_avatar_url,
+ COALESCE(
+ visible_users.username,
+ '' :: text
+ ) AS initiator_by_username,
+ COALESCE(visible_users.name, '' :: text) AS initiator_by_name
+FROM
+ (
+ workspace_builds
+ LEFT JOIN visible_users ON (
+ (
+ workspace_builds.initiator_id = visible_users.id
+ )
+ )
+ );
+
+COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.';
diff --git a/coderd/database/models.go b/coderd/database/models.go
index 75d2b941dab3c..29ef4630185e3 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -4153,7 +4153,6 @@ type WorkspaceBuild struct {
MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"`
TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"`
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
- AITaskSidebarAppID uuid.NullUUID `db:"ai_task_sidebar_app_id" json:"ai_task_sidebar_app_id"`
InitiatorByAvatarUrl string `db:"initiator_by_avatar_url" json:"initiator_by_avatar_url"`
InitiatorByUsername string `db:"initiator_by_username" json:"initiator_by_username"`
InitiatorByName string `db:"initiator_by_name" json:"initiator_by_name"`
@@ -4184,7 +4183,6 @@ type WorkspaceBuildTable struct {
MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"`
TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"`
HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
- AITaskSidebarAppID uuid.NullUUID `db:"ai_task_sidebar_app_id" json:"ai_task_sidebar_app_id"`
}
type WorkspaceLatestBuild struct {
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 74cefd09359b0..f39f87cdfd2ac 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -15702,7 +15702,7 @@ const getWorkspaceAgentAndLatestBuildByAuthToken = `-- name: GetWorkspaceAgentAn
SELECT
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl,
workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.expanded_directory, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps, workspace_agents.api_version, workspace_agents.display_order, workspace_agents.parent_id, workspace_agents.api_key_scope, workspace_agents.deleted,
- workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.ai_task_sidebar_app_id, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name
+ workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name
FROM
workspace_agents
JOIN
@@ -15814,7 +15814,6 @@ func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Cont
&i.WorkspaceBuild.MaxDeadline,
&i.WorkspaceBuild.TemplateVersionPresetID,
&i.WorkspaceBuild.HasAITask,
- &i.WorkspaceBuild.AITaskSidebarAppID,
&i.WorkspaceBuild.InitiatorByAvatarUrl,
&i.WorkspaceBuild.InitiatorByUsername,
&i.WorkspaceBuild.InitiatorByName,
@@ -18469,7 +18468,7 @@ func (q *sqlQuerier) InsertWorkspaceBuildParameters(ctx context.Context, arg Ins
}
const getActiveWorkspaceBuildsByTemplateID = `-- name: GetActiveWorkspaceBuildsByTemplateID :many
-SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
+SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
FROM (
SELECT
workspace_id, MAX(build_number) as max_build_number
@@ -18525,7 +18524,6 @@ func (q *sqlQuerier) GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, t
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -18625,7 +18623,7 @@ func (q *sqlQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, a
const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one
SELECT
- id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
+ id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, initiator_by_avatar_url, initiator_by_username, initiator_by_name
FROM
workspace_build_with_user AS workspace_builds
WHERE
@@ -18656,7 +18654,6 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -18665,7 +18662,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w
}
const getLatestWorkspaceBuilds = `-- name: GetLatestWorkspaceBuilds :many
-SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
+SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
FROM (
SELECT
workspace_id, MAX(build_number) as max_build_number
@@ -18705,7 +18702,6 @@ func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceB
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -18724,7 +18720,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceB
}
const getLatestWorkspaceBuildsByWorkspaceIDs = `-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many
-SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
+SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name
FROM (
SELECT
workspace_id, MAX(build_number) as max_build_number
@@ -18766,7 +18762,6 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context,
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -18786,7 +18781,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context,
const getWorkspaceBuildByID = `-- name: GetWorkspaceBuildByID :one
SELECT
- id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
+ id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, initiator_by_avatar_url, initiator_by_username, initiator_by_name
FROM
workspace_build_with_user AS workspace_builds
WHERE
@@ -18815,7 +18810,6 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -18825,7 +18819,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W
const getWorkspaceBuildByJobID = `-- name: GetWorkspaceBuildByJobID :one
SELECT
- id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
+ id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, initiator_by_avatar_url, initiator_by_username, initiator_by_name
FROM
workspace_build_with_user AS workspace_builds
WHERE
@@ -18854,7 +18848,6 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -18864,7 +18857,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU
const getWorkspaceBuildByWorkspaceIDAndBuildNumber = `-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one
SELECT
- id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
+ id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, initiator_by_avatar_url, initiator_by_username, initiator_by_name
FROM
workspace_build_with_user AS workspace_builds
WHERE
@@ -18897,7 +18890,6 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Co
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -18974,7 +18966,7 @@ func (q *sqlQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, sinc
const getWorkspaceBuildsByWorkspaceID = `-- name: GetWorkspaceBuildsByWorkspaceID :many
SELECT
- id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name
+ id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, initiator_by_avatar_url, initiator_by_username, initiator_by_name
FROM
workspace_build_with_user AS workspace_builds
WHERE
@@ -19046,7 +19038,6 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -19065,7 +19056,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge
}
const getWorkspaceBuildsCreatedAfter = `-- name: GetWorkspaceBuildsCreatedAfter :many
-SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user WHERE created_at > $1
+SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user WHERE created_at > $1
`
func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error) {
@@ -19094,7 +19085,6 @@ func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, created
&i.MaxDeadline,
&i.TemplateVersionPresetID,
&i.HasAITask,
- &i.AITaskSidebarAppID,
&i.InitiatorByAvatarUrl,
&i.InitiatorByUsername,
&i.InitiatorByName,
@@ -19176,25 +19166,18 @@ UPDATE
workspace_builds
SET
has_ai_task = $1,
- ai_task_sidebar_app_id = $2,
- updated_at = $3::timestamptz
-WHERE id = $4::uuid
+ updated_at = $2::timestamptz
+WHERE id = $3::uuid
`
type UpdateWorkspaceBuildAITaskByIDParams struct {
- HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
- SidebarAppID uuid.NullUUID `db:"sidebar_app_id" json:"sidebar_app_id"`
- UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
- ID uuid.UUID `db:"id" json:"id"`
+ HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"`
+ UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
+ ID uuid.UUID `db:"id" json:"id"`
}
func (q *sqlQuerier) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg UpdateWorkspaceBuildAITaskByIDParams) error {
- _, err := q.db.ExecContext(ctx, updateWorkspaceBuildAITaskByID,
- arg.HasAITask,
- arg.SidebarAppID,
- arg.UpdatedAt,
- arg.ID,
- )
+ _, err := q.db.ExecContext(ctx, updateWorkspaceBuildAITaskByID, arg.HasAITask, arg.UpdatedAt, arg.ID)
return err
}
diff --git a/coderd/database/queries/workspacebuilds.sql b/coderd/database/queries/workspacebuilds.sql
index be76b6642df1f..f30c0619b7c67 100644
--- a/coderd/database/queries/workspacebuilds.sql
+++ b/coderd/database/queries/workspacebuilds.sql
@@ -156,7 +156,6 @@ UPDATE
workspace_builds
SET
has_ai_task = @has_ai_task,
- ai_task_sidebar_app_id = @sidebar_app_id,
updated_at = @updated_at::timestamptz
WHERE id = @id::uuid;
diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go
index d1b03cbd68a27..7a359de3362f7 100644
--- a/coderd/provisionerdserver/provisionerdserver.go
+++ b/coderd/provisionerdserver/provisionerdserver.go
@@ -1944,32 +1944,17 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
}
}
- var sidebarAppID uuid.NullUUID
- hasAITask := len(jobType.WorkspaceBuild.AiTasks) == 1
- if hasAITask {
- task := jobType.WorkspaceBuild.AiTasks[0]
- if task.SidebarApp == nil {
- return xerrors.Errorf("update ai task: sidebar app is nil")
- }
-
- id, err := uuid.Parse(task.SidebarApp.Id)
- if err != nil {
- return xerrors.Errorf("parse sidebar app id: %w", err)
- }
-
- sidebarAppID = uuid.NullUUID{UUID: id, Valid: true}
- }
-
+ // Generally a template _should_ only define zero or one coder_ai_task resources. This is enforced in the provider.
+ hasAITask := len(jobType.WorkspaceBuild.AiTasks) > 0
// Regardless of whether there is an AI task or not, update the field to indicate one way or the other since it
- // always defaults to nil. ONLY if has_ai_task=true MUST ai_task_sidebar_app_id be set.
+ // always defaults to nil.
err = db.UpdateWorkspaceBuildAITaskByID(ctx, database.UpdateWorkspaceBuildAITaskByIDParams{
ID: workspaceBuild.ID,
HasAITask: sql.NullBool{
Bool: hasAITask,
Valid: true,
},
- SidebarAppID: sidebarAppID,
- UpdatedAt: now,
+ UpdatedAt: now,
})
if err != nil {
return xerrors.Errorf("update workspace build ai tasks flag: %w", err)
diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go
index ec26a2b92000f..ad0a5da06a3c4 100644
--- a/coderd/provisionerdserver/provisionerdserver_test.go
+++ b/coderd/provisionerdserver/provisionerdserver_test.go
@@ -2794,6 +2794,21 @@ func TestCompleteJob(t *testing.T) {
},
expected: true,
},
+ { // Checks regression for https://github.com/coder/coder/issues/18776
+ name: "non-existing app",
+ input: &proto.CompletedJob_WorkspaceBuild{
+ AiTasks: []*sdkproto.AITask{
+ {
+ Id: uuid.NewString(),
+ SidebarApp: &sdkproto.AITaskSidebarApp{
+ // Non-existing app ID would previously trigger a FK violation.
+ Id: uuid.NewString(),
+ },
+ },
+ },
+ },
+ expected: true,
+ },
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
@@ -2884,10 +2899,6 @@ func TestCompleteJob(t *testing.T) {
require.NoError(t, err)
require.True(t, build.HasAITask.Valid) // We ALWAYS expect a value to be set, therefore not nil, i.e. valid = true.
require.Equal(t, tc.expected, build.HasAITask.Bool)
-
- if tc.expected {
- require.Equal(t, sidebarAppID, build.AITaskSidebarAppID.UUID.String())
- }
})
}
})
diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go
index 583b9c4edaf21..70e700f82bba6 100644
--- a/coderd/workspacebuilds.go
+++ b/coderd/workspacebuilds.go
@@ -1152,10 +1152,6 @@ func (api *API) convertWorkspaceBuild(
if build.HasAITask.Valid {
hasAITask = &build.HasAITask.Bool
}
- var aiTasksSidebarAppID *uuid.UUID
- if build.AITaskSidebarAppID.Valid {
- aiTasksSidebarAppID = &build.AITaskSidebarAppID.UUID
- }
apiJob := convertProvisionerJob(job)
transition := codersdk.WorkspaceTransition(build.Transition)
@@ -1184,7 +1180,7 @@ func (api *API) convertWorkspaceBuild(
MatchedProvisioners: &matchedProvisioners,
TemplateVersionPresetID: presetID,
HasAITask: hasAITask,
- AITaskSidebarAppID: aiTasksSidebarAppID,
+ AITaskSidebarAppID: nil,
}, nil
}
diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go
index 96381043db0ab..d7f2ac8714d06 100644
--- a/coderd/workspaces_test.go
+++ b/coderd/workspaces_test.go
@@ -4584,23 +4584,13 @@ func TestWorkspaceFilterHasAITask(t *testing.T) {
}
job := dbgen.ProvisionerJob(t, db, pubsub, jobConfig)
- res := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: job.ID})
- agnt := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ResourceID: res.ID})
-
- var sidebarAppID uuid.UUID
- if hasAITask.Bool {
- sidebarApp := dbgen.WorkspaceApp(t, db, database.WorkspaceApp{AgentID: agnt.ID})
- sidebarAppID = sidebarApp.ID
- }
-
build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
- WorkspaceID: ws.ID,
- TemplateVersionID: version.ID,
- InitiatorID: user.UserID,
- JobID: job.ID,
- BuildNumber: 1,
- HasAITask: hasAITask,
- AITaskSidebarAppID: uuid.NullUUID{UUID: sidebarAppID, Valid: sidebarAppID != uuid.Nil},
+ WorkspaceID: ws.ID,
+ TemplateVersionID: version.ID,
+ InitiatorID: user.UserID,
+ JobID: job.ID,
+ BuildNumber: 1,
+ HasAITask: hasAITask,
})
if aiTaskPrompt != nil {
diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go
index 53d2a89290bca..fea85fd0694e4 100644
--- a/codersdk/workspacebuilds.go
+++ b/codersdk/workspacebuilds.go
@@ -89,7 +89,8 @@ type WorkspaceBuild struct {
MatchedProvisioners *MatchedProvisioners `json:"matched_provisioners,omitempty"`
TemplateVersionPresetID *uuid.UUID `json:"template_version_preset_id" format:"uuid"`
HasAITask *bool `json:"has_ai_task,omitempty"`
- AITaskSidebarAppID *uuid.UUID `json:"ai_task_sidebar_app_id,omitempty" format:"uuid"`
+ // Deprecated: This field is unused and will be removed in a future release.
+ AITaskSidebarAppID *uuid.UUID `json:"ai_task_sidebar_app_id,omitempty" format:"uuid"`
}
// WorkspaceResource describes resources used to create a workspace, for instance:
diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md
index 0232c3d45a0c2..70cc9e0c30947 100644
--- a/docs/admin/security/audit-logs.md
+++ b/docs/admin/security/audit-logs.md
@@ -35,7 +35,7 @@ We track the following resources:
| Template
write, delete |
Field | Tracked |
| active_version_id | true |
activity_bump | true |
allow_user_autostart | true |
allow_user_autostop | true |
allow_user_cancel_workspace_jobs | true |
autostart_block_days_of_week | true |
autostop_requirement_days_of_week | true |
autostop_requirement_weeks | true |
cors_behavior | true |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_name | false |
created_by_username | false |
default_ttl | true |
deleted | false |
deprecated | true |
description | true |
display_name | true |
failure_ttl | true |
group_acl | true |
icon | true |
id | true |
max_port_sharing_level | true |
name | true |
organization_display_name | false |
organization_icon | false |
organization_id | false |
organization_name | false |
provisioner | true |
require_active_version | true |
time_til_dormant | true |
time_til_dormant_autodelete | true |
updated_at | false |
use_classic_parameter_flow | true |
user_acl | true |
|
| TemplateVersion
create, write | Field | Tracked |
| archived | true |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_name | false |
created_by_username | false |
external_auth_providers | false |
has_ai_task | false |
id | true |
job_id | false |
message | false |
name | true |
organization_id | false |
readme | true |
source_example_id | false |
template_id | true |
updated_at | false |
|
| User
create, write, delete | Field | Tracked |
| avatar_url | false |
created_at | false |
deleted | true |
email | true |
github_com_user_id | false |
hashed_one_time_passcode | false |
hashed_password | true |
id | true |
is_system | true |
last_seen_at | false |
login_type | true |
name | true |
one_time_passcode_expires_at | true |
quiet_hours_schedule | true |
rbac_roles | true |
status | true |
updated_at | false |
username | true |
|
-| WorkspaceBuild
start, stop | Field | Tracked |
| ai_task_sidebar_app_id | false |
build_number | false |
created_at | false |
daily_cost | false |
deadline | false |
has_ai_task | false |
id | false |
initiator_by_avatar_url | false |
initiator_by_name | false |
initiator_by_username | false |
initiator_id | false |
job_id | false |
max_deadline | false |
provisioner_state | false |
reason | false |
template_version_id | true |
template_version_preset_id | false |
transition | false |
updated_at | false |
workspace_id | false |
|
+| WorkspaceBuild
start, stop | Field | Tracked |
| build_number | false |
created_at | false |
daily_cost | false |
deadline | false |
has_ai_task | false |
id | false |
initiator_by_avatar_url | false |
initiator_by_name | false |
initiator_by_username | false |
initiator_id | false |
job_id | false |
max_deadline | false |
provisioner_state | false |
reason | false |
template_version_id | true |
template_version_preset_id | false |
transition | false |
updated_at | false |
workspace_id | false |
|
| WorkspaceProxy
| Field | Tracked |
| created_at | true |
deleted | false |
derp_enabled | true |
derp_only | true |
display_name | true |
icon | true |
id | true |
name | true |
region_id | true |
token_hashed_secret | true |
updated_at | false |
url | true |
version | true |
wildcard_hostname | true |
|
| WorkspaceTable
| Field | Tracked |
| automatic_updates | true |
autostart_schedule | true |
created_at | false |
deleted | false |
deleting_at | true |
dormant_at | true |
favorite | true |
group_acl | true |
id | true |
last_used_at | false |
name | true |
next_start_at | true |
organization_id | false |
owner_id | true |
template_id | true |
ttl | true |
updated_at | false |
user_acl | true |
|
diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md
index a465575baeaa3..565db51ce3770 100644
--- a/docs/reference/api/builds.md
+++ b/docs/reference/api/builds.md
@@ -1522,7 +1522,7 @@ Status Code **200**
| Name | Type | Required | Restrictions | Description |
|----------------------------------|--------------------------------------------------------------------------------------------------------|----------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `[array item]` | array | false | | |
-| `» ai_task_sidebar_app_id` | string(uuid) | false | | |
+| `» ai_task_sidebar_app_id` | string(uuid) | false | | Deprecated: This field is unused and will be removed in a future release. |
| `» build_number` | integer | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md
index b3824d0c9b9b8..2ac0684e4f686 100644
--- a/docs/reference/api/schemas.md
+++ b/docs/reference/api/schemas.md
@@ -10123,33 +10123,33 @@ If the schedule is empty, the user will be updated to use the default schedule.|
### Properties
-| Name | Type | Required | Restrictions | Description |
-|------------------------------|-------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------|
-| `ai_task_sidebar_app_id` | string | false | | |
-| `build_number` | integer | false | | |
-| `created_at` | string | false | | |
-| `daily_cost` | integer | false | | |
-| `deadline` | string | false | | |
-| `has_ai_task` | boolean | false | | |
-| `id` | string | false | | |
-| `initiator_id` | string | false | | |
-| `initiator_name` | string | false | | |
-| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | |
-| `matched_provisioners` | [codersdk.MatchedProvisioners](#codersdkmatchedprovisioners) | false | | |
-| `max_deadline` | string | false | | |
-| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | |
-| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | |
-| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | |
-| `template_version_id` | string | false | | |
-| `template_version_name` | string | false | | |
-| `template_version_preset_id` | string | false | | |
-| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | |
-| `updated_at` | string | false | | |
-| `workspace_id` | string | false | | |
-| `workspace_name` | string | false | | |
-| `workspace_owner_avatar_url` | string | false | | |
-| `workspace_owner_id` | string | false | | |
-| `workspace_owner_name` | string | false | | Workspace owner name is the username of the owner of the workspace. |
+| Name | Type | Required | Restrictions | Description |
+|------------------------------|-------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------|
+| `ai_task_sidebar_app_id` | string | false | | Deprecated: This field is unused and will be removed in a future release. |
+| `build_number` | integer | false | | |
+| `created_at` | string | false | | |
+| `daily_cost` | integer | false | | |
+| `deadline` | string | false | | |
+| `has_ai_task` | boolean | false | | |
+| `id` | string | false | | |
+| `initiator_id` | string | false | | |
+| `initiator_name` | string | false | | |
+| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | |
+| `matched_provisioners` | [codersdk.MatchedProvisioners](#codersdkmatchedprovisioners) | false | | |
+| `max_deadline` | string | false | | |
+| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | |
+| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | |
+| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | |
+| `template_version_id` | string | false | | |
+| `template_version_name` | string | false | | |
+| `template_version_preset_id` | string | false | | |
+| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | |
+| `updated_at` | string | false | | |
+| `workspace_id` | string | false | | |
+| `workspace_name` | string | false | | |
+| `workspace_owner_avatar_url` | string | false | | |
+| `workspace_owner_id` | string | false | | |
+| `workspace_owner_name` | string | false | | Workspace owner name is the username of the owner of the workspace. |
#### Enumerated Values
diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go
index 1ad76a1e44ca9..b4541441deefe 100644
--- a/enterprise/audit/table.go
+++ b/enterprise/audit/table.go
@@ -196,7 +196,7 @@ var auditableResourcesTypes = map[any]map[string]Action{
"initiator_by_name": ActionIgnore,
"template_version_preset_id": ActionIgnore, // Never changes.
"has_ai_task": ActionIgnore, // Never changes.
- "ai_task_sidebar_app_id": ActionIgnore, // Never changes.
+ // "ai_task_sidebar_app_id": ActionIgnore, // Never changes.
},
&database.AuditableGroup{}: {
"id": ActionTrack,
diff --git a/site/src/pages/TaskPage/TaskApps.tsx b/site/src/pages/TaskPage/TaskApps.tsx
index 83cd01f37c004..7311079dc0aae 100644
--- a/site/src/pages/TaskPage/TaskApps.tsx
+++ b/site/src/pages/TaskPage/TaskApps.tsx
@@ -21,6 +21,7 @@ import { TaskAppIFrame } from "./TaskAppIframe";
type TaskAppsProps = {
task: Task;
+ sidebarApp: WorkspaceApp | null;
};
type AppWithAgent = {
@@ -28,7 +29,7 @@ type AppWithAgent = {
agent: WorkspaceAgent;
};
-export const TaskApps: FC = ({ task }) => {
+export const TaskApps: FC = ({ task, sidebarApp }) => {
const agents = task.workspace.latest_build.resources
.flatMap((r) => r.agents)
.filter((a) => !!a);
@@ -42,10 +43,7 @@ export const TaskApps: FC = ({ task }) => {
agent,
})),
)
- .filter(
- ({ app }) =>
- !!app && app.id !== task.workspace.latest_build.ai_task_sidebar_app_id,
- );
+ .filter(({ app }) => !!app && app.id !== sidebarApp?.id);
const embeddedApps = apps.filter(({ app }) => !app.external);
const externalApps = apps.filter(({ app }) => app.external);
diff --git a/site/src/pages/TaskPage/TaskPage.tsx b/site/src/pages/TaskPage/TaskPage.tsx
index 19e2c5aafdcd7..e045a7e8e4b24 100644
--- a/site/src/pages/TaskPage/TaskPage.tsx
+++ b/site/src/pages/TaskPage/TaskPage.tsx
@@ -1,7 +1,11 @@
import { API } from "api/api";
import { getErrorDetail, getErrorMessage } from "api/errors";
import { template as templateQueryOptions } from "api/queries/templates";
-import type { Workspace, WorkspaceStatus } from "api/typesGenerated";
+import type {
+ Workspace,
+ WorkspaceApp,
+ WorkspaceStatus,
+} from "api/typesGenerated";
import { Button } from "components/Button/Button";
import { Loader } from "components/Loader/Loader";
import { Margins } from "components/Margins/Margins";
@@ -105,6 +109,8 @@ const TaskPage = () => {
"stopping",
];
+ const [sidebarApp, sidebarAppStatus] = getSidebarApp(task);
+
if (waitingStatuses.includes(task.workspace.latest_build.status)) {
// If no template yet, use an indeterminate progress bar.
const transition = (template &&
@@ -171,7 +177,7 @@ const TaskPage = () => {
);
} else {
- content = ;
+ content = ;
}
return (
@@ -181,7 +187,11 @@ const TaskPage = () => {
-
+
@@ -229,3 +239,66 @@ export const data = {
} satisfies Task;
},
};
+
+const getSidebarApp = (
+ task: Task,
+): [WorkspaceApp | null, "error" | "loading" | "healthy"] => {
+ if (!task.workspace.latest_build.job.completed_at) {
+ // while the workspace build is running, we don't have a sidebar app yet
+ return [null, "loading"];
+ }
+
+ // Ensure all the agents are healthy before continuing.
+ const healthyAgents = task.workspace.latest_build.resources
+ .flatMap((res) => res.agents)
+ .filter((agt) => !!agt && agt.health.healthy);
+ if (!healthyAgents) {
+ return [null, "loading"];
+ }
+
+ // TODO(Cian): Improve logic for determining sidebar app.
+ // For now, we take the first workspace_app with at least one app_status.
+ const sidebarApps = healthyAgents
+ .flatMap((a) => a?.apps)
+ .filter((a) => !!a && a.statuses && a.statuses.length > 0);
+
+ // At this point the workspace build is complete but no app has reported a status
+ // indicating that it is ready. Most well-behaved agentic AI applications will
+ // indicate their readiness status via MCP(coder_report_task).
+ // It's also possible that the application is just not ready yet.
+ // We return "loading" instead of "error" to avoid showing an error state if the app
+ // becomes available shortly after. The tradeoff is that users may see a loading state
+ // indefinitely if there's a genuine issue, but this is preferable to false error alerts.
+ if (!sidebarApps) {
+ return [null, "loading"];
+ }
+
+ const sidebarApp = sidebarApps[0];
+ if (!sidebarApp) {
+ return [null, "loading"];
+ }
+
+ // "disabled" means that the health check is disabled, so we assume
+ // that the app is healthy
+ if (sidebarApp.health === "disabled") {
+ return [sidebarApp, "healthy"];
+ }
+ if (sidebarApp.health === "healthy") {
+ return [sidebarApp, "healthy"];
+ }
+ if (sidebarApp.health === "initializing") {
+ return [sidebarApp, "loading"];
+ }
+ if (sidebarApp.health === "unhealthy") {
+ return [sidebarApp, "error"];
+ }
+
+ // exhaustiveness check
+ const _: never = sidebarApp.health;
+ // this should never happen
+ console.error(
+ "Task workspace has a finished build but the sidebar app is in an unknown health state",
+ task.workspace,
+ );
+ return [null, "error"];
+};
diff --git a/site/src/pages/TaskPage/TaskSidebar.tsx b/site/src/pages/TaskPage/TaskSidebar.tsx
index ca691bea08788..b194e49e52f42 100644
--- a/site/src/pages/TaskPage/TaskSidebar.tsx
+++ b/site/src/pages/TaskPage/TaskSidebar.tsx
@@ -22,66 +22,15 @@ import { TaskStatusLink } from "./TaskStatusLink";
type TaskSidebarProps = {
task: Task;
+ sidebarApp: WorkspaceApp | null;
+ sidebarAppStatus: "error" | "loading" | "healthy";
};
-type SidebarAppStatus = "error" | "loading" | "healthy";
-
-const getSidebarApp = (task: Task): [WorkspaceApp | null, SidebarAppStatus] => {
- const sidebarAppId = task.workspace.latest_build.ai_task_sidebar_app_id;
- // a task workspace with a finished build must have a sidebar app id
- if (!sidebarAppId && task.workspace.latest_build.job.completed_at) {
- console.error(
- "Task workspace has a finished build but no sidebar app id",
- task.workspace,
- );
- return [null, "error"];
- }
-
- const sidebarApp = task.workspace.latest_build.resources
- .flatMap((r) => r.agents)
- .flatMap((a) => a?.apps)
- .find((a) => a?.id === sidebarAppId);
-
- if (!task.workspace.latest_build.job.completed_at) {
- // while the workspace build is running, we don't have a sidebar app yet
- return [null, "loading"];
- }
- if (!sidebarApp) {
- // The workspace build is complete but the expected sidebar app wasn't found in the resources.
- // This could happen due to timing issues or temporary inconsistencies in the data.
- // We return "loading" instead of "error" to avoid showing an error state if the app
- // becomes available shortly after. The tradeoff is that users may see a loading state
- // indefinitely if there's a genuine issue, but this is preferable to false error alerts.
- return [null, "loading"];
- }
- // "disabled" means that the health check is disabled, so we assume
- // that the app is healthy
- if (sidebarApp.health === "disabled") {
- return [sidebarApp, "healthy"];
- }
- if (sidebarApp.health === "healthy") {
- return [sidebarApp, "healthy"];
- }
- if (sidebarApp.health === "initializing") {
- return [sidebarApp, "loading"];
- }
- if (sidebarApp.health === "unhealthy") {
- return [sidebarApp, "error"];
- }
-
- // exhaustiveness check
- const _: never = sidebarApp.health;
- // this should never happen
- console.error(
- "Task workspace has a finished build but the sidebar app is in an unknown health state",
- task.workspace,
- );
- return [null, "error"];
-};
-
-export const TaskSidebar: FC = ({ task }) => {
- const [sidebarApp, sidebarAppStatus] = getSidebarApp(task);
-
+export const TaskSidebar: FC = ({
+ task,
+ sidebarApp,
+ sidebarAppStatus,
+}) => {
return (