Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { flowChatStore } from '@/flow_chat/store/FlowChatStore';
import { useSessionProfile } from '../../session-profiles';
import { getWorkspaceSceneDef } from '../../navigation/workspaceSceneRegistry';
import { resolveWorkContextForSurface } from '../../navigation/workspaceTopBarContext';
import { useSessionHeaderContext } from '../../hooks/useSessionHeaderContext';
import {
useWorkspaceSurfaceStore,
type WorkspaceSceneHistoryEntry,
Expand Down Expand Up @@ -106,7 +107,7 @@ const UnifiedTopBar: React.FC<UnifiedTopBarProps> = ({
const { themes, themeId, setTheme, loading: themeLoading } = useTheme();
const { hasWorkspace } = useLastUsedWorkspace();
const { warning } = useNotification();
const sessionContext = useHeaderStore((s) => s.sessionContext);
const sessionContext = useSessionHeaderContext();
const contextNavOverrides = useHeaderStore((s) => s.contextNavOverrides);
const requestOpenWorkDock = useWorkDockStore((s) => s.requestOpenWorkDock);
const works = useWorkStore((s) => s.works);
Expand Down
3 changes: 3 additions & 0 deletions src/web-ui/src/app/components/WorkDock/NewWorkDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
initialScopeRequirement?: ProductAppLaunchScopeRequirement;
}

export function productAppWorkChoice(appId: string): NewWorkAgentChoice {

Check warning on line 67 in src/web-ui/src/app/components/WorkDock/NewWorkDialog.tsx

View workflow job for this annotation

GitHub Actions / Frontend Build

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
return `${PRODUCT_APP_CHOICE_PREFIX}${appId}` as NewWorkAgentChoice;
}

Expand Down Expand Up @@ -633,6 +633,9 @@
try {
if (startMode === 'agentic-os') {
const agenticOsSessionId = await openAgenticOsSession();
if (!agenticOsSessionId) {
throw new Error('Failed to open Agentic OS session');
}
await flowChatManager.sendMessage(
trimmedObjective,
agenticOsSessionId,
Expand Down
7 changes: 4 additions & 3 deletions src/web-ui/src/app/globalOverlayEscape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* and when nested UI should consume Esc first.
*/

import { useWorkspaceSurfaceStore } from './navigation/workspaceSurfaceStore';
import { useWorkspaceSurfaceStore, selectFocusedSessionId } from './navigation/workspaceSurfaceStore';
import { shortcutManager } from '@/infrastructure/services/ShortcutManager';
import { ALL_SHORTCUTS } from '@/shared/constants/shortcuts';
import { flowChatStore } from '@/flow_chat/store/FlowChatStore';
Expand Down Expand Up @@ -52,7 +52,7 @@ function isTargetInsideChatShortcutScope(target: EventTarget | null): boolean {
}

function isActiveSessionProcessing(): boolean {
const sid = useWorkspaceSurfaceStore.getState().focusedSessionId;
const sid = selectFocusedSessionId(useWorkspaceSurfaceStore.getState());
if (!sid) return false;
return stateMachineManager.getCurrentState(sid) === SessionExecutionState.PROCESSING;
}
Expand All @@ -76,7 +76,8 @@ export function installGlobalSurfaceEscapeToHome(): void {
if (!eventMatchesEscapeToAgenticBinding(event)) return;
if (shouldDeferForNestedEscapeUi(event)) return;

const { activeSurface, focusedSessionId } = useWorkspaceSurfaceStore.getState();
const { activeSurface } = useWorkspaceSurfaceStore.getState();
const focusedSessionId = selectFocusedSessionId(useWorkspaceSurfaceStore.getState());
if (activeSurface.kind !== 'agentic-os-home') {
event.preventDefault();
event.stopImmediatePropagation();
Expand Down
4 changes: 2 additions & 2 deletions src/web-ui/src/app/hooks/useCurrentSessionTitle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* Subscribes to flowChatStore so the value updates reactively.
*/

import { useWorkspaceSurfaceStore } from '../navigation/workspaceSurfaceStore';
import { useWorkspaceSurfaceStore, selectFocusedSessionId } from '../navigation/workspaceSurfaceStore';
import { useFlowChatStoreSelector } from '../../flow_chat/hooks/useFlowChatStoreSelector';

export function useCurrentSessionTitle(): string {
const focusedSessionId = useWorkspaceSurfaceStore(state => state.focusedSessionId);
const focusedSessionId = useWorkspaceSurfaceStore(selectFocusedSessionId);
return useFlowChatStoreSelector(
state => {
const session = focusedSessionId ? state.sessions.get(focusedSessionId) : undefined;
Expand Down
52 changes: 52 additions & 0 deletions src/web-ui/src/app/hooks/useSessionHeaderContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Derived session header context for UnifiedTopBar.
* Single source: focused session from surface store + FlowChatStore session data.
*/

import { useMemo } from 'react';
import {
getWorkspaceDisplayName,
useWorkspaceContext,
} from '@/infrastructure/contexts/WorkspaceContext';
import {
fallbackWorkspaceFolderLabel,
resolveWorkspaceForSession,
} from '@/flow_chat/utils/sessionOrdering';
import { useFlowChatStoreSelector } from '@/flow_chat/hooks/useFlowChatStoreSelector';
import {
selectFocusedSessionId,
useWorkspaceSurfaceStore,
} from '../navigation/workspaceSurfaceStore';
import type { SessionHeaderContext } from '../stores/headerStore';

export function useSessionHeaderContext(): SessionHeaderContext | null {
const focusedSessionId = useWorkspaceSurfaceStore(selectFocusedSessionId);
const { openedWorkspacesList } = useWorkspaceContext();

const session = useFlowChatStoreSelector((state) => (
focusedSessionId ? state.sessions.get(focusedSessionId) : undefined
));

return useMemo((): SessionHeaderContext | null => {
if (!session?.sessionId || !session.descriptor) {
return null;
}

let workspaceDisplayName = '';
if (session.workspacePath?.trim()) {
const ws = resolveWorkspaceForSession(session, openedWorkspacesList);
if (ws) {
workspaceDisplayName = getWorkspaceDisplayName(ws).trim();
}
if (!workspaceDisplayName) {
workspaceDisplayName = fallbackWorkspaceFolderLabel(session.workspacePath);
}
}

return {
descriptor: session.descriptor,
workspacePath: session.workspacePath,
workspaceDisplayName,
};
}, [openedWorkspacesList, session]);
}
18 changes: 9 additions & 9 deletions src/web-ui/src/app/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import { useI18n } from '@/infrastructure/i18n';
import { consumeDeferredNewSessionWorkspace } from '../utils/deferredWorkspaceSession';
import { appRuntime, runtimePolicy } from '@/infrastructure/app-runtime';
import { descriptorFromAgentType, getDefaultSessionDescriptor } from '@/flow_chat/domain/sessionDescriptor';
import { useWorkspaceSurfaceStore } from '../navigation/workspaceSurfaceStore';
import { useWorkspaceSurfaceStore, selectFocusedSessionId } from '../navigation/workspaceSurfaceStore';
import { commitStartupHome } from '../navigation/navigationController';
import './AppLayout.scss';

const log = createLogger('AppLayout');
Expand Down Expand Up @@ -177,20 +178,21 @@ const AppLayout: React.FC<AppLayoutProps> = ({ className = '' }) => {
const initializationPreferredDescriptor = explicitPreferredMode
? descriptorFromAgentType(explicitPreferredMode)
: undefined;
const suppressAutoSessionSelection = consumeDeferredNewSessionWorkspace(
lastUsedWorkspace.rootPath
);
consumeDeferredNewSessionWorkspace(lastUsedWorkspace.rootPath);

commitStartupHome();

const flowChatManager = FlowChatManager.getInstance();
const initialization = await flowChatManager.initializeWorkspaceSessionState(
lastUsedWorkspace.rootPath,
{
preferredDescriptor: initializationPreferredDescriptor,
skipAutoSelectSession: suppressAutoSessionSelection,
skipAutoSelectSession: true,
createDefaultSession: true,
defaultSessionConfig: {
workspaceId: lastUsedWorkspace.id,
workspacePath: lastUsedWorkspace.rootPath,
navigate: false,
},
defaultSessionDescriptor: initializationPreferredDescriptor ?? getDefaultSessionDescriptor(),
}
Expand All @@ -199,8 +201,7 @@ const AppLayout: React.FC<AppLayoutProps> = ({ className = '' }) => {
const surfaceState = useWorkspaceSurfaceStore.getState();
const workspaceScopedActiveId =
initialization.createdSessionId ||
surfaceState.composerTargetSessionId ||
surfaceState.focusedSessionId ||
selectFocusedSessionId(surfaceState) ||
initialization.focusedSessionId;

const pendingDescription = sessionStorage.getItem('pendingProjectDescription');
Expand All @@ -213,8 +214,7 @@ const AppLayout: React.FC<AppLayoutProps> = ({ className = '' }) => {
const latestSurfaceState = useWorkspaceSurfaceStore.getState();
const targetSessionId =
pendingTargetSessionId ||
latestSurfaceState.composerTargetSessionId ||
latestSurfaceState.focusedSessionId;
selectFocusedSessionId(latestSurfaceState);

if (!targetSessionId) {
log.error('Cannot find active session ID');
Expand Down
Loading
Loading