@@ -41,6 +41,7 @@ import {
4141import { createLogger } from '@sim/logger'
4242import { useParams , usePathname , useRouter } from 'next/navigation'
4343import { usePostHog } from 'posthog-js/react'
44+ import type { DesktopChatActivity } from '@/lib/api/contracts/desktop-executor'
4445import { useSession } from '@/lib/auth/auth-client'
4546import { canViewWorkspaceBillingSettings } from '@/lib/billing/workspace-permissions'
4647import { focusVisibleBrowserOmnibox } from '@/lib/browser-agent/renderer-shortcuts'
@@ -123,6 +124,7 @@ import { useImportWorkflow } from '@/app/workspace/[workspaceId]/w/hooks'
123124import { useCustomBlockOverlayVersion } from '@/blocks/custom/client-overlay'
124125import { useWorkspaceAccessRequestFeatures } from '@/ee/access-requests/components/permission-access-boundary'
125126import { useWorkspaceCredentials } from '@/hooks/queries/credentials'
127+ import { useDesktopActivity } from '@/hooks/queries/desktop-activity'
126128import { useFolderMap , useFolders } from '@/hooks/queries/folders'
127129import { type LogFilters , useLogsList } from '@/hooks/queries/logs'
128130import type { MothershipChatMetadata } from '@/hooks/queries/mothership-chats'
@@ -183,13 +185,50 @@ const SEARCH_MODAL_DATE_FORMAT = new Intl.DateTimeFormat(undefined, {
183185 minute : '2-digit' ,
184186} )
185187
188+ const DESKTOP_ACTIVITY_COLOR : Record < DesktopChatActivity [ 'state' ] , string > = {
189+ running : '#EAB308' ,
190+ needs_input : '#F97316' ,
191+ blocked : 'var(--text-error)' ,
192+ }
193+
194+ function desktopActivityLabel ( { state, deviceName } : DesktopChatActivity ) : string {
195+ if ( state === 'needs_input' ) return 'Needs input'
196+ if ( state === 'blocked' ) return `Blocked: ${ deviceName } is offline`
197+ return `Running on ${ deviceName } `
198+ }
199+
200+ interface DesktopActivityDotProps {
201+ activity : DesktopChatActivity
202+ }
203+
204+ /** The status of a chat one of the user's desktops is running in the background. */
205+ function DesktopActivityDot ( { activity } : DesktopActivityDotProps ) {
206+ const label = desktopActivityLabel ( activity )
207+ return (
208+ < Tooltip . Root >
209+ < Tooltip . Trigger asChild >
210+ < span
211+ role = 'img'
212+ aria-label = { label }
213+ className = 'size-[6px] rounded-full'
214+ style = { { backgroundColor : DESKTOP_ACTIVITY_COLOR [ activity . state ] } }
215+ />
216+ </ Tooltip . Trigger >
217+ < Tooltip . Content >
218+ < p > { label } </ p >
219+ </ Tooltip . Content >
220+ </ Tooltip . Root >
221+ )
222+ }
223+
186224const SidebarChatItem = memo ( function SidebarChatItem ( {
187225 chat,
188226 isCurrentRoute,
189227 isSelected,
190228 isActive,
191229 isUnread,
192230 isPinned,
231+ desktopActivity,
193232 isMenuOpen,
194233 showCollapsedTooltips,
195234 onMultiSelectClick,
@@ -203,6 +242,8 @@ const SidebarChatItem = memo(function SidebarChatItem({
203242 isActive : boolean
204243 isUnread : boolean
205244 isPinned : boolean
245+ /** Set while one of the user's desktops runs this chat's turn in the background. */
246+ desktopActivity ?: DesktopChatActivity
206247 isMenuOpen : boolean
207248 showCollapsedTooltips : boolean
208249 onMultiSelectClick : ( chatId : string , shiftKey : boolean ) => void
@@ -217,7 +258,7 @@ const SidebarChatItem = memo(function SidebarChatItem({
217258 * transient state (a run in progress, or an unread reply elsewhere), while pinning
218259 * is persistent and already conveyed by the row sorting to the top of the list.
219260 */
220- const showStatusDot = isActive || ( ! isCurrentRoute && isUnread )
261+ const showStatusDot = Boolean ( desktopActivity ) || isActive || ( ! isCurrentRoute && isUnread )
221262
222263 function handleDragStart ( e : React . DragEvent ) {
223264 e . dataTransfer . effectAllowed = 'copyMove'
@@ -262,7 +303,9 @@ const SidebarChatItem = memo(function SidebarChatItem({
262303 < RowActions
263304 open = { isMenuOpen }
264305 indicator = {
265- showStatusDot ? (
306+ desktopActivity ? (
307+ < DesktopActivityDot activity = { desktopActivity } />
308+ ) : showStatusDot ? (
266309 < span
267310 aria-hidden = 'true'
268311 className = 'size-[6px] rounded-full'
@@ -843,6 +886,15 @@ export const Sidebar = memo(function Sidebar({ organizationHref }: SidebarProps)
843886 )
844887
845888 useMothershipChatEvents ( workspaceId , chatEnabled && ! permissionConfig . hideCopilot )
889+ const desktopExecutorEnabled = useFeatureFlag ( 'mothership-desktop-background-executor' )
890+ const { data : desktopActivity } = useDesktopActivity (
891+ workspaceId ,
892+ desktopExecutorEnabled && chatEnabled && ! permissionConfig . hideCopilot
893+ )
894+ const desktopActivityByChat = useMemo (
895+ ( ) => new Map ( ( desktopActivity ?? [ ] ) . map ( ( activity ) => [ activity . chatId , activity ] ) ) ,
896+ [ desktopActivity ]
897+ )
846898
847899 /**
848900 * Stays empty when Chat is disabled, which also drops the command palette's
@@ -1537,6 +1589,7 @@ export const Sidebar = memo(function Sidebar({ organizationHref }: SidebarProps)
15371589 isActive = { ! ! chat . isActive }
15381590 isUnread = { ! ! chat . isUnread }
15391591 isPinned = { ! ! chat . isPinned }
1592+ desktopActivity = { desktopActivityByChat . get ( chat . id ) }
15401593 isMenuOpen = { menuOpenChatId === chat . id }
15411594 showCollapsedTooltips = { showCollapsedTooltips }
15421595 onMultiSelectClick = { handleChatClick }
0 commit comments