feat(chat): allow prompting subagent sessions

This commit is contained in:
Bohdan Triapitsyn
2026-07-13 01:06:44 +03:00
parent 04307e163b
commit 7e12a0dcef
16 changed files with 92 additions and 9 deletions
@@ -404,6 +404,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
// UI store
const isExpandedInput = useUIStore((state) => state.isExpandedInput);
const stickyUserHeader = useUIStore((state) => state.stickyUserHeader);
const allowPromptingSubagentSessions = useUIStore((state) => state.allowPromptingSubagentSessions);
const isTimelineDialogOpen = useUIStore((s) => s.isTimelineDialogOpen);
const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen);
@@ -585,7 +586,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
{t('chat.container.returnToParent.label')}
</Button>
) : null;
const promptReadOnly = readOnly || Boolean(parentSession);
const promptReadOnly = readOnly || (Boolean(parentSession) && !allowPromptingSubagentSessions);
React.useEffect(() => {
if (autoOpenDraft && !currentSessionId && !draftOpen) {
@@ -839,7 +840,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
: 'flex-1 items-center justify-center bg-background px-0 pb-[6vh]'
)}
>
{promptReadOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={scrollToBottomOnSend} />}
{promptReadOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={scrollToBottomOnSend} />}
</div>
</div>
);
@@ -899,7 +900,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
: 'bg-background'
)}
>
{promptReadOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={scrollToBottomOnSend} />}
{promptReadOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={scrollToBottomOnSend} />}
</div>
</div>
);
@@ -934,7 +935,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
: 'bg-background'
)}
>
{promptReadOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={scrollToBottomOnSend} />}
{promptReadOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={scrollToBottomOnSend} />}
</div>
</div>
);
@@ -436,9 +436,10 @@ const writeRevealedToolIds = (messageId: string, value: Set<string>): void => {
revealedToolIdsByMessage.set(messageId, new Set(value));
};
const UserMessageBody = React.memo(({ messageId, parts, isMobile, alwaysShowActions = isMobile, hasTouchInput, hasTextContent, onCopyMessage, copiedMessage, onShowPopup, agentMention, onRevert, onFork, userActionsMode = 'inline', stickyUserHeaderEnabled = true }: {
const UserMessageBody = React.memo(({ messageId, parts, messageCreatedAt, isMobile, alwaysShowActions = isMobile, hasTouchInput, hasTextContent, onCopyMessage, copiedMessage, onShowPopup, agentMention, onRevert, onFork, userActionsMode = 'inline', stickyUserHeaderEnabled = true }: {
messageId: string;
parts: Part[];
messageCreatedAt?: number | null;
isMobile: boolean;
alwaysShowActions?: boolean;
hasTouchInput?: boolean;
@@ -452,8 +453,9 @@ const UserMessageBody = React.memo(({ messageId, parts, isMobile, alwaysShowActi
userActionsMode?: 'inline' | 'external-content' | 'external-actions';
stickyUserHeaderEnabled?: boolean;
}) => {
const { t } = useI18n();
const { locale, t } = useI18n();
const chatSurfaceMode = useChatSurfaceMode();
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
const [copyHintVisible, setCopyHintVisible] = React.useState(false);
const copyHintTimeoutRef = React.useRef<number | null>(null);
@@ -528,6 +530,12 @@ const UserMessageBody = React.memo(({ messageId, parts, isMobile, alwaysShowActi
);
const effectiveOnFork = chatSurfaceMode === 'mini-chat' ? undefined : onFork;
const timestamp = React.useMemo(() => {
void locale;
if (typeof messageCreatedAt !== 'number' || messageCreatedAt <= 0) return null;
const formatted = formatTimestampForDisplay(messageCreatedAt, timeFormatPreference);
return formatted.length > 0 ? formatted : null;
}, [locale, messageCreatedAt, timeFormatPreference]);
const actionsBlock = ((canCopyMessage && hasCopyableText) || onRevert || effectiveOnFork) && showUserActions ? (
<div className={cn(
'group/user-actions',
@@ -556,6 +564,20 @@ const UserMessageBody = React.memo(({ messageId, parts, isMobile, alwaysShowActi
: 'pointer-events-none opacity-0 transition-opacity duration-150 group-hover/message:pointer-events-auto group-hover/message:opacity-100 group-hover/user-actions:pointer-events-auto group-hover/user-actions:opacity-100 group-hover/user-shell:pointer-events-auto group-hover/user-shell:opacity-100'
)}
>
{timestamp ? (
<Tooltip>
<TooltipTrigger asChild>
<span
className="mr-1 flex items-center gap-1 text-sm tabular-nums text-muted-foreground/60"
aria-label={`Message time: ${timestamp}`}
>
<Icon name="time" className="h-3.5 w-3.5" />
<span className="message-footer__label">{timestamp}</span>
</span>
</TooltipTrigger>
<TooltipContent>{timestamp}</TooltipContent>
</Tooltip>
) : null}
{onRevert && (
<Tooltip>
<TooltipTrigger asChild>
@@ -2125,6 +2147,7 @@ const MessageBody = React.memo(({ isUser, ...props }: MessageBodyProps) => {
<UserMessageBody
messageId={props.messageId}
parts={props.parts}
messageCreatedAt={props.messageCreatedAt}
isMobile={props.isMobile}
alwaysShowActions={props.alwaysShowActions}
hasTouchInput={props.hasTouchInput}