Skip to content
Draft
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
22 changes: 21 additions & 1 deletion desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from "react";
import { CheckCheck, ChevronDown, Radio } from "lucide-react";

import type { UserProfileLookup } from "@/features/profile/lib/identity";
import { useAnchoredScroll } from "@/features/messages/ui/useAnchoredScroll";
import { cn } from "@/shared/lib/cn";
import {
Dialog,
Expand Down Expand Up @@ -67,18 +68,31 @@ export function AgentSessionTranscriptList({
agentAvatarUrl,
agentName,
agentPubkey,
autoTail = false,
emptyDescription,
items,
profiles,
scrollScopeKey,
}: AgentTranscriptIdentityProps & {
autoTail?: boolean;
emptyDescription: string;
items: TranscriptItem[];
profiles?: UserProfileLookup;
scrollScopeKey?: string | null;
}) {
const displayBlocks = React.useMemo(
() => buildTranscriptDisplayBlocks(items),
[items],
);
const scrollContainerRef = React.useRef<HTMLDivElement>(null);
const contentRef = React.useRef<HTMLDivElement>(null);
const anchoredScroll = useAnchoredScroll({
channelId: autoTail ? (scrollScopeKey ?? agentPubkey) : null,
contentRef,
isLoading: false,
messages: items,
scrollContainerRef,
});

if (items.length === 0) {
return (
Expand All @@ -91,16 +105,22 @@ export function AgentSessionTranscriptList({
}

return (
<div className="w-full">
<div
className={cn("w-full", autoTail ? "h-full overflow-y-auto" : null)}
onScroll={autoTail ? anchoredScroll.onScroll : undefined}
ref={autoTail ? scrollContainerRef : undefined}
>
<div
aria-label="Live ACP transcript"
aria-live="polite"
className="flex w-full flex-col gap-2.5"
ref={autoTail ? contentRef : undefined}
role="log"
>
{displayBlocks.map((block) => (
<div
className="content-visibility-auto"
data-message-id={getDisplayBlockKey(block)}
key={getDisplayBlockKey(block)}
>
<TranscriptDisplayBlockView
Expand Down
10 changes: 10 additions & 0 deletions desktop/src/features/agents/ui/ManagedAgentSessionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type ManagedAgentSessionPanelProps = {
agent: Pick<ManagedAgent, "pubkey" | "name" | "status"> & {
avatarUrl?: string | null;
};
autoTail?: boolean;
channelId?: string | null;
className?: string;
emptyDescription?: string;
Expand All @@ -47,6 +48,7 @@ type ManagedAgentSessionPanelProps = {

export function ManagedAgentSessionPanel({
agent,
autoTail = false,
channelId = null,
className,
emptyDescription = "Mention this agent in a channel to watch the next turn.",
Expand Down Expand Up @@ -106,6 +108,8 @@ export function ManagedAgentSessionPanel({
agentName={agent.name}
agentPubkey={agent.pubkey}
connectionState={connectionState}
autoTail={autoTail}
channelId={channelId}
emptyDescription={emptyDescription}
errorMessage={errorMessage}
events={displayEvents}
Expand Down Expand Up @@ -159,7 +163,9 @@ function SessionBody({
agentAvatarUrl,
agentName,
agentPubkey,
autoTail,
connectionState,
channelId,
emptyDescription,
errorMessage,
events,
Expand All @@ -173,6 +179,8 @@ function SessionBody({
agentAvatarUrl: string | null;
agentName: string;
agentPubkey: string;
autoTail: boolean;
channelId: string | null;
connectionState: ConnectionState;
emptyDescription: string;
errorMessage: string | null;
Expand Down Expand Up @@ -224,6 +232,8 @@ function SessionBody({
emptyDescription={emptyDescription}
items={transcript}
profiles={profiles}
scrollScopeKey={`${agentPubkey}:${channelId ?? "all"}`}
autoTail={autoTail}
/>
{rawRail.mode === "side" ? <RawEventRail events={events} /> : null}
</div>
Expand Down
9 changes: 6 additions & 3 deletions desktop/src/features/channels/ui/AgentSessionThreadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type { ChannelAgentSessionAgent } from "./useChannelAgentSessions";
type AgentSessionThreadPanelProps = {
agent: ChannelAgentSessionAgent;
channel: Channel | null;
channelId?: string | null;
canInterruptTurn: boolean;
isWorking: boolean;
layout?: "standalone" | "split";
Expand All @@ -47,6 +48,7 @@ export function AgentSessionThreadPanel({
agent,
canInterruptTurn,
channel,
channelId = null,
isWorking,
layout = "standalone",
isSinglePanelView = false,
Expand All @@ -62,7 +64,8 @@ export function AgentSessionThreadPanel({
useEscapeKey(onClose, isOverlay || isSinglePanelView);

const { ref: scrollRef, onScroll } = useStickToBottom<HTMLDivElement>();
const rawFeedScopeKey = `${agent.pubkey}:${channel?.id ?? "all"}`;
const sessionChannelId = channelId ?? channel?.id ?? null;
const rawFeedScopeKey = `${agent.pubkey}:${sessionChannelId ?? "all"}`;
const [rawFeedState, setRawFeedState] = React.useState(() => ({
scopeKey: rawFeedScopeKey,
show: false,
Expand Down Expand Up @@ -223,10 +226,10 @@ export function AgentSessionThreadPanel({
>
<ManagedAgentSessionPanel
agent={agent}
channelId={channel?.id ?? null}
channelId={sessionChannelId}
className="border-0 bg-transparent p-0 shadow-none"
emptyDescription={
channel
sessionChannelId
? `Mention ${agent.name} in the channel to see its work here.`
: `Mention ${agent.name} in any channel to see its work here.`
}
Expand Down
4 changes: 2 additions & 2 deletions desktop/src/features/channels/ui/BotActivityBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type BotActivityAgent = Pick<ManagedAgent, "pubkey" | "name">;
type BotActivityBarProps = {
agents: BotActivityAgent[];
channelId?: string | null;
onOpenAgentSession: (pubkey: string) => void;
onOpenAgentSession: (pubkey: string, channelId?: string | null) => void;
openAgentSessionPubkey: string | null;
profiles?: UserProfileLookup;
typingBotPubkeys: string[];
Expand Down Expand Up @@ -237,7 +237,7 @@ export function BotActivityComposerAction({
onClick={() => {
clearHoverTimer();
setOpen(false);
onOpenAgentSession(agent.pubkey);
onOpenAgentSession(agent.pubkey, channelId);
}}
type="button"
>
Expand Down
18 changes: 12 additions & 6 deletions desktop/src/features/channels/ui/ChannelPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export const ChannelPane = React.memo(function ChannelPane({
profiles,
openThreadHeadId,
shouldShowThreadSkeleton,
openAgentSessionChannelId,
openAgentSessionPubkey,
onProfilePanelViewChange,
onProfilePanelTabChange,
Expand Down Expand Up @@ -822,13 +823,18 @@ export const ChannelPane = React.memo(function ChannelPane({
agent={selectedAgent}
canInterruptTurn={selectedAgent.canInterruptTurn}
channel={
agentSessionSelection.isAgentInActivityList({
activityAgents,
selectedAgent,
})
? activeChannel
: null
openAgentSessionChannelId
? activeChannel?.id === openAgentSessionChannelId
? activeChannel
: null
: agentSessionSelection.isAgentInActivityList({
activityAgents,
selectedAgent,
})
? activeChannel
: null
}
channelId={openAgentSessionChannelId}
isWorking={botTypingEntries.some(
(entry) =>
entry.pubkey.toLowerCase() ===
Expand Down
3 changes: 2 additions & 1 deletion desktop/src/features/channels/ui/ChannelPane.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export type ChannelPaneProps = {
onMarkRead?: (message: TimelineMessage) => void;
onExpandThreadReplies: (message: TimelineMessage) => void;
onJoinChannel?: () => Promise<void>;
onOpenAgentSession: (pubkey: string) => void;
onOpenAgentSession: (pubkey: string, channelId?: string | null) => void;
onOpenDm?: (pubkeys: string[]) => Promise<void> | void;
onOpenMembers?: () => void;
onOpenProfilePanel: (pubkey: string) => void;
Expand Down Expand Up @@ -94,6 +94,7 @@ export type ChannelPaneProps = {
profiles?: UserProfileLookup;
openThreadHeadId: string | null;
shouldShowThreadSkeleton: boolean;
openAgentSessionChannelId: string | null;
openAgentSessionPubkey: string | null;
onProfilePanelViewChange: (
view: ProfilePanelView,
Expand Down
4 changes: 4 additions & 0 deletions desktop/src/features/channels/ui/ChannelScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,14 @@ export function ChannelScreen({
const {
channelManagementOpen,
clearMessageRouteTarget,
openAgentSessionChannelId,
openAgentSessionPubkey,
openThreadHeadId,
profilePanelPubkey,
profilePanelTab,
profilePanelView,
setChannelManagementOpen,
setOpenAgentSessionChannelId,
setOpenAgentSessionPubkey,
setOpenThreadHeadId,
setProfilePanelTab,
Expand Down Expand Up @@ -591,6 +593,7 @@ export function ChannelScreen({
profilePanelPubkey,
setChannelManagementOpen,
setExpandedThreadReplyIds,
setOpenAgentSessionChannelId,
setOpenAgentSessionPubkey,
setOpenThreadHeadId,
setProfilePanelPubkey,
Expand Down Expand Up @@ -927,6 +930,7 @@ export function ChannelScreen({
onThreadPanelResizeStart={handleThreadPanelResizeStart}
onTargetReached={handleTargetReached}
onToggleReaction={effectiveToggleReaction}
openAgentSessionChannelId={openAgentSessionChannelId}
openAgentSessionPubkey={openAgentSessionPubkey}
openThreadHeadId={effectiveOpenThreadHeadId}
shouldShowThreadSkeleton={shouldShowThreadSkeleton}
Expand Down
11 changes: 8 additions & 3 deletions desktop/src/features/channels/ui/useChannelAgentSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type UseChannelAgentSessionsOptions = {
profilePanelPubkey?: string | null;
setChannelManagementOpen: (open: boolean) => void;
setExpandedThreadReplyIds: (value: Set<string>) => void;
setOpenAgentSessionChannelId: PanelValueSetter;
setOpenAgentSessionPubkey: PanelValueSetter;
setOpenThreadHeadId: (value: string | null) => void;
setProfilePanelPubkey: (value: string | null) => void;
Expand Down Expand Up @@ -164,6 +165,7 @@ export function useChannelAgentSessions({
profilePanelPubkey = null,
setChannelManagementOpen,
setExpandedThreadReplyIds,
setOpenAgentSessionChannelId,
setOpenAgentSessionPubkey,
setOpenThreadHeadId,
setProfilePanelPubkey,
Expand All @@ -187,17 +189,19 @@ export function useChannelAgentSessions({
}, [setOpenAgentSessionPubkey]);

const openAgentSession = React.useCallback(
(pubkey: string) => {
(pubkey: string, channelId?: string | null) => {
setOpenThreadHeadId(null);
setExpandedThreadReplyIds(new Set());
setThreadScrollTargetId(null);
setThreadReplyTargetId(null);
setChannelManagementOpen(false);
setOpenAgentSessionPubkey(pubkey);
setOpenAgentSessionChannelId(channelId ?? null);
},
[
setChannelManagementOpen,
setExpandedThreadReplyIds,
setOpenAgentSessionChannelId,
setOpenAgentSessionPubkey,
setOpenThreadHeadId,
setThreadReplyTargetId,
Expand All @@ -206,10 +210,11 @@ export function useChannelAgentSessions({
);

const selectAgentSession = React.useCallback(
(pubkey: string) => {
(pubkey: string, channelId?: string | null) => {
setOpenAgentSessionPubkey(pubkey);
setOpenAgentSessionChannelId(channelId ?? null);
},
[setOpenAgentSessionPubkey],
[setOpenAgentSessionChannelId, setOpenAgentSessionPubkey],
);

const openThreadAndCloseAgentSession = React.useCallback(
Expand Down
17 changes: 15 additions & 2 deletions desktop/src/features/channels/ui/useChannelPanelHistoryState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
*
* Params: `thread` (open thread head id), `profile` (profile panel pubkey),
* `profileView` (profile panel focused view), `profileTab` (profile summary
* tab), `agentSession` (agent session panel pubkey), `channelManagement`
* tab), `agentSession` (agent session panel pubkey), `agentSessionChannel`
* (optional channel scope for the agent session panel), `channelManagement`
* (presence flag for the channel-management panel — open/closed only, so it
* carries a sentinel `"1"` rather than an id).
*/
Expand All @@ -32,6 +33,7 @@ export type PanelValueSetter = (

const CHANNEL_SEARCH_KEYS = [
"agentSession",
"agentSessionChannel",
"channelManagement",
"messageId",
"profile",
Expand Down Expand Up @@ -75,7 +77,16 @@ export function useChannelPanelHistoryState() {
);

const setOpenAgentSessionPubkey = React.useCallback<PanelValueSetter>(
(value, options) => applyPatch({ agentSession: value }, options),
(value, options) =>
applyPatch(
{ agentSession: value, agentSessionChannel: value ? undefined : null },
options,
),
[applyPatch],
);

const setOpenAgentSessionChannelId = React.useCallback<PanelValueSetter>(
(value, options) => applyPatch({ agentSessionChannel: value }, options),
[applyPatch],
);

Expand All @@ -97,12 +108,14 @@ export function useChannelPanelHistoryState() {
return {
channelManagementOpen: values.channelManagement != null,
clearMessageRouteTarget,
openAgentSessionChannelId: values.agentSessionChannel,
openAgentSessionPubkey: values.agentSession,
openThreadHeadId: values.thread,
profilePanelPubkey: values.profile,
profilePanelTab: profilePanelTabFromSearch(values.profileTab),
profilePanelView: profilePanelViewFromSearch(values.profileView),
setChannelManagementOpen,
setOpenAgentSessionChannelId,
setOpenAgentSessionPubkey,
setOpenThreadHeadId,
setProfilePanelTab,
Expand Down
4 changes: 1 addition & 3 deletions desktop/src/features/messages/ui/useAnchoredScroll.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as React from "react";

import type { TimelineMessage } from "@/features/messages/types";

/**
* Distance (in CSS pixels) below which we consider the scroll position
* "at the bottom" of the message list. Tight enough that the user has to
Expand Down Expand Up @@ -44,7 +42,7 @@ type UseAnchoredScrollOptions = {
isLoading: boolean;
/** Source of truth for the rendered list. Used to detect new-at-bottom
* arrivals and to seed/refresh the anchor pre-render. */
messages: TimelineMessage[];
messages: Array<{ id: string }>;

/** When set, scroll to and highlight this message on mount and on change. */
targetMessageId?: string | null;
Expand Down
Loading
Loading