From 3b9ba3122ed807a27743cd1c0a7650cea0b4cf83 Mon Sep 17 00:00:00 2001 From: Harshit Singh Bhandari <24b4506@iitb.ac.in> Date: Sun, 3 May 2026 20:39:57 +0530 Subject: [PATCH] feat(web): add 'Open orchestrator' to sidebar 3-dot menu (#1615) * feat(web): add 'Open orchestrator' to sidebar 3-dot menu Adds a labeled menu entry above 'Project settings' that navigates to the project's orchestrator session. The orchestrator is the most-used session in any project, but today the only path to it is the unlabeled icon button next to the dashboard icon - easy to miss for new users. The entry is hidden when no live orchestrator exists (matching the existing icon-button pattern), so the menu shrinks gracefully on projects where 'ao start' has never run or has stopped. Closes #1613 Co-Authored-By: Claude Opus 4.7 * refactor(web): drop redundant guard in orchestrator menu render Hold the validated session in `liveOrchestrator` instead of a separate boolean flag. TypeScript narrows automatically from the assignment, so the render condition no longer needs `&& orchestratorSession` to satisfy the type checker. Addresses review feedback on #1613. Co-Authored-By: Claude Opus 4.7 --------- Co-authored-by: Claude Opus 4.7 --- .../web/src/components/ProjectSidebar.tsx | 26 +++++- .../__tests__/ProjectSidebar.test.tsx | 93 +++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/packages/web/src/components/ProjectSidebar.tsx b/packages/web/src/components/ProjectSidebar.tsx index 8164f7c87..7d810c836 100644 --- a/packages/web/src/components/ProjectSidebar.tsx +++ b/packages/web/src/components/ProjectSidebar.tsx @@ -6,7 +6,7 @@ import { useRouter } from "next/navigation"; import { cn } from "@/lib/cn"; import type { ProjectInfo } from "@/lib/project-name"; import { getAttentionLevel, type DashboardSession, type AttentionLevel } from "@/lib/types"; -import { isOrchestratorSession } from "@aoagents/ao-core/types"; +import { isOrchestratorSession, isTerminalSession } from "@aoagents/ao-core/types"; import { getSessionTitle, humanizeBranch } from "@/lib/format"; import { usePopoverClamp } from "@/hooks/usePopoverClamp"; import { getOrchestratorSessionId } from "@/lib/orchestrator-utils"; @@ -397,6 +397,14 @@ function ProjectSidebarInner({ const orchestratorSession = sessions?.find( (s) => s.projectId === project.id && s.id === canonicalOrchestratorId, ); + const liveOrchestrator = + orchestratorSession && + !isTerminalSession({ + status: orchestratorSession.status, + activity: orchestratorSession.activity, + }) + ? orchestratorSession + : null; return (
@@ -563,6 +571,22 @@ function ProjectSidebarInner({ role="menu" aria-label={`${project.name} actions`} > + {liveOrchestrator ? ( + + ) : null}