From 7e12a0dcef01484c704d7c428cf38447598a1d0f Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Mon, 13 Jul 2026 01:06:44 +0300 Subject: [PATCH] feat(chat): allow prompting subagent sessions --- .../ui/src/components/chat/ChatContainer.tsx | 9 +++--- .../components/chat/message/MessageBody.tsx | 27 +++++++++++++++-- .../sections/openchamber/OpenChamberPage.tsx | 2 +- .../openchamber/OpenChamberVisualSettings.tsx | 30 +++++++++++++++++-- .../ui/src/lib/i18n/messages/en.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/es.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/fr.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/ja.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/ko.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/pl.settings.ts | 2 ++ .../src/lib/i18n/messages/pt-BR.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/uk.settings.ts | 2 ++ .../src/lib/i18n/messages/zh-CN.settings.ts | 2 ++ .../src/lib/i18n/messages/zh-TW.settings.ts | 2 ++ packages/ui/src/lib/settings/search.ts | 6 ++++ packages/ui/src/stores/useUIStore.ts | 7 +++++ 16 files changed, 92 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/components/chat/ChatContainer.tsx b/packages/ui/src/components/chat/ChatContainer.tsx index ea3f4964..35a28c07 100644 --- a/packages/ui/src/components/chat/ChatContainer.tsx +++ b/packages/ui/src/components/chat/ChatContainer.tsx @@ -404,6 +404,7 @@ export const ChatContainer: React.FC = ({ 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 = ({ autoOpenDraft = tr {t('chat.container.returnToParent.label')} ) : 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 = ({ autoOpenDraft = tr : 'flex-1 items-center justify-center bg-background px-0 pb-[6vh]' )} > - {promptReadOnly ? : } + {promptReadOnly ? : } ); @@ -899,7 +900,7 @@ export const ChatContainer: React.FC = ({ autoOpenDraft = tr : 'bg-background' )} > - {promptReadOnly ? : } + {promptReadOnly ? : } ); @@ -934,7 +935,7 @@ export const ChatContainer: React.FC = ({ autoOpenDraft = tr : 'bg-background' )} > - {promptReadOnly ? : } + {promptReadOnly ? : } ); diff --git a/packages/ui/src/components/chat/message/MessageBody.tsx b/packages/ui/src/components/chat/message/MessageBody.tsx index 6ff02043..8bf31eee 100644 --- a/packages/ui/src/components/chat/message/MessageBody.tsx +++ b/packages/ui/src/components/chat/message/MessageBody.tsx @@ -436,9 +436,10 @@ const writeRevealedToolIds = (messageId: string, value: Set): 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(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 ? (
+ {timestamp ? ( + + + + + {timestamp} + + + {timestamp} + + ) : null} {onRevert && ( @@ -2125,6 +2147,7 @@ const MessageBody = React.memo(({ isUser, ...props }: MessageBodyProps) => { { // Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Follow-up behavior, Persist draft const ChatSectionContent: React.FC = () => { - return ; + return ; }; // Sessions section: Default model & agent, Session retention diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx index 4c70c252..f0436697 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx @@ -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 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
)} - {(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')) && (
{shouldShow('sessionAssist') && ( <> @@ -2095,6 +2097,30 @@ export const OpenChamberVisualSettings: React.FC )} + {shouldShow('subagentReadOnlyBanner') && ( +
setAllowPromptingSubagentSessions(!allowPromptingSubagentSessions)} + onKeyDown={(event) => { + if (event.key === ' ' || event.key === 'Enter') { + event.preventDefault(); + setAllowPromptingSubagentSessions(!allowPromptingSubagentSessions); + } + }} + > + + {t('settings.openchamber.visual.field.allowPromptingSubagentSessions')} +
+ )} + {shouldShow('codeBlockLineWrap') && (
void; setExpandedEditorToolbar: (value: boolean) => void; setShowSplitAssistantMessageActions: (value: boolean) => void; + setAllowPromptingSubagentSessions: (value: boolean) => void; setIsMobileSessionStatusBarCollapsed: (value: boolean) => void; setMobileSessionPanelOpen: (value: boolean) => void; setMobileSessionFilterProjectId: (value: string | null) => void; @@ -954,6 +956,7 @@ export const useUIStore = create()( stickyUserHeader: false, expandedEditorToolbar: false, showSplitAssistantMessageActions: false, + allowPromptingSubagentSessions: false, isMobileSessionStatusBarCollapsed: false, mobileSessionPanelOpen: false, mobileSessionFilterProjectId: null, @@ -2115,6 +2118,9 @@ export const useUIStore = create()( setShowSplitAssistantMessageActions: (value) => { set({ showSplitAssistantMessageActions: value }); }, + setAllowPromptingSubagentSessions: (value) => { + set({ allowPromptingSubagentSessions: value }); + }, setIsMobileSessionStatusBarCollapsed: (value) => { set({ isMobileSessionStatusBarCollapsed: value }); }, @@ -2357,6 +2363,7 @@ export const useUIStore = create()( stickyUserHeader: state.stickyUserHeader, expandedEditorToolbar: state.expandedEditorToolbar, showSplitAssistantMessageActions: state.showSplitAssistantMessageActions, + allowPromptingSubagentSessions: state.allowPromptingSubagentSessions, isMobileSessionStatusBarCollapsed: state.isMobileSessionStatusBarCollapsed, mobileSessionFilterProjectId: state.mobileSessionFilterProjectId, shortcutOverrides: state.shortcutOverrides,