(workspaceId: string)
| 2012 | } |
| 2013 | |
| 2014 | getWorkspaceShellStatus(workspaceId: string): WorkspaceShellStatus { |
| 2015 | const aggregator = this.assertGet(workspaceId); |
| 2016 | const transient = this.assertChatTransientState(workspaceId); |
| 2017 | const hasMessages = aggregator.hasMessages(); |
| 2018 | const isActiveWorkspace = this.activeOnChatWorkspaceId === workspaceId; |
| 2019 | |
| 2020 | // Keep this selector lighter than getWorkspaceState(): the shell only needs enough |
| 2021 | // state to decide placeholder vs mounted chat. Avoid rebuilding the full displayed |
| 2022 | // transcript on every streaming delta unless init/hydration placeholder behavior needs it. |
| 2023 | const hasRunningInitMessage = |
| 2024 | !hasMessages || (isActiveWorkspace && transient.isHydratingTranscript && !transient.caughtUp) |
| 2025 | ? aggregator |
| 2026 | .getDisplayedMessages() |
| 2027 | .some((message) => message.type === "workspace-init" && message.status === "running") |
| 2028 | : false; |
| 2029 | |
| 2030 | const activity = this.workspaceActivity.get(workspaceId); |
| 2031 | const hasInterruptibleActiveStream = aggregator.hasInterruptibleActiveStream(); |
| 2032 | const pendingStreamStartTime = aggregator.getPendingStreamStartTime(); |
| 2033 | const streamLifecycle = aggregator.getStreamLifecycle(); |
| 2034 | const bufferedActiveStreamStart = |
| 2035 | isActiveWorkspace && !transient.caughtUp |
| 2036 | ? getBufferedActiveStreamStart( |
| 2037 | transient.pendingStreamEvents, |
| 2038 | transient.historicalMessages, |
| 2039 | (messageId) => aggregator.isSideQuestionAnswerMessage(messageId) |
| 2040 | ) |
| 2041 | : null; |
| 2042 | const useAggregatorState = isActiveWorkspace && transient.caughtUp; |
| 2043 | const canInterrupt = useAggregatorState |
| 2044 | ? hasInterruptibleActiveStream |
| 2045 | : bufferedActiveStreamStart !== null |
| 2046 | ? true |
| 2047 | : (activity?.streaming ?? hasInterruptibleActiveStream); |
| 2048 | const activePendingStreamStartTime = isActiveWorkspace ? pendingStreamStartTime : null; |
| 2049 | // Match getWorkspaceState so the shell does not flash between lifecycle and stream-start. |
| 2050 | const isStreamStarting = |
| 2051 | isActiveWorkspace && |
| 2052 | !canInterrupt && |
| 2053 | (streamLifecycle?.phase === "preparing" || activePendingStreamStartTime !== null); |
| 2054 | const isHydratingTranscript = |
| 2055 | isActiveWorkspace && |
| 2056 | transient.isHydratingTranscript && |
| 2057 | !transient.caughtUp && |
| 2058 | !hasRunningInitMessage; |
| 2059 | const loading = !hasMessages && !hasRunningInitMessage && !transient.caughtUp; |
| 2060 | |
| 2061 | const cached = this.workspaceShellStatusCache.get(workspaceId); |
| 2062 | if ( |
| 2063 | cached?.loading === loading && |
| 2064 | cached.isHydratingTranscript === isHydratingTranscript && |
| 2065 | cached.isStreamStarting === isStreamStarting |
| 2066 | ) { |
| 2067 | return cached; |
| 2068 | } |
| 2069 | |
| 2070 | const next = { loading, isHydratingTranscript, isStreamStarting }; |
| 2071 | this.workspaceShellStatusCache.set(workspaceId, next); |
no test coverage detected