feat(chat): allow prompting subagent sessions
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -145,7 +145,7 @@ const VisualSectionContent: React.FC = () => {
|
||||
|
||||
// Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Follow-up behavior, Persist draft
|
||||
const ChatSectionContent: React.FC = () => {
|
||||
return <OpenChamberVisualSettings visibleSettings={['sessionGoal', 'sessionAssist', 'chatRenderMode', 'messageTransport', 'activityRenderMode', 'userMessageRendering', 'mermaidRendering', 'reasoning', 'showToolFileIcons', 'showTurnChangedFiles', 'expandedTools', 'collapsibleUserMessages', 'stickyUserHeader', 'wideChatLayout', 'codeBlockLineWrap', 'splitAssistantMessageActions', 'diffLayout', 'dotfiles', 'fileViewerPreview', 'followUpBehavior', 'persistDraft', 'inputSpellcheck']} />;
|
||||
return <OpenChamberVisualSettings visibleSettings={['sessionGoal', 'sessionAssist', 'chatRenderMode', 'messageTransport', 'activityRenderMode', 'userMessageRendering', 'mermaidRendering', 'reasoning', 'showToolFileIcons', 'showTurnChangedFiles', 'expandedTools', 'collapsibleUserMessages', 'stickyUserHeader', 'wideChatLayout', 'codeBlockLineWrap', 'splitAssistantMessageActions', 'subagentReadOnlyBanner', 'diffLayout', 'dotfiles', 'fileViewerPreview', 'followUpBehavior', 'persistDraft', 'inputSpellcheck']} />;
|
||||
};
|
||||
|
||||
// Sessions section: Default model & agent, Session retention
|
||||
|
||||
@@ -245,7 +245,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain'
|
||||
return mode === 'markdown' ? 'markdown' : 'plain';
|
||||
};
|
||||
|
||||
type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'editorFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'wideChatLayout' | 'codeBlockLineWrap' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'expandedEditorToolbar';
|
||||
type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'editorFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'wideChatLayout' | 'codeBlockLineWrap' | 'splitAssistantMessageActions' | 'subagentReadOnlyBanner' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'expandedEditorToolbar';
|
||||
|
||||
interface OpenChamberVisualSettingsProps {
|
||||
/** Which settings to show. If undefined, shows all. */
|
||||
@@ -333,6 +333,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
const setWeekStartPreference = useUIStore(state => state.setWeekStartPreference);
|
||||
const showSplitAssistantMessageActions = useUIStore(state => state.showSplitAssistantMessageActions);
|
||||
const setShowSplitAssistantMessageActions = useUIStore(state => state.setShowSplitAssistantMessageActions);
|
||||
const allowPromptingSubagentSessions = useUIStore(state => state.allowPromptingSubagentSessions);
|
||||
const setAllowPromptingSubagentSessions = useUIStore(state => state.setAllowPromptingSubagentSessions);
|
||||
const messageStreamTransport = useConfigStore((state) => state.settingsMessageStreamTransport);
|
||||
const setMessageStreamTransport = useConfigStore((state) => state.setSettingsMessageStreamTransport);
|
||||
const effectiveMessageStreamTransport = messageStreamTransport;
|
||||
@@ -1894,7 +1896,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(shouldShow('sessionAssist') || shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('codeBlockLineWrap') || shouldShow('splitAssistantMessageActions') || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || shouldShow('showTurnChangedFiles') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
|
||||
{(shouldShow('sessionAssist') || shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('codeBlockLineWrap') || shouldShow('splitAssistantMessageActions') || shouldShow('subagentReadOnlyBanner') || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || shouldShow('showTurnChangedFiles') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
|
||||
<section className="p-2 space-y-0.5">
|
||||
{shouldShow('sessionAssist') && (
|
||||
<>
|
||||
@@ -2095,6 +2097,30 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldShow('subagentReadOnlyBanner') && (
|
||||
<div
|
||||
data-settings-item="chat.subagent-read-only-banner"
|
||||
className="group flex cursor-pointer items-center gap-2 py-0.5"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={allowPromptingSubagentSessions}
|
||||
onClick={() => setAllowPromptingSubagentSessions(!allowPromptingSubagentSessions)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
setAllowPromptingSubagentSessions(!allowPromptingSubagentSessions);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={allowPromptingSubagentSessions}
|
||||
onChange={setAllowPromptingSubagentSessions}
|
||||
ariaLabel={t('settings.openchamber.visual.field.allowPromptingSubagentSessionsAria')}
|
||||
/>
|
||||
<span className="typography-ui-label text-foreground">{t('settings.openchamber.visual.field.allowPromptingSubagentSessions')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldShow('codeBlockLineWrap') && (
|
||||
<div
|
||||
data-settings-item="chat.code-block-line-wrap"
|
||||
|
||||
Reference in New Issue
Block a user