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:
+53
-21
@@ -31,7 +31,8 @@ import { useSessionUIStore } from '@/sync/session-ui-store';
|
|||||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||||
import { opencodeClient } from '@/lib/opencode/client';
|
import { opencodeClient } from '@/lib/opencode/client';
|
||||||
import { SyncProvider, useSessions } from '@/sync/sync-context';
|
import { SyncProvider } from '@/sync/sync-context';
|
||||||
|
import { useSync } from '@/sync/use-sync';
|
||||||
import { ConfigUpdateOverlay } from '@/components/ui/ConfigUpdateOverlay';
|
import { ConfigUpdateOverlay } from '@/components/ui/ConfigUpdateOverlay';
|
||||||
import { AboutDialog } from '@/components/ui/AboutDialog';
|
import { AboutDialog } from '@/components/ui/AboutDialog';
|
||||||
import { RuntimeAPIProvider } from '@/contexts/RuntimeAPIProvider';
|
import { RuntimeAPIProvider } from '@/contexts/RuntimeAPIProvider';
|
||||||
@@ -95,12 +96,18 @@ type AppProps = {
|
|||||||
type EmbeddedSessionChatConfig = {
|
type EmbeddedSessionChatConfig = {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
directory: string | null;
|
directory: string | null;
|
||||||
|
readOnly: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmbeddedVisibilityPayload = {
|
type EmbeddedVisibilityPayload = {
|
||||||
visible?: unknown;
|
visible?: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeEmbeddedDirectory = (value: string | null | undefined): string => {
|
||||||
|
if (!value) return '';
|
||||||
|
return value.replace(/\\/g, '/').replace(/\/+$/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
const readEmbeddedSessionChatConfig = (): EmbeddedSessionChatConfig | null => {
|
const readEmbeddedSessionChatConfig = (): EmbeddedSessionChatConfig | null => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return null;
|
return null;
|
||||||
@@ -125,6 +132,7 @@ const readEmbeddedSessionChatConfig = (): EmbeddedSessionChatConfig | null => {
|
|||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
directory,
|
directory,
|
||||||
|
readOnly: params.get('readOnly') === '1' || params.get('readOnly') === 'true',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -136,31 +144,55 @@ const isMcpOAuthCallbackPath = (): boolean => {
|
|||||||
return window.location.pathname === MCP_OAUTH_CALLBACK_PATH;
|
return window.location.pathname === MCP_OAUTH_CALLBACK_PATH;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmbeddedSessionSelectionGate: React.FC<{
|
const EmbeddedSessionChatContent: React.FC<{
|
||||||
embeddedSessionChat: EmbeddedSessionChatConfig | null;
|
embeddedSessionChat: EmbeddedSessionChatConfig;
|
||||||
isVSCodeRuntime: boolean;
|
isVSCodeRuntime: boolean;
|
||||||
}> = ({ embeddedSessionChat, isVSCodeRuntime }) => {
|
embeddedBackgroundWorkEnabled: boolean;
|
||||||
const sessions = useSessions();
|
}> = ({ embeddedSessionChat, isVSCodeRuntime, embeddedBackgroundWorkEnabled }) => {
|
||||||
|
const currentDirectory = useDirectoryStore((state) => state.currentDirectory);
|
||||||
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||||
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
|
||||||
|
const sync = useSync();
|
||||||
|
const bootstrapKeyRef = React.useRef<string | null>(null);
|
||||||
|
|
||||||
|
const expectedDirectory = normalizeEmbeddedDirectory(embeddedSessionChat.directory);
|
||||||
|
const activeDirectory = normalizeEmbeddedDirectory(currentDirectory);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!embeddedSessionChat || isVSCodeRuntime) {
|
if (isVSCodeRuntime) return;
|
||||||
|
if (expectedDirectory && activeDirectory !== expectedDirectory) return;
|
||||||
|
|
||||||
|
const bootstrapKey = `${expectedDirectory}\n${embeddedSessionChat.sessionId}`;
|
||||||
|
if (bootstrapKeyRef.current === bootstrapKey && currentSessionId === embeddedSessionChat.sessionId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSessionId === embeddedSessionChat.sessionId) {
|
bootstrapKeyRef.current = bootstrapKey;
|
||||||
return;
|
setCurrentSession(embeddedSessionChat.sessionId, embeddedSessionChat.directory);
|
||||||
}
|
void sync.ensureSessionRenderable(embeddedSessionChat.sessionId, true);
|
||||||
|
}, [
|
||||||
if (!sessions.some((session) => session.id === embeddedSessionChat.sessionId)) {
|
activeDirectory,
|
||||||
return;
|
currentSessionId,
|
||||||
}
|
embeddedSessionChat.directory,
|
||||||
|
embeddedSessionChat.sessionId,
|
||||||
void setCurrentSession(embeddedSessionChat.sessionId);
|
expectedDirectory,
|
||||||
}, [currentSessionId, embeddedSessionChat, isVSCodeRuntime, sessions, setCurrentSession]);
|
isVSCodeRuntime,
|
||||||
|
setCurrentSession,
|
||||||
|
sync,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (expectedDirectory && activeDirectory !== expectedDirectory) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SyncAppEffects embeddedBackgroundWorkEnabled={embeddedBackgroundWorkEnabled} />
|
||||||
|
<OpenCodeUpdateToast />
|
||||||
|
<ChatView readOnly={embeddedSessionChat.readOnly} />
|
||||||
|
<Toaster />
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function App({ apis }: AppProps) {
|
function App({ apis }: AppProps) {
|
||||||
@@ -780,11 +812,11 @@ function App({ apis }: AppProps) {
|
|||||||
<RuntimeAPIProvider apis={apis}>
|
<RuntimeAPIProvider apis={apis}>
|
||||||
<TooltipProvider delayDuration={300} skipDelayDuration={150}>
|
<TooltipProvider delayDuration={300} skipDelayDuration={150}>
|
||||||
<div className="h-full text-foreground bg-background">
|
<div className="h-full text-foreground bg-background">
|
||||||
<EmbeddedSessionSelectionGate embeddedSessionChat={embeddedSessionChat} isVSCodeRuntime={isVSCodeRuntime} />
|
<EmbeddedSessionChatContent
|
||||||
<SyncAppEffects embeddedBackgroundWorkEnabled={embeddedBackgroundWorkEnabled} />
|
embeddedSessionChat={embeddedSessionChat}
|
||||||
<OpenCodeUpdateToast />
|
isVSCodeRuntime={isVSCodeRuntime}
|
||||||
<ChatView />
|
embeddedBackgroundWorkEnabled={embeddedBackgroundWorkEnabled}
|
||||||
<Toaster />
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</RuntimeAPIProvider>
|
</RuntimeAPIProvider>
|
||||||
|
|||||||
@@ -315,11 +315,24 @@ const HYDRATING_SKELETON_ITEMS: Array<{
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
type ChatContainerProps = {
|
const ReadOnlyPromptBanner: React.FC = () => {
|
||||||
autoOpenDraft?: boolean;
|
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();
|
const { t } = useI18n();
|
||||||
// Session UI state
|
// Session UI state
|
||||||
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
||||||
@@ -751,7 +764,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
|
|||||||
: 'bg-background'
|
: 'bg-background'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ChatInput scrollToBottom={resumeToLatestInstant} />
|
{readOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={resumeToLatestInstant} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -811,7 +824,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
|
|||||||
: 'bg-background'
|
: 'bg-background'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ChatInput scrollToBottom={resumeToLatestInstant} />
|
{readOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={resumeToLatestInstant} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -844,7 +857,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
|
|||||||
: 'bg-background'
|
: 'bg-background'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ChatInput scrollToBottom={resumeToLatestInstant} />
|
{readOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={resumeToLatestInstant} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -892,7 +905,7 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
|
|||||||
onClick={navigation.resumeToLatest}
|
onClick={navigation.resumeToLatest}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ChatInput scrollToBottom={resumeToLatestInstant} />
|
{readOnly ? <ReadOnlyPromptBanner /> : <ChatInput scrollToBottom={resumeToLatestInstant} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TimelineDialog
|
<TimelineDialog
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ const normalizeSubtaskModel = (model: SubtaskPartLike['model']): string | null =
|
|||||||
|
|
||||||
const UserSubtaskPart: React.FC<{ part: SubtaskPartLike }> = ({ part }) => {
|
const UserSubtaskPart: React.FC<{ part: SubtaskPartLike }> = ({ part }) => {
|
||||||
const [expanded, setExpanded] = React.useState(false);
|
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 { t } = useI18n();
|
||||||
|
|
||||||
const description = typeof part.description === 'string' ? part.description.trim() : '';
|
const description = typeof part.description === 'string' ? part.description.trim() : '';
|
||||||
@@ -151,7 +152,13 @@ const UserSubtaskPart: React.FC<{ part: SubtaskPartLike }> = ({ part }) => {
|
|||||||
type="button"
|
type="button"
|
||||||
className="typography-meta text-muted-foreground hover:text-foreground transition-colors underline underline-offset-2"
|
className="typography-meta text-muted-foreground hover:text-foreground transition-colors underline underline-offset-2"
|
||||||
onClick={() => {
|
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')}
|
{t('chat.messageBody.subtask.openSession')}
|
||||||
|
|||||||
@@ -1080,7 +1080,8 @@ const TaskToolSummary: React.FC<{
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}> = ({ entries, isExpanded, isMobile, output, sessionId, onShowPopup, input, animateTailText = true, isActive = false }) => {
|
}> = ({ entries, isExpanded, isMobile, output, sessionId, onShowPopup, input, animateTailText = true, isActive = false }) => {
|
||||||
const { t } = useI18n();
|
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 showToolFileIcons = useUIStore((state) => state.showToolFileIcons);
|
||||||
const displayEntries = entries;
|
const displayEntries = entries;
|
||||||
|
|
||||||
@@ -1092,8 +1093,13 @@ const TaskToolSummary: React.FC<{
|
|||||||
|
|
||||||
const handleOpenSession = (event: React.MouseEvent) => {
|
const handleOpenSession = (event: React.MouseEvent) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (sessionId) {
|
if (sessionId && currentDirectory) {
|
||||||
setCurrentSession(sessionId);
|
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') {
|
if (typeof window === 'undefined') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -494,6 +494,11 @@ const buildEmbeddedSessionChatURL = (sessionID: string, directory: string | null
|
|||||||
const url = new URL(window.location.pathname, window.location.origin);
|
const url = new URL(window.location.pathname, window.location.origin);
|
||||||
url.searchParams.set('ocPanel', 'session-chat');
|
url.searchParams.set('ocPanel', 'session-chat');
|
||||||
url.searchParams.set('sessionId', sessionID);
|
url.searchParams.set('sessionId', sessionID);
|
||||||
|
if (readOnly) {
|
||||||
|
url.searchParams.set('readOnly', '1');
|
||||||
|
} else {
|
||||||
|
url.searchParams.delete('readOnly');
|
||||||
|
}
|
||||||
if (directory && directory.trim().length > 0) {
|
if (directory && directory.trim().length > 0) {
|
||||||
url.searchParams.set('directory', directory);
|
url.searchParams.set('directory', directory);
|
||||||
} else {
|
} else {
|
||||||
@@ -1937,7 +1942,7 @@ export const ContextPanel: React.FC = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const src = buildEmbeddedSessionChatURL(sessionID, directoryKey || null);
|
const src = buildEmbeddedSessionChatURL(sessionID, directoryKey || null, tab.readOnly);
|
||||||
if (!src) {
|
if (!src) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ type Props = {
|
|||||||
removeSessionFromFolder: (scopeKey: string, sessionId: string) => void;
|
removeSessionFromFolder: (scopeKey: string, sessionId: string) => void;
|
||||||
addSessionToFolder: (scopeKey: string, folderId: string, sessionId: string) => void;
|
addSessionToFolder: (scopeKey: string, folderId: string, sessionId: string) => void;
|
||||||
createFolderAndStartRename: (scopeKey: string, parentId?: string | null) => { id: string } | null;
|
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;
|
handleDeleteSession: (session: Session, source?: { archivedBucket?: boolean }) => void;
|
||||||
mobileVariant: boolean;
|
mobileVariant: boolean;
|
||||||
alwaysShowActions: boolean;
|
alwaysShowActions: boolean;
|
||||||
|
|||||||
@@ -3,12 +3,16 @@ import { ChatContainer } from '@/components/chat/ChatContainer';
|
|||||||
import { ChatErrorBoundary } from '@/components/chat/ChatErrorBoundary';
|
import { ChatErrorBoundary } from '@/components/chat/ChatErrorBoundary';
|
||||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
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);
|
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatErrorBoundary sessionId={currentSessionId || undefined}>
|
<ChatErrorBoundary sessionId={currentSessionId || undefined}>
|
||||||
<ChatContainer />
|
<ChatContainer readOnly={readOnly} />
|
||||||
</ChatErrorBoundary>
|
</ChatErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1433,6 +1433,7 @@ export const dict = {
|
|||||||
'chat.container.returnToParent.titleNamed': 'Return to: {title}',
|
'chat.container.returnToParent.titleNamed': 'Return to: {title}',
|
||||||
'chat.container.returnToParent.title': 'Return to parent session',
|
'chat.container.returnToParent.title': 'Return to parent session',
|
||||||
'chat.container.returnToParent.label': 'Parent',
|
'chat.container.returnToParent.label': 'Parent',
|
||||||
|
'chat.container.readOnlySubagentPromptBanner': 'Subagent sessions cannot be prompted.',
|
||||||
'chat.unifiedControls.title': 'Controls',
|
'chat.unifiedControls.title': 'Controls',
|
||||||
'chat.unifiedControls.model.title': 'Model',
|
'chat.unifiedControls.model.title': 'Model',
|
||||||
'chat.unifiedControls.model.noRecent': 'No recent models',
|
'chat.unifiedControls.model.noRecent': 'No recent models',
|
||||||
|
|||||||
@@ -1399,6 +1399,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"chat.container.returnToParent.titleNamed": "Volver a: {title}",
|
"chat.container.returnToParent.titleNamed": "Volver a: {title}",
|
||||||
"chat.container.returnToParent.title": "Volver a la sesión principal",
|
"chat.container.returnToParent.title": "Volver a la sesión principal",
|
||||||
"chat.container.returnToParent.label": "Principal",
|
"chat.container.returnToParent.label": "Principal",
|
||||||
|
"chat.container.readOnlySubagentPromptBanner": "Las sesiones de subagentes no pueden recibir prompts.",
|
||||||
"chat.unifiedControls.title": "Controles",
|
"chat.unifiedControls.title": "Controles",
|
||||||
"chat.unifiedControls.model.title": "Modelo",
|
"chat.unifiedControls.model.title": "Modelo",
|
||||||
"chat.unifiedControls.model.noRecent": "No hay modelos recientes",
|
"chat.unifiedControls.model.noRecent": "No hay modelos recientes",
|
||||||
|
|||||||
@@ -1435,6 +1435,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'chat.container.returnToParent.titleNamed': '돌아가기: {title}',
|
'chat.container.returnToParent.titleNamed': '돌아가기: {title}',
|
||||||
'chat.container.returnToParent.title': '상위 세션으로 돌아가기',
|
'chat.container.returnToParent.title': '상위 세션으로 돌아가기',
|
||||||
'chat.container.returnToParent.label': '상위',
|
'chat.container.returnToParent.label': '상위',
|
||||||
|
'chat.container.readOnlySubagentPromptBanner': '하위 에이전트 세션에는 프롬프트를 보낼 수 없습니다.',
|
||||||
'chat.unifiedControls.title': '컨트롤',
|
'chat.unifiedControls.title': '컨트롤',
|
||||||
'chat.unifiedControls.model.title': '모델',
|
'chat.unifiedControls.model.title': '모델',
|
||||||
'chat.unifiedControls.model.noRecent': '최근 모델 없음',
|
'chat.unifiedControls.model.noRecent': '최근 모델 없음',
|
||||||
|
|||||||
@@ -489,6 +489,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'chat.container.returnToParent.titleNamed': 'Powrót do: {title}',
|
'chat.container.returnToParent.titleNamed': 'Powrót do: {title}',
|
||||||
'chat.container.returnToParent.title': 'Powrót do sesji nadrzędnej',
|
'chat.container.returnToParent.title': 'Powrót do sesji nadrzędnej',
|
||||||
'chat.container.returnToParent.label': 'Nadrzędna',
|
'chat.container.returnToParent.label': 'Nadrzędna',
|
||||||
|
'chat.container.readOnlySubagentPromptBanner': 'Sesje podagentów nie mogą otrzymywać promptów.',
|
||||||
'chat.unifiedControls.title': 'Kontrolki',
|
'chat.unifiedControls.title': 'Kontrolki',
|
||||||
'chat.unifiedControls.model.title': 'Model',
|
'chat.unifiedControls.model.title': 'Model',
|
||||||
'chat.unifiedControls.model.noRecent': 'Brak ostatnich modeli',
|
'chat.unifiedControls.model.noRecent': 'Brak ostatnich modeli',
|
||||||
|
|||||||
@@ -1399,6 +1399,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"chat.container.returnToParent.titleNamed": "Voltar para: {title}",
|
"chat.container.returnToParent.titleNamed": "Voltar para: {title}",
|
||||||
"chat.container.returnToParent.title": "Voltar para a sessão principal",
|
"chat.container.returnToParent.title": "Voltar para a sessão principal",
|
||||||
"chat.container.returnToParent.label": "Principal",
|
"chat.container.returnToParent.label": "Principal",
|
||||||
|
"chat.container.readOnlySubagentPromptBanner": "Sessões de subagente não podem receber prompts.",
|
||||||
"chat.unifiedControls.title": "Controles",
|
"chat.unifiedControls.title": "Controles",
|
||||||
"chat.unifiedControls.model.title": "Modelo",
|
"chat.unifiedControls.model.title": "Modelo",
|
||||||
"chat.unifiedControls.model.noRecent": "Não há modelos recentes",
|
"chat.unifiedControls.model.noRecent": "Não há modelos recentes",
|
||||||
|
|||||||
@@ -1399,6 +1399,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"chat.container.returnToParent.titleNamed": "Повернутися до: {title}",
|
"chat.container.returnToParent.titleNamed": "Повернутися до: {title}",
|
||||||
"chat.container.returnToParent.title": "Повернутися до батьківської сесії",
|
"chat.container.returnToParent.title": "Повернутися до батьківської сесії",
|
||||||
"chat.container.returnToParent.label": "Батьківська",
|
"chat.container.returnToParent.label": "Батьківська",
|
||||||
|
"chat.container.readOnlySubagentPromptBanner": "Сесії субагентів не можна запитувати.",
|
||||||
"chat.unifiedControls.title": "Елементи керування",
|
"chat.unifiedControls.title": "Елементи керування",
|
||||||
"chat.unifiedControls.model.title": "Модель",
|
"chat.unifiedControls.model.title": "Модель",
|
||||||
"chat.unifiedControls.model.noRecent": "Немає останніх моделей",
|
"chat.unifiedControls.model.noRecent": "Немає останніх моделей",
|
||||||
|
|||||||
@@ -1399,6 +1399,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'chat.container.returnToParent.titleNamed': '返回到:{title}',
|
'chat.container.returnToParent.titleNamed': '返回到:{title}',
|
||||||
'chat.container.returnToParent.title': '返回父会话',
|
'chat.container.returnToParent.title': '返回父会话',
|
||||||
'chat.container.returnToParent.label': '父级',
|
'chat.container.returnToParent.label': '父级',
|
||||||
|
'chat.container.readOnlySubagentPromptBanner': '无法向子代理会话发送提示。',
|
||||||
'chat.unifiedControls.title': '控制',
|
'chat.unifiedControls.title': '控制',
|
||||||
'chat.unifiedControls.model.title': '模型',
|
'chat.unifiedControls.model.title': '模型',
|
||||||
'chat.unifiedControls.model.noRecent': '没有最近使用的模型',
|
'chat.unifiedControls.model.noRecent': '没有最近使用的模型',
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type ContextPanelTab = {
|
|||||||
targetPath: string | null;
|
targetPath: string | null;
|
||||||
dedupeKey: string;
|
dedupeKey: string;
|
||||||
label: string | null;
|
label: string | null;
|
||||||
|
readOnly: boolean;
|
||||||
touchedAt: number;
|
touchedAt: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ type ContextPanelTabDescriptor = {
|
|||||||
targetPath?: string | null;
|
targetPath?: string | null;
|
||||||
dedupeKey?: string | null;
|
dedupeKey?: string | null;
|
||||||
label?: string | null;
|
label?: string | null;
|
||||||
|
readOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ContextPanelDirectoryState = {
|
type ContextPanelDirectoryState = {
|
||||||
@@ -201,6 +203,7 @@ const createContextPanelTab = (descriptor: ContextPanelTabDescriptor): ContextPa
|
|||||||
targetPath: normalizedTargetPath,
|
targetPath: normalizedTargetPath,
|
||||||
dedupeKey,
|
dedupeKey,
|
||||||
label: normalizeContextTabLabel(descriptor.label),
|
label: normalizeContextTabLabel(descriptor.label),
|
||||||
|
readOnly: descriptor.readOnly === true,
|
||||||
touchedAt: Date.now(),
|
touchedAt: Date.now(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -239,6 +242,7 @@ const sanitizeContextPanelTabs = (tabs: unknown): ContextPanelTab[] => {
|
|||||||
targetPath?: unknown;
|
targetPath?: unknown;
|
||||||
dedupeKey?: unknown;
|
dedupeKey?: unknown;
|
||||||
label?: unknown;
|
label?: unknown;
|
||||||
|
readOnly?: unknown;
|
||||||
touchedAt?: unknown;
|
touchedAt?: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -264,6 +268,7 @@ const sanitizeContextPanelTabs = (tabs: unknown): ContextPanelTab[] => {
|
|||||||
targetPath,
|
targetPath,
|
||||||
dedupeKey,
|
dedupeKey,
|
||||||
label: normalizeContextTabLabel(typeof candidate.label === 'string' ? candidate.label : null),
|
label: normalizeContextTabLabel(typeof candidate.label === 'string' ? candidate.label : null),
|
||||||
|
readOnly: candidate.readOnly === true,
|
||||||
touchedAt: typeof candidate.touchedAt === 'number' && Number.isFinite(candidate.touchedAt)
|
touchedAt: typeof candidate.touchedAt === 'number' && Number.isFinite(candidate.touchedAt)
|
||||||
? candidate.touchedAt
|
? candidate.touchedAt
|
||||||
: Date.now(),
|
: Date.now(),
|
||||||
|
|||||||
Reference in New Issue
Block a user