Skip to content

chore: deprecate AITaskSidebarAppID and remove existing usage #19245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore(site): remove dependency on sidebar_app_id
  • Loading branch information
johnstcn committed Aug 7, 2025
commit 31e48b6ed8fb4c2f8816a9719d6f2828d85670cb
8 changes: 3 additions & 5 deletions site/src/pages/TaskPage/TaskApps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import { TaskAppIFrame } from "./TaskAppIframe";

type TaskAppsProps = {
task: Task;
sidebarApp: WorkspaceApp | null;
};

type AppWithAgent = {
app: WorkspaceApp;
agent: WorkspaceAgent;
};

export const TaskApps: FC<TaskAppsProps> = ({ task }) => {
export const TaskApps: FC<TaskAppsProps> = ({ task, sidebarApp }) => {
const agents = task.workspace.latest_build.resources
.flatMap((r) => r.agents)
.filter((a) => !!a);
Expand All @@ -42,10 +43,7 @@ export const TaskApps: FC<TaskAppsProps> = ({ 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);
Expand Down
79 changes: 76 additions & 3 deletions site/src/pages/TaskPage/TaskPage.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -171,7 +177,7 @@ const TaskPage = () => {
</Margins>
);
} else {
content = <TaskApps task={task} />;
content = <TaskApps task={task} sidebarApp={sidebarApp} />;
}

return (
Expand All @@ -181,7 +187,11 @@ const TaskPage = () => {
</Helmet>
<PanelGroup autoSaveId="task" direction="horizontal">
<Panel defaultSize={25} minSize={20}>
<TaskSidebar task={task} />
<TaskSidebar
task={task}
sidebarApp={sidebarApp}
sidebarAppStatus={sidebarAppStatus}
/>
</Panel>
<PanelResizeHandle>
<div className="w-1 bg-border h-full hover:bg-border-hover transition-all relative" />
Expand Down Expand Up @@ -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"];
};
65 changes: 7 additions & 58 deletions site/src/pages/TaskPage/TaskSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<TaskSidebarProps> = ({ task }) => {
const [sidebarApp, sidebarAppStatus] = getSidebarApp(task);

export const TaskSidebar: FC<TaskSidebarProps> = ({
task,
sidebarApp,
sidebarAppStatus,
}) => {
return (
<aside className="flex flex-col h-full shrink-0 w-full">
<header className="border-0 border-b border-solid border-border p-4 pt-0">
Expand Down