diff --git a/desktop/src/features/channels/ui/ChannelAgentSessionSurface.tsx b/desktop/src/features/channels/ui/ChannelAgentSessionSurface.tsx
new file mode 100644
index 0000000000..64589bc316
--- /dev/null
+++ b/desktop/src/features/channels/ui/ChannelAgentSessionSurface.tsx
@@ -0,0 +1,105 @@
+import type * as React from "react";
+
+import * as agentSessionSelection from "@/features/channels/ui/agentSessionSelection";
+import type { ChannelAgentSessionAgent } from "@/features/channels/ui/useChannelAgentSessions";
+import { getAgentSessionPanelPresentation } from "@/features/channels/lib/agentSessionPanelPresentation";
+import { AgentActivityDrawer } from "@/features/channels/ui/AgentActivityDrawer";
+import { AgentSessionThreadPanel } from "@/features/channels/ui/AgentSessionThreadPanel";
+import type { BotActivityAgent } from "@/features/channels/ui/BotActivityBar";
+import type { UserProfileLookup } from "@/features/profile/lib/identity";
+import type { Channel } from "@/shared/api/types";
+
+type ChannelAgentSessionSurfaceProps = {
+ activeChannel: Channel;
+ activeChannelId: string | null;
+ activityAgents: BotActivityAgent[];
+ agent: ChannelAgentSessionAgent;
+ /** Render the panel inside the cover drawer instead of the split pane. */
+ isCoverDrawer: boolean;
+ isSinglePanelView: boolean;
+ onBack?: () => void;
+ onClose: () => void;
+ openAgentSessionChannelId: string | null;
+ profiles?: UserProfileLookup;
+ useSplitAuxiliaryPane: boolean;
+ widthPx: number;
+ /**
+ * Applies the split-pane presentation, including its resize affordances.
+ * Supplied by `ChannelPane` because that pane owns the resize state; the
+ * cover-drawer presentation is applied here.
+ */
+ wrapSplitPane: (panel: React.ReactNode) => React.ReactNode;
+};
+
+/**
+ * The channel's agent activity surface: the session panel plus the channel
+ * re-scoping its content and actions depend on.
+ *
+ * Split out of `ChannelPane` so the re-scoping rule below has one home and is
+ * not another branch inside that component's auxiliary-surface chain. Which
+ * presentation this lands in is decided upstream and applied through `wrap`.
+ */
+export function ChannelAgentSessionSurface({
+ activeChannel,
+ activeChannelId,
+ activityAgents,
+ agent,
+ isCoverDrawer,
+ isSinglePanelView,
+ onBack,
+ onClose,
+ openAgentSessionChannelId,
+ profiles,
+ useSplitAuxiliaryPane,
+ widthPx,
+ wrapSplitPane,
+}: ChannelAgentSessionSurfaceProps) {
+ // When the panel was opened from a different channel than the currently
+ // active one, re-scope it to the active channel so that both the
+ // content/header AND channel-backed actions (e.g. Stop current turn) operate
+ // on the same channel object.
+ const effectiveAgentSessionChannelId =
+ openAgentSessionChannelId && activeChannel.id !== openAgentSessionChannelId
+ ? activeChannelId
+ : openAgentSessionChannelId;
+ const channel = effectiveAgentSessionChannelId
+ ? effectiveAgentSessionChannelId === activeChannel.id
+ ? activeChannel
+ : null
+ : agentSessionSelection.isAgentInActivityList({
+ activityAgents,
+ selectedAgent: agent,
+ })
+ ? activeChannel
+ : null;
+
+ const layoutProps = getAgentSessionPanelPresentation({
+ isCoverDrawer,
+ isSinglePanelView,
+ useSplitAuxiliaryPane,
+ });
+ const panel = (
+
+ );
+
+ return isCoverDrawer ? (
+
+ {panel}
+
+ ) : (
+ wrapSplitPane(panel)
+ );
+}
diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx
index 3312e37843..653af8bf8a 100644
--- a/desktop/src/features/channels/ui/ChannelPane.tsx
+++ b/desktop/src/features/channels/ui/ChannelPane.tsx
@@ -25,16 +25,12 @@ import {
import { buildVideoReviewPresentationByMessageId } from "@/features/messages/lib/videoReviewContext";
import { useComposerHeightPadding } from "@/features/messages/ui/useComposerHeightPadding";
import { UserProfilePanel } from "@/features/profile/ui/UserProfilePanel";
-import { AgentSessionThreadPanel } from "@/features/channels/ui/AgentSessionThreadPanel";
import { ChannelManagementAuxiliaryPanel } from "@/features/channels/ui/ChannelManagementAuxiliaryPanel";
import { RightAuxiliaryPane } from "@/features/channels/ui/RightAuxiliaryPane";
import { ThreadViewModeToggle } from "@/features/channels/ui/ThreadViewModeToggle";
-import { AgentActivityDrawer } from "@/features/channels/ui/AgentActivityDrawer";
+import { ChannelAgentSessionSurface } from "@/features/channels/ui/ChannelAgentSessionSurface";
import { FocusThreadDrawer } from "@/features/channels/ui/FocusThreadDrawer";
-import {
- AGENT_SESSION_SURFACE_KEY,
- getAgentSessionPanelPresentation,
-} from "@/features/channels/lib/agentSessionPanelPresentation";
+import { AGENT_SESSION_SURFACE_KEY } from "@/features/channels/lib/agentSessionPanelPresentation";
import {
resolveChannelAuxiliarySurface,
resolveChannelCoverDrawer,
@@ -534,20 +530,10 @@ export const ChannelPane = React.memo(function ChannelPane({
) : (
wrapAux(panel, "message-thread-panel", { key: THREAD_SURFACE_KEY })
);
- const wrapAgentSessionPanel = (panel: React.ReactNode) =>
- useAgentActivityDrawer ? (
-
- {panel}
-
- ) : (
- wrapAux(panel, "agent-session-thread-panel", {
- key: AGENT_SESSION_SURFACE_KEY,
- })
- );
+ const wrapAgentSessionSplitPane = (panel: React.ReactNode) =>
+ wrapAux(panel, "agent-session-thread-panel", {
+ key: AGENT_SESSION_SURFACE_KEY,
+ });
const threadHeaderLeading = useSplitAuxiliaryPane ? (
) : undefined;
@@ -557,11 +543,6 @@ export const ChannelPane = React.memo(function ChannelPane({
isSinglePanelView,
useSplitAuxiliaryPane,
});
- const agentSessionLayoutProps = getAgentSessionPanelPresentation({
- isCoverDrawer: useAgentActivityDrawer,
- isSinglePanelView,
- useSplitAuxiliaryPane,
- });
const timelineReplyHandler =
activeChannel?.archivedAt || isHuddleTranscript ? undefined : onOpenThread;
return (
@@ -910,42 +891,22 @@ export const ChannelPane = React.memo(function ChannelPane({
) : auxiliarySurface === "agent-session" &&
activeChannel &&
selectedAgent ? (
- (() => {
- // When the panel was opened from a different channel than the
- // currently active one, re-scope it to the active channel so
- // that both the content/header AND channel-backed actions (e.g.
- // Stop current turn) operate on the same channel object.
- const effectiveAgentSessionChannelId =
- openAgentSessionChannelId &&
- activeChannel.id !== openAgentSessionChannelId
- ? activeChannelId
- : openAgentSessionChannelId;
- const panel = (
-
- );
- return wrapAgentSessionPanel(panel);
- })()
+
) : auxiliarySurface === "profile" && profilePanelPubkey ? (
(() => {
const panel = (