feat: open subagent sessions read-only in context panel
Adds read-only embedded chat mode without hiding permission prompts Opens subagent sessions in the context panel instead of replacing the main chat Fixes context panel message loading for embedded sessions
This commit is contained in:
@@ -315,11 +315,24 @@ const HYDRATING_SKELETON_ITEMS: Array<{
|
||||
},
|
||||
];
|
||||
|
||||
type ChatContainerProps = {
|
||||
autoOpenDraft?: boolean;
|
||||
const ReadOnlyPromptBanner: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<div className="p-3">
|
||||
<div className="rounded-2xl border border-border/70 bg-[var(--surface-background)] px-4 py-3 typography-ui-label text-muted-foreground">
|
||||
{t('chat.container.readOnlySubagentPromptBanner')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = true }) => {
|
||||
type ChatContainerProps = {
|
||||
autoOpenDraft?: boolean;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = true, readOnly = false }) => {
|
||||
const { t } = useI18n();
|
||||
// Session UI state
|
||||
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
||||
@@ -751,7 +764,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
|
||||
: 'bg-background'
|
||||
)}
|
||||
>
|
||||
<ChatInput scrollToBottom={resumeToLatestInstant} />
|
||||
{readOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={resumeToLatestInstant} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -811,7 +824,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
|
||||
: 'bg-background'
|
||||
)}
|
||||
>
|
||||
<ChatInput scrollToBottom={resumeToLatestInstant} />
|
||||
{readOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={resumeToLatestInstant} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -844,7 +857,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
|
||||
: 'bg-background'
|
||||
)}
|
||||
>
|
||||
<ChatInput scrollToBottom={resumeToLatestInstant} />
|
||||
{readOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={resumeToLatestInstant} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -892,7 +905,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
|
||||
onClick={navigation.resumeToLatest}
|
||||
/>
|
||||
)}
|
||||
<ChatInput scrollToBottom={resumeToLatestInstant} />
|
||||
{readOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={resumeToLatestInstant} />}
|
||||
</div>
|
||||
|
||||
<TimelineDialog
|
||||
|
||||
@@ -91,7 +91,8 @@ const normalizeSubtaskModel = (model: SubtaskPartLike['model']): string | null =
|
||||
|
||||
const UserSubtaskPart: React.FC<{ part: SubtaskPartLike }> = ({ part }) => {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
||||
const effectiveDirectory = useEffectiveDirectory();
|
||||
const openContextPanelTab = useUIStore((state) => state.openContextPanelTab);
|
||||
const { t } = useI18n();
|
||||
|
||||
const description = typeof part.description === 'string' ? part.description.trim() : '';
|
||||
@@ -151,7 +152,13 @@ const UserSubtaskPart: React.FC<{ part: SubtaskPartLike }> = ({ part }) => {
|
||||
type="button"
|
||||
className="typography-meta text-muted-foreground hover:text-foreground transition-colors underline underline-offset-2"
|
||||
onClick={() => {
|
||||
void setCurrentSession(taskSessionID);
|
||||
if (!effectiveDirectory) return;
|
||||
openContextPanelTab(effectiveDirectory, {
|
||||
mode: 'chat',
|
||||
dedupeKey: `session:${taskSessionID}`,
|
||||
label: description || agent || t('contextPanel.mode.chat'),
|
||||
readOnly: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('chat.messageBody.subtask.openSession')}
|
||||
|
||||
@@ -1080,7 +1080,8 @@ const TaskToolSummary: React.FC<{
|
||||
isActive?: boolean;
|
||||
}> = ({ entries, isExpanded, isMobile, output, sessionId, onShowPopup, input, animateTailText = true, isActive = false }) => {
|
||||
const { t } = useI18n();
|
||||
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
||||
const currentDirectory = useDirectoryStore((state) => state.currentDirectory);
|
||||
const openContextPanelTab = useUIStore((state) => state.openContextPanelTab);
|
||||
const showToolFileIcons = useUIStore((state) => state.showToolFileIcons);
|
||||
const displayEntries = entries;
|
||||
|
||||
@@ -1092,8 +1093,13 @@ const TaskToolSummary: React.FC<{
|
||||
|
||||
const handleOpenSession = (event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
if (sessionId) {
|
||||
setCurrentSession(sessionId);
|
||||
if (sessionId && currentDirectory) {
|
||||
openContextPanelTab(currentDirectory, {
|
||||
mode: 'chat',
|
||||
dedupeKey: `session:${sessionId}`,
|
||||
label: agentType.charAt(0).toUpperCase() + agentType.slice(1),
|
||||
readOnly: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -486,7 +486,7 @@ const desktopAnnotationToFile = async (
|
||||
}
|
||||
};
|
||||
|
||||
const buildEmbeddedSessionChatURL = (sessionID: string, directory: string | null): string => {
|
||||
const buildEmbeddedSessionChatURL = (sessionID: string, directory: string | null, readOnly: boolean): string => {
|
||||
if (typeof window === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
@@ -494,6 +494,11 @@ const buildEmbeddedSessionChatURL = (sessionID: string, directory: string | null
|
||||
const url = new URL(window.location.pathname, window.location.origin);
|
||||
url.searchParams.set('ocPanel', 'session-chat');
|
||||
url.searchParams.set('sessionId', sessionID);
|
||||
if (readOnly) {
|
||||
url.searchParams.set('readOnly', '1');
|
||||
} else {
|
||||
url.searchParams.delete('readOnly');
|
||||
}
|
||||
if (directory && directory.trim().length > 0) {
|
||||
url.searchParams.set('directory', directory);
|
||||
} else {
|
||||
@@ -1937,7 +1942,7 @@ export const ContextPanel: React.FC = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const src = buildEmbeddedSessionChatURL(sessionID, directoryKey || null);
|
||||
const src = buildEmbeddedSessionChatURL(sessionID, directoryKey || null, tab.readOnly);
|
||||
if (!src) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ type Props = {
|
||||
removeSessionFromFolder: (scopeKey: string, sessionId: string) => void;
|
||||
addSessionToFolder: (scopeKey: string, folderId: string, sessionId: string) => void;
|
||||
createFolderAndStartRename: (scopeKey: string, parentId?: string | null) => { id: string } | null;
|
||||
openContextPanelTab: (directory: string, options: { mode: 'chat'; dedupeKey: string; label: string }) => void;
|
||||
openContextPanelTab: (directory: string, options: { mode: 'chat'; dedupeKey: string; label: string; readOnly?: boolean }) => void;
|
||||
handleDeleteSession: (session: Session, source?: { archivedBucket?: boolean }) => void;
|
||||
mobileVariant: boolean;
|
||||
alwaysShowActions: boolean;
|
||||
|
||||
@@ -3,12 +3,16 @@ import { ChatContainer } from '@/components/chat/ChatContainer';
|
||||
import { ChatErrorBoundary } from '@/components/chat/ChatErrorBoundary';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
|
||||
export const ChatView: React.FC = () => {
|
||||
type ChatViewProps = {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export const ChatView: React.FC<ChatViewProps> = ({ readOnly = false }) => {
|
||||
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||
|
||||
return (
|
||||
<ChatErrorBoundary sessionId={currentSessionId || undefined}>
|
||||
<ChatContainer />
|
||||
<ChatContainer readOnly={readOnly} />
|
||||
</ChatErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user