From 5787ea5d4988d57a9a5cfc684f0f7cbc33fc210b Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Tue, 28 Jul 2026 02:50:02 +0300 Subject: [PATCH] fix: resolve chat prompt read-only state for subagent sessions Allows prompting subagent sessions without needing the parent record Keeps subagents read-only when prompting is disabled Covers root and subagent cases with unit tests --- .../ui/src/components/chat/ChatContainer.tsx | 5 +++- .../chat/chatPromptReadOnly.test.ts | 30 +++++++++++++++++++ .../src/components/chat/chatPromptReadOnly.ts | 13 ++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 packages/ui/src/components/chat/chatPromptReadOnly.test.ts create mode 100644 packages/ui/src/components/chat/chatPromptReadOnly.ts diff --git a/packages/ui/src/components/chat/ChatContainer.tsx b/packages/ui/src/components/chat/ChatContainer.tsx index 30ff57fd..5af4cd64 100644 --- a/packages/ui/src/components/chat/ChatContainer.tsx +++ b/packages/ui/src/components/chat/ChatContainer.tsx @@ -43,6 +43,7 @@ import { useScopedBlockingPermissions, useScopedBlockingQuestions, useParentSession, + useSession, } from '@/sync/sync-context'; import { useSync } from '@/sync/use-sync'; import { usePlanDetection } from '@/hooks/usePlanDetection'; @@ -53,6 +54,7 @@ import { getEmbeddedSessionChatOriginSessionId } from '@/components/layout/conte import { isFullySyntheticMessage } from '@/lib/messages/synthetic'; import { normalizeUserDisplayParts } from './message/normalizeUserDisplayParts'; import { findShellCommandForMessage, isUserShellMarkerMessage } from './lib/shellBridge'; +import { resolveChatPromptReadOnly } from './chatPromptReadOnly'; const EMPTY_MESSAGES: Array<{ info: Message; parts: Part[] }> = []; const IDLE_SESSION_STATUS = { type: 'idle' as const }; @@ -679,6 +681,7 @@ export const ChatContainer: React.FC = ({ active = true, aut const isDesktopExpandedInput = isExpandedInput; const useCompactDraftLayout = isMobile || isVSCode || chatSurfaceMode === 'mini-chat'; const messageListRef = React.useRef(null); + const currentSession = useSession(currentSessionId, effectiveSessionDirectory); const parentSession = useParentSession(currentSessionId, effectiveSessionDirectory); // In the embedded session-chat iframe, hide "Return to parent" when @@ -712,7 +715,7 @@ export const ChatContainer: React.FC = ({ active = true, aut {t('chat.container.returnToParent.label')} ) : null; - const promptReadOnly = parentSession ? !allowPromptingSubagentSessions : readOnly; + const promptReadOnly = resolveChatPromptReadOnly(currentSession, allowPromptingSubagentSessions, readOnly); React.useEffect(() => { // VS Code/Cursor/Positron webviews delete window.parent (and window.top). diff --git a/packages/ui/src/components/chat/chatPromptReadOnly.test.ts b/packages/ui/src/components/chat/chatPromptReadOnly.test.ts new file mode 100644 index 00000000..38ccc362 --- /dev/null +++ b/packages/ui/src/components/chat/chatPromptReadOnly.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test } from 'bun:test'; +import type { Session } from '@opencode-ai/sdk/v2'; + +import { resolveChatPromptReadOnly } from './chatPromptReadOnly'; + +const session = (parentID?: string): Session => ({ + id: 'session', + slug: 'session', + title: 'Session', + version: '1', + projectID: 'project', + directory: '/repo', + parentID, + time: { created: 1, updated: 1 }, +}); + +describe('resolveChatPromptReadOnly', () => { + test('allows prompting a subagent without requiring its parent record', () => { + expect(resolveChatPromptReadOnly(session('parent'), true, true)).toBe(false); + }); + + test('keeps a subagent read-only when prompting is disabled', () => { + expect(resolveChatPromptReadOnly(session('parent'), false, false)).toBe(true); + }); + + test('preserves the surface read-only state for root sessions', () => { + expect(resolveChatPromptReadOnly(session(), true, true)).toBe(true); + expect(resolveChatPromptReadOnly(session(), true, false)).toBe(false); + }); +}); diff --git a/packages/ui/src/components/chat/chatPromptReadOnly.ts b/packages/ui/src/components/chat/chatPromptReadOnly.ts new file mode 100644 index 00000000..1fcc78af --- /dev/null +++ b/packages/ui/src/components/chat/chatPromptReadOnly.ts @@ -0,0 +1,13 @@ +import type { Session } from '@opencode-ai/sdk/v2'; + +export const resolveChatPromptReadOnly = ( + session: Session | null | undefined, + allowPromptingSubagentSessions: boolean, + readOnly: boolean, +): boolean => { + if (session?.parentID) { + return !allowPromptingSubagentSessions; + } + + return readOnly; +};